diff --git a/omarchy/README.md b/omarchy/README.md index 859c56c..ac1fec8 100644 --- a/omarchy/README.md +++ b/omarchy/README.md @@ -95,36 +95,33 @@ under `Apps` instead of below `System`. A `before:` that names an unknown id is ignored, and a row without one keeps its file order. `plugins/blob.lock/` clones `omarchy.lock` to put `branding/screensaver.txt` on -the lock screen. Stock `LockView.qml` draws a blurred wallpaper, the password -field, and a fingerprint hint - there is no branding element and no config key -for one, so the only way to get the art behind the password prompt is a fork. -`Service.qml` gains a watched `FileView` on the branding file and passes the -text to both `LockView` instances (the real lock surface and the theme -preview); `LockView.qml` gains one `Text` above the input field. The field -itself keeps the position and size it has upstream. `Text.Fit` scales the art -down to whatever room is left above the field, so a wider or taller -`screensaver.txt` cannot run off the screen, and an unreadable or missing file -leaves the lock unbranded rather than broken. +the lock screen and to restyle the password field like the AGS widgets. Stock +`LockView.qml` draws a blurred wallpaper, the field, and a fingerprint hint - +there is no branding element and no config key for one, so a fork is the only +way in. -The password field is restyled to match the AGS widgets: a 2px border and -square corners in place of the shell's 3px rounded outline, an -`alpha(background, 0.6)` fill matching `.qs-tile`, and the AGS border pair - -`alpha(color4, 0.5)` while the field is empty, going to a solid `accent` once -there is something in it, with `urgent` on a failed attempt. Only the geometry -and the alphas are fixed; the colors come from the active theme, so the field -follows a theme change the way the AGS widgets do. The `[lock]` tokens in a -theme's `shell.toml` no longer reach the border or the fill. - -`color4` needs reading from disk. The `Color` singleton resolves the palette -down to `foreground`, `background`, `accent`, `urgent`, and `muted`, and uses -`color4` only as the fallback when a theme declares no separate `accent` - so -the raw slot the AGS stylesheet borders with is not exposed anywhere. The -service parses it out of the active theme's `colors.toml` with the same regex -`Color.loadColors` uses. Stock `Color` reads that file once at startup and -takes runtime theme switches over IPC instead, but +The fork is three small changes. `BrandingSource.qml` is new and holds both +file reads: the branding text, and the `color4` slot parsed out of the active +theme's `colors.toml`. `Color` resolves a palette down to `foreground`, +`background`, `accent`, `urgent`, and `muted` and never exposes `color4`, which +is the color AGS borders with. Stock `Color` reads that file once at startup +and takes theme switches over IPC, but `~/.local/state/omarchy/current/theme` is a real directory rewritten in place -rather than a swapped symlink, so watching the file is enough here. A palette -with no `color4` falls back to the accent. +rather than a swapped symlink, so watching the file is enough here. +`Service.qml` gains only that component and two bindings on each of its two +`LockView` instances (the lock surface and the theme preview). `LockView.qml` +gains a `Text` above the field, and the field's own skin. + +The styling follows `ags/style.css`: a 2px border and square corners in place +of the shell's 3px rounded outline, an `alpha(background, 0.6)` fill matching +`.qs-tile`, and the AGS border pair - `alpha(color4, 0.5)` while the field is +empty, going to a solid `accent` once there is something in it, and `urgent` on +a failed attempt. Only the geometry and the alphas are fixed; the colors come +from the active theme, so the field follows a theme change the way the AGS +widgets do. The `[lock]` tokens in a theme's `shell.toml` no longer reach the +border or the fill. `Text.Fit` scales the branding into whatever room is left +above the field, so a wider or taller `screensaver.txt` cannot run off the +screen, and a missing file leaves the lock unbranded rather than broken. Unlike the other clones this one is load-bearing for security. The lock is a `service` plugin, so it is enabled by its id appearing in `plugins[]` and the @@ -140,7 +137,8 @@ removed through `omarchy plugin remove`. Re-copy `LockView.qml` and `Service.qml` from `/usr/share/omarchy/shell/plugins/lock/` after an Omarchy update that touches -the lock, then re-apply the branding property, the `FileView`, and the `Text`. +the lock, then re-apply the two blocks above. `BrandingSource.qml` is wholly +ours and carries over untouched. `plugins/blob.bar/` replaces the whole bar so the clock cannot be dragged out of the center. Omarchy 4 puts a drag-to-reorder handler on every bar module and diff --git a/omarchy/plugins/blob.lock/BrandingSource.qml b/omarchy/plugins/blob.lock/BrandingSource.qml new file mode 100644 index 0000000..845f374 --- /dev/null +++ b/omarchy/plugins/blob.lock/BrandingSource.qml @@ -0,0 +1,41 @@ +import QtQuick +import Quickshell +import Quickshell.Io + +Item { + id: root + + readonly property string home: Quickshell.env("HOME") + readonly property string brandingPath: home + "/.config/omarchy/branding/screensaver.txt" + readonly property string palettePath: home + "/.local/state/omarchy/current/theme/colors.toml" + + property string brandingText: "" + property string paletteColor4: "" + + function readPaletteColor4(raw) { + var lines = String(raw || "").split("\n") + for (var i = 0; i < lines.length; i++) { + var match = lines[i].match(/^\s*color4\s*=\s*["']?(#[0-9A-Fa-f]{6})/) + if (match) return match[1] + } + return "" + } + + FileView { + path: root.brandingPath + watchChanges: true + printErrors: false + onLoaded: root.brandingText = text() + onLoadFailed: root.brandingText = "" + onFileChanged: reload() + } + + FileView { + path: root.palettePath + watchChanges: true + printErrors: false + onLoaded: root.paletteColor4 = root.readPaletteColor4(text()) + onLoadFailed: root.paletteColor4 = "" + onFileChanged: reload() + } +} diff --git a/omarchy/plugins/blob.lock/LockView.qml b/omarchy/plugins/blob.lock/LockView.qml index 7620edf..3df8f7b 100644 --- a/omarchy/plugins/blob.lock/LockView.qml +++ b/omarchy/plugins/blob.lock/LockView.qml @@ -22,8 +22,6 @@ Item { readonly property string placeholderText: "Enter Password" readonly property int fieldWidth: 381 readonly property int fieldHeight: 67 - // AGS draws every surface with a 2px border and square corners; see the - // 2px/border-radius: 0 pairs throughout ags/style.css. readonly property int outlineThickness: 2 readonly property int fieldRadius: 0 readonly property int fieldFontSize: Math.round(Style.font.heading * 1.125) @@ -37,22 +35,12 @@ Item { readonly property real passwordDotScale: dotMetrics.advanceWidth > 0 ? Math.min(1, (passwordInput.width - 4) / dotMetrics.advanceWidth) : 1 - // Gap between the branding and the password field, and the share of the - // screen the art may claim. Text.Fit needs a bounded width and height to - // scale into, so both are given explicitly below. readonly property int brandingGap: Style.space(48) readonly property int brandingMaxWidth: 1100 readonly property int brandingMaxFontSize: Math.round(Style.font.heading * 1.5) readonly property bool showPasswordCursor: inputEnabled && !authenticatingPassword && failureMessage.length === 0 readonly property bool errorState: failureMessage.length > 0 - // The AGS border pair: containers rest on alpha(@color4, 0.5) and go to a - // solid @accent once in use. An empty field reads as resting and a filled - // one as active. The spec is built here rather than read from the [lock] - // theme tokens, since those carry the shell's own heavier look; the colors - // themselves still come from the active theme, so this follows a theme - // change exactly like the AGS widgets do. color4 falls back to the accent - // for a palette that does not define one. readonly property bool inputActive: passwordText.length > 0 || authenticatingPassword readonly property color inputRestingBorder: paletteColor4.length > 0 ? paletteColor4 : Color.accent readonly property color inputBorderColor: errorState @@ -142,10 +130,6 @@ Item { onPositionChanged: root.wakeRequested() } - // Sits above the password field rather than replacing anything, so the - // input keeps the position and size it has upstream. Text.Fit shrinks the - // art to whatever room is left above the field, which keeps a tall or wide - // screensaver.txt from running off the top of a small screen. Text { id: branding anchors.horizontalCenter: parent.horizontalCenter diff --git a/omarchy/plugins/blob.lock/Service.qml b/omarchy/plugins/blob.lock/Service.qml index 6f228f7..bbe3cbb 100644 --- a/omarchy/plugins/blob.lock/Service.qml +++ b/omarchy/plugins/blob.lock/Service.qml @@ -15,8 +15,6 @@ Item { readonly property string stateHome: home + "/.local/state" readonly property string userName: Quickshell.env("USER") || Quickshell.env("LOGNAME") readonly property string currentBackgroundLink: stateHome + "/omarchy/current/background" - readonly property string brandingPath: home + "/.config/omarchy/branding/screensaver.txt" - readonly property string currentThemePath: stateHome + "/omarchy/current/theme" property bool lockRequested: false property bool pendingSessionLock: false @@ -31,8 +29,6 @@ Item { property int failedAttempts: 0 property string backgroundPath: "" property int backgroundVersion: 0 - property string brandingText: "" - property string paletteColor4: "" property string lastEvent: "init" property string lastEventAt: "" property bool strandedLock: false @@ -274,8 +270,8 @@ Item { anchors.fill: parent backgroundPath: root.backgroundPath backgroundVersion: root.backgroundVersion - brandingText: root.brandingText - paletteColor4: root.paletteColor4 + brandingText: brandingSource.brandingText + paletteColor4: brandingSource.paletteColor4 fingerprintConfigured: root.fingerprintConfigured authenticatingPassword: root.authenticatingPassword failureMessage: root.failureMessage @@ -306,8 +302,8 @@ Item { anchors.fill: parent backgroundPath: root.backgroundPath backgroundVersion: root.backgroundVersion - brandingText: root.brandingText - paletteColor4: root.paletteColor4 + brandingText: brandingSource.brandingText + paletteColor4: brandingSource.paletteColor4 fingerprintConfigured: root.fingerprintConfigured authenticatingPassword: false failureMessage: "" @@ -489,39 +485,8 @@ Item { else armBlankTimer() } - // The Color singleton exposes only foreground, background, accent, urgent, - // and muted, so the raw color4 slot the AGS stylesheet borders with has to - // be read here. Same file and same shape Color itself parses; the current - // theme directory is a real directory rewritten in place on every theme - // change, so watching the file is enough to follow a theme switch. - function readPaletteColor4(raw) { - var lines = String(raw || "").split("\n") - for (var i = 0; i < lines.length; i++) { - var match = lines[i].match(/^\s*color4\s*=\s*["']?(#[0-9A-Fa-f]{6})/) - if (match) return match[1] - } - return "" - } - - FileView { - path: root.currentThemePath + "/colors.toml" - watchChanges: true - printErrors: false - onLoaded: root.paletteColor4 = root.readPaletteColor4(text()) - onLoadFailed: root.paletteColor4 = "" - onFileChanged: reload() - } - - // The same file the screensaver paints. Watched so an edit through - // `omarchy branding screensaver text` shows up on the next lock without a - // shell restart. A missing file simply leaves the lock screen unbranded. - FileView { - path: root.brandingPath - watchChanges: true - printErrors: false - onLoaded: root.brandingText = text() - onLoadFailed: root.brandingText = "" - onFileChanged: reload() + BrandingSource { + id: brandingSource } FileView { diff --git a/omarchy/plugins/blob.menu/MenuModel.js b/omarchy/plugins/blob.menu/MenuModel.js index e4387f0..c672c0f 100644 --- a/omarchy/plugins/blob.menu/MenuModel.js +++ b/omarchy/plugins/blob.menu/MenuModel.js @@ -63,9 +63,6 @@ function parseMenuJsonc(raw) { return out } -// A row may name a sibling's id in `before:` to sit ahead of it. User rows are -// appended after every default row, so without this a new top-level entry can -// only land at the bottom of the root menu. function applyBeforeHints(items, itemOrder) { var order = itemOrder.slice()