From 64a2ed96edd7238e2955a6a5f9d10aa75fc74f78 Mon Sep 17 00:00:00 2001 From: guyb Date: Tue, 6 Oct 2026 22:17:48 +0300 Subject: [PATCH 1/3] plugins: add Launchpad 0.1.0 --- plugins/launchpad/.gitignore | 3 + plugins/launchpad/AGENTS.md | 84 +++ plugins/launchpad/CLAUDE.md | 4 + plugins/launchpad/LICENSE | 21 + plugins/launchpad/README.md | 70 +++ plugins/launchpad/assets/preview-edit.png | Bin 0 -> 55331 bytes plugins/launchpad/assets/preview-panel.png | Bin 0 -> 45726 bytes plugins/launchpad/assets/preview-widget.png | Bin 0 -> 15883 bytes plugins/launchpad/content/DeskWidgets.qml | 610 +++++++++++++++++++ plugins/launchpad/content/OrderList.qml | 158 +++++ plugins/launchpad/content/Panel.qml | 611 ++++++++++++++++++++ plugins/launchpad/content/Widget.qml | 45 ++ plugins/launchpad/manifest.json | 110 ++++ plugins/launchpad/product-manifest.json | 121 ++++ plugins/launchpad/service/Main.qml | 262 +++++++++ plugins/registry.json | 29 + 16 files changed, 2128 insertions(+) create mode 100644 plugins/launchpad/.gitignore create mode 100644 plugins/launchpad/AGENTS.md create mode 100644 plugins/launchpad/CLAUDE.md create mode 100644 plugins/launchpad/LICENSE create mode 100644 plugins/launchpad/README.md create mode 100644 plugins/launchpad/assets/preview-edit.png create mode 100644 plugins/launchpad/assets/preview-panel.png create mode 100644 plugins/launchpad/assets/preview-widget.png create mode 100644 plugins/launchpad/content/DeskWidgets.qml create mode 100644 plugins/launchpad/content/OrderList.qml create mode 100644 plugins/launchpad/content/Panel.qml create mode 100644 plugins/launchpad/content/Widget.qml create mode 100644 plugins/launchpad/manifest.json create mode 100644 plugins/launchpad/product-manifest.json create mode 100644 plugins/launchpad/service/Main.qml diff --git a/plugins/launchpad/.gitignore b/plugins/launchpad/.gitignore new file mode 100644 index 00000000..b8eb3e3c --- /dev/null +++ b/plugins/launchpad/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +*.log +*.tmp diff --git a/plugins/launchpad/AGENTS.md b/plugins/launchpad/AGENTS.md new file mode 100644 index 00000000..49657a12 --- /dev/null +++ b/plugins/launchpad/AGENTS.md @@ -0,0 +1,84 @@ +# Agent guide for the launchpad plugin + +This folder is a Ryoku shell plugin. If you are an AI agent editing it, follow +every rule below. They are the same rules `ryoku plugin validate` enforces, so +work that breaks one will not install. This file is self-contained: you do not +need any other document to work here. + +## The rules + +R1 Place. A plugin is one folder named after its id. Author it under +`$(xdg-user-dir DOCUMENTS)/ryoku-plugins//` (fallback `~/ryoku-plugins//`); +`ryoku plugin new ` creates it there. Never write into +`~/.local/share/ryoku/plugins/` (the store's install root, receipt-owned) or +`~/.config/quickshell/` (the shipped shell). + +R2 Shape. `manifest.json` at the root; `service/Main.qml` (logic, no UI); +`content/Widget.qml` (the one view); optional `content/Panel.qml` (a bar panel); +`README.md`; `LICENSE`; `assets/preview-widget.png` (a real capture); scripts +only under `bin/` with a shebang and the exec bit. Every extra file listed in +manifest `files`. + +R3 Id. Lowercase `[a-z0-9][a-z0-9-]*`, unique, not a built-in widget id. + +R4 Imports. Only `QtQuick*`, `Quickshell*`, `Ryoku.PluginKit`, +`Ryoku.PluginKit.Singletons`, and files inside the plugin folder. Never +`shell.*`, `Ryoku.Ui*` internals, or a relative import that climbs out of the +folder. + +R5 Settings. Declared in `metadata.settings` (types the bar renders: toggle, +choice, multi, int, text); read through `pluginApi.pluginSettings` behind a +default; written only through `pluginApi.saveSetting(key, value)`. Never edit +`shell.json` or `plugins.json` directly. + +R6 Commands. Every external program the plugin runs is either in its own `bin/` +or listed in `dependencies.commands`. No `sudo`, `doas`, `su`. A privileged +action is allowed only through `pkexec`, only on an explicit click, only when +listed in manifest `capabilities.privileged` (exact command strings) and +explained in README. + +R7 Network. Every host the plugin talks to is listed in +`capabilities.network`. No `curl … | sh`, no downloading and running code. + +R8 Files. Write only under `pluginApi.stateDir` +(`$XDG_STATE_HOME/ryoku/plugins/`), `$XDG_CACHE_HOME/ryoku/plugins/`, or +a temp dir. Never touch `~/.ssh`, `/etc`, shell rc files, or another plugin's +folder. + +R9 Shell. No `sh -c` with a string built from settings or program output +(injection). Pass argv arrays. + +R10 Secrets and binaries. No tokens, keys or credentials in the tree; no +compiled binaries (ELF/Mach-O/.so); scripts only. + +R11 Honesty. `official` is never true for a community plugin. `author` is +`Name `. README says what runs, what it reads, what it writes, and every +privileged/network capability. + +## The workflow + +1. `ryoku plugin new ` scaffolds this folder (already done). +2. Edit: `service/Main.qml` for logic, `content/Widget.qml` for the view, and + `content/Panel.qml` for the bar panel. Capture `assets/preview-widget.png`. +3. `ryoku plugin validate ` and fix every blocking finding (warnings are + advisory but read them). +4. `ryoku plugin add --bar --yes` to install it, then verify it on the bar + and under **QS Bar Settings > Community**. +5. `ryoku plugin share ` ONLY when the user asks to publish. Never share a + plugin unless asked. + +## What the host sets + +Every entry point receives, from the host: `pluginApi` (see below), `s` (a scale +factor), `active`, `density`, and `widthBudget`. Read them; never assign them. + +`pluginApi` exposes: + +- `mainInstance`: the live `service/Main.qml` instance every view shares. +- `pluginSettings`: the resolved settings object; read behind a default. +- `pluginDir`: this plugin's folder. +- `stateDir`: `$XDG_STATE_HOME/ryoku/plugins/`; the only place to write. +- `saveSetting(key, value)`: persist one setting. +- `panelOpen` (bool), `openPanel()`, `closePanel()`, `togglePanel()`: the bar + panel controls (present but inert off the bar). The sanctioned widget click is + `onClicked: pluginApi.togglePanel()`. diff --git a/plugins/launchpad/CLAUDE.md b/plugins/launchpad/CLAUDE.md new file mode 100644 index 00000000..3c35c327 --- /dev/null +++ b/plugins/launchpad/CLAUDE.md @@ -0,0 +1,4 @@ +# CLAUDE.md + +Read `AGENTS.md` in this folder before editing anything here. +It holds every rule this plugin must follow and the full authoring workflow. diff --git a/plugins/launchpad/LICENSE b/plugins/launchpad/LICENSE new file mode 100644 index 00000000..d98d35e6 --- /dev/null +++ b/plugins/launchpad/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 guyb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/launchpad/README.md b/plugins/launchpad/README.md new file mode 100644 index 00000000..7c7cf4bb --- /dev/null +++ b/plugins/launchpad/README.md @@ -0,0 +1,70 @@ +# Launchpad + +A Ryoku bar plugin (`launchpad`): one tile mark on the QS Bar that opens a +pinboard panel. + +![panel](assets/preview-panel.png) ![edit mode](assets/preview-edit.png) + +- **Widgets**: the widgets you pick, as tabs: **Clock, Calendar, Music** (any + MPRIS player), **All-in-one** (clock + weather + visualizer), **System stats** + (CPU, memory, swap, load, uptime), **Weather** (now + 5 days), **Notes** and + **Visualizer** (a cava spectrum), plus every desktop-widget plugin you have + installed from Ryostore, mounted in the panel the way the desktop mounts it. +- **Pinned apps**: your favourite apps as a grid; click to launch. +- **Commands**: one-click chips that run a command. + +Click **EDIT** to change any of it: + +- every section (Widgets, Pinned, Commands) gets a grip to **drag** it into place + and a **HIDE / SHOW** pill; +- widgets, pinned apps and commands turn into lists: **drag** a row by its grip + to reorder, **×** to remove; +- **Pin an app** opens a search, the widget list offers every widget not yet + picked, and a label + command row adds a command. + +Everything is also in QS Bar Settings > Community > Launchpad. + +## What it runs, reads and writes + +- **Runs**: the apps you pin (through their `.desktop` entries); your quick + commands, exactly as you typed them, as an argv split on spaces (quotes group + words). There is **no shell**, so pipes, `&&` and `$VARS` do nothing; point a + command at a script on your `PATH` if you need them. It also runs + `ryoku-shell icons` once (the icon index), and `cava -p /cava.conf` + only while the Visualizer or All-in-one tab is on screen. +- **Reads**: the installed desktop entries, `ryoku plugin list --json` (to find + desktop-widget plugins; their own code then runs inside the panel), the MPRIS + players on the session bus, the shell daemon's `weather` feed over its local + socket (`$XDG_RUNTIME_DIR/ryoku-shell.sock`; Ryoku does the fetch, so set + your location and units in Ryoku Hub), `/proc/loadavg`, `/proc/uptime`, `/proc/stat` and `/proc/meminfo` + (every 2 s, only while the panel is open). +- **Writes**: its own settings, through the bar (`pluginApi.saveSetting`), and + your notes and the cava config to `$XDG_STATE_HOME/ryoku/plugins/launchpad/`. A hosted + desktop-widget plugin writes only to its own state folder, as it does on the desktop. +- **Network**: none. **Privileged**: none. + +## Settings + +QS Bar Settings > Community > Launchpad. + +| key | type | default | description | +| ------------ | ------ | -------------------------------------------------------- | ----------------------------------------- | +| showWidgets | toggle | true | Show the Widgets section | +| showPinned | toggle | true | Show the Pinned section | +| showCommands | toggle | true | Show the Commands section | +| sectionOrder | text | `widgets pinned commands` | Section order | +| widgetList | text | `calendar music notes` | Widget tabs in order: `clock calendar music aio stats weather notes visualizer`, `plugin:` for a store widget | +| apps | text | (empty) | Pinned desktop ids, space-separated | +| columns | int | 5 | App grid columns (3–8) | +| commands | text | `Lock=loginctl lock-session ; Suspend=systemctl suspend` | `Label=command` pairs split by ` ; ` | + +## Install + +``` +ryoku plugin validate . +ryoku plugin add . --bar --yes +``` + +## Author + +guyb . Community-made (`official` is false). MIT licence. diff --git a/plugins/launchpad/assets/preview-edit.png b/plugins/launchpad/assets/preview-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..558c421d093dbf864f41947653774c72c285bf5f GIT binary patch literal 55331 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 K3@(#23Hl$a4an92 literal 0 HcmV?d00001 diff --git a/plugins/launchpad/assets/preview-widget.png b/plugins/launchpad/assets/preview-widget.png new file mode 100644 index 0000000000000000000000000000000000000000..1eb4a02bedbad7fad151ec5fd442ae70efa1778b GIT binary patch literal 15883 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 DY{w=T literal 0 HcmV?d00001 diff --git a/plugins/launchpad/content/DeskWidgets.qml b/plugins/launchpad/content/DeskWidgets.qml new file mode 100644 index 00000000..20ff979b --- /dev/null +++ b/plugins/launchpad/content/DeskWidgets.qml @@ -0,0 +1,610 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Services.Mpris +import Ryoku.PluginKit +import Ryoku.PluginKit.Singletons + +// The panel's Widgets section: the widgets you picked, as tabs, one mounted at a time. +// Clock, Calendar, Music, All-in-one, System stats, Weather, Notes and Visualizer are +// drawn here; a picked desktop-widget plugin is mounted the way the desktop mounts it. +// EDIT shows the picked list (drag to reorder, × to remove) and every widget you can add. +Column { + id: dw + + property var service + property real s: 1 + property bool active: false + property bool editing: false + + readonly property var tabs: service ? service.chosenWidgets : [] + property string current: "" + readonly property var tab: tabs.find(t => t.key === current) || tabs[0] || null + + spacing: 12 * s + + // ---- EDIT: picked widgets in order, then the ones you can add --------- + OrderList { + visible: dw.editing + width: dw.width + s: dw.s + model: dw.editing ? dw.tabs : [] + labelOf: (w) => w.label + subOf: (w) => w.plugin ? "store widget" : "" + onMoveTo: (from, to) => dw.service.moveWidget(from, to) + onRemove: (i) => dw.service.toggleWidget(dw.tabs[i].key) + } + Flow { + visible: dw.editing + width: dw.width + spacing: 6 * dw.s + Repeater { + model: dw.editing && dw.service ? dw.service.availableWidgets.filter(w => !dw.service.widgetKeys.includes(w.key)) : [] + delegate: Pill { + required property var modelData + label: modelData.label + glyph: "install" + onClicked: dw.service.toggleWidget(modelData.key) + } + } + } + + Text { + visible: !dw.editing && dw.tabs.length === 0 + text: "No widgets yet. EDIT to pick some." + color: Theme.faint + font.family: Theme.font + font.pixelSize: 11.5 * dw.s + } + + // ---- tab row ---------------------------------------------------------- + Flow { + visible: !dw.editing && dw.tabs.length > 1 + width: dw.width + spacing: 6 * dw.s + Repeater { + model: dw.tabs + delegate: Pill { + required property var modelData + label: modelData.label + on: dw.tab && dw.tab.key === modelData.key + onClicked: dw.current = modelData.key + } + } + } + + // ---- the mounted widget ----------------------------------------------- + Loader { + visible: !dw.editing + width: dw.width + active: dw.active && !dw.editing && !!dw.tab + sourceComponent: !dw.tab ? null + : dw.tab.plugin ? hostedView + : ({ clock: clockView, calendar: calendarView, music: musicView, aio: aioView, + stats: statsView, weather: weatherView, notes: notesView, visualizer: vizView })[dw.tab.key] || null + } + + // ====================================================================== + + component Pill: Rectangle { + id: pill + property string label + property string glyph: "" + property bool on: false + signal clicked() + width: pillRow.implicitWidth + 20 * dw.s + height: 26 * dw.s + radius: height / 2 + color: on ? Theme.threadBg : pillArea.containsMouse ? Theme.sheen : "transparent" + border.width: 1 + border.color: on || pillArea.containsMouse ? Theme.accent : Theme.hair + Behavior on color { ColorAnimation { duration: Motion.fast } } + Row { + id: pillRow + anchors.centerIn: parent + spacing: 6 * dw.s + GlyphIcon { + visible: pill.glyph.length > 0 + anchors.verticalCenter: parent.verticalCenter + width: 10 * dw.s + height: 10 * dw.s + name: pill.glyph + color: pillArea.containsMouse ? Theme.accent : Theme.iconDim + } + Text { + anchors.verticalCenter: parent.verticalCenter + text: pill.label + color: pill.on ? Theme.accent : Theme.dim + font.family: Theme.mono + font.pixelSize: 10.5 * dw.s + } + } + MouseArea { + id: pillArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: pill.clicked() + } + } + + component Stat: Column { + id: st + property string label + property string value + property real frac: -1 // 0..1 draws a meter + width: dw.width + spacing: 5 * dw.s + Item { + width: st.width + height: stLabel.implicitHeight + Text { id: stLabel; text: st.label; color: Theme.faint; font.family: Theme.mono; font.pixelSize: 9.5 * dw.s; font.letterSpacing: 1.6 * dw.s } + Text { anchors.right: parent.right; text: st.value; color: Theme.bright; font.family: Theme.mono; font.pixelSize: 10.5 * dw.s } + } + Rectangle { + visible: st.frac >= 0 + width: st.width + height: 3 * dw.s + radius: height / 2 + color: Theme.hair + Rectangle { + width: parent.width * Math.max(0, Math.min(1, st.frac)) + height: parent.height + radius: parent.radius + color: st.frac > 0.85 ? Theme.verm : Theme.accent + Behavior on width { NumberAnimation { duration: Motion.standard; easing.type: Motion.easeStandard } } + } + } + } + + // ---- clock ------------------------------------------------------------ + component ClockFace: Column { + id: cf + property real size: 54 + SystemClock { id: now; precision: SystemClock.Minutes } + spacing: 2 * dw.s + Text { + text: Qt.formatTime(now.date, "HH:mm") + color: Theme.bright + font.family: Theme.display + font.pixelSize: cf.size * dw.s + } + Text { + text: Qt.formatDate(now.date, "dddd · d MMMM").toLowerCase() + color: Theme.faint + font.family: Theme.mono + font.pixelSize: 11 * dw.s + } + } + Component { id: clockView; ClockFace {} } + + // ---- calendar --------------------------------------------------------- + Component { + id: calendarView + Column { + id: cal + spacing: 8 * dw.s + SystemClock { id: today; precision: SystemClock.Hours } + property int offset: 0 + readonly property date month: new Date(today.date.getFullYear(), today.date.getMonth() + offset, 1) + // Monday-first grid of 42 days covering the month + readonly property var days: { + const first = new Date(month); + first.setDate(1 - (month.getDay() + 6) % 7); + return Array.from({ length: 42 }, (_, i) => new Date(first.getFullYear(), first.getMonth(), first.getDate() + i)); + } + Item { + width: cal.width + height: monthTitle.implicitHeight + Text { + id: monthTitle + text: Qt.formatDate(cal.month, "MMMM yyyy") + color: Theme.bright + font.family: Theme.display + font.pixelSize: 16 * dw.s + } + Row { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 4 * dw.s + Repeater { + model: [{ g: "chevron-left", d: -1 }, { g: "chevron-right", d: 1 }] + delegate: Rectangle { + id: nav + required property var modelData + width: 24 * dw.s + height: 24 * dw.s + radius: width / 2 + color: navArea.containsMouse ? Theme.sheen : "transparent" + border.width: 1 + border.color: navArea.containsMouse ? Theme.accent : Theme.hair + GlyphIcon { anchors.centerIn: parent; width: 12 * dw.s; height: 12 * dw.s; name: nav.modelData.g; color: Theme.dim } + MouseArea { id: navArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: cal.offset += nav.modelData.d } + } + } + } + } + Grid { + columns: 7 + width: cal.width + readonly property real cell: width / 7 + Repeater { + model: ["M", "T", "W", "T", "F", "S", "S"] + delegate: Text { + required property string modelData + width: parent.cell + horizontalAlignment: Text.AlignHCenter + text: modelData + color: Theme.faint + font.family: Theme.mono + font.pixelSize: 9.5 * dw.s + } + } + Repeater { + model: cal.days + delegate: Item { + id: day + required property var modelData + readonly property bool inMonth: modelData.getMonth() === cal.month.getMonth() + readonly property bool isToday: modelData.toDateString() === today.date.toDateString() + width: parent.cell + height: 26 * dw.s + Rectangle { + anchors.centerIn: parent + width: 24 * dw.s + height: 24 * dw.s + radius: width / 2 + color: day.isToday ? Theme.accent : "transparent" + } + Text { + anchors.centerIn: parent + text: day.modelData.getDate() + color: day.isToday ? Theme.cardBot : day.inMonth ? Theme.bright : Theme.ghost + font.family: Theme.mono + font.pixelSize: 11 * dw.s + } + } + } + } + } + } + + // ---- music ------------------------------------------------------------ + Component { + id: musicView + Item { + id: media + readonly property var player: Mpris.players.values.find(p => p.isPlaying) || Mpris.players.values[0] || null + height: 72 * dw.s + + Text { + visible: !media.player + anchors.verticalCenter: parent.verticalCenter + text: "Nothing is playing." + color: Theme.faint + font.family: Theme.font + font.pixelSize: 12 * dw.s + } + Rectangle { + id: art + visible: !!media.player + width: 72 * dw.s + height: 72 * dw.s + radius: Motion.rSmall * dw.s + color: Theme.tileBg + clip: true + Image { + anchors.fill: parent + source: media.player ? media.player.trackArtUrl : "" + fillMode: Image.PreserveAspectCrop + asynchronous: true + } + GlyphIcon { + visible: !media.player || !media.player.trackArtUrl + anchors.centerIn: parent + width: 26 * dw.s + height: 26 * dw.s + name: "music" + color: Theme.iconDim + } + } + Column { + visible: !!media.player + anchors.left: art.right + anchors.leftMargin: 14 * dw.s + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 3 * dw.s + Text { + width: parent.width + text: media.player ? (media.player.trackTitle || media.player.identity) : "" + color: Theme.bright + font.family: Theme.font + font.pixelSize: 13.5 * dw.s + elide: Text.ElideRight + } + Text { + width: parent.width + text: media.player ? (media.player.trackArtist || "") : "" + color: Theme.faint + font.family: Theme.mono + font.pixelSize: 10.5 * dw.s + elide: Text.ElideRight + } + Row { + topPadding: 4 * dw.s + spacing: 8 * dw.s + Repeater { + model: media.player ? [ + { g: "prev", ok: media.player.canGoPrevious, act: () => media.player.previous() }, + { g: media.player.isPlaying ? "pause" : "play", ok: media.player.canTogglePlaying, act: () => media.player.togglePlaying(), main: true }, + { g: "next", ok: media.player.canGoNext, act: () => media.player.next() } + ] : [] + delegate: Rectangle { + id: btn + required property var modelData + width: 28 * dw.s + height: 28 * dw.s + radius: width / 2 + opacity: modelData.ok ? 1 : 0.4 + color: modelData.main ? (btnArea.containsMouse ? Theme.vermLit : Theme.accent) + : btnArea.containsMouse ? Theme.sheen : "transparent" + border.width: modelData.main ? 0 : 1 + border.color: Theme.hair + GlyphIcon { + anchors.centerIn: parent + width: 11 * dw.s + height: 11 * dw.s + name: btn.modelData.g + color: btn.modelData.main ? Theme.cardBot : Theme.dim + } + MouseArea { + id: btnArea + anchors.fill: parent + enabled: btn.modelData.ok + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: btn.modelData.act() + } + } + } + } + } + } + } + + // ---- weather ---------------------------------------------------------- + component WeatherFace: Column { + id: wf + property bool compact: false + readonly property var w: dw.service ? dw.service.weather : null + readonly property var cur: w && w.current ? w.current : null + function glyph(code, isDay) { + if (code === 0) return isDay === false ? "moon" : "sun"; + if (code === 45 || code === 48) return "fog"; + if (code >= 95) return "storm"; + if ((code >= 71 && code <= 77) || code === 85 || code === 86) return "snow"; + if (code >= 51) return "rain"; + return "cloud"; + } + spacing: 10 * dw.s + + Text { + visible: !wf.cur + text: wf.w && wf.w.status === "error" ? "Weather is unavailable." : "Loading weather…" + color: Theme.faint + font.family: Theme.font + font.pixelSize: 12 * dw.s + } + Row { + visible: !!wf.cur + spacing: 14 * dw.s + GlyphIcon { + anchors.verticalCenter: parent.verticalCenter + width: (wf.compact ? 28 : 40) * dw.s + height: width + name: wf.cur ? wf.glyph(wf.cur.code, wf.cur.isDay) : "cloud" + color: Theme.accent + } + Column { + anchors.verticalCenter: parent.verticalCenter + spacing: 1 * dw.s + Text { + text: wf.cur ? wf.cur.temperature : "" + color: Theme.bright + font.family: Theme.display + font.pixelSize: (wf.compact ? 22 : 30) * dw.s + } + Text { + text: wf.cur ? (wf.cur.condition + " · " + (wf.w.city || "")).toLowerCase() : "" + color: Theme.faint + font.family: Theme.mono + font.pixelSize: 10.5 * dw.s + } + } + } + Text { + visible: !!wf.cur && !wf.compact + text: wf.cur ? "feels " + wf.cur.feelsLike + " · ↑ " + wf.cur.high + " ↓ " + wf.cur.low + " · " + wf.cur.humidity + "% humidity" : "" + color: Theme.dim + font.family: Theme.mono + font.pixelSize: 10 * dw.s + } + Row { + visible: !!wf.cur && !wf.compact + width: dw.width + Repeater { + model: wf.w && wf.w.daily ? wf.w.daily.slice(0, 5) : [] + delegate: Column { + id: dayCol + required property var modelData + width: dw.width / 5 + spacing: 4 * dw.s + Text { anchors.horizontalCenter: parent.horizontalCenter; text: dayCol.modelData.weekday; color: Theme.faint; font.family: Theme.mono; font.pixelSize: 9.5 * dw.s } + GlyphIcon { anchors.horizontalCenter: parent.horizontalCenter; width: 16 * dw.s; height: 16 * dw.s; name: wf.glyph(dayCol.modelData.code, true); color: Theme.iconDim } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: dayCol.modelData.hi + "° " + dayCol.modelData.lo + "°"; color: Theme.dim; font.family: Theme.mono; font.pixelSize: 10 * dw.s } + } + } + } + } + Component { id: weatherView; WeatherFace {} } + + // ---- visualizer ------------------------------------------------------- + component Viz: Item { + id: vz + readonly property var bars: dw.service ? dw.service.bars : [] + height: 80 * dw.s + Component.onCompleted: if (dw.service) dw.service.vizUsers++ + Component.onDestruction: if (dw.service) dw.service.vizUsers-- + Row { + anchors.bottom: parent.bottom + width: parent.width + spacing: 3 * dw.s + Repeater { + model: 32 + delegate: Rectangle { + required property int index + width: (vz.width - 31 * 3 * dw.s) / 32 + height: Math.max(2 * dw.s, vz.height * (vz.bars[index] || 0)) + anchors.bottom: parent.bottom + radius: Math.min(width / 2, 2 * dw.s) + color: Theme.accent + opacity: 0.45 + 0.55 * (vz.bars[index] || 0) + Behavior on height { NumberAnimation { duration: 60 } } + } + } + } + Text { + visible: vz.bars.length === 0 + anchors.centerIn: parent + text: "Play something to see it move." + color: Theme.faint + font.family: Theme.font + font.pixelSize: 11.5 * dw.s + } + } + Component { id: vizView; Viz {} } + + // ---- all-in-one: clock + weather + visualizer -------------------------- + Component { + id: aioView + Column { + spacing: 14 * dw.s + Item { + width: parent.width + height: Math.max(aioClock.implicitHeight, aioWx.implicitHeight) + ClockFace { id: aioClock; size: 44 } + WeatherFace { id: aioWx; compact: true; anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter } + } + Viz { width: parent.width; height: 56 * dw.s } + } + } + + // ---- system stats ----------------------------------------------------- + Component { + id: statsView + Column { + readonly property var svc: dw.service + function dur(sec) { + const d = Math.floor(sec / 86400), h = Math.floor(sec % 86400 / 3600), m = Math.floor(sec % 3600 / 60); + return (d ? d + "d " : "") + (d || h ? h + "h " : "") + m + "m"; + } + spacing: 12 * dw.s + Stat { label: "CPU"; value: Math.round(parent.svc.cpu * 100) + "%"; frac: parent.svc.cpu } + Stat { label: "MEMORY"; value: parent.svc.memText; frac: parent.svc.mem } + Stat { label: "SWAP"; value: Math.round(parent.svc.swap * 100) + "%"; frac: parent.svc.swap } + Stat { label: "LOAD"; value: parent.svc.load.map(x => x.toFixed(2)).join(" ") } + Stat { label: "UPTIME"; value: parent.dur(parent.svc.uptime) } + } + } + + // ---- notes ------------------------------------------------------------ + Component { + id: notesView + Rectangle { + height: 150 * dw.s + radius: Motion.rSmall * dw.s + color: "transparent" + border.width: 1 + border.color: notesEdit.activeFocus ? Theme.accent : Theme.border + Flickable { + id: notesFlick + anchors.fill: parent + anchors.margins: 10 * dw.s + contentHeight: notesEdit.implicitHeight + clip: true + boundsBehavior: Flickable.StopAtBounds + TextEdit { + id: notesEdit + width: notesFlick.width + wrapMode: TextEdit.Wrap + color: Theme.bright + selectionColor: Theme.accent + selectedTextColor: Theme.cardBot + font.family: Theme.mono + font.pixelSize: 11.5 * dw.s + text: dw.service ? dw.service.notes : "" + onTextChanged: if (dw.service && activeFocus) dw.service.setNotes(text) + onCursorRectangleChanged: { + if (cursorRectangle.y < notesFlick.contentY) notesFlick.contentY = cursorRectangle.y; + else if (cursorRectangle.y + cursorRectangle.height > notesFlick.contentY + notesFlick.height) + notesFlick.contentY = cursorRectangle.y + cursorRectangle.height - notesFlick.height; + } + Text { + visible: !notesEdit.text && !notesEdit.activeFocus + text: "Jot something down…" + color: Theme.faint + font: notesEdit.font + } + } + } + } + } + + // ---- an installed desktop-widget plugin ------------------------------- + Component { + id: hostedView + Item { + id: hosted + readonly property var plugin: dw.tab ? dw.tab.plugin : null + readonly property string dir: plugin ? plugin.dir : "" + height: slot.item ? Math.min(slot.item.implicitHeight || 200 * dw.s, 420 * dw.s) : 0 + clip: true + + // the handle a desktop plugin expects (as the desktop host gives it); its + // settings fall back to its own defaults, and saveSetting is inert here. + QtObject { + id: api + property var mainInstance: hostedSvc.item + readonly property var pluginSettings: ({}) + readonly property string pluginDir: hosted.dir + readonly property string stateDir: hosted.plugin + ? (Quickshell.env("XDG_STATE_HOME") || (Quickshell.env("HOME") + "/.local/state")) + "/ryoku/plugins/" + hosted.plugin.id : "" + function saveSetting(key, value) {} + function saveSettings() {} + } + PluginObjectSlot { + id: hostedSvc + source: hosted.dir ? "file://" + hosted.dir + "/service/Main.qml" : "" + configure: (svc) => { svc.pluginApi = api; } + } + PluginObjectSlot { + id: slot + width: hosted.width + height: hosted.height + fill: true + source: hosted.dir ? "file://" + hosted.dir + "/content/Widget.qml" : "" + configure: (it) => { + try { + it.pluginApi = api; + it.density = "compact"; + it.s = dw.s; + it.widthBudget = hosted.width; + it.active = true; + } catch (e) {} + } + } + } + } +} diff --git a/plugins/launchpad/content/OrderList.qml b/plugins/launchpad/content/OrderList.qml new file mode 100644 index 00000000..da08db25 --- /dev/null +++ b/plugins/launchpad/content/OrderList.qml @@ -0,0 +1,158 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Ryoku.PluginKit +import Ryoku.PluginKit.Singletons + +// An EDIT list you reorder by dragging a row's grip: the row follows the pointer, the +// rows it passes slide out of its way, and it lands where you let go. × removes a row. +// labelOf / subOf / iconOf map a model entry to its text and icon. +Column { + id: list + + property var model: [] + property real s: 1 + property var labelOf: (e) => String(e) + property var subOf: (e) => "" + property var iconOf: (e) => "" + signal moveTo(int from, int to) + signal remove(int index) + + // the drag in flight: which row, and where it would land + property int dragFrom: -1 + property int dragTo: -1 + readonly property real pitch: 34 * s + spacing + + spacing: 6 * s + + Repeater { + model: list.model + delegate: Rectangle { + id: row + required property var modelData + required property int index + readonly property bool dragging: grip.drag.active + readonly property string icon: list.iconOf(modelData) || "" + readonly property string sub: list.subOf(modelData) || "" + // rows between the dragged row and its landing spot step aside + readonly property real shift: list.dragFrom < 0 || dragging ? 0 + : (index > list.dragFrom && index <= list.dragTo) ? -list.pitch + : (index < list.dragFrom && index >= list.dragTo) ? list.pitch : 0 + + width: list.width + height: 34 * list.s + radius: Motion.rSmall * list.s + z: dragging ? 10 : 0 + color: dragging ? Theme.threadBg : hover.hovered ? Theme.sheen : Theme.cardBot + border.width: 1 + border.color: dragging ? Theme.accent : Theme.hair + transform: Translate { + y: row.dragging ? grip.drag.translation.y : row.shift + Behavior on y { enabled: !row.dragging; NumberAnimation { duration: Motion.fast; easing.type: Motion.easeStandard } } + } + HoverHandler { id: hover } + + // grip: the drag handle + Item { + id: grip + property alias drag: dragH + width: 28 * list.s + height: parent.height + Column { + anchors.centerIn: parent + spacing: 2.5 * list.s + Repeater { + model: 3 + Rectangle { + width: 10 * list.s + height: 1.5 * list.s + radius: height / 2 + color: row.dragging || gripHover.hovered ? Theme.accent : Theme.iconDim + } + } + } + HoverHandler { id: gripHover; cursorShape: row.dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor } + DragHandler { + id: dragH + target: null + xAxis.enabled: false + onTranslationChanged: if (active) { + list.dragFrom = row.index; + list.dragTo = Math.max(0, Math.min(list.model.length - 1, row.index + Math.round(translation.y / list.pitch))); + } + onActiveChanged: if (!active) { + const from = list.dragFrom, to = list.dragTo; + list.dragFrom = -1; + list.dragTo = -1; + if (from >= 0 && to >= 0 && from !== to) list.moveTo(from, to); + } + } + } + + Image { + id: img + visible: row.icon.length > 0 + anchors.left: grip.right + anchors.verticalCenter: parent.verticalCenter + width: visible ? 18 * list.s : 0 + height: 18 * list.s + sourceSize: Qt.size(width * 2, height * 2) + source: row.icon + asynchronous: true + } + + Column { + anchors.left: img.right + anchors.leftMargin: (row.icon.length > 0 ? 10 : 2) * list.s + anchors.right: del.left + anchors.rightMargin: 8 * list.s + anchors.verticalCenter: parent.verticalCenter + Text { + width: parent.width + text: list.labelOf(row.modelData) + color: Theme.bright + font.family: Theme.font + font.pixelSize: 12 * list.s + elide: Text.ElideRight + } + Text { + visible: row.sub.length > 0 + width: parent.width + text: row.sub + color: Theme.faint + font.family: Theme.mono + font.pixelSize: 9.5 * list.s + elide: Text.ElideRight + } + } + + // remove + Rectangle { + id: del + anchors.right: parent.right + anchors.rightMargin: 5 * list.s + anchors.verticalCenter: parent.verticalCenter + width: 24 * list.s + height: 24 * list.s + radius: width / 2 + color: delArea.containsMouse ? Theme.sheen : "transparent" + border.width: delArea.containsMouse ? 1 : 0 + border.color: Theme.verm + GlyphIcon { + anchors.centerIn: parent + width: 11 * list.s + height: 11 * list.s + name: "close" + color: Theme.verm + } + MouseArea { + id: delArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: list.remove(row.index) + } + } + } + } +} diff --git a/plugins/launchpad/content/Panel.qml b/plugins/launchpad/content/Panel.qml new file mode 100644 index 00000000..c85f5d37 --- /dev/null +++ b/plugins/launchpad/content/Panel.qml @@ -0,0 +1,611 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Ryoku.PluginKit +import Ryoku.PluginKit.Singletons + +// Panel: desktop widgets, pinned apps, and quick commands. EDIT reveals +// unpin / remove marks, the add rows, the widget picker, and each section's HIDE / SHOW. +Item { + id: root + + property var pluginApi + property string density: "full" + property real s: 1 + property real widthBudget: 380 + property bool active: false + + readonly property var service: pluginApi ? pluginApi.mainInstance : null + function setting(key, def) { return root.service ? root.service.get(key, def) : def; } + readonly property int cols: Math.max(3, Math.min(8, root.setting("columns", 5))) + + // section drag in flight: which section (by order), where it lands, its height + property int secFrom: -1 + property int secTo: -1 + property real secDragH: 0 + // landing index for the section at `from` dragged by `dy`: the sections whose + // middles end up above its middle + function secTarget(from, dy) { + const it = secRep.itemAt(from); + const mid = it.y + it.height / 2 + dy; + let t = 0; + for (let i = 0; i < secRep.count; i++) { + const o = secRep.itemAt(i); + if (i !== from && o.visible && o.y + o.height / 2 < mid) t++; + } + return t; + } + property bool editing: false + property bool adding: false + + implicitWidth: root.widthBudget + implicitHeight: col.implicitHeight + 32 * root.s + + onActiveChanged: if (!active) { editing = false; adding = false; } + + // a section: hairline, eyebrow, body. In EDIT every section shows its header with + // a HIDE / SHOW pill (its show* setting); a hidden section keeps only that header. + component Section: Column { + id: sec + property string label + property string key // its show* setting + property string sid // its id in the section order + readonly property int pos: root.service ? root.service.sectionOrder.indexOf(sid) : 0 + readonly property bool shown: root.setting(key, true) + readonly property bool dragging: secDrag.active + // how far the section's wrapper is moved: with the pointer while dragged, aside + // while another section passes it + readonly property real offset: dragging ? secDrag.translation.y + : root.secFrom < 0 ? 0 + : (pos > root.secFrom && pos <= root.secTo) ? -(root.secDragH + col.spacing) + : (pos < root.secFrom && pos >= root.secTo) ? (root.secDragH + col.spacing) : 0 + default property alias body: secBody.data + width: col.width + spacing: 12 * root.s + visible: root.editing || shown + + Rectangle { width: sec.width; height: 1; color: Theme.hair } + Item { + width: sec.width + height: Math.max(secLabel.implicitHeight, secPill.height) + // grip: drag the section + Item { + id: secGrip + visible: root.editing + width: visible ? 24 * root.s : 0 + height: 20 * root.s + anchors.verticalCenter: parent.verticalCenter + Column { + anchors.verticalCenter: parent.verticalCenter + spacing: 2.5 * root.s + Repeater { + model: 3 + Rectangle { + width: 12 * root.s + height: 1.5 * root.s + radius: height / 2 + color: sec.dragging || secGripHover.hovered ? Theme.accent : Theme.iconDim + } + } + } + HoverHandler { id: secGripHover; cursorShape: sec.dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor } + DragHandler { + id: secDrag + target: null + xAxis.enabled: false + onTranslationChanged: if (active) { + root.secFrom = sec.pos; + root.secDragH = sec.height; + root.secTo = root.secTarget(sec.pos, translation.y); + } + onActiveChanged: if (!active) { + const from = root.secFrom, to = root.secTo; + root.secFrom = -1; + root.secTo = -1; + if (from >= 0 && to >= 0 && from !== to) root.service.moveSection(from, to); + } + } + } + MicroLabel { + id: secLabel + anchors.left: secGrip.right + anchors.verticalCenter: parent.verticalCenter + label: sec.label + s: root.s + opacity: sec.shown ? 1 : 0.45 + } + Rectangle { + id: secPill + visible: root.editing + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + width: secPillText.implicitWidth + 14 * root.s + height: 20 * root.s + radius: height / 2 + color: secPillArea.containsMouse ? Theme.sheen : "transparent" + border.width: 1 + border.color: sec.shown ? Theme.hair : Theme.accent + Text { + id: secPillText + anchors.centerIn: parent + text: sec.shown ? "HIDE" : "SHOW" + color: sec.shown ? Theme.faint : Theme.accent + font.family: Theme.mono + font.pixelSize: 9.5 * root.s + font.letterSpacing: 1.2 * root.s + } + MouseArea { + id: secPillArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.service.save(sec.key, !sec.shown) + } + } + } + Column { + id: secBody + visible: sec.shown + width: sec.width + spacing: 12 * root.s + } + } + + // a hairline chip: icon + label, accent on hover + component Chip: Rectangle { + id: chip + property string label + property string glyph: "" + signal clicked() + width: chipRow.implicitWidth + 22 * root.s + height: 30 * root.s + radius: Motion.rSmall * root.s + color: chipArea.pressed ? Theme.threadBg : chipArea.containsMouse ? Theme.sheen : "transparent" + border.width: 1 + border.color: chipArea.containsMouse ? Theme.accent : Theme.hair + Behavior on color { ColorAnimation { duration: Motion.fast } } + Behavior on border.color { ColorAnimation { duration: Motion.fast } } + MouseArea { + id: chipArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: chip.clicked() + } + Row { + id: chipRow + anchors.centerIn: parent + spacing: 7 * root.s + GlyphIcon { + visible: chip.glyph.length > 0 + anchors.verticalCenter: parent.verticalCenter + width: 12 * root.s + height: 12 * root.s + name: chip.glyph + color: chipArea.containsMouse ? Theme.accent : Theme.iconDim + } + Text { + anchors.verticalCenter: parent.verticalCenter + text: chip.label + color: chipArea.containsMouse ? Theme.bright : Theme.dim + font.family: Theme.mono + font.pixelSize: 11.5 * root.s + } + } + } + + // a bordered single-line input + component Field: Rectangle { + id: fld + property alias text: inp.text + property alias input: inp + property string placeholder + signal accepted() + height: 30 * root.s + radius: Motion.rSmall * root.s + color: "transparent" + border.width: 1 + border.color: inp.activeFocus ? Theme.accent : Theme.border + TextInput { + id: inp + anchors.fill: parent + anchors.leftMargin: 10 * root.s + anchors.rightMargin: 10 * root.s + verticalAlignment: TextInput.AlignVCenter + clip: true + color: Theme.bright + selectionColor: Theme.accent + selectedTextColor: Theme.cardBot + font.family: Theme.mono + font.pixelSize: 11.5 * root.s + onAccepted: fld.accepted() + Text { + visible: !inp.text && !inp.activeFocus + anchors.verticalCenter: parent.verticalCenter + text: fld.placeholder + color: Theme.faint + font: inp.font + } + } + } + + Column { + id: col + x: 16 * root.s + y: 16 * root.s + width: root.width - 32 * root.s + spacing: 14 * root.s + + // header: title, EDIT toggle + Item { + width: col.width + height: Math.max(head.implicitHeight, editPill.height) + + Text { + id: head + anchors.verticalCenter: parent.verticalCenter + text: "Launchpad" + color: Theme.bright + font.family: Theme.display + font.pixelSize: 20 * root.s + } + + Rectangle { + id: editPill + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + width: editText.implicitWidth + 18 * root.s + height: editText.implicitHeight + 9 * root.s + radius: height / 2 + color: root.editing ? Theme.threadBg : editArea.containsMouse ? Theme.sheen : "transparent" + border.width: 1 + border.color: root.editing ? Theme.accent : Theme.border + Text { + id: editText + anchors.centerIn: parent + text: root.editing ? "DONE" : "EDIT" + color: root.editing ? Theme.accent : Theme.dim + font.family: Theme.mono + font.pixelSize: 10.5 * root.s + font.letterSpacing: 1.2 * root.s + } + MouseArea { + id: editArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { root.editing = !root.editing; root.adding = false; } + } + } + } + + // the sections, in your order (EDIT moves them) + Repeater { + id: secRep + model: root.service ? root.service.sectionOrder : [] + delegate: Item { + id: secSlot + required property string modelData + readonly property var sec: secLoader.item + width: col.width + height: secLoader.height + visible: root.editing || root.setting(({ widgets: "showWidgets", pinned: "showPinned", commands: "showCommands" })[modelData], true) + z: sec && sec.dragging ? 10 : 0 + transform: Translate { + y: secSlot.sec ? secSlot.sec.offset : 0 + Behavior on y { + enabled: !(secSlot.sec && secSlot.sec.dragging) + NumberAnimation { duration: Motion.fast; easing.type: Motion.easeStandard } + } + } + // the lifted card under a dragged section + Rectangle { + visible: !!secSlot.sec && secSlot.sec.dragging + x: -8 * root.s + y: -4 * root.s + width: parent.width + 16 * root.s + height: parent.height + 12 * root.s + radius: Motion.rSmall * root.s + color: Theme.cardBot + border.width: 1 + border.color: Theme.accent + } + Loader { + id: secLoader + width: col.width + sourceComponent: ({ widgets: widgetsSec, pinned: pinnedSec, commands: commandsSec })[secSlot.modelData] + } + } + } + } + + // desktop widgets + Component { + id: widgetsSec + Section { + label: "Widgets" + key: "showWidgets" + sid: "widgets" + DeskWidgets { + width: col.width + s: root.s + active: root.active + editing: root.editing + service: root.service + } + } + } + + // pinned apps + Component { + id: pinnedSec + Section { + label: "Pinned" + key: "showPinned" + sid: "pinned" + Grid { + id: grid + visible: !root.editing + width: col.width + columns: root.cols + readonly property real cell: width / root.cols + + Repeater { + model: root.service ? root.service.pinned : [] + delegate: Item { + id: tile + required property var modelData + width: grid.cell + height: 66 * root.s + + Rectangle { + anchors.fill: parent + anchors.margins: 2 * root.s + radius: Motion.rSmall * root.s + color: tileArea.containsMouse ? Theme.sheen : "transparent" + border.width: 1 + border.color: tileArea.containsMouse ? Theme.accent : "transparent" + Behavior on color { ColorAnimation { duration: Motion.fast } } + } + Image { + id: appIcon + anchors.horizontalCenter: parent.horizontalCenter + y: 9 * root.s + width: 30 * root.s + height: 30 * root.s + sourceSize: Qt.size(width * 2, height * 2) + source: root.service ? root.service.icon(tile.modelData) : "" + asynchronous: true + } + // fallback monogram when the theme has no icon + Text { + visible: appIcon.status !== Image.Ready + anchors.centerIn: appIcon + text: tile.modelData.name.charAt(0).toUpperCase() + color: Theme.accent + font.family: Theme.display + font.pixelSize: 20 * root.s + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: appIcon.bottom + anchors.topMargin: 6 * root.s + width: parent.width - 8 * root.s + horizontalAlignment: Text.AlignHCenter + text: tile.modelData.name + color: tileArea.containsMouse ? Theme.bright : Theme.dim + font.family: Theme.font + font.pixelSize: 10 * root.s + elide: Text.ElideRight + } + MouseArea { + id: tileArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.service.launch(tile.modelData) + } + } + } + + // add tile + Item { + visible: root.adding || (root.service && root.service.pinned.length === 0) + width: grid.cell + height: 66 * root.s + Rectangle { + anchors.fill: parent + anchors.margins: 2 * root.s + radius: Motion.rSmall * root.s + color: "transparent" + border.width: 1 + border.color: addArea.containsMouse || root.adding ? Theme.accent : Theme.border + } + Text { + anchors.centerIn: parent + text: root.adding ? "−" : "+" + color: addArea.containsMouse || root.adding ? Theme.accent : Theme.faint + font.family: Theme.display + font.pixelSize: 22 * root.s + } + MouseArea { + id: addArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + root.adding = !root.adding; + if (root.adding) Qt.callLater(() => search.input.forceActiveFocus()); + } + } + } + } + + // EDIT: pinned apps in order, then a button that opens the picker + Column { + visible: root.editing + width: col.width + spacing: 6 * root.s + OrderList { + width: col.width + s: root.s + model: root.editing && root.service ? root.service.pinned : [] + labelOf: (e) => e.name + iconOf: (e) => root.service.icon(e) + onMoveTo: (from, to) => root.service.moveApp(from, to) + onRemove: (i) => root.service.togglePin(root.service.pinned[i]) + } + Chip { + label: root.adding ? "Done adding" : "Pin an app" + glyph: root.adding ? "check" : "install" + onClicked: { + root.adding = !root.adding; + if (root.adding) Qt.callLater(() => search.input.forceActiveFocus()); + } + } + } + + // app picker + Column { + visible: root.adding + width: col.width + spacing: 6 * root.s + + SearchField { + id: search + width: col.width + s: root.s + kanji: "探" + placeholder: "Find an app to pin" + onDismissed: root.adding = false + } + Rectangle { width: col.width; height: 1; color: Theme.hair } + + Repeater { + model: { + if (!root.adding || !root.service) return []; + const q = search.text.toLowerCase(); + return root.service.allApps.filter(e => !q || e.name.toLowerCase().includes(q) + || String(e.genericName || "").toLowerCase().includes(q)).slice(0, 6); + } + delegate: Rectangle { + id: pick + required property var modelData + readonly property bool on: root.service ? root.service.isPinned(modelData) : false + width: col.width + height: 34 * root.s + radius: Motion.rSmall * root.s + color: pickArea.containsMouse ? Theme.sheen : "transparent" + Image { + id: pickIcon + x: 8 * root.s + anchors.verticalCenter: parent.verticalCenter + width: 20 * root.s + height: 20 * root.s + sourceSize: Qt.size(width * 2, height * 2) + source: root.service ? root.service.icon(pick.modelData) : "" + asynchronous: true + } + Text { + anchors.left: pickIcon.right + anchors.leftMargin: 10 * root.s + anchors.right: pickMark.left + anchors.verticalCenter: parent.verticalCenter + text: pick.modelData.name + color: pick.on ? Theme.bright : Theme.dim + font.family: Theme.font + font.pixelSize: 12 * root.s + elide: Text.ElideRight + } + GlyphIcon { + id: pickMark + anchors.right: parent.right + anchors.rightMargin: 10 * root.s + anchors.verticalCenter: parent.verticalCenter + width: 13 * root.s + height: 13 * root.s + name: pick.on ? "check" : "install" + color: pick.on ? Theme.accent : pickArea.containsMouse ? Theme.bright : Theme.iconDim + } + MouseArea { + id: pickArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.service.togglePin(pick.modelData) + } + } + } + } + } + } + + // quick commands + Component { + id: commandsSec + Section { + label: "Commands" + key: "showCommands" + sid: "commands" + Flow { + visible: !root.editing + width: col.width + spacing: 6 * root.s + Repeater { + model: root.service ? root.service.commands : [] + delegate: Chip { + required property var modelData + required property int index + label: modelData.label + glyph: "send" + onClicked: root.service.run(modelData) + } + } + Text { + visible: root.service && root.service.commands.length === 0 + height: 30 * root.s + verticalAlignment: Text.AlignVCenter + text: "No commands yet. EDIT to add one." + color: Theme.faint + font.family: Theme.font + font.pixelSize: 11.5 * root.s + } + } + + // EDIT: commands in order + OrderList { + visible: root.editing + width: col.width + s: root.s + model: root.editing && root.service ? root.service.commands : [] + labelOf: (c) => c.label + subOf: (c) => c.cmd + onMoveTo: (from, to) => root.service.moveCommand(from, to) + onRemove: (i) => root.service.removeCommand(i) + } + + // add a command + Row { + visible: root.editing + width: col.width + spacing: 6 * root.s + Field { id: cmdLabel; width: 92 * root.s; placeholder: "label"; onAccepted: cmdLine.input.forceActiveFocus() } + Field { id: cmdLine; width: col.width - 92 * root.s - 36 * root.s - 12 * root.s; placeholder: "command"; onAccepted: addCmd.add() } + Rectangle { + id: addCmd + function add() { + root.service.addCommand(cmdLabel.text, cmdLine.text); + cmdLabel.text = ""; + cmdLine.text = ""; + } + width: 36 * root.s + height: 30 * root.s + radius: Motion.rSmall * root.s + color: addCmdArea.pressed ? Theme.vermDeep : addCmdArea.containsMouse ? Theme.vermLit : Theme.accent + Text { anchors.centerIn: parent; text: "+"; color: Theme.cardBot; font.family: Theme.display; font.pixelSize: 18 * root.s } + MouseArea { id: addCmdArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: addCmd.add() } + } + } + } + } +} diff --git a/plugins/launchpad/content/Widget.qml b/plugins/launchpad/content/Widget.qml new file mode 100644 index 00000000..186a05bc --- /dev/null +++ b/plugins/launchpad/content/Widget.qml @@ -0,0 +1,45 @@ +import QtQuick +import Ryoku.PluginKit.Singletons + +// Bar glyph: a 2x2 tile mark; the accent tile lights while the panel is open. +// A click only toggles the panel. +Item { + id: root + + property var pluginApi + property var screen + property bool active: false + property string density: "glyph" + property real s: 1 + property real widthBudget: 0 + + readonly property bool open: pluginApi ? pluginApi.panelOpen : false + + implicitWidth: 16 * root.s + implicitHeight: 18 * root.s + + Grid { + anchors.centerIn: parent + columns: 2 + spacing: 2.5 * root.s + Repeater { + model: 4 + Rectangle { + required property int index + width: 6 * root.s + height: 6 * root.s + radius: 1.5 * root.s + color: index === 0 ? Theme.accent : (root.open || area.containsMouse ? Theme.bright : Theme.iconDim) + Behavior on color { ColorAnimation { duration: Motion.fast } } + } + } + } + + MouseArea { + id: area + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: if (root.pluginApi) root.pluginApi.togglePanel() + } +} diff --git a/plugins/launchpad/manifest.json b/plugins/launchpad/manifest.json new file mode 100644 index 00000000..546c73d1 --- /dev/null +++ b/plugins/launchpad/manifest.json @@ -0,0 +1,110 @@ +{ + "id": "launchpad", + "name": "Launchpad", + "version": "0.1.0", + "author": "guyb ", + "description": "A pinboard under one bar mark: your pick of widgets (clock, calendar, music, all-in-one, system stats, weather, notes, visualizer, or any installed desktop-widget plugin), favourite apps and one-click commands. Reorder or hide anything.", + "license": "MIT", + "official": false, + "hosts": [ + "topbarGlyph" + ], + "entryPoints": { + "main": "service/Main.qml", + "content": "content/Widget.qml", + "panel": "content/Panel.qml" + }, + "panel": { + "width": 380 + }, + "files": [ + "content/DeskWidgets.qml", + "content/OrderList.qml", + "assets/preview-widget.png", + "assets/preview-panel.png", + "assets/preview-edit.png" + ], + "capabilities": { + "densities": [ + "glyph", + "compact" + ] + }, + "defaults": { + "host": "topbarGlyph", + "label": "Launchpad" + }, + "dependencies": { + "commands": [ + "ryoku", + "ryoku-shell", + "cava" + ] + }, + "metadata": { + "settings": [ + { + "key": "showWidgets", + "type": "toggle", + "label": "Show Widgets", + "group": "Sections", + "default": true + }, + { + "key": "showPinned", + "type": "toggle", + "label": "Show Pinned apps", + "group": "Sections", + "default": true + }, + { + "key": "showCommands", + "type": "toggle", + "label": "Show Commands", + "group": "Sections", + "default": true + }, + { + "key": "sectionOrder", + "type": "text", + "label": "Section order (or drag the sections in the panel's EDIT)", + "group": "Sections", + "default": "widgets pinned commands", + "placeholder": "pinned widgets commands" + }, + { + "key": "widgetList", + "type": "text", + "label": "Widgets (keys, space-separated; or pick them in the panel's EDIT)", + "group": "Widgets", + "default": "calendar music notes", + "placeholder": "clock calendar music aio stats weather notes visualizer plugin:" + }, + { + "key": "apps", + "type": "text", + "label": "Pinned apps (desktop ids, space-separated)", + "group": "Pinned", + "default": "", + "placeholder": "firefox kitty org.gnome.Nautilus" + }, + { + "key": "columns", + "type": "int", + "label": "App columns", + "group": "Pinned", + "default": 5, + "min": 3, + "max": 8 + }, + { + "key": "commands", + "type": "text", + "label": "Quick commands (Label=command ; ...)", + "group": "Commands", + "default": "Lock=loginctl lock-session ; Suspend=systemctl suspend", + "placeholder": "Lock=loginctl lock-session ; Files=nautilus" + } + ] + } +} diff --git a/plugins/launchpad/product-manifest.json b/plugins/launchpad/product-manifest.json new file mode 100644 index 00000000..4f092cb7 --- /dev/null +++ b/plugins/launchpad/product-manifest.json @@ -0,0 +1,121 @@ +{ + "schema": 1, + "id": "launchpad", + "category": "plugins", + "version": "0.1.0", + "destination": "ryoku/plugins/launchpad", + "files": [ + { + "source": ".gitignore", + "destination": ".gitignore", + "sha256": "379dfa9273952c08fa44042de676ed4addc26a7c4d4c90235d6ff2c73dffb810", + "mode": "0644", + "size": 22, + "install": true + }, + { + "source": "AGENTS.md", + "destination": "AGENTS.md", + "sha256": "1ed2303778c198c25ba780642175eba1cac2f191ec0b3fe464847dac017cc54f", + "mode": "0644", + "size": 3958, + "install": true + }, + { + "source": "CLAUDE.md", + "destination": "CLAUDE.md", + "sha256": "09ffb8ebdb8adf50bf4bde26dfe2109b4af3a4a8cc3f30cc536be42bb912c033", + "mode": "0644", + "size": 152, + "install": true + }, + { + "source": "LICENSE", + "destination": "LICENSE", + "sha256": "06181c521ca2a124453c1db837e83a0c076ccb465b6df225d8ad2376c5bfb20f", + "mode": "0644", + "size": 1061, + "install": false + }, + { + "source": "README.md", + "destination": "README.md", + "sha256": "8b81c6808f5bdaca38f9bd5f081732ef555b95957cc323465a27c6498587ffe5", + "mode": "0644", + "size": 3982, + "install": false + }, + { + "source": "assets/preview-edit.png", + "destination": "assets/preview-edit.png", + "sha256": "f5bfaa348c13d1438b5f0f58b9412beb0314ab8bf1eb520b387c6a578ef3a9c1", + "mode": "0644", + "size": 55331, + "install": false + }, + { + "source": "assets/preview-panel.png", + "destination": "assets/preview-panel.png", + "sha256": "32387ed8cf6fbd941ee06c998bd20456342d1eaca4169576000ced47879139d2", + "mode": "0644", + "size": 45726, + "install": false + }, + { + "source": "assets/preview-widget.png", + "destination": "assets/preview-widget.png", + "sha256": "696ba6fce98962e2bb20d57fa31e4aa704baf9e8997808c215cd4ae4ef4e5661", + "mode": "0644", + "size": 15883, + "install": false + }, + { + "source": "content/DeskWidgets.qml", + "destination": "content/DeskWidgets.qml", + "sha256": "f681f24300f1b4b80d055b15a02b25861bed486532690aa89d28404cf1dca215", + "mode": "0644", + "size": 25327, + "install": true + }, + { + "source": "content/OrderList.qml", + "destination": "content/OrderList.qml", + "sha256": "1d92e1a46df06e337f478ec5430c63f9cb7157be1cc4df31430dd00ffd055bae", + "mode": "0644", + "size": 6100, + "install": true + }, + { + "source": "content/Panel.qml", + "destination": "content/Panel.qml", + "sha256": "400d82b7de3df92ecc9d1a9d46f5d502427ac39b245a2f29dc5c0b4f2004238f", + "mode": "0644", + "size": 25119, + "install": true + }, + { + "source": "content/Widget.qml", + "destination": "content/Widget.qml", + "sha256": "a61466c500a46003c20addcc12db423a307cef87a1b1ba152faa575269ee38f8", + "mode": "0644", + "size": 1242, + "install": true + }, + { + "source": "manifest.json", + "destination": "manifest.json", + "sha256": "19a8d465d08c1e1f4b47a0b3909edd9ea95d36ee8655529e0a287fd27c2c0a15", + "mode": "0644", + "size": 2868, + "install": true + }, + { + "source": "service/Main.qml", + "destination": "service/Main.qml", + "sha256": "6902eb3cb2dfaf97366a0c4b2a8ff0559680d22144516a3be1858a51ad537b06", + "mode": "0644", + "size": 11697, + "install": true + } + ] +} diff --git a/plugins/launchpad/service/Main.qml b/plugins/launchpad/service/Main.qml new file mode 100644 index 00000000..6952b28b --- /dev/null +++ b/plugins/launchpad/service/Main.qml @@ -0,0 +1,262 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io + +// Pinned apps, quick commands and the chosen widgets (all live in the plugin's settings), +// the app and widget lists to pick from, notes, an icon index, and a CPU / memory sample. +Item { + id: svc + + property var pluginApi + readonly property var settings: pluginApi && pluginApi.pluginSettings ? pluginApi.pluginSettings : ({}) + + // A save takes a moment to round-trip through plugins.json. Until it lands, `pending` + // holds the new value so the panel shows it at once and a quick second edit builds on + // it instead of on the stale setting (which made reorders snap back). + property var pending: ({}) + function get(key, def) { + const v = svc.pending[key]; + return v !== undefined ? v : (svc.settings[key] ?? def); + } + function save(key, value) { + const p = Object.assign({}, svc.pending); + p[key] = value; + svc.pending = p; + pendingExpiry.restart(); + pluginApi.saveSetting(key, value); + } + onSettingsChanged: { + const p = {}; + for (const k in svc.pending) + if (JSON.stringify(svc.settings[k]) !== JSON.stringify(svc.pending[k])) p[k] = svc.pending[k]; + svc.pending = p; + } + // a save that never lands (tool failed) stops masking the real setting + Timer { id: pendingExpiry; interval: 5000; onTriggered: svc.pending = ({}) } + + // apps: space-separated desktop ids, e.g. "firefox kitty org.gnome.Nautilus" + readonly property var appIds: String(get("apps", "")).split(/\s+/).filter(x => x.length) + // allApps is read so this re-resolves once the desktop entries load (heuristicLookup + // alone is not reactive, so pins came up empty after a shell restart) + readonly property var pinned: allApps.length >= 0 ? appIds.map(id => DesktopEntries.heuristicLookup(id)).filter(e => !!e) : [] + readonly property var allApps: DesktopEntries.applications.values + .filter(e => !e.noDisplay) + .sort((a, b) => a.name.localeCompare(b.name)) + + // commands: "Label=command ; Label=command" + readonly property var commands: String(get("commands", "")).split(";") + .map(c => c.trim()).filter(c => c.includes("=")) + .map(c => ({ label: c.slice(0, c.indexOf("=")).trim(), cmd: c.slice(c.indexOf("=") + 1).trim() })) + + property var icons: ({}) + property real cpu: 0 // 0..1 + property real mem: 0 // 0..1 + property real swap: 0 // 0..1 + property string memText: "" + property var load: [0, 0, 0] + property real uptime: 0 // seconds + property var _lastCpu: null + + function icon(entry) { + const n = entry ? String(entry.icon || "") : ""; + if (n.startsWith("/")) return "file://" + n; + return svc.icons[n] ? "file://" + svc.icons[n] : ""; + } + function isPinned(entry) { return svc.appIds.includes(entry.id); } + function togglePin(entry) { + const ids = svc.isPinned(entry) ? svc.appIds.filter(x => x !== entry.id) : svc.appIds.concat([entry.id]); + svc.save("apps", ids.join(" ")); + } + // list with the item at `from` moved to `to`; null when nothing changes + function movedTo(list, from, to) { + if (from < 0 || to < 0 || from >= list.length || to >= list.length || from === to) return null; + const a = list.slice(); + a.splice(to, 0, a.splice(from, 1)[0]); + return a; + } + function moveApp(from, to) { + const a = svc.movedTo(svc.pinned.map(e => e.id), from, to); + if (a) svc.save("apps", a.join(" ")); + } + function moveCommand(from, to) { + const a = svc.movedTo(svc.commands, from, to); + if (a) svc.saveCommands(a); + } + + // sections: the panel order, "widgets pinned commands"; unknown keys drop, + // missing ones append, so a new section shows up for old settings + readonly property var allSections: ["widgets", "pinned", "commands"] + readonly property var sectionOrder: { + const o = String(get("sectionOrder", "")).split(/\s+/).filter(k => allSections.includes(k)); + return o.concat(allSections.filter(k => !o.includes(k))); + } + function moveSection(from, to) { + const a = svc.movedTo(svc.sectionOrder, from, to); + if (a) svc.save("sectionOrder", a.join(" ")); + } + + function launch(entry) { + entry.execute(); + pluginApi.closePanel(); + } + + function addCommand(label, cmd) { + label = label.replace(/[=;]/g, " ").trim(); + cmd = cmd.replace(/;/g, " ").trim(); + if (!label || !cmd) return; + svc.saveCommands(svc.commands.concat([{ label, cmd }])); + } + function removeCommand(i) { svc.saveCommands(svc.commands.filter((_, j) => j !== i)); } + function saveCommands(list) { + svc.save("commands", list.map(c => c.label + "=" + c.cmd).join(" ; ")); + } + // ponytail: argv split with simple quotes, no shell (no pipes / && / $VARS); wrap a + // script in bin/ or your PATH if you need those. + function argv(cmd) { + return (cmd.match(/"[^"]*"|'[^']*'|\S+/g) || []).map(a => a.replace(/^(["'])(.*)\1$/, "$2")); + } + function run(c) { + Quickshell.execDetached(svc.argv(c.cmd)); + pluginApi.closePanel(); + } + // installed desktop-widget plugins, hosted in the panel's Widgets section + property var deskPlugins: [] + + // widgets: the chosen widget keys in panel order, e.g. "calendar notes plugin:awe-git-dashboard" + readonly property var availableWidgets: [ + { key: "clock", label: "Clock" }, + { key: "calendar", label: "Calendar" }, + { key: "music", label: "Music" }, + { key: "aio", label: "All-in-one" }, + { key: "stats", label: "System stats" }, + { key: "weather", label: "Weather" }, + { key: "notes", label: "Notes" }, + { key: "visualizer", label: "Visualizer" } + ].concat(deskPlugins.map(p => ({ key: "plugin:" + p.id, label: p.name, plugin: p }))) + readonly property var widgetKeys: String(get("widgetList", "calendar music notes")).split(/\s+/).filter(x => x.length) + readonly property var chosenWidgets: widgetKeys.map(k => availableWidgets.find(w => w.key === k)).filter(w => !!w) + function moveWidget(from, to) { + const a = svc.movedTo(svc.chosenWidgets.map(w => w.key), from, to); + if (a) svc.save("widgetList", a.join(" ")); + } + function toggleWidget(key) { + const ks = widgetKeys.includes(key) ? widgetKeys.filter(k => k !== key) : widgetKeys.concat([key]); + svc.save("widgetList", ks.join(" ")); + } + Process { + id: plugList + command: ["ryoku", "plugin", "list", "--json"] + running: true + stdout: StdioCollector { + onStreamFinished: { + try { + svc.deskPlugins = JSON.parse(this.text) + .filter(p => (p.hosts || []).includes("desktopWidget") && p.id !== "launchpad"); + } catch (e) {} + } + } + } + + // notes: one plain-text file in stateDir, saved shortly after typing stops + property string notes: notesFile.loaded ? notesFile.text() : "" + FileView { + id: notesFile + path: svc.pluginApi ? svc.pluginApi.stateDir + "/notes.txt" : "" + printErrors: false + property bool loaded: false + onLoaded: loaded = true + onLoadFailed: loaded = true // first run: no file yet + } + Timer { id: notesSave; interval: 600; onTriggered: notesFile.setText(svc.notes) } + function setNotes(t) { if (t === svc.notes) return; svc.notes = t; notesSave.restart(); } + + // re-list desktop-widget plugins each time the panel opens + readonly property bool panelOpen: pluginApi ? pluginApi.panelOpen : false + onPanelOpenChanged: if (panelOpen && !plugList.running) plugList.running = true + + Process { + command: ["ryoku-shell", "icons"] + running: true + stdout: StdioCollector { + onStreamFinished: { + try { svc.icons = JSON.parse(this.text).icons || {}; } catch (e) {} + } + } + } + + FileView { id: stat; path: "/proc/stat"; blockAllReads: true; printErrors: false } + FileView { id: loadavg; path: "/proc/loadavg"; blockAllReads: true; printErrors: false } + FileView { id: uptimeFile; path: "/proc/uptime"; blockAllReads: true; printErrors: false } + FileView { id: meminfo; path: "/proc/meminfo"; blockAllReads: true; printErrors: false } + + // sampled only while the panel is open + Timer { + interval: 2000 + repeat: true + triggeredOnStart: true + running: svc.pluginApi ? svc.pluginApi.panelOpen : false + onTriggered: { + stat.reload(); + meminfo.reload(); + const f = (stat.text().split("\n")[0] || "").trim().split(/\s+/).slice(1).map(Number); + if (f.length >= 4) { + const idle = f[3] + (f[4] || 0), total = f.reduce((a, b) => a + b, 0); + if (svc._lastCpu && total > svc._lastCpu.total) + svc.cpu = 1 - (idle - svc._lastCpu.idle) / (total - svc._lastCpu.total); + svc._lastCpu = { idle, total }; + } + const m = meminfo.text(); + const kb = k => Number((m.match(new RegExp("^" + k + ":\\s+(\\d+)", "m")) || [0, 0])[1]); + if (kb("MemTotal") > 0) { + svc.mem = 1 - kb("MemAvailable") / kb("MemTotal"); + svc.memText = ((kb("MemTotal") - kb("MemAvailable")) / 1048576).toFixed(1) + " / " + (kb("MemTotal") / 1048576).toFixed(1) + " GiB"; + } + svc.swap = kb("SwapTotal") > 0 ? 1 - kb("SwapFree") / kb("SwapTotal") : 0; + loadavg.reload(); + svc.load = loadavg.text().trim().split(/\s+/).slice(0, 3).map(Number); + uptimeFile.reload(); + svc.uptime = Number(uptimeFile.text().split(" ")[0]) || 0; + } + } + + // weather: the shell daemon's public `weather` feed (it owns the fetch, location and + // units, set in Ryoku Hub), so this plugin makes no network call of its own + property var weather: null + Socket { + id: wx + path: (Quickshell.env("XDG_RUNTIME_DIR") || "/tmp") + "/ryoku-shell.sock" + parser: SplitParser { + onRead: line => { + try { const f = JSON.parse(line); if (f.status) svc.weather = f; } catch (e) {} + } + } + onConnectedChanged: if (connected) { write("subscribe weather\n"); flush(); } + } + Timer { + interval: 5000 + running: svc.panelOpen && !wx.connected + repeat: true + triggeredOnStart: true + onTriggered: wx.connected = true + } + + // visualizer: a cava spectrum, run only while a panel widget asks for it + property int vizUsers: 0 + property var bars: [] + readonly property string cavaConf: pluginApi ? pluginApi.stateDir + "/cava.conf" : "" + FileView { id: cavaFile; path: svc.cavaConf; printErrors: false } + Process { + id: cava + command: ["cava", "-p", svc.cavaConf] + running: svc.vizUsers > 0 && svc.panelOpen && svc.cavaConf.length > 0 + onRunningChanged: if (!running) svc.bars = [] + stdout: SplitParser { + onRead: line => svc.bars = line.split(";").filter(x => x.length).map(x => Number(x) / 100) + } + } + onCavaConfChanged: if (cavaConf) Qt.callLater(() => cavaFile.setText("[general]\nframerate = 30\nbars = 32\n\n[input]\nmethod = pipewire\nsource = auto\n\n" + + "[output]\nmethod = raw\nraw_target = /dev/stdout\ndata_format = ascii\nascii_max_range = 100\nchannels = mono\nmono_option = average\n\n" + + "[smoothing]\nnoise_reduction = 45\n")) +} diff --git a/plugins/registry.json b/plugins/registry.json index 900f9833..df5fdf72 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -1354,6 +1354,35 @@ "desktopWidget" ], "lastUpdated": "2026-10-02" + }, + { + "id": "launchpad", + "name": "Launchpad", + "version": "0.1.0", + "path": "plugins/launchpad", + "author": "guyb ", + "summary": "A pinboard under one bar mark: your pick of widgets (clock, calendar, music, all-in-one, system stats, weather, notes, visualizer, or any...", + "description": "A pinboard under one bar mark: your pick of widgets (clock, calendar, music, all-in-one, system stats, weather, notes, visualizer, or any installed desktop-widget plugin), favourite apps and one-click commands. Reorder or hide anything.", + "tags": [ + "bar-widget" + ], + "accent": "#8f8378", + "surface": "#101010", + "preview": "assets/preview-widget.png", + "screenshots": [ + "assets/preview-widget.png", + "assets/preview-edit.png", + "assets/preview-panel.png" + ], + "manifest": "product-manifest.json", + "manifestSha256": "ec01062c137d202f175baa9281e3a8b1bbbacb86a02f787b81be291cc150c24d", + "official": false, + "tagline": "A pinboard under one bar mark: your pick of widgets (clock, calendar, music, all-in-one, system stats, weather, notes, visualizer, or any...", + "icon": "", + "hosts": [ + "topbarGlyph" + ], + "lastUpdated": "2026-10-06" } ] } From 4062de24b57ce9cdf066abd7fc47e4e9190dfcbd Mon Sep 17 00:00:00 2001 From: guyb Date: Tue, 6 Oct 2026 22:19:51 +0300 Subject: [PATCH 2/3] launchpad: add upstream, lead with the panel screenshot Co-Authored-By: Claude Opus 5.5 --- plugins/registry.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/registry.json b/plugins/registry.json index df5fdf72..a201bff9 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -1368,11 +1368,11 @@ ], "accent": "#8f8378", "surface": "#101010", - "preview": "assets/preview-widget.png", + "preview": "assets/preview-panel.png", "screenshots": [ - "assets/preview-widget.png", + "assets/preview-panel.png", "assets/preview-edit.png", - "assets/preview-panel.png" + "assets/preview-widget.png" ], "manifest": "product-manifest.json", "manifestSha256": "ec01062c137d202f175baa9281e3a8b1bbbacb86a02f787b81be291cc150c24d", @@ -1382,7 +1382,8 @@ "hosts": [ "topbarGlyph" ], - "lastUpdated": "2026-10-06" + "lastUpdated": "2026-10-06", + "upstream": "https://github.com/ryoku-dev/ryostore/tree/main/plugins/launchpad" } ] } From b968e67857055d538fd2a78ababe726270220f6d Mon Sep 17 00:00:00 2001 From: guyb Date: Wed, 7 Oct 2026 00:11:33 +0300 Subject: [PATCH 3/3] launchpad: run quick commands in the terminal Commands now open through `ryoku-app terminal --`, so they run in the user's chosen terminal instead of detached with no window. Co-Authored-By: Claude Opus 5.5 --- plugins/launchpad/README.md | 2 +- plugins/launchpad/manifest.json | 1 + plugins/launchpad/service/Main.qml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/launchpad/README.md b/plugins/launchpad/README.md index 7c7cf4bb..3a7845e5 100644 --- a/plugins/launchpad/README.md +++ b/plugins/launchpad/README.md @@ -26,7 +26,7 @@ Everything is also in QS Bar Settings > Community > Launchpad. ## What it runs, reads and writes -- **Runs**: the apps you pin (through their `.desktop` entries); your quick + commands, exactly as you typed them, in your terminal (`ryoku-app terminal --`), as an argv split on spaces (quotes group commands, exactly as you typed them, as an argv split on spaces (quotes group words). There is **no shell**, so pipes, `&&` and `$VARS` do nothing; point a command at a script on your `PATH` if you need them. It also runs diff --git a/plugins/launchpad/manifest.json b/plugins/launchpad/manifest.json index 546c73d1..0572f6de 100644 --- a/plugins/launchpad/manifest.json +++ b/plugins/launchpad/manifest.json @@ -38,6 +38,7 @@ "commands": [ "ryoku", "ryoku-shell", + "ryoku-app", "cava" ] }, diff --git a/plugins/launchpad/service/Main.qml b/plugins/launchpad/service/Main.qml index 6952b28b..ef71fc80 100644 --- a/plugins/launchpad/service/Main.qml +++ b/plugins/launchpad/service/Main.qml @@ -118,7 +118,7 @@ Item { return (cmd.match(/"[^"]*"|'[^']*'|\S+/g) || []).map(a => a.replace(/^(["'])(.*)\1$/, "$2")); } function run(c) { - Quickshell.execDetached(svc.argv(c.cmd)); + Quickshell.execDetached(["ryoku-app", "terminal", "--"].concat(svc.argv(c.cmd))); pluginApi.closePanel(); } // installed desktop-widget plugins, hosted in the panel's Widgets section