Repository navigation
Expand file tree
/
Copy pathService.qml
More file actions
185 lines (161 loc) · 8.27 KB
/
Copy pathService.qml
File metadata and controls
185 lines (161 loc) · 8.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import QtQuick
import Quickshell
import Quickshell.Io
import "stores"
// Installs the launcher entry, so the panel is reachable from SUPER+SPACE
// without the user wiring up a keybind first. Omarchy has no install hook and
// no manifest field for registering one, so it happens here instead.
//
// Only a file carrying the X-Lacquer-Managed marker is ever written or
// deleted: if something else already owns that path, it is left alone.
QtObject {
id: root
property string omarchyPath: ""
property var shell: null
property var manifest: null
// The theme shuffle runs here rather than in the panel: the panel only exists
// once it has been opened, and a shuffle has to act on boot and at sunset
// whether or not anyone opens Lacquer.
readonly property ShuffleEngine shuffle: ShuffleEngine { }
// The pages the panel has, as it reports them when it is first built. Empty
// until then, which showSection treats as "cannot tell yet".
property var knownSections: []
readonly property string pluginId: (manifest && manifest.id) ? String(manifest.id)
: "io.github.deunnis.lacquer"
// Lacquer's IPC lives here rather than in the panel, because the panel only
// exists once it has been opened: a keybinding or a script that asks Lacquer
// to do something should not depend on someone having opened it first.
//
// omarchy-shell lacquer open '{"section":"theme"}'
// omarchy-shell lacquer applyTheme gruvbox
// omarchy-shell lacquer currentTheme
// omarchy-shell lacquer shuffleStatus
//
// Opening and closing go through the host (shell.summon/hide), which is what
// `omarchy-shell shell summon io.github.deunnis.lacquer` does too.
// A QtObject has no default property, so the handler is held by name.
readonly property IpcHandler ipc: IpcHandler {
target: "lacquer"
function open(payload: string): string {
if (!root.shell) return "the shell did not hand Lacquer its panel api"
root.shell.summon(root.pluginId, payload && payload.length > 0 ? payload : "{}")
return "ok"
}
function close(): string {
if (!root.shell) return "the shell did not hand Lacquer its panel api"
root.shell.hide(root.pluginId)
return "ok"
}
function toggle(): string {
if (!root.shell) return "the shell did not hand Lacquer its panel api"
root.shell.toggle(root.pluginId, "{}")
return "ok"
}
// Open on one page. The name is the section's id, as listed in the README.
function showSection(id: string): string {
var name = String(id)
if (!/^[a-z]+$/.test(name)) return "no section called " + name
// The panel hands over its list of pages the first time it is built;
// until then an unknown name is taken on trust rather than refused, and
// the panel falls back to Home as it always did.
if (root.knownSections.length > 0 && root.knownSections.indexOf(name) < 0)
return "no section called " + name
return open(JSON.stringify({ section: name }))
}
function currentTheme(): string {
return root.shuffle ? root.shuffle.currentThemeSlug : ""
}
// A theme by its folder name, the way `omarchy theme set` takes it.
function applyTheme(slug: string): string {
var name = String(slug)
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(name)) return "no theme called " + name
// The switch is handed to Omarchy and cannot be waited for, so a name
// that is not installed is caught here rather than answered "ok" and
// silently doing nothing. Before the themes have been scanned the name
// is taken on trust, which is better than refusing a real one.
var known = root.shuffle ? root.shuffle.themeBySlug : null
if (known && Object.keys(known).length > 0 && !known[name])
return "no theme called " + name
Quickshell.execDetached(["omarchy-theme-set", name])
return "ok"
}
// Only a wallpaper of the theme that is on: anything else is refused, so a
// stray call cannot point the desktop at an arbitrary file.
function setWallpaper(path: string): string {
var backgrounds = Quickshell.env("HOME") + "/.local/state/omarchy/current/theme/backgrounds/"
var real = String(path)
if (real.indexOf(backgrounds) !== 0 || real.indexOf("..") >= 0) return "not a wallpaper of the current theme"
Quickshell.execDetached(["omarchy-theme-bg-set", real])
return "ok"
}
function shuffleStatus(): string {
var e = root.shuffle
if (!e) return JSON.stringify({ engine: null })
return JSON.stringify({ dormant: e.dormant, active: e.active, stateLoaded: e.stateLoaded,
bootEnabled: e.st.enabled, dayNight: e.st.schedule.enabled,
pool: (e.st.pool || []).length, themes: e.themes.length,
lastBootId: e.st.lastBootId, currentBootId: e.currentBootId })
}
}
// 4.0.3 strips __sourceDir from every third-party manifest
// (shell.qml publicPluginManifest), so the plugin's own directory has to come
// from the QML file's own URL rather than from the host.
readonly property string pluginDir: {
var url = String(Qt.resolvedUrl("."))
if (url.indexOf("file://") === 0) url = url.substring(7)
return decodeURIComponent(url).replace(/\/+$/, "")
}
readonly property string dest: Quickshell.env("HOME") + "/.local/share/applications/lacquer.desktop"
readonly property string marker: "^X-Lacquer-Managed=true$"
readonly property string installScript:
'[ -f "$1" ] || exit 0\n'
+ 'if [ -e "$2" ] && ! grep -q "$3" "$2"; then exit 0; fi\n'
+ 'mkdir -p "${2%/*}" || exit 0\n'
+ 'tmp=$2.lacquer.new\n'
+ 'sed "s|@ICON@|$4|" "$1" > "$tmp" || exit 0\n'
+ 'if cmp -s "$tmp" "$2"; then rm -f "$tmp"; else mv -f "$tmp" "$2"; fi\n'
// Same ownership rule as the launcher entry, copied verbatim.
readonly property string hookScript:
'mkdir -p "$4" && chmod 700 "$4"\n'
+ '[ -f "$1" ] || exit 0\n'
+ 'if [ -e "$2" ] && ! grep -q "$3" "$2"; then exit 0; fi\n'
+ 'mkdir -p "${2%/*}" || exit 0\n'
+ 'if cmp -s "$1" "$2"; then exit 0; fi\n'
+ 'cp -f "$1" "$2.lacquer.new" && mv -f "$2.lacquer.new" "$2"\n'
readonly property string removeScript:
'grep -q "$2" "$1" 2>/dev/null && rm -f "$1"\n'
property bool installed: false
// Pinned GTK, colour-scheme and icon choices are put back after every theme
// switch by a hook, so they hold even while Lacquer is closed. The hook
// removes itself if Lacquer is ever uninstalled.
readonly property string hookDest: Quickshell.env("HOME") + "/.config/omarchy/hooks/theme-set.d/lacquer-reapply"
readonly property string stateDir: Quickshell.env("HOME") + "/.local/state/omarchy/io.github.deunnis.lacquer"
// Setting a monospace font restarts the shell. The panel leaves a marker
// naming its section; a fresh one reopens Lacquer right where it was.
readonly property string reopenScript:
'f=$1; [ -f "$f" ] && [ ! -L "$f" ] || exit 0\n'
+ 'age=$(( $(date +%s) - $(stat -c %Y "$f") ))\n'
+ 'sec=$(timeout 2 head -c 40 "$f" | tr -cd "a-z-")\n'
+ 'rm -f "$f"\n'
+ '[ "$age" -lt 120 ] && [ -n "$sec" ] || exit 0\n'
+ 'for i in 1 2 3 4 5 6 7 8 9 10; do\n'
+ ' sleep 1\n'
+ ' [ "$(omarchy-shell shell summon io.github.deunnis.lacquer "{\\"section\\":\\"$sec\\"}" 2>/dev/null)" = ok ] && exit 0\n'
+ 'done\n'
// Installing on completion rather than on a manifest signal: the directory
// no longer comes from the host, so there is nothing to wait for.
Component.onCompleted: {
installed = true
Quickshell.execDetached(["sh", "-c", installScript, "sh",
pluginDir + "/lacquer.desktop", dest, marker, pluginDir + "/icon.png"])
Quickshell.execDetached(["sh", "-c", hookScript, "sh",
pluginDir + "/lacquer-reapply.hook", hookDest, "^# X-Lacquer-Managed=true$", stateDir])
Quickshell.execDetached(["sh", "-c", reopenScript, "sh", stateDir + "/reopen"])
}
// Reached on disable and on remove alike: omarchy-plugin-remove disables
// first, so the service is torn down while the entry is still ours.
Component.onDestruction: {
if (!installed) return
Quickshell.execDetached(["sh", "-c", removeScript, "sh", dest, marker])
}
}