Move panel plugins under panels
This commit is contained in:
@@ -29,6 +29,13 @@ shell/
|
|||||||
image-picker/
|
image-picker/
|
||||||
menu/
|
menu/
|
||||||
notifications/
|
notifications/
|
||||||
|
panels/
|
||||||
|
audio/
|
||||||
|
bluetooth/
|
||||||
|
monitor/
|
||||||
|
network/
|
||||||
|
power/
|
||||||
|
weather/
|
||||||
services/
|
services/
|
||||||
battery/
|
battery/
|
||||||
idle/
|
idle/
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ User-installed plugins live alongside these conceptually but on disk under
|
|||||||
| Reminders | `omarchy.reminders` | `overlay` | `reminders/ReminderFlow.qml` |
|
| Reminders | `omarchy.reminders` | `overlay` | `reminders/ReminderFlow.qml` |
|
||||||
| Omarchy menu | `omarchy.menu` | `menu`, `bar-widget` | `menu/Menu.qml`, `menu/BarWidget.qml` |
|
| Omarchy menu | `omarchy.menu` | `menu`, `bar-widget` | `menu/Menu.qml`, `menu/BarWidget.qml` |
|
||||||
| Notifications | `omarchy.notifications` | `service`, `bar-widget` | `notifications/Service.qml`, `notifications/BarWidget.qml` |
|
| Notifications | `omarchy.notifications` | `service`, `bar-widget` | `notifications/Service.qml`, `notifications/BarWidget.qml` |
|
||||||
|
| Audio | `omarchy.audio` | `bar-widget` | `panels/audio/Panel.qml` |
|
||||||
|
| Bluetooth | `omarchy.bluetooth` | `bar-widget` | `panels/bluetooth/Panel.qml` |
|
||||||
|
| Monitor | `omarchy.monitor` | `bar-widget` | `panels/monitor/Panel.qml` |
|
||||||
|
| Network | `omarchy.network` | `bar-widget` | `panels/network/Panel.qml` |
|
||||||
|
| Power | `omarchy.power` | `bar-widget` | `panels/power/Panel.qml` |
|
||||||
|
| Weather | `omarchy.weather` | `bar-widget` | `panels/weather/BarWidget.qml` |
|
||||||
| Media | `omarchy.media` | `service`, `bar-widget` | `services/media/Service.qml`, `services/media/BarWidget.qml` |
|
| Media | `omarchy.media` | `service`, `bar-widget` | `services/media/Service.qml`, `services/media/BarWidget.qml` |
|
||||||
| Battery | `omarchy.battery` | `service` | `services/battery/Service.qml` |
|
| Battery | `omarchy.battery` | `service` | `services/battery/Service.qml` |
|
||||||
| Idle | `omarchy.idle` | `service` | `services/idle/Service.qml` |
|
| Idle | `omarchy.idle` | `service` | `services/idle/Service.qml` |
|
||||||
@@ -28,9 +34,8 @@ User-installed plugins live alongside these conceptually but on disk under
|
|||||||
| Polkit agent | `omarchy.polkit` | `service` | `polkit/PolkitAgent.qml` |
|
| Polkit agent | `omarchy.polkit` | `service` | `polkit/PolkitAgent.qml` |
|
||||||
|
|
||||||
First-party bar-only widgets also carry manifests next to their QML files,
|
First-party bar-only widgets also carry manifests next to their QML files,
|
||||||
e.g. `bar/widgets/Clock.manifest.json`. Rich popup widgets live in feature
|
e.g. `bar/widgets/Clock.manifest.json`. Rich popup widgets live under
|
||||||
plugin directories such as `audio/`, `network/`, and `power/`, each with its
|
`panels/`, each with its own `manifest.json`.
|
||||||
own `manifest.json`.
|
|
||||||
|
|
||||||
## Bar
|
## Bar
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ the shell for its whole session.
|
|||||||
- `manifest.json` declares the plugin (`id: omarchy.bar`, `kind: bar`) and points at `Bar.qml` as the entry point.
|
- `manifest.json` declares the plugin (`id: omarchy.bar`, `kind: bar`) and points at `Bar.qml` as the entry point.
|
||||||
- `Bar.qml` is Omarchy-owned bar engine code, loaded by the omarchy-shell host. Users should not edit it directly.
|
- `Bar.qml` is Omarchy-owned bar engine code, loaded by the omarchy-shell host. Users should not edit it directly.
|
||||||
- `widgets/` holds simple first-party bar widgets with sibling manifests.
|
- `widgets/` holds simple first-party bar widgets with sibling manifests.
|
||||||
- Feature plugins such as `../audio/`, `../network/`, and `../power/` provide richer popup bar widgets.
|
- Feature plugins such as `../panels/audio/`, `../panels/network/`, and `../panels/power/` provide richer popup bar widgets.
|
||||||
- The bar receives its config from the host shell as a `barConfig` property; the host loads it from `~/.config/omarchy/shell.json` (or `config/omarchy/shell.json` when the user has no file).
|
- The bar receives its config from the host shell as a `barConfig` property; the host loads it from `~/.config/omarchy/shell.json` (or `config/omarchy/shell.json` when the user has no file).
|
||||||
- `omarchy-style-bar-position` updates only the user shell.json file.
|
- `omarchy-style-bar-position` updates only the user shell.json file.
|
||||||
|
|
||||||
@@ -167,8 +167,8 @@ Widgets receive `bar` (the shell root), `moduleName` (string), and `settings` (o
|
|||||||
|
|
||||||
First-party bar widgets are manifest-backed just like third-party widgets.
|
First-party bar widgets are manifest-backed just like third-party widgets.
|
||||||
Simple widgets carry sibling manifests such as `widgets/Clock.manifest.json`;
|
Simple widgets carry sibling manifests such as `widgets/Clock.manifest.json`;
|
||||||
richer popup widgets live in feature directories such as `../audio/` and
|
richer popup widgets live in feature directories such as `../panels/audio/` and
|
||||||
`../network/`; and feature plugins such as `omarchy.menu`, `omarchy.media`, and
|
`../panels/network/`; and feature plugins such as `omarchy.menu`, `omarchy.media`, and
|
||||||
`omarchy.notifications` declare their bar-widget entry points in their own
|
`omarchy.notifications` declare their bar-widget entry points in their own
|
||||||
`manifest.json`. Bar layout ids are namespaced, e.g. `omarchy.audio`,
|
`manifest.json`. Bar layout ids are namespaced, e.g. `omarchy.audio`,
|
||||||
`omarchy.network`, and `omarchy.clock`. Older UpperCamelCase ids such as
|
`omarchy.network`, and `omarchy.clock`. Older UpperCamelCase ids such as
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ Item {
|
|||||||
color: Qt.darker(root.foreground, 1.4)
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: Style.font.bodySmall
|
font.pixelSize: Style.font.bodySmall
|
||||||
text: "Single cursor. Most reusable panel primitives expose hasCursor: bool and emit hovered(bool); composed rows (including sliders) wrap their content in CursorSurface. The panel root owns cursorActive + focusSection + selectedIndex; each element binds hasCursor: root.cursorActive && root.focusSection === 'X' && root.selectedIndex === N, and onHovered flips cursorActive on while updating the same state. No initial highlight, then one highlight on screen once the keyboard or mouse enters. See plugins/audio/Panel.qml for the canonical recipe."
|
text: "Single cursor. Most reusable panel primitives expose hasCursor: bool and emit hovered(bool); composed rows (including sliders) wrap their content in CursorSurface. The panel root owns cursorActive + focusSection + selectedIndex; each element binds hasCursor: root.cursorActive && root.focusSection === 'X' && root.selectedIndex === N, and onHovered flips cursorActive on while updating the same state. No initial highlight, then one highlight on screen once the keyboard or mouse enters. See plugins/panels/audio/Panel.qml for the canonical recipe."
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -191,11 +191,11 @@ Panel {
|
|||||||
|
|
||||||
function brightness(percent: string): string { return root.brightnessIpc(percent) }
|
function brightness(percent: string): string { return root.brightnessIpc(percent) }
|
||||||
function state(): string { return root.stateIpc() }
|
function state(): string { return root.stateIpc() }
|
||||||
function open(): void { root.open() }
|
function open() { root.open() }
|
||||||
function close(): void { root.close() }
|
function close() { root.close() }
|
||||||
function toggle(): void { root.toggle() }
|
function toggle() { root.toggle() }
|
||||||
function show(): void { root.open() }
|
function show() { root.open() }
|
||||||
function hide(): void { root.close() }
|
function hide() { root.close() }
|
||||||
}
|
}
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
@@ -267,7 +267,8 @@ QtObject {
|
|||||||
if (scanning) return
|
if (scanning) return
|
||||||
scanning = true
|
scanning = true
|
||||||
// $0 = first-party dir, $1 = third-party dir. Some bash versions need the explicit -- separator.
|
// $0 = first-party dir, $1 = third-party dir. Some bash versions need the explicit -- separator.
|
||||||
// First-party plugins may be grouped one level deeper, e.g. services/battery.
|
// First-party plugins may be grouped one level deeper, e.g. panels/audio
|
||||||
|
// or services/battery.
|
||||||
// First-party bar widgets can also carry sibling manifests such as
|
// First-party bar widgets can also carry sibling manifests such as
|
||||||
// widgets/Clock.manifest.json so multiple widgets can live in one source
|
// widgets/Clock.manifest.json so multiple widgets can live in one source
|
||||||
// directory without wrapper folders.
|
// directory without wrapper folders.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
|||||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
run_node_test <<'JS'
|
run_node_test <<'JS'
|
||||||
const audio = requireFromRoot('shell/plugins/audio/Model.js')
|
const audio = requireFromRoot('shell/plugins/panels/audio/Model.js')
|
||||||
|
|
||||||
assert(audio.isPlaybackStream({ isStream: true, isSink: true }), 'audio detects sink-backed playback streams')
|
assert(audio.isPlaybackStream({ isStream: true, isSink: true }), 'audio detects sink-backed playback streams')
|
||||||
assert(audio.isPlaybackStream({ isStream: true, type: 'Stream/Output/Audio' }), 'audio detects typed playback streams')
|
assert(audio.isPlaybackStream({ isStream: true, type: 'Stream/Output/Audio' }), 'audio detects typed playback streams')
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
|||||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
run_node_test <<'JS'
|
run_node_test <<'JS'
|
||||||
const bluetooth = requireFromRoot('shell/plugins/bluetooth/Model.js')
|
const bluetooth = requireFromRoot('shell/plugins/panels/bluetooth/Model.js')
|
||||||
|
|
||||||
assert(bluetooth.isUuidLike('0000110b-0000-1000-8000-00805f9b34fb'), 'bluetooth detects UUID-like names')
|
assert(bluetooth.isUuidLike('0000110b-0000-1000-8000-00805f9b34fb'), 'bluetooth detects UUID-like names')
|
||||||
assert(bluetooth.isAddressLike('AA:BB:CC:DD:EE:FF'), 'bluetooth detects address-like names')
|
assert(bluetooth.isAddressLike('AA:BB:CC:DD:EE:FF'), 'bluetooth detects address-like names')
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
|||||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
run_node_test <<'JS'
|
run_node_test <<'JS'
|
||||||
const monitor = requireFromRoot('shell/plugins/monitor/Model.js')
|
const monitor = requireFromRoot('shell/plugins/panels/monitor/Model.js')
|
||||||
|
|
||||||
assertEqual(monitor.clampBrightness(0), 1, 'monitor clamps minimum brightness')
|
assertEqual(monitor.clampBrightness(0), 1, 'monitor clamps minimum brightness')
|
||||||
assertEqual(monitor.clampBrightness(101), 100, 'monitor clamps maximum brightness')
|
assertEqual(monitor.clampBrightness(101), 100, 'monitor clamps maximum brightness')
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
|||||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
run_node_test <<'JS'
|
run_node_test <<'JS'
|
||||||
const network = requireFromRoot('shell/plugins/network/Model.js')
|
const network = requireFromRoot('shell/plugins/panels/network/Model.js')
|
||||||
|
|
||||||
assertDeepEqual(
|
assertDeepEqual(
|
||||||
network.parseNetworkStatus('wifi\tCafe WiFi\t78\t5200\n'),
|
network.parseNetworkStatus('wifi\tCafe WiFi\t78\t5200\n'),
|
||||||
|
|||||||
@@ -59,24 +59,29 @@ function assertSafeEntryPoint(manifest, manifestPath, key, value) {
|
|||||||
const manifests = walk(pluginsDir)
|
const manifests = walk(pluginsDir)
|
||||||
const manifestPaths = manifests.map(relativeFromPlugins)
|
const manifestPaths = manifests.map(relativeFromPlugins)
|
||||||
const manifestSet = new Set(manifestPaths)
|
const manifestSet = new Set(manifestPaths)
|
||||||
|
const groupedPluginRoots = new Set(['panels', 'services'])
|
||||||
|
|
||||||
assert(manifests.length > 0, 'plugin manifests are present')
|
assert(manifests.length > 0, 'plugin manifests are present')
|
||||||
|
|
||||||
for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
|
||||||
if (!entry.isDirectory() || entry.name === 'services') continue
|
if (!entry.isDirectory() || groupedPluginRoots.has(entry.name)) continue
|
||||||
check(
|
check(
|
||||||
manifestSet.has(`${entry.name}/manifest.json`),
|
manifestSet.has(`${entry.name}/manifest.json`),
|
||||||
`top-level plugin ${entry.name} must have a manifest`
|
`top-level plugin ${entry.name} must have a manifest`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceRoot = path.join(pluginsDir, 'services')
|
for (const groupName of groupedPluginRoots) {
|
||||||
for (const entry of fs.readdirSync(serviceRoot, { withFileTypes: true })) {
|
const groupRoot = path.join(pluginsDir, groupName)
|
||||||
if (!entry.isDirectory()) continue
|
if (!fs.existsSync(groupRoot)) continue
|
||||||
check(
|
|
||||||
manifestSet.has(`services/${entry.name}/manifest.json`),
|
for (const entry of fs.readdirSync(groupRoot, { withFileTypes: true })) {
|
||||||
`service plugin ${entry.name} must have a manifest`
|
if (!entry.isDirectory()) continue
|
||||||
)
|
check(
|
||||||
|
manifestSet.has(`${groupName}/${entry.name}/manifest.json`),
|
||||||
|
`${groupName} plugin ${entry.name} must have a manifest`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const manifestPath of manifestPaths) {
|
for (const manifestPath of manifestPaths) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
|||||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
run_node_test <<'JS'
|
run_node_test <<'JS'
|
||||||
const power = requireFromRoot('shell/plugins/power/Model.js')
|
const power = requireFromRoot('shell/plugins/panels/power/Model.js')
|
||||||
const states = { Charging: 1, Discharging: 2, FullyCharged: 3, PendingCharge: 4 }
|
const states = { Charging: 1, Discharging: 2, FullyCharged: 3, PendingCharge: 4 }
|
||||||
|
|
||||||
assertEqual(power.selectProfileIndex(0, 1, ['balanced', 'performance']), 1, 'power advances profile selection')
|
assertEqual(power.selectProfileIndex(0, 1, ['balanced', 'performance']), 1, 'power advances profile selection')
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
|||||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
run_node_test <<'JS'
|
run_node_test <<'JS'
|
||||||
const weather = requireFromRoot('shell/plugins/weather/Model.js')
|
const weather = requireFromRoot('shell/plugins/panels/weather/Model.js')
|
||||||
|
|
||||||
assertDeepEqual(
|
assertDeepEqual(
|
||||||
weather.parseWeatherStatus('{"text":"☀","class":"sunny"}'),
|
weather.parseWeatherStatus('{"text":"☀","class":"sunny"}'),
|
||||||
|
|||||||
Reference in New Issue
Block a user