Split the lock branding source into its own component
This commit is contained in:
+27
-29
@@ -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.
|
ignored, and a row without one keeps its file order.
|
||||||
|
|
||||||
`plugins/blob.lock/` clones `omarchy.lock` to put `branding/screensaver.txt` on
|
`plugins/blob.lock/` clones `omarchy.lock` to put `branding/screensaver.txt` on
|
||||||
the lock screen. Stock `LockView.qml` draws a blurred wallpaper, the password
|
the lock screen and to restyle the password field like the AGS widgets. Stock
|
||||||
field, and a fingerprint hint - there is no branding element and no config key
|
`LockView.qml` draws a blurred wallpaper, the field, and a fingerprint hint -
|
||||||
for one, so the only way to get the art behind the password prompt is a fork.
|
there is no branding element and no config key for one, so a fork is the only
|
||||||
`Service.qml` gains a watched `FileView` on the branding file and passes the
|
way in.
|
||||||
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 password field is restyled to match the AGS widgets: a 2px border and
|
The fork is three small changes. `BrandingSource.qml` is new and holds both
|
||||||
square corners in place of the shell's 3px rounded outline, an
|
file reads: the branding text, and the `color4` slot parsed out of the active
|
||||||
`alpha(background, 0.6)` fill matching `.qs-tile`, and the AGS border pair -
|
theme's `colors.toml`. `Color` resolves a palette down to `foreground`,
|
||||||
`alpha(color4, 0.5)` while the field is empty, going to a solid `accent` once
|
`background`, `accent`, `urgent`, and `muted` and never exposes `color4`, which
|
||||||
there is something in it, with `urgent` on a failed attempt. Only the geometry
|
is the color AGS borders with. Stock `Color` reads that file once at startup
|
||||||
and the alphas are fixed; the colors come from the active theme, so the field
|
and takes theme switches over IPC, but
|
||||||
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
|
|
||||||
`~/.local/state/omarchy/current/theme` is a real directory rewritten in place
|
`~/.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
|
rather than a swapped symlink, so watching the file is enough here.
|
||||||
with no `color4` falls back to the accent.
|
`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
|
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
|
`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
|
Re-copy `LockView.qml` and `Service.qml` from
|
||||||
`/usr/share/omarchy/shell/plugins/lock/` after an Omarchy update that touches
|
`/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
|
`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
|
of the center. Omarchy 4 puts a drag-to-reorder handler on every bar module and
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,8 +22,6 @@ Item {
|
|||||||
readonly property string placeholderText: "Enter Password"
|
readonly property string placeholderText: "Enter Password"
|
||||||
readonly property int fieldWidth: 381
|
readonly property int fieldWidth: 381
|
||||||
readonly property int fieldHeight: 67
|
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 outlineThickness: 2
|
||||||
readonly property int fieldRadius: 0
|
readonly property int fieldRadius: 0
|
||||||
readonly property int fieldFontSize: Math.round(Style.font.heading * 1.125)
|
readonly property int fieldFontSize: Math.round(Style.font.heading * 1.125)
|
||||||
@@ -37,22 +35,12 @@ Item {
|
|||||||
readonly property real passwordDotScale: dotMetrics.advanceWidth > 0
|
readonly property real passwordDotScale: dotMetrics.advanceWidth > 0
|
||||||
? Math.min(1, (passwordInput.width - 4) / dotMetrics.advanceWidth)
|
? Math.min(1, (passwordInput.width - 4) / dotMetrics.advanceWidth)
|
||||||
: 1
|
: 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 brandingGap: Style.space(48)
|
||||||
readonly property int brandingMaxWidth: 1100
|
readonly property int brandingMaxWidth: 1100
|
||||||
readonly property int brandingMaxFontSize: Math.round(Style.font.heading * 1.5)
|
readonly property int brandingMaxFontSize: Math.round(Style.font.heading * 1.5)
|
||||||
|
|
||||||
readonly property bool showPasswordCursor: inputEnabled && !authenticatingPassword && failureMessage.length === 0
|
readonly property bool showPasswordCursor: inputEnabled && !authenticatingPassword && failureMessage.length === 0
|
||||||
readonly property bool errorState: 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 bool inputActive: passwordText.length > 0 || authenticatingPassword
|
||||||
readonly property color inputRestingBorder: paletteColor4.length > 0 ? paletteColor4 : Color.accent
|
readonly property color inputRestingBorder: paletteColor4.length > 0 ? paletteColor4 : Color.accent
|
||||||
readonly property color inputBorderColor: errorState
|
readonly property color inputBorderColor: errorState
|
||||||
@@ -142,10 +130,6 @@ Item {
|
|||||||
onPositionChanged: root.wakeRequested()
|
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 {
|
Text {
|
||||||
id: branding
|
id: branding
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ Item {
|
|||||||
readonly property string stateHome: home + "/.local/state"
|
readonly property string stateHome: home + "/.local/state"
|
||||||
readonly property string userName: Quickshell.env("USER") || Quickshell.env("LOGNAME")
|
readonly property string userName: Quickshell.env("USER") || Quickshell.env("LOGNAME")
|
||||||
readonly property string currentBackgroundLink: stateHome + "/omarchy/current/background"
|
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 lockRequested: false
|
||||||
property bool pendingSessionLock: false
|
property bool pendingSessionLock: false
|
||||||
@@ -31,8 +29,6 @@ Item {
|
|||||||
property int failedAttempts: 0
|
property int failedAttempts: 0
|
||||||
property string backgroundPath: ""
|
property string backgroundPath: ""
|
||||||
property int backgroundVersion: 0
|
property int backgroundVersion: 0
|
||||||
property string brandingText: ""
|
|
||||||
property string paletteColor4: ""
|
|
||||||
property string lastEvent: "init"
|
property string lastEvent: "init"
|
||||||
property string lastEventAt: ""
|
property string lastEventAt: ""
|
||||||
property bool strandedLock: false
|
property bool strandedLock: false
|
||||||
@@ -274,8 +270,8 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
backgroundPath: root.backgroundPath
|
backgroundPath: root.backgroundPath
|
||||||
backgroundVersion: root.backgroundVersion
|
backgroundVersion: root.backgroundVersion
|
||||||
brandingText: root.brandingText
|
brandingText: brandingSource.brandingText
|
||||||
paletteColor4: root.paletteColor4
|
paletteColor4: brandingSource.paletteColor4
|
||||||
fingerprintConfigured: root.fingerprintConfigured
|
fingerprintConfigured: root.fingerprintConfigured
|
||||||
authenticatingPassword: root.authenticatingPassword
|
authenticatingPassword: root.authenticatingPassword
|
||||||
failureMessage: root.failureMessage
|
failureMessage: root.failureMessage
|
||||||
@@ -306,8 +302,8 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
backgroundPath: root.backgroundPath
|
backgroundPath: root.backgroundPath
|
||||||
backgroundVersion: root.backgroundVersion
|
backgroundVersion: root.backgroundVersion
|
||||||
brandingText: root.brandingText
|
brandingText: brandingSource.brandingText
|
||||||
paletteColor4: root.paletteColor4
|
paletteColor4: brandingSource.paletteColor4
|
||||||
fingerprintConfigured: root.fingerprintConfigured
|
fingerprintConfigured: root.fingerprintConfigured
|
||||||
authenticatingPassword: false
|
authenticatingPassword: false
|
||||||
failureMessage: ""
|
failureMessage: ""
|
||||||
@@ -489,39 +485,8 @@ Item {
|
|||||||
else armBlankTimer()
|
else armBlankTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Color singleton exposes only foreground, background, accent, urgent,
|
BrandingSource {
|
||||||
// and muted, so the raw color4 slot the AGS stylesheet borders with has to
|
id: brandingSource
|
||||||
// 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FileView {
|
FileView {
|
||||||
|
|||||||
@@ -63,9 +63,6 @@ function parseMenuJsonc(raw) {
|
|||||||
return out
|
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) {
|
function applyBeforeHints(items, itemOrder) {
|
||||||
var order = itemOrder.slice()
|
var order = itemOrder.slice()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user