feat(quickshell): open a calendar on clock click
Update PKGBUILD version / update-pkgver (push) Successful in 6s
Update PKGBUILD version / update-pkgver (push) Successful in 6s
The clock's click toggled the date between short and long form, which the tooltip already showed in full. It now opens a calendar popout instead. CalendarPopout uses MonthGrid + WeekNumberColumn + DayOfWeekRow from QtQuick.Controls (already imported by TrayMenu) so the locale rules — week start, ISO week numbers, weekday and month names — come from Qt rather than being re-implemented; only the delegates are ours. Leading and trailing days stay visible but dimmed, since the grid always renders six weeks and hiding them would leave the week-number column labelling blank rows. Clock takes the full bar height on `height`, not `implicitHeight`: on a Text root that property is read-only, and the assignment fails the config load outright.
This commit is contained in:
@@ -92,10 +92,12 @@ quickshell is the **default bar and notification daemon**, spawned by niri as `q
|
||||
|
||||
- **Own popouts:** a composed widget carries its own popout as a `Component` and renders it in the shared morphing host. Pattern (see any indicator, e.g. `AudioIndicator.qml`): an inline `Component { AudioPopout { popouts: root.popouts } }`; the widget declares a `popoutName` and registers on completion (`popouts.register(popoutName, popoutComponent, root, payloadFn)`), then its click handler calls `popouts.toggleByName(popoutName)`. The widget file needs `pragma ComponentBehavior: Bound` so the popout resolves `root` when the shared host instantiates it. The low-level `popouts.open(component, rect, payload)` still exists for non-registered popouts (Tray). The host anchors the popout body to the trigger's on-screen box and picks the side from it: **left-aligned** when the trigger sits in the left half of the screen (so left-cluster popouts grow rightward and stay on-screen), **right-aligned** otherwise (`PopoutHost.qml` `frame.leftAnchored`). The anchor rect carries the trigger's left edge + width; both `openByName` and Tray's direct `open()` pass that box.
|
||||
|
||||
- **Popouts by keybind:** registered popouts are addressable by name through the `PopoutRouter` singleton, which exposes an `IpcHandler { target: "popout" }` with `toggle`/`open`/`close`. A niri keybind fires `qs -c moonarch ipc call popout toggle <name>` (e.g. `Mod+A` → `audio`, in `defaults/xdg/niri/config.kdl`). The router routes to the focused monitor's `PopoutState` via `Niri.activeOutput` (workspace `is_focused` → `output`, matched against `screen.name`); an open popout grabs the keyboard (Escape closes). Names: `audio backlight network bluetooth nightlight cpugov battery notifications media launcher camera`. Tray is not keybind-addressable.
|
||||
- **Popouts by keybind:** registered popouts are addressable by name through the `PopoutRouter` singleton, which exposes an `IpcHandler { target: "popout" }` with `toggle`/`open`/`close`. A niri keybind fires `qs -c moonarch ipc call popout toggle <name>` (e.g. `Mod+A` → `audio`, in `defaults/xdg/niri/config.kdl`). The router routes to the focused monitor's `PopoutState` via `Niri.activeOutput` (workspace `is_focused` → `output`, matched against `screen.name`); an open popout grabs the keyboard (Escape closes). Names: `audio backlight network bluetooth nightlight cpugov battery notifications media launcher camera calendar`. Tray is not keybind-addressable.
|
||||
|
||||
- **Launcher:** `Launcher.qml` is the first widget in the left cluster (an apps glyph) and owns `LauncherPopout.qml` — an application launcher over `DesktopEntries.applications` (search field + keyboard-navigable icon+name list; ↑/↓ select, Enter/click launch via `entry.execute()`, Esc close; app icons via `Quickshell.iconPath`). It registers under `launcher` (so it is IPC-addressable) and is bound to `Mod+Space` (`qs -c moonarch ipc call popout toggle launcher`, in `config.kdl`) in addition to icon-click. Scope is app-launch (drun) only; `walker` stays installed and keeps the other modes — `Super+C` still opens walker's clipboard, and walker remains the run-command/calc launcher (no longer on a keybind).
|
||||
|
||||
- **Calendar:** clicking the clock opens `CalendarPopout.qml` (registered as `calendar`) — a `MonthGrid` with a `WeekNumberColumn`, both from `QtQuick.Controls` (already pulled in by `TrayMenu`). Today's cell is an `accent` rectangle with `base` text; leading/trailing days stay visible in `Theme.surface2` because the grid always renders six weeks and hiding them would leave the week-number column pointing at blank rows. Header actions page the month (‹ / ›) and snap back to today (calendar glyph); ←/→ page as well, which no other control in this popout claims. Week start and weekday/month names come from the **system locale** (`Qt.locale()`), so `de_DE` yields Monday-first and ISO week numbers; the title uses `standaloneMonthName` because some locales inflect the month inside a full date. The popout instance survives reopens, so `onShownChanged` snaps back to the current month on every open. Two implementation notes: the clock's root is a `Text`, whose `implicitHeight` is **read-only** (derived from content) — the full bar height goes on `height`, which is what keeps the topmost pixels clickable and anchors the popout below the bar; and a derived `Component.onCompleted` **adds** a handler rather than replacing the base one (verified with an exit-code probe), so `PopoutPanel`'s `focusFirst` still runs alongside the popout's own init. The clock's former click action (long/short date toggle) was dropped — the popout and the tooltip both carry the full date.
|
||||
|
||||
- **Hover affordance:** clickable bar widgets dim to `opacity: 0.8` on hover (110ms `animDurationShort` fade) so they read as interactive. Deliberately **opacity, not color** — the icon color already encodes status (muted/off → `subtext0`, active → `accent`, battery critical → red), so a color-based hover would clobber the state signal. The Launcher is the exception: no status color, so it uses an `accent` hover instead. Applied per widget (no shared parent to hoist it to); `Workspaces`/`Tray` keep their own per-element hover, non-clickable `PrivacyIndicator`/`WindowTitle` get none.
|
||||
|
||||
- **Network indicator + VPN:** quickshell's Networking API exposes no VPN, so `NetworkService.qml` (singleton) holds the VPN connection list via nmcli — event-driven through a long-running `nmcli monitor` process (debounced refresh) plus a 60s fallback poll; shared by `NetworkIndicator` and `NetworkPopout`. The indicator shows connecting (cycling strength glyphs), an active VPN (MDI `wifi-strength-N-lock` variants; wired+VPN → `security-network`), and wired (`ethernet` glyph). **Wired wins over wifi** in both indicator and popout: when both a wired and a wifi connection are up, NM routes over ethernet (lower metric) and nm-applet shows the wired icon — so the widget checks `wiredDev` first (also beats wifi-off, so ethernet-only machines don't show a dead wifi icon).
|
||||
|
||||
@@ -268,3 +268,10 @@
|
||||
- **Tradeoffs**: (1) **Mechanism** — `authorized` on the USB device beats `modprobe -r uvcvideo` (per device rather than all cameras at once, and no reboot after 59 toggles as the Codeberg camera-control tool reports for the module path) and beats interface-level tricks (there is no per-interface authorize for non-wireless USB). Measured: `authorized=0` drops the `/dev/videoN` nodes immediately, udev emits `remove` per node, the PipeWire node disappears; `authorized=1` brings the nodes back, `uvcvideo` rebinds itself, capture works, and PipeWire re-exposes the camera a few seconds later under a **new** node id. Cost: the switch cuts the whole USB function, so a webcam's built-in microphone dies with it (`3-1` carries `01/01,01/02`) and the internal camera's DFU interface too (`5-1` carries `fe/01`, so fwupd cannot update it while off) — surfaced in the popout. USB-only by construction: MIPI/IPU6 cameras have no `authorized`, the widget self-hides there. (2) **Identification** — `descriptors` (world-readable, `-r--r--r--`) carries every interface class and is the only source that works in **both** states. Measured across a full deauthorize cycle of the internal camera: the blob stays at 1420 bytes with identical interface classes while `authorized=0`, whereas the interface directories drop from 5 to 0 — so they cannot be the gate. `bDeviceClass` is useless as well (`0xef` on every composite camera). `moonarch-camera-list` correctly reported the switched-off camera as `off`, i.e. it stays listable and switchable with no state of our own. Verified across 21 USB devices that interface class `0x0e` selects exactly the two cameras (hubs `09`, HID `03`, audio `01`, AV adapter `11`). Gotcha: `wc -c` reports 65553 for that file on every device — the real length is 1420 / 1332 bytes here, so the TLV walk must stop on `bLength == 0`. (3) **No state file** — an earlier draft kept a registry of known cameras under `/var/lib/moonarch/`; rejected because that directory is group-writable by `wheel`, so the privileged helper would have been validating its target against a caller-writable allowlist. It now trusts only kernel data: `descriptors`, `DEVTYPE=usb_device` from `uevent`, and the `authorized` symlink resolved into `/sys/devices/`. Note `/sys/bus/usb/devices/<id>` is itself a symlink, so confinement cannot be checked against that prefix. (4) **Boot enforcement** — a systemd oneshot, not a udev rule. A rule on `add` would also disable **hot-plugged** cameras, and plugging one in is a deliberate act; a fresh webcam that is dead until a password prompt is the wrong default. Accepted gap: `udevadm settle` only waits for already-queued events, so a camera enumerating very late behind a dock can stay on until switched. (5) **Auth** — plain `pkexec` with the standard password prompt, as `moonarch-batsaver-*` (2026-05-04); no polkit rule, since a no-password rule would let any process in the session re-arm the camera unnoticed and defeat the switch. Popout-spawned prompts are typeable thanks to the `OnDemand` keyboard focus fix (2026-07-09).
|
||||
- **How**: Four scripts in `defaults/bin/` — `moonarch-camera-list` (unprivileged; prints `id|vid:pid|label|on/off|video[+audio]`, sanitizes device-supplied product strings so they cannot forge columns), `moonarch-camera-toggle` (unprivileged pkexec dispatch, passes the exit code through), `moonarch-camera-apply` (privileged; validation ladder = value ∈ {0,1}, bus-port id shape, device exists, `authorized` is a real file, resolved path under `/sys/devices/`, `DEVTYPE=usb_device`, video interface class present, then an idempotence skip before the write), `moonarch-camera-boot` (enumerates via `-list`, writes 0 to each). New unit `defaults/etc/systemd/system/moonarch-camera.service` (`Type=oneshot`, `ConditionPathExistsGlob=/sys/class/video4linux/video*`, batsaver's hardening block, enabled from `moonarch.install`; `Type=oneshot` also satisfies `moonarch-doctor`'s oneshot handling). QML trio `CameraService` / `CameraIndicator` / `CameraPopout`: the service keeps a `probed` flag so a failed list never empties the bar widget, an optimistic `pending` map so a row reacts before the password prompt returns, a single in-flight `pkexec` so prompts cannot stack, and treats exit 126 as a silent cancel. Its `udevadm monitor` watches the **usb** subsystem, not `video4linux` — unplugging an already switched-off camera emits no video4linux event, and the widget would keep listing a device that is gone.
|
||||
- **Follow-up (same day, after audit)**: A security audit of the finished code found that the camera gate accepts a device as soon as *any* interface reports class `0x0e`, while `authorized=1` re-authorizes the **whole** device — so a composite device with an alibi video interface plus a hidden keyboard would appear in the popout as a camera and go live on the first switch-on. Rejected blocking it by allow-list or deny-list: the internal camera itself carries a DFU interface (`0xfe`) and multimedia devices commonly expose HID for their buttons (measured: the USB audio device at `3-2.2` reports `01,01,01,01,01,03`), so a class filter would lock out legitimate hardware, while the attack needs physical access to a port. Instead `moonarch-camera-list` now reports every notable interface class and the popout names them per row, in red for classes that have no business on a camera. Also fixed from the same audit: `textFormat: Text.PlainText` on the device-supplied label and in the shared `Tooltip` (matching `NotifToast`/`NotifCenter`, which already guard foreign text), and the label sanitizer now strips the invisible/directional Unicode range (`U+200B..U+200F`, `U+202A..U+202E`, `U+2066..U+2069`) under `LC_ALL=C`, so a device cannot make its name render as another one's — verified that `|`, control characters, RLO and zero-width characters are removed while umlauts survive. Left as known and accepted: a TOCTOU window between the checks and the write that would need dedicated switching hardware inside a millisecond gap after authentication, and the absence of `CapabilityBoundingSet`/`SystemCallFilter` in the unit, which is inherited from `moonarch-batsaver.service` and would be a shared change.
|
||||
|
||||
## 2026-08-03 – Calendar popout on the clock (Qt calendar controls, no backend)
|
||||
- **Who**: Dominik, ClaudeCode
|
||||
- **Why**: Clicking the clock did nothing useful — it toggled the date between a short and a long form, which the tooltip already showed in full. A calendar is the expected thing behind a clock, and every other bar widget already opens a popout on click.
|
||||
- **Tradeoffs**: (1) **Grid** — `MonthGrid` + `WeekNumberColumn` + `DayOfWeekRow` from `QtQuick.Controls` rather than a hand-rolled grid: they carry the locale rules (week start, ISO week numbers, weekday names) that would otherwise be re-implemented, and `QtQuick.Controls` is already imported by `TrayMenu` (`StackView`), so no new dependency. Only the delegates are ours, so the Catppuccin look is unaffected. (2) **Click action** — the `showYear` toggle was dropped rather than moved to right-click: the popout header carries month + year and the tooltip carries the full date, so the toggle was redundant, and right-click in this bar means "tray context menu" everywhere else. (3) **Leading/trailing days shown dimmed** (`Theme.surface2`) instead of hidden as Qt's default delegate does — the grid always renders six weeks, so hiding them leaves the week-number column labelling blank rows. (4) **Locale, not a hardcoded week start** — `Qt.locale()`, so `de_DE` gives Monday-first + ISO weeks and an `en_US` install gets its own convention; the title uses `standaloneMonthName` because a month inflected for a full date reads wrong on its own. (5) **No appointments** — nothing on the system provides them (no khal/vdirsyncer/Evolution), so a source would have to be chosen first; scoped out.
|
||||
- **How**: New `CalendarPopout.qml` (`PopoutPanel`, `panelWidth: 300`; cell size derived so the week column plus seven day columns fill `contentWidth` exactly; header actions ‹ / calendar-today / ›, ←/→ page months, `onShownChanged` snaps back to today since the instance survives reopens). `Clock.qml` gains the standard popout wiring (`popoutName: "calendar"`, `register`, `toggleByName`) plus `pragma ComponentBehavior: Bound`, loses `showYear`, and takes the full bar height on **`height`** — `Text.implicitHeight` is read-only (content-derived) and assigning it fails the config load. `shell.qml` passes `popouts: bar.popouts` to `Clock`; `qmldir` regenerated. No niri keybind added (the name is IPC-addressable, a binding is a separate call).
|
||||
- **Verified**: `qmllint` clean; throwaway `quickshell -p …/moonarch` reached `Configuration Loaded` with no binding loops (only the expected `org.freedesktop.Notifications` collision with the running instance). Screenshot of the live popout for August 2026: Monday-first, week 31 on the 27.07.–02.08. row, today's cell measured at `#B3BEFD` = `Theme.accent` (the colour fringes on thin digits are subpixel antialiasing, confirmed by sampling). A throwaway probe config that imports the repo component stepped December 2026 → `shiftMonth(1)` → **January 2027**, i.e. the year rollover and the dimmed neighbour-month days behave. Glyph `0xf00f6` confirmed as calendar-today by rendering the MDI range with ImageMagick. That a derived `Component.onCompleted` does not replace the base handler (so `PopoutPanel.focusFirst` still runs) was verified with a separate exit-code probe.
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
// ABOUTME: Calendar popout — month grid with week numbers, opened from the clock widget.
|
||||
// ABOUTME: Month navigation is local view state; every open snaps back to the current month.
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
|
||||
PopoutPanel {
|
||||
id: root
|
||||
property var popouts: null
|
||||
panelWidth: 300
|
||||
// Standalone month name — the genitive form some locales use inside a full date reads wrong alone.
|
||||
title: Qt.locale().standaloneMonthName(root.viewMonth) + " " + root.viewYear
|
||||
|
||||
// Cell geometry — the week-number column plus seven day columns fill contentWidth exactly.
|
||||
readonly property int gridSpacing: 4
|
||||
readonly property int weekColWidth: 24
|
||||
readonly property int cellSize: Math.floor((root.contentWidth - root.weekColWidth - root.gridSpacing * 7) / 7)
|
||||
|
||||
// Displayed month, 0-based as MonthGrid/WeekNumberColumn expect.
|
||||
property int viewMonth: 0
|
||||
property int viewYear: 0
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
|
||||
function showToday() {
|
||||
const d = clock.date;
|
||||
root.viewMonth = d.getMonth();
|
||||
root.viewYear = d.getFullYear();
|
||||
}
|
||||
// Paging through Date handles the year rollover (month -1 / 12) without a manual wrap.
|
||||
function shiftMonth(delta) {
|
||||
const d = new Date(root.viewYear, root.viewMonth + delta, 1);
|
||||
root.viewMonth = d.getMonth();
|
||||
root.viewYear = d.getFullYear();
|
||||
}
|
||||
|
||||
// The popout instance persists across reopens, so initialize once on load and snap back to the
|
||||
// current month on every open. PopoutPanel's own onCompleted (focusFirst) still runs — a derived
|
||||
// Component.onCompleted adds a handler, it does not replace the base one.
|
||||
Component.onCompleted: root.showToday()
|
||||
readonly property bool shown: root.popouts && root.popouts.hasCurrent
|
||||
onShownChanged: if (root.shown)
|
||||
root.showToday()
|
||||
|
||||
// ←/→ page months (no slider in this popout to claim them); Tab/↑/↓ walk the header buttons.
|
||||
Keys.onLeftPressed: root.shiftMonth(-1)
|
||||
Keys.onRightPressed: root.shiftMonth(1)
|
||||
|
||||
headerActions: Row {
|
||||
spacing: 10
|
||||
|
||||
IconButton {
|
||||
pixelSize: Theme.fontSize + 2
|
||||
text: String.fromCodePoint(0xf0141) // md-chevron-left
|
||||
onActivated: root.shiftMonth(-1)
|
||||
}
|
||||
IconButton {
|
||||
pixelSize: Theme.fontSize + 2
|
||||
text: String.fromCodePoint(0xf00f6) // md-calendar-today
|
||||
onActivated: root.showToday()
|
||||
}
|
||||
IconButton {
|
||||
pixelSize: Theme.fontSize + 2
|
||||
text: String.fromCodePoint(0xf0142) // md-chevron-right
|
||||
onActivated: root.shiftMonth(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Week numbers left, weekday header + days right. Both columns use the same row spacing and
|
||||
// cell height, so the week number lines up with its row; the spacer skips the weekday header.
|
||||
Row {
|
||||
spacing: root.gridSpacing
|
||||
|
||||
Column {
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
width: root.weekColWidth
|
||||
height: dow.height
|
||||
}
|
||||
WeekNumberColumn {
|
||||
month: root.viewMonth
|
||||
year: root.viewYear
|
||||
spacing: root.gridSpacing
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
delegate: Text {
|
||||
id: weekCell
|
||||
required property int weekNumber
|
||||
width: root.weekColWidth
|
||||
height: root.cellSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: weekCell.weekNumber
|
||||
color: Theme.surface2
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize - 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 0
|
||||
|
||||
DayOfWeekRow {
|
||||
id: dow
|
||||
spacing: root.gridSpacing
|
||||
|
||||
delegate: Text {
|
||||
id: dowCell
|
||||
required property string shortName
|
||||
width: root.cellSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: dowCell.shortName
|
||||
color: Theme.subtext0
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize - 1
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
MonthGrid {
|
||||
month: root.viewMonth
|
||||
year: root.viewYear
|
||||
spacing: root.gridSpacing
|
||||
|
||||
// Leading/trailing days stay visible but dimmed — the grid always shows six weeks,
|
||||
// so hiding them would leave the week-number column pointing at blank rows.
|
||||
delegate: Item {
|
||||
id: dayCell
|
||||
required property var model
|
||||
width: root.cellSize
|
||||
height: root.cellSize
|
||||
readonly property bool inMonth: dayCell.model.month === root.viewMonth
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.radius
|
||||
color: Theme.accent
|
||||
visible: dayCell.model.today
|
||||
}
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: dayCell.model.day
|
||||
color: dayCell.model.today ? Theme.base : dayCell.inMonth ? Theme.text : Theme.surface2
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.bold: dayCell.model.today
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// ABOUTME: Clock/date bar widget backed by the quickshell SystemClock.
|
||||
// ABOUTME: Click toggles between the short (no year) and long (with year) date.
|
||||
// ABOUTME: Click opens the calendar popout; the tooltip carries the full date.
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
@@ -12,22 +13,43 @@ Text {
|
||||
duration: Theme.animDurationShort
|
||||
}
|
||||
}
|
||||
property bool showYear: false
|
||||
property var popouts: null
|
||||
property string popoutName: "calendar"
|
||||
|
||||
// Register with the shared popout state so this popout is addressable by keybind (niri IPC).
|
||||
Component.onCompleted: if (root.popouts)
|
||||
root.popouts.register(popoutName, popoutComponent, root, null)
|
||||
|
||||
// This widget's popout — handed to the shared animated host on click.
|
||||
Component {
|
||||
id: popoutComponent
|
||||
CalendarPopout {
|
||||
popouts: root.popouts
|
||||
}
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
|
||||
// Full bar height so the topmost pixels stay clickable and the popout anchors below the bar.
|
||||
// Set on height, not implicitHeight — Text derives that from its content and rejects the write.
|
||||
height: Theme.barHeight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.bold: true
|
||||
color: Theme.text
|
||||
text: Qt.formatDateTime(clock.date, root.showYear ? "dd.MM.yyyy HH:mm" : "dd.MM. HH:mm")
|
||||
text: Qt.formatDateTime(clock.date, "dd.MM. HH:mm")
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.showYear = !root.showYear
|
||||
onClicked: {
|
||||
if (!root.popouts)
|
||||
return;
|
||||
root.popouts.toggleByName(root.popoutName);
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
|
||||
@@ -7,6 +7,7 @@ Battery Battery.qml
|
||||
BatteryPopout BatteryPopout.qml
|
||||
BluetoothIndicator BluetoothIndicator.qml
|
||||
BluetoothPopout BluetoothPopout.qml
|
||||
CalendarPopout CalendarPopout.qml
|
||||
CameraIndicator CameraIndicator.qml
|
||||
CameraPopout CameraPopout.qml
|
||||
Clock Clock.qml
|
||||
|
||||
@@ -20,7 +20,9 @@ ShellRoot {
|
||||
},
|
||||
ModuleBox {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Clock {}
|
||||
Clock {
|
||||
popouts: bar.popouts
|
||||
}
|
||||
},
|
||||
Workspaces {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Reference in New Issue
Block a user