1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-04 03:22:12 -04:00

Compare commits

..

135 Commits

Author SHA1 Message Date
bbedward
8271d8423d greeter: sync power menu options 2026-02-25 14:50:06 -05:00
bbedward
c76e29c457 dankdash: fix menu overlays 2026-02-25 14:37:55 -05:00
purian23
4750a7553b feat: Add independent power action confirmation settings for dms greeter 2026-02-25 14:33:09 -05:00
Joaquim S.
60786921a9 matugen/template: Pasterizing neovim. (#1828)
* matugen/template: Pasterizing neovim.

* matugen/template: More contrast
2026-02-25 13:58:36 -05:00
bbedward
751bbcc127 desktop widgets: fix deactive loaders when widgets disabled fixes #1813 2026-02-25 12:34:09 -05:00
null
58e8dd5456 feat: add more disk usage viewing options (#1833)
* feat: show memory widget in gb

* cleanup

* even more cleanup

* fix

* feat: add more disk usage viewing options
2026-02-25 10:53:12 -05:00
bbedward
1586c25847 dankbar: layer enabled false + binding tweak 2026-02-25 10:45:08 -05:00
null
cded5a7948 feat: show memory widget in gb (#1825)
* feat: show memory widget in gb

* cleanup

* even more cleanup

* fix
2026-02-25 08:01:41 -05:00
purian23
6238e065f2 distros: Workflows input type updates 2026-02-24 23:24:05 -05:00
purian23
72fbbfdd0d distros: Update workflows 2026-02-24 22:59:17 -05:00
purian23
2796c1cd4d fix: Defer DankCircularImage saving until the window is available 2026-02-24 21:23:36 -05:00
bbedward
54c9886627 settings: make horizontal change more smart 2026-02-24 20:48:42 -05:00
bbedward
05713cb389 settings: restore notifyHorizontalBarChanged 2026-02-24 19:42:29 -05:00
purian23
8bb3ee5f18 fix: Update HTML rendering injections 2026-02-24 19:34:46 -05:00
purian23
bc0b4825f1 dbar: Refactor to memoize dbar & widget state via json 2026-02-24 18:56:30 -05:00
purian23
ef7f17abf4 cpu widget: Fix monitor binding 2026-02-24 17:21:42 -05:00
Yamada.Kazuyoshi
876cd21f0b display battery consumption / charging W (#1814) 2026-02-24 15:42:47 -05:00
bbedward
5c92d49873 settings: use Image in theme colors tab wp preview 2026-02-24 15:22:47 -05:00
bbedward
da47b573be popout: fully unload popout layers on close 2026-02-24 15:19:30 -05:00
bbedward
2f04be8778 wallpaper: handle initial load better, add dms randr command for quick
physical scale retrieval
2026-02-24 15:09:04 -05:00
bbedward
69178ddfd8 privacy indicator: fix width when not active 2026-02-24 13:59:38 -05:00
bbedward
a310f6fff0 settings: use Image for per-mode previews 2026-02-24 13:36:57 -05:00
bbedward
7474abe286 matugen: skip theme refreshes if no colors changed 2026-02-24 13:28:13 -05:00
bbedward
df2ba3a3c6 dock: fix tooltip positioning 2026-02-24 13:28:13 -05:00
bbedward
e536456236 dankbar: fix some defaults in reset 2026-02-24 13:28:13 -05:00
bbedward
8d77122da3 widgets: set updatesEnabled false on background layers, if qs supports
it
2026-02-24 13:28:13 -05:00
bbedward
fb66effa51 widgets: fix moddedAppID consistency 2026-02-24 13:28:13 -05:00
purian23
5052e71c31 settings: Re-adjust dbar layout 2026-02-24 12:24:05 -05:00
purian23
bfc78d16ca settings: Dankbar layout updates 2026-02-24 12:02:16 -05:00
purian23
c425e3562b fix: Clipboard button widget alignment 2026-02-24 11:50:36 -05:00
bbedward
1f26092aa9 dankbar: fix syncing settings to new bars 2026-02-24 11:00:43 -05:00
bbedward
2849bb96f4 i18n: term sync 2026-02-24 10:50:16 -05:00
bbedward
7b749f2a4c dankbar: restore horizontal change debounce 2026-02-24 10:49:09 -05:00
bbedward
8803c94ce0 dpms: disable fade overlay in onRequestMonitorOn 2026-02-24 10:38:12 -05:00
bbedward
f5235c943b dankbar: optimize bindings in bar window 2026-02-24 10:22:43 -05:00
bbedward
59fec889b5 widgets: fix undefined icon warnings 2026-02-24 10:05:28 -05:00
null
f42f04a807 feat: improve icon resolution and align switcher fallback styling (#1823)
- 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.
2026-02-24 09:40:15 -05:00
purian23
51f6f37925 display: Fix output config on delete & popup height 2026-02-24 01:03:51 -05:00
purian23
9651a4ca98 template: Refine bug report tracker 2026-02-24 00:41:04 -05:00
purian23
2b7fd36322 feat: Refactor DankBar w/New granular options
- 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
2026-02-23 23:18:27 -05:00
purian23
b8014fd4df fix: Animated Image warnings 2026-02-23 23:18:02 -05:00
Lucas
07460f6e1f doctor: fix imageformats detection (#1811) 2026-02-23 19:44:51 -05:00
bbedward
f7bf3b2afb keybinds: preserve scroll position of expanded item on list change
fixes #1766
2026-02-23 19:32:13 -05:00
bbedward
056f298cdf widgets: fallback when AnimatedImage probe fails to static Image 2026-02-23 19:03:14 -05:00
bbedward
e83da53162 thememode: connect to loginctl PrepareForSleep event 2026-02-23 19:01:20 -05:00
purian23
9f38a47a02 dms-greeter: Update dankinstall greeter automation w/distro packages 2026-02-23 15:36:17 -05:00
bbedward
236a4d4a6d launcher: don't tie unload to visibility 2026-02-23 15:28:29 -05:00
purian23
0909471510 audio: Sync audio hide opts w/dash Output devices 2026-02-23 13:48:33 -05:00
bbedward
05eaf59c89 audio: fix cycle output, improve icon resolution for sink
fixes #1808
2026-02-23 13:21:43 -05:00
Lucas
7749613801 nix: update flake.lock (#1809) 2026-02-23 12:31:36 -05:00
bbedward
e3dbaedbb4 audio: disable effects when mpris player is playing 2026-02-23 12:31:04 -05:00
bbedward
9f17ced6de launcher: implement memory for selected tab
fixes #1806
2026-02-23 10:19:31 -05:00
dms-ci[bot]
de54ef871d nix: update vendorHash for go.mod changes 2026-02-23 15:04:39 +00:00
Jonas Bloch
b0da45d6d0 Wifi QR code generation (requires testing) (#1794)
* 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>
2026-02-23 10:02:51 -05:00
bbedward
9b2a46fa92 widgets: make AnimatedImage conditional in DankCircularImage
- Cut potential overhead of always using AnimatedImage
2026-02-23 09:51:56 -05:00
bbedward
12099d2db6 osd: disable media playback OSD by default 2026-02-23 09:25:23 -05:00
Triệu Kha
84fa75936a clipboard: fix html elements get parsed in clipboard entry (#1798)
* 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
2026-02-23 09:16:15 -05:00
Jonas Bloch
d78d8121a1 fix(notepad): decode path URI when saving/creating a file (#1805) 2026-02-23 09:15:51 -05:00
Jonas Bloch
a9a3a52872 feat: add support for animated gifs as profile pictures (#1804) 2026-02-23 09:15:26 -05:00
purian23
912e3bdfce dms-greeter: Enable greetd via dms greeter install all-in-one cmd 2026-02-22 23:26:40 -05:00
bbedward
ee1b25d9e8 matugen: unconditionally run portal sync even if matugen errors 2026-02-22 22:57:51 -05:00
purian23
20ef5e2c18 dms-greeter: Enhance DMS Greeter dankinstall & packaging across distros
- Added support for Debian, Ubuntu, Fedora, Arch, and OpenSUSE on dankinstall / dms greeter install
2026-02-22 22:40:51 -05:00
bbedward
6ee419bc52 launcher: fix frecency ranking in search results
fixes #1799
2026-02-22 22:34:26 -05:00
bbedward
85b00d3c76 scripts: fix shellcheck 2026-02-22 22:13:57 -05:00
bbedward
bc4ad31d48 bluetooth: expose trust/untrust on devices 2026-02-22 22:10:07 -05:00
长夜月玩Fedora
71aad8ee32 Add support for 'evernight' distribution in Fedora (#1786) 2026-02-22 22:02:37 -05:00
Triệu Kha
8bb8231559 Fix dock visible when theres no app (#1797)
* 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
2026-02-22 21:56:33 -05:00
purian23
3cf9caae89 feat: DMS Greeter packaging for Debian/OpenSUSE on OBS 2026-02-22 15:44:42 -05:00
Lucas
f983c67135 zen: add more commands to detection (#1792) 2026-02-21 14:58:36 -05:00
bbedward
f2aef5b93f Merge branch 'master' of github.com:AvengeMedia/DankMaterialShell 2026-02-21 10:43:42 -05:00
purian23
46d4288969 ipc: Fix DankDash Wallpaper call 2026-02-21 00:52:08 -05:00
purian23
65516e872f theme: Fix Light/Dark mode portal sync 2026-02-21 00:31:26 -05:00
Connor Welsh
171329246c distro: add cups-pk-helper as suggested dependency (#1670) 2026-02-20 14:26:59 -05:00
bbedward
b2bee699e0 window rules: default to fixed for width/height
part of #1774
2026-02-20 13:51:03 -05:00
purian23
95c66b4d67 ubuntu: Fix dms-git Go versioning to restore builds 2026-02-20 13:12:12 -05:00
bbedward
babc8feb2b clipboard: fix memory leak from unbounded offer maps and unguarded file reads 2026-02-20 11:39:41 -05:00
bbedward
2f445c546a keybinds/niri: fix quote preservation 2026-02-20 11:37:02 -05:00
bbedward
a0283b3e3e dankdash: fix widgets across different bar section
fixes #1764s
2026-02-20 11:28:56 -05:00
bbedward
61bd156fb0 core/screenshot: light cleanups 2026-02-20 11:16:53 -05:00
Patrick Fischer
8ad0cf8e5f screensaver: emit ActiveChanged on lock/unlock (#1761) 2026-02-20 11:05:30 -05:00
Triệu Kha
ecd6d70da6 clipboard: improve image thumbnail (#1759)
- thumbnail image is now bigger
- circular mask has been replaced with rounded rectangular mask
2026-02-20 11:02:20 -05:00
purian23
359617d927 template: Default install method 2026-02-20 10:15:20 -05:00
purian23
38c286329a issues: Template fix 2026-02-20 09:57:58 -05:00
purian23
401b4095cc templates: Fix GitHub issue labels 2026-02-20 09:44:54 -05:00
shorinkiwata
06ab1a8ef0 feat(distros): allow CatOS to run DMS installer (#1768)
- This PR adds support for **CatOS**
- CatOS is fully compatible with Arch Linux
2026-02-20 09:28:42 -05:00
purian23
726fb8b015 templates: Update DMS issue formats 2026-02-20 09:26:20 -05:00
bbedward
b3b5c7a59f running apps: fix ordering on niri 2026-02-19 20:46:14 -05:00
purian23
d18f934978 fedora: Update trial run of bundled go deps 2026-02-19 19:21:01 -05:00
bbedward
e67f1f79bc launcher/dsearch: support for folder search and extra filters 2026-02-19 18:19:46 -05:00
purian23
e931829411 distros: Fix Go versioning on dms-git builds 2026-02-19 17:20:22 -05:00
bbedward
db8ebd606c launcher: fix premature exit of file search
fixes #1749
2026-02-19 16:46:50 -05:00
Jonas Bloch
072a358a94 Search keybinds fixes (#1748)
* fix: close keybind cheatsheet on escape press

* feat: match all space separated words in keybind cheatsheet search
2026-02-19 16:26:45 -05:00
bbedward
6ceb1b150c audio: fix hide device not working 2026-02-19 16:24:28 -05:00
bbedward
a4e03e1877 i18n: term sync 2026-02-19 13:32:06 -05:00
Youseffo13
02b3e4277b Added missing i18n strings and changed reset button (#1746)
* Update it.json

* Enhance SettingsSliderRow: add resetText property and update reset button styling

* added i18n strings

* adjust reset button width to be dynamic based on content size

* added i18n strings

* Update template.json

* reverted changes

* Update it.json

* Update template.json
2026-02-19 13:31:24 -05:00
bbedward
37daf801e6 dankbar: remove behaviors from monitoring widgets 2026-02-19 11:49:55 -05:00
bbedward
68d9f7eeb2 dgop: round computed values to match display format 2026-02-19 11:02:56 -05:00
bbedward
526e2420ca flake: fix dev flake for go 1.25 and ashellchheck 2026-02-19 09:30:05 -05:00
bbedward
a9cc58fc28 hyprland: add serial to output model generator 2026-02-19 09:22:10 -05:00
bbedward
77889ce1c6 dock: fix context menu styling
fixes #1742
2026-02-19 09:15:26 -05:00
bbedward
549073119e dock: fix transparency setting
fixes #1739
2026-02-19 09:11:30 -05:00
bbedward
5c5af5795f launcher: improve perf of settings search 2026-02-19 08:46:19 -05:00
bbedward
68e10934e4 launcher: always heuristic lookup cached entries 2026-02-18 23:47:00 -05:00
bbedward
c67bb1444a launcher v2: always heuristicLookup tab actions 2026-02-18 19:07:03 -05:00
bbedward
07389a152e i18n: term updates 2026-02-18 18:34:41 -05:00
bbedward
e562e21555 system tray: fix to take up 0 space when empty 2026-02-18 18:31:33 -05:00
Youseffo13
86dfe7dd3f Added Missing i18n strings (#1729)
* inverted dock visibility and position option

* added missing I18n strings

* added missing i18n strings

* added i18n strings

* Added missing i18n strings

* updated translations

* Update it.json
2026-02-18 18:28:57 -05:00
bbedward
ac0a8f3449 widgets: add openWith/toggleWith modes for dankbar widgets 2026-02-18 16:23:42 -05:00
bbedward
8e4a63db67 keybinds: fix escape in keybinds modal 2026-02-18 14:57:34 -05:00
bbedward
c02c63806f launcher v2: remove calc
cc: enhancements for plugins to size details
2026-02-18 14:48:20 -05:00
beluch-dev
42e5d7f6e9 fix: correct parameter name in Hyprland windowrule (no_initial_focus) (#1726)
##Description
This PR corrects the parameter name to match new Hyprland standard.

## Changes
-Before: 'noinitialfocus'
-After: 'no_initial_focus'
2026-02-18 13:41:12 -05:00
bbedward
d8cf1af422 plugins: fix settings focus loss 2026-02-18 13:36:23 -05:00
Evgeny Zemtsov
9723661c80 handle recycled server object IDs for workspace/group handles (#1725)
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.
2026-02-18 12:51:17 -05:00
bbedward
81cba7ad97 popout: decouple shadow from content layer 2026-02-18 10:45:45 -05:00
bbedward
c23f58de40 popout: disable layer after animation 2026-02-18 10:34:07 -05:00
purian23
2cf67ca7da notifications: Maintain shadow during expansion 2026-02-18 10:27:57 -05:00
purian23
392bd850ea notifications: Update initial popup height surfaces 2026-02-18 10:16:11 -05:00
bbedward
3b2ad9d1bd running apps: fix scroll events being propagated
fixes #1724
2026-02-18 10:12:15 -05:00
bbedward
27b7474180 matugen: make v4 detection more resilient 2026-02-18 09:55:45 -05:00
bbedward
63948d728e process list: fix scaling with fonts
fixes #1721
2026-02-18 09:55:45 -05:00
purian23
d219d3b873 dankinstall: Fix Debian ARM64 detection 2026-02-18 09:41:36 -05:00
bbedward
93ab290bc1 matugen: detect emacs directory
fixes #1720
2026-02-18 09:23:13 -05:00
bbedward
7335c5d79a osd: optimize bindings 2026-02-18 09:04:39 -05:00
bbedward
242ead722a screenshot: adjust cursor CLI option to be more explicit 2026-02-17 22:28:19 -05:00
bbedward
8a6d9696a8 settings: workaround crash 2026-02-17 22:20:01 -05:00
purian23
896b7ea242 notifications: Tweak animation scale & settings 2026-02-17 22:05:19 -05:00
bbedward
0c7f4c7828 settings: guard internal writes from watcher 2026-02-17 22:03:36 -05:00
bbedward
3d35af2a87 cc: fix plugin reloading in bar position changes 2026-02-17 17:24:22 -05:00
bbedward
fed3c36f84 popout: anchor height changing popout surfaces to top and bottom 2026-02-17 17:18:07 -05:00
bbedward
414d81aa40 workspaces: fix named workspace icons 2026-02-17 16:02:13 -05:00
bbedward
d548803769 dankinstall: no_anim on dms layers 2026-02-17 15:32:08 -05:00
bbedward
1180258394 system updater: fix hide no update option 2026-02-17 13:53:17 -05:00
bbedward
48a566a24b launcher: fix kb navigation not always showing last delegate in view 2026-02-17 13:07:43 -05:00
bbedward
3bc5d1df81 doctor: add qt6-imageformats check 2026-02-17 12:58:09 -05:00
bbedward
c7222e2e86 bump version, codename, disable changelog 2026-02-17 12:02:36 -05:00
50 changed files with 1510 additions and 265 deletions

View File

@@ -9,8 +9,8 @@ on:
type: choice
options:
- dms
- dms-git
- dms-greeter
- dms-git
- all
default: "dms"
rebuild_release:
@@ -119,9 +119,8 @@ jobs:
echo "🔄 Manual rebuild requested: $PKG (db$REBUILD)"
elif [[ "$PKG" == "all" ]]; then
# Check each package and build list of those needing updates
# Check each stable package and build list of those needing updates
PACKAGES_TO_UPDATE=()
check_dms_git && PACKAGES_TO_UPDATE+=("dms-git")
if check_dms_stable; then
PACKAGES_TO_UPDATE+=("dms")
if [[ -n "$LATEST_TAG" ]]; then
@@ -140,7 +139,7 @@ jobs:
else
echo "packages=" >> $GITHUB_OUTPUT
echo "has_updates=false" >> $GITHUB_OUTPUT
echo "✓ All packages up to date"
echo "✓ Both packages up to date"
fi
elif [[ "$PKG" == "dms-git" ]]; then
@@ -245,7 +244,7 @@ jobs:
fi
- name: Update dms-git spec version
if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
if: contains(steps.packages.outputs.packages, 'dms-git')
run: |
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD)
@@ -266,7 +265,7 @@ jobs:
} > distro/opensuse/dms-git.spec
- name: Update Debian dms-git changelog version
if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
if: contains(steps.packages.outputs.packages, 'dms-git')
run: |
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD)
@@ -389,7 +388,7 @@ jobs:
UPLOADED_PACKAGES=()
SKIPPED_PACKAGES=()
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
# PACKAGES can be space-separated list (e.g., "dms dms-greeter" from "all" check)
# Loop through each package and upload
for PKG in $PACKAGES; do
echo ""

View File

@@ -4,9 +4,15 @@ on:
workflow_dispatch:
inputs:
package:
description: "Package to upload (dms, dms-git, dms-greeter, or all)"
required: false
default: "dms-git"
description: "Package to upload"
required: true
type: choice
options:
- dms
- dms-greeter
- dms-git
- all
default: "dms"
rebuild_release:
description: "Release number for rebuilds (e.g., 2, 3, 4 for ppa2, ppa3, ppa4)"
required: false
@@ -139,7 +145,7 @@ jobs:
fi
else
# Fallback
echo "packages=dms-git" >> $GITHUB_OUTPUT
echo "packages=dms" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
fi
@@ -209,7 +215,7 @@ jobs:
echo "✓ Using rebuild release number: ppa$REBUILD_RELEASE"
fi
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
# PACKAGES can be space-separated list (e.g., "dms-git dms dms-greeter" from "all" check)
# Loop through each package and upload
for PKG in $PACKAGES; do
# Map package to PPA name

View File

@@ -16,6 +16,8 @@ require (
github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/yeqown/go-qrcode/v2 v2.2.5
github.com/yeqown/go-qrcode/writer/standard v1.3.0
github.com/yuin/goldmark v1.7.16
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
go.etcd.io/bbolt v1.4.3
@@ -32,15 +34,19 @@ require (
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/go-git/gcfg/v2 v2.0.2 // indirect
github.com/go-git/go-billy/v6 v6.0.0-20260209124918-37866f83c2d3 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.50.0 // indirect
)

View File

@@ -58,6 +58,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@@ -75,6 +77,8 @@ github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -115,6 +119,8 @@ github.com/pilebones/go-udev v0.9.1 h1:uN72M1C1fgzhsVmBGEM8w9RD1JY4iVsPZpr+Z6rb3
github.com/pilebones/go-udev v0.9.1/go.mod h1:Bgcl07crebF3JSeS4+nuaRvhWFdCeFoBhXXeAp93XNo=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -142,6 +148,12 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yeqown/go-qrcode/v2 v2.2.5 h1:HCOe2bSjkhZyYoyyNaXNzh4DJZll6inVJQQw+8228Zk=
github.com/yeqown/go-qrcode/v2 v2.2.5/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw=
github.com/yeqown/go-qrcode/writer/standard v1.3.0 h1:chdyhEfRtUPgQtuPeaWVGQ/TQx4rE1PqeoW3U+53t34=
github.com/yeqown/go-qrcode/writer/standard v1.3.0/go.mod h1:O4MbzsotGCvy8upYPCR91j81dr5XLT7heuljcNXW+oQ=
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=

View File

@@ -1062,6 +1062,62 @@ func (_c *MockBackend_GetWiFiNetworkDetails_Call) RunAndReturn(run func(string)
return _c
}
// GetWiFiQRCodeContent provides a mock function with given fields: ssid
func (_m *MockBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
ret := _m.Called(ssid)
if len(ret) == 0 {
panic("no return value specified for GetWiFiQRCodeContent")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
return rf(ssid)
}
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(ssid)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(ssid)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockBackend_GetWiFiQRCodeContent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWiFiQRCodeContent'
type MockBackend_GetWiFiQRCodeContent_Call struct {
*mock.Call
}
// GetWiFiQRCodeContent is a helper method to define mock.On call
// - ssid string
func (_e *MockBackend_Expecter) GetWiFiQRCodeContent(ssid interface{}) *MockBackend_GetWiFiQRCodeContent_Call {
return &MockBackend_GetWiFiQRCodeContent_Call{Call: _e.mock.On("GetWiFiQRCodeContent", ssid)}
}
func (_c *MockBackend_GetWiFiQRCodeContent_Call) Run(run func(ssid string)) *MockBackend_GetWiFiQRCodeContent_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *MockBackend_GetWiFiQRCodeContent_Call) Return(_a0 string, _a1 error) *MockBackend_GetWiFiQRCodeContent_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockBackend_GetWiFiQRCodeContent_Call) RunAndReturn(run func(string) (string, error)) *MockBackend_GetWiFiQRCodeContent_Call {
_c.Call.Return(run)
return _c
}
// GetWiredConnections provides a mock function with no fields
func (_m *MockBackend) GetWiredConnections() ([]network.WiredConnection, error) {
ret := _m.Called()

View File

@@ -10,6 +10,7 @@ type Backend interface {
ScanWiFi() error
ScanWiFiDevice(device string) error
GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
GetWiFiQRCodeContent(ssid string) (string, error)
GetWiFiDevices() []WiFiDevice
ConnectWiFi(req ConnectionRequest) error

View File

@@ -111,6 +111,10 @@ func (b *HybridIwdNetworkdBackend) GetWiFiNetworkDetails(ssid string) (*NetworkI
return b.wifi.GetWiFiNetworkDetails(ssid)
}
func (b *HybridIwdNetworkdBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
return b.wifi.GetWiFiQRCodeContent(ssid)
}
func (b *HybridIwdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
if err := b.wifi.ConnectWiFi(req); err != nil {
return err

View File

@@ -1,6 +1,9 @@
package network
import "fmt"
import (
"fmt"
"os"
)
func (b *IWDBackend) GetWiredConnections() ([]WiredConnection, error) {
return nil, fmt.Errorf("wired connections not supported by iwd")
@@ -112,3 +115,19 @@ func (b *IWDBackend) getWiFiDevicesLocked() []WiFiDevice {
Networks: b.state.WiFiNetworks,
}}
}
func (b *IWDBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
path := iwdConfigPath(ssid)
data, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("no saved iwd config for `%s`: %w", ssid, err)
}
passphrase, err := parseIWDPassphrase(string(data))
if err != nil {
return "", fmt.Errorf("failed to read passphrase for `%s`: %w", ssid, err)
}
return FormatWiFiQRString("WPA", ssid, passphrase), nil
}

View File

@@ -18,6 +18,10 @@ func (b *SystemdNetworkdBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInf
return nil, fmt.Errorf("WiFi details not supported by networkd backend")
}
func (b *SystemdNetworkdBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
return "", fmt.Errorf("WiFi QR Code not supported by networkd backend")
}
func (b *SystemdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
return fmt.Errorf("WiFi connect not supported by networkd backend")
}

View File

@@ -196,6 +196,65 @@ func (b *NetworkManagerBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfo
}, nil
}
func (b *NetworkManagerBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
conn, err := b.findConnection(ssid)
if err != nil {
return "", fmt.Errorf("no saved connection for `%s`: %w", ssid, err)
}
connSettings, err := conn.GetSettings()
if err != nil {
return "", fmt.Errorf("failed to get settings for `%s`: %w", ssid, err)
}
secSettings, ok := connSettings["802-11-wireless-security"]
if !ok {
return "", fmt.Errorf("network `%s` has no security settings", ssid)
}
keyMgmt, ok := secSettings["key-mgmt"].(string)
if !ok {
return "", fmt.Errorf("failed to identify security type of network `%s`", ssid)
}
var securityType string
switch keyMgmt {
case "none":
authAlg, _ := secSettings["auth-alg"].(string)
switch authAlg {
case "open":
securityType = "nopass"
default:
securityType = "WEP"
}
case "ieee8021x":
securityType = "WEP"
default:
securityType = "WPA"
}
if securityType != "WPA" {
return "", fmt.Errorf("QR code generation only supports WPA connections, `%s` uses %s", ssid, securityType)
}
secrets, err := conn.GetSecrets("802-11-wireless-security")
if err != nil {
return "", fmt.Errorf("failed to retrieve connection secrets for `%s`: %w", ssid, err)
}
secSecrets, ok := secrets["802-11-wireless-security"]
if !ok {
return "", fmt.Errorf("failed to retrieve password for `%s`", ssid)
}
psk, ok := secSecrets["psk"].(string)
if !ok {
return "", fmt.Errorf("failed to retrieve password for `%s`", ssid)
}
return FormatWiFiQRString(securityType, ssid, psk), nil
}
func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
devInfo, err := b.getWifiDeviceForConnection(req.Device)
if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net"
"os"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
@@ -40,6 +41,10 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
handleSetPreference(conn, req, manager)
case "network.info":
handleGetNetworkInfo(conn, req, manager)
case "network.qrcode":
handleGetNetworkQRCode(conn, req, manager)
case "network.delete-qrcode":
handleDeleteQRCode(conn, req, manager)
case "network.ethernet.info":
handleGetWiredNetworkInfo(conn, req, manager)
case "network.subscribe":
@@ -320,6 +325,42 @@ func handleGetNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
models.Respond(conn, req.ID, network)
}
func handleGetNetworkQRCode(conn net.Conn, req models.Request, manager *Manager) {
ssid, err := params.String(req.Params, "ssid")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
content, err := manager.GetNetworkQRCode(ssid)
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, content)
}
func handleDeleteQRCode(conn net.Conn, req models.Request, _ *Manager) {
path, err := params.String(req.Params, "path")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
if !isValidQRCodePath(path) {
models.RespondError(conn, req.ID, "invalid QR code path")
return
}
if err := os.Remove(path); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "QR code file deleted"})
}
func handleGetWiredNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
uuid, err := params.String(req.Params, "uuid")
if err != nil {

View File

@@ -6,6 +6,8 @@ import (
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
)
func NewManager() (*Manager, error) {
@@ -438,6 +440,43 @@ func (m *Manager) GetNetworkInfoDetailed(ssid string) (*NetworkInfoResponse, err
return m.backend.GetWiFiNetworkDetails(ssid)
}
func (m *Manager) GetNetworkQRCode(ssid string) ([2]string, error) {
content, err := m.backend.GetWiFiQRCodeContent(ssid)
if err != nil {
return [2]string{}, err
}
qrc, err := qrcode.New(content)
if err != nil {
return [2]string{}, fmt.Errorf("failed to create QR code for `%s`: %w", ssid, err)
}
pathThemed, pathNormal := qrCodePaths(ssid)
wThemed, err := standard.New(
pathThemed,
standard.WithBuiltinImageEncoder(standard.PNG_FORMAT),
standard.WithBgTransparent(),
standard.WithFgColorRGBHex("#ffffff"),
)
if err != nil {
return [2]string{}, fmt.Errorf("failed to create QR code writer: %w", err)
}
if err := qrc.Save(wThemed); err != nil {
return [2]string{}, fmt.Errorf("failed to save QR code for `%s`: %w", ssid, err)
}
wNormal, err := standard.New(pathNormal, standard.WithBuiltinImageEncoder(standard.PNG_FORMAT))
if err != nil {
return [2]string{}, fmt.Errorf("failed to create QR code writer: %w", err)
}
if err := qrc.Save(wNormal); err != nil {
return [2]string{}, fmt.Errorf("failed to save QR code for `%s`: %w", ssid, err)
}
return [2]string{pathThemed, pathNormal}, nil
}
func (m *Manager) ToggleWiFi() error {
enabled, err := m.backend.GetWiFiEnabled()
if err != nil {

View File

@@ -0,0 +1,59 @@
package network
import (
"fmt"
"path/filepath"
"regexp"
"strings"
)
const qrCodeTmpPrefix = "/tmp/dank-wifi-qrcode-"
func FormatWiFiQRString(securityType, ssid, password string) string {
return fmt.Sprintf("WIFI:T:%s;S:%s;P:%s;;", securityType, ssid, password)
}
func qrCodePaths(ssid string) (themed, normal string) {
safe := sanitizeSSIDForPath(ssid)
themed = fmt.Sprintf("%s%s-themed.png", qrCodeTmpPrefix, safe)
normal = fmt.Sprintf("%s%s-normal.png", qrCodeTmpPrefix, safe)
return
}
func isValidQRCodePath(path string) bool {
clean := filepath.Clean(path)
return strings.HasPrefix(clean, qrCodeTmpPrefix) && strings.HasSuffix(clean, ".png")
}
var safePathChar = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
func sanitizeSSIDForPath(ssid string) string {
return safePathChar.ReplaceAllString(ssid, "_")
}
var iwdVerbatimSSID = regexp.MustCompile(`^[a-zA-Z0-9 _-]+$`)
func iwdConfigPath(ssid string) string {
switch {
case iwdVerbatimSSID.MatchString(ssid):
return fmt.Sprintf("/var/lib/iwd/%s.psk", ssid)
default:
return fmt.Sprintf("/var/lib/iwd/=%x.psk", []byte(ssid))
}
}
func parseIWDPassphrase(data string) (string, error) {
inSecurity := false
for _, line := range strings.Split(data, "\n") {
line = strings.TrimSpace(line)
switch {
case line == "[Security]":
inSecurity = true
case strings.HasPrefix(line, "["):
inSecurity = false
case inSecurity && strings.HasPrefix(line, "Passphrase="):
return strings.TrimPrefix(line, "Passphrase="), nil
}
}
return "", fmt.Errorf("no passphrase found in iwd config")
}

View File

@@ -27,12 +27,12 @@ override_dh_auto_build:
# Verify core directory exists (native package format has source at root)
test -d core || (echo "ERROR: core directory not found!" && exit 1)
# Patch go.mod to use Go 1.24 base version (Debian 13 has 1.23.x, may vary)
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' core/go.mod
# Pin go.mod and vendor/modules.txt to the installed Go toolchain version
GO_INSTALLED=$$(go version | grep -oP 'go\K[0-9]+\.[0-9]+'); \
sed -i "s/^go [0-9]\+\.[0-9]\+\(\.[0-9]*\)\?$$/go $${GO_INSTALLED}/" core/go.mod; \
sed -i "s/^\(## explicit; go \)[0-9]\+\.[0-9]\+\(\.[0-9]*\)\?$$/\1$${GO_INSTALLED}/" core/vendor/modules.txt
# Build dms-cli from source using vendored dependencies
# Extract version info and build in single shell to preserve variables
# Architecture mapping: Debian amd64/arm64 -> Makefile amd64/arm64
# Build dms-cli (single shell to preserve variables; arch: Debian amd64/arm64 -> Makefile amd64/arm64)
VERSION="$(UPSTREAM_VERSION)"; \
COMMIT=$$(echo "$(UPSTREAM_VERSION)" | grep -oP '(?<=git)[0-9]+\.[a-f0-9]+' | cut -d. -f2 | head -c8 || echo "unknown"); \
if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \

View File

@@ -3,7 +3,7 @@
<service name="download_url">
<param name="protocol">https</param>
<param name="host">github.com</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.4.2/dms-qml.tar.gz</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.4.3/dms-qml.tar.gz</param>
<param name="filename">dms-qml.tar.gz</param>
</service>
</services>

View File

@@ -1,6 +1,5 @@
dms-greeter (1.4.2db8) unstable; urgency=medium
dms-greeter (1.4.3db1) unstable; urgency=medium
* Initial Debian OBS package
* Port from Ubuntu/Fedora packaging
* Update to v1.4.3 stable release
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 21 Feb 2026 00:00:00 +0000
-- Avenge Media <AvengeMedia.US@gmail.com> Tue, 25 Feb 2026 02:40:00 +0000

View File

@@ -3,6 +3,7 @@
%global debug_package %{nil}
%global version {{{ git_repo_version }}}
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
%global go_toolchain_version 1.25.7
Name: dms
Epoch: 2
@@ -14,12 +15,12 @@ License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}}
Source1: https://go.dev/dl/go%{go_toolchain_version}.linux-amd64.tar.gz
Source2: https://go.dev/dl/go%{go_toolchain_version}.linux-arm64.tar.gz
BuildRequires: git-core
BuildRequires: gzip
BuildRequires: golang >= 1.24
BuildRequires: make
BuildRequires: wget
BuildRequires: systemd-rpm-macros
# Core requirements
@@ -28,7 +29,7 @@ Requires: accountsservice
Requires: dms-cli = %{epoch}:%{version}-%{release}
Requires: dgop
# Core utilities (Highly recommended for DMS functionality)
# Core utilities (Recommended for DMS functionality)
Recommends: cava
Recommends: danksearch
Recommends: matugen
@@ -66,6 +67,28 @@ Provides native DBus bindings, NetworkManager integration, and system utilities.
VERSION="%{version}"
COMMIT=$(echo "%{version}" | grep -oP '[a-f0-9]{7,}' | head -n1 || echo "unknown")
# Use pinned bundled Go toolchain (deterministic across chroots)
case "%{_arch}" in
x86_64)
GO_TARBALL="%{_sourcedir}/go%{go_toolchain_version}.linux-amd64.tar.gz"
;;
aarch64)
GO_TARBALL="%{_sourcedir}/go%{go_toolchain_version}.linux-arm64.tar.gz"
;;
*)
echo "Unsupported architecture for bundled Go: %{_arch}"
exit 1
;;
esac
rm -rf .go
tar -xzf "$GO_TARBALL"
mv go .go
export GOROOT="$PWD/.go"
export PATH="$GOROOT/bin:$PATH"
export GOTOOLCHAIN=local
go version
cd core
make dist VERSION="$VERSION" COMMIT="$COMMIT"

View File

@@ -56,8 +56,10 @@ mkdir -p $HOME $GOCACHE $GOMODCACHE
# OBS has no network access, so use local toolchain only
export GOTOOLCHAIN=local
# Patch go.mod to use base Go version (e.g., go 1.24 instead of go 1.24.6)
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' core/go.mod
# Pin go.mod and vendor/modules.txt to the installed Go toolchain version
GO_INSTALLED=$(go version | grep -oP 'go\K[0-9]+\.[0-9]+')
sed -i "s/^go [0-9]\+\.[0-9]\+\(\.[0-9]*\)\?$/go ${GO_INSTALLED}/" core/go.mod
sed -i "s/^\(## explicit; go \)[0-9]\+\.[0-9]\+\(\.[0-9]*\)\?$/\1${GO_INSTALLED}/" core/vendor/modules.txt
# Extract version info for embedding in binary
VERSION="%{version}"

View File

@@ -419,6 +419,9 @@ if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ -f "distro/opensuse/$PACKAGE.spec" ]];
sed -i "s/VERSION_PLACEHOLDER/${DMS_GREETER_BASE_VERSION}/g" "$WORK_DIR/$PACKAGE.spec"
sed -i "s/RELEASE_PLACEHOLDER/${DMS_GREETER_RELEASE}/g" "$WORK_DIR/$PACKAGE.spec"
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" "$WORK_DIR/$PACKAGE.spec"
# Explicitly set Version:/Release: in case the spec uses %{version} macro
sed -i "s/^Version:.*/Version: ${DMS_GREETER_BASE_VERSION}/" "$WORK_DIR/$PACKAGE.spec"
sed -i "s/^Release:.*/Release: ${DMS_GREETER_RELEASE}%{?dist}/" "$WORK_DIR/$PACKAGE.spec"
fi
if [[ -f "$WORK_DIR/.osc/$PACKAGE.spec" ]]; then
@@ -813,6 +816,9 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
sed -i "s/VERSION_PLACEHOLDER/${DMS_GREETER_BASE_VERSION}/g" "$WORK_DIR/$PACKAGE.spec"
sed -i "s/RELEASE_PLACEHOLDER/${DMS_GREETER_RELEASE}/g" "$WORK_DIR/$PACKAGE.spec"
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" "$WORK_DIR/$PACKAGE.spec"
# Explicitly set Version:/Release: in case the spec uses %{version} macro
sed -i "s/^Version:.*/Version: ${DMS_GREETER_BASE_VERSION}/" "$WORK_DIR/$PACKAGE.spec"
sed -i "s/^Release:.*/Release: ${DMS_GREETER_RELEASE}%{?dist}/" "$WORK_DIR/$PACKAGE.spec"
fi
fi

View File

@@ -182,6 +182,7 @@
with pkgs;
[
go_1_25
go-mockery_2
gopls
delve
go-tools

View File

@@ -1 +1 @@
Saffron Bloom
The Wolverine

View File

@@ -365,6 +365,23 @@ Item {
}
}
LazyLoader {
id: wifiQRCodeModalLoader
active: false
Component.onCompleted: {
PopoutService.wifiQRCodeModalLoader = wifiQRCodeModalLoader;
}
WifiQRCodeModal {
id: wifiQRCodeModalItem
Component.onCompleted: {
PopoutService.wifiQRCodeModal = wifiQRCodeModalItem;
}
}
}
LazyLoader {
id: polkitAuthModalLoader
active: false

View File

@@ -162,6 +162,11 @@ Item {
}
]
property string fileSearchType: "all"
property string fileSearchExt: ""
property string fileSearchFolder: ""
property string fileSearchSort: "score"
property string pluginFilter: ""
property string activePluginName: ""
property var activePluginCategories: []
@@ -346,6 +351,10 @@ Item {
previousSearchMode = "all";
autoSwitchedToFiles = false;
isFileSearching = false;
fileSearchType = "all";
fileSearchExt = "";
fileSearchFolder = "";
fileSearchSort = "score";
sections = [];
flatModel = [];
selectedFlatIndex = 0;
@@ -399,6 +408,34 @@ Item {
performSearch();
}
function setFileSearchType(type) {
if (fileSearchType === type)
return;
fileSearchType = type;
performFileSearch();
}
function setFileSearchExt(ext) {
if (fileSearchExt === ext)
return;
fileSearchExt = ext;
performFileSearch();
}
function setFileSearchFolder(folder) {
if (fileSearchFolder === folder)
return;
fileSearchFolder = folder;
performFileSearch();
}
function setFileSearchSort(sort) {
if (fileSearchSort === sort)
return;
fileSearchSort = sort;
performFileSearch();
}
function clearPluginFilter() {
if (pluginFilter) {
pluginFilter = "";
@@ -827,10 +864,20 @@ Item {
var params = {
limit: 20,
fuzzy: true,
sort: "score",
sort: fileSearchSort || "score",
desc: true
};
if (DSearchService.supportsTypeFilter) {
params.type = (fileSearchType && fileSearchType !== "all") ? fileSearchType : "all";
}
if (fileSearchExt) {
params.ext = fileSearchExt;
}
if (fileSearchFolder) {
params.folder = fileSearchFolder;
}
DSearchService.search(fileQuery, params, function (response) {
isFileSearching = false;
if (response.error)
@@ -840,34 +887,73 @@ Item {
for (var i = 0; i < hits.length; i++) {
var hit = hits[i];
var docTypes = hit.locations?.doc_type;
var isDir = docTypes ? !!docTypes["dir"] : false;
fileItems.push(transformFileResult({
path: hit.id || "",
score: hit.score || 0
score: hit.score || 0,
is_dir: isDir
}));
}
var fileSection = {
id: "files",
title: I18n.tr("Files"),
icon: "folder",
priority: 4,
items: fileItems,
collapsed: collapsedSections["files"] || false,
flatStartIndex: 0
};
var fileSections = [];
var showType = fileSearchType || "all";
if (showType === "all" && DSearchService.supportsTypeFilter) {
var onlyFiles = [];
var onlyDirs = [];
for (var j = 0; j < fileItems.length; j++) {
if (fileItems[j].data?.is_dir)
onlyDirs.push(fileItems[j]);
else
onlyFiles.push(fileItems[j]);
}
if (onlyFiles.length > 0) {
fileSections.push({
id: "files",
title: I18n.tr("Files"),
icon: "insert_drive_file",
priority: 4,
items: onlyFiles,
collapsed: collapsedSections["files"] || false,
flatStartIndex: 0
});
}
if (onlyDirs.length > 0) {
fileSections.push({
id: "folders",
title: I18n.tr("Folders"),
icon: "folder",
priority: 4.1,
items: onlyDirs,
collapsed: collapsedSections["folders"] || false,
flatStartIndex: 0
});
}
} else {
var filesIcon = showType === "dir" ? "folder" : showType === "file" ? "insert_drive_file" : "folder";
var filesTitle = showType === "dir" ? I18n.tr("Folders") : I18n.tr("Files");
if (fileItems.length > 0) {
fileSections.push({
id: "files",
title: filesTitle,
icon: filesIcon,
priority: 4,
items: fileItems,
collapsed: collapsedSections["files"] || false,
flatStartIndex: 0
});
}
}
var newSections;
if (searchMode === "files") {
newSections = fileItems.length > 0 ? [fileSection] : [];
newSections = fileSections;
} else {
var existingNonFile = sections.filter(function (s) {
return s.id !== "files";
return s.id !== "files" && s.id !== "folders";
});
if (fileItems.length > 0) {
newSections = existingNonFile.concat([fileSection]);
} else {
newSections = existingNonFile;
}
newSections = existingNonFile.concat(fileSections);
}
newSections.sort(function (a, b) {
return a.priority - b.priority;
@@ -913,7 +999,7 @@ Item {
}
function transformFileResult(file) {
return Transform.transformFileResult(file, I18n.tr("Open"), I18n.tr("Open folder"), I18n.tr("Copy path"));
return Transform.transformFileResult(file, I18n.tr("Open"), I18n.tr("Open folder"), I18n.tr("Copy path"), I18n.tr("Open in terminal"));
}
function detectTrigger(query) {
@@ -1581,6 +1667,9 @@ Item {
case "copy_path":
copyToClipboard(item.data.path);
break;
case "open_terminal":
openTerminal(item.data.path);
break;
case "copy":
copyToClipboard(item.name);
break;
@@ -1662,6 +1751,16 @@ Item {
Qt.openUrlExternally("file://" + folder);
}
function openTerminal(path) {
if (!path)
return;
var terminal = Quickshell.env("TERMINAL") || "xterm";
Quickshell.execDetached({
command: [terminal],
workingDirectory: path
});
}
function copyToClipboard(text) {
if (!text)
return;

View File

@@ -107,6 +107,10 @@ Item {
spotlightContent.controller.activePluginId = "";
spotlightContent.controller.activePluginName = "";
spotlightContent.controller.pluginFilter = "";
spotlightContent.controller.fileSearchType = "all";
spotlightContent.controller.fileSearchExt = "";
spotlightContent.controller.fileSearchFolder = "";
spotlightContent.controller.fileSearchSort = "score";
spotlightContent.controller.collapsedSections = {};
spotlightContent.controller.selectedFlatIndex = 0;
spotlightContent.controller.selectedItem = null;

View File

@@ -116,31 +116,43 @@ function transformBuiltInLauncherItem(item, pluginId, openLabel) {
};
}
function transformFileResult(file, openLabel, openFolderLabel, copyPathLabel) {
function transformFileResult(file, openLabel, openFolderLabel, copyPathLabel, openTerminalLabel) {
var filename = file.path ? file.path.split("/").pop() : "";
var dirname = file.path ? file.path.substring(0, file.path.lastIndexOf("/")) : "";
var isDir = file.is_dir || false;
var actions = [];
if (isDir) {
if (openTerminalLabel) {
actions.push({
name: openTerminalLabel,
icon: "terminal",
action: "open_terminal"
});
}
} else {
actions.push({
name: openFolderLabel,
icon: "folder_open",
action: "open_folder"
});
}
actions.push({
name: copyPathLabel,
icon: "content_copy",
action: "copy_path"
});
return {
id: file.path || "",
type: "file",
name: filename,
subtitle: dirname,
icon: Utils.getFileIcon(filename),
icon: isDir ? "folder" : Utils.getFileIcon(filename),
iconType: "material",
section: "files",
data: file,
actions: [
{
name: openFolderLabel,
icon: "folder_open",
action: "open_folder"
},
{
name: copyPathLabel,
icon: "content_copy",
action: "copy_path"
}
],
actions: actions,
primaryAction: {
name: openLabel,
icon: "open_in_new",

View File

@@ -549,8 +549,151 @@ FocusScope {
}
Item {
id: fileFilterRow
width: parent.width
height: parent.height - searchField.height - categoryRow.height - actionPanel.height - Theme.spacingXS * (categoryRow.visible ? 3 : 2)
height: showFileFilters ? fileFilterContent.height : 0
visible: showFileFilters
readonly property bool showFileFilters: controller.searchMode === "files"
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Row {
id: fileFilterContent
width: parent.width
spacing: Theme.spacingS
Row {
id: typeChips
anchors.verticalCenter: parent.verticalCenter
spacing: 2
visible: DSearchService.supportsTypeFilter
Repeater {
model: [
{
id: "all",
label: I18n.tr("All"),
icon: "search"
},
{
id: "file",
label: I18n.tr("Files"),
icon: "insert_drive_file"
},
{
id: "dir",
label: I18n.tr("Folders"),
icon: "folder"
}
]
Rectangle {
required property var modelData
required property int index
width: chipContent.width + Theme.spacingM * 2
height: sortDropdown.height
radius: Theme.cornerRadius
color: controller.fileSearchType === modelData.id || chipArea.containsMouse ? Theme.primaryContainer : "transparent"
Row {
id: chipContent
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: modelData.icon
size: 14
color: controller.fileSearchType === modelData.id ? Theme.primary : Theme.surfaceVariantText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: modelData.label
font.pixelSize: Theme.fontSizeSmall
color: controller.fileSearchType === modelData.id ? Theme.primary : Theme.surfaceText
}
}
MouseArea {
id: chipArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: controller.setFileSearchType(modelData.id)
}
}
}
}
Rectangle {
width: 1
height: 20
anchors.verticalCenter: parent.verticalCenter
color: Theme.outlineMedium
visible: typeChips.visible
}
DankDropdown {
id: sortDropdown
anchors.verticalCenter: parent.verticalCenter
width: Math.min(130, parent.width / 3)
compactMode: true
dropdownWidth: 130
popupWidth: 150
maxPopupHeight: 200
currentValue: {
switch (controller.fileSearchSort) {
case "score":
return I18n.tr("Score");
case "name":
return I18n.tr("Name");
case "modified":
return I18n.tr("Modified");
case "size":
return I18n.tr("Size");
default:
return I18n.tr("Score");
}
}
options: [I18n.tr("Score"), I18n.tr("Name"), I18n.tr("Modified"), I18n.tr("Size")]
onValueChanged: value => {
var sortMap = {};
sortMap[I18n.tr("Score")] = "score";
sortMap[I18n.tr("Name")] = "name";
sortMap[I18n.tr("Modified")] = "modified";
sortMap[I18n.tr("Size")] = "size";
controller.setFileSearchSort(sortMap[value] || "score");
}
}
DankTextField {
id: extFilterField
anchors.verticalCenter: parent.verticalCenter
width: Math.min(100, parent.width / 4)
height: sortDropdown.height
placeholderText: I18n.tr("ext")
font.pixelSize: Theme.fontSizeSmall
showClearButton: text.length > 0
onTextChanged: {
controller.setFileSearchExt(text.trim());
}
}
}
}
Item {
width: parent.width
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
opacity: root.parentModal?.isClosing ? 0 : 1
ResultsList {
@@ -586,6 +729,9 @@ FocusScope {
function onSearchQueryRequested(query) {
searchField.text = query;
}
function onModeChanged() {
extFilterField.text = "";
}
}
FocusScope {

View File

@@ -113,6 +113,7 @@ Rectangle {
font.family: Theme.fontFamily
color: Theme.surfaceVariantText
elide: Text.ElideRight
clip: true
visible: (root.item?.subtitle ?? "").length > 0
horizontalAlignment: Text.AlignLeft
}
@@ -181,7 +182,7 @@ Rectangle {
case "plugin":
return I18n.tr("Plugin");
case "file":
return I18n.tr("File");
return root.item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
default:
return "";
}

View File

@@ -435,7 +435,15 @@ Item {
var mode = root.controller?.searchMode ?? "all";
switch (mode) {
case "files":
return "folder_open";
var fileType = root.controller?.fileSearchType ?? "all";
switch (fileType) {
case "dir":
return "folder_open";
case "file":
return "insert_drive_file";
default:
return "folder_open";
}
case "plugins":
return "extension";
case "apps":
@@ -465,7 +473,15 @@ Item {
return I18n.tr("Type to search files");
if (root.controller.searchQuery.length < 2)
return I18n.tr("Type at least 2 characters");
return I18n.tr("No files found");
var fileType = root.controller?.fileSearchType ?? "all";
switch (fileType) {
case "dir":
return I18n.tr("No folders found");
case "file":
return I18n.tr("No files found");
default:
return I18n.tr("No results found");
}
case "plugins":
return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins");
case "apps":

View File

@@ -0,0 +1,170 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import Quickshell
import Quickshell.Io
import qs.Modals.Common
import qs.Modals.FileBrowser
import qs.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
visible: false
layerNamespace: "dms:wifi-qrcode"
property bool disablePopupTransparency: true
property string wifiSSID: ""
property string themedQrCodePath: ""
property string normalQrCodePath: ""
modalWidth: 420
modalHeight: 480
onBackgroundClicked: hide()
onOpened: {
Qt.callLater(() => {
modalFocusScope.forceActiveFocus();
contentLoader.item.wifiSSID = wifiSSID;
contentLoader.item.themedQrCodePath = themedQrCodePath;
contentLoader.item.saveBrowserLoader = saveBrowserLoader;
});
}
function show(ssid) {
wifiSSID = ssid;
fetchNetworkQRCode(ssid);
}
function hide() {
if (themedQrCodePath !== "") {
deleteQRCodeFile(themedQrCodePath);
}
if (normalQrCodePath !== "") {
deleteQRCodeFile(normalQrCodePath);
}
close();
}
function fetchNetworkQRCode(ssid) {
// TODO: Add loading UI?
DMSService.sendRequest("network.qrcode", {
ssid: ssid
}, response => {
if (response.error) {
ToastService.showError("Failed to fetch network QR code: ", JSON.stringify(response.error));
} else if (response.result) {
themedQrCodePath = response.result[0];
normalQrCodePath = response.result[1];
open();
}
});
}
function deleteQRCodeFile(path) {
DMSService.sendRequest("network.delete-qrcode", {
path: path
}, response => {
if (response.error) {
ToastService.showError(`Failed to remove QR code at ${path}: `, JSON.stringify(response.error));
}
})
}
LazyLoader {
id: saveBrowserLoader
active: false
FileBrowserSurfaceModal {
id: saveBrowser
browserTitle: I18n.tr("Save QR Code")
browserIcon: "qr_code"
browserType: "default"
fileExtensions: ["*.png"]
allowStacking: true
saveMode: true
defaultFileName: `${root.wifiSSID ?? "wifi-qrcode"}.png`
onFileSelected: path => {
const cleanPath = decodeURI(path.toString().replace(/^file:\/\//, ''));
const fileName = cleanPath.split('/').pop();
const fileUrl = "file://" + cleanPath;
copyQrCodeProcess.exec(["cp", root.normalQrCodePath, cleanPath, "-f"])
}
Process {
id: copyQrCodeProcess
stdout: StdioCollector {
onStreamFinished: {
saveBrowser.close();
}
}
}
}
}
content: Component {
Item {
id: theItem
property alias themedQrCodePath: qrCodeImg.source
property var saveBrowserLoader: null
property string wifiSSID: ""
anchors.fill: parent
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
RowLayout {
id: modalTitle
width: parent.width
StyledText {
text: I18n.tr("WiFi QR code for ") + theItem.wifiSSID
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Bold
Layout.alignment: Qt.AlignLeft
}
DankActionButton {
iconName: "save"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: {
saveBrowserLoader.active = true;
if (saveBrowserLoader.item) {
saveBrowserLoader.item.open();
}
}
Layout.alignment: Qt.AlignRight
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: root.hide()
Layout.alignment: Qt.AlignRight
}
}
Image {
id: qrCodeImg
height: parent.height - parent.spacing - modalTitle.height
width: height
anchors.horizontalCenter: parent.horizontalCenter
MultiEffect {
source: qrCodeImg
anchors.fill: source
colorization: 1.0
colorizationColor: Theme.primary
}
}
}
}
}
}

View File

@@ -651,6 +651,7 @@ Rectangle {
}
Rectangle {
id: pinButton
anchors.right: parent.right
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
@@ -711,6 +712,19 @@ Rectangle {
}
}
DankActionButton {
id: qrCodeButton
visible: modelData.secured && modelData.saved
anchors.right: parent.right
anchors.rightMargin: optionsButton.width + pinWifiRow.width + 3 * Theme.spacingM + Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
iconName: "qr_code"
buttonSize: 28
onClicked: {
PopoutService.showWifiQRCodeModal(modelData.ssid);
}
}
DankRipple {
id: wifiRipple
cornerRadius: parent.radius
@@ -719,7 +733,7 @@ Rectangle {
MouseArea {
id: networkMouseArea
anchors.fill: parent
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS + pinWifiRow.width + Theme.spacingS * 4
anchors.rightMargin: optionsButton.width + pinWifiRow.width + (qrCodeButton.visible ? qrCodeButton.width : 0) + Theme.spacingS * 5 + Theme.spacingM
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => wifiRipple.trigger(mouse.x, mouse.y)

View File

@@ -624,7 +624,7 @@ PanelWindow {
Item {
id: topBarCore
anchors.fill: parent
layer.enabled: true
layer.enabled: false
property bool autoHide: barConfig?.autoHide ?? false
property bool revealSticky: false

View File

@@ -167,9 +167,22 @@ DankPopout {
}
Column {
id: headerInfoColumn
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
width: parent.width - Theme.iconSizeLarge - 32 - Theme.spacingM * 2
readonly property string timeInfoText: {
if (!BatteryService.batteryAvailable)
return "Power profile management available";
const time = BatteryService.formatTimeRemaining();
if (time !== "Unknown") {
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`;
}
return "";
}
readonly property bool showPowerRate: BatteryService.batteryAvailable && Math.abs(BatteryService.changeRate) > 0.05
readonly property bool isOnAC: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn)
readonly property bool isDischarging: BatteryService.batteryAvailable && !BatteryService.isCharging && !BatteryService.isPluggedIn
Row {
spacing: Theme.spacingS
@@ -207,21 +220,35 @@ DankPopout {
}
}
StyledText {
text: {
if (!BatteryService.batteryAvailable)
return "Power profile management available";
const time = BatteryService.formatTimeRemaining();
if (time !== "Unknown") {
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`;
}
return "";
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
visible: text.length > 0
elide: Text.ElideRight
Row {
width: parent.width
spacing: Theme.spacingS
visible: headerInfoColumn.timeInfoText.length > 0
StyledText {
id: powerRateText
text: `${headerInfoColumn.isOnAC ? "+" : (headerInfoColumn.isDischarging ? "-" : "")}${Math.abs(BatteryService.changeRate).toFixed(1)}W`
font.pixelSize: Theme.fontSizeSmall
color: {
if (headerInfoColumn.isOnAC) {
return Theme.primary;
}
if (headerInfoColumn.isDischarging) {
return Theme.warning;
}
return Theme.surfaceTextMedium;
}
font.weight: Font.Medium
visible: headerInfoColumn.showPowerRate
}
StyledText {
text: headerInfoColumn.timeInfoText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
elide: Text.ElideRight
width: parent.width - (powerRateText.visible ? (powerRateText.implicitWidth + parent.spacing) : 0)
}
}
}

View File

@@ -29,12 +29,21 @@ Loader {
readonly property bool orientationMatches: (axis?.isVertical ?? false) === isInColumn
readonly property bool widgetEnabled: widgetData?.enabled !== false
active: orientationMatches && getWidgetVisible(widgetId, DgopService.dgopAvailable) && (widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: getWidgetComponent(widgetId, components)
opacity: getWidgetEnabled(widgetData?.enabled) ? 1 : 0
signal contentItemReady(var item)
Binding {
target: root.item
when: root.item && !root.widgetEnabled
property: "visible"
value: false
restoreMode: Binding.RestoreBinding
}
Binding {
target: root.item
when: root.item && "parentScreen" in root.item
@@ -269,8 +278,4 @@ Loader {
return widgetVisibility[widgetId] ?? true;
}
function getWidgetEnabled(enabled) {
return enabled !== false;
}
}

View File

@@ -9,6 +9,7 @@ BasePill {
property var widgetData: null
property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/"
property int diskUsageMode: (widgetData && widgetData.diskUsageMode !== undefined) ? widgetData.diskUsageMode : 0
property bool isHovered: mouseArea.containsMouse
property bool isAutoHideBar: false
@@ -130,7 +131,13 @@ BasePill {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--";
}
return root.diskUsagePercent.toFixed(0);
if (!root.selectedMount) return "--";
switch (root.diskUsageMode) {
case 1: return root.selectedMount.size || "--";
case 2: return root.selectedMount.avail || "--";
case 3: return (root.selectedMount.avail || "--") + " / " + (root.selectedMount.size || "--");
default: return root.diskUsagePercent.toFixed(0);
}
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
@@ -178,7 +185,13 @@ BasePill {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--%";
}
return root.diskUsagePercent.toFixed(0) + "%";
if (!root.selectedMount) return "--%";
switch (root.diskUsageMode) {
case 1: return root.selectedMount.size || "--";
case 2: return root.selectedMount.avail || "--";
case 3: return (root.selectedMount.avail || "--") + " / " + (root.selectedMount.size || "--");
default: return root.diskUsagePercent.toFixed(0) + "%";
}
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
@@ -189,7 +202,14 @@ BasePill {
StyledTextMetrics {
id: diskBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
text: "100%"
text: {
switch (root.diskUsageMode) {
case 3: return "888.8G / 888.8G";
case 1:
case 2: return "888.8G";
default: return "100%";
}
}
}
width: Math.max(diskBaseline.width, paintedWidth)

View File

@@ -14,6 +14,7 @@ BasePill {
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
property bool showSwap: (widgetData && widgetData.showSwap !== undefined) ? widgetData.showSwap : false
property bool showInGb: (widgetData && widgetData.showInGb !== undefined) ? widgetData.showInGb : false
readonly property real swapUsage: DgopService.totalSwapKB > 0 ? (DgopService.usedSwapKB / DgopService.totalSwapKB) * 100 : 0
signal ramClicked
@@ -59,6 +60,10 @@ BasePill {
return "--";
}
if (root.showInGb) {
return (DgopService.usedMemoryMB / 1024).toFixed(1);
}
return DgopService.memoryUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
@@ -113,13 +118,14 @@ BasePill {
id: ramBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
text: {
let baseText = root.showInGb ? "88.8 GB" : "88%";
if (!root.showSwap) {
return "88%";
return baseText;
}
if (root.swapUsage < 10) {
return "88% · 0%";
return baseText + " · 0%";
}
return "88% · 88%";
return baseText + " · 88%";
}
}
@@ -127,10 +133,16 @@ BasePill {
id: ramText
text: {
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
return "--%";
return root.showInGb ? "-- GB" : "--%";
}
let ramText = "";
if (root.showInGb) {
ramText = (DgopService.usedMemoryMB / 1024).toFixed(1) + " GB";
} else {
ramText = DgopService.memoryUsage.toFixed(0) + "%";
}
let ramText = DgopService.memoryUsage.toFixed(0) + "%";
if (root.showSwap && DgopService.totalSwapKB > 0) {
return ramText + " · " + root.swapUsage.toFixed(0) + "%";
}

View File

@@ -41,6 +41,11 @@ Singleton {
property string lockDateFormat: ""
property bool lockScreenShowPowerActions: true
property bool lockScreenShowProfileImage: true
property bool powerActionConfirm: true
property real powerActionHoldDuration: 0.5
property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"]
property string powerMenuDefaultAction: "logout"
property bool powerMenuGridLayout: false
property var screenPreferences: ({})
property int animationSpeed: 2
property string wallpaperFillMode: "Fill"
@@ -75,6 +80,11 @@ Singleton {
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
lockScreenShowProfileImage = settings.lockScreenShowProfileImage !== undefined ? settings.lockScreenShowProfileImage : true;
powerActionConfirm = settings.powerActionConfirm !== undefined ? settings.powerActionConfirm : true;
powerActionHoldDuration = settings.powerActionHoldDuration !== undefined ? settings.powerActionHoldDuration : 0.5;
powerMenuActions = settings.powerMenuActions !== undefined ? settings.powerMenuActions : ["reboot", "logout", "poweroff", "lock", "suspend", "restart"];
powerMenuDefaultAction = settings.powerMenuDefaultAction !== undefined ? settings.powerMenuDefaultAction : "logout";
powerMenuGridLayout = settings.powerMenuGridLayout !== undefined ? settings.powerMenuGridLayout : false;
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";

View File

@@ -1231,6 +1231,12 @@ Item {
LockPowerMenu {
id: powerMenu
showLogout: false
powerActionConfirmOverride: GreetdSettings.powerActionConfirm
powerActionHoldDurationOverride: GreetdSettings.powerActionHoldDuration
powerMenuActionsOverride: GreetdSettings.powerMenuActions
powerMenuDefaultActionOverride: GreetdSettings.powerMenuDefaultAction
powerMenuGridLayoutOverride: GreetdSettings.powerMenuGridLayout
requiredActions: ["poweroff"]
onClosed: {
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
Qt.callLater(() => inputField.forceActiveFocus());

View File

@@ -24,13 +24,20 @@ Rectangle {
property real holdProgress: 0
property bool showHoldHint: false
readonly property bool needsConfirmation: SettingsData.powerActionConfirm
readonly property int holdDurationMs: SettingsData.powerActionHoldDuration * 1000
property var powerActionConfirmOverride: undefined
property var powerActionHoldDurationOverride: undefined
property var powerMenuActionsOverride: undefined
property var powerMenuDefaultActionOverride: undefined
property var powerMenuGridLayoutOverride: undefined
property var requiredActions: []
readonly property bool needsConfirmation: powerActionConfirmOverride !== undefined ? powerActionConfirmOverride : SettingsData.powerActionConfirm
readonly property int holdDurationMs: (powerActionHoldDurationOverride !== undefined ? powerActionHoldDurationOverride : SettingsData.powerActionHoldDuration) * 1000
signal closed
function updateVisibleActions() {
const allActions = (typeof SettingsData !== "undefined" && SettingsData.powerMenuActions) ? SettingsData.powerMenuActions : ["logout", "suspend", "hibernate", "reboot", "poweroff"];
const allActions = powerMenuActionsOverride !== undefined ? powerMenuActionsOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuActions) ? SettingsData.powerMenuActions : ["logout", "suspend", "hibernate", "reboot", "poweroff"]);
const hibernateSupported = (typeof SessionService !== "undefined" && SessionService.hibernateSupported) || false;
let filtered = allActions.filter(action => {
if (action === "hibernate" && !hibernateSupported)
@@ -44,9 +51,14 @@ Rectangle {
return true;
});
for (const action of requiredActions) {
if (!filtered.includes(action))
filtered.push(action);
}
visibleActions = filtered;
useGridLayout = (typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined) ? SettingsData.powerMenuGridLayout : false;
useGridLayout = powerMenuGridLayoutOverride !== undefined ? powerMenuGridLayoutOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined) ? SettingsData.powerMenuGridLayout : false);
if (!useGridLayout)
return;
const count = visibleActions.length;
@@ -73,7 +85,7 @@ Rectangle {
}
function getDefaultActionIndex() {
const defaultAction = (typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction) ? SettingsData.powerMenuDefaultAction : "suspend";
const defaultAction = powerMenuDefaultActionOverride !== undefined ? powerMenuDefaultActionOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction) ? SettingsData.powerMenuDefaultAction : "suspend");
const index = visibleActions.indexOf(defaultAction);
return index >= 0 ? index : 0;
}
@@ -780,8 +792,9 @@ Rectangle {
}
StyledText {
readonly property real totalMs: SettingsData.powerActionHoldDuration * 1000
readonly property real totalMs: root.holdDurationMs
readonly property int remainingMs: Math.ceil(totalMs * (1 - root.holdProgress))
readonly property real durationSec: root.holdDurationMs / 1000
text: {
if (root.showHoldHint)
return I18n.tr("Hold longer to confirm");
@@ -792,7 +805,7 @@ Rectangle {
}
if (totalMs < 1000)
return I18n.tr("Hold to confirm (%1 ms)").arg(totalMs);
return I18n.tr("Hold to confirm (%1s)").arg(SettingsData.powerActionHoldDuration);
return I18n.tr("Hold to confirm (%1s)").arg(durationSec);
}
font.pixelSize: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)

View File

@@ -351,6 +351,7 @@ Item {
Loader {
id: contentLoader
anchors.fill: parent
active: root.widgetEnabled && root.activeComponent !== null
sourceComponent: root.activeComponent
function reloadComponent() {

View File

@@ -1281,6 +1281,15 @@ Item {
}
}
DankActionButton {
iconName: "qr_code"
buttonSize: 28
visible: modelData.secured && modelData.saved
onClicked: {
PopoutService.showWifiQRCodeModal(modelData.ssid);
}
}
DankActionButton {
iconName: isPinned ? "push_pin" : "push_pin"
buttonSize: 28

View File

@@ -216,6 +216,15 @@ Rectangle {
onToggled: checked => root.updateConfig("showMemoryGraph", checked)
}
DankToggle {
width: parent.width - Theme.spacingM * 2
x: Theme.spacingM
text: I18n.tr("Show Memory in GB")
visible: root.cfg.showMemory
checked: root.cfg.showInGb ?? false
onToggled: checked => root.updateConfig("showInGb", checked)
}
SettingsDivider {}
DankToggle {

View File

@@ -398,10 +398,14 @@ Item {
widgetObj.runningAppsCurrentWorkspace = SettingsData.runningAppsCurrentWorkspace;
widgetObj.runningAppsCurrentMonitor = false;
}
if (widgetId === "diskUsage")
if (widgetId === "diskUsage") {
widgetObj.mountPath = "/";
widgetObj.diskUsageMode = 0;
}
if (widgetId === "cpuUsage" || widgetId === "memUsage" || widgetId === "cpuTemp" || widgetId === "gpuTemp")
widgetObj.minimumWidth = true;
if (widgetId === "memUsage")
widgetObj.showInGb = false;
var widgets = getWidgetsForSection(targetSection).slice();
widgets.push(widgetObj);
@@ -425,7 +429,7 @@ Item {
"id": widget.id,
"enabled": widget.enabled
};
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]];
@@ -536,6 +540,30 @@ Item {
setWidgetsForSection(sectionId, widgets);
}
function handleShowInGbChanged(sectionId, widgetIndex, enabled) {
var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) {
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = cloneWidgetData(widgets[widgetIndex]);
newWidget.showInGb = enabled;
widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets);
}
function handleDiskUsageModeChanged(sectionId, widgetIndex, mode) {
var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) {
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = cloneWidgetData(widgets[widgetIndex]);
newWidget.diskUsageMode = mode;
widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets);
}
function handleOverflowSettingChanged(sectionId, widgetIndex, settingName, value) {
var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) {
@@ -601,6 +629,8 @@ Item {
item.pciId = widget.pciId;
if (widget.mountPath !== undefined)
item.mountPath = widget.mountPath;
if (widget.diskUsageMode !== undefined)
item.diskUsageMode = widget.diskUsageMode;
if (widget.showNetworkIcon !== undefined)
item.showNetworkIcon = widget.showNetworkIcon;
if (widget.showBluetoothIcon !== undefined)
@@ -629,6 +659,8 @@ Item {
item.minimumWidth = widget.minimumWidth;
if (widget.showSwap !== undefined)
item.showSwap = widget.showSwap;
if (widget.showInGb !== undefined)
item.showInGb = widget.showInGb;
if (widget.mediaSize !== undefined)
item.mediaSize = widget.mediaSize;
if (widget.clockCompactMode !== undefined)
@@ -925,6 +957,12 @@ Item {
onShowSwapChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowSwapChanged(sectionId, index, enabled);
}
onShowInGbChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowInGbChanged(sectionId, index, enabled);
}
onDiskUsageModeChanged: (sectionId, widgetIndex, mode) => {
widgetsTab.handleDiskUsageModeChanged(sectionId, widgetIndex, mode);
}
onCompactModeChanged: (widgetId, value) => {
widgetsTab.handleCompactModeChanged(sectionId, widgetId, value);
}
@@ -983,6 +1021,12 @@ Item {
onShowSwapChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowSwapChanged(sectionId, index, enabled);
}
onShowInGbChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowInGbChanged(sectionId, index, enabled);
}
onDiskUsageModeChanged: (sectionId, widgetIndex, mode) => {
widgetsTab.handleDiskUsageModeChanged(sectionId, widgetIndex, mode);
}
onCompactModeChanged: (widgetId, value) => {
widgetsTab.handleCompactModeChanged(sectionId, widgetId, value);
}
@@ -1041,6 +1085,12 @@ Item {
onShowSwapChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowSwapChanged(sectionId, index, enabled);
}
onShowInGbChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowInGbChanged(sectionId, index, enabled);
}
onDiskUsageModeChanged: (sectionId, widgetIndex, mode) => {
widgetsTab.handleDiskUsageModeChanged(sectionId, widgetIndex, mode);
}
onCompactModeChanged: (widgetId, value) => {
widgetsTab.handleCompactModeChanged(sectionId, widgetId, value);
}

View File

@@ -30,6 +30,8 @@ Column {
signal privacySettingChanged(string sectionId, int widgetIndex, string settingName, bool value)
signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled)
signal showSwapChanged(string sectionId, int widgetIndex, bool enabled)
signal showInGbChanged(string sectionId, int widgetIndex, bool enabled)
signal diskUsageModeChanged(string sectionId, int widgetIndex, int mode)
signal overflowSettingChanged(string sectionId, int widgetIndex, string settingName, var value)
function cloneWidgetData(widget) {
@@ -37,7 +39,7 @@ Column {
"id": widget.id,
"enabled": widget.enabled
};
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]];
@@ -67,53 +69,6 @@ Column {
color: Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
Item {
height: 1
Layout.fillWidth: true
}
RowLayout {
spacing: Theme.spacingXS
Layout.alignment: Qt.AlignVCenter
visible: root.sectionId === "center"
DankActionButton {
id: indexCenterButton
buttonSize: 28
iconName: "format_list_numbered"
iconSize: 16
iconColor: SettingsData.centeringMode === "index" ? Theme.primary : Theme.outline
onClicked: {
console.log("Centering mode changed to: index");
SettingsData.set("centeringMode", "index");
}
onEntered: {
sharedTooltip.show("Index Centering", indexCenterButton, 0, 0, "bottom");
}
onExited: {
sharedTooltip.hide();
}
}
DankActionButton {
id: geometricCenterButton
buttonSize: 28
iconName: "center_focus_weak"
iconSize: 16
iconColor: SettingsData.centeringMode === "geometric" ? Theme.primary : Theme.outline
onClicked: {
console.log("Centering mode changed to: geometric");
SettingsData.set("centeringMode", "geometric");
}
onEntered: {
sharedTooltip.show("Geometric Centering", geometricCenterButton, 0, 0, "bottom");
}
onExited: {
sharedTooltip.hide();
}
}
}
}
Column {
@@ -135,6 +90,7 @@ Column {
height: 70
z: held ? 2 : 1
Rectangle {
id: itemBackground
@@ -275,6 +231,34 @@ Column {
}
}
DankActionButton {
id: diskMenuButton
visible: modelData.id === "diskUsage"
buttonSize: 32
iconName: "more_vert"
iconSize: 18
iconColor: Theme.outline
onClicked: {
diskUsageContextMenu.widgetData = modelData;
diskUsageContextMenu.sectionId = root.sectionId;
diskUsageContextMenu.widgetIndex = index;
var buttonPos = diskMenuButton.mapToItem(root, 0, 0);
var xPos = buttonPos.x - diskUsageContextMenu.width - Theme.spacingS;
if (xPos < 0)
xPos = buttonPos.x + diskMenuButton.width + Theme.spacingS;
var yPos = buttonPos.y - diskUsageContextMenu.height / 2 + diskMenuButton.height / 2;
if (yPos < 0)
yPos = Theme.spacingS;
else if (yPos + diskUsageContextMenu.height > root.height)
yPos = root.height - diskUsageContextMenu.height - Theme.spacingS;
diskUsageContextMenu.x = xPos;
diskUsageContextMenu.y = yPos;
diskUsageContextMenu.open();
}
}
Item {
width: 32
height: 32
@@ -354,23 +338,36 @@ Column {
}
DankActionButton {
id: showSwapButton
buttonSize: 28
id: memMenuButton
visible: modelData.id === "memUsage"
iconName: "swap_horiz"
iconSize: 16
iconColor: (modelData.showSwap !== undefined ? modelData.showSwap : false) ? Theme.primary : Theme.outline
buttonSize: 32
iconName: "more_vert"
iconSize: 18
iconColor: Theme.outline
onClicked: {
var currentEnabled = modelData.showSwap !== undefined ? modelData.showSwap : false;
root.showSwapChanged(root.sectionId, index, !currentEnabled);
}
onEntered: {
var currentEnabled = modelData.showSwap !== undefined ? modelData.showSwap : false;
const tooltipText = currentEnabled ? "Hide Swap" : "Show Swap";
sharedTooltip.show(tooltipText, showSwapButton, 0, 0, "bottom");
}
onExited: {
sharedTooltip.hide();
memUsageContextMenu.widgetData = modelData;
memUsageContextMenu.sectionId = root.sectionId;
memUsageContextMenu.widgetIndex = index;
var buttonPos = memMenuButton.mapToItem(root, 0, 0);
var popupWidth = memUsageContextMenu.width;
var popupHeight = memUsageContextMenu.height;
var xPos = buttonPos.x - popupWidth - Theme.spacingS;
if (xPos < 0) {
xPos = buttonPos.x + memMenuButton.width + Theme.spacingS;
}
var yPos = buttonPos.y - popupHeight / 2 + memMenuButton.height / 2;
if (yPos < 0) {
yPos = Theme.spacingS;
} else if (yPos + popupHeight > root.height) {
yPos = root.height - popupHeight - Theme.spacingS;
}
memUsageContextMenu.x = xPos;
memUsageContextMenu.y = yPos;
memUsageContextMenu.open();
}
}
@@ -799,6 +796,257 @@ Column {
}
}
Popup {
id: memUsageContextMenu
property var widgetData: null
property string sectionId: ""
property int widgetIndex: -1
width: 200
height: 80
padding: 0
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle {
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
}
contentItem: Item {
Column {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 2
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: swapToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "swap_horiz"
size: 16
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Show Swap")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
id: swapToggle
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
width: 40
height: 20
checked: memUsageContextMenu.widgetData?.showSwap ?? false
onToggled: {
root.showSwapChanged(memUsageContextMenu.sectionId, memUsageContextMenu.widgetIndex, toggled);
}
}
MouseArea {
id: swapToggleArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
swapToggle.checked = !swapToggle.checked;
root.showSwapChanged(memUsageContextMenu.sectionId, memUsageContextMenu.widgetIndex, swapToggle.checked);
}
}
}
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: gbToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "straighten"
size: 16
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Show in GB")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
id: gbToggle
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
width: 40
height: 20
checked: memUsageContextMenu.widgetData?.showInGb ?? false
onToggled: {
root.showInGbChanged(memUsageContextMenu.sectionId, memUsageContextMenu.widgetIndex, toggled);
}
}
MouseArea {
id: gbToggleArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
gbToggle.checked = !gbToggle.checked;
root.showInGbChanged(memUsageContextMenu.sectionId, memUsageContextMenu.widgetIndex, gbToggle.checked);
}
}
}
}
}
}
Popup {
id: diskUsageContextMenu
property var widgetData: null
property string sectionId: ""
property int widgetIndex: -1
readonly property var currentWidgetData: (widgetIndex >= 0 && widgetIndex < root.items.length) ? root.items[widgetIndex] : widgetData
width: 240
height: diskMenuColumn.implicitHeight + Theme.spacingS * 2
padding: 0
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle {
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
}
contentItem: Item {
Column {
id: diskMenuColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 2
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: "transparent"
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Disk Usage Display")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
}
Repeater {
model: [
{ label: I18n.tr("Percentage"), mode: 0, icon: "percent" },
{ label: I18n.tr("Total"), mode: 1, icon: "storage" },
{ label: I18n.tr("Remaining"), mode: 2, icon: "hourglass_empty" },
{ label: I18n.tr("Remaining / Total"), mode: 3, icon: "pie_chart" }
]
delegate: Rectangle {
required property var modelData
required property int index
width: diskMenuColumn.width
height: 32
radius: Theme.cornerRadius
color: diskOptionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
function isSelected() {
return (diskUsageContextMenu.currentWidgetData?.diskUsageMode ?? 0) === modelData.mode;
}
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: modelData.icon
size: 16
color: isSelected() ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.label
font.pixelSize: Theme.fontSizeSmall
color: isSelected() ? Theme.primary : Theme.surfaceText
font.weight: isSelected() ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
DankIcon {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
name: "check"
size: 16
color: Theme.primary
visible: isSelected()
}
MouseArea {
id: diskOptionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.diskUsageModeChanged(diskUsageContextMenu.sectionId, diskUsageContextMenu.widgetIndex, modelData.mode);
diskUsageContextMenu.close();
}
}
}
}
}
}
}
Popup {
id: controlCenterContextMenu

View File

@@ -11,7 +11,7 @@ Singleton {
id: root
readonly property string currentVersion: "1.4"
readonly property bool changelogEnabled: true
readonly property bool changelogEnabled: false
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell"
readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion

View File

@@ -1,8 +1,6 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import Quickshell
import Quickshell.Io
@@ -13,6 +11,9 @@ Singleton {
property bool dsearchAvailable: false
property int searchIdCounter: 0
property int indexVersion: 0
property bool supportsTypeFilter: false
property bool versionChecked: false
signal searchResultsReceived(var results)
signal statsReceived(var stats)
@@ -26,118 +27,157 @@ Singleton {
stdout: SplitParser {
onRead: line => {
if (line && line.trim().length > 0) {
root.dsearchAvailable = true
root.dsearchAvailable = true;
}
}
}
onExited: exitCode => {
if (exitCode !== 0) {
root.dsearchAvailable = false
root.dsearchAvailable = false;
} else {
root._checkVersion();
}
}
}
function _checkVersion() {
Proc.runCommand("dsearch-version", ["dsearch", "version", "--json"], (stdout, exitCode) => {
root.versionChecked = true;
if (exitCode !== 0)
return;
const response = JSON.parse(stdout);
root.indexVersion = response.index_schema || 0;
root.supportsTypeFilter = root.indexVersion >= 2;
});
}
function ping(callback) {
if (!dsearchAvailable) {
if (callback) {
callback({ "error": "dsearch not available" })
callback({
"error": "dsearch not available"
});
}
return
return;
}
Proc.runCommand("dsearch-ping", ["dsearch", "ping", "--json"], (stdout, exitCode) => {
if (callback) {
if (exitCode === 0) {
try {
const response = JSON.parse(stdout)
callback({ "result": response })
const response = JSON.parse(stdout);
callback({
"result": response
});
} catch (e) {
callback({ "error": "failed to parse ping response" })
callback({
"error": "failed to parse ping response"
});
}
} else {
callback({ "error": "ping failed" })
callback({
"error": "ping failed"
});
}
}
})
});
}
function search(query, params, callback) {
if (!query || query.length === 0) {
if (callback) {
callback({ "error": "query is required" })
callback({
"error": "query is required"
});
}
return
return;
}
if (!dsearchAvailable) {
if (callback) {
callback({ "error": "dsearch not available" })
callback({
"error": "dsearch not available"
});
}
return
return;
}
const args = ["dsearch", "search", query, "--json"]
const args = ["dsearch", "search", query, "--json"];
if (params) {
if (params.limit !== undefined) {
args.push("-n", String(params.limit))
args.push("-n", String(params.limit));
}
if (params.type) {
args.push("-t", params.type);
}
if (params.ext) {
args.push("-e", params.ext)
args.push("-e", params.ext);
}
if (params.folder) {
args.push("--folder", params.folder);
}
if (params.field) {
args.push("-f", params.field)
args.push("-f", params.field);
}
if (params.fuzzy) {
args.push("--fuzzy")
args.push("--fuzzy");
}
if (params.sort) {
args.push("--sort", params.sort)
args.push("--sort", params.sort);
}
if (params.desc !== undefined) {
args.push("--desc=" + (params.desc ? "true" : "false"))
args.push("--desc=" + (params.desc ? "true" : "false"));
}
if (params.minSize !== undefined) {
args.push("--min-size", String(params.minSize))
args.push("--min-size", String(params.minSize));
}
if (params.maxSize !== undefined) {
args.push("--max-size", String(params.maxSize))
args.push("--max-size", String(params.maxSize));
}
}
Proc.runCommand("dsearch-search", args, (stdout, exitCode) => {
if (exitCode === 0) {
try {
const response = JSON.parse(stdout)
searchResultsReceived(response)
const response = JSON.parse(stdout);
searchResultsReceived(response);
if (callback) {
callback({ "result": response })
callback({
"result": response
});
}
} catch (e) {
const error = "failed to parse search response"
errorOccurred(error)
const error = "failed to parse search response";
errorOccurred(error);
if (callback) {
callback({ "error": error })
callback({
"error": error
});
}
}
} else if (exitCode === 124) {
const error = "search timed out"
errorOccurred(error)
const error = "search timed out";
errorOccurred(error);
if (callback) {
callback({ "error": error })
callback({
"error": error
});
}
} else {
const error = "search failed"
errorOccurred(error)
const error = "search failed";
errorOccurred(error);
if (callback) {
callback({ "error": error })
callback({
"error": error
});
}
}
}, 100, 5000)
}, 100, 5000);
}
function rediscover() {
checkProcess.running = true
checkProcess.running = true;
}
}

View File

@@ -41,6 +41,8 @@ Singleton {
property var notificationModal: null
property var wifiPasswordModal: null
property var wifiPasswordModalLoader: null
property var wifiQRCodeModal: null
property var wifiQRCodeModalLoader: null
property var polkitAuthModal: null
property var polkitAuthModalLoader: null
property var bluetoothPairingModal: null
@@ -661,6 +663,13 @@ Singleton {
wifiPasswordModal.show(ssid);
}
function showWifiQRCodeModal(ssid) {
if (wifiQRCodeModalLoader)
wifiQRCodeModalLoader.active = true;
if (wifiQRCodeModal)
wifiQRCodeModal.show(ssid);
}
function showHiddenNetworkModal() {
if (wifiPasswordModalLoader)
wifiPasswordModalLoader.active = true;

View File

@@ -1 +1 @@
v1.4.3
v1.5-beta

View File

@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Window
import QtQuick.Effects
import qs.Common
import qs.Widgets
@@ -18,9 +19,30 @@ Rectangle {
signal imageSaved(string filePath)
property string _pendingSavePath: ""
property var _attachedWindow: root.Window.window
on_AttachedWindowChanged: {
if (_attachedWindow && _pendingSavePath !== "") {
Qt.callLater(function () {
if (root._pendingSavePath !== "") {
let path = root._pendingSavePath;
root._pendingSavePath = "";
root.saveImageToFile(path);
}
});
}
}
function saveImageToFile(filePath) {
if (activeImage.status !== Image.Ready)
return false;
if (!activeImage.Window.window) {
_pendingSavePath = filePath;
return true;
}
activeImage.grabToImage(function (result) {
if (result && result.saveToFile(filePath)) {
root.imageSaved(filePath);

View File

@@ -289,7 +289,7 @@ Item {
visible: false
color: "transparent"
Component.onCompleted: {
if (typeof updatesEnabled !== "undefined")
if (typeof updatesEnabled !== "undefined" && !root.overlayContent)
updatesEnabled = false;
}

View File

@@ -4,10 +4,12 @@ return {
priority = 1000,
config = function()
require('base16-colorscheme').setup({
base00 = '{{dank16.color0.default.hex}}',
base01 = '{{dank16.color0.default.hex}}',
base02 = '{{dank16.color8.default.hex}}',
base03 = '{{dank16.color8.default.hex}}',
base00 = '{{colors.background.dark.hex}}',
base01 = '{{colors.surface_container_low.dark.hex}}',
base02 = '{{colors.surface_container.dark.hex}}',
base03 = '{{dank16.color8.dark.hex}}',
base0B = '{{dank16.color3.dark.hex}}',
base04 = '{{dank16.color7.default.hex}}',
base05 = '{{dank16.color15.default.hex}}',
base06 = '{{dank16.color15.default.hex}}',
@@ -15,65 +17,12 @@ return {
base08 = '{{dank16.color9.default.hex}}',
base09 = '{{dank16.color9.default.hex}}',
base0A = '{{dank16.color12.default.hex}}',
base0B = '{{dank16.color10.default.hex}}',
base0C = '{{dank16.color14.default.hex}}',
base0D = '{{dank16.color12.default.hex}}',
base0E = '{{dank16.color13.default.hex}}',
base0F = '{{dank16.color13.default.hex}}',
})
vim.api.nvim_set_hl(0, 'Visual', {
bg = '{{dank16.color8.default.hex}}',
fg = '{{dank16.color15.default.hex}}',
bold = true
})
vim.api.nvim_set_hl(0, 'Statusline', {
bg = '{{dank16.color12.default.hex}}',
fg = '{{dank16.color0.default.hex}}',
})
vim.api.nvim_set_hl(0, 'LineNr', { fg = '{{dank16.color8.default.hex}}' })
vim.api.nvim_set_hl(0, 'CursorLineNr', { fg = '{{dank16.color14.default.hex}}', bold = true })
vim.api.nvim_set_hl(0, 'Statement', {
fg = '{{dank16.color13.default.hex}}',
bold = true
})
vim.api.nvim_set_hl(0, 'Keyword', { link = 'Statement' })
vim.api.nvim_set_hl(0, 'Repeat', { link = 'Statement' })
vim.api.nvim_set_hl(0, 'Conditional', { link = 'Statement' })
vim.api.nvim_set_hl(0, 'Function', {
fg = '{{dank16.color12.default.hex}}',
bold = true
})
vim.api.nvim_set_hl(0, 'Macro', {
fg = '{{dank16.color12.default.hex}}',
italic = true
})
vim.api.nvim_set_hl(0, '@function.macro', { link = 'Macro' })
vim.api.nvim_set_hl(0, 'Type', {
fg = '{{dank16.color14.default.hex}}',
bold = true,
italic = true
})
vim.api.nvim_set_hl(0, 'Structure', { link = 'Type' })
vim.api.nvim_set_hl(0, 'String', {
fg = '{{dank16.color10.default.hex}}',
italic = true
})
vim.api.nvim_set_hl(0, 'Operator', { fg = '{{dank16.color7.default.hex}}' })
vim.api.nvim_set_hl(0, 'Delimiter', { fg = '{{dank16.color7.default.hex}}' })
vim.api.nvim_set_hl(0, '@punctuation.bracket', { link = 'Delimiter' })
vim.api.nvim_set_hl(0, '@punctuation.delimiter', { link = 'Delimiter' })
vim.api.nvim_set_hl(0, 'Comment', {
fg = '{{dank16.color8.default.hex}}',
italic = true
})
local current_file_path = vim.fn.stdpath("config") .. "/lua/plugins/dankcolors.lua"
if not _G._matugen_theme_watcher then
local uv = vim.uv or vim.loop