This is a projection, not a development branch. The tree above was constructed from the internal source named below under a manifest that decides which paths may leave, then scanned as a whole tree rather than as a series of patches, and only then published. Public history starts here because the history before it was not admissible, and neither was the tree. What used to stand in this repository included a rescue copy of another machine, a directory of phone handoffs, deployment wired to one house, and a submodule pointing at a forge no stranger can reach. None of that was ever the product. It stays in the private forge, which is allowed to hold the whole working organism, and this is what was deliberately sent out instead. Three mechanisms produced this tree, in decreasing order of trust. A top-level path the manifest does not name never arrives at all, which is the one that catches directories nobody has thought of yet. Named internal files inside admitted roots are dropped. A short, reviewed table replaces deployment defaults that a public build must not carry -- an endpoint aimed at one LAN, a VPN profile belonging to one phone, packaging built from one checkout path. Everything after this commit is an ordinary publication with the same three trailers, so a force push stops being routine and starts meaning that something deliberate happened. The trailers bind the projection to its source without pretending the public SHA is the private one: same lineage, different tree, and the record says so. Source-Sha: 8f27b1e76a8fef560a336aba18e6990713ff1047 Policy-Sha: 6b261d2f3e6e1fb19874846ba4bb1dfe15565d25b8618c1c1afba0419c101d27 Tree-Digest: 18ec3563c5e5ef9a414993a9f6734b251ff9ed3cd56eebdd6cac01e45c6e3067
217 lines
8.8 KiB
QML
217 lines
8.8 KiB
QML
// Souveraine patch to ii's stock Lock.qml.
|
|
//
|
|
// Selects the lock surface by config: lock.touchKeypad picks the
|
|
// TouchLockSurface (PIN pad for the phone's touch panel) instead of the
|
|
// stock keyboard-driven LockSurface. Everything else is unchanged stock ii.
|
|
pragma ComponentBehavior: Bound
|
|
import qs
|
|
import qs.services
|
|
import qs.modules.common
|
|
import qs.modules.common.functions
|
|
import qs.modules.common.panels.lock
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Hyprland
|
|
|
|
LockScreen {
|
|
id: root
|
|
|
|
// Monitor name -> workspace id to restore on unlock (set when locking)
|
|
property var savedWorkspaces: ({})
|
|
|
|
// Session arbiter surface. Lives here beside the lock because the lock IS
|
|
// the session gate on this device — LockScreen already owns WlSessionLock,
|
|
// and the session verbs (suspend/poweroff/inhibit) are the other half of
|
|
// the same lifecycle. The logic lives in the Session singleton
|
|
// (modules/common/functions/Session.qml); this is only the IPC face.
|
|
//
|
|
// Every mutating method returns {ok, reason?} rather than throwing, so a
|
|
// refusal is information the agent learns from — same contract as dock.*,
|
|
// shell.* and apps.*. `lock` (target: "lock") stays as it was: it is the
|
|
// surface's own activate/focus pair, not the session lifecycle.
|
|
//
|
|
// `session` is the one lifecycle authority across phone, laptop, and
|
|
// desktop. The visual power-menu toggles live at `sessionMenu`; a menu is
|
|
// a presentation concern, whereas this target is the system contract.
|
|
// The ii screen is vendored with that one target rename so Quickshell
|
|
// never has to silently choose between duplicate `session` handlers.
|
|
// Every method returns `string`, not `var`, and the payload is JSON.
|
|
// This is not a style choice — quickshell marshals exactly five types over
|
|
// IPC (string, int, bool, double, color; see src/io/ipc.cpp ipcType()) and
|
|
// maps a `var` return to VOID, discarding the value with no error. A
|
|
// method declared `: var` therefore looks correct in QML, registers as
|
|
// `(): void`, and silently returns nothing to the caller. JSON-over-string
|
|
// is the only way a structured {ok, reason} result actually crosses.
|
|
IpcHandler {
|
|
target: "session"
|
|
|
|
// Read-only projection: lock state (read through from the compositor,
|
|
// never a cached bool), idle inhibitors with their reasons, and what
|
|
// this machine can actually do.
|
|
function state(): string {
|
|
return JSON.stringify(Session.state());
|
|
}
|
|
|
|
function capabilities(): string {
|
|
return JSON.stringify(Session.caps());
|
|
}
|
|
|
|
function lock(): string {
|
|
return JSON.stringify(Session.lock());
|
|
}
|
|
|
|
// Refuses by design — unlocking is the credential gate.
|
|
function unlock(): string {
|
|
return JSON.stringify(Session.unlock());
|
|
}
|
|
|
|
function suspend(): string {
|
|
return JSON.stringify(Session.suspend());
|
|
}
|
|
|
|
function hibernate(): string {
|
|
return JSON.stringify(Session.hibernate());
|
|
}
|
|
|
|
function poweroff(): string {
|
|
return JSON.stringify(Session.poweroff());
|
|
}
|
|
|
|
function reboot(): string {
|
|
return JSON.stringify(Session.reboot());
|
|
}
|
|
|
|
function logout(): string {
|
|
return JSON.stringify(Session.logout());
|
|
}
|
|
|
|
// Reason is mandatory: an inhibitor nobody can explain is exactly the
|
|
// thing that leaves the phone awake in a pocket at 3am.
|
|
function inhibit(what: string, reason: string): string {
|
|
return JSON.stringify(Session.inhibit(what, reason));
|
|
}
|
|
|
|
function uninhibit(cookie: string): string {
|
|
return JSON.stringify(Session.uninhibit(cookie));
|
|
}
|
|
}
|
|
|
|
// Preview-only diagnostic ingress. The physical FPC1020 producer publishes
|
|
// its root-owned pulse record for LockContext to watch; it cannot use
|
|
// generic XF86WakeUp here because the touch controller emits that key too.
|
|
// This target lets us exercise the same visual-only path manually. It
|
|
// never unlocks, reveals Personal content, or mints step-up. Task 41 owns
|
|
// replacing this with an attested, source-specific producer.
|
|
IpcHandler {
|
|
target: "fingerprint"
|
|
|
|
function signal(): string {
|
|
return JSON.stringify(root.context.noteProvisionalFingerprintPulse());
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: restoreTimer
|
|
interval: 150
|
|
repeat: false
|
|
onTriggered: {
|
|
var batch = ""
|
|
for (var j = 0; j < Quickshell.screens.length; ++j) {
|
|
var monName = Quickshell.screens[j].name
|
|
var wsId = root.savedWorkspaces[monName]
|
|
if (wsId !== undefined) {
|
|
batch += `hyprctl dispatch 'hl.dsp.focus({monitor="${monName}"})'; hyprctl dispatch 'hl.dsp.focus({workspace=${wsId}})';`
|
|
}
|
|
}
|
|
if (batch.length > 0) {
|
|
Quickshell.execDetached(["bash", "-c", batch])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Which surface, decided WITHOUT waiting for the config file.
|
|
//
|
|
// `Config.options` is a JsonAdapter, so it answers with its QML defaults
|
|
// from the instant it exists — `touchKeypad` reads `false` until `onLoaded`
|
|
// flips `ready`. That is survivable at boot, where `initIfReady()` waits for
|
|
// `Config.ready` before requesting a lock, and fatal on a reload, where the
|
|
// lock is adopted at construction: the surface would bind to the DESKTOP
|
|
// keypad on the phone, and `WlSessionLock.surfaceComponent` cannot be
|
|
// changed while the lock is active — quickshell qCritical's and keeps the
|
|
// old one. The result is a lock screen that will not take your PIN, which
|
|
// is the worst of the three failures on this path because you cannot get
|
|
// back in.
|
|
//
|
|
// So the last known-good answer rides through the reload in-process, and
|
|
// Config takes over the moment it is genuinely loaded.
|
|
//
|
|
// `PersistentProperties` is in-process, so it covers a reload and not a
|
|
// cold start. sessiond takes the lock before the shell exists, so a shell
|
|
// started by greetd adopts a live lock at construction with `ready` still
|
|
// false — the fatal path above, reached without any reload. `SOUVERAINE_
|
|
// TOUCH_KEYPAD` is read from the environment, which is answerable at that
|
|
// instant; unset it reads exactly as before.
|
|
PersistentProperties {
|
|
id: lockPrefs
|
|
reloadableId: "souveraineLockPrefs"
|
|
property bool touchKeypad: Quickshell.env("SOUVERAINE_TOUCH_KEYPAD") === "1"
|
|
}
|
|
|
|
Connections {
|
|
target: Config
|
|
function onReadyChanged() {
|
|
if (Config.ready)
|
|
lockPrefs.touchKeypad = Config.options.lock.touchKeypad;
|
|
}
|
|
}
|
|
|
|
lockSurface: (Config.ready ? Config.options.lock.touchKeypad : lockPrefs.touchKeypad)
|
|
? touchSurfaceComponent
|
|
: desktopSurfaceComponent
|
|
|
|
property Component desktopSurfaceComponent: LockSurface {
|
|
context: root.context
|
|
}
|
|
property Component touchSurfaceComponent: TouchLockSurface {
|
|
context: root.context
|
|
}
|
|
|
|
// Single batch for lock and unlock so we don't race multiple hyprctl calls
|
|
Connections {
|
|
target: GlobalStates
|
|
function onScreenLockedChanged() {
|
|
if (GlobalStates.screenLocked) {
|
|
// Lock: save workspace per monitor and move all to temp workspace in one batch
|
|
var next = {}
|
|
var batch = "keyword animation workspaces,1,7,menu_decel,slidevert; "
|
|
for (var i = 0; i < Quickshell.screens.length; ++i) {
|
|
var mon = Quickshell.screens[i].name
|
|
var mData = HyprlandData.monitors.find(m => m.name === mon)
|
|
if (mData?.activeWorkspace == undefined) {
|
|
return;
|
|
}
|
|
var ws = (mData?.activeWorkspace?.id ?? 1)
|
|
next[mon] = ws
|
|
batch += `hyprctl dispatch 'hl.dsp.focus({monitor="${mon}"})'; hyprctl dispatch 'hl.dsp.focus({workspace=${2147483647 - ws}})';`
|
|
}
|
|
root.savedWorkspaces = next
|
|
Quickshell.execDetached(["bash", "-c", batch])
|
|
} else {
|
|
restoreTimer.start()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Push everything down (visual only; workspace switch is in Connections above)
|
|
Variants {
|
|
model: Quickshell.screens
|
|
delegate: Scope {
|
|
required property ShellScreen modelData
|
|
property bool shouldPush: GlobalStates.screenLocked
|
|
property string targetMonitorName: modelData.name
|
|
property int verticalMovementDistance: modelData.height
|
|
property int horizontalSqueeze: modelData.width * 0.2
|
|
}
|
|
}
|
|
}
|