shell: an owner change is not a visibility event
sm.puri.OSK0 is a name, not a process. Boot, a crash-restart and an osk-switch swap each put a fresh keyboard behind it, and the visibility monitor mirrored the newcomer's opening claim into oskOpen as though the user had asked for a keyboard. Measured: shell up 03:03:38, self-showed at 03:03:43, nobody near the phone. Track the bus owner - gdbus already prints it and the 'Visible' filter was throwing it away - and on a change push oskOpen at the new owner instead of pulling state out of it. A process that just started has no history; the shell is the continuity of intent across the keyboard's lifetime.
This commit is contained in:
parent
bc09480375
commit
d7a04f27d5
1 changed files with 83 additions and 0 deletions
|
|
@ -105,6 +105,63 @@ Scope {
|
|||
property int maxReasserts: 5
|
||||
property int reassertCount: 0
|
||||
|
||||
// An owner change is not a visibility event.
|
||||
//
|
||||
// sm.puri.OSK0 is a *name*, not a process. Three different things put a
|
||||
// fresh process behind it: boot, a crash-restart (the keyboards exit
|
||||
// `Error reading events from display: Broken pipe` when their Wayland
|
||||
// connection is perturbed, and both units are Restart=on-failure), and
|
||||
// `osk-switch` swapping squeekboard for stevia at the user's gesture.
|
||||
// Each new process announces its own idea of Visible, and until now this
|
||||
// monitor mirrored that into oskOpen — indistinguishable from the
|
||||
// keyboard deciding to show itself. Measured on 2026-08-05: shell up at
|
||||
// 03:03:38, `[osk] squeekboard self-showed` at 03:03:43, nine seconds
|
||||
// after boot with nobody near the phone.
|
||||
//
|
||||
// A process that just started has no history. It cannot know whether the
|
||||
// user wanted a keyboard. The shell does — oskOpen is the continuity of
|
||||
// that intent across the keyboard's whole lifetime. So on an owner change
|
||||
// we PUSH intent onto the new owner instead of PULLING state out of it,
|
||||
// and we ignore its opening claim while our push is in flight.
|
||||
//
|
||||
// This is also why "the keyboard comes back on wake" needs no compositor
|
||||
// change to stop hurting: wake perturbs the display connection, the OSK
|
||||
// restarts, and it is the mirror — not the compositor and not the
|
||||
// keyboard — that turns that restart into a keyboard on the user's
|
||||
// screen.
|
||||
property string oskOwner: ""
|
||||
property int ownerSettleMs: 2000
|
||||
// NOT a binding on Date.now() — QML bindings do not re-evaluate because
|
||||
// time passed, so a `Date.now() - t < ms` property latches at creation
|
||||
// and never clears. A timer is the only honest way to express "for a
|
||||
// moment after".
|
||||
property bool ownerSettling: false
|
||||
|
||||
Timer {
|
||||
id: ownerSettleTimer
|
||||
interval: root.ownerSettleMs
|
||||
onTriggered: root.ownerSettling = false
|
||||
}
|
||||
|
||||
function adoptOwner(owner: string): void {
|
||||
if (owner === root.oskOwner) return;
|
||||
const previous = root.oskOwner;
|
||||
root.oskOwner = owner;
|
||||
root.ownerSettling = true;
|
||||
ownerSettleTimer.restart();
|
||||
if (owner === "") {
|
||||
console.log("[osk] keyboard left the bus (was " + previous + ")");
|
||||
return;
|
||||
}
|
||||
console.log("[osk] keyboard is now " + owner
|
||||
+ (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();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: GlobalStates
|
||||
function onOskHoldsChanged() {
|
||||
|
|
@ -119,8 +176,34 @@ Scope {
|
|||
"--dest", "sm.puri.OSK0", "--object-path", "/sm/puri/OSK0"]
|
||||
stdout: SplitParser {
|
||||
onRead: line => {
|
||||
// gdbus prints the owner of --dest at startup and again on
|
||||
// every NameOwnerChanged. These lines were being dropped by
|
||||
// the 'Visible' filter below; they are the evidence we need.
|
||||
// The name sm.puri.OSK0 is owned by :1.41
|
||||
// The name sm.puri.OSK0 does not have an owner
|
||||
const owned = line.match(/is owned by (\S+)/);
|
||||
if (owned) {
|
||||
root.adoptOwner(owned[1]);
|
||||
return;
|
||||
}
|
||||
if (line.includes("does not have an owner")) {
|
||||
root.adoptOwner("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!line.includes("'Visible'")) return;
|
||||
const vis = line.includes("<true>");
|
||||
|
||||
// Still inside an owner change: this is the new process
|
||||
// introducing itself, or the echo of the intent we just
|
||||
// pushed at it. Either way it is not the user speaking.
|
||||
if (root.ownerSettling) {
|
||||
if (vis !== GlobalStates.oskOpen)
|
||||
console.log("[osk] ignoring Visible=" + vis
|
||||
+ " from freshly-arrived " + root.oskOwner
|
||||
+ "; oskOpen=" + GlobalStates.oskOpen + " stands");
|
||||
return;
|
||||
}
|
||||
if (!vis && GlobalStates.oskHolds.length > 0) {
|
||||
if (root.reassertCount >= root.maxReasserts) {
|
||||
console.log("[osk] squeekboard self-hid under hold ["
|
||||
|
|
|
|||
Loading…
Reference in a new issue