Watch
1
0
Fork
You've already forked souveraine
0

shell: intent wins the settle window, and a swap is a request for a keyboard

Two gaps in the owner-change handling, both found on device.

The push at adopt time can lose a race. osk-switch sleeps 0.3s before
its deliberate SetVisible(true), and stevia self-shows once it has
finished coming up - both land after the shell has spoken. Measured:
adopt+hide 07:37:51, Visible=true 07:37:52, leaving oskOpen=false with a
keyboard on the screen. Re-assert when the settle window closes so the
last speaker is not the winner.

And the oskSwap gesture never recorded that a swap means the user wants
a keyboard, so osk-switch's re-show and the shell's stale false were
racing with nothing to arbitrate them. Declare the intent first.
This commit is contained in:
Fimeg 2026-08-05 07:41:02 -04:00
commit 3aea8398a9
2 changed files with 29 additions and 4 deletions

View file

@ -140,7 +140,23 @@ Scope {
Timer {
id: ownerSettleTimer
interval: root.ownerSettleMs
onTriggered: root.ownerSettling = false
onTriggered: {
root.ownerSettling = false;
// Push once more on the way out. The first push (at adopt time)
// can lose a race: osk-switch sleeps 0.3s and then deliberately
// SetVisible(true), and a keyboard that self-shows on startup
// does so after it has finished coming up both land *after* we
// spoke. Measured 2026-08-05: adopt+hide at 07:37:51, stevia
// Visible=true at 07:37:52, leaving oskOpen=false while a
// keyboard sat on the screen. Whoever spoke last during the
// settle window does not get to be the winner; intent does.
root.assertIntent();
}
}
function assertIntent(): void {
if (GlobalStates.oskOpen) root.showOsk();
else root.hideOsk();
}
function adoptOwner(owner: string): void {
@ -157,9 +173,9 @@ Scope {
+ (previous === "" ? "" : " (was " + previous + ")")
+ "; re-asserting oskOpen=" + GlobalStates.oskOpen);
// Push our intent, not theirs. A fresh keyboard claiming Visible=true
// when nobody asked for one gets closed here.
if (GlobalStates.oskOpen) root.showOsk();
else root.hideOsk();
// when nobody asked for one gets closed here and again when the
// settle window closes, in case it spoke after we did.
root.assertIntent();
}
Connections {

View file

@ -80,6 +80,15 @@ Singleton {
// feel the same as crossing the line.
"oskSwap": () => {
Haptics.confirm();
// Declare the intent before the swap, not after it. osk-switch
// deliberately re-shows the keyboard on the new daemon ("so the
// swap is visible immediately"), and OnScreenKeyboard re-asserts
// oskOpen at whoever takes the sm.puri.OSK0 name. If the shell
// did not record that a swap means "keyboard up", those two would
// fight: osk-switch shows it, the shell pushes its stale false,
// and which one the user gets is a race. A swap is a request for
// a keyboard.
GlobalStates.oskOpen = true;
Quickshell.execDetached(["osk-switch"]);
},
"screenshot": () => {