Switch the image picker to IpcHandler

This commit is contained in:
Ryan Hughes
2026-05-14 02:21:48 -04:00
parent 4e8bf99240
commit 5c0f5669c9
8 changed files with 115 additions and 110 deletions
@@ -8,11 +8,11 @@ so they cannot be disabled.
User-installed plugins live alongside these conceptually but on disk under
`~/.config/omarchy/plugins/<plugin-id>/` rather than in this directory.
| Plugin | id | kinds | activation | entry point |
|-----------------------|-------------------------------|--------------|-------------|----------------------------|
| Bar | `omarchy.bar` | `bar` | persistent | `bar/Bar.qml` |
| Bar settings | `omarchy.bar-settings` | `panel` | on-demand | `bar-settings/BarSettingsPanel.qml` |
| Background switcher | `omarchy.background-switcher` | `overlay` | on-demand | `background-switcher/BackgroundSwitcher.qml` |
| Plugin | id | kinds | activation | entry point |
|------------------|--------------------------|--------------|-------------|----------------------------------------------|
| Bar | `omarchy.bar` | `bar` | persistent | `bar/Bar.qml` |
| Bar settings | `omarchy.bar-settings` | `panel` | on-demand | `bar-settings/BarSettingsPanel.qml` |
| Image picker | `omarchy.image-picker` | `overlay` | on-demand | `image-picker/ImagePicker.qml` |
## Bar
@@ -33,13 +33,31 @@ Visual editor for the bar layout. Summoned by
- a Plugin Manager tab for enabling/disabling third-party plugins
- a dynamic settings form driven by each widget's manifest schema
## Background switcher
## Image picker
Fullscreen wallpaper / image picker overlay. Summoned for ad-hoc wallpaper
selection. Keeps its legacy unix socket protocol at
`/run/user/<uid>/omarchy-image-selector.sock` so existing callers like
`omarchy-menu-images` keep working without any wire-format change. The
plugin has `keepLoaded: true` so the socket survives between summons.
Fullscreen image-grid selector overlay. Used by `omarchy-menu-images`
(wallpaper picker) and `omarchy-theme-switcher` (theme picker) and any
other caller that wants to present a directory of images with previews.
Two ways to drive it:
- Shell-level summon: `omarchy-shell-ipc shell summon omarchy.image-picker '<jsonPayload>'`.
The payload can carry `imageDirs`, `imageRows`, `selectedImage`,
`selectionFile`, `doneFile`, `colorsFile`, `colorsRaw`, `showLabels`,
`filterable`. Best for in-shell callers that already speak JSON.
- Direct IPC target: `omarchy-shell-ipc image-selector open <imageDirs> <imageRowsB64> <selectedImage> <selectionFile> <doneFile> <colorsFile> <colorsRawB64> <showLabels> <filterable>`.
Positional args; `imageRowsB64` and `colorsRawB64` are base64-encoded so
embedded newlines / tabs survive the bash argv handoff. This is what
`omarchy-menu-images` uses.
The selection round-trip remains file-based: callers create a
`selection_file` and `done_file` (both `mktemp`), pass the paths, and
poll `done_file` for existence. The plugin writes the chosen path into
`selection_file` and touches `done_file` when it's done. `cancel` IPC
clears it without writing a selection.
The plugin has `keepLoaded: true` so the layer-shell window survives
between summons within a single shell session.
## Coming soon
@@ -1,16 +0,0 @@
{
"schemaVersion": 1,
"id": "omarchy.background-switcher",
"name": "Background switcher",
"version": "1.0.0",
"author": "Omarchy",
"description": "Wallpaper / image picker overlay",
"kinds": ["overlay"],
"activation": "on-demand",
"keepLoaded": true,
"entryPoints": { "overlay": "BackgroundSwitcher.qml" },
"ipc": {
"summon": "background-switcher",
"legacySocket": "omarchy-image-selector.sock"
}
}
@@ -22,7 +22,7 @@ Item {
property string imageRows: ""
property string selectionFile: Quickshell.env("OMARCHY_IMAGE_SELECTOR_SELECTION_FILE") || Quickshell.env("OMARCHY_BACKGROUND_SELECTION_FILE")
property string selectedImage: Quickshell.env("OMARCHY_IMAGE_SELECTOR_SELECTED")
property string colorsFile: Quickshell.env("OMARCHY_IMAGE_SELECTOR_COLORS_FILE") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/background-switcher-colors.json")
property string colorsFile: Quickshell.env("OMARCHY_IMAGE_SELECTOR_COLORS_FILE") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/image-picker-colors.json")
property int selectedIndex: 0
property bool imagesLoaded: false
property bool opened: false
@@ -34,7 +34,6 @@ Item {
property string doneFile: ""
property string filterText: ""
property var doneFilesToRelease: []
property string socketPath: (Quickshell.env("XDG_RUNTIME_DIR") || ("/run/user/" + Quickshell.env("UID"))) + "/omarchy-image-selector.sock"
property color accent: "#798186"
property color background: "#101315"
property color foreground: "#cacccc"
@@ -53,8 +52,13 @@ Item {
return "'" + String(value).replace(/'/g, "'\\''") + "'"
}
function decodeField(value) {
return String(value || "").replace(/\v/g, "\n").replace(/\f/g, "\t")
// 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) {
@@ -251,7 +255,7 @@ Item {
showLabels = nextShowLabels === true || nextShowLabels === "true"
filterable = nextFilterable === true || nextFilterable === "true"
filterText = ""
colorsFile = nextColorsFile || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/background-switcher-colors.json")
colorsFile = nextColorsFile || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/image-picker-colors.json")
if (nextColorsRaw)
loadColors(nextColorsRaw)
imageModel.clear()
@@ -303,16 +307,11 @@ Item {
}
}
// The plugin is keep-loaded inside omarchy-shell, so the env-driven
// auto-open path that the standalone background-switcher.qml used would
// now fire once at shell startup with stale env. The legacy callers
// (omarchy-menu-images) deliver their request over the unix socket
// declared below; modern callers go through `shell summon` -> open(payload).
// Lifecycle hooks invoked by omarchy-shell summon/hide. The legacy entry
// point remains the unix socket below callers that already have a
// selection_file/done_file flow keep using it. summon() with no payload
// simply opens the picker against the user's current theme backgrounds.
// Lifecycle hooks invoked by omarchy-shell summon/hide. shell.summon(id,
// payloadJson) hands the JSON to open() here; shell.hide(id) calls close().
// External CLI callers can either go through `shell summon omarchy.image-
// picker` (JSON payload), or hit the dedicated `image-selector` IpcHandler
// below for the lower-level positional call that omarchy-menu-images uses.
function open(payload) {
var args = {}
if (payload) {
@@ -334,39 +333,44 @@ Item {
cancel()
}
// IPC surface. All arguments are strings (Quickshell IPC marshalling).
// imageRows and colorsRaw can contain newlines/tabs, so the CLI caller
// base64-encodes them; everything else passes through verbatim. The two
// boolean-like fields use the literal strings "true" or "false".
IpcHandler {
target: "image-selector"
function open(imageDirs: string, imageRows: string, selectedImage: string, selectionFile: string, doneFile: string, colorsFile: string): void {
root.openSelector(imageDirs, imageRows, selectedImage, selectionFile, doneFile, colorsFile, "", false, false)
function open(imageDirs: string,
imageRowsB64: string,
selectedImage: string,
selectionFile: string,
doneFile: string,
colorsFile: string,
colorsRawB64: string,
showLabels: string,
filterable: string): string {
var rows = root.decodeBase64(imageRowsB64)
var colorsRaw = root.decodeBase64(colorsRawB64)
root.openSelector(imageDirs, rows, selectedImage, selectionFile, doneFile,
colorsFile, colorsRaw, showLabels, filterable)
return "ok"
}
}
SocketServer {
active: true
path: root.socketPath
handler: Socket {
id: clientSocket
parser: SplitParser {
onRead: function(message) {
var fields = message.split("\t")
if (root.opened) {
root.closeSelector(fields[3] || "")
clientSocket.connected = false
return
}
function cancel(doneFile: string): void {
root.closeSelector(doneFile || "")
}
root.openSelector("", root.decodeField(fields[0]), fields[1] || "", fields[2] || "", fields[3] || "", "", root.decodeField(fields[4]), fields[5] || "false", fields[6] || "false")
clientSocket.connected = false
}
}
function ping(): string {
return "ok"
}
}
// Tolerate the file being absent (e.g. theme templates not yet re-rendered
// after the rename) without a startup warning.
FileView {
path: root.colorsFile
watchChanges: true
printErrors: false
onLoaded: root.loadColors(text())
onFileChanged: { reload(); root.loadColors(text()) }
}
@@ -0,0 +1,15 @@
{
"schemaVersion": 1,
"id": "omarchy.image-picker",
"name": "Image picker",
"version": "1.0.0",
"author": "Omarchy",
"description": "Image-grid selector overlay used for wallpapers, themes, and any other directory of images",
"kinds": ["overlay"],
"activation": "on-demand",
"keepLoaded": true,
"entryPoints": { "overlay": "ImagePicker.qml" },
"ipc": {
"summon": "image-picker"
}
}