* feat(bar): separate workspace appearance for unfocused displays
Add a toggle in the Workspace Appearance card to style workspace
indicators independently on displays that aren't currently focused.
The card is split into Focused Display and Unfocused Display(s) tabs.
When the new master toggle is off (default), unfocused displays inherit
the focused settings, preserving existing behavior. When on, unfocused
displays use their own colors (focused/occupied/unfocused/urgent) and
focused border (enable, color, thickness).
WorkspaceSwitcher resolves the focused monitor per compositor and routes
color/border resolution through isFocusedMonitor/useUnfocusedAppearance.
* refactor(bar): address review feedback for unfocused workspace appearance
- Consolidate focused-monitor lookup into BarWidgetService.getFocusedScreenName(),
adding Mango support, and drop the duplicate compositor switches in
WorkspaceSwitcher (effectiveScreenName + isFocusedMonitor now use the helper).
- Extract the shared color and border options into reusable
WorkspaceAppearanceColorOptions and WorkspaceAppearanceBorderFields components
so the focused and unfocused tabs no longer duplicate the layout.
- Gate the unfocused-display options on BarWidgetService.focusedScreenDetectionSupported
and show an explanatory note on compositors without focus detection (e.g. labwc),
instead of silently no-opping.
- Add composite plugin type with multi-surface components field
- Add startupCheck manifest field and dependency gating docs
- Add IPC runtime plugin discovery commands (plugin-scan target)
- Add getPluginPath() to data persistence reference
- Document bar reveal visibility optimization for widget plugins
- Sync plugin-schema.json with source, add dependencies field
- Bump skill version to 1.1
* feat: parallax-scroll wallpaper
Add a `Scrolling` wallpaper fill mode that translates the wallpaper crop
with the active workspace — like Android home-screen parallax, but along
niri's vertical workspace axis.
The image is scaled to cover the screen on its non-scroll axis, and the
active workspace index drives a fractional offset into the cropped
overflow along the scroll axis. Scroll position is spring-animated
CPU-side and handed to a minimal single-texture shader as a UV offset.
Per-monitor scroll position is published into SessionData so the lock
screen renders the same crop as the active workspace, keeping visual
continuity across lock/unlock.
Two implementation details worth calling out for review:
- QSG_USE_SIMPLE_ANIMATION_DRIVER=1 is exported to the spawned quickshell
process. The default animation driver advances in fixed ~16ms steps,
capping the scroll at 60Hz and desyncing it from compositor motion on
high-refresh displays; the simple driver advances by real elapsed time,
restoring native-refresh pacing. Removing it visibly regresses to 60Hz.
- The wallpaper survives wl_output rebind cycles (e.g. OLED image-cleaning
on DPMS soft-off), which otherwise leave a stuck or void background.
Recovery re-anchors the scroll target on output-lifecycle signals,
rebuilds the ShaderEffect against the current render context, and
re-attaches the wallpaper-layer surface on unlock for parallax-active
monitors — guarded against lock state so the shader gets reliable frame
hints.
* simplify bindings and gate lock screen shader in a loader
---------
Co-authored-by: bbedward <bbedward@gmail.com>
- Store a desktop session identity instead of relying on raw Exec commands
- Resolve the current session desktop file when auto-login launches
- Preserve legacy memory compatibility while ignoring stale lastSessionExec
- Add regression coverage for stale /nix/store session paths
- Autologin users should rerun the process
* feat(battery): add options to toggle percentage and remaining time
* fix(battery): add SettingsSpec entries and combine verticalDisplayText
* fix(battery): format vertical battery remaining time as HH\nMM and center alignment
* fix(battery): remove percent sign from vertical layout when time is enabled
* feat(wallpaper): support configurable background color
* chore(translations): update settings search index
* revert(wallpaper): keep Pad naming instead of Center
When pam_faillock locks the account (DMS authenticates through the system PAM
stack: /etc/pam.d/login -> system-auth), the lock screen kept showing the
generic "Incorrect password - try again" even though the real cause is a
lockout, so a correct password looks rejected and only a reboot (which clears
the tmpfs /run/faillock tally) appears to help. See #2647.
The previous onMessageChanged only matched the *English* faillock strings
("The account is locked ...") and then wiped that text again on the trailing
pam_unix "Password:" prompt. On a non-English system (e.g. German) the strings
never matched, so the lockout was never surfaced at all.
Detect the notice by position rather than by text: pam emits its informational
messages within an attempt before the password prompt. Collect every non-prompt
info message and, once the prompt arrives, surface the collected lines (minus
the prompt itself) as lockMessage. If the stack short-circuits without ever
prompting (e.g. pam_faillock preauth configured as requisite), the notice is
surfaced on completion instead. This is locale-independent. A per-attempt flag
keeps the message stable across repeated locked attempts and retires it when an
attempt completes without a lockout (faillock reset / unlock_time elapsed).
Fixes#2647
When a bar with background transparency + blur uses auto-hide, the
ext-background-effect-v1 blur region was only slid off-surface via the
reveal Translate, but the region object stayed non-empty.
Hyprland gates layer-surface blur on `!m_blurRegion.empty()`, so the
non-empty region keeps blur enabled; the renderer then intersects the
off-surface region with the surface box, the clip degenerates to empty,
and an empty clip is treated as "unclipped" — so the whole bar surface
box gets blurred, leaving a blurred strip where the hidden bar would be.
(niri clips correctly, so it never showed there.)
Gate the published blur region on `barRevealed`: tear it down (null)
whenever the bar is not currently shown, so the region is genuinely
empty and the compositor disables the effect. Fixes#2656.
* fix(ui): use primaryText instead of primary for text and icons displayed on primaryContainer background in the launcher
* fix(ui): use onPrimaryContainer instead of primaryText on primaryContainer background
* launcher pills: switch to buttonBg buttonText
---------
Co-authored-by: bbedward <bbedward@gmail.com>
* feat: add battery settings tab and update battery widget interactions
* fix: import Quickshell.Io in BatteryTab.qml to fix Process type compilation error
* feat: move battery tab under media player and add notify when charge limit reached option
* chore: change default notification settings to false
* feat: move battery tab under Power & Security section in settings
* feat: add notification type button selection for battery alerts
DankDropdown popups opened and closed at a fixed speed regardless of the
configured Animation Duration. The Popup inherited Qt Material's default
enter/exit transitions, whose durations are hardcoded and never reference
Theme.shortDuration.
Override enter/exit with theme-driven transitions that keep the Material
grow/fade look (scale + opacity) but read their duration from
Theme.shortDuration, so every DankDropdown instance follows the
animation-speed setting.
Fixes#2659
A DPMS off/on cycle removes an output from Quickshell.screens and re-adds
it, which DMSShell's onScreensChanged cannot distinguish from a hotplug. It
fired triggerSurfaceRecovery() on every such event; on hardware where
recreating layer-shell surfaces re-wakes the just-powered-down output, this
drives an endless recovery storm that visibly power-cycles the monitor.
Route the screen-reconnect path through a 450 ms debounce (collapsing the
output-remove + re-add pair into a single pass) followed by a 4 s cooldown,
so repeated flaps trigger at most one recovery per window. Recovery still
runs once per resume, so the partial-DPMS-resume recovery added for #2579 is
preserved. The session-resume path runs its own recovery directly and now
clears any queued screen-reconnect recovery to avoid a redundant follow-up.
Fixes#2642
Decouple weather data fetching from reverse geocoding so that
weather loads as soon as coordinates are available, even when
Nominatim is unreachable (e.g. mainland China).
- Fetch Open-Meteo weather immediately once lat/lon are known.
- Resolve city name in parallel via Nominatim -> Photon -> BigDataCloud.
- If all reverse geocoding fails, keep displaying weather with a
placeholder city name.
- Skip reverse geocoding entirely when the user has configured a
city name.
- Fall back to ip-api.com when GeoClue2 is unavailable or returns
zero coordinates.
- Add request-generation tracking to discard stale geocoding
responses after location changes.
- Hold explicit WeatherService refs in bar widget and dashboard tab.
Co-authored-by: lingdiansr <2077258365@qq.com>
When a custom lock command is configured, Lock.lock() runs the command and
returns early without engaging WlSessionLock, so IdleService.isShellLocked
never transitions true->false. That transition is the only trigger that
dismisses a completed FadeToLockWindow, so the fully-faded black overlay stays
on screen and the desktop is unusable after re-login (regression from b8f4c35,
which added the _completed guard and tied dismissal solely to isShellLocked).
Add a dedicated dismissFadeToLock signal that the custom-lock branch emits
after launching the external locker, mirroring the existing fade signal wiring,
so the overlay is handed off and torn down. The built-in WlSessionLock path is
unchanged and still dismisses on unlock.
Fixes#2595
Commit e3dbaed started skipping all system sounds (notifications, volume,
power, login) whenever an MPRIS player is playing, with no way to opt out.
Users who rely on notification sounds while listening to music lost them
entirely.
Add a 'Mute During Playback' toggle (SettingsData.muteSoundsWhenMediaPlaying,
default true) so the current behaviour is preserved, but users can disable
it to restore audible sounds while media is playing. The media check is
wrapped in shouldMuteForMedia(), which gates on the new setting before
querying the active player.
Closes#2616
* feat(tailscale): add connect/disconnect/exit-node/LAN-access backend
The Tailscale backend previously exposed only read-only status
(tailscale.getStatus, tailscale.refresh). This adds write actions through the
existing tailscale.com/client/local integration:
- tailscale.connect / tailscale.disconnect (EditPrefs WantRunning)
- tailscale.setExitNode (EditPrefs ExitNodeID; empty id clears it and any
legacy ExitNodeIP, mirroring `tailscale set --exit-node`)
- tailscale.setAllowLanAccess (EditPrefs ExitNodeAllowLANAccess)
The manager's client interface gains GetPrefs/EditPrefs; fetchState merges
ExitNodeAllowLANAccess from prefs, and Peer exposes ExitNodeOption so the UI
can list exit-node-capable peers.
* feat(tailscale): expose the new actions in TailscaleService
Adds connectTailscale/disconnectTailscale, setExitNode/clearExitNode and
setAllowLanAccess wrappers, plus derived exitNodeOptions/currentExitNode and the
exitNodeAllowLanAccess state. Write-action errors surface via ToastService.
* feat(tailscale): add connection, exit-node and LAN-access controls to the widget
The control-center widget toggle was a no-op. It now connects/disconnects, and
the detail panel gains a connection status row with a connect/disconnect button,
an exit-node picker and a LAN-access toggle.
* fix(keybinds): record numpad keys as KP_* keysyms
The shortcut recorder passed only the Qt key code to xkbKeyFromQtKey and
dropped Qt.KeypadModifier. Since Qt reuses the same Qt::Key_* values for the
numpad and the main row / nav cluster, numpad presses collapsed onto their
twins: numpad-7 became "7" (NumLock on) or "Home" (NumLock off) instead of
"KP_7"/"KP_Home", numpad-+ became "Equal", numpad-* became "8", numpad Enter
became "Return". numpad-5 with NumLock off (Qt.Key_Clear) was missing from the
map entirely, so the capture was silently dropped.
niri and the other providers bind against the xkb KP_* keysym names, so these
tokens never matched the physical key.
Pass the keypad flag through to xkbKeyFromQtKey and map keypad presses to the
KP_* keysyms: KP_0..KP_9 for the NumLock-on digit codes, the navigation names
(KP_Home, KP_End, KP_Up, ...) for the NumLock-off codes, plus the operators
and KP_Enter. Main-row keys are unaffected because they never carry the keypad
modifier.
* fix(keybinds): ignore lock keys while capturing a shortcut
NumLock/CapsLock/ScrollLock are toggles, not useful bind targets. Pressing
NumLock to switch the numpad between its digit and navigation keysyms
(KP_7 vs KP_Home) was captured as the bind itself (e.g. "Super+Num_Lock").
Skip them in the recorder like the other pure modifier keys already are.
DMS reads the niri config with kdl-go, which rejects '_' as the first
character of a bare identifier ("unexpected character _") even though niri's
own parser and the KDL spec accept it. The common trigger is the
`_JAVA_AWT_WM_NONREPARENTING "1"` environment node (the standard Java /
tiling-WM fix). When the parse aborts, `dms keybinds show` returns nothing and
the Keyboard Shortcuts UI shows no binds at all.
Extend the existing preprocessor approach (the brace fix from #2230) with
quoteLeadingUnderscoreIdents, which double-quotes bare identifiers that begin
with '_' before the text reaches kdl-go. The scan is string/comment aware and
only touches a leading '_' at a token boundary, so mid-identifier underscores
(XDG_CURRENT_DESKTOP) and underscores inside strings/comments are left alone.
Token boundaries include the ends of block comments and KDL slashdash (/-), so
a node abutting a comment with no whitespace is handled too. This is safe
because the niri parser only dispatches on fixed node/section names that never
start with '_', so re-quoting such a name cannot change what DMS reads.
Refs #2230
- Add a full popout Notepad experience w/new layout settings
- New ability to choose the left or right side of the screen
- Add more safegaurds to preserve user data throughout
- New banner to show file reload/conflict handling
- New extensionless file support
- Polish settings with gap controls, compact buttons, and shortcuts help
* feat(media-control): support scroll and right-click on audio output devices in media popout
* feat(media-control): make device list volume scrolling optional
- Keep wallpaper surfaces persistent and remove `updatesEnabled` throttling that could leave wallpapers grey or frozen after DPMS, suspend, fullscreen, or output changes
Fixes#2612Fixes#2299Fixes#2272Fixes#2028
* feat(notifications): add user-configurable font size for summary and body in notification popups
* feat: add Unset for falling back to previous default values
* fix: prek hook errors
---------
Co-authored-by: Klesh Wong <kleshwong@gmail.com>
Adds a thin bar pinned to the bottom of the notification card that drains
full->empty over the auto-dismiss timer, as a visual countdown to
dismissal. Opt-in via notificationShowTimeoutBar (default off), with a
toggle in Settings > Notifications. Shown for any timed notification
(timer.interval > 0, including timed criticals); inset by the corner
radius, and frozen while hovered or during the exit animation. Plain
Rectangle - no offscreen textures or shader passes. A Connections on the
timer resets the bar on every (re)start, including the in-place restart
on a deduped notification.
Co-authored-by: bogdan-velicu <hydrotech074@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* 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
---------
* fix: ignore keyboard shortcuts of disabled powermenu actions
* fix typo when checking for lock shortcut
* ignore shortcuts for hidden powermenu actions in grid navigation
* ignore keyboard shortcuts of disabled actions in lock power menu
* ignore keyboard shortcuts of disabled actions in lock power menu (list navigation)
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>
- Read and convert external compositor rules into editable DMS rules
- Preserve niri multi-match rules and add match editor
- niri background-effect (blur/xray/noise/saturation) support
* feat(control-center): add widget config overlay with showMountPath toggle for DiskUsage
Introduces WidgetConfigOverlay and DiskUsageWidgetConfigMenu components, allowing
users to toggle mount path visibility per DiskUsage widget in edit mode
* refactor(control-center): use Theme.iconSizeLarge and Theme.fontSizeLarge for small tiles
Standardize SmallDiskUsageButton and SmallBatteryButton sizing with Theme.iconSizeLarge
and Theme.fontSizeLarge, and unify font weight to Font.Bold on both tile widgets.
* fix(control-center): adjust SmallDiskUsageButton font size based on showMountPath
* refactor(control-center): simplify DiskUsage config menu i18n strings
Remove the redundant "Disk Usage Widget" title and the toggle description
to reduce translatable strings
The networkd backend treated any link reporting Type=ether as a wired uplink.
Podman bridges and veth pairs report Type=ether, so they were classified as
ethernet: isWired() short-circuited on Type and never consulted looksVirtual(),
which also lacked a podman prefix.
The link map was also never pruned. Links discovered at enumeration or via
signals were kept forever, so torn-down container interfaces lingered as
routable and could win the wired-uplink slot over the real NIC -- leaving the
indicator showing WiFi while a wired connection was active and default-routed.
- isWired()/isWireless() exclude virtual interfaces before consulting Type, and
looksVirtual() now recognises podman.
- enumerateLinks() reconciles the cached map against ListLinks via syncLinks(),
pruning links that no longer appear so dead interfaces don't accumulate.
Currently, when switching dark/light theme in DMS, pywalfox's mode does not get updated. This commit adds `pywalfox light/dark` to the matugen post_hook to fix that.
* 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
* feat(WidgetsTabSection.qml): added dynamic width and static padding for DiskUsage
* feat(DiskUsage.qml):added functionality for dynamic width and static padding, also changed spacing to work like in cpu and ram monitor. Now they look complete and same
* fix(DiskUsage): restore display modes & formatting
---------
Co-authored-by: purian23 <purian23@gmail.com>
Add applyStoredIconTheme() calls alongside existing applyStoredTheme()
calls in loadSettings(), onLoaded, and onLoadFailed, ensuring the stored
icon theme is synced to GTK/Qt/Cosmic configs on every startup and reload.
The applyStoredIconTheme() function and its _hooks entry already existed
in the codebase but were never invoked during initial settings loading.
Co-authored-by: lingdiansr <2077258365@qq.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* 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
The toast Rectangle uses `layer.enabled: true`, which renders to a
texture before compositing. With fractional implicit/content sizes
(derived from text and icon metrics), the cached texture was being
sampled with sub-pixel interpolation and the toast looked blurry
under fractional-scale-aware compositors (e.g., niri).
Wrap toastWidth/toastHeight and implicitWidth/implicitHeight with
Theme.px(value, dpr), matching the alignment NotificationPopup.qml
already applies to its surface.
* 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>
* feat(Spotlight): fix submenu keyboard navigation
* feat(Launcher/Spotlight): disable persisting last mode when changed by triggers
* feat(Launcher/Spotlight): add option to disable last mode being persisted
* fix(Launcher/Spotlight): fix context menu keys navigation
* fix(NiriOverviewOverlay): fix context menu keys navigation and position
Sort networks by signal bucket of 25 with SSID as tiebreaker so minor
RSSI fluctuations between scans no longer reshuffle the list while the
user is selecting a network.
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
Some monitors, especially cheaper models, are slow to power on after sleep and may not be present in Quickshell.screens
when onScreensChanged is triggered. This change adds a 3-second delay before updating currentOSDsByScreen to ensure all
screens are detected, mitigating the issue of OSDs not appearing on certain monitors after resume.
Co-authored-by: Klesh Wong <kleshwong@gmail.com>
* networkd: classify links by Type instead of name prefix
The systemd-networkd backend decided wifi-vs-ethernet by checking
whether the interface name started with "wlan" or "wlp". Anything
else (that was not on a small virtual-prefix denylist) was treated
as wired ethernet. That misclassified two common cases:
* Nebula tunnels (kernel name like "nebula.homelab", Type=none,
Kind=tun) showed up as a wired ethernet device — DMS rendered an
"Ethernet connected" indicator whenever the overlay was up, even
with no physical NIC plugged in.
* Renamed wifi interfaces (e.g. systemd link files that rename
wlan0 to a friendlier name like "wifi") were also miscategorised
as ethernet, because they no longer matched wlan*/wlp*.
networkd already publishes the real link kind in the JSON returned
by the per-link Describe method ("ether", "wlan", "loopback",
"none"). Fetch it during enumerateLinks, cache it on linkInfo, and
classify against that. The old prefix logic is kept as a fallback
for the case where Describe ever fails to populate Type.
The package-level looksVirtual() helper replaces the unexported
isVirtualInterface method so the new classification helpers and
their tests can use it without needing a live backend.
Tests cover both the Type-based and fallback paths, including the
Nebula-shaped Type=none/tun case that motivated this change.
* networkd: cache linkType across signal ticks and unit-test Describe fallback
enumerateLinks runs on every PropertiesChanged signal under
/org/freedesktop/network1, which fires on carrier flap, DHCP renew, and
each address change. The previous version rebuilt every linkInfo from
scratch on each tick, including a synchronous Describe D-Bus round-trip
per link, despite the link Type being fixed at netlink creation. Preserve
existing entries when the D-Bus path matches, refreshing only ifindex,
and only call fetchLinkType on a genuinely new entry. A link torn down
and re-created at a different path still triggers a refetch.
Extract parseDescribeType from fetchLinkType so the JSON failure path —
malformed payload, missing Type field, wrong type for Type — can be
exercised without a live D-Bus connection. The classifier's fallback to
name-prefix heuristics already had coverage; this locks in that the seam
between Describe and the classifier surfaces an empty string on every
failure mode rather than misclassifying a link.
- 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.
Fixed a visual bug where the install button would overflow its container after a plugin was installed. This was caused by the width not re-evaluating correctly and StyledText's default elide/wrap properties interfering with implicit width calculations.
* feat(settings): Added Default Apps page to settings
Added a new page to settings. This page relies on xdg-mime and gio, so their existence is checked to show the page.
This logic and the mime type stuff was added to DesktopService.qml.
Slightly reordered the settings sidebar to include an Applications category
* fix(settings): read xdg-terminals.list directly
read the file directly instead of using xdg-terminal-exec which might not be installed
* feat: add Display Profiles builtin control center widget
Adds a new built-in widget that lets users switch between their configured
display profiles directly from the Control Center, without opening Settings.
- Widget shows the active profile name and cycles profiles on pill click
- Detail panel shows all profiles as a button group for direct selection
- Integrates with existing DisplayConfigState/SettingsData singletons
- Follows the same loader/instance pattern as VPN, CUPS, and Tailscale widgets
* fix: refine display profiles widget behavior
NetworkManager rejects AddConnection for 802-1x without a non-empty
identity, so the new API v7 agent-prompt flow never reaches the
SecretAgent for fresh enterprise connections. Fall back to the existing
modal-upfront path (already wired to ask for username + password via
requiresEnterprise) when modelData.enterprise, mirroring the legacy
DMSService.apiVersion < 7 branch.
Fixes#2358
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add cycleProfile IPC command for display outputs
Adds `dms ipc outputs cycleProfile` which cycles through configured display
profiles in order, wrapping around from the last back to the first.
Useful for binding to a compositor keybind (e.g. Super+P in niri/Hyprland)
without needing an external wrapper script.
* feat: add Mod+P default keybind for cycling display profiles
Binds Super+P to `dms ipc outputs cycleProfile` in both the niri and
Hyprland default keybind configs. Mod+Shift+P is already reserved for
powering off monitors on both compositors, so Mod+P is a natural fit.
* fix: preserve pre-existing connection profiles on cancelled WiFi activation
When a pre-existing WiFi connection activation is cancelled, do not
remove the connection profile — only remove profiles for newly created
connections.
* feat: retrieve WiFi/VPN secrets from D-Bus secret service with unlock
Wireless, 802.1x, VPN, and WireGuard secrets are now looked up from
org.freedesktop.Secret before prompting the user. If the keyring is
locked, the unlock prompt is triggered via Prompt.Prompt() and the
lookup retried after the vault is unlocked.
* fix(ClipboardHistory): toggle button now toggles between saved and recents tabs
- Change tooltip text to reflect current tab state ('Recent' when on saved tab, 'Saved' when on recents tab)
- Previously always switched to saved tab; now toggles between 'saved' and 'recents'
* refactor(ClipboardHistory): remove redundant History button, keep single toggle button
The Saved button now toggles between saved/recents tabs, so the separate History
button is redundant and has been removed for a cleaner UI.
getPreferredBar("clockButtonRef") returns null when the clock widget
is removed from the bar. Fall back to getPreferredBar() (any bar) so
triggerDashTab can still open the popout with default positioning.
* fix(niri): properly close KDL output block when disabled
* feat(display): parse off directive and sync disabled state from compositor configs
* feat(display): visual treatment and canvas filtering for disabled outputs
* feat(display): eager auto-profile generation with debounced auto-select
* refactor(display): pass target profile ID to confirmChanges and remove dead code
* fix(display): make profile dropdown reactive by binding to property directly
* i18n(display): add Disabled translation term for output state
The dynamic-mode currentThemeData object at Theme.qml:447-468 omitted
tertiary entirely, even though matugen generates it and writes it to
the matugen colors JSON. As a result, Theme.tertiary returned undefined
in dynamic mode, and any QML binding through .r/.g/.b/.a defaulted to
white — visible as desaturated near-white highlights in the bar's
ChromeShader (which mixes Theme.tertiary at highlight peaks).
Two-line fix:
- Add tertiary to the dynamic-mode currentThemeData via getMatugenColor
- Add property color tertiary on the Theme singleton, falling back to
secondary if tertiary is absent (covers the stock-theme path where
tertiary is added separately via buildMatugenColorsFromTheme:1811)
Fallback hex #efb8c8 is the M3 baseline tertiary (light pink) for dark
mode, in case matugen output is missing.
Theme.tertiary was already referenced at LockScreenContent.qml:737 so
the property name is the right one.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hasFullscreenToplevelOnScreen's fast-path used `NiriService.currentOutput
=== screenName` as a proxy for "the active toplevel is on screenName".
Those are two independent state channels (niri's workspace-activated
events drive currentOutput; wlr-foreign-toplevel-management drives
ToplevelManager.activeToplevel) and they don't update atomically.
When focus crosses from a fullscreen toplevel on monitor A to a
non-fullscreen toplevel on monitor B, currentOutput flips to B before
activeToplevel updates away from A's still-fullscreen-and-activated
window. For one tick, B's bar evaluates `active.fullscreen &&
active.activated && currentOutput === B` → true, hides itself, then
flips back when activeToplevel finally updates. Visible as a one-frame
bar flicker on focus changes between monitors when one has a fullscreen
window.
Replace the proxy with _toplevelOnScreen(active, screenName), the same
helper the X11 fallback path uses 25 lines below. The check now
inspects the toplevel's actual outputs instead of trusting a separate
state signal, so the race can't fire.
The per-workspace loop below was already correct; it would catch any
real fullscreen+activated toplevel on the bar's workspace regardless
of focused output. The fast-path was redundant when the assertion
held and wrong when it didn't.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(tailscale): add Tailscale control center widget
Full-stack Tailscale integration for DMS control center:
Backend (Go):
- Event-driven manager via WatchIPNBus (no polling)
- Reconnects with exponential backoff when tailscaled unavailable
- Typed conversion from ipnstate.Status to QML-friendly IPC types
- Testable via tailscaleClient interface with mock watcher
- Manager cleanup in cleanupManagers()
- 19 unit tests
Frontend (QML):
- TailscaleService with WebSocket subscription
- TailscaleWidget with peer list, filter chips, search
- Copy-to-clipboard for IPs and DNS names
- Daemon lifecycle handling (offline/stopped states)
Dependencies:
- Add tailscale.com v1.96.1 (official local API client)
- Bump Go to 1.26.1 (required by tailscale.com)
* cleanups
---------
Co-authored-by: bbedward <bbedward@gmail.com>
- Introduces Standalone & Connected Modes
- Updated Animations & Motion effects for both modes
- Numerous QOL tweaks and updates throughout the system
- Highly inspired to the OG Caelestia Shell / @Soramanew
When quickshell crashes or is force-killed, the child systemd-inhibit
process (used as fallback when native IdleInhibitor is unavailable) gets
reparented to PID 1 and continues to block idle indefinitely. This causes
hypridle to ignore all idle timeout rules even though the idle inhibitor
widget appears inactive after restart.
Add a cleanup step during initialization that kills any orphaned
systemd-inhibit processes from previous sessions.
Move system update flow to GO, with a CLI (convenient AIO tool) and
server integration. All lifecycle, scheduling, execution occurs on
backend side.
Run some backends via pkexec, some via terminal like paru/yay.
Incorporate flatpak as an option to update.
Add terminal override setting in GUI, in addition to $TERMINAL env
variable.
fixes#2307fixes#822fixes#1102fixes#1812fixes#1087fixes#1743
The nixosModule/homeModule path previously called `buildDmsPkgs pkgs` but
internally referenced `self.packages.${system}.default`, which was
instantiated via `nixpkgs.legacyPackages`, an unoverlayed pkgs. That
meant downstream flakes couldn't reach through their own overlays to
the dms-shell build (e.g. to swap `kdePackages.sonnet` or trim perl
out of the aspell closure).
Extract the derivation as `mkDmsShell = pkgs: ...` at the top-level
`let`, and call it from both `packages.${system}.dms-shell` (for
direct consumers of the flake) and `buildDmsPkgs pkgs` (for module
consumers, which now pass in the system's overlayed pkgs).
Also re-checks overrideAttrs / .override still work: `mkDmsShell pkgs`
is the same `pkgs.lib.makeOverridable` wrapper as before, just
parameterized on the caller's pkgs instance.
Co-authored-by: Lucas <43530291+LuckShiba@users.noreply.github.com>
* dock: add trash bin button
- icon reflects content- filled/empty
- multiple file manager support with nautilus as default, builtin as
fallback
- settingsspec at dock tab
- context menu
* fix: remove support for builtin filebrowser
needs specific adaptors at FB adhering the trash freedesktop spec
* fix: suppress auto-hide dock with trash context menu open
* feat: allow for custom file manager command
* feat: switch runner to proc.runcommand with toasts on command failures
* Close notification center after clicking action buttons
When clicking action buttons (e.g., "View", "Activate") in the
notification center, the action fires but the popout stays open. Since
the center is a layer-shell surface, it blocks focus changes on Wayland
compositors like niri, making the action appear to do nothing.
The keyboard navigation path already closes the center after invoking
actions; this brings the mouse click path in line.
Also fix closeNotificationCenter() in PopoutService to set
notificationHistoryVisible = false (matching PopoutManager._closePopout)
instead of calling close() directly, which left the visibility property
stale and caused the bell toggle to require two presses to reopen.
Fixes#2178
* Sync notificationHistoryVisible with shouldBeVisible
NotificationCenterPopout has its own notificationHistoryVisible property
that drives open/close, but the PopoutService public API (open, close,
toggle) calls DankPopout methods directly, bypassing that property. This
leaves notificationHistoryVisible stale, causing the bell toggle to
require two presses to reopen after a programmatic close.
Sync the property from onShouldBeVisibleChanged so any caller going
through open()/close() gets the state corrected automatically.
* services: add LabwcService with quit
labwc has a minimal IPC surface (no socket, no queries) but it does
expose `labwc --exit` as a clean shutdown path. Wrap that in a small
Singleton service following the same shape as DwlService/NiriService
so the compositor-specific dispatch in callers can stay uniform.
* session: dispatch labwc logout via LabwcService
CompositorService.isLabwc was detected but never dispatched in
_logout(); labwc sessions therefore fell through to the Hyprland
exit call, which silently no-ops under labwc. Users had to set
customPowerActionLogout to 'labwc --exit' as a workaround.
Add a labwc branch alongside the existing niri/dwl/sway branches
so the power menu logout works out of the box.
Adds a 'Monochrome Icons' toggle to the system tray widget context menu.
When enabled, all system tray icons are desaturated using MultiEffect,
giving a cleaner monochrome bar aesthetic that matches minimal themes.
The setting is per-user (settings.json), defaults to false to preserve
existing behavior.
The unconditional startup scan introduced duplicate tray icons on normal boot because apps were still registering their own SNI items when the scan ran.
Use CLOCK_BOOTTIME − CLOCK_MONOTONIC to detect whether the system has ever been suspended. The startup scan now only runs when the difference exceeds 5 s, meaning at least one suspend/resume cycle has occurred.
On a fresh boot the difference is ≈ 0 and the scan is skipped entirely.
* Fix ddc brightness not applying because process exits before debounce timer runs
* Added sync.WaitGroup to DDCBackend and use it instead of loop in wait logic, added timeout in case i2c hangs.
* go fmt
---------
Co-authored-by: bbedward <bbedward@gmail.com>
outputs
fixes#2199 , possibly regresses #1235 - but I think the original bug
was lastAppliedTemp being incorrectly set, this allows compositors to
cache last applied gamma
gamma: add a bunch of defensive mechanisms for output changes
related to #2197
gamma: ensure gamma is re-evaluate on resume
fixes#1036
Screen sharing was not detected by PrivacyService on Niri because:
1. Niri creates the screencast as a Stream/Output/Video node, but
screensharingActive only checked PwNodeType.VideoSource nodes.
2. looksLikeScreencast() only inspected application.name and
node.name, missing Niri's node which has an empty application.name
but identifies itself via media.name (niri-screen-cast-src).
Add Stream/Output/Video to the checked media classes and include
media.name in the screencast heuristic. Also add a forward-compatible
check for NiriService.hasActiveCast for when Niri gains cast tracking
in its IPC.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(system-update): open popout on first click
The SystemUpdate widget required two clicks to open its popout.
On the first click, the LazyLoader was activated but popoutTarget
(bound to the loader's item) was still null in the MouseArea handler,
so setTriggerPosition was never called. The popout's open() then
returned early because screen was unset.
Restructure the onClicked handler to call setTriggerPosition directly
on the loaded item (matching the pattern used by Clock, Clipboard, and
other bar widgets) and use PopoutManager.requestPopout() instead of
toggle() for consistent popout management.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(system-update): include AUR packages in update list
When paru or yay is the package manager, the update list only showed
official repo packages (via checkupdates or -Qu) while the upgrade
command (paru/yay -Syu) also processes AUR packages. This mismatch
meant AUR updates appeared as a surprise during the upgrade.
Combine the repo update listing with the AUR helper's -Qua flag so
both official and AUR packages are shown in the popout before the
user triggers the upgrade. The output format is identical for both
sources, so the existing parser works unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* added login sound functionality and settings entry
* Removed debug warning that was accidentally left in
* loginSound is off by default, and fixed toggle not working
* Prevent login sound from playing in the same session
---------
Co-authored-by: Iris <iris@raidev.eu>
* fix: add TrayRecoveryService with bidirectional SNI dedup (Go server)
Add TrayRecoveryService manager that re-registers lost tray icons after
resume from suspend via native DBus scanning in the Go server.
The service resolves every registered SNI item (both well-known names and
:1.xxx connection IDs) to a canonical connection ID, building a unified
registeredConnIDs set before either scan section runs. This prevents
duplicates in both directions:
- If an app registered via well-known name, the connection-ID section
skips its :1.xxx entry.
- If an app registered via connection ID, the well-known-name section
skips its well-known name (checked through registeredConnIDs).
- After successfully registering via well-known name, registeredConnIDs
is updated immediately so the connection-ID section won't probe the
same app in the same run.
A startup scan (3 s delay) covers the common case where the DMS process
is killed during suspend and restarted by systemd (Type=dbus), so the
loginctl PrepareForSleep watcher alone is not sufficient. The startup
scan is harmless on a normal boot — it finds all items already registered
and exits early.
Go port of quickshell commit 1470aa3.
* fix: 'interface{}' can be replaced by 'any'
* TrayRecoveryService: Remove objPath parameter from registerSNI
* feat: rewind to track start on previous when past 8 seconds
Adds MprisController.previousOrRewind() which rewinds the current track
to position 0 if more than 8 seconds in (with canSeek check), and falls
back to previous() otherwise — matching traditional media player behaviour.
All previous() call sites across Media.qml, MediaPlayerTab.qml,
MediaOverviewCard.qml, LockScreenContent.qml and DMSShellIPC.qml
are updated to use the new shared function.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: poll position in MprisController for previousOrRewind accuracy
Without a polling timer, activePlayer.position is never updated in
contexts that don't display a seekbar (e.g. the DankBar widget), causing
the position > 8 check in previousOrRewind() to always see 0 and fall
through to previous() instead of rewinding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use UnsetWorkspaceName for empty input in workspace rename
Previously, empty input would set workspace name to empty string,
causing issues with Niri's unique workspace name requirement.
Now uses UnsetWorkspaceName action when input is empty.
* prevent false failed to load config toast on niri validation
Move error toast logic from StdioCollector.onStreamFinished to Process.onExited
so it only displays when niri validate actually fails (non-zero exit code),
not when stderr outputs early progress messages during config processing.
Previously, empty input would set workspace name to empty string,
causing issues with Niri's unique workspace name requirement.
Now uses UnsetWorkspaceName action when input is empty.
- Add a neutral `dms auth sync` command and reuse the shared auth flow from:
- Settings auth toggle auto-apply
- `dms greeter sync`
- `dms greeter install`
- greeter auth cleanup paths
- Rework lockscreen PAM so DMS builds /etc/pam.d/dankshell from the system login stack, but removes fingerprint and U2F from that password path. Keep /etc/pam.d/dankshell-u2f separate.
- Preserve custom PAM files in place to avoid adding duplicate greeter auth when the distro already provides it, and keep NixOS on the non-writing path.
Niri already renders fullscreen windows above the top layer-shell layer
(see render_above_top_layer() in niri's scrolling.rs). Aside from
redundancy, this check also hides the bar for windowed-fullscreen
windows (aka fake fullscreen), since the Wayland protocol reports
identical state for both.
- Re-add loginConfigWatcher so installs can still fall through to
/etc/pam.d instead of the bundled PAM assets
- Add login-faillock bundled PAM asset at runtime. Use it as the bundled fallback when dankshell config is absent
- Fix invalid bare property writes (u2fPending, u2fState, unlockInProgress,
state) in Pam.qml
- Improve lockscreen auth feedback
* feat: add neovim-lualine template, set vim.o.background automatically based on dms light/dark mode
* feat(matugen): add option to follow dms background color or not on neovim
* chore: regenerate settings and translation index after merging master
* feat(filebrowser): add filebrowser video thumbnails display
- Find cached thumbnails first
- If not found, generate with ffmpegthumbnailer
- Fallback to placeholder icon if dependency not met
* fix(filebrowser): create thumbnail cache dir if not exists
* refactor(filebrowser): prefer using Paths lib
* fix(filebrowser): only check filetype once for each file
* fix(filebrowser): early test for thumbnails
* feat: add xdgCache path
The current template doesn't work for an OOTB config of pywalfox
without manual configuration. This commit fixes the colors to work
better with its defaults.
* feat: add FullscreenToplevel detection
For animating bar for fullscreen events
* fix: respect overview reveal settings
perform the overview check first
Plugin items can set _preScored to signal a priority boost (e.g. recently
used items). Previously _preScored was only respected when a search query
was active, so no-query default lists always fell back to typeBonus+frecency
scoring, making plugin-controlled ordering impossible.
Change the condition from:
if (query && item._preScored !== undefined)
to:
if (item._preScored !== undefined && (query || item._preScored > 900))
This respects _preScored in no-query mode only when the value exceeds 900
(the plugin typeBonus), which avoids changing behaviour for "all" mode items
whose _preScored is set to 900-j by the controller (≤ 900). Items without
_preScored set continue to use the existing typeBonus + frecency formula.
fzf.js relied on stable Array.sort to preserve score ordering, which is
not guaranteed in QML's JS engine. Results appeared in arbitrary order
with low-relevance matches above exact matches. The sort comparator now
explicitly sorts by score descending, with a length-based tiebreaker so
shorter matches rank first when scores are tied.
Also fixed Object.assign mutating the shared defaultOpts object, which
could cause options to leak between Finder instances.
DankDropdown's onOpened handler now reinitializes the search when previous
search text exists, fixing the empty results shown on reopen.
Added resetSearch() for consumers to clear search state externally.
* fix: restore lock screen U2F/fingerprint auth to working state
* fix(pam): Keep SettingsData as single source of truth for auth availability
- Restores SettingsData for fingerprint/U2F, keeping lock screen and New Greeter Settings UI in sync
---------
Co-authored-by: purian23 <purian23@gmail.com>
Fixed a QML property binding timing issue where dynamically created timers
and processes for per-monitor wallpaper cycling were being assigned to
properties and then immediately read back, which could return undefined
or stale values.
The fix stores the created object in a local variable before assigning
to the property map, ensuring a valid reference is always used.
Affected functions:
- startMonitorCycling() - timer creation
- cycleToNextWallpaper() - process creation
- cycleToPrevWallpaper() - process creation
After unlocking the screen (startup lock or wake from sleep), the desktop
showed Hyprland's background color instead of the wallpaper.
WallpaperBackground disables QML updates via updatesEnabled after a 1-second
settle timer. While WlSessionLock is active, Hyprland does not composite the
background layer, so when the lock is released it needs a fresh Wayland buffer
— but none is committed because the render loop is already paused.
The previous attempt used SessionService.sessionUnlocked, which is unreliable
for the startup lock case: DMSService is not yet connected when lock() is
called at startup, so notifyLoginctl is a no-op and the loginctl state never
transitions, meaning sessionUnlocked never fires.
Fix by tracking the shell lock state directly from Lock.qml's shouldLock via
a new IdleService.isShellLocked property. WallpaperBackground watches this and
re-enables rendering for 1 second on unlock, ensuring a fresh buffer is
committed to Wayland before the compositor resumes displaying the layer.
* Add grouped element reordering to control center setting popup.
Reorganize the control center widget menu into grouped rows and add drag handles for reordering.
Introduce controlCenterGroups to drive the grouped popup layout, along with dynamic content width calculation.
Disable dependent options when their parent icon is turned off, and refine DankToggle disabled colors to better distinguish checked and unchecked states.
* Apply Control Center group order to live widget rendering.
Apply persisted `controlCenterGroupOrder` to the actual control center button rendering path instead of only using it in the settings UI.
This refactors `ControlCenterButton.qml` to derive a normalized effective group order, build a small render model from that order, and use model-driven rendering for both vertical and horizontal layouts.
Highlights:
- add a default control center group order and normalize saved order data
- ignore unknown ids, deduplicate duplicates, and append missing known groups
- add shared group visibility helpers and derive a render model from them
- render both vertical and horizontal indicators from the effective order
- preserve existing icon, color, percent text, and visibility behavior
- keep the fallback settings icon outside persisted ordering
- reconnect cached interaction refs for audio, mic, and brightness to the real rendered group containers so wheel and right-click behavior still work
- clear and refresh interaction refs on orientation, visibility, and delegate lifecycle changes
- tighten horizontal composite group sizing by measuring actual rendered content, fixing extra spacing around the audio indicator
Also updates the settings widgets UI to persist and restore control center group ordering consistently with the live control center rendering.
With programs like rofi, pressing the tab key advances to the next item
in the list. This change makes the Launcher behave in the same way,
moving the action cycling to Ctrl+Tab (and Ctrl+Shift+Tab for reverse.
* Add tmux
* Add mux modal
* Restore the settings config version
* Revert typo
* Use DankModal for InputModal
* Simplify terminal flags
* use showWithOptions for inputModals instead
* Fix translation
* use Quickshell.env("TERMINAL") to choose terminal
* Fix typo
* Hide muxModal after creating new session
* Add mux check, moved exclusion to service, And use ScriptModel
* Revert unrelated change
* Add blank line
* fix(nix/greeter): skip invalid customThemeFile in preStart
Avoid attempting to copy a null/empty/missing customThemeFile path by validating the jq result and file existence before cp.
Update distro/nix/greeter.nix
Co-authored-by: Lucas <43530291+LuckShiba@users.noreply.github.com>
* nix/greeter: update customTheme verification
---------
Co-authored-by: Lucas <43530291+LuckShiba@users.noreply.github.com>
Co-authored-by: LuckShiba <luckshiba@protonmail.com>
- Split auth capability state by lock screen and greeter
- Share detection between settings UI and lock runtime
- Broaden greeter PAM include detection across supported distros
The display config UI only applied changes for compositors with a
config-file backend (niri, hyprland, dwl). For any other compositor
that supports wlr-output-management-unstable-v1 the "Apply Changes"
button was silently a no-op.
Add WlrOutputService.applyOutputsConfig() as a high-level apply that
mirrors the generateOutputsConfig() pattern of the existing services
but applies directly via the protocol instead of writing a config file.
Route the default case in backendWriteOutputsConfig() to it.
This enables using dms-shell as a wayland compositor for emacs wayland
manager (ewm).
* Issue:(Settings)Switched Neovim Mutagen Theme To Default False
* also set to false in settingsData
- this is the case when file fails to parse
---------
Co-authored-by: bbedward <bbedward@gmail.com>
Fixes two cases:
- some apps (e.g., Zen browser use the "—" character at the end of
webpage name)
- in compact mode, when app has only appName, and not window name, we
should display the appName to avoid empty title.
- Added pre-run checks for greeter and setup commands to enforce policy restrictions
- Created cli-policy.default.json to define blocked commands and user messages for immutable environments.
* Not everyone uses paru or yay on Arch: Support pacman command
* Handle sudo properly when using pacman
* Move pacman to bottom per Purian23
* Remote duplicate which -- thanks Purian23!
Supersedes #1878. Rather than duplicating the moddedAppId + file path
substitution pattern inline across 8 files, this introduces two
centralized functions in Paths.qml:
- resolveIconPath(iconName): for Quickshell.iconPath() callsites,
with DesktopService.resolveIconPath() fallback
- resolveIconUrl(iconName): for image://icon/ URL callsites
All consumer files now use one-line calls. When no substitutions are
configured, moddedAppId() returns the original name unchanged (zero
cost), so this has no impact on users who don't use the feature.
Affected components:
- AppIconRenderer (8 lines → 1)
- NotificationCard, NotificationPopup, HistoryNotificationCard
- DockContextMenu, AppsDockContextMenu
- LauncherContent, LauncherTab (×3)
Co-authored-by: odtgit <odtgit@taliops.com>
Follow-up to #1867. The launcher's AppIconRenderer used its own
Quickshell.iconPath() call without going through appIdSubstitutions,
so PWA icons configured via regex file path rules were not resolved
in the app launcher.
Co-authored-by: odtgit <odtgit@taliops.com>
- Added global toggles in the Themes tab
- Light color & directional user ovverides
- Independent shadow overrides per/bar
- Refactored various components to sync the updated designs
* feat: add setting for first day of the week
* fix: extract settings indices
* fix: formatting mistake
* fix(ui): add outline rectangle between settings and reorder settings
* fix: don't set firstDayOfWeek automatically to system's locale
Add a new "Add by Address" flow in the printer settings that allows
users to manually add printers by IP address or hostname, enabling
printing to devices not visible via mDNS/Avahi discovery (e.g.,
printers behind Tailscale subnet routers, VPNs, or across network
boundaries).
Go backend:
- New cups.testConnection IPC method that probes remote printers via
IPP Get-Printer-Attributes with /ipp/print then / fallback
- Input validation with host sanitization and protocol allowlist
- Auth-aware probing (HTTP 401/403 reported as reachable)
- lpadmin CLI fallback for CreatePrinter/DeletePrinter when
cups-pk-helper polkit authorization fails
QML frontend:
- "Add by Address" toggle alongside existing device discovery
- Manual entry form with host, port, protocol fields
- Test Connection button with loading state and result display
- Smart PPD auto-selection by probed makeModel with driverless fallback
- All strings use I18n.tr() with translator context
Includes 20+ unit tests covering validation, probe delegation, TLS
flag propagation, auth error detection, and handler routing.
Allow appIdSubstitutions to return absolute file paths (/, ~, file://)
that bypass Quickshell.iconPath theme lookup. This enables users to map
app IDs directly to icon files on disk via the existing substitution UI.
Fixes PWA icon resolution for Chrome, Chromium and Edge PWAs where
Qt's icon theme lookup fails to find icons installed to
~/.local/share/icons/hicolor/ by the browser.
Example substitutions (Settings → Running Apps → App ID Substitutions):
^msedge-_(.+)$ → ~/.local/share/icons/hicolor/128x128/apps/msedge-$1.png
^(chrome|msedge|chromium)-(.+)$ → ~/.local/share/icons/hicolor/128x128/apps/$1-$2.png
Tested with Chrome PWAs (YouTube, Twitch, ai-ta) and Edge PWAs
(Microsoft Teams, Outlook) on niri/Wayland.
Co-authored-by: odtgit <odtgit@taliops.com>
* feat: switch auto location in weather widget to use GeoClue2 instead of simple IP check
* nix: enable GeoClue2 service by default
* lint: fix line endings
* fix: fall back to IP location if GeoClue is not available
* feat: add setting to change and hotreload locale
* fix: typo in component id
* feat: add persistent locale setting
* feat: wrap useLocale in a settings set hook, enable locale hotreload when editing settings file
* chore: update translation and settings file
* feat: enable fuzzy search in locale setting
* fix: regenerate translations with official plugins cloned
* fix: revert back to system's locale for displaying certain time formats
* feat: Add FIDO2/U2F security key support for lock screen
Adds hardware security key authentication (e.g. YubiKey) with two modes:
Alternative (OR) and Second Factor (AND). Includes settings UI, PAM
integration, availability detection, and proper state cleanup.
Also fixes persist:false properties being reset on settings file reload.
* feat: Add U2F pending timeout and Escape to cancel
Cancel U2F second factor after 30s or on Escape key press,
returning to password/fingerprint input.
* fix: U2F detection honors custom PAM override for non-default key paths
* feat(lockscreen): enable use of videos as screensaver in the lock screen
* reducing debug logs
* feature becomes available only when QtMultimedia is available
This fixes the problem that the system update terminal closes when the package manager encounters a problem (exit code != 0), allowing the user to understand the problem.
Signed-off-by: Jan Phillip Greimann <jan.greimann@ionos.com>
- Implement deep search icon resolution in DesktopService with runtime caching.
- Update Paths.getAppIcon to utilize enhanced resolution for mismatched app IDs.
- Align Workspace Switcher fallback icons with AppsDock visual style.
- Synchronize fallback text logic between Switcher and Dock using app names.
- New background toggles
- New maxIcon & maxText widget sizes (global)
- Dedicated M3 padding slider
- New independent icon scale options
- Updated logic to improve performance on single & dual bar modes
* fix: add mockery v2 to nix flake pkgs
* feat: add requests for generating wifi qr codes as png files in /tmp, and to delete them later. only supports NetworkManager backend for now.
* feat: add modal for sharing wifi via qr code and saving the code as png file.
* fix: uncomment QR code file deletion
* network: light refactor and cleanup for QR code generation
---------
Co-authored-by: bbedward <bbedward@gmail.com>
* clipboard: fix html elements get parsed in clipboard entry
* Revert "clipboard: fix html elements get parsed in clipboard entry"
This reverts commit 52b11eeb98.
* clipboard: fix html elements get parsed in clipboard entry
* clipboard: improve image thumbnail
- thumbnail image is now bigger
- circular mask has been replaced with rounded rectangular mask
* dock: fix dock still visible when there's no app
When switching tabs rapidly or closing multiple tabs, the taskbar shows
"ghost" workspaces — entries with no name, no coordinates, and no active
state. The ghosts appear at positions where workspaces were removed and
then recreated by the compositor.
When a compositor removes a workspace (sends `removed` event) and the
client calls Destroy(), the proxy is marked as zombie but stays in the
Context.objects map. For server-created objects (IDs >= 0xFF000000), the
server never sends `delete_id`, so the zombie proxy persists indefinitely.
When the compositor later creates a new workspace that gets a recycled
server object ID, GetProxy() returns the old zombie proxy. The dispatch
loop in GetDispatch() checks IsZombie() and silently drops ALL events
for zombie proxies — including property events (name, id, coordinates,
state, capabilities) intended for the new workspace. This causes the
ghost workspaces with empty properties in the UI.
Fix: check IsZombie() when handling `workspace` and `workspace_group`
events that carry a `new_id` argument. If the existing proxy is a
zombie, treat it as absent and create a fresh proxy via
registerServerProxy(), which replaces the zombie in the map. Subsequent
property events are then dispatched to the live proxy.
* feat(wip): add fuzzy finder in keybinds cheatsheet
* fix: replace GridLayout with RowLayout and don't use anchors in KeybindsModal
* fix: replace fuzzyfinder with simple inclusion criterion for keybind search
* fix: bring back categoryKeys (there was no reason to remove it)
The _preScored property allows plugins to control the ordering of
launcher results. Previously, this property was being overwritten
with undefined during item transformation, preventing plugins from
controlling which items appear first.
This change preserves the _preScored value from plugin items in:
- transformBuiltInLauncherItem()
- transformPluginItem()
Required for: devnullvoid/dms-web-search#7
* greeter: Detect user and group used by greetd
On most distros greetd runs as user and group "greeter",
but on Debian the user and group "_greetd" are used.
* greeter: Use correct group in sync command
* greeter: more generic group detection
---------
Co-authored-by: bbedward <bbedward@gmail.com>
* fix(launcher): release DankLauncherV2 resources after close
* launcher: make unload on close optional
---------
Co-authored-by: bbedward <bbedward@gmail.com>
Replace layer+MultiEffect mask with GPU-native clipping for rounded
corners. The previous approach created offscreen textures for every
clickable element with rounded corners (used in 34+ files across the
UI), adding 100-300MB VRAM on NVIDIA GPUs.
The new approach uses clip: true on a Rectangle with the corner
radius, which is handled natively by the GPU without creating
intermediate textures.
Visual impact: Minimal - the ripple effect works identically.
The only theoretical difference is slightly less smooth edges on
rounded corners during the ripple animation, which is not noticeable
at 10% opacity during the quick animation.
VRAM improvement: Tested on NVIDIA, ~100-300MB reduction.
* fix(brightness): refresh sysfs cache on hotplug
The SysfsBackend used a cache that was never refreshed on display hot plug, causing new backlight devices to not appear in IPC until restart.
This adds Rescan() to SysfsBackend and calls it in Manager.Rescan(), matching the behavior of DDCBackend.
Fixes: hotplugged external monitor brightness control via IPC
* make fmt
---------
Co-authored-by: bbedward <bbedward@gmail.com>
* add mangowc greeter to nix.
i am going to be suprised if this only needed this line
* point mangowc to mango
there is no way this works
* mango flake detection and maybe scroll support
* " "
* no mango flake dependency
* mango dependency remove too
i have got to add "parenthesis" to stuff more
* Final De-dependification of MangoWC
it works without the flake YES
* mangowc -> mango pt 1
* mangowc -> mango pt 2
necessary evil. will break inital greetd confs but works after change
* Preserve Compatibility
* feat: add osd toggles to search index
this commit also regenerates the search index
* add newline at end of translations file
* ran prek to fix the file :)
* Add audioWheelScrollAmount setting
* Add UI to configure volume change amount for Media Player widget
* Let Media Player widget use the configured volume change on wheel event
* feat: decouple track art downloads into new TrackArtService
* feat: beautify media playback osd with track art
* fix: bug when switching from art to no art
* fix(vpn-widget): correct tooltip positioning for bottom bar alignment
- Calculate tooltip Y position based on bar edge (bottom vs top)
- Position tooltip above bar when edge is bottom to prevent overflow
- Account for tooltip height when positioning on bottom edge
- Use proper screen reference for consistent positioning across displays
* fix(VpnPopout): use correct screen height for popup sizing
- Fix popup height calculation to use the assigned screen property
instead of the global Screen object
- Prevents incorrect positioning when multiple screens are present
- Fallback to Screen.height if screen property is not set
* fix(widgets): close DankPopout when screen is removed
- Add Connections handler to monitor Quickshell.onScreensChanged
- Automatically close popout if its assigned screen no longer exists
- Prevent orphaned popouts when displays are disconnected
This will optionally include one or two files into the Niri conf for
dms-greeter:
- `/usr/share/greetd/niri_overrides.kdl` (for building into a system image)
- `/etc/greetd/niri_overrides.kdl` (for local overrides)
This enables a distro or a system administrator to easily add their own
customizations to the dms-greeter Niri configuration.
Note: Niri next includes https://github.com/YaLTeR/niri/pull/3022 which
can make this a whole lot simpler: we're testing for the existence of
files and optionally adding `include` lines within the dms-greeter shell
script, but in the future it will be possible to simply use `include
optional=true` which we can just append unconditionally.
Lastly, I deduplicated the line that spawns the actual greeter by
appending that unconditionally at the end.
* feat(niri): Add drag-and-drop workspace reordering
Add interactive drag-and-drop reordering for Niri workspace indicators
with smooth animations matching the system tray behavior.
- Add moveWorkspaceToIndex() to NiriService for workspace reordering
- Implement drag detection with 5px threshold
- Add shift animation for items between source and target
- Clamp drag offset to stay within workspace row bounds
- Reset drag state when workspace list changes during drag
- Visual feedback: opacity change, border highlight on drag/drop target
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(settings): Add workspace drag reorder toggle
Add workspaceDragReorder setting to enable/disable workspace
drag-and-drop reordering. Enabled by default, only visible on Niri.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: Add browserPickerViewMode persistence to settings spec
The BrowserPickerModal (used by xdg-open feature) was not persisting
view mode selection between sessions. While the modal had code to save
the view mode preference, the browserPickerViewMode property was not
registered in SettingsSpec.js, preventing it from being saved to disk.
Added browserPickerViewMode and browserUsageHistory to SettingsSpec.js
to ensure user's view preference (list/grid) is properly persisted.
Fixes view mode reverting to grid after restarting DMS/QuickShell.
* fix: Add view mode persistence for both browser and file pickers
Extended the fix to include both picker modals used by xdg-open:
BrowserPickerModal (URLs):
- Added browserPickerViewMode and browserUsageHistory to SettingsSpec.js
- Already had save logic in BrowserPickerModal.qml
AppPickerModal/filePickerModal (files):
- Added appPickerViewMode and filePickerUsageHistory to SettingsSpec.js
- Added appPickerViewMode and filePickerUsageHistory properties to SettingsData.qml
- Added viewMode binding and onViewModeChanged handler to filePickerModal
Both modals now properly persist user's view preference (list/grid) and
usage history between sessions.
Fixes view mode reverting to default grid after restarting DMS/QuickShell
for both 'dms open https://...' and 'dms open file.pdf' workflows.
* greetd: add lockScreenShowProfileImage option
* lockscreen/greetd: for non 24 hour formats, add 0 in front of single digit hours to ensure that everything is always centered properly - previously, it would only appear centered if on a double digit hour. also add getEffectiveTimeFormat function to GreetdSettings.
* clock: made pad 12 hour formats optional
---------
Co-authored-by: bbedward <bbedward@gmail.com>
* feat: add workspace rename dialog
- Adds a modal dialog to rename the current workspace
- Supports both Niri (via IPC socket) and Hyprland (via hyprctl dispatch)
- Default keybinding: Ctrl+Shift+R to open the dialog
- Pre-fills with current workspace name
- Allows setting empty name to reset to default
* refactor: wrap WorkspaceRenameModal in LazyLoader
Reduces memory footprint when the modal is not in use.
* dankbar: show niri workspace names
Keep labels aligned with niri indices and live renames.
* dankbar: prefix named workspaces with index
Use workspace index toggle to show index: name labels.
* workspaces: change size conditions for workspace names
---------
Co-authored-by: bbedward <bbedward@gmail.com>
- Aggregate plugins/extensions in new "all" tab
- Quick tab actions
- New tile mode for results
- Plugins can enforce/require view mode, or set preferred default
- Danksearch under "files" category
Add ability to immediately power off monitors when the lock screen
activates, controlled by a new setting "Power off monitors on lock".
Uses a 100ms polling timer to detect when the session lock actually
becomes active, then invokes compositor-specific DPMS commands.
For niri, uses the new power-off-monitors action via niri msg CLI
with socket fallback.
Wake on input: first input after lock arms wake, second input
actually powers monitors back on while keeping the lock screen visible.
Closes#1157
Add togglePlugin() function and IPC command to toggle plugin visibility,
particularly for slideout-capable daemon plugins like AI Assistant.
Implementation uses lazy instantiation for daemon plugins:
- Daemons remain uninstantiated on load (respecting lifecycle)
- First toggle() call instantiates the daemon on-demand
- Subsequent toggles use the existing instance
- Prevents duplicate instantiation while supporting toggle functionality
This approach preserves the fix from f9b9d986 (ensure daemon plugins
not instantiated twice) while enabling new toggle capabilities.
Changes:
- Add PluginService.togglePlugin() with lazy instantiation
- Add DMSShellIPC plugin.toggle() command
- Maintains compatibility with existing daemon plugins
* niri: Handle new Cast events
* bar: Add screen sharing indicator
Configurable like other icons; on by default.
* lockscreen: Add screen sharing indicator
* feat: add configurable app ID substitutions setting
* feat: add live icon updates when substitutions change
* fix: cursor not showing on headerActions in non-collapsible cards
* fix: address PR review feedback
- add tags for search index
- remove hardcoded height from text fields
Steam Proton games use window class steam_app_XXXXX. Steam installs
icons as steam_icon_XXXXX. This maps between them so actual game
icons display instead of generic controller fallback.
- we are dumb about importing by just trying to import everythting
- that caused errors to not be represented correctly
- just aggregate them all and present them in toast details
- Better would be to detect the type of file being imported, but this is
better than nothing
* feat: doctor command
* doctor: use console.warn for quickshell feature logs
* doctor: show compositor, quickshell and cli path in verbose
* doctor: show useful env variables
* doctor: indicate if config files are readonly
* doctor: add power-profiles-daemon and i2c
* doctor: cleanup code
* doctor: fix icon theme env variable
* doctor: use builtin config/cache dir functions
* doctor: refactor to use DoctorStatus struct and 'enum'
* doctor: use network backend detector
ci: run apt as sudo
ci: fix flatpak remote in runner
ci: flatpak install steps in runner
ci: specific version of freedesktop
ci: freedesktop install perms
* Adding a way to use the start-hyprland wrapper when it's needed from Hyprland 0.53 it's recommended because offers more security if happens a fail
* Deleting unnecessary things and doing verifications
* fix pre-commit
* Changing to not depend on hyprctl to obtain version and avoid posible problems
---------
Co-authored-by: bbedward <bbedward@gmail.com>
* Adding a way to use the start-hyprland wrapper when it's needed from Hyprland 0.53 it's recommended because offers more security if happens a fail
* Deleting unnecessary things and doing verifications
* fix pre-commit
---------
Co-authored-by: bbedward <bbedward@gmail.com>
* added reverse scrolling to settings and widget
* added support for dankbar scrolling
* Better settings description
* removed isNiri conditional from search index
* Added numpad enter key to finish screenshot selection
* Adding Zen Browser matugen template
* Fixing indentation for matugen.go edits
* Trying to fix linting again..
* Tweaking contrasting surface colors in css, renamed file to match how firefox userchrome is named, also changed output directory to DMS config directory (like firefox)
* Modifing Zen userChrome again: removing unused css stuff, tweaking colors to better align with how pywalfox handles backgrounds/toolbars
* Last few tweaks on CSS - changing url bar highlight color, changing contrast on selected urls in dropdown
* matugen.go: fix check command for zen browser
* search_index: add zen browser setting
* added reverse scrolling to settings and widget
* added support for dankbar scrolling
* Better settings description
* removed isNiri conditional from search index
* fix#1179 breaking normal scroll direction
* added reverse scrolling to settings and widget
* added support for dankbar scrolling
* Better settings description
* removed isNiri conditional from search index
* Fix touchpad scrolling behavior
* Make touchpad scroll smoothly
For a normal mouse wheel, adjusting by 5% per scroll makes sense. For a touchpad, however, it should adjust by the smallest increment possible for a smooth experience.
- Configure position, VRR, orientation, resolution, refresh rate
- Split Display section into Configuration, Gamma, and Widgets
- MangoWC omits VRR because it doesnt have per-display VRR
- HDR configuration not present for Hyprland
- QML will sync its desired state with GO, when IE settings are changed
or opened. Go was applying gamma even if unchanged
- Track last applied gamma to avoid sends
* dankmodal: removed backgroundWindow
removed 'backgroundWindow' but combined it with 'contentWindow'
* made single window behavior specific to hyprland
this should keep other compositor behavior the same and fix double
clicking to exit out of Spotlight/ClipboardHist/Powermenu
* ci: change to prek for pre-commit
* refactor: fix shellcheck warnings for the scripts
* chore: unify whitespace formatting
* nix: add prek to dev shell
- niri only for now
- requires quickshell-git, hidden otherwise
- Add, Edit, Delete keybinds
- Large suite of pre-defined and custom actions
- Works with niri 25.11+ include feature
* feat: add browser picker for opening URLs
- Introduce a QML modal allowing users to select a web browser to open a given URL.
- Add a CLI command `dms open <url>` that sends a `browser.open` request to the DMS server.
- Implement server‑side Browser manager, request handling, and subscription handling to propagate open events to clients.
- Extend router and server initialization to register the new “browser” capability and include it in advertised capabilities.
- Expose `openUrlRequested` signal in DMSService.qml and connect it to the modal for seamless UI activation.
- Add a desktop entry for the Browser Picker and update the active subscriptions list to include the browser service.
* fix(browser-picker): resolve QML errors in BrowserPickerModal and DMSShell
* fix(browser-picker): fix socket discovery in dms open command
* feat: add keyboard navigation and dynamic model to browser picker
- Replace the static browsers array with a ListModel built from AppSearchService, ensuring robust iteration and future‑proofing of the browser list.
- Introduce keyboard navigation (arrow keys and Enter) using selectedIndex and gridColumns, allowing users to select a browser without a mouse.
- Reset URL, selected index, and navigation flag when the modal closes to avoid stale state.
- Redesign the grid layout to compute cell width from columns, improve focus handling, and use AppLauncherGridDelegate for a consistent UI.
- Enhance delegate behavior to update selection on hover and reset keyboard navigation state appropriately.
* feat: add searchable list/grid view to browser picker
- Introduce view mode setting (list or grid) saved in SettingsData for persistent user preference
- Add search field with real‑time filtering to quickly locate a browser by name
- Sort browsers by usage frequency from AppUsageHistoryData, falling back to alphabetical order
- Provide UI toggle buttons to switch between list and grid layouts, updating the stored setting
- Adjust keyboard navigation logic to support both layouts and improve focus handling
- Refine modal dimensions and header layout for better visual consistency
- Record launched browser usage to keep usage rankings up‑to‑date.
* feat(browser-picker): improve UX with search, view persistence, and usage tracking
Enhance BrowserPickerModal to match AppLauncher design and functionality:
UI/UX Improvements:
- Add search bar with DankTextField for filtering browsers
- Move view mode switcher (list/grid) to header next to title
- Persist view mode preference to SettingsData.browserPickerViewMode
- Match AppLauncher dimensions (520x500)
- Add proper spacing between list items
- Improve URL display with truncation (single line, elide middle)
- Remove redundant close button
Functionality:
- Implement separate browser usage tracking in SettingsData.browserUsageHistory
- Sort browsers by most recently used (independent from app launcher stats)
- Add keyboard navigation auto-scrolling for list and grid views
- Track usage count, last used timestamp, and browser name
- Filter browsers by search query
Technical:
- Add ensureVisible() functions to DankListView and DankGridView
- Store browser usage with count, lastUsed, and name fields
- Update browser list reactively on search query changes
* feat(browser-picker): use appLauncherGridColumns setting for grid layout
Make browser picker grid view respect the same column setting as the app launcher
for consistent UI across both components.
* refactor: make browser picker extensible for any MIME type/category
Refactor browser picker into a generic, reusable application picker
system that can handle any MIME type or application category, similar
to Junction. This addresses the maintainer feedback about making the
functionality "as re-usable as possible."
Frontend (QML):
- Create generic AppPickerModal component (~450 lines)
- Configurable filtering by application categories
- Customizable title, view modes, and usage tracking
- Emits applicationSelected signal for flexibility
- Refactor BrowserPickerModal as thin wrapper (473 → 46 lines)
- Demonstrates how to create specialized pickers
- Maintains all existing browser picker functionality
Backend (Go):
- Rename browser package to apppicker for clarity
- Enhance event model to support:
- MIME types (for future file associations)
- Application categories (WebBrowser, Office, Graphics, etc.)
- Request types (url, file, custom)
- Maintain backward compatibility with browser.open method
- Add new apppicker.open method for generic usage
CLI:
- Rename commands_browser.go to commands_open.go
- Add extensibility flags:
--mime/-m: Filter by MIME type
--category/-c: Filter by category (repeatable)
--type/-t: Specify request type
- Examples:
dms open file.pdf --category Office
dms open image.png --category Graphics
DMSService:
- Add appPickerRequested signal for generic events
- Smart routing between URL and generic app picker events
- Fully backward compatible
Benefits:
- Easy to create new pickers (~15 lines of wrapper code)
- Foundation for universal file handling system
- Consistent UX across all picker types
- Ready for MIME type associations
Future extensions:
- PDF picker, image viewer picker, text editor picker
- Default application management
- File association UI in settings
- Multi-MIME type desktop file integration
* fix(cli): remove all shorthands from open command flags for consistency
Remove shorthands from --mime, --category, and --type flags to maintain
consistency and avoid conflicts with global flags.
Flags now (all long-form only):
- --category: Application categories
- --mime: MIME type
- --type: Request type
Global flags still available:
- --config, -c: Config directory path
* style: apply gofmt formatting to apppicker files
Fix formatting issues caught by CI:
- Align struct field spacing in OpenEvent
- Align variable declaration spacing
- Fix Args field alignment in cobra.Command
* feat(apppicker): add generic file opener with auto MIME detection
Implements Junction-style generic file opening capabilities:
**Backend (Go):**
- Enhanced CLI to parse file:// URIs and extract file paths
- Auto-detect MIME types from file extensions using Go's mime package
- Auto-map MIME types to desktop categories:
- Images → Graphics, Viewer
- Videos → Video, AudioVideo
- Audio → Audio, AudioVideo
- Text → TextEditor, Office (or WebBrowser for HTML)
- PDFs → Office, Viewer
- Office docs → Office
- Archives → Archiving, Utility
- Added debug logging to CLI and server handler for troubleshooting
**Frontend (QML):**
- Added generic AppPickerModal (filePickerModal) for file selection
- Connected to DMSService.appPickerRequested signal
- Implemented onApplicationSelected handler with desktop entry field code support:
- %f/%F for file paths
- %u/%U for file:// URIs
- Fallback to appending path if no field codes
- Separate usage tracking: filePickerUsageHistory
**Desktop Integration:**
- Updated dms-open.desktop to handle x-scheme-handler/file
- Changed category from Network;WebBrowser to Utility (more generic)
- Added text/html to MIME types
**Usage:**
Set DMS as default for specific MIME types in ~/.config/mimeapps.list:
text/plain=dms-open.desktop
image/png=dms-open.desktop
application/pdf=dms-open.desktop
Then use:
xdg-open file.txt
xdg-open image.png
dms open document.pdf
The picker will show appropriate apps based on auto-detected categories.
Related to #815
* fix: resolve relative path handling by converting to absolute paths
- Convert file:// URIs to absolute filesystem paths for reliable file resolution
- Convert plain local file arguments to absolute paths to ensure consistent processing
- Update log messages to display absolute paths, improving traceability
- Retain request type detection while using absolute path extensions for MIME type inference
* feat(app-picker): add Tab key view toggle and fix targetData binding
- Add Tab key to toggle between grid and list views for better keyboard UX
- Fix bug where targetData binding broke after first modal close
- Removed targetData reset from onDialogClosed
- Parent components (BrowserPickerModal, filePickerModal) now manage targetData
- Fixes issue where URL/file path disappeared on subsequent opens
* fix(app-picker): properly escape URLs and file paths for shell execution
- Add shellEscape() function to wrap arguments in single quotes
- Prevents shell interpretation of special characters (&, ?, =, spaces, etc.)
- Fixes bug where URLs with query parameters were truncated at first &
- Example: http://localhost:36275/vnc.html?autoconnect=true&reconnect=true
now properly passes the full URL instead of cutting at first &
- Applied to both BrowserPickerModal (URLs) and filePickerModal (file paths)
* fix: check error return from InitializeAppPickerManager
* feat: bar visibility and autoHide IPC
also changed reveal to show
* feat: settings get/set IPC
* fix: show -> reveal, show is reserved keyword
* move IpcHandlers from SettingsData to DMSShellIPC
* feat: add sun and moon view to WeatherTab
* feat: hourly forecast and scrollable date
* fix: put listviews in loaders to prevent ui blocking
* dankdash/weather: wrap all tab content in loaders, weather updates
- remove a bunch of transitions that make things feel glitchy
- use animation durations from Theme
- configurable detailed/compact hourly view
* weather: fix scroll and some display issues
---------
Co-authored-by: bbedward <bbedward@gmail.com>
- Size content window to content size, buffer for shadow
- Add second window for click outside behavior
- User overriding the layer disables the click outside behavior
part of #716
This directory contains agent skills following the [Agent Skills](https://agentskills.io) open standard - a portable, version-controlled format for giving AI agents specialized capabilities.
Each skill is a directory with a `SKILL.md` entrypoint, optional reference docs, scripts, and templates. Agents load skills progressively: metadata at startup, full instructions on activation, and supporting files on demand.
## Available Skills
| Skill | Description |
|-------|-------------|
| [dms-plugin-dev](dms-plugin-dev/) | Develop plugins for DankMaterialShell - covers all 4 plugin types (widget, daemon, launcher, desktop), manifest creation, QML components, settings UI, data persistence, theme integration, and PopoutService usage. |
## Installation
The `.agents/skills/` directory at the project root is the standard location defined by the agentskills.io spec. Many agents discover skills from this path automatically. Some agents use their own directory conventions and need a symlink or copy.
### Claude Code
Claude Code discovers skills from `.claude/skills/` (project-level) or `~/.claude/skills/` (personal). To make skills from `.agents/skills/` available, symlink them into the Claude Code skills directory:
After linking, the skill appears in Claude Code's `/` menu as `/dms-plugin-dev`, and Claude loads it automatically when you ask about DMS plugin development.
See the [Claude Code skills docs](https://code.claude.com/docs/en/skills) for more on skill configuration, invocation control, and frontmatter options.
### Cursor
Cursor discovers skills from `.cursor/skills/` in the project root:
See [Codex skills docs](https://developers.openai.com/codex/skills/) for details.
### Other Agents
The Agent Skills standard is supported by 30+ tools including Goose, Roo Code, JetBrains Junie, Amp, OpenCode, OpenHands, Kiro, and more. Most discover skills from a dot-directory at the project root (e.g., `.goose/skills/`, `.roo/skills/`). Some read `.agents/skills/` directly.
Check the [Agent Skills client showcase](https://agentskills.io/clients) for setup instructions specific to your agent.
"description":"Relative path to main QML component file. Required unless 'components' is provided.",
"pattern":"^\\./.*\\.qml$"
},
"components":{
"type":"object",
"description":"Map of surface name to relative QML component path, for multi-surface (composite) plugins. Provide any subset of surfaces; each is loaded independently.",
"properties":{
"widget":{
"type":"string",
"description":"Bar/Control Center widget component (PluginComponent)",
"description":"Trigger string for launcher activation (required for launcher type)"
},
"icon":{
"type":"string",
"description":"Material Design icon name"
},
"settings":{
"type":"string",
"description":"Path to settings component QML file",
"pattern":"^\\./.*\\.qml$"
},
"startupCheck":{
"type":"string",
"description":"Path to a non-visual (QtObject) component exposing a check(done) function that gates activation. done(null) allows; done(error) blocks, where error is a string or { title, details }.",
"pattern":"^\\./.*\\.qml$"
},
"requires_dms":{
"type":"string",
"description":"Minimum DMS version requirement (e.g., '>=0.1.18', '>0.1.0')",
"pattern":"^(>=?|<=?|=|>|<)\\d+\\.\\d+\\.\\d+$"
},
"dependencies":{
"type":"array",
"description":"Array of required system tools/dependencies (registry metadata)",
"items":{
"type":"string"
}
},
"requires":{
"type":"array",
"description":"Deprecated alias for 'dependencies'.",
NOT persisted. Shared across all instances of a plugin. Use for cross-instance state synchronization (multi-monitor consistency, multi-instance widgets).
| User preferences (API keys, themes, intervals) | `savePluginData` / `loadPluginData` | Yes (settings.json) | Per plugin |
| Runtime state (history, cache, counters) | `savePluginState` / `loadPluginState` | Yes (state file) | Per plugin |
| Cross-instance sync (multi-monitor data) | `PluginGlobalVar` or `getGlobalVar`/`setGlobalVar` | No (runtime only) | All instances |
| Quick reactive reads from settings | `pluginData` property | N/A (read-only) | Per instance |
## Plugin Path
Retrieve a plugin's installation directory at runtime:
```qml
vardir=pluginService.getPluginPath(pluginId)
```
Returns the absolute path to the plugin's directory (e.g., `~/.config/DankMaterialShell/plugins/MyPlugin`), or an empty string if the plugin is not found. Useful for loading bundled assets (images, data files) relative to the plugin's location.
## Important Notes
1.**pluginData is reactive** - bindings update automatically when data changes
2.**Global vars are NOT persistent** - they reset when the shell restarts
3.**State vs Data** - data is for user-facing settings, state is for internal runtime data
4.**Null safety** - always check `pluginService` is not null before calling methods
5.**Signal namespacing** - global var signals include `pluginId` to filter for your plugin
6.**Performance** - global vars are efficient for frequent updates; settings writes are batched
| `editMode` | bool | `true` when user is dragging/resizing |
| `widgetWidth` | real | Current widget container width |
| `widgetHeight` | real | Current widget container height |
## Optional Properties
Define these on your root item to customize behavior:
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `minWidth` | real | 100 | Minimum allowed width during resize |
| `minHeight` | real | 100 | Minimum allowed height during resize |
## Position and Size Persistence
Position (`desktopX`, `desktopY`) and size (`desktopWidth`, `desktopHeight`) are automatically managed by the DesktopPluginWrapper. You do not need to handle persistence for positioning.
## Edit Mode
When `editMode` is true, the user is repositioning or resizing. Use this to:
- Show visual indicators (borders, handles)
- Disable interactive elements to prevent accidental actions
Launcher plugins extend the DMS launcher with custom searchable items and actions. They use trigger-based filtering and integrate directly into the app drawer.
## Base Component
Launchers use a plain `Item` (not PluginComponent):
| `author` | string | Creator name or email | Non-empty |
| `type` | string | Plugin type | One of: `widget`, `daemon`, `launcher`, `desktop`, `composite` |
| `capabilities` | array | Plugin capabilities | At least 1 string item |
One of `component` or `components` is required (not both):
| Field | Type | Description | Validation |
|-------|------|-------------|------------|
| `component` | string | Path to main QML file (single-surface plugins) | Must start with `./`, end with `.qml` |
| `components` | object | Map of surface name to QML path (multi-surface plugins) | At least 1 entry; keys: `widget`, `desktop`, `daemon`, `launcher` |
| `components` has `launcher` key | `trigger` | Same requirement applies to composite plugins with a launcher surface |
## Optional Fields
| Field | Type | Description |
|-------|------|-------------|
| `icon` | string | Material Design icon name (displayed in plugin list UI) |
| `settings` | string | Path to settings QML file (must start with `./`, end with `.qml`) |
| `startupCheck` | string | Path to a QtObject component that gates plugin activation via a `check(done)` function (must start with `./`, end with `.qml`). See Startup Check section below. |
| `process` | Execute system commands | No (not currently enforced) |
| `network` | Network access | No (not currently enforced) |
If your plugin has a `settings` component but does not declare `settings_write`, users will see an error instead of the settings UI.
## Capabilities
Capabilities are free-form strings that describe what the plugin does. Common values:
-`dankbar-widget` - general bar widget
-`control-center` - integrates with Control Center
-`monitoring` - system/service monitoring
-`launcher` - launcher search provider
-`desktop-widget` - desktop background widget
-`ai` - AI/LLM integration
-`slideout` - uses slideout panel
## Startup Check
The `startupCheck` field points to a non-visual `QtObject` component that gates plugin activation on dependency checks. The component must expose a `check(done)` function:
The schema allows additional properties (`"additionalProperties": true`), so plugins can include custom fields. Common custom fields seen in production plugins:
-`viewMode` - launcher display mode (`"tile"` for image grids)
-`viewModeEnforced` - lock launcher to specific view mode (`true`/`false`)
The `PopoutService` singleton lets plugins control all DMS popouts and modals. It is automatically injected into widget, daemon, and settings components.
## Setup
Declare the property in your component for injection to work:
```qml
propertyvarpopoutService:null
```
Without this declaration, injection fails with: `Cannot assign to non-existent property "popoutService"`
## Popouts (DankPopout-based)
| Component | Open | Close | Toggle |
|-----------|------|-------|--------|
| Control Center | `openControlCenter()` | `closeControlCenter()` | `toggleControlCenter()` |
| Notification Center | `openNotificationCenter()` | `closeNotificationCenter()` | `toggleNotificationCenter()` |
All plugin settings use the `PluginSettings` wrapper. Setting components auto-save on change and auto-load on creation.
## PluginSettings Wrapper
```qml
importQtQuick
importqs.Common
importqs.Widgets
importqs.Modules.Plugins
PluginSettings{
pluginId:"yourPlugin"// Required: must match plugin.json id
// Setting components go here
}
```
**Important:** The plugin must declare `"permissions": ["settings_write"]` in plugin.json for the settings UI to render. Without it, users see an error.
**PluginSettings provides to children:**
-`saveValue(key, value)` - save a setting value
-`loadValue(key, defaultValue)` - load a setting value
-`saveState(key, value)` - save plugin state (separate file)
-`loadState(key, defaultValue)` - load plugin state
| `ccWidgetSecondaryText` | string | Subtitle / status text |
| `ccWidgetIsActive` | bool | Active state (changes styling) |
**CC signals:**
| Signal | When fired |
|--------|-----------|
| `ccWidgetToggled()` | Icon area clicked |
| `ccWidgetExpanded()` | Expand area clicked (CompoundPill only) |
**CC sizing rules:**
- 25% width - SmallToggleButton (icon only)
- 50% width - ToggleButton (no detail) or CompoundPill (with ccDetailContent)
- Users can resize in CC edit mode
### Detail Content (CompoundPill)
Add an expandable panel below the CC widget:
```qml
ccDetailContent:Component{
Rectangle{
implicitHeight:200
color:Theme.surfaceContainerHigh
radius:Theme.cornerRadius
Column{
anchors.fill:parent
anchors.margins:Theme.spacingM
spacing:Theme.spacingS
// Detail UI here
}
}
}
```
## Visibility Control
Conditionally show/hide the bar pill:
```qml
PluginComponent{
visibilityCommand:"pgrep -x myapp"
visibilityInterval:5// seconds between checks
}
```
**Bar reveal optimization:** The visibility timer automatically pauses while the bar is hidden (auto-hide mode) and resumes checks when the bar is revealed. This is handled via the internal `_barRevealed` property - no plugin code needed. Plugins using `visibilityCommand` with `visibilityInterval` benefit from this automatically.
## Popout Namespace
For plugins with multiple popout instances, use `layerNamespacePlugin` to isolate popout state:
```qml
PluginComponent{
layerNamespacePlugin:true
}
```
## Reading Plugin Data
Access saved settings reactively via the injected `pluginData`:
- Purple and black checkerboards are QT's way of signalling an icon doesn't exist
- FIX: Configure a QT6 or Icon Pack in DMS Settings that has the icon you want
- Follow the [THEMING](https://danklinux.com/docs/dankmaterialshell/icon-theming) section to ensure your QT environment variable is configured correctly for themes.
- Once done, configure an icon theme - either however you normally do with gtk3 or qt6ct, or through the built-in settings modal. -->
## Compositor
- [ ] niri
- [ ] Hyprland
- [ ] dwl (MangoWC)
- [ ] sway
- [ ] Other (specify)
## Distribution
<!-- Arch, Fedora, Debian, etc. -->
## dms version
<!-- Output of dms version command -->
## Description
<!-- Brief description of the issue -->
## Expected Behavior
<!-- Describe what you expected to happen -->
## Steps to Reproduce
<!-- Please provide detailed steps to reproduce the issue -->
1.
2.
3.
## Error Messages/Logs
<!-- Please include any error messages, stack traces, or relevant logs -->
<!-- you can get a log file with the following steps:
dms kill
mkdir ~/dms_logs
nohup dms run > ~/dms_logs/dms-$(date +%s).txt 2>&1 &
Then trigger your issue, and share the contents of ~/dms_logs/dms-<timestamp>.txt
-->
```
Paste error messages or logs here
```
## Screenshots/Recordings
<!-- If applicable, add screenshots or screen recordings -->
description:Suggest a new feature or improvement for DMS. Keep features focused on a single topic with clear benefits, examples, etc. Avoid vague or broad requests, they will be closed.
labels:
- enhancement
body:
- type:markdown
attributes:
value:|
## DankMaterialShell Feature Request
- type:textarea
id:feature_description
attributes:
label:Feature Description
description:Brief description of the feature requested
placeholder:What feature would you like to see?
validations:
required:true
- type:textarea
id:use_case
attributes:
label:Use Case
description:Explain the purpose of this feature/why it'd be useful to you
placeholder:Why is this feature important?
validations:
required:false
- type:dropdown
id:compositor
attributes:
label:Compositor(s)
description:Is this feature specific to one or more compositors?
options:
- All compositors
- Niri
- Hyprland
- MangoWC (dwl)
- Sway
- Other (specify below)
validations:
required:true
- type:input
id:compositor_other
attributes:
label:If Other, please specify
placeholder:e.g., Wayfire, Mutter, etc.
validations:
required:false
- type:textarea
id:proposed_solution
attributes:
label:Proposed Solution
description:If you have any ideas for how to implement this, please share!
placeholder:Suggest a solution or approach
validations:
required:false
- type:textarea
id:alternatives
attributes:
label:Alternatives/Existing Solutions
description:Include any similar/pre-existing products that solve this problem
placeholder:List alternatives or existing solutions
entry:bash -c 'python3 quickshell/translations/extract_settings_index.py >/dev/null || exit 1; if ! git diff --exit-code -- quickshell/translations/settings_search_index.json; then echo "settings_search_index.json is out of date; run quickshell/translations/extract_settings_index.py and stage the result" >&2; exit 1; fi'
To contribute fork this repository, make your changes, and open a pull request.
The preferred tool for formatting files is [qmlfmt](https://github.com/jesperhh/qmlfmt) (also available on aur as qmlfmt-git). It actually kinda sucks, but `qmlformat` doesn't work with null safe operators and ternarys and pragma statements and a bunch of other things that are supported.
## Setup
We need some consistent style, so this at least gives the same formatter that Qt Creator uses.
Install [prek](https://prek.j178.dev/) then activate pre-commit hooks:
You can configure it to format on save in vscode by configuring the "custom local formatters" extension then adding this to settings json.
Sometimes it just breaks code though. Like turning `"_\""` into `"_""`, so you may not want to do formatOnSave.
### Nix Development Shell
If you have Nix installed with flakes enabled, you can use the provided development shell which includes all necessary dependencies:
```bash
nix develop
```
This will provide:
- Go 1.25+ toolchain (go, gopls, delve, go-tools) and GNU Make
- Quickshell and required QML packages
- Properly configured QML2_IMPORT_PATH
The dev shell automatically creates the `.qmlls.ini` file in the `quickshell/` directory.
## VSCode Setup
This is a monorepo, the easiest thing to do is to open an editor in either `quickshell`, `core`, or both depending on which part of the project you are working on.
### QML (`quickshell` directory)
1. Install the [QML Extension](https://doc.qt.io/vscodeext/)
2. Configure `ctrl+shift+p` -> user preferences (json) with qmlls path
**Note:** Paths may vary by distribution. Below are examples for Arch Linux and Fedora.
3. Create empty `.qmlls.ini` file in `quickshell/` directory
```bash
cd quickshell
touch .qmlls.ini
```
4. Restart dms to generate the `.qmlls.ini` file
5. Run `make lint-qml` from the repo root to lint QML entrypoints (requires the `.qmlls.ini` generated above). The script needs the **Qt 6**`qmllint`; it checks `qmllint6`, Fedora's `qmllint-qt6`, `/usr/lib/qt6/bin/qmllint`, then `qmllint` in `PATH`. If your Qt 6 binary lives elsewhere, set `QMLLINT=/path/to/qmllint`.
6. Make your changes, test, and open a pull request.
### I18n/Localization
When adding user-facing strings, ensure they are wrapped in `I18n.tr()` with context, for example.
```qml
importqs.Common
Text{
text:I18n.tr("Hello World","<This is context for the translators, example> Hello world greeting that appears on the lock screen")
}
```
Preferably, try to keep new terms to a minimum and re-use existing terms where possible. See `quickshell/translations/en.json` for the list of existing terms. (This isn't always possible obviously, but instead of using `Auto-connect` you would use `Autoconnect` since it's already translated)
### GO (`core` directory)
1. Install the [Go Extension](https://code.visualstudio.com/docs/languages/go)
2. Ensure code is formatted with `make fmt`
3. Add appropriate test coverage and ensure tests pass with `make test`
[)](https://aur.archlinux.org/packages/dms-shell-git)
[>)](https://aur.archlinux.org/packages/dms-shell-git)
DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), [labwc](https://labwc.github.io/), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop.
DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), [labwc](https://labwc.github.io/), [Scroll](https://github.com/dawsers/scroll), [Miracle WM](https://github.com/miracle-wm-org/miracle-wm), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop.
## Repository Structure
@@ -105,7 +105,7 @@ Extend functionality with the [plugin registry](https://plugins.danklinux.com).
## Supported Compositors
Works best with [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [Sway](https://swaywm.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), and [labwc](https://labwc.github.io/) with full workspace switching, overview integration, and monitor management. Other Wayland compositors work with reduced features.
Works best with [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [Sway](https://swaywm.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [labwc](https://labwc.github.io/), [Scroll](https://github.com/dawsers/scroll), and [Miracle WM](https://github.com/miracle-wm-org/miracle-wm) with full workspace switching, overview integration, and monitor management. Other Wayland compositors work with reduced features.
@@ -10,48 +10,96 @@ Go-based backend for DankMaterialShell providing system integration, IPC, and in
Command-line interface and daemon for shell management and system control.
**dankinstall**
Distribution-aware installer with TUI for deploying DMS and compositor configurations on Arch, Fedora, Debian, Ubuntu, openSUSE, and Gentoo.
Distribution-aware installer for deploying DMS and compositor configurations on Arch, Fedora, Debian, Ubuntu, openSUSE, and Gentoo. Supports both an interactive TUI and a headless (unattended) mode via CLI flags.
## System Integration
**Wayland Protocols**
-`wlr-gamma-control-unstable-v1` - Night mode and gamma control
@@ -115,6 +152,12 @@ var pluginsInstallCmd = &cobra.Command{
Short:"Install a plugin by ID",
Long:"Install a DMS plugin from the registry using its ID (e.g., 'myPlugin'). Plugin names with spaces are also supported for backward compatibility.",
Long:"Create or update a keybind override for the specified provider",
Args:cobra.ExactArgs(3),
Run:runKeybindsSet,
}
varkeybindsRemoveCmd=&cobra.Command{
Use:"remove <provider> <key>",
Short:"Remove a keybind",
Long:"Remove a keybind. For Hyprland this writes a negative override to dms/binds-user.lua so the key stays unbound across DMS updates. For other providers it deletes the entry from the managed file.",
Args:cobra.ExactArgs(2),
Run:runKeybindsRemove,
}
varkeybindsResetCmd=&cobra.Command{
Use:"reset <provider> <key>",
Short:"Reset a keybind override to its DMS default",
Long:"Drop the user override for the given key so the DMS default re-applies. For providers without a separate default file (Niri, MangoWC) this is equivalent to remove.",
Args:cobra.ExactArgs(2),
Run:runKeybindsReset,
}
funcinit(){
keybindsListCmd.Flags().BoolP("json","j",false,"Output as JSON")
keybindsShowCmd.Flags().String("path","","Override config path for the provider")
keybindsSetCmd.Flags().String("desc","","Description for hotkey overlay")
keybindsSetCmd.Flags().Bool("allow-when-locked",false,"Allow when screen is locked")
keybindsSetCmd.Flags().Int("cooldown-ms",0,"Cooldown in milliseconds")
returnnil,fmt.Errorf("failed to check enabled state: %w",err)
}
state.EnabledState=enabledState
state.NeedsDisable=needsDisable
ifenabledState=="not-found"{
state.Exists=false
returnstate,nil
}
state.Exists=true
returnstate,nil
}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.