Consolidate utils
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
pragma Singleton
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
// Shared utility helpers used across plugins. Pure functions only — no
|
||||||
|
// state. Anything stateful belongs on Color, Style, or a service.
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Compose a base color with an opacity. Accepts a color object or a hex
|
||||||
|
// string; null/undefined yields transparent black at the requested alpha.
|
||||||
|
function alpha(c, opacity) {
|
||||||
|
if (!c) return Qt.rgba(0, 0, 0, opacity)
|
||||||
|
if (typeof c === "string") c = Qt.color(c)
|
||||||
|
return Qt.rgba(c.r, c.g, c.b, opacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// file:// URL with each path segment percent-encoded so spaces and
|
||||||
|
// special chars in user paths don't break Image.source.
|
||||||
|
function fileUrl(path) {
|
||||||
|
if (!path) return ""
|
||||||
|
return "file://" + String(path).split("/").map(encodeURIComponent).join("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single-quote a string for bash. The replace handles embedded single
|
||||||
|
// quotes by closing, escaping, and re-opening the literal.
|
||||||
|
function shellQuote(value) {
|
||||||
|
return "'" + String(value || "").replace(/'/g, "'\\''") + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainObject(value) {
|
||||||
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort base64 decode. Returns "" on parse failure rather than
|
||||||
|
// surfacing garbage downstream.
|
||||||
|
function decodeBase64(value) {
|
||||||
|
var s = String(value || "")
|
||||||
|
if (!s) return ""
|
||||||
|
try { return Qt.atob(s) } catch (e) { return "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneJson(value) {
|
||||||
|
return JSON.parse(JSON.stringify(value === undefined ? null : value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout normalization shared by the bar host and the bar settings panel
|
||||||
|
// so the two never drift. Entries are deep-cloned to decouple from the
|
||||||
|
// input config; consumers can mutate without leaking back to shell.json.
|
||||||
|
function normalizeLayoutEntry(entry) {
|
||||||
|
if (typeof entry === "string") return { id: entry }
|
||||||
|
if (isPlainObject(entry) && entry.id) return cloneJson(entry)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLayoutSection(list) {
|
||||||
|
if (!Array.isArray(list)) return []
|
||||||
|
var out = []
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
var e = normalizeLayoutEntry(list[i])
|
||||||
|
if (e) out.push(e)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLayout(layout) {
|
||||||
|
var src = isPlainObject(layout) ? layout : {}
|
||||||
|
return {
|
||||||
|
left: normalizeLayoutSection(src.left),
|
||||||
|
center: normalizeLayoutSection(src.center),
|
||||||
|
right: normalizeLayoutSection(src.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
module qs.Commons
|
module qs.Commons
|
||||||
singleton Color 1.0 Color.qml
|
singleton Color 1.0 Color.qml
|
||||||
singleton Style 1.0 Style.qml
|
singleton Style 1.0 Style.qml
|
||||||
|
singleton Util 1.0 Util.qml
|
||||||
|
|||||||
@@ -78,19 +78,11 @@ Item {
|
|||||||
root.shell.hide((root.manifest && root.manifest.id) || "omarchy.app-launcher")
|
root.shell.hide((root.manifest && root.manifest.id) || "omarchy.app-launcher")
|
||||||
}
|
}
|
||||||
|
|
||||||
function withAlpha(color, alpha) {
|
|
||||||
return Qt.rgba(color.r, color.g, color.b, alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileUrl(path) {
|
|
||||||
return "file://" + String(path).split("/").map(encodeURIComponent).join("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
function iconSource(icon) {
|
function iconSource(icon) {
|
||||||
var value = String(icon || "")
|
var value = String(icon || "")
|
||||||
if (value.length === 0) return Quickshell.iconPath("application-x-executable", true)
|
if (value.length === 0) return Quickshell.iconPath("application-x-executable", true)
|
||||||
if (value.indexOf("file://") === 0 || value.indexOf("image://") === 0) return value
|
if (value.indexOf("file://") === 0 || value.indexOf("image://") === 0) return value
|
||||||
if (value.charAt(0) === "/") return root.fileUrl(value)
|
if (value.charAt(0) === "/") return Util.fileUrl(value)
|
||||||
return Quickshell.iconPath(value, true)
|
return Quickshell.iconPath(value, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,10 +102,6 @@ Item {
|
|||||||
try { return ToplevelManager.toplevels.values.length } catch (e) { return 0 }
|
try { return ToplevelManager.toplevels.values.length } catch (e) { return 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
function shellQuote(value) {
|
|
||||||
return "'" + String(value).replace(/'/g, "'\\''") + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
function entrySearchText(entry) {
|
function entrySearchText(entry) {
|
||||||
if (!entry) return ""
|
if (!entry) return ""
|
||||||
var keywords = ""
|
var keywords = ""
|
||||||
@@ -140,7 +128,7 @@ Item {
|
|||||||
function hiddenEntryScanCommand() {
|
function hiddenEntryScanCommand() {
|
||||||
var desktop = [Quickshell.env("XDG_CURRENT_DESKTOP"), Quickshell.env("XDG_SESSION_DESKTOP"), Quickshell.env("DESKTOP_SESSION")].filter(function(v) { return String(v || "").length > 0 }).join(":")
|
var desktop = [Quickshell.env("XDG_CURRENT_DESKTOP"), Quickshell.env("XDG_SESSION_DESKTOP"), Quickshell.env("DESKTOP_SESSION")].filter(function(v) { return String(v || "").length > 0 }).join(":")
|
||||||
var script = root.omarchyPath + "/shell/scripts/app-launcher-hidden-entries.sh"
|
var script = root.omarchyPath + "/shell/scripts/app-launcher-hidden-entries.sh"
|
||||||
return root.shellQuote(script) + " " + root.shellQuote(desktop)
|
return Util.shellQuote(script) + " " + Util.shellQuote(desktop)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fuzzyScore(entry, query) {
|
function fuzzyScore(entry, query) {
|
||||||
|
|||||||
@@ -62,13 +62,9 @@ Item {
|
|||||||
revealProgress = 0
|
revealProgress = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodePayload(payload) {
|
|
||||||
try { return Qt.atob(String(payload || "")) } catch (e) { return "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPendingTheme(colorsB64, shellB64) {
|
function setPendingTheme(colorsB64, shellB64) {
|
||||||
pendingColorsRaw = decodePayload(colorsB64)
|
pendingColorsRaw = Util.decodeBase64(colorsB64)
|
||||||
pendingShellRaw = decodePayload(shellB64)
|
pendingShellRaw = Util.decodeBase64(shellB64)
|
||||||
pendingThemeVersion = backgroundVersion
|
pendingThemeVersion = backgroundVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-41
@@ -101,40 +101,14 @@ Item {
|
|||||||
return /^(top|bottom|left|right)$/.test(next) ? next : "top"
|
return /^(top|bottom|left|right)$/.test(next) ? next : "top"
|
||||||
}
|
}
|
||||||
|
|
||||||
function shellQuote(value) {
|
// Apply tray-pinning on top of the shared layout normalization so the
|
||||||
return "'" + String(value).replace(/'/g, "'\\''") + "'"
|
// bar host and the bar settings panel can't drift on entry shape.
|
||||||
}
|
|
||||||
|
|
||||||
function fileUrl(path) {
|
|
||||||
return "file://" + path.split("/").map(encodeURIComponent).join("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPlainObject(value) {
|
|
||||||
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeLayoutEntry(entry) {
|
|
||||||
if (typeof entry === "string") return { id: entry }
|
|
||||||
if (isPlainObject(entry) && entry.id) return entry
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeLayoutSection(list) {
|
|
||||||
if (!Array.isArray(list)) return []
|
|
||||||
var result = []
|
|
||||||
for (var i = 0; i < list.length; i++) {
|
|
||||||
var normalized = normalizeLayoutEntry(list[i])
|
|
||||||
if (normalized) result.push(normalized)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeLayout(layout) {
|
function normalizeLayout(layout) {
|
||||||
if (!isPlainObject(layout)) layout = fallbackBarConfig.layout
|
var normalized = Util.normalizeLayout(Util.isPlainObject(layout) ? layout : fallbackBarConfig.layout)
|
||||||
return {
|
return {
|
||||||
left: pinTrayToInner(normalizeLayoutSection(layout.left), "left"),
|
left: pinTrayToInner(normalized.left, "left"),
|
||||||
center: pinTrayToInner(normalizeLayoutSection(layout.center), "center"),
|
center: pinTrayToInner(normalized.center, "center"),
|
||||||
right: pinTrayToInner(normalizeLayoutSection(layout.right), "right")
|
right: pinTrayToInner(normalized.right, "right")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +131,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyBarConfig() {
|
function applyBarConfig() {
|
||||||
var config = isPlainObject(barConfig) ? barConfig : fallbackBarConfig
|
var config = Util.isPlainObject(barConfig) ? barConfig : fallbackBarConfig
|
||||||
|
|
||||||
position = normalizePosition(config.position)
|
position = normalizePosition(config.position)
|
||||||
transparent = config.transparent === true
|
transparent = config.transparent === true
|
||||||
@@ -175,7 +149,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function entrySettings(entry) {
|
function entrySettings(entry) {
|
||||||
if (!isPlainObject(entry)) return {}
|
if (!Util.isPlainObject(entry)) return {}
|
||||||
var copy = {}
|
var copy = {}
|
||||||
for (var key in entry) {
|
for (var key in entry) {
|
||||||
if (key === "id") continue
|
if (key === "id") continue
|
||||||
@@ -186,7 +160,7 @@ Item {
|
|||||||
|
|
||||||
function entryId(entry) {
|
function entryId(entry) {
|
||||||
if (typeof entry === "string") return entry
|
if (typeof entry === "string") return entry
|
||||||
if (isPlainObject(entry) && entry.id) return String(entry.id)
|
if (Util.isPlainObject(entry) && entry.id) return String(entry.id)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +244,7 @@ Item {
|
|||||||
if (!source && customModuleSafeName(name))
|
if (!source && customModuleSafeName(name))
|
||||||
source = omarchyConfigDir + "/bar/modules/" + String(name) + ".qml"
|
source = omarchyConfigDir + "/bar/modules/" + String(name) + ".qml"
|
||||||
|
|
||||||
return source ? fileUrl(source) : ""
|
return source ? Util.fileUrl(source) : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// First-party widgets are registered with the BarWidgetRegistry at startup.
|
// First-party widgets are registered with the BarWidgetRegistry at startup.
|
||||||
@@ -357,7 +331,7 @@ Item {
|
|||||||
var nextTransparent = !(root.transparent === true)
|
var nextTransparent = !(root.transparent === true)
|
||||||
if (root.shell && typeof root.shell.mutateShellConfig === "function") {
|
if (root.shell && typeof root.shell.mutateShellConfig === "function") {
|
||||||
root.shell.mutateShellConfig(function(config) {
|
root.shell.mutateShellConfig(function(config) {
|
||||||
if (!root.isPlainObject(config.bar)) config.bar = {}
|
if (!Util.isPlainObject(config.bar)) config.bar = {}
|
||||||
config.bar.transparent = nextTransparent
|
config.bar.transparent = nextTransparent
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -470,7 +444,7 @@ Item {
|
|||||||
|
|
||||||
var name = value.substring(0, markerIndex).split("/").pop()
|
var name = value.substring(0, markerIndex).split("/").pop()
|
||||||
var iconPath = value.substring(markerIndex + marker.length).split("&")[0]
|
var iconPath = value.substring(markerIndex + marker.length).split("&")[0]
|
||||||
return fileUrl(iconPath + "/hicolor/16x16/status/" + name + ".png")
|
return Util.fileUrl(iconPath + "/hicolor/16x16/status/" + name + ".png")
|
||||||
}
|
}
|
||||||
|
|
||||||
function trayTooltip(item) {
|
function trayTooltip(item) {
|
||||||
@@ -478,7 +452,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function focusWorkspace(id) {
|
function focusWorkspace(id) {
|
||||||
root.run("hyprctl dispatch " + shellQuote("hl.dsp.focus({ workspace = \"" + id + "\" })"))
|
root.run("hyprctl dispatch " + Util.shellQuote("hl.dsp.focus({ workspace = \"" + id + "\" })"))
|
||||||
}
|
}
|
||||||
|
|
||||||
function clockEntry() {
|
function clockEntry() {
|
||||||
@@ -562,7 +536,7 @@ Item {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: screenRecordingProc
|
id: screenRecordingProc
|
||||||
command: ["bash", "-lc", root.shellQuote(root.omarchyPath + "/shell/scripts/indicators/screen-recording.sh")]
|
command: ["bash", "-lc", Util.shellQuote(root.omarchyPath + "/shell/scripts/indicators/screen-recording.sh")]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
waitForEnd: true
|
waitForEnd: true
|
||||||
onStreamFinished: root.updateIndicator("screenRecording", text)
|
onStreamFinished: root.updateIndicator("screenRecording", text)
|
||||||
@@ -571,7 +545,7 @@ Item {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: notificationSilencingProc
|
id: notificationSilencingProc
|
||||||
command: ["bash", "-lc", root.shellQuote(root.omarchyPath + "/shell/scripts/indicators/notification-silencing.sh")]
|
command: ["bash", "-lc", Util.shellQuote(root.omarchyPath + "/shell/scripts/indicators/notification-silencing.sh")]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
waitForEnd: true
|
waitForEnd: true
|
||||||
onStreamFinished: root.updateIndicator("notifications", text)
|
onStreamFinished: root.updateIndicator("notifications", text)
|
||||||
|
|||||||
@@ -316,8 +316,8 @@ Item {
|
|||||||
if (!node) return
|
if (!node) return
|
||||||
Pipewire.preferredDefaultAudioSink = node
|
Pipewire.preferredDefaultAudioSink = node
|
||||||
if (root.bar && node.id !== undefined && node.name) {
|
if (root.bar && node.id !== undefined && node.name) {
|
||||||
var idArg = root.bar.shellQuote(String(node.id))
|
var idArg = Util.shellQuote(String(node.id))
|
||||||
var nameArg = root.bar.shellQuote(String(node.name))
|
var nameArg = Util.shellQuote(String(node.name))
|
||||||
root.bar.run("wpctl set-default " + idArg + " 2>/dev/null || true; "
|
root.bar.run("wpctl set-default " + idArg + " 2>/dev/null || true; "
|
||||||
+ "pactl set-default-sink " + nameArg + " 2>/dev/null || true; "
|
+ "pactl set-default-sink " + nameArg + " 2>/dev/null || true; "
|
||||||
+ "pactl list short sink-inputs 2>/dev/null | awk '{ print $1 }' | while read -r input; do "
|
+ "pactl list short sink-inputs 2>/dev/null | awk '{ print $1 }' | while read -r input; do "
|
||||||
@@ -329,8 +329,8 @@ Item {
|
|||||||
if (!node) return
|
if (!node) return
|
||||||
Pipewire.preferredDefaultAudioSource = node
|
Pipewire.preferredDefaultAudioSource = node
|
||||||
if (root.bar && node.id !== undefined && node.name) {
|
if (root.bar && node.id !== undefined && node.name) {
|
||||||
var idArg = root.bar.shellQuote(String(node.id))
|
var idArg = Util.shellQuote(String(node.id))
|
||||||
var nameArg = root.bar.shellQuote(String(node.name))
|
var nameArg = Util.shellQuote(String(node.name))
|
||||||
root.bar.run("wpctl set-default " + idArg + " 2>/dev/null || true; "
|
root.bar.run("wpctl set-default " + idArg + " 2>/dev/null || true; "
|
||||||
+ "pactl set-default-source " + nameArg + " 2>/dev/null || true; "
|
+ "pactl set-default-source " + nameArg + " 2>/dev/null || true; "
|
||||||
+ "pactl list short source-outputs 2>/dev/null | awk '{ print $1 }' | while read -r output; do "
|
+ "pactl list short source-outputs 2>/dev/null | awk '{ print $1 }' | while read -r output; do "
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ Item {
|
|||||||
if (!net) return
|
if (!net) return
|
||||||
if (net.connected) { disconnect(net.ssid); return }
|
if (net.connected) { disconnect(net.ssid); return }
|
||||||
if (isProtected(net.security) && !net.known) {
|
if (isProtected(net.security) && !net.known) {
|
||||||
var quotedSsid = bar.shellQuote(net.ssid)
|
var quotedSsid = Util.shellQuote(net.ssid)
|
||||||
knownCheck.targetSsid = net.ssid
|
knownCheck.targetSsid = net.ssid
|
||||||
knownCheck.command = ["bash", "-c", `
|
knownCheck.command = ["bash", "-c", `
|
||||||
iwctl known-networks list 2>/dev/null \\
|
iwctl known-networks list 2>/dev/null \\
|
||||||
@@ -193,7 +193,7 @@ iwctl known-networks list 2>/dev/null \\
|
|||||||
|
|
||||||
function copyToClipboard(value) {
|
function copyToClipboard(value) {
|
||||||
if (!value || !root.bar) return
|
if (!value || !root.bar) return
|
||||||
Quickshell.execDetached(["bash", "-lc", "printf %s " + root.bar.shellQuote(value) + " | wl-copy"])
|
Quickshell.execDetached(["bash", "-lc", "printf %s " + Util.shellQuote(value) + " | wl-copy"])
|
||||||
}
|
}
|
||||||
|
|
||||||
function networkCommand() {
|
function networkCommand() {
|
||||||
@@ -374,8 +374,8 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dnsCommand(provider) {
|
function dnsCommand(provider) {
|
||||||
var command = root.bar ? root.bar.shellQuote(root.bar.omarchyPath + "/bin/omarchy-dns") : "omarchy-dns"
|
var command = root.bar ? Util.shellQuote(root.bar.omarchyPath + "/bin/omarchy-dns") : "omarchy-dns"
|
||||||
if (provider) command += " " + root.bar.shellQuote(provider)
|
if (provider) command += " " + Util.shellQuote(provider)
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,8 +383,8 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
|
|||||||
if (!root.bar || !provider || actionProc.running) return
|
if (!root.bar || !provider || actionProc.running) return
|
||||||
|
|
||||||
if (provider === "Custom") {
|
if (provider === "Custom") {
|
||||||
var launcher = root.bar.shellQuote(root.bar.omarchyPath + "/bin/omarchy-launch-floating-terminal-with-presentation")
|
var launcher = Util.shellQuote(root.bar.omarchyPath + "/bin/omarchy-launch-floating-terminal-with-presentation")
|
||||||
root.bar.run(launcher + " " + root.bar.shellQuote(root.dnsCommand(provider)))
|
root.bar.run(launcher + " " + Util.shellQuote(root.dnsCommand(provider)))
|
||||||
root.closePopout()
|
root.closePopout()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -415,7 +415,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connectKnown(ssid) {
|
function connectKnown(ssid) {
|
||||||
var quotedSsid = bar.shellQuote(ssid)
|
var quotedSsid = Util.shellQuote(ssid)
|
||||||
runAction("connect", ssid, `
|
runAction("connect", ssid, `
|
||||||
station=$(iwctl station list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk '/^[[:space:]]*wl/ { print $1; exit }')
|
station=$(iwctl station list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk '/^[[:space:]]*wl/ { print $1; exit }')
|
||||||
[[ -z $station ]] && { echo "no Wi-Fi station available" >&2; exit 1; }
|
[[ -z $station ]] && { echo "no Wi-Fi station available" >&2; exit 1; }
|
||||||
@@ -424,8 +424,8 @@ iwctl --dont-ask station "$station" connect ${quotedSsid}
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connectWithPassphrase(ssid, passphrase) {
|
function connectWithPassphrase(ssid, passphrase) {
|
||||||
var quotedSsid = bar.shellQuote(ssid)
|
var quotedSsid = Util.shellQuote(ssid)
|
||||||
var quotedPass = bar.shellQuote(passphrase)
|
var quotedPass = Util.shellQuote(passphrase)
|
||||||
runAction("connect", ssid, `
|
runAction("connect", ssid, `
|
||||||
station=$(iwctl station list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk '/^[[:space:]]*wl/ { print $1; exit }')
|
station=$(iwctl station list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk '/^[[:space:]]*wl/ { print $1; exit }')
|
||||||
[[ -z $station ]] && { echo "No Wi-Fi station available" >&2; exit 1; }
|
[[ -z $station ]] && { echo "No Wi-Fi station available" >&2; exit 1; }
|
||||||
@@ -452,7 +452,7 @@ iwctl station "$station" disconnect
|
|||||||
// success. If the station isn't on this SSID (or there is no station),
|
// success. If the station isn't on this SSID (or there is no station),
|
||||||
// skip straight to forget.
|
// skip straight to forget.
|
||||||
function forget(ssid) {
|
function forget(ssid) {
|
||||||
var quotedSsid = bar.shellQuote(ssid)
|
var quotedSsid = Util.shellQuote(ssid)
|
||||||
runAction("forget", ssid, `
|
runAction("forget", ssid, `
|
||||||
station=$(iwctl station list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk '/^[[:space:]]*wl/ { print $1; exit }')
|
station=$(iwctl station list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk '/^[[:space:]]*wl/ { print $1; exit }')
|
||||||
if [[ -n $station ]]; then
|
if [[ -n $station ]]; then
|
||||||
@@ -1098,7 +1098,7 @@ fi
|
|||||||
// held a reference to the row delegate it could be destroyed by a
|
// held a reference to the row delegate it could be destroyed by a
|
||||||
// model refresh, and a rapid second click would overwrite it and
|
// model refresh, and a rapid second click would overwrite it and
|
||||||
// misroute the first result.
|
// misroute the first result.
|
||||||
var quotedSsid = root.bar.shellQuote(row.net.ssid)
|
var quotedSsid = Util.shellQuote(row.net.ssid)
|
||||||
knownCheck.targetSsid = row.net.ssid
|
knownCheck.targetSsid = row.net.ssid
|
||||||
knownCheck.command = ["bash", "-c", `
|
knownCheck.command = ["bash", "-c", `
|
||||||
iwctl known-networks list 2>/dev/null \\
|
iwctl known-networks list 2>/dev/null \\
|
||||||
|
|||||||
@@ -552,7 +552,7 @@ Item {
|
|||||||
// Poll the weather pill text/class every minute. Local to this widget.
|
// Poll the weather pill text/class every minute. Local to this widget.
|
||||||
Process {
|
Process {
|
||||||
id: weatherProc
|
id: weatherProc
|
||||||
command: ["bash", "-lc", root.bar ? root.bar.shellQuote(root.bar.omarchyPath + "/shell/scripts/weather.sh") : ""]
|
command: ["bash", "-lc", root.bar ? Util.shellQuote(root.bar.omarchyPath + "/shell/scripts/weather.sh") : ""]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
waitForEnd: true
|
waitForEnd: true
|
||||||
onStreamFinished: root.updateWeather(text)
|
onStreamFinished: root.updateWeather(text)
|
||||||
|
|||||||
@@ -53,18 +53,6 @@ Item {
|
|||||||
else root.open("{}")
|
else root.open("{}")
|
||||||
}
|
}
|
||||||
|
|
||||||
function withAlpha(color, alpha) {
|
|
||||||
return Qt.rgba(color.r, color.g, color.b, alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
function shellQuote(value) {
|
|
||||||
return "'" + String(value || "").replace(/'/g, "'\\''") + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileUrl(path) {
|
|
||||||
return "file://" + String(path).split("/").map(encodeURIComponent).join("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeEntry(value) {
|
function normalizeEntry(value) {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return value.length > 0 ? { type: "text", text: value } : null
|
return value.length > 0 ? { type: "text", text: value } : null
|
||||||
@@ -155,7 +143,7 @@ Item {
|
|||||||
entryType: entry.type,
|
entryType: entry.type,
|
||||||
fullText: isImage ? "" : String(entry.text || ""),
|
fullText: isImage ? "" : String(entry.text || ""),
|
||||||
previewText: isImage ? "Image" : String(entry.text || "").replace(/\s+/g, " "),
|
previewText: isImage ? "Image" : String(entry.text || "").replace(/\s+/g, " "),
|
||||||
previewImage: isImage ? root.fileUrl(entry.path) : "",
|
previewImage: isImage ? Util.fileUrl(entry.path) : "",
|
||||||
path: isImage ? String(entry.path || "") : "",
|
path: isImage ? String(entry.path || "") : "",
|
||||||
mime: isImage ? String(entry.mime || "image/png") : "text/plain",
|
mime: isImage ? String(entry.mime || "image/png") : "text/plain",
|
||||||
index: outCount
|
index: outCount
|
||||||
@@ -201,9 +189,9 @@ Item {
|
|||||||
if (!row) return
|
if (!row) return
|
||||||
root.opened = false
|
root.opened = false
|
||||||
if (row.entryType === "image") {
|
if (row.entryType === "image") {
|
||||||
Quickshell.execDetached(["bash", "-lc", "wl-copy --type " + root.shellQuote(row.mime) + " < " + root.shellQuote(row.path) + "; sleep 0.15; wtype -M shift -k Insert -m shift 2>/dev/null || true"])
|
Quickshell.execDetached(["bash", "-lc", "wl-copy --type " + Util.shellQuote(row.mime) + " < " + Util.shellQuote(row.path) + "; sleep 0.15; wtype -M shift -k Insert -m shift 2>/dev/null || true"])
|
||||||
} else if (row.fullText) {
|
} else if (row.fullText) {
|
||||||
Quickshell.execDetached(["bash", "-lc", "printf %s " + root.shellQuote(row.fullText) + " | wl-copy; sleep 0.15; wtype -M shift -k Insert -m shift 2>/dev/null || true"])
|
Quickshell.execDetached(["bash", "-lc", "printf %s " + Util.shellQuote(row.fullText) + " | wl-copy; sleep 0.15; wtype -M shift -k Insert -m shift 2>/dev/null || true"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,8 +417,8 @@ Item {
|
|||||||
width: parent.width / 2 - root.contentSpacing / 2
|
width: parent.width / 2 - root.contentSpacing / 2
|
||||||
height: parent.height
|
height: parent.height
|
||||||
radius: root.cornerRadius
|
radius: root.cornerRadius
|
||||||
color: root.withAlpha(root.background, 0.5)
|
color: Util.alpha(root.background, 0.5)
|
||||||
border.color: root.withAlpha(root.border, 0.1)
|
border.color: Util.alpha(root.border, 0.1)
|
||||||
border.width: Style.normalBorderWidth
|
border.width: Style.normalBorderWidth
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
|||||||
@@ -64,10 +64,6 @@ Item {
|
|||||||
else root.open("{}")
|
else root.open("{}")
|
||||||
}
|
}
|
||||||
|
|
||||||
function withAlpha(color, alpha) {
|
|
||||||
return Qt.rgba(color.r, color.g, color.b, alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadEmojis(raw) {
|
function loadEmojis(raw) {
|
||||||
try {
|
try {
|
||||||
var data = JSON.parse(raw)
|
var data = JSON.parse(raw)
|
||||||
|
|||||||
@@ -46,14 +46,6 @@ Item {
|
|||||||
|
|
||||||
onOpenedChanged: if (!opened) layoutSettled = false
|
onOpenedChanged: if (!opened) layoutSettled = false
|
||||||
|
|
||||||
function fileUrl(path) {
|
|
||||||
return "file://" + path.split("/").map(encodeURIComponent).join("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
function shellQuote(value) {
|
|
||||||
return "'" + String(value).replace(/'/g, "'\\''") + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
function scriptPath(name) {
|
function scriptPath(name) {
|
||||||
return omarchyPath + "/shell/scripts/" + name
|
return omarchyPath + "/shell/scripts/" + name
|
||||||
}
|
}
|
||||||
@@ -72,19 +64,6 @@ Item {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode a base64-encoded UTF-8 string sent via IPC. Used for fields that
|
|
||||||
// would otherwise carry embedded newlines or tabs (image rows, raw colors
|
|
||||||
// JSON) which bash IPC arguments can't reliably round-trip.
|
|
||||||
function decodeBase64(value) {
|
|
||||||
var s = String(value || "")
|
|
||||||
if (!s) return ""
|
|
||||||
try { return Qt.atob(s) } catch (e) { return s }
|
|
||||||
}
|
|
||||||
|
|
||||||
function withAlpha(color, alpha) {
|
|
||||||
return Qt.rgba(color.r, color.g, color.b, alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
function currentPath() {
|
function currentPath() {
|
||||||
if (imageArray.length === 0 || !itemMatches(selectedIndex)) return ""
|
if (imageArray.length === 0 || !itemMatches(selectedIndex)) return ""
|
||||||
return imageArray[selectedIndex].filePath
|
return imageArray[selectedIndex].filePath
|
||||||
@@ -176,7 +155,7 @@ Item {
|
|||||||
if (releaseProc.running || doneFilesToRelease.length === 0) return
|
if (releaseProc.running || doneFilesToRelease.length === 0) return
|
||||||
|
|
||||||
var path = doneFilesToRelease.shift()
|
var path = doneFilesToRelease.shift()
|
||||||
releaseProc.command = ["bash", "-lc", ": > " + shellQuote(path)]
|
releaseProc.command = ["bash", "-lc", ": > " + Util.shellQuote(path)]
|
||||||
releaseProc.running = true
|
releaseProc.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +179,7 @@ Item {
|
|||||||
selectionFile = ""
|
selectionFile = ""
|
||||||
doneFile = ""
|
doneFile = ""
|
||||||
|
|
||||||
applyProc.command = ["bash", "-lc", "printf '%s\\n' " + shellQuote(path) + " > " + shellQuote(activeSelectionFile) + "; : > " + shellQuote(activeDoneFile)]
|
applyProc.command = ["bash", "-lc", "printf '%s\\n' " + Util.shellQuote(path) + " > " + Util.shellQuote(activeSelectionFile) + "; : > " + Util.shellQuote(activeDoneFile)]
|
||||||
applyProc.running = true
|
applyProc.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +371,7 @@ Item {
|
|||||||
doneFile: string,
|
doneFile: string,
|
||||||
showLabels: string,
|
showLabels: string,
|
||||||
filterable: string): string {
|
filterable: string): string {
|
||||||
var rows = root.decodeBase64(imageRowsB64)
|
var rows = Util.decodeBase64(imageRowsB64)
|
||||||
root.openSelector(imageDirs, rows, selectedImage, selectionFile, doneFile,
|
root.openSelector(imageDirs, rows, selectedImage, selectionFile, doneFile,
|
||||||
showLabels, filterable)
|
showLabels, filterable)
|
||||||
return "ok"
|
return "ok"
|
||||||
@@ -402,7 +381,7 @@ Item {
|
|||||||
selectedImage: string,
|
selectedImage: string,
|
||||||
showLabels: string,
|
showLabels: string,
|
||||||
filterable: string): string {
|
filterable: string): string {
|
||||||
var rows = root.decodeBase64(imageRowsB64)
|
var rows = Util.decodeBase64(imageRowsB64)
|
||||||
root.preloadRows(rows, selectedImage, showLabels, filterable)
|
root.preloadRows(rows, selectedImage, showLabels, filterable)
|
||||||
return "ok"
|
return "ok"
|
||||||
}
|
}
|
||||||
@@ -576,7 +555,7 @@ Item {
|
|||||||
// Load only the initial/visited nearby images, but keep the
|
// Load only the initial/visited nearby images, but keep the
|
||||||
// source once activated so Qt does not tear textures down as
|
// source once activated so Qt does not tear textures down as
|
||||||
// selection moves through the carousel.
|
// selection moves through the carousel.
|
||||||
source: item.sourceActivated && item.thumbnailPath ? root.fileUrl(item.thumbnailPath) : ""
|
source: item.sourceActivated && item.thumbnailPath ? Util.fileUrl(item.thumbnailPath) : ""
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
cache: true
|
cache: true
|
||||||
@@ -585,7 +564,7 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: root.withAlpha(root.dimColor, item.selected ? 0 : 0.42)
|
color: Util.alpha(root.dimColor, item.selected ? 0 : 0.42)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,7 +603,7 @@ Item {
|
|||||||
text: root.currentLabel()
|
text: root.currentLabel()
|
||||||
color: root.foreground
|
color: root.foreground
|
||||||
style: Text.Outline
|
style: Text.Outline
|
||||||
styleColor: root.withAlpha(root.dimColor, 0.7)
|
styleColor: Util.alpha(root.dimColor, 0.7)
|
||||||
font.pixelSize: Style.font.display
|
font.pixelSize: Style.font.display
|
||||||
font.weight: Font.DemiBold
|
font.weight: Font.DemiBold
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -641,7 +620,7 @@ Item {
|
|||||||
color: root.foreground
|
color: root.foreground
|
||||||
opacity: 0.85
|
opacity: 0.85
|
||||||
style: Text.Outline
|
style: Text.Outline
|
||||||
styleColor: root.withAlpha(root.dimColor, 0.7)
|
styleColor: Util.alpha(root.dimColor, 0.7)
|
||||||
font.pixelSize: Style.font.title
|
font.pixelSize: Style.font.title
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
|||||||
@@ -27,10 +27,9 @@ Item {
|
|||||||
signal clearFailureRequested()
|
signal clearFailureRequested()
|
||||||
signal wakeRequested()
|
signal wakeRequested()
|
||||||
|
|
||||||
function withAlpha(color, alpha) {
|
// Cache-busts the lock background by appending `?v=`. Adding a query
|
||||||
return Qt.rgba(color.r, color.g, color.b, alpha)
|
// string keeps Image's loader happy while forcing it to reload when the
|
||||||
}
|
// user picks a new background mid-session.
|
||||||
|
|
||||||
function fileUrl(path) {
|
function fileUrl(path) {
|
||||||
if (!path) return ""
|
if (!path) return ""
|
||||||
var encoded = String(path).split("/").map(encodeURIComponent).join("/")
|
var encoded = String(path).split("/").map(encodeURIComponent).join("/")
|
||||||
|
|||||||
@@ -88,14 +88,6 @@ Item {
|
|||||||
? Math.min(contentMargin * 2 + headerHeight + (mode === "input" ? 0 : contentSpacing + visibleRowsHeight), panel.height - Style.gapsOut * 2)
|
? Math.min(contentMargin * 2 + headerHeight + (mode === "input" ? 0 : contentSpacing + visibleRowsHeight), panel.height - Style.gapsOut * 2)
|
||||||
: Math.min(Math.max(Style.space(220), contentMargin * 2 + headerHeight + contentSpacing + visibleRowsHeight), panel.height - Style.gapsOut * 2)
|
: Math.min(Math.max(Style.space(220), contentMargin * 2 + headerHeight + contentSpacing + visibleRowsHeight), panel.height - Style.gapsOut * 2)
|
||||||
|
|
||||||
function withAlpha(color, alpha) {
|
|
||||||
return Qt.rgba(color.r, color.g, color.b, alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
function shellQuote(value) {
|
|
||||||
return "'" + String(value || "").replace(/'/g, "'\\''") + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
function finishRequest(selection) {
|
function finishRequest(selection) {
|
||||||
if (!root.requestActive || !root.doneFile) {
|
if (!root.requestActive || !root.doneFile) {
|
||||||
root.opened = false
|
root.opened = false
|
||||||
@@ -109,9 +101,9 @@ Item {
|
|||||||
root.doneFile = ""
|
root.doneFile = ""
|
||||||
|
|
||||||
if (selection === null || selection === undefined) {
|
if (selection === null || selection === undefined) {
|
||||||
resultProc.command = ["bash", "-lc", ": > " + root.shellQuote(activeDoneFile)]
|
resultProc.command = ["bash", "-lc", ": > " + Util.shellQuote(activeDoneFile)]
|
||||||
} else {
|
} else {
|
||||||
resultProc.command = ["bash", "-lc", "printf '%s\\n' " + root.shellQuote(selection) + " > " + root.shellQuote(activeSelectionFile) + "; : > " + root.shellQuote(activeDoneFile)]
|
resultProc.command = ["bash", "-lc", "printf '%s\\n' " + Util.shellQuote(selection) + " > " + Util.shellQuote(activeSelectionFile) + "; : > " + Util.shellQuote(activeDoneFile)]
|
||||||
}
|
}
|
||||||
resultProc.running = true
|
resultProc.running = true
|
||||||
}
|
}
|
||||||
@@ -1061,7 +1053,7 @@ Item {
|
|||||||
anchors.rightMargin: Style.space(4)
|
anchors.rightMargin: Style.space(4)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: Style.spacing.hairline
|
height: Style.spacing.hairline
|
||||||
color: root.withAlpha(root.foreground, 0.2)
|
color: Util.alpha(root.foreground, 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ Item {
|
|||||||
readonly property int cardWidth: Math.min(Style.space(312), Math.max(Style.space(260), panel.width - Style.gapsOut * 2))
|
readonly property int cardWidth: Math.min(Style.space(312), Math.max(Style.space(260), panel.width - Style.gapsOut * 2))
|
||||||
readonly property int cardHeight: panel.height > 0 ? Math.min(fieldHeight + contentMargin * 2, panel.height - Style.gapsOut * 2) : fieldHeight + contentMargin * 2
|
readonly property int cardHeight: panel.height > 0 ? Math.min(fieldHeight + contentMargin * 2, panel.height - Style.gapsOut * 2) : fieldHeight + contentMargin * 2
|
||||||
|
|
||||||
function withAlpha(color, alpha) {
|
|
||||||
return Qt.rgba(color.r, color.g, color.b, alpha)
|
|
||||||
}
|
|
||||||
function promptLooksFingerprint(text) {
|
function promptLooksFingerprint(text) {
|
||||||
var s = String(text || "").toLowerCase()
|
var s = String(text || "").toLowerCase()
|
||||||
return s.indexOf("finger") !== -1 || s.indexOf("fprint") !== -1 || s.indexOf("swipe") !== -1
|
return s.indexOf("finger") !== -1 || s.indexOf("fprint") !== -1 || s.indexOf("swipe") !== -1
|
||||||
|
|||||||
@@ -173,33 +173,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- draft helpers ------------------------------------------
|
// ---------------- draft helpers ------------------------------------------
|
||||||
function cloneJson(value) { return JSON.parse(JSON.stringify(value || null)) }
|
|
||||||
function isPlainObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value) }
|
|
||||||
|
|
||||||
function normalizeLayoutEntry(entry) {
|
|
||||||
if (typeof entry === "string") return { id: entry }
|
|
||||||
if (isPlainObject(entry) && entry.id) return cloneJson(entry)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeLayout(layout) {
|
|
||||||
var sections = ["left", "center", "right"]
|
|
||||||
var result = {}
|
|
||||||
for (var i = 0; i < sections.length; i++) {
|
|
||||||
var s = sections[i]
|
|
||||||
var arr = []
|
|
||||||
var src = (layout && layout[s]) || []
|
|
||||||
for (var j = 0; j < src.length; j++) {
|
|
||||||
var entry = normalizeLayoutEntry(src[j])
|
|
||||||
if (entry) arr.push(entry)
|
|
||||||
}
|
|
||||||
result[s] = arr
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeDraft(source) {
|
function normalizeDraft(source) {
|
||||||
var bar = isPlainObject(source.bar) ? source.bar : {}
|
var bar = Util.isPlainObject(source.bar) ? source.bar : {}
|
||||||
var plugins = Array.isArray(source.plugins) ? source.plugins.slice() : []
|
var plugins = Array.isArray(source.plugins) ? source.plugins.slice() : []
|
||||||
return {
|
return {
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -208,10 +183,10 @@ Item {
|
|||||||
transparent: bar.transparent === true,
|
transparent: bar.transparent === true,
|
||||||
centerAnchor: String(bar.centerAnchor || ""),
|
centerAnchor: String(bar.centerAnchor || ""),
|
||||||
fontFamily: "monospace",
|
fontFamily: "monospace",
|
||||||
layout: normalizeLayout(bar.layout || {})
|
layout: Util.normalizeLayout(bar.layout || {})
|
||||||
},
|
},
|
||||||
plugins: plugins
|
plugins: plugins
|
||||||
.map(normalizeLayoutEntry)
|
.map(Util.normalizeLayoutEntry)
|
||||||
.filter(function(e) {
|
.filter(function(e) {
|
||||||
if (!e) return false
|
if (!e) return false
|
||||||
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[e.id] : null
|
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[e.id] : null
|
||||||
@@ -227,7 +202,7 @@ Item {
|
|||||||
if (diskText) {
|
if (diskText) {
|
||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(diskText)
|
var parsed = JSON.parse(diskText)
|
||||||
if (isPlainObject(parsed) && parsed.version === 1) defaults = parsed
|
if (Util.isPlainObject(parsed) && parsed.version === 1) defaults = parsed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Bad shell-defaults JSON, falling back to builtin:", e)
|
console.warn("Bad shell-defaults JSON, falling back to builtin:", e)
|
||||||
defaults = builtinShellConfig
|
defaults = builtinShellConfig
|
||||||
@@ -240,7 +215,7 @@ Item {
|
|||||||
if (userText.trim()) {
|
if (userText.trim()) {
|
||||||
try {
|
try {
|
||||||
var u = JSON.parse(userText)
|
var u = JSON.parse(userText)
|
||||||
if (isPlainObject(u) && u.version === 1) source = u
|
if (Util.isPlainObject(u) && u.version === 1) source = u
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("shell.json parse failed in panel:", e)
|
console.warn("shell.json parse failed in panel:", e)
|
||||||
}
|
}
|
||||||
@@ -256,7 +231,7 @@ Item {
|
|||||||
|
|
||||||
function defaultBarDraft() {
|
function defaultBarDraft() {
|
||||||
var source = defaultConfig
|
var source = defaultConfig
|
||||||
if (!isPlainObject(source) || !isPlainObject(source.bar) || !isPlainObject(source.bar.layout)) {
|
if (!Util.isPlainObject(source) || !Util.isPlainObject(source.bar) || !Util.isPlainObject(source.bar.layout)) {
|
||||||
source = builtinShellConfig
|
source = builtinShellConfig
|
||||||
} else {
|
} else {
|
||||||
var l = source.bar.layout
|
var l = source.bar.layout
|
||||||
@@ -267,8 +242,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetBarToDefaults() {
|
function resetBarToDefaults() {
|
||||||
var next = cloneJson(draft)
|
var next = Util.cloneJson(draft)
|
||||||
next.bar = cloneJson(defaultBarDraft())
|
next.bar = Util.cloneJson(defaultBarDraft())
|
||||||
draft = next
|
draft = next
|
||||||
draftRevision++
|
draftRevision++
|
||||||
persistDraft()
|
persistDraft()
|
||||||
@@ -287,7 +262,7 @@ Item {
|
|||||||
function mutateSection(section, mutator) {
|
function mutateSection(section, mutator) {
|
||||||
var arr = sectionArray(section).slice()
|
var arr = sectionArray(section).slice()
|
||||||
mutator(arr)
|
mutator(arr)
|
||||||
var nextDraft = cloneJson(draft)
|
var nextDraft = Util.cloneJson(draft)
|
||||||
if (section === "plugins") nextDraft.plugins = arr
|
if (section === "plugins") nextDraft.plugins = arr
|
||||||
else nextDraft.bar.layout[section] = arr
|
else nextDraft.bar.layout[section] = arr
|
||||||
draft = nextDraft
|
draft = nextDraft
|
||||||
@@ -314,7 +289,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateEntry(section, index, newEntry) {
|
function updateEntry(section, index, newEntry) {
|
||||||
mutateSection(section, function(a) { a[index] = cloneJson(newEntry) })
|
mutateSection(section, function(a) { a[index] = Util.cloneJson(newEntry) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- widget catalog -----------------------------------------
|
// ---------------- widget catalog -----------------------------------------
|
||||||
@@ -491,7 +466,7 @@ Item {
|
|||||||
property var widgetDialogEntry: ({})
|
property var widgetDialogEntry: ({})
|
||||||
|
|
||||||
function openWidgetSettings(sectionKey, entryIndex, entry) {
|
function openWidgetSettings(sectionKey, entryIndex, entry) {
|
||||||
widgetDialogEntry = root.cloneJson(entry)
|
widgetDialogEntry = Util.cloneJson(entry)
|
||||||
widgetDialogSection = sectionKey
|
widgetDialogSection = sectionKey
|
||||||
widgetDialogIndex = entryIndex
|
widgetDialogIndex = entryIndex
|
||||||
widgetDialogVisible = true
|
widgetDialogVisible = true
|
||||||
@@ -509,7 +484,7 @@ Item {
|
|||||||
function discardWidgetSettings() { widgetDialogVisible = false }
|
function discardWidgetSettings() { widgetDialogVisible = false }
|
||||||
|
|
||||||
function widgetDialogFieldChanged(key, value) {
|
function widgetDialogFieldChanged(key, value) {
|
||||||
var copy = root.cloneJson(widgetDialogEntry)
|
var copy = Util.cloneJson(widgetDialogEntry)
|
||||||
copy[key] = value
|
copy[key] = value
|
||||||
widgetDialogEntry = copy
|
widgetDialogEntry = copy
|
||||||
}
|
}
|
||||||
@@ -777,7 +752,7 @@ Item {
|
|||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
onChanged: function(v) {
|
onChanged: function(v) {
|
||||||
if (root.draft.bar.position === v) return
|
if (root.draft.bar.position === v) return
|
||||||
var next = root.cloneJson(root.draft)
|
var next = Util.cloneJson(root.draft)
|
||||||
next.bar.position = v
|
next.bar.position = v
|
||||||
root.draft = next
|
root.draft = next
|
||||||
root.markDirty()
|
root.markDirty()
|
||||||
@@ -800,7 +775,7 @@ Item {
|
|||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
cornerRadius: root.cornerRadius
|
cornerRadius: root.cornerRadius
|
||||||
onChanged: function(v) {
|
onChanged: function(v) {
|
||||||
var next = root.cloneJson(root.draft)
|
var next = Util.cloneJson(root.draft)
|
||||||
next.bar.centerAnchor = v === "(none)" ? "" : v
|
next.bar.centerAnchor = v === "(none)" ? "" : v
|
||||||
root.draft = next
|
root.draft = next
|
||||||
root.markDirty()
|
root.markDirty()
|
||||||
@@ -817,7 +792,7 @@ Item {
|
|||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
checked: root.draft.bar.transparent === true
|
checked: root.draft.bar.transparent === true
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var next = root.cloneJson(root.draft)
|
var next = Util.cloneJson(root.draft)
|
||||||
next.bar.transparent = !(next.bar.transparent === true)
|
next.bar.transparent = !(next.bar.transparent === true)
|
||||||
root.draft = next
|
root.draft = next
|
||||||
root.markDirty()
|
root.markDirty()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
import qs.Commons
|
||||||
|
|
||||||
// Instance, not a singleton — see BarWidgetRegistry for rationale.
|
// Instance, not a singleton — see BarWidgetRegistry for rationale.
|
||||||
QtObject {
|
QtObject {
|
||||||
@@ -29,14 +30,6 @@ QtObject {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- helpers
|
// ---------------------------------------------------------------- helpers
|
||||||
|
|
||||||
function isPlainObject(value) {
|
|
||||||
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileUrl(path) {
|
|
||||||
return "file://" + String(path).split("/").map(encodeURIComponent).join("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSafeEntryPoint(value) {
|
function isSafeEntryPoint(value) {
|
||||||
if (typeof value !== "string" || value.length === 0) return false
|
if (typeof value !== "string" || value.length === 0) return false
|
||||||
if (value.charAt(0) === "/") return false
|
if (value.charAt(0) === "/") return false
|
||||||
@@ -45,7 +38,7 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateManifest(manifest, sourcePath) {
|
function validateManifest(manifest, sourcePath) {
|
||||||
if (!isPlainObject(manifest)) {
|
if (!Util.isPlainObject(manifest)) {
|
||||||
console.warn("PluginRegistry: manifest is not an object at " + sourcePath)
|
console.warn("PluginRegistry: manifest is not an object at " + sourcePath)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -69,7 +62,7 @@ QtObject {
|
|||||||
console.warn("PluginRegistry: kinds must be a non-empty array at " + sourcePath)
|
console.warn("PluginRegistry: kinds must be a non-empty array at " + sourcePath)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (!isPlainObject(manifest.entryPoints)) {
|
if (!Util.isPlainObject(manifest.entryPoints)) {
|
||||||
console.warn("PluginRegistry: entryPoints must be an object at " + sourcePath)
|
console.warn("PluginRegistry: entryPoints must be an object at " + sourcePath)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -87,7 +80,7 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function entryPointUrl(manifest, kind) {
|
function entryPointUrl(manifest, kind) {
|
||||||
if (!isPlainObject(manifest)) return ""
|
if (!Util.isPlainObject(manifest)) return ""
|
||||||
var ep = manifest.entryPoints ? manifest.entryPoints[kind] : null
|
var ep = manifest.entryPoints ? manifest.entryPoints[kind] : null
|
||||||
if (!ep) return ""
|
if (!ep) return ""
|
||||||
var dir = manifest.__sourceDir || ""
|
var dir = manifest.__sourceDir || ""
|
||||||
@@ -100,7 +93,7 @@ QtObject {
|
|||||||
console.warn("PluginRegistry: entry point escapes sourceDir: " + resolved)
|
console.warn("PluginRegistry: entry point escapes sourceDir: " + resolved)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return fileUrl(resolved)
|
return Util.fileUrl(resolved)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enabled = the plugin id is referenced somewhere in shell.json. That can
|
// Enabled = the plugin id is referenced somewhere in shell.json. That can
|
||||||
@@ -125,8 +118,8 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findEntryLocation(config, id) {
|
function findEntryLocation(config, id) {
|
||||||
if (!isPlainObject(config)) return { found: false }
|
if (!Util.isPlainObject(config)) return { found: false }
|
||||||
if (isPlainObject(config.bar) && isPlainObject(config.bar.layout)) {
|
if (Util.isPlainObject(config.bar) && Util.isPlainObject(config.bar.layout)) {
|
||||||
var sections = ["left", "center", "right"]
|
var sections = ["left", "center", "right"]
|
||||||
for (var s = 0; s < sections.length; s++) {
|
for (var s = 0; s < sections.length; s++) {
|
||||||
var arr = config.bar.layout[sections[s]]
|
var arr = config.bar.layout[sections[s]]
|
||||||
@@ -157,8 +150,8 @@ QtObject {
|
|||||||
var isBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1
|
var isBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1
|
||||||
shellConfigMutator(function(config) {
|
shellConfigMutator(function(config) {
|
||||||
// Ensure shape exists.
|
// Ensure shape exists.
|
||||||
if (!isPlainObject(config.bar)) config.bar = { layout: { left: [], center: [], right: [] } }
|
if (!Util.isPlainObject(config.bar)) config.bar = { layout: { left: [], center: [], right: [] } }
|
||||||
if (!isPlainObject(config.bar.layout)) config.bar.layout = { left: [], center: [], right: [] }
|
if (!Util.isPlainObject(config.bar.layout)) config.bar.layout = { left: [], center: [], right: [] }
|
||||||
if (!Array.isArray(config.plugins)) config.plugins = []
|
if (!Array.isArray(config.plugins)) config.plugins = []
|
||||||
var location = findEntryLocation(config, key)
|
var location = findEntryLocation(config, key)
|
||||||
if (value && !location.found) {
|
if (value && !location.found) {
|
||||||
|
|||||||
+7
-11
@@ -56,21 +56,17 @@ ShellRoot {
|
|||||||
property var shellConfig: builtinShellConfig
|
property var shellConfig: builtinShellConfig
|
||||||
property bool suppressUserReload: false
|
property bool suppressUserReload: false
|
||||||
|
|
||||||
function isPlainObject(value) {
|
|
||||||
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyShellConfig() {
|
function applyShellConfig() {
|
||||||
// Decide which source is canonical: a valid user shell.json overrides
|
// Decide which source is canonical: a valid user shell.json overrides
|
||||||
// defaults entirely; otherwise fall back to defaults. We do not deep-merge.
|
// defaults entirely; otherwise fall back to defaults. We do not deep-merge.
|
||||||
var defaults = isPlainObject(defaultsConfig) ? defaultsConfig : builtinShellConfig
|
var defaults = Util.isPlainObject(defaultsConfig) ? defaultsConfig : builtinShellConfig
|
||||||
var user = null
|
var user = null
|
||||||
var userText = userConfigFile.text() || ""
|
var userText = userConfigFile.text() || ""
|
||||||
if (userText.trim()) {
|
if (userText.trim()) {
|
||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(userText)
|
var parsed = JSON.parse(userText)
|
||||||
if (isPlainObject(parsed) && parsed.version === 1) user = parsed
|
if (Util.isPlainObject(parsed) && parsed.version === 1) user = parsed
|
||||||
else if (isPlainObject(parsed)) console.warn("shell.json missing version: 1, using defaults")
|
else if (Util.isPlainObject(parsed)) console.warn("shell.json missing version: 1, using defaults")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("shell.json parse failed, using defaults:", e)
|
console.warn("shell.json parse failed, using defaults:", e)
|
||||||
}
|
}
|
||||||
@@ -87,7 +83,7 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(text)
|
var parsed = JSON.parse(text)
|
||||||
if (isPlainObject(parsed) && parsed.version === 1) defaultsConfig = parsed
|
if (Util.isPlainObject(parsed) && parsed.version === 1) defaultsConfig = parsed
|
||||||
else defaultsConfig = builtinShellConfig
|
else defaultsConfig = builtinShellConfig
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("shell-defaults.json parse failed, using builtin:", e)
|
console.warn("shell-defaults.json parse failed, using builtin:", e)
|
||||||
@@ -104,7 +100,7 @@ ShellRoot {
|
|||||||
userConfigFile.setText(JSON.stringify(payload, null, 2) + "\n")
|
userConfigFile.setText(JSON.stringify(payload, null, 2) + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var barConfig: shellConfig && isPlainObject(shellConfig.bar) ? shellConfig.bar : builtinShellConfig.bar
|
readonly property var barConfig: shellConfig && Util.isPlainObject(shellConfig.bar) ? shellConfig.bar : builtinShellConfig.bar
|
||||||
FileView {
|
FileView {
|
||||||
id: defaultsFile
|
id: defaultsFile
|
||||||
path: shell.defaultsPath
|
path: shell.defaultsPath
|
||||||
@@ -262,8 +258,8 @@ ShellRoot {
|
|||||||
function updateEntryInline(moduleName, settings) {
|
function updateEntryInline(moduleName, settings) {
|
||||||
var stripped = String(moduleName)
|
var stripped = String(moduleName)
|
||||||
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
|
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
|
||||||
if (!isPlainObject(copy.bar)) copy.bar = { layout: { left: [], center: [], right: [] } }
|
if (!Util.isPlainObject(copy.bar)) copy.bar = { layout: { left: [], center: [], right: [] } }
|
||||||
if (!isPlainObject(copy.bar.layout)) copy.bar.layout = { left: [], center: [], right: [] }
|
if (!Util.isPlainObject(copy.bar.layout)) copy.bar.layout = { left: [], center: [], right: [] }
|
||||||
if (!Array.isArray(copy.plugins)) copy.plugins = []
|
if (!Array.isArray(copy.plugins)) copy.plugins = []
|
||||||
|
|
||||||
var sections = ["left", "center", "right"]
|
var sections = ["left", "center", "right"]
|
||||||
|
|||||||
Reference in New Issue
Block a user