osk: polkit holds the keyboard open instead of poking it once
squeekboard hides itself when input-method focus drops, so the password field was left with no keyboard. A hold re-asserts through that and releases on dismiss; a manual close drops the hold.
This commit is contained in:
parent
4f113ad62d
commit
7475fae505
3 changed files with 120 additions and 9 deletions
|
|
@ -47,6 +47,47 @@ Singleton {
|
|||
property bool osdVolumeOpen: false
|
||||
// WRITER: ii OSK module (OnScreenKeyboard.qml)
|
||||
property bool oskOpen: false
|
||||
// WRITER: OnScreenKeyboard.qml (via oskHold/oskRelease/oskDropHolds).
|
||||
// The names of surfaces that need the keyboard for as long as they are
|
||||
// up, rather than for the instant they appeared.
|
||||
//
|
||||
// Setting oskOpen once is not enough. squeekboard hides ITSELF whenever
|
||||
// input-method focus drops (the journal is full of `self-hid`), and a
|
||||
// layer-shell password prompt does not reliably hold that focus — so a
|
||||
// surface that pokes the keyboard open gets a keyboard that vanishes
|
||||
// while the field is still waiting for input. A hold survives that: the
|
||||
// keyboard is re-asserted for as long as the holder is up.
|
||||
//
|
||||
// A user close outranks every hold (oskDropHolds). The person in front
|
||||
// of the phone gets to dismiss the keyboard even mid-prompt.
|
||||
property var oskHolds: []
|
||||
// oskOpen as it stood when the first hold was taken, so releasing the
|
||||
// last hold restores rather than clobbers a keyboard the user had open.
|
||||
property bool oskHoldRestoreOpen: false
|
||||
|
||||
function oskHold(reason) {
|
||||
if (root.oskHolds.indexOf(reason) >= 0) return;
|
||||
if (root.oskHolds.length === 0)
|
||||
root.oskHoldRestoreOpen = root.oskOpen;
|
||||
root.oskHolds = root.oskHolds.concat([reason]);
|
||||
root.oskOpen = true;
|
||||
}
|
||||
|
||||
function oskRelease(reason) {
|
||||
const next = root.oskHolds.filter(r => r !== reason);
|
||||
if (next.length === root.oskHolds.length) return;
|
||||
root.oskHolds = next;
|
||||
if (next.length === 0)
|
||||
root.oskOpen = root.oskHoldRestoreOpen;
|
||||
}
|
||||
|
||||
// The user closed the keyboard by hand. Every hold drops — otherwise the
|
||||
// re-assert below would fight them key for key.
|
||||
function oskDropHolds() {
|
||||
if (root.oskHolds.length === 0) return;
|
||||
root.oskHolds = [];
|
||||
root.oskHoldRestoreOpen = false;
|
||||
}
|
||||
// WRITER: ii overlay module
|
||||
property bool overlayOpen: false
|
||||
// WRITER: ii overview module + Dock.qml IPC
|
||||
|
|
|
|||
|
|
@ -86,6 +86,21 @@ Scope {
|
|||
// squeekboard's real Visible property back into oskOpen so there is
|
||||
// exactly one truth. Setting oskOpen from here re-triggers SetVisible
|
||||
// with the value squeekboard already has, which is a harmless no-op.
|
||||
// A self-hide is squeekboard's opinion, not the user's. While a surface
|
||||
// holds the keyboard (GlobalStates.oskHolds — polkit's password field is
|
||||
// the first) that opinion is overridden and the keyboard comes straight
|
||||
// back. Bounded: after this many re-asserts within one hold we stop and
|
||||
// say so, rather than trading SetVisible calls with squeekboard forever.
|
||||
property int maxReasserts: 5
|
||||
property int reassertCount: 0
|
||||
|
||||
Connections {
|
||||
target: GlobalStates
|
||||
function onOskHoldsChanged() {
|
||||
if (GlobalStates.oskHolds.length > 0) root.reassertCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: oskVisMonitor
|
||||
running: true
|
||||
|
|
@ -95,6 +110,22 @@ Scope {
|
|||
onRead: line => {
|
||||
if (!line.includes("'Visible'")) return;
|
||||
const vis = line.includes("<true>");
|
||||
if (!vis && GlobalStates.oskHolds.length > 0) {
|
||||
if (root.reassertCount >= root.maxReasserts) {
|
||||
console.log("[osk] squeekboard self-hid under hold ["
|
||||
+ GlobalStates.oskHolds.join(",") + "] more than "
|
||||
+ root.maxReasserts + " times; giving up the hold");
|
||||
GlobalStates.oskDropHolds();
|
||||
GlobalStates.oskOpen = false;
|
||||
return;
|
||||
}
|
||||
root.reassertCount++;
|
||||
console.log("[osk] squeekboard self-hid while held by ["
|
||||
+ GlobalStates.oskHolds.join(",") + "]; re-asserting ("
|
||||
+ root.reassertCount + "/" + root.maxReasserts + ")");
|
||||
root.showOsk();
|
||||
return;
|
||||
}
|
||||
if (GlobalStates.oskOpen !== vis) {
|
||||
console.log("[osk] squeekboard self-" + (vis ? "showed" : "hid") + ", syncing oskOpen");
|
||||
GlobalStates.oskOpen = vis;
|
||||
|
|
@ -103,21 +134,35 @@ Scope {
|
|||
}
|
||||
}
|
||||
|
||||
// Every deliberate close — pill, gesture, shortcut, IPC — goes through
|
||||
// here, because closing by hand is what drops a hold.
|
||||
function userClose() {
|
||||
GlobalStates.oskDropHolds();
|
||||
GlobalStates.oskOpen = false;
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "osk"
|
||||
|
||||
function toggle(): void {
|
||||
GlobalStates.oskOpen = !GlobalStates.oskOpen;
|
||||
if (GlobalStates.oskOpen) root.userClose();
|
||||
else GlobalStates.oskOpen = true;
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
GlobalStates.oskOpen = false;
|
||||
root.userClose();
|
||||
}
|
||||
|
||||
function open(): void {
|
||||
GlobalStates.oskOpen = true;
|
||||
}
|
||||
|
||||
// Which surfaces are holding the keyboard open, if any. The pill and
|
||||
// the agent both ask "why won't this close"; this answers it.
|
||||
function holds(): string {
|
||||
return JSON.stringify(GlobalStates.oskHolds);
|
||||
}
|
||||
|
||||
// Double-tapping the pill while the keyboard is open calls this —
|
||||
// pops the (suppressed, see Dock.qml) dock back up for a few
|
||||
// seconds without having to close the keyboard first.
|
||||
|
|
@ -131,7 +176,8 @@ Scope {
|
|||
description: "Toggles on screen keyboard on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.oskOpen = !GlobalStates.oskOpen;
|
||||
if (GlobalStates.oskOpen) root.userClose();
|
||||
else GlobalStates.oskOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +195,7 @@ Scope {
|
|||
description: "Closes on screen keyboard on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.oskOpen = false;
|
||||
root.userClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,20 +10,44 @@ import Quickshell.Wayland
|
|||
// Souveraine: on a phone deploy, surface the on-screen keyboard when a polkit
|
||||
// prompt appears. The polkit window is a wlr-layer-shell Overlay and the OSK
|
||||
// is a separate layer-shell surface (squeekboard), so the OSK won't auto-rise
|
||||
// to it. Since both run under this shell, we drive the OSK explicitly: open
|
||||
// it when polkit activates, close it when it dismisses. Laptop deploys
|
||||
// (souveraine.phone == false) are unchanged — hardware keyboard handles it.
|
||||
// to it. Since both run under this shell, we drive the OSK explicitly.
|
||||
// Laptop deploys (souveraine.phone == false) are unchanged — hardware
|
||||
// keyboard handles it.
|
||||
//
|
||||
// This was a poke — `oskOpen = PolkitService.active` on the transition — and
|
||||
// a poke is not enough for two reasons, both of which left a password field
|
||||
// on screen with no way to type into it:
|
||||
//
|
||||
// 1. squeekboard hides itself when input-method focus drops, which this
|
||||
// layer-shell surface does not reliably hold. The keyboard appeared and
|
||||
// then left while the prompt was still waiting.
|
||||
// 2. The transition is all a poke sees. pkexec run before Config.ready —
|
||||
// an update on login, a boot-time authorization — has its prompt up
|
||||
// before this file is even loaded, so no transition ever arrives.
|
||||
//
|
||||
// A hold fixes both: the keyboard is re-asserted for as long as the prompt
|
||||
// is up (GlobalStates.oskHold), and onCompleted takes the hold for a prompt
|
||||
// that was already waiting.
|
||||
FullscreenPolkitWindow {
|
||||
id: root
|
||||
contentComponent: Component {
|
||||
PolkitContent {}
|
||||
}
|
||||
|
||||
readonly property bool phone: Config.options?.souveraine?.phone ?? false
|
||||
|
||||
function syncKeyboard() {
|
||||
if (!root.phone) return;
|
||||
if (PolkitService.active) GlobalStates.oskHold("polkit");
|
||||
else GlobalStates.oskRelease("polkit");
|
||||
}
|
||||
|
||||
Component.onCompleted: root.syncKeyboard()
|
||||
|
||||
Connections {
|
||||
target: PolkitService
|
||||
function onActiveChanged() {
|
||||
if (!Config.options.souveraine || !Config.options.souveraine.phone) return;
|
||||
GlobalStates.oskOpen = PolkitService.active;
|
||||
root.syncKeyboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue