Watch
1
0
Fork
You've already forked souveraine
0
souveraine/surfaces/quickshell/GlobalStates.qml
Fimeg 914e48f2d3 shell: locking closes the keyboard, and unlocking does not bring it back
The hold machinery remembers whether the keyboard was up when the first
hold was taken, so releasing the last hold restores rather than clobbers
it. Across a lock that restore is wrong: the keyboard was up because of
something being typed before the phone went down, and the first thing
you see on unlocking is a keyboard sitting over whatever you came back
for.

Intent to type does not survive the screen going away. Anything that
still wants the keyboard — a field taking focus again — asks for it, on
the path that already works.
2026-08-03 22:20:51 -04:00

236 lines
10 KiB
QML

// Souveraine patch to ii's stock GlobalStates.qml.
//
// Adds dockRevealed: the explicit, persistent dock state driven by the
// Souveraine's integrated navigation rail. Everything else in this file is
// unchanged stock ii — diff against upstream before re-applying this patch if
// ii updates.
import qs.modules.common
import qs.services
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
// ── Write authority ────────────────────────────────────────────
// Each property below names its single authorized writer. Other
// files MUST NOT write to these directly — route through the
// writer's IPC or signal instead. This is a convention, not
// enforced at runtime; a lint rule or future QML analyzer should
// flag writes from files other than the named writer.
//
// Stock ii properties (barOpen, crosshairOpen, sidebarLeftOpen,
// sidebarRightOpen, mediaControlsOpen, osdBrightnessOpen,
// osdVolumeOpen, oskOpen, overlayOpen, overviewOpen,
// regionSelectorOpen, searchOpen, screenTranslatorOpen,
// sessionOpen, wallpaperSelectorOpen, workspaceShowNumbers) are
// written by their respective ii modules. Souveraine does not
// own these — they follow ii's own conventions.
// WRITER: ii bar module
property bool barOpen: true
// WRITER: ii crosshair module
property bool crosshairOpen: false
// WRITER: ii sidebar module
property bool sidebarLeftOpen: false
// WRITER: ii sidebar module
property bool sidebarRightOpen: false
// WRITER: ii media controls module
property bool mediaControlsOpen: false
// WRITER: ii OSD module
property bool osdBrightnessOpen: false
// WRITER: ii OSD module
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;
}
// Locking closes the keyboard, and unlocking does not bring it back.
//
// `oskHoldRestoreOpen` remembers whether the keyboard was up when the
// first hold was taken, so releasing the last hold restores rather than
// clobbers it. Across a lock that restore is wrong: the keyboard was up
// because of something the user was typing into before they put the phone
// down, and the first thing they see on unlocking is a keyboard over
// whatever they actually came back for.
//
// Intent to type does not survive the screen going away. Anything that
// still wants the keyboard — a field taking focus again — will ask for it,
// which is the path that already works.
onScreenLockedChanged: {
if (!root.screenLocked) return;
root.oskHolds = [];
root.oskHoldRestoreOpen = false;
root.oskOpen = false;
}
// WRITER: ii overlay module
property bool overlayOpen: false
// WRITER: ii overview module + Dock.qml IPC
property bool overviewOpen: false
// WRITER: ii region selector module
property bool regionSelectorOpen: false
// WRITER: ii search module
property bool searchOpen: false
// WRITER: LockScreen.qml — the shell's lock *request*. Drives
// WlSessionLock and hides ordinary surfaces immediately. Deliberately
// separate from screenLockSecure (compositor ack). Consumers that
// disclose personal data must gate on secure, not merely request.
property bool screenLocked: false
// WRITER: IdleCoordinator.qml — true while the display is genuinely
// in use (Active or Waking). Widgets/pollers whose data may go stale
// while the screen is dimmed/locked/asleep gate their timers on this
// instead of importing IdleCoordinator into ii-base.
property bool displayActive: true
// WRITER: LockScreen.qml — WlSessionLock.secure. Only true once the
// compositor has acknowledged the lock surface. This is the real
// "session is locked" signal; screenLocked is just the request.
property bool screenLockSecure: false
// WRITER: LockScreen.qml
property bool screenLockContainsCharacters: false
// WRITER: LockScreen.qml
property bool screenUnlockFailed: false
// WRITER: ii translator module
property bool screenTranslatorOpen: false
// WRITER: ii session module
property bool sessionOpen: false
// WRITER: GlobalShortcut handler in this file
property bool superDown: false
// WRITER: GlobalShortcut handler in this file
property bool superReleaseMightTrigger: true
// WRITER: GlobalShortcut handler in this file
property real superPressTime: 0
// WRITER: GlobalShortcut handler in this file
property real superLastPressDuration: -1
// WRITER: GlobalShortcut handler in this file
property real superLastReleaseTime: 0
// WRITER: ii wallpaper selector module
property bool wallpaperSelectorOpen: false
// WRITER: ii workspace module
property bool workspaceShowNumbers: false
// WRITER: LockScreen.qml — true from shell start until the lock
// surface is secure. Covers Hyprland's boot render, continuing the
// C splash bloom animation. LockScreen.onSecureChanged clears it.
property bool bootBloomActive: true
// WRITER: Dock.qml IPC (swipeUp/reveal) + navigation rail. The
// fullscreen dock toggle. Deliberately has no timer.
property bool dockRevealed: false
// WRITER: navigation rail (triple swipe-up). The Souveraine
// process/task surface. Deliberately distinct from overviewOpen
// (app launcher/search) — this is the running-work view.
property bool missionControlOpen: false
// WRITER: Dock.qml IPC (swipeDown). A rail swipe-down on a visible
// dock dismisses it in ANY state — including pinned and
// shown-on-empty-desktop. Swipe up clears it.
property bool dockSuppressed: false
// WRITER: this file (pulseDockReveal) + OnScreenKeyboard.qml IPC.
// Transient dock reveal: shows for 3s and restores prior state.
// Restored 2026-07-16 — definition was lost in a refactor while
// its IPC caller survived, so pulseDock threw.
property bool dockRevealPulse: false
function pulseDockReveal() {
root.dockRevealPulse = true;
dockRevealPulseTimer.restart();
}
Timer {
id: dockRevealPulseTimer
interval: 3000
onTriggered: root.dockRevealPulse = false
}
// WRITER: DockAppButton drag lifecycle. True while a dock icon drag
// is in flight. Read by DockManifest's state checks so structural
// edits (pin/stack mutations) can't land mid-drag.
property bool dockDragInProgress: false
function superPressDuration() {
const now = Date.now();
if (root.superPressTime > 0)
return now - root.superPressTime;
if (root.superLastReleaseTime > 0 && now - root.superLastReleaseTime < 250)
return root.superLastPressDuration;
return -1;
}
function shouldSuppressSuperReleaseSearch() {
const autoHide = Config?.options?.bar?.autoHide;
const showWhenPressingSuper = autoHide?.showWhenPressingSuper;
if (!autoHide?.enable || !showWhenPressingSuper?.enable || !showWhenPressingSuper?.suppressSearchOnHold)
return false;
const duration = root.superPressDuration();
return duration >= (showWhenPressingSuper?.suppressSearchDelay ?? showWhenPressingSuper?.delay ?? 140);
}
onSidebarRightOpenChanged: {
if (GlobalStates.sidebarRightOpen) {
Notifications.timeoutAll();
Notifications.markAllRead();
}
}
GlobalShortcut {
name: "workspaceNumber"
description: "Hold to show workspace numbers, release to show icons"
onPressed: {
root.superDown = true
root.superPressTime = Date.now()
root.superLastPressDuration = -1
}
onReleased: {
const now = Date.now()
if (root.superPressTime > 0)
root.superLastPressDuration = now - root.superPressTime
root.superLastReleaseTime = now
root.superPressTime = 0
root.superDown = false
}
}
}