* feat(plugins): expose IPC handlers for runtime plugin discovery
Follow-up to #1659. That issue landed hot-reload for settings.json via
FileView.watchChanges + a 1ms Timer to skirt the JSON parse race. It does
not cover plugin discovery in runtime: adding a new plugin directory to
~/.config/DankMaterialShell/plugins/ while the shell is running is not
consistently picked up by the existing FolderListModel watcher in
PluginService.qml, and there is no IPC handle for forcing a rescan from
outside the shell.
Adds an IpcHandler on PluginService with five small functions:
- scan(): wraps existing scanPlugins(), returns count snapshot
- rescan(pluginId): wraps existing forceRescanPlugin(id), validates id
- reload(pluginId): wraps existing reloadPlugin(id), validates id
- list(): newline-joined id\tloaded\ttype\tname for every known plugin
- status(pluginId): loaded\ttype\terror for one plugin
Scope intentionally small: no file-watcher changes, no new daemons, no
schema additions. Target string "plugins" does not collide with any
existing target in DMSShellIPC.qml.
Validation:
- qs ipc --pid <PID> call plugins list returns one row per known plugin
- qs ipc --pid <PID> call plugins scan returns SCAN_TRIGGERED with count
- qs ipc --pid <PID> call plugins rescan <id> returns RESCAN_TRIGGERED
- Empty-arg paths return ERROR strings instead of throwing
- git merge-tree against origin/master is clean
* hardening(plugins): fix 7 review findings in scan-ipc IPC handlers
Follow-up to commit 43603f56 which ported PR #2601 (AvengeMedia scan-ipc)
to the fork. The original port was functionally correct but had seven
review issues that would block upstream adoption. This patch addresses
each one with a minimal, focused change.
* B1 IPC target collision: renamed `target: "plugins"` to
`target: "plugin-scan"`. The original name collided with the
existing IpcHandler in DMSShellIPC.qml:1180 which already registers
enable/disable/toggle/list/status under "plugins". The split keeps
both APIs discoverable without one shadowing the other.
* H1 Fire-and-forget scan: documented that scan() returns the
pre-debounce count and that callers must poll list/status (or wait
~200ms) to observe the post-debounce state. A proper requestId +
await mechanism was considered and rejected for scope reasons.
* H2 TOCTOU in rescan(): the handler now reads availablePlugins[id]
inside forceRescanPlugin via the id string only — no captured
object reference. A parallel resyncDebounce tick can otherwise
mutate the entry between the read and the use.
* M1 list() cap: added a 256-entry cap and a leading header line
(`# count=N returned=M`) so callers can detect truncation. A
hostile / buggy plugin mass-creating entries could otherwise
allocate 80 KB+ per IPC call.
* M2 status() prefix: "unknown\t\t" became
`ERROR: unknown pluginId '...'` to match the rest of the
handlers' prefix convention. Empty trailing field means no error.
* M3 id sanitization: every handler that takes pluginId now
validates against `/^[a-zA-Z0-9_\-:]{1,64}$` before use. This
rejects shell-injection payloads ("foo\tmalicious") and prototype
pollution attempts ("__proto__", "constructor"). The list() and
status() handlers also sanitize \t/\n in name and error fields
so callers can rely on the TSV structure.
Verification: brace count balanced (252/252). Manual read of all
five handlers confirms no logic regression. QML runtime tests are
not part of the DMS test suite, so end-to-end validation requires
rebuilding the shell — deferred to the user.
Not pushed. Stage-local-first rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(plugins): strip inline comments per review feedback
Purian23 in PR #2611 review: 'let's address the amount of line
comments in the code, there's not a need for all of them to exist.'
Removed 48 comment lines. The substantive justification (why the
regex, why fire-and-forget, why re-read inside forceRescanPlugin,
why the 256 cap, why the target rename) now lives in the PR body
under 'Review-driven fixes in this iteration' and 'What changed'
where the reviewer already reads it.
No code logic changed. Brace count 252/252. Diff is -48/+0 on
quickshell/Services/PluginService.qml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
* feat(quickshell): add local to-do planner / tasks to calendar overview card
* feat(quickshell): add auto-focus and task reordering support in calendar planner
* feat(quickshell): implement smooth drag-and-drop task reordering and inline editing
* fix(quickshell): resolve overlap and jitter in task drag-and-drop
* fix(quickshell): fix boundary swaps and prevent task list scrambling on reload
* fix(quickshell): resolve race, fix qml error, simplify dragging, and remove python dependency
* fix(quickshell): use Log service instead of console.warn in CalendarService
* style: format QML files w/qmlformat-qt6
---------
The "power off monitors on lock" path relies on wake handlers
(MouseArea/Keys) at Lock.qml's root Scope, which sit outside the
WlSessionLock surface and never receive input while locked. The feature
only appeared to work on compositors that auto-restore output power on
input; compositors that fully tear down the output (e.g. MangoWC
disable_monitor) leave the screen off until blind-unlock.
Add a seat-level IdleMonitor (wlr idle-notify, surface-independent) in
IdleService that restores monitor power on any keyboard/pointer
activity. Gated on isShellLocked + monitorsOff + the setting, so it is
inert unless that path actually powered the monitors off.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix gaps and overlaps when filtering clipboard history
* feat(Clipboard-Bar-Hist): Add search/filter to saved clipboard entries as well. Change title on toggle between recent/saved.
* keep Pinned/Saved icon highlighted when selected
* add back filter animations
* Implement snap state for list views based on animation settings
---------
Co-authored-by: purian23 <purian23@gmail.com>
* feat: add native powerprofile IPC target for power profiles management
* feat: show centered PowerProfileModal with 3 square buttons for powerprofile IPC toggle
* style: enhance PowerProfileModal size, icons, description, and keyboard hints
* feat: add Space key binding to select highlighted power profile
* fix(Mpris): exclude idle players from active-player selection
Add MprisController.isIdle() (player Stopped with empty title and
artist). _resolveActivePlayer excludes idle players from every
selection path, and re-resolves when the active player itself goes
idle. Existing triggers (availablePlayers change, isPlaying becoming
true) do not fire for a player merely stopping.
The fallback now gates on !isIdle(p) instead of p.canPlay. canPlay
describes whether Play() would succeed; !isIdle describes whether
there is anything to surface. canControl unchanged.
When no eligible player remains, activePlayer becomes null and
consumers that gate on it unload (the bar media widget via
WidgetHost; the dash via the companion fix).
* fix(DankDash): show no-player state when active player resolves to null
showNoPlayerNow gated on _noneAvailable (player count === 0) or
activePlayer being idle. Neither covers activePlayer being null
while players remain registered, which is now possible (an
always-on player sitting stopped with empty metadata).
Key showNoPlayerNow on !activePlayer; drop the unreachable
_trulyIdle.
* add(MprisController): add track artist change handling to active player resolution
---------
* feat: unify media controls dropdown interactions, hover behavior and cycle controls
- Implement hover-to-show and hover-to-hide for all media control dropdowns.
- Make clicking the Output Devices and Media Players buttons cycle through items when expanded.
- Always display the 'speaker' icon for Output Devices to maintain visual consistency.
- Bind dropdown player properties dynamically to fix list stale rendering states.
* fix(DankDash): use trackArtist property for artist label in MediaPlayerTab
* fix(DankDash): simplify active player label for consistency with output devices
* feat(DankDash): display volume levels for audio output devices in dropdown
* fix(DankDash): display Unknown Artist when artist is empty in player list
* feat(DankDash): add keyboard shortcuts for seeking, track cycling and playback control in Media popout
* feat(DankDash): change Up/Down arrow keys to adjust volume in Media popout
* feat(DankDash): auto-open volume dropdown overlay when using Up/Down shortcuts
* feat(DankDash): add Key M shortcut to toggle mute in Media popout
* fix(mpris): clamp minimum seek position to 0.1s to prevent browser player reset
* fix(mpris): cache stable length to prevent browser transient reset issues
* fix(mpris): persist activePlayerStableLength in MprisController singleton
* fix(mpris): resolve browser player album art with raw metadata and YouTube url fallbacks
* fix(mpris): resolve browser player album art with local caching and 16:9 youtube fallbacks
* style(mpris): trim trailing whitespace in TrackArtService
* fix(mpris): address code review feedback on remote caching, stale artwork, and hover state
* fix: secure curl commands and prevent premature dropdown overlays closing on button re-hover
* feat(clipboard): Add editing capability to clipboard entries
* Add split save menu for clipboard editor
* Add clipboard editor shortcuts and hints
* Show full clipboard text in editor
* feat(Clipboard): Revive ClipboardEditor PR
- Original PR #1916 by @nabaco
* fix(clipboard): restore Save button targets in editor
---------
Co-authored-by: Nachum Barcohen <38861757+nabaco@users.noreply.github.com>
- Introduce multi-account greeter login with per-user theme previews
- Add `dms greeter sync --profile` for secondary users with or without sudo
- Add Manage greeter group membership from Settings UI → Users Tab
- Core user is logged in tty1 while user two is in tty3, you can now seamlessly switch bewteen them
New IPC options:
- `dms ipc call sessions list`
- `dms switch-user [target]`
- New Powermenu switch users option
Fixes#2354
Root cause (tray freeze): In clickThrough mode, the PanelWindow mask uses
sectionRect() with mapToItem() to compute input regions. After DPMS resume,
the PanelWindow is recreated with width=0, and mapToItem() returns wrong
positions. The right section's implicitWidth doesn't change after creation
(fixed-size tray icons), so the mask binding is never re-evaluated when the
compositor sets the actual screen width. Adding barWindow.width as a binding
dependency ensures the mask recalculates on resize.
Root cause (wallpaper loss): Wallpaper PanelWindows are recreated by Variants
during screen reconnection before the compositor finishes output initialization.
The wallpaper Image renders at 0x0 dimensions, resulting in a black screen.
Changes:
- DankBarWindow: add barWindow.width dependency to clickThrough mask bindings
- DMSShell: add surface recovery mechanism (screen reconnect + session resume)
with progressive 2-pass timer (800ms + 2800ms) that recreates bar, Frame,
wallpaper, and dock surfaces after the compositor is ready
- WlrOutputService: re-request output state on session resume
Pulses the WiFi and Bluetooth status icons while a connection is in
progress (lock screen, DankBar control center button, control center
compound pill). The pulse is implemented as a reusable Widgets/DankBlink
component, and the wifi-connecting condition is centralized as
NetworkService.isWifiConnecting.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New Spotlight toggle to show/hide chips, off by default
- Updated blur effects on all launcher inputs and footers
- Fixed previous queries resurfacing
- Upated Spotlight keyboard navigation
- Added functionality to show and shortcut to keybinds from the Launcher tab
- Update to add DMS Action keys in Keyboard Shortcuts
- Defaulted in niri/hyprland includes file as `Alt+Space`
- New (IPC): `dms ipc call spotlight-bar toggle`
- Slight UI update to follow user radius
- Replaced fullscreen hide/reveal toggles with Show Over Fullscreen layer toggles
- Added Launcher opt to Show Over Fullscreen setting
- Kept fullscreen stacking compositor-owned via top/overlay layer choices
- Fixed Hyrland Special Workspaces
- Updated DMS Advanced Configuration docs
- Note: We do not convert your existing conf configs to lua. This update only reflects DMS defaults state
- Updated README.md to reflect changes
- Updated Keyboard shortcut support
Senders that play their own audio for a notification can set the
standard org.freedesktop.Notifications "suppress-sound" boolean hint
to ask the server not to double up. NotificationService skipped its
sound only as a side effect of the dedup early-return (when an
identically-keyed popup was still visible), so transient notifications
double-sounded while lingering ones didn't — nondeterministic. Read
notif.hints["suppress-sound"] and gate the AudioService call on it.