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

Compare commits

..

145 Commits

Author SHA1 Message Date
purian23 1452c85946 Finalize m3 shadow & elevation 2026-03-01 00:43:46 -05:00
purian23 36951f82c6 Implement Auto-Bar location aware shadowing 2026-02-28 17:54:03 -05:00
purian23 1d25d18ca7 Refactor shadow levels to their own layer 2026-02-28 16:46:11 -05:00
purian23 771e59fcb8 Restore click to expand animation on notif cards 2026-02-28 16:09:52 -05:00
purian23 35593fa9d9 Fix some moar m3 changes w/shadows 2026-02-28 15:46:53 -05:00
purian23 88b7889447 Core m3 spec complete 2026-02-27 10:10:28 -05:00
purian23 3c5d8ba66a Moar M3 iteration stuffs 2026-02-26 23:02:49 -05:00
purian23 972bc8397d theme: Continue to iterate m3 specs 2026-02-26 16:18:59 -05:00
purian23 788c1fa9e6 feat: Implement Material Design 3 elevation and shadow effects
- Added global toggle for enabling M3 elevation & shadow effects
- Refactored various components to sync updated specs
2026-02-26 01:09:21 -05:00
Kangheng Liu a6269084c0 Systray: call context menu fallback for legacy protocol (#1839)
* systray: add call contextmenu fallback

directly call dbus contextmenu method. needs refactoring to be more
robust.

* add TODO

---------

Co-authored-by: bbedward <bbedward@gmail.com>
2026-02-25 17:19:09 -05:00
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
83 changed files with 3126 additions and 921 deletions
+6 -7
View File
@@ -9,8 +9,8 @@ on:
type: choice type: choice
options: options:
- dms - dms
- dms-git
- dms-greeter - dms-greeter
- dms-git
- all - all
default: "dms" default: "dms"
rebuild_release: rebuild_release:
@@ -119,9 +119,8 @@ jobs:
echo "🔄 Manual rebuild requested: $PKG (db$REBUILD)" echo "🔄 Manual rebuild requested: $PKG (db$REBUILD)"
elif [[ "$PKG" == "all" ]]; then 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=() PACKAGES_TO_UPDATE=()
check_dms_git && PACKAGES_TO_UPDATE+=("dms-git")
if check_dms_stable; then if check_dms_stable; then
PACKAGES_TO_UPDATE+=("dms") PACKAGES_TO_UPDATE+=("dms")
if [[ -n "$LATEST_TAG" ]]; then if [[ -n "$LATEST_TAG" ]]; then
@@ -140,7 +139,7 @@ jobs:
else else
echo "packages=" >> $GITHUB_OUTPUT echo "packages=" >> $GITHUB_OUTPUT
echo "has_updates=false" >> $GITHUB_OUTPUT echo "has_updates=false" >> $GITHUB_OUTPUT
echo "✓ All packages up to date" echo "✓ Both packages up to date"
fi fi
elif [[ "$PKG" == "dms-git" ]]; then elif [[ "$PKG" == "dms-git" ]]; then
@@ -245,7 +244,7 @@ jobs:
fi fi
- name: Update dms-git spec version - 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: | run: |
COMMIT_HASH=$(git rev-parse --short=8 HEAD) COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD) COMMIT_COUNT=$(git rev-list --count HEAD)
@@ -266,7 +265,7 @@ jobs:
} > distro/opensuse/dms-git.spec } > distro/opensuse/dms-git.spec
- name: Update Debian dms-git changelog version - 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: | run: |
COMMIT_HASH=$(git rev-parse --short=8 HEAD) COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD) COMMIT_COUNT=$(git rev-list --count HEAD)
@@ -389,7 +388,7 @@ jobs:
UPLOADED_PACKAGES=() UPLOADED_PACKAGES=()
SKIPPED_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 # Loop through each package and upload
for PKG in $PACKAGES; do for PKG in $PACKAGES; do
echo "" echo ""
+11 -5
View File
@@ -4,9 +4,15 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
package: package:
description: "Package to upload (dms, dms-git, dms-greeter, or all)" description: "Package to upload"
required: false required: true
default: "dms-git" type: choice
options:
- dms
- dms-greeter
- dms-git
- all
default: "dms"
rebuild_release: rebuild_release:
description: "Release number for rebuilds (e.g., 2, 3, 4 for ppa2, ppa3, ppa4)" description: "Release number for rebuilds (e.g., 2, 3, 4 for ppa2, ppa3, ppa4)"
required: false required: false
@@ -139,7 +145,7 @@ jobs:
fi fi
else else
# Fallback # Fallback
echo "packages=dms-git" >> $GITHUB_OUTPUT echo "packages=dms" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT echo "has_updates=true" >> $GITHUB_OUTPUT
fi fi
@@ -209,7 +215,7 @@ jobs:
echo "✓ Using rebuild release number: ppa$REBUILD_RELEASE" echo "✓ Using rebuild release number: ppa$REBUILD_RELEASE"
fi 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 # Loop through each package and upload
for PKG in $PACKAGES; do for PKG in $PACKAGES; do
# Map package to PPA name # Map package to PPA name
+6
View File
@@ -16,6 +16,8 @@ require (
github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6 github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1 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 v1.7.16
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
go.etcd.io/bbolt v1.4.3 go.etcd.io/bbolt v1.4.3
@@ -32,15 +34,19 @@ require (
github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/emirpasic/gods v1.18.1 // 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/gcfg/v2 v2.0.2 // indirect
github.com/go-git/go-billy/v6 v6.0.0-20260209124918-37866f83c2d3 // 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/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/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/pjbgf/sha1cd v0.5.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/sergi/go-diff v1.4.0 // indirect
github.com/stretchr/objx v0.5.3 // 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/crypto v0.48.0 // indirect
golang.org/x/net v0.50.0 // indirect golang.org/x/net v0.50.0 // indirect
) )
+12
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/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 h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 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 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 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.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= 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/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 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 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/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 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 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/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 h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 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.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 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
@@ -1062,6 +1062,62 @@ func (_c *MockBackend_GetWiFiNetworkDetails_Call) RunAndReturn(run func(string)
return _c 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 // GetWiredConnections provides a mock function with no fields
func (_m *MockBackend) GetWiredConnections() ([]network.WiredConnection, error) { func (_m *MockBackend) GetWiredConnections() ([]network.WiredConnection, error) {
ret := _m.Called() ret := _m.Called()
+1
View File
@@ -10,6 +10,7 @@ type Backend interface {
ScanWiFi() error ScanWiFi() error
ScanWiFiDevice(device string) error ScanWiFiDevice(device string) error
GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error) GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
GetWiFiQRCodeContent(ssid string) (string, error)
GetWiFiDevices() []WiFiDevice GetWiFiDevices() []WiFiDevice
ConnectWiFi(req ConnectionRequest) error ConnectWiFi(req ConnectionRequest) error
@@ -111,6 +111,10 @@ func (b *HybridIwdNetworkdBackend) GetWiFiNetworkDetails(ssid string) (*NetworkI
return b.wifi.GetWiFiNetworkDetails(ssid) 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 { func (b *HybridIwdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
if err := b.wifi.ConnectWiFi(req); err != nil { if err := b.wifi.ConnectWiFi(req); err != nil {
return err return err
@@ -1,6 +1,9 @@
package network package network
import "fmt" import (
"fmt"
"os"
)
func (b *IWDBackend) GetWiredConnections() ([]WiredConnection, error) { func (b *IWDBackend) GetWiredConnections() ([]WiredConnection, error) {
return nil, fmt.Errorf("wired connections not supported by iwd") return nil, fmt.Errorf("wired connections not supported by iwd")
@@ -112,3 +115,19 @@ func (b *IWDBackend) getWiFiDevicesLocked() []WiFiDevice {
Networks: b.state.WiFiNetworks, 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
}
@@ -18,6 +18,10 @@ func (b *SystemdNetworkdBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInf
return nil, fmt.Errorf("WiFi details not supported by networkd backend") 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 { func (b *SystemdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
return fmt.Errorf("WiFi connect not supported by networkd backend") return fmt.Errorf("WiFi connect not supported by networkd backend")
} }
@@ -196,6 +196,65 @@ func (b *NetworkManagerBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfo
}, nil }, 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 { func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
devInfo, err := b.getWifiDeviceForConnection(req.Device) devInfo, err := b.getWifiDeviceForConnection(req.Device)
if err != nil { if err != nil {
+41
View File
@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"os"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "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) handleSetPreference(conn, req, manager)
case "network.info": case "network.info":
handleGetNetworkInfo(conn, req, manager) handleGetNetworkInfo(conn, req, manager)
case "network.qrcode":
handleGetNetworkQRCode(conn, req, manager)
case "network.delete-qrcode":
handleDeleteQRCode(conn, req, manager)
case "network.ethernet.info": case "network.ethernet.info":
handleGetWiredNetworkInfo(conn, req, manager) handleGetWiredNetworkInfo(conn, req, manager)
case "network.subscribe": case "network.subscribe":
@@ -320,6 +325,42 @@ func handleGetNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
models.Respond(conn, req.ID, network) 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) { func handleGetWiredNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
uuid, err := params.String(req.Params, "uuid") uuid, err := params.String(req.Params, "uuid")
if err != nil { if err != nil {
+39
View File
@@ -6,6 +6,8 @@ import (
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
) )
func NewManager() (*Manager, error) { func NewManager() (*Manager, error) {
@@ -438,6 +440,43 @@ func (m *Manager) GetNetworkInfoDetailed(ssid string) (*NetworkInfoResponse, err
return m.backend.GetWiFiNetworkDetails(ssid) 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 { func (m *Manager) ToggleWiFi() error {
enabled, err := m.backend.GetWiFiEnabled() enabled, err := m.backend.GetWiFiEnabled()
if err != nil { if err != nil {
@@ -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")
}
+5 -5
View File
@@ -27,12 +27,12 @@ override_dh_auto_build:
# Verify core directory exists (native package format has source at root) # Verify core directory exists (native package format has source at root)
test -d core || (echo "ERROR: core directory not found!" && exit 1) 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) # Pin go.mod and vendor/modules.txt to the installed Go toolchain version
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' core/go.mod 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 # Build dms-cli (single shell to preserve variables; arch: Debian amd64/arm64 -> Makefile amd64/arm64)
# Extract version info and build in single shell to preserve variables
# Architecture mapping: Debian amd64/arm64 -> Makefile amd64/arm64
VERSION="$(UPSTREAM_VERSION)"; \ VERSION="$(UPSTREAM_VERSION)"; \
COMMIT=$$(echo "$(UPSTREAM_VERSION)" | grep -oP '(?<=git)[0-9]+\.[a-f0-9]+' | cut -d. -f2 | head -c8 || echo "unknown"); \ 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 \ if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \
+1 -1
View File
@@ -3,7 +3,7 @@
<service name="download_url"> <service name="download_url">
<param name="protocol">https</param> <param name="protocol">https</param>
<param name="host">github.com</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> <param name="filename">dms-qml.tar.gz</param>
</service> </service>
</services> </services>
+3 -4
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 * Update to v1.4.3 stable release
* Port from Ubuntu/Fedora packaging
-- 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
+26 -3
View File
@@ -3,6 +3,7 @@
%global debug_package %{nil} %global debug_package %{nil}
%global version {{{ git_repo_version }}} %global version {{{ git_repo_version }}}
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors %global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
%global go_toolchain_version 1.25.7
Name: dms Name: dms
Epoch: 2 Epoch: 2
@@ -14,12 +15,12 @@ License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell URL: https://github.com/AvengeMedia/DankMaterialShell
VCS: {{{ git_repo_vcs }}} VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}} 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: git-core
BuildRequires: gzip BuildRequires: gzip
BuildRequires: golang >= 1.24
BuildRequires: make BuildRequires: make
BuildRequires: wget
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
# Core requirements # Core requirements
@@ -28,7 +29,7 @@ Requires: accountsservice
Requires: dms-cli = %{epoch}:%{version}-%{release} Requires: dms-cli = %{epoch}:%{version}-%{release}
Requires: dgop Requires: dgop
# Core utilities (Highly recommended for DMS functionality) # Core utilities (Recommended for DMS functionality)
Recommends: cava Recommends: cava
Recommends: danksearch Recommends: danksearch
Recommends: matugen Recommends: matugen
@@ -66,6 +67,28 @@ Provides native DBus bindings, NetworkManager integration, and system utilities.
VERSION="%{version}" VERSION="%{version}"
COMMIT=$(echo "%{version}" | grep -oP '[a-f0-9]{7,}' | head -n1 || echo "unknown") 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 cd core
make dist VERSION="$VERSION" COMMIT="$COMMIT" make dist VERSION="$VERSION" COMMIT="$COMMIT"
+4 -2
View File
@@ -56,8 +56,10 @@ mkdir -p $HOME $GOCACHE $GOMODCACHE
# OBS has no network access, so use local toolchain only # OBS has no network access, so use local toolchain only
export GOTOOLCHAIN=local export GOTOOLCHAIN=local
# Patch go.mod to use base Go version (e.g., go 1.24 instead of go 1.24.6) # Pin go.mod and vendor/modules.txt to the installed Go toolchain version
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' core/go.mod 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 # Extract version info for embedding in binary
VERSION="%{version}" VERSION="%{version}"
+6
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/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/RELEASE_PLACEHOLDER/${DMS_GREETER_RELEASE}/g" "$WORK_DIR/$PACKAGE.spec"
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/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
if [[ -f "$WORK_DIR/.osc/$PACKAGE.spec" ]]; then 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/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/RELEASE_PLACEHOLDER/${DMS_GREETER_RELEASE}/g" "$WORK_DIR/$PACKAGE.spec"
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/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
fi fi
+1
View File
@@ -182,6 +182,7 @@
with pkgs; with pkgs;
[ [
go_1_25 go_1_25
go-mockery_2
gopls gopls
delve delve
go-tools go-tools
+1 -1
View File
@@ -1 +1 @@
Saffron Bloom The Wolverine
+54
View File
@@ -0,0 +1,54 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import qs.Common
Item {
id: root
property var level: Theme.elevationLevel2
property string direction: Theme.elevationLightDirection
property real fallbackOffset: 4
property color targetColor: "white"
property real targetRadius: Theme.cornerRadius
property color borderColor: "transparent"
property real borderWidth: 0
property bool shadowEnabled: Theme.elevationEnabled
property real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0
property real shadowSpreadPx: level && level.spreadPx !== undefined ? level.spreadPx : 0
property real shadowOffsetX: Theme.elevationOffsetXFor(level, direction, fallbackOffset)
property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset)
property color shadowColor: Theme.elevationShadowColor(level)
property real shadowOpacity: 1
property real blurMax: Theme.elevationBlurMax
property alias sourceRect: sourceRect
layer.enabled: shadowEnabled
layer.effect: MultiEffect {
autoPaddingEnabled: true
shadowEnabled: true
blurEnabled: false
maskEnabled: false
shadowBlur: Math.max(0, Math.min(1, root.shadowBlurPx / Math.max(1, root.blurMax)))
shadowScale: 1 + (2 * root.shadowSpreadPx) / Math.max(1, Math.min(root.width, root.height))
shadowHorizontalOffset: root.shadowOffsetX
shadowVerticalOffset: root.shadowOffsetY
blurMax: root.blurMax
shadowColor: root.shadowColor
shadowOpacity: root.shadowOpacity
}
Rectangle {
id: sourceRect
anchors.fill: parent
radius: root.targetRadius
color: root.targetColor
border.color: root.borderColor
border.width: root.borderWidth
}
}
+20 -2
View File
@@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store
Singleton { Singleton {
id: root id: root
readonly property int settingsConfigVersion: 5 readonly property int settingsConfigVersion: 6
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true" readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
@@ -165,6 +165,24 @@ Singleton {
property int modalCustomAnimationDuration: 150 property int modalCustomAnimationDuration: 150
property bool enableRippleEffects: true property bool enableRippleEffects: true
onEnableRippleEffectsChanged: saveSettings() onEnableRippleEffectsChanged: saveSettings()
property bool m3ElevationEnabled: true
onM3ElevationEnabledChanged: saveSettings()
property int m3ElevationIntensity: 12
onM3ElevationIntensityChanged: saveSettings()
property int m3ElevationOpacity: 30
onM3ElevationOpacityChanged: saveSettings()
property string m3ElevationColorMode: "default"
onM3ElevationColorModeChanged: saveSettings()
property string m3ElevationLightDirection: "top"
onM3ElevationLightDirectionChanged: saveSettings()
property string m3ElevationCustomColor: "#000000"
onM3ElevationCustomColorChanged: saveSettings()
property bool modalElevationEnabled: true
onModalElevationEnabledChanged: saveSettings()
property bool popoutElevationEnabled: true
onPopoutElevationEnabledChanged: saveSettings()
property bool barElevationEnabled: true
onBarElevationEnabledChanged: saveSettings()
property string wallpaperFillMode: "Fill" property string wallpaperFillMode: "Fill"
property bool blurredWallpaperLayer: false property bool blurredWallpaperLayer: false
property bool blurWallpaperOnOverview: false property bool blurWallpaperOnOverview: false
@@ -602,7 +620,7 @@ Singleton {
"scrollYBehavior": "workspace", "scrollYBehavior": "workspace",
"shadowIntensity": 0, "shadowIntensity": 0,
"shadowOpacity": 60, "shadowOpacity": 60,
"shadowColorMode": "text", "shadowColorMode": "default",
"shadowCustomColor": "#000000", "shadowCustomColor": "#000000",
"clickThrough": false "clickThrough": false
} }
+226
View File
@@ -673,6 +673,232 @@ Singleton {
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08) property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3) property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
readonly property bool elevationEnabled: typeof SettingsData !== "undefined" && (SettingsData.m3ElevationEnabled ?? true)
readonly property real elevationBlurMax: typeof SettingsData !== "undefined" && SettingsData.m3ElevationIntensity !== undefined ? Math.min(128, Math.max(32, SettingsData.m3ElevationIntensity * 2)) : 64
readonly property real _elevMult: typeof SettingsData !== "undefined" && SettingsData.m3ElevationIntensity !== undefined ? SettingsData.m3ElevationIntensity / 12 : 1
readonly property real _opMult: typeof SettingsData !== "undefined" && SettingsData.m3ElevationOpacity !== undefined ? SettingsData.m3ElevationOpacity / 60 : 1
function normalizeElevationDirection(direction) {
switch (direction) {
case "top":
case "topLeft":
case "topRight":
case "bottom":
case "bottomLeft":
case "bottomRight":
case "left":
case "right":
case "autoBar":
return direction;
default:
return "top";
}
}
readonly property string elevationLightDirection: {
if (typeof SettingsData === "undefined" || !SettingsData.m3ElevationLightDirection)
return "top";
switch (SettingsData.m3ElevationLightDirection) {
case "autoBar":
case "top":
case "topLeft":
case "topRight":
case "bottom":
return SettingsData.m3ElevationLightDirection;
default:
return "top";
}
}
readonly property real _elevDiagRatio: 0.55
readonly property string _globalElevationDirForTokens: {
const normalized = normalizeElevationDirection(elevationLightDirection);
return normalized === "autoBar" ? "top" : normalized;
}
readonly property real _elevDirX: {
switch (_globalElevationDirForTokens) {
case "topLeft":
case "bottomLeft":
case "left":
return 1;
case "topRight":
case "bottomRight":
case "right":
return -1;
default:
return 0;
}
}
readonly property real _elevDirY: {
switch (_globalElevationDirForTokens) {
case "bottom":
case "bottomLeft":
case "bottomRight":
return -1;
case "left":
case "right":
return 0;
default:
return 1;
}
}
readonly property real _elevDirXScale: (_globalElevationDirForTokens === "left" || _globalElevationDirForTokens === "right") ? 1 : _elevDiagRatio
readonly property var elevationLevel1: ({
blurPx: 4 * _elevMult,
offsetX: 1 * _elevMult * _elevDirXScale * _elevDirX,
offsetY: 1 * _elevMult * _elevDirY,
spreadPx: 0,
alpha: 0.2 * _opMult
})
readonly property var elevationLevel2: ({
blurPx: 8 * _elevMult,
offsetX: 4 * _elevMult * _elevDirXScale * _elevDirX,
offsetY: 4 * _elevMult * _elevDirY,
spreadPx: 0,
alpha: 0.25 * _opMult
})
readonly property var elevationLevel3: ({
blurPx: 12 * _elevMult,
offsetX: 6 * _elevMult * _elevDirXScale * _elevDirX,
offsetY: 6 * _elevMult * _elevDirY,
spreadPx: 0,
alpha: 0.3 * _opMult
})
readonly property var elevationLevel4: ({
blurPx: 16 * _elevMult,
offsetX: 8 * _elevMult * _elevDirXScale * _elevDirX,
offsetY: 8 * _elevMult * _elevDirY,
spreadPx: 0,
alpha: 0.3 * _opMult
})
readonly property var elevationLevel5: ({
blurPx: 20 * _elevMult,
offsetX: 10 * _elevMult * _elevDirXScale * _elevDirX,
offsetY: 10 * _elevMult * _elevDirY,
spreadPx: 0,
alpha: 0.3 * _opMult
})
function elevationOffsetMagnitude(level, fallback, direction) {
if (!level) {
return fallback !== undefined ? Math.abs(fallback) : 0;
}
const yMag = Math.abs(level.offsetY !== undefined ? level.offsetY : 0);
if (yMag > 0)
return yMag;
const xMag = Math.abs(level.offsetX !== undefined ? level.offsetX : 0);
if (xMag > 0) {
if (direction === "left" || direction === "right")
return xMag;
return xMag / _elevDiagRatio;
}
return fallback !== undefined ? Math.abs(fallback) : 0;
}
function elevationOffsetXFor(level, direction, fallback) {
const dir = normalizeElevationDirection(direction || elevationLightDirection);
const mag = elevationOffsetMagnitude(level, fallback, dir);
switch (dir) {
case "topLeft":
case "bottomLeft":
return mag * _elevDiagRatio;
case "topRight":
case "bottomRight":
return -mag * _elevDiagRatio;
case "left":
return mag;
case "right":
return -mag;
default:
return 0;
}
}
function elevationOffsetYFor(level, direction, fallback) {
const dir = normalizeElevationDirection(direction || elevationLightDirection);
const mag = elevationOffsetMagnitude(level, fallback, dir);
switch (dir) {
case "bottom":
case "bottomLeft":
case "bottomRight":
return -mag;
case "left":
case "right":
return 0;
default:
return mag;
}
}
function elevationOffsetX(level, fallback) {
return elevationOffsetXFor(level, elevationLightDirection, fallback);
}
function elevationOffsetY(level, fallback) {
return elevationOffsetYFor(level, elevationLightDirection, fallback);
}
function elevationRenderPadding(level, direction, fallbackOffset, extraPadding, minPadding) {
const dir = direction !== undefined ? direction : elevationLightDirection;
const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0;
const spread = (level && level.spreadPx !== undefined) ? Math.max(0, level.spreadPx) : 0;
const fallback = fallbackOffset !== undefined ? fallbackOffset : 0;
const extra = extraPadding !== undefined ? extraPadding : 8;
const minPad = minPadding !== undefined ? minPadding : 16;
const offsetX = Math.abs(elevationOffsetXFor(level, dir, fallback));
const offsetY = Math.abs(elevationOffsetYFor(level, dir, fallback));
return Math.max(minPad, blur + spread + Math.max(offsetX, offsetY) + extra);
}
function elevationShadowColor(level) {
const alpha = (level && level.alpha !== undefined) ? level.alpha : 0.3;
let r = 0;
let g = 0;
let b = 0;
if (typeof SettingsData !== "undefined") {
const mode = SettingsData.m3ElevationColorMode || "default";
if (mode === "default") {
r = 0;
g = 0;
b = 0;
} else if (mode === "text") {
r = surfaceText.r;
g = surfaceText.g;
b = surfaceText.b;
} else if (mode === "primary") {
r = primary.r;
g = primary.g;
b = primary.b;
} else if (mode === "surfaceVariant") {
r = surfaceVariant.r;
g = surfaceVariant.g;
b = surfaceVariant.b;
} else if (mode === "custom" && SettingsData.m3ElevationCustomColor) {
const c = Qt.color(SettingsData.m3ElevationCustomColor);
r = c.r;
g = c.g;
b = c.b;
}
}
return Qt.rgba(r, g, b, alpha);
}
function elevationTintOpacity(level) {
if (!level)
return 0;
if (level === elevationLevel1)
return 0.05;
if (level === elevationLevel2)
return 0.08;
if (level === elevationLevel3)
return 0.11;
if (level === elevationLevel4)
return 0.12;
if (level === elevationLevel5)
return 0.14;
return 0.08;
}
readonly property var animationDurations: [ readonly property var animationDurations: [
{ {
"shorter": 0, "shorter": 0,
+11 -2
View File
@@ -21,7 +21,7 @@ var SPEC = {
widgetColorMode: { def: "default" }, widgetColorMode: { def: "default" },
controlCenterTileColorMode: { def: "primary" }, controlCenterTileColorMode: { def: "primary" },
buttonColorMode: { def: "primary" }, buttonColorMode: { def: "primary" },
cornerRadius: { def: 12, onChange: "updateCompositorLayout" }, cornerRadius: { def: 16, onChange: "updateCompositorLayout" },
niriLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" }, niriLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" },
niriLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" }, niriLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
niriLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" }, niriLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
@@ -46,6 +46,15 @@ var SPEC = {
modalAnimationSpeed: { def: 1 }, modalAnimationSpeed: { def: 1 },
modalCustomAnimationDuration: { def: 150 }, modalCustomAnimationDuration: { def: 150 },
enableRippleEffects: { def: true }, enableRippleEffects: { def: true },
m3ElevationEnabled: { def: true },
m3ElevationIntensity: { def: 12 },
m3ElevationOpacity: { def: 30 },
m3ElevationColorMode: { def: "default" },
m3ElevationLightDirection: { def: "top" },
m3ElevationCustomColor: { def: "#000000" },
modalElevationEnabled: { def: true },
popoutElevationEnabled: { def: true },
barElevationEnabled: { def: true },
wallpaperFillMode: { def: "Fill" }, wallpaperFillMode: { def: "Fill" },
blurredWallpaperLayer: { def: false }, blurredWallpaperLayer: { def: false },
blurWallpaperOnOverview: { def: false }, blurWallpaperOnOverview: { def: false },
@@ -425,7 +434,7 @@ var SPEC = {
scrollYBehavior: "workspace", scrollYBehavior: "workspace",
shadowIntensity: 0, shadowIntensity: 0,
shadowOpacity: 60, shadowOpacity: 60,
shadowColorMode: "text", shadowColorMode: "default",
shadowCustomColor: "#000000", shadowCustomColor: "#000000",
clickThrough: false clickThrough: false
}], onChange: "updateBarConfigs" }], onChange: "updateBarConfigs"
@@ -226,6 +226,25 @@ function migrateToVersion(obj, targetVersion) {
settings.configVersion = 5; settings.configVersion = 5;
} }
if (currentVersion < 6) {
console.info("Migrating settings from version", currentVersion, "to version 6");
if (settings.barElevationEnabled === undefined) {
var legacyBars = Array.isArray(settings.barConfigs) ? settings.barConfigs : [];
var hadLegacyBarShadowEnabled = false;
for (var j = 0; j < legacyBars.length; j++) {
var legacyIntensity = Number(legacyBars[j] && legacyBars[j].shadowIntensity);
if (!isNaN(legacyIntensity) && legacyIntensity > 0) {
hadLegacyBarShadowEnabled = true;
break;
}
}
settings.barElevationEnabled = hadLegacyBarShadowEnabled;
}
settings.configVersion = 6;
}
return settings; return settings;
} }
+17
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 { LazyLoader {
id: polkitAuthModalLoader id: polkitAuthModalLoader
active: false active: false
+16 -8
View File
@@ -32,9 +32,9 @@ Item {
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property color backgroundColor: Theme.surfaceContainer property color backgroundColor: Theme.surfaceContainer
property color borderColor: Theme.outlineMedium property color borderColor: Theme.outlineMedium
property real borderWidth: 1 property real borderWidth: 0
property real cornerRadius: Theme.cornerRadius property real cornerRadius: Theme.cornerRadius
property bool enableShadow: false property bool enableShadow: true
property alias modalFocusScope: focusScope property alias modalFocusScope: focusScope
property bool shouldBeVisible: false property bool shouldBeVisible: false
property bool shouldHaveFocus: shouldBeVisible property bool shouldHaveFocus: shouldBeVisible
@@ -142,7 +142,11 @@ Item {
} }
} }
readonly property real shadowBuffer: 5 readonly property var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: animationType === "slide" ? 30 : Math.max(0, animationOffset)
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
readonly property real alignedWidth: Theme.px(modalWidth, dpr) readonly property real alignedWidth: Theme.px(modalWidth, dpr)
readonly property real alignedHeight: Theme.px(modalHeight, dpr) readonly property real alignedHeight: Theme.px(modalHeight, dpr)
@@ -377,12 +381,16 @@ Item {
} }
} }
Rectangle { ElevationShadow {
id: modalShadowLayer
anchors.fill: parent anchors.fill: parent
color: root.backgroundColor level: root.shadowLevel
border.color: root.borderColor fallbackOffset: root.shadowFallbackOffset
border.width: root.borderWidth targetRadius: root.cornerRadius
radius: root.cornerRadius targetColor: root.backgroundColor
borderColor: root.borderColor
borderWidth: root.borderWidth
shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
} }
FocusScope { FocusScope {
+118 -19
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 pluginFilter: ""
property string activePluginName: "" property string activePluginName: ""
property var activePluginCategories: [] property var activePluginCategories: []
@@ -346,6 +351,10 @@ Item {
previousSearchMode = "all"; previousSearchMode = "all";
autoSwitchedToFiles = false; autoSwitchedToFiles = false;
isFileSearching = false; isFileSearching = false;
fileSearchType = "all";
fileSearchExt = "";
fileSearchFolder = "";
fileSearchSort = "score";
sections = []; sections = [];
flatModel = []; flatModel = [];
selectedFlatIndex = 0; selectedFlatIndex = 0;
@@ -399,6 +408,34 @@ Item {
performSearch(); 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() { function clearPluginFilter() {
if (pluginFilter) { if (pluginFilter) {
pluginFilter = ""; pluginFilter = "";
@@ -827,10 +864,20 @@ Item {
var params = { var params = {
limit: 20, limit: 20,
fuzzy: true, fuzzy: true,
sort: "score", sort: fileSearchSort || "score",
desc: true 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) { DSearchService.search(fileQuery, params, function (response) {
isFileSearching = false; isFileSearching = false;
if (response.error) if (response.error)
@@ -840,34 +887,73 @@ Item {
for (var i = 0; i < hits.length; i++) { for (var i = 0; i < hits.length; i++) {
var hit = hits[i]; var hit = hits[i];
var docTypes = hit.locations?.doc_type;
var isDir = docTypes ? !!docTypes["dir"] : false;
fileItems.push(transformFileResult({ fileItems.push(transformFileResult({
path: hit.id || "", path: hit.id || "",
score: hit.score || 0 score: hit.score || 0,
is_dir: isDir
})); }));
} }
var fileSection = { var fileSections = [];
id: "files", var showType = fileSearchType || "all";
title: I18n.tr("Files"),
icon: "folder", if (showType === "all" && DSearchService.supportsTypeFilter) {
priority: 4, var onlyFiles = [];
items: fileItems, var onlyDirs = [];
collapsed: collapsedSections["files"] || false, for (var j = 0; j < fileItems.length; j++) {
flatStartIndex: 0 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; var newSections;
if (searchMode === "files") { if (searchMode === "files") {
newSections = fileItems.length > 0 ? [fileSection] : []; newSections = fileSections;
} else { } else {
var existingNonFile = sections.filter(function (s) { var existingNonFile = sections.filter(function (s) {
return s.id !== "files"; return s.id !== "files" && s.id !== "folders";
}); });
if (fileItems.length > 0) { newSections = existingNonFile.concat(fileSections);
newSections = existingNonFile.concat([fileSection]);
} else {
newSections = existingNonFile;
}
} }
newSections.sort(function (a, b) { newSections.sort(function (a, b) {
return a.priority - b.priority; return a.priority - b.priority;
@@ -913,7 +999,7 @@ Item {
} }
function transformFileResult(file) { 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) { function detectTrigger(query) {
@@ -1581,6 +1667,9 @@ Item {
case "copy_path": case "copy_path":
copyToClipboard(item.data.path); copyToClipboard(item.data.path);
break; break;
case "open_terminal":
openTerminal(item.data.path);
break;
case "copy": case "copy":
copyToClipboard(item.name); copyToClipboard(item.name);
break; break;
@@ -1662,6 +1751,16 @@ Item {
Qt.openUrlExternally("file://" + folder); 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) { function copyToClipboard(text) {
if (!text) if (!text)
return; return;
@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
@@ -75,7 +76,7 @@ Item {
return Theme.primary; return Theme.primary;
} }
} }
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 1 readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
signal dialogClosed signal dialogClosed
@@ -107,6 +108,10 @@ Item {
spotlightContent.controller.activePluginId = ""; spotlightContent.controller.activePluginId = "";
spotlightContent.controller.activePluginName = ""; spotlightContent.controller.activePluginName = "";
spotlightContent.controller.pluginFilter = ""; spotlightContent.controller.pluginFilter = "";
spotlightContent.controller.fileSearchType = "all";
spotlightContent.controller.fileSearchExt = "";
spotlightContent.controller.fileSearchFolder = "";
spotlightContent.controller.fileSearchSort = "score";
spotlightContent.controller.collapsedSections = {}; spotlightContent.controller.collapsedSections = {};
spotlightContent.controller.selectedFlatIndex = 0; spotlightContent.controller.selectedFlatIndex = 0;
spotlightContent.controller.selectedItem = null; spotlightContent.controller.selectedItem = null;
@@ -386,12 +391,16 @@ Item {
} }
} }
Rectangle { ElevationShadow {
id: launcherShadowLayer
anchors.fill: parent anchors.fill: parent
color: root.backgroundColor level: Theme.elevationLevel3
border.color: root.borderColor fallbackOffset: 6
border.width: root.borderWidth targetColor: root.backgroundColor
radius: root.cornerRadius borderColor: root.borderColor
borderWidth: root.borderWidth
targetRadius: root.cornerRadius
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
} }
MouseArea { MouseArea {
@@ -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 filename = file.path ? file.path.split("/").pop() : "";
var dirname = file.path ? file.path.substring(0, file.path.lastIndexOf("/")) : ""; 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 { return {
id: file.path || "", id: file.path || "",
type: "file", type: "file",
name: filename, name: filename,
subtitle: dirname, subtitle: dirname,
icon: Utils.getFileIcon(filename), icon: isDir ? "folder" : Utils.getFileIcon(filename),
iconType: "material", iconType: "material",
section: "files", section: "files",
data: file, data: file,
actions: [ actions: actions,
{
name: openFolderLabel,
icon: "folder_open",
action: "open_folder"
},
{
name: copyPathLabel,
icon: "content_copy",
action: "copy_path"
}
],
primaryAction: { primaryAction: {
name: openLabel, name: openLabel,
icon: "open_in_new", icon: "open_in_new",
@@ -549,8 +549,151 @@ FocusScope {
} }
Item { Item {
id: fileFilterRow
width: parent.width 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 opacity: root.parentModal?.isClosing ? 0 : 1
ResultsList { ResultsList {
@@ -586,6 +729,9 @@ FocusScope {
function onSearchQueryRequested(query) { function onSearchQueryRequested(query) {
searchField.text = query; searchField.text = query;
} }
function onModeChanged() {
extFilterField.text = "";
}
} }
FocusScope { FocusScope {
@@ -113,6 +113,7 @@ Rectangle {
font.family: Theme.fontFamily font.family: Theme.fontFamily
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
elide: Text.ElideRight elide: Text.ElideRight
clip: true
visible: (root.item?.subtitle ?? "").length > 0 visible: (root.item?.subtitle ?? "").length > 0
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
} }
@@ -181,7 +182,7 @@ Rectangle {
case "plugin": case "plugin":
return I18n.tr("Plugin"); return I18n.tr("Plugin");
case "file": case "file":
return I18n.tr("File"); return root.item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
default: default:
return ""; return "";
} }
@@ -435,7 +435,15 @@ Item {
var mode = root.controller?.searchMode ?? "all"; var mode = root.controller?.searchMode ?? "all";
switch (mode) { switch (mode) {
case "files": 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": case "plugins":
return "extension"; return "extension";
case "apps": case "apps":
@@ -465,7 +473,15 @@ Item {
return I18n.tr("Type to search files"); return I18n.tr("Type to search files");
if (root.controller.searchQuery.length < 2) if (root.controller.searchQuery.length < 2)
return I18n.tr("Type at least 2 characters"); 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": case "plugins":
return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins"); return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins");
case "apps": case "apps":
+170
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
}
}
}
}
}
}
@@ -12,7 +12,7 @@ DankPopout {
id: root id: root
layerNamespace: "dms:control-center" layerNamespace: "dms:control-center"
fullHeightSurface: true fullHeightSurface: false
property string expandedSection: "" property string expandedSection: ""
property var triggerScreen: null property var triggerScreen: null
@@ -651,6 +651,7 @@ Rectangle {
} }
Rectangle { Rectangle {
id: pinButton
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS
anchors.verticalCenter: parent.verticalCenter 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 { DankRipple {
id: wifiRipple id: wifiRipple
cornerRadius: parent.radius cornerRadius: parent.radius
@@ -719,7 +733,7 @@ Rectangle {
MouseArea { MouseArea {
id: networkMouseArea id: networkMouseArea
anchors.fill: parent 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 hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => wifiRipple.trigger(mouse.x, mouse.y) onPressed: mouse => wifiRipple.trigger(mouse.x, mouse.y)
+64 -45
View File
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Effects
import QtQuick.Shapes import QtQuick.Shapes
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -53,15 +52,43 @@ Item {
} }
} }
readonly property real shadowIntensity: barConfig?.shadowIntensity ?? 0 // M3 elevation shadow Level 2 baseline (navigation bar), with per-bar override support
readonly property bool shadowEnabled: shadowIntensity > 0 readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0
readonly property int blurMax: 64 readonly property var elevLevel: Theme.elevationLevel2
readonly property real shadowBlurPx: shadowIntensity * 0.2 readonly property bool shadowEnabled: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride
readonly property real shadowBlur: Math.max(0, Math.min(1, shadowBlurPx / blurMax)) readonly property string autoBarShadowDirection: isTop ? "top" : (isBottom ? "bottom" : (isLeft ? "left" : (isRight ? "right" : "top")))
readonly property real shadowOpacity: (barConfig?.shadowOpacity ?? 60) / 100 readonly property string globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
readonly property string shadowColorMode: barConfig?.shadowColorMode ?? "text" readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit"
readonly property color shadowBaseColor: { readonly property string perBarManualShadowDirection: {
switch (shadowColorMode) { switch (barConfig?.shadowDirection) {
case "top":
case "topLeft":
case "topRight":
case "bottom":
return barConfig.shadowDirection;
default:
return "top";
}
}
readonly property string effectiveShadowDirection: {
if (!hasPerBarOverride)
return globalShadowDirection;
switch (perBarShadowDirectionMode) {
case "autoBar":
return autoBarShadowDirection;
case "manual":
return perBarManualShadowDirection === "autoBar" ? autoBarShadowDirection : perBarManualShadowDirection;
default:
return globalShadowDirection;
}
}
// Per-bar override values (when barConfig.shadowIntensity > 0)
readonly property real overrideBlurPx: (barConfig?.shadowIntensity ?? 0) * 0.2
readonly property real overrideOpacity: (barConfig?.shadowOpacity ?? 60) / 100
readonly property string overrideColorMode: barConfig?.shadowColorMode ?? "default"
readonly property color overrideBaseColor: {
switch (overrideColorMode) {
case "surface": case "surface":
return Theme.surface; return Theme.surface;
case "primary": case "primary":
@@ -71,10 +98,16 @@ Item {
case "custom": case "custom":
return barConfig?.shadowCustomColor ?? "#000000"; return barConfig?.shadowCustomColor ?? "#000000";
default: default:
return Theme.surfaceText; return "#000000";
} }
} }
readonly property color shadowColor: Theme.withAlpha(shadowBaseColor, shadowOpacity * barWindow._backgroundAlpha)
// Resolved values per-bar override wins if set, otherwise use global M3 elevation
readonly property real shadowBlurPx: hasPerBarOverride ? overrideBlurPx : (elevLevel.blurPx ?? 8)
readonly property color shadowColor: hasPerBarOverride ? Theme.withAlpha(overrideBaseColor, overrideOpacity) : Theme.elevationShadowColor(elevLevel)
readonly property real shadowOffsetMagnitude: hasPerBarOverride ? (overrideBlurPx * 0.5) : Theme.elevationOffsetMagnitude(elevLevel, 4, effectiveShadowDirection)
readonly property real shadowOffsetX: Theme.elevationOffsetXFor(hasPerBarOverride ? null : elevLevel, effectiveShadowDirection, shadowOffsetMagnitude)
readonly property real shadowOffsetY: Theme.elevationOffsetYFor(hasPerBarOverride ? null : elevLevel, effectiveShadowDirection, shadowOffsetMagnitude)
readonly property string mainPath: generatePathForPosition(width, height) readonly property string mainPath: generatePathForPosition(width, height)
readonly property string borderFullPath: generateBorderFullPath(width, height) readonly property string borderFullPath: generateBorderFullPath(width, height)
@@ -118,42 +151,28 @@ Item {
} }
} }
Loader { ElevationShadow {
id: shadowLoader id: barShadow
anchors.fill: parent visible: root.shadowEnabled && root.width > 0 && root.height > 0
active: root.shadowEnabled && mainPathCorrectShape
asynchronous: false
sourceComponent: Item {
anchors.fill: parent
layer.enabled: true // Size to the bar's rectangular body, excluding gothic wing extensions
layer.smooth: true x: root.isRight ? root.wing : 0
layer.samples: 8 y: root.isBottom ? root.wing : 0
layer.textureSize: Qt.size(Math.round(width * barWindow._dpr * 2), Math.round(height * barWindow._dpr * 2)) width: axis.isVertical ? (parent.width - root.wing) : parent.width
layer.effect: MultiEffect { height: axis.isVertical ? parent.height : (parent.height - root.wing)
shadowEnabled: true
shadowBlur: root.shadowBlur
shadowColor: root.shadowColor
shadowVerticalOffset: root.isTop ? root.shadowBlurPx * 0.5 : (root.isBottom ? -root.shadowBlurPx * 0.5 : 0)
shadowHorizontalOffset: root.isLeft ? root.shadowBlurPx * 0.5 : (root.isRight ? -root.shadowBlurPx * 0.5 : 0)
autoPaddingEnabled: true
}
Shape { shadowEnabled: root.shadowEnabled
anchors.fill: parent level: root.hasPerBarOverride ? null : root.elevLevel
preferredRendererType: Shape.CurveRenderer direction: root.effectiveShadowDirection
fallbackOffset: 4
targetRadius: root.rt
targetColor: barWindow._bgColor
ShapePath { shadowBlurPx: root.shadowBlurPx
fillColor: barWindow._bgColor shadowOffsetX: root.shadowOffsetX
strokeColor: "transparent" shadowOffsetY: root.shadowOffsetY
strokeWidth: 0 shadowColor: root.shadowColor
blurMax: Theme.elevationBlurMax
PathSvg {
path: root.mainPath
}
}
}
}
} }
Loader { Loader {
+17 -3
View File
@@ -140,6 +140,20 @@ PanelWindow {
} }
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen) readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
// Shadow buffer: extra window space for shadow to render beyond bar bounds
readonly property bool _shadowActive: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || (barConfig?.shadowIntensity ?? 0) > 0
readonly property real _shadowBuffer: {
if (!_shadowActive)
return 0;
const hasOverride = (barConfig?.shadowIntensity ?? 0) > 0;
if (hasOverride) {
const blur = (barConfig.shadowIntensity ?? 0) * 0.2;
const offset = blur * 0.5;
return Theme.snap(Math.max(16, blur + offset + 8), _dpr);
}
return Theme.snap(Theme.elevationRenderPadding(Theme.elevationLevel2, "top", 4, 8, 16), _dpr);
}
property string screenName: modelData.name property string screenName: modelData.name
property bool hasMaximizedToplevel: false property bool hasMaximizedToplevel: false
@@ -354,8 +368,8 @@ PanelWindow {
} }
screen: modelData screen: modelData
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) : 0 implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) : 0 implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
color: "transparent" color: "transparent"
property var nativeInhibitor: null property var nativeInhibitor: null
@@ -624,7 +638,7 @@ PanelWindow {
Item { Item {
id: topBarCore id: topBarCore
anchors.fill: parent anchors.fill: parent
layer.enabled: true layer.enabled: false
property bool autoHide: barConfig?.autoHide ?? false property bool autoHide: barConfig?.autoHide ?? false
property bool revealSticky: false property bool revealSticky: false
@@ -167,9 +167,22 @@ DankPopout {
} }
Column { Column {
id: headerInfoColumn
spacing: Theme.spacingXS spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.width - Theme.iconSizeLarge - 32 - Theme.spacingM * 2 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 { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -207,21 +220,35 @@ DankPopout {
} }
} }
StyledText { Row {
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
width: parent.width 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)
}
} }
} }
+10 -5
View File
@@ -29,12 +29,21 @@ Loader {
readonly property bool orientationMatches: (axis?.isVertical ?? false) === isInColumn 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) active: orientationMatches && getWidgetVisible(widgetId, DgopService.dgopAvailable) && (widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: getWidgetComponent(widgetId, components) sourceComponent: getWidgetComponent(widgetId, components)
opacity: getWidgetEnabled(widgetData?.enabled) ? 1 : 0
signal contentItemReady(var item) signal contentItemReady(var item)
Binding {
target: root.item
when: root.item && !root.widgetEnabled
property: "visible"
value: false
restoreMode: Binding.RestoreBinding
}
Binding { Binding {
target: root.item target: root.item
when: root.item && "parentScreen" in root.item when: root.item && "parentScreen" in root.item
@@ -269,8 +278,4 @@ Loader {
return widgetVisibility[widgetId] ?? true; return widgetVisibility[widgetId] ?? true;
} }
function getWidgetEnabled(enabled) {
return enabled !== false;
}
} }
@@ -9,6 +9,7 @@ BasePill {
property var widgetData: null property var widgetData: null
property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/" 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 isHovered: mouseArea.containsMouse
property bool isAutoHideBar: false property bool isAutoHideBar: false
@@ -130,7 +131,13 @@ BasePill {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) { if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--"; 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) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor color: Theme.widgetTextColor
@@ -178,7 +185,13 @@ BasePill {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) { if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--%"; 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) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor color: Theme.widgetTextColor
@@ -189,7 +202,14 @@ BasePill {
StyledTextMetrics { StyledTextMetrics {
id: diskBaseline id: diskBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) 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) width: Math.max(diskBaseline.width, paintedWidth)
+6 -4
View File
@@ -178,8 +178,9 @@ BasePill {
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) { if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = parent.mapToItem(null, 0, 0); const globalPos = parent.mapToItem(null, 0, 0);
const currentScreen = root.parentScreen || Screen; const currentScreen = root.parentScreen || Screen;
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, parent.width); const barPosition = root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1));
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen); const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, parent.width, root.barSpacing, barPosition, root.barConfig);
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen, barPosition, root.barThickness, root.barSpacing, root.barConfig);
} }
root.clicked(); root.clicked();
} }
@@ -334,8 +335,9 @@ BasePill {
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) { if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = mapToItem(null, 0, 0); const globalPos = mapToItem(null, 0, 0);
const currentScreen = root.parentScreen || Screen; const currentScreen = root.parentScreen || Screen;
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, root.width); const barPosition = root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1));
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen); const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, root.width, root.barSpacing, barPosition, root.barConfig);
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen, barPosition, root.barThickness, root.barSpacing, root.barConfig);
} }
root.clicked(); root.clicked();
} }
@@ -14,6 +14,7 @@ BasePill {
property var widgetData: null property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
property bool showSwap: (widgetData && widgetData.showSwap !== undefined) ? widgetData.showSwap : false 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 readonly property real swapUsage: DgopService.totalSwapKB > 0 ? (DgopService.usedSwapKB / DgopService.totalSwapKB) * 100 : 0
signal ramClicked signal ramClicked
@@ -59,6 +60,10 @@ BasePill {
return "--"; return "--";
} }
if (root.showInGb) {
return (DgopService.usedMemoryMB / 1024).toFixed(1);
}
return DgopService.memoryUsage.toFixed(0); return DgopService.memoryUsage.toFixed(0);
} }
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
@@ -113,13 +118,14 @@ BasePill {
id: ramBaseline id: ramBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
text: { text: {
let baseText = root.showInGb ? "88.8 GB" : "88%";
if (!root.showSwap) { if (!root.showSwap) {
return "88%"; return baseText;
} }
if (root.swapUsage < 10) { if (root.swapUsage < 10) {
return "88% · 0%"; return baseText + " · 0%";
} }
return "88% · 88%"; return baseText + " · 88%";
} }
} }
@@ -127,10 +133,16 @@ BasePill {
id: ramText id: ramText
text: { text: {
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) { 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) { if (root.showSwap && DgopService.totalSwapKB > 0) {
return ramText + " · " + root.swapUsage.toFixed(0) + "%"; return ramText + " · " + root.swapUsage.toFixed(0) + "%";
} }
@@ -41,6 +41,12 @@ BasePill {
return `${id}::${tooltipTitle}`; return `${id}::${tooltipTitle}`;
} }
// ! TODO - replace with either native dbus client (like plugins use) or just a DMS cli or something
function callContextMenuFallback(trayItemId, globalX, globalY) {
const script = ['ITEMS=$(dbus-send --session --print-reply --dest=org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierWatcher string:RegisteredStatusNotifierItems 2>/dev/null)', 'while IFS= read -r line; do', ' line="${line#*\\\"}"', ' line="${line%\\\"*}"', ' [ -z "$line" ] && continue', ' BUS="${line%%/*}"', ' OBJ="/${line#*/}"', ' ID=$(dbus-send --session --print-reply --dest="$BUS" "$OBJ" org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierItem string:Id 2>/dev/null | grep -oP "(?<=\\\")(.*?)(?=\\\")" | tail -1)', ' if [ "$ID" = "$1" ]; then', ' dbus-send --session --type=method_call --dest="$BUS" "$OBJ" org.kde.StatusNotifierItem.ContextMenu int32:"$2" int32:"$3"', ' exit 0', ' fi', 'done <<< "$ITEMS"',].join("\n");
Quickshell.execDetached(["bash", "-c", script, "_", trayItemId, String(globalX), String(globalY)]);
}
property int _trayOrderTrigger: 0 property int _trayOrderTrigger: 0
Connections { Connections {
@@ -380,8 +386,11 @@ BasePill {
return; return;
if (mouse.button !== Qt.RightButton) if (mouse.button !== Qt.RightButton)
return; return;
if (!delegateRoot.trayItem?.hasMenu) if (!delegateRoot.trayItem?.hasMenu) {
const gp = trayItemArea.mapToGlobal(mouse.x, mouse.y);
root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
return; return;
}
root.menuOpen = false; root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis); root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
@@ -637,8 +646,11 @@ BasePill {
return; return;
if (mouse.button !== Qt.RightButton) if (mouse.button !== Qt.RightButton)
return; return;
if (!delegateRoot.trayItem?.hasMenu) if (!delegateRoot.trayItem?.hasMenu) {
const gp = trayItemArea.mapToGlobal(mouse.x, mouse.y);
root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
return; return;
}
root.menuOpen = false; root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis); root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
@@ -928,9 +940,10 @@ BasePill {
} }
})(), overflowMenu.dpr) })(), overflowMenu.dpr)
property real shadowBlurPx: 10 readonly property var elev: Theme.elevationLevel2
property real shadowSpreadPx: 0 property real shadowBlurPx: elev && elev.blurPx !== undefined ? elev.blurPx : 8
property real shadowBaseAlpha: 0.60 property real shadowSpreadPx: elev && elev.spreadPx !== undefined ? elev.spreadPx : 0
property real shadowBaseAlpha: elev && elev.alpha !== undefined ? elev.alpha : 0.25
readonly property real popupSurfaceAlpha: Theme.popupTransparency readonly property real popupSurfaceAlpha: Theme.popupTransparency
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha)) readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
@@ -951,37 +964,26 @@ BasePill {
} }
} }
Item { ElevationShadow {
id: bgShadowLayer id: bgShadowLayer
anchors.fill: parent anchors.fill: parent
layer.enabled: true level: menuContainer.elev
fallbackOffset: 4
shadowBlurPx: menuContainer.shadowBlurPx
shadowSpreadPx: menuContainer.shadowSpreadPx
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
}
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
targetRadius: Theme.cornerRadius
sourceRect.antialiasing: true
sourceRect.smooth: true
shadowEnabled: Theme.elevationEnabled
layer.smooth: true layer.smooth: true
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2)) layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically
layer.samples: 4 layer.samples: 4
readonly property int blurMax: 64
layer.effect: MultiEffect {
autoPaddingEnabled: true
shadowEnabled: true
blurEnabled: false
maskEnabled: false
shadowBlur: Math.max(0, Math.min(1, menuContainer.shadowBlurPx / bgShadowLayer.blurMax))
shadowScale: 1 + (2 * menuContainer.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
}
}
Rectangle {
anchors.fill: parent
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
antialiasing: true
smooth: true
}
} }
Grid { Grid {
@@ -1065,9 +1067,11 @@ BasePill {
root.menuOpen = false; root.menuOpen = false;
return; return;
} }
if (!trayItem.hasMenu) {
if (!trayItem.hasMenu) const gp = itemArea.mapToGlobal(mouse.x, mouse.y);
root.callContextMenuFallback(trayItem.id, Math.round(gp.x), Math.round(gp.y));
return; return;
}
root.showForTrayItem(trayItem, menuContainer, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis); root.showForTrayItem(trayItem, menuContainer, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
} }
@@ -1398,9 +1402,10 @@ BasePill {
} }
})(), menuWindow.dpr) })(), menuWindow.dpr)
property real shadowBlurPx: 10 readonly property var elev: Theme.elevationLevel2
property real shadowSpreadPx: 0 property real shadowBlurPx: elev && elev.blurPx !== undefined ? elev.blurPx : 8
property real shadowBaseAlpha: 0.60 property real shadowSpreadPx: elev && elev.spreadPx !== undefined ? elev.spreadPx : 0
property real shadowBaseAlpha: elev && elev.alpha !== undefined ? elev.alpha : 0.25
readonly property real popupSurfaceAlpha: Theme.popupTransparency readonly property real popupSurfaceAlpha: Theme.popupTransparency
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha)) readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
@@ -1421,35 +1426,24 @@ BasePill {
} }
} }
Item { ElevationShadow {
id: menuBgShadowLayer id: menuBgShadowLayer
anchors.fill: parent anchors.fill: parent
layer.enabled: true level: menuContainer.elev
fallbackOffset: 4
shadowBlurPx: menuContainer.shadowBlurPx
shadowSpreadPx: menuContainer.shadowSpreadPx
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
}
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
targetRadius: Theme.cornerRadius
sourceRect.antialiasing: true
shadowEnabled: Theme.elevationEnabled
layer.smooth: true layer.smooth: true
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr)) layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically
readonly property int blurMax: 64
layer.effect: MultiEffect {
autoPaddingEnabled: true
shadowEnabled: true
blurEnabled: false
maskEnabled: false
shadowBlur: Math.max(0, Math.min(1, menuContainer.shadowBlurPx / menuBgShadowLayer.blurMax))
shadowScale: 1 + (2 * menuContainer.shadowSpreadPx) / Math.max(1, Math.min(menuBgShadowLayer.width, menuBgShadowLayer.height))
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
}
}
Rectangle {
anchors.fill: parent
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
antialiasing: true
}
} }
QsMenuAnchor { QsMenuAnchor {
@@ -177,8 +177,9 @@ BasePill {
if (popoutTarget && popoutTarget.setTriggerPosition) { if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToItem(null, 0, 0); const globalPos = root.visualContent.mapToItem(null, 0, 0);
const currentScreen = parentScreen || Screen; const currentScreen = parentScreen || Screen;
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth); const barPosition = root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1));
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen); const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth, root.barSpacing, barPosition, root.barConfig);
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen, barPosition, barThickness, root.barSpacing, root.barConfig);
} }
root.clicked(); root.clicked();
} }
@@ -89,14 +89,18 @@ Item {
} }
} }
layer.enabled: true ElevationShadow {
layer.effect: MultiEffect { id: volumeShadowLayer
shadowEnabled: true anchors.fill: parent
shadowHorizontalOffset: 0 z: -1
shadowVerticalOffset: 8 level: Theme.elevationLevel2
shadowBlur: 1.0 fallbackOffset: 4
shadowColor: Qt.rgba(0, 0, 0, 0.4) targetRadius: volumePanel.radius
shadowOpacity: 0.7 targetColor: volumePanel.color
borderColor: volumePanel.border.color
borderWidth: volumePanel.border.width
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
shadowEnabled: Theme.elevationEnabled
} }
MouseArea { MouseArea {
@@ -223,14 +227,18 @@ Item {
} }
} }
layer.enabled: true ElevationShadow {
layer.effect: MultiEffect { id: audioDevicesShadowLayer
shadowEnabled: true anchors.fill: parent
shadowHorizontalOffset: 0 z: -1
shadowVerticalOffset: 8 level: Theme.elevationLevel2
shadowBlur: 1.0 fallbackOffset: 4
shadowColor: Qt.rgba(0, 0, 0, 0.4) targetRadius: audioDevicesPanel.radius
shadowOpacity: 0.7 targetColor: audioDevicesPanel.color
borderColor: audioDevicesPanel.border.color
borderWidth: audioDevicesPanel.border.width
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
shadowEnabled: Theme.elevationEnabled
} }
Column { Column {
@@ -373,14 +381,18 @@ Item {
} }
} }
layer.enabled: true ElevationShadow {
layer.effect: MultiEffect { id: playersShadowLayer
shadowEnabled: true anchors.fill: parent
shadowHorizontalOffset: 0 z: -1
shadowVerticalOffset: 8 level: Theme.elevationLevel2
shadowBlur: 1.0 fallbackOffset: 4
shadowColor: Qt.rgba(0, 0, 0, 0.4) targetRadius: playersPanel.radius
shadowOpacity: 0.7 targetColor: playersPanel.color
borderColor: playersPanel.border.color
borderWidth: playersPanel.border.width
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
shadowEnabled: Theme.elevationEnabled
} }
Column { Column {
@@ -529,14 +529,15 @@ Item {
onClicked: activePlayer && activePlayer.togglePlaying() onClicked: activePlayer && activePlayer.togglePlaying()
} }
layer.enabled: true ElevationShadow {
layer.effect: MultiEffect { anchors.fill: parent
shadowEnabled: true z: -1
shadowHorizontalOffset: 0 level: Theme.elevationLevel1
shadowVerticalOffset: 0 fallbackOffset: 1
shadowBlur: 1.0 targetRadius: parent.radius
shadowColor: Qt.rgba(0, 0, 0, 0.3) targetColor: parent.color
shadowOpacity: 0.3 shadowOpacity: Theme.elevationLevel1 && Theme.elevationLevel1.alpha !== undefined ? Theme.elevationLevel1.alpha : 0.2
shadowEnabled: Theme.elevationEnabled
} }
} }
} }
+22 -21
View File
@@ -241,14 +241,15 @@ Item {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
layer.enabled: true layer.enabled: Theme.elevationEnabled
layer.effect: MultiEffect { layer.effect: MultiEffect {
shadowEnabled: true shadowEnabled: Theme.elevationEnabled
shadowHorizontalOffset: 0 shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel1)
shadowVerticalOffset: 4 shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel1, 1)
shadowBlur: 0.8 shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel1 && Theme.elevationLevel1.blurPx !== undefined ? Theme.elevationLevel1.blurPx : 4) / Theme.elevationBlurMax)) : 0
shadowColor: Qt.rgba(0, 0, 0, 0.2) blurMax: Theme.elevationBlurMax
shadowOpacity: 0.2 shadowColor: Theme.elevationShadowColor(Theme.elevationLevel1)
shadowOpacity: Theme.elevationLevel1 && Theme.elevationLevel1.alpha !== undefined ? Theme.elevationLevel1.alpha : 0.2
} }
} }
@@ -812,14 +813,14 @@ Item {
x: (pos?.h ?? 0) * skyBox.effectiveWidth - (moonPhase.width / 2) + skyBox.hMargin x: (pos?.h ?? 0) * skyBox.effectiveWidth - (moonPhase.width / 2) + skyBox.hMargin
y: (pos?.v ?? 0) * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 - (moonPhase.height / 2) + skyBox.vMargin y: (pos?.v ?? 0) * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 - (moonPhase.height / 2) + skyBox.vMargin
layer.enabled: true layer.enabled: Theme.elevationEnabled
layer.effect: MultiEffect { layer.effect: MultiEffect {
shadowEnabled: true shadowEnabled: Theme.elevationEnabled
shadowHorizontalOffset: 0 shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel2)
shadowVerticalOffset: 4 shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel2, 4)
shadowBlur: 0.8 shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel2 && Theme.elevationLevel2.blurPx !== undefined ? Theme.elevationLevel2.blurPx : 8) / Theme.elevationBlurMax)) : 0
shadowColor: Qt.rgba(0, 0, 0, 0.2) blurMax: Theme.elevationBlurMax
shadowOpacity: 0.2 shadowColor: Theme.elevationShadowColor(Theme.elevationLevel2)
} }
} }
@@ -834,14 +835,14 @@ Item {
x: (pos?.h ?? 0) * skyBox.effectiveWidth - (sun.width / 2) + skyBox.hMargin x: (pos?.h ?? 0) * skyBox.effectiveWidth - (sun.width / 2) + skyBox.hMargin
y: (pos?.v ?? 0) * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 - (sun.height / 2) + skyBox.vMargin y: (pos?.v ?? 0) * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 - (sun.height / 2) + skyBox.vMargin
layer.enabled: true layer.enabled: Theme.elevationEnabled
layer.effect: MultiEffect { layer.effect: MultiEffect {
shadowEnabled: true shadowEnabled: Theme.elevationEnabled
shadowHorizontalOffset: 0 shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel2)
shadowVerticalOffset: 4 shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel2, 4)
shadowBlur: 0.8 shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel2 && Theme.elevationLevel2.blurPx !== undefined ? Theme.elevationLevel2.blurPx : 8) / Theme.elevationBlurMax)) : 0
shadowColor: Qt.rgba(0, 0, 0, 0.2) blurMax: Theme.elevationBlurMax
shadowOpacity: 0.2 shadowColor: Theme.elevationShadowColor(Theme.elevationLevel2)
} }
} }
} }
@@ -41,6 +41,11 @@ Singleton {
property string lockDateFormat: "" property string lockDateFormat: ""
property bool lockScreenShowPowerActions: true property bool lockScreenShowPowerActions: true
property bool lockScreenShowProfileImage: 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 var screenPreferences: ({})
property int animationSpeed: 2 property int animationSpeed: 2
property string wallpaperFillMode: "Fill" property string wallpaperFillMode: "Fill"
@@ -75,6 +80,11 @@ Singleton {
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : ""; lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true; lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
lockScreenShowProfileImage = settings.lockScreenShowProfileImage !== undefined ? settings.lockScreenShowProfileImage : 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 : ({}); screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2; animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill"; wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";
@@ -1231,6 +1231,12 @@ Item {
LockPowerMenu { LockPowerMenu {
id: powerMenu id: powerMenu
showLogout: false showLogout: false
powerActionConfirmOverride: GreetdSettings.powerActionConfirm
powerActionHoldDurationOverride: GreetdSettings.powerActionHoldDuration
powerMenuActionsOverride: GreetdSettings.powerMenuActions
powerMenuDefaultActionOverride: GreetdSettings.powerMenuDefaultAction
powerMenuGridLayoutOverride: GreetdSettings.powerMenuGridLayout
requiredActions: ["poweroff"]
onClosed: { onClosed: {
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) { if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
Qt.callLater(() => inputField.forceActiveFocus()); Qt.callLater(() => inputField.forceActiveFocus());
+20 -7
View File
@@ -24,13 +24,20 @@ Rectangle {
property real holdProgress: 0 property real holdProgress: 0
property bool showHoldHint: false property bool showHoldHint: false
readonly property bool needsConfirmation: SettingsData.powerActionConfirm property var powerActionConfirmOverride: undefined
readonly property int holdDurationMs: SettingsData.powerActionHoldDuration * 1000 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 signal closed
function updateVisibleActions() { 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; const hibernateSupported = (typeof SessionService !== "undefined" && SessionService.hibernateSupported) || false;
let filtered = allActions.filter(action => { let filtered = allActions.filter(action => {
if (action === "hibernate" && !hibernateSupported) if (action === "hibernate" && !hibernateSupported)
@@ -44,9 +51,14 @@ Rectangle {
return true; return true;
}); });
for (const action of requiredActions) {
if (!filtered.includes(action))
filtered.push(action);
}
visibleActions = filtered; 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) if (!useGridLayout)
return; return;
const count = visibleActions.length; const count = visibleActions.length;
@@ -73,7 +85,7 @@ Rectangle {
} }
function getDefaultActionIndex() { 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); const index = visibleActions.indexOf(defaultAction);
return index >= 0 ? index : 0; return index >= 0 ? index : 0;
} }
@@ -780,8 +792,9 @@ Rectangle {
} }
StyledText { 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 int remainingMs: Math.ceil(totalMs * (1 - root.holdProgress))
readonly property real durationSec: root.holdDurationMs / 1000
text: { text: {
if (root.showHoldHint) if (root.showHoldHint)
return I18n.tr("Hold longer to confirm"); return I18n.tr("Hold longer to confirm");
@@ -792,7 +805,7 @@ Rectangle {
} }
if (totalMs < 1000) if (totalMs < 1000)
return I18n.tr("Hold to confirm (%1 ms)").arg(totalMs); 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 font.pixelSize: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -30,7 +31,21 @@ Rectangle {
width: parent ? parent.width : 400 width: parent ? parent.width : 400
height: baseCardHeight + contentItem.extraHeight height: baseCardHeight + contentItem.extraHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
clip: true clip: false
readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
ElevationShadow {
id: shadowLayer
anchors.fill: parent
z: -1
level: Theme.elevationLevel1
fallbackOffset: 1
targetRadius: root.radius
targetColor: root.color
borderColor: root.border.color
borderWidth: root.border.width
shadowEnabled: root.shadowsAllowed
}
color: { color: {
if (isSelected && keyboardNavigationActive) if (isSelected && keyboardNavigationActive)
@@ -49,7 +64,7 @@ Rectangle {
return 1.5; return 1.5;
if (historyItem.urgency === 2) if (historyItem.urgency === 2)
return 2; return 2;
return 1; return 0;
} }
Behavior on border.color { Behavior on border.color {
@@ -232,6 +232,11 @@ Item {
height: parent.height - filterChips.height - Theme.spacingS height: parent.height - filterChips.height - Theme.spacingS
clip: true clip: true
spacing: Theme.spacingS spacing: Theme.spacingS
readonly property real horizontalShadowGutter: Theme.snap(Math.max(Theme.spacingXS, 4), 1)
readonly property real verticalShadowGutter: Theme.snap(Math.max(Theme.spacingS, 8), 1)
readonly property real delegateShadowGutter: Theme.snap(Math.max(Theme.spacingXS, 4), 1)
topMargin: verticalShadowGutter
bottomMargin: verticalShadowGutter
model: ScriptModel { model: ScriptModel {
id: historyModel id: historyModel
@@ -263,13 +268,14 @@ Item {
} }
width: ListView.view.width width: ListView.view.width
height: historyCard.height height: historyCard.height + historyListView.delegateShadowGutter
clip: true clip: false
HistoryNotificationCard { HistoryNotificationCard {
id: historyCard id: historyCard
width: parent.width width: Math.max(0, parent.width - (historyListView.horizontalShadowGutter * 2))
x: delegateRoot.swipeOffset y: historyListView.delegateShadowGutter / 2
x: historyListView.horizontalShadowGutter + delegateRoot.swipeOffset
historyItem: modelData historyItem: modelData
isSelected: root.keyboardActive && root.selectedIndex === index isSelected: root.keyboardActive && root.selectedIndex === index
keyboardNavigationActive: root.keyboardActive keyboardNavigationActive: root.keyboardActive
@@ -18,6 +18,10 @@ DankListView {
property real swipingCardOffset: 0 property real swipingCardOffset: 0
property real __pendingStableHeight: 0 property real __pendingStableHeight: 0
property real __heightUpdateThreshold: 20 property real __heightUpdateThreshold: 20
readonly property real shadowBlurPx: Theme.elevationEnabled ? ((Theme.elevationLevel1 && Theme.elevationLevel1.blurPx !== undefined) ? Theme.elevationLevel1.blurPx : 4) : 0
readonly property real shadowHorizontalGutter: Theme.snap(Math.max(Theme.spacingS, Math.min(32, shadowBlurPx * 1.5 + 6)), 1)
readonly property real shadowVerticalGutter: Theme.snap(Math.max(Theme.spacingXS, 6), 1)
readonly property real delegateShadowGutter: Theme.snap(Math.max(Theme.spacingXS, 4), 1)
Component.onCompleted: { Component.onCompleted: {
Qt.callLater(() => { Qt.callLater(() => {
@@ -56,21 +60,26 @@ DankListView {
let delta = 0; let delta = 0;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const item = itemAtIndex(i); const item = itemAtIndex(i);
if (item && item.children[0] && item.children[0].isAnimating) if (item && item.children[0] && item.children[0].isAnimating) {
delta += item.children[0].targetHeight - item.height; const targetDelegateHeight = item.children[0].targetHeight + listView.delegateShadowGutter;
delta += targetDelegateHeight - item.height;
}
} }
const targetHeight = contentHeight + delta; const targetHeight = contentHeight + delta;
// During expansion, always update immediately without threshold check // During expansion, always update immediately without threshold check
stableContentHeight = targetHeight; stableContentHeight = targetHeight;
} else { } else {
__pendingStableHeight = contentHeight; __pendingStableHeight = contentHeight;
heightUpdateDebounce.restart(); heightUpdateDebounce.stop();
stableContentHeight = __pendingStableHeight;
} }
} }
clip: true clip: true
model: NotificationService.groupedNotifications model: NotificationService.groupedNotifications
spacing: Theme.spacingL spacing: Theme.spacingL
topMargin: shadowVerticalGutter
bottomMargin: shadowVerticalGutter
onIsUserScrollingChanged: { onIsUserScrollingChanged: {
if (isUserScrolling && keyboardController && keyboardController.keyboardNavigationActive) { if (isUserScrolling && keyboardController && keyboardController.keyboardNavigationActive) {
@@ -134,8 +143,7 @@ DankListView {
readonly property real dismissThreshold: width * 0.35 readonly property real dismissThreshold: width * 0.35
property bool __delegateInitialized: false property bool __delegateInitialized: false
readonly property bool isAdjacentToSwipe: listView.count >= 2 && listView.swipingCardIndex !== -1 && readonly property bool isAdjacentToSwipe: listView.count >= 2 && listView.swipingCardIndex !== -1 && (index === listView.swipingCardIndex - 1 || index === listView.swipingCardIndex + 1)
(index === listView.swipingCardIndex - 1 || index === listView.swipingCardIndex + 1)
readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? listView.swipingCardOffset * 0.10 : 0 readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? listView.swipingCardOffset * 0.10 : 0
readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(listView.swipingCardOffset) / width * 0.02 : 1.0 readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(listView.swipingCardOffset) / width * 0.02 : 1.0
readonly property real swipeFadeStartOffset: width * 0.75 readonly property real swipeFadeStartOffset: width * 0.75
@@ -149,13 +157,14 @@ DankListView {
} }
width: ListView.view.width width: ListView.view.width
height: notificationCard.height height: notificationCard.height + listView.delegateShadowGutter
clip: notificationCard.isAnimating clip: false
NotificationCard { NotificationCard {
id: notificationCard id: notificationCard
width: parent.width width: Math.max(0, parent.width - (listView.shadowHorizontalGutter * 2))
x: delegateRoot.swipeOffset + delegateRoot.adjacentSwipeInfluence y: listView.delegateShadowGutter / 2
x: listView.shadowHorizontalGutter + delegateRoot.swipeOffset + delegateRoot.adjacentSwipeInfluence
listLevelAdjacentScaleInfluence: delegateRoot.adjacentScaleInfluence listLevelAdjacentScaleInfluence: delegateRoot.adjacentScaleInfluence
listLevelScaleAnimationsEnabled: listView.swipingCardIndex === -1 || !delegateRoot.isAdjacentToSwipe listLevelScaleAnimationsEnabled: listView.swipingCardIndex === -1 || !delegateRoot.isAdjacentToSwipe
notificationGroup: modelData notificationGroup: modelData
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import qs.Common import qs.Common
@@ -38,7 +39,14 @@ Rectangle {
height: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight) height: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight) readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
radius: Theme.cornerRadius radius: Theme.cornerRadius
scale: (cardHoverHandler.hovered ? 1.01 : 1.0) * listLevelAdjacentScaleInfluence scale: (cardHoverHandler.hovered ? 1.004 : 1.0) * listLevelAdjacentScaleInfluence
readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
readonly property var shadowElevation: Theme.elevationLevel1
readonly property real baseShadowBlurPx: (shadowElevation && shadowElevation.blurPx !== undefined) ? shadowElevation.blurPx : 4
readonly property real hoverShadowBlurBoost: cardHoverHandler.hovered ? Math.min(2, baseShadowBlurPx * 0.25) : 0
property real shadowBlurPx: shadowsAllowed ? (baseShadowBlurPx + hoverShadowBlurBoost) : 0
property real shadowOffsetXPx: shadowsAllowed ? Theme.elevationOffsetX(shadowElevation) : 0
property real shadowOffsetYPx: shadowsAllowed ? (Theme.elevationOffsetY(shadowElevation, 1) + (cardHoverHandler.hovered ? 0.35 : 0)) : 0
property bool __initialized: false property bool __initialized: false
Component.onCompleted: { Component.onCompleted: {
@@ -56,6 +64,27 @@ Rectangle {
} }
} }
Behavior on shadowBlurPx {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on shadowOffsetXPx {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on shadowOffsetYPx {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on border.color { Behavior on border.color {
enabled: root.__initialized enabled: root.__initialized
ColorAnimation { ColorAnimation {
@@ -95,14 +124,31 @@ Rectangle {
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) { if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
return 2; return 2;
} }
return 1; return 0;
} }
clip: true clip: false
HoverHandler { HoverHandler {
id: cardHoverHandler id: cardHoverHandler
} }
ElevationShadow {
id: shadowLayer
anchors.fill: parent
z: -1
level: root.shadowElevation
targetRadius: root.radius
targetColor: root.color
borderColor: root.border.color
borderWidth: root.border.width
shadowBlurPx: root.shadowBlurPx
shadowSpreadPx: 0
shadowOffsetX: root.shadowOffsetXPx
shadowOffsetY: root.shadowOffsetYPx
shadowColor: root.shadowElevation ? Theme.elevationShadowColor(root.shadowElevation) : "transparent"
shadowEnabled: root.shadowsAllowed
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: parent.radius radius: parent.radius
@@ -304,8 +350,13 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) { if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
root.userInitiatedExpansion = true;
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""; const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "";
NotificationService.toggleMessageExpansion(messageId); NotificationService.toggleMessageExpansion(messageId);
Qt.callLater(() => {
if (root && !root.isAnimating)
root.userInitiatedExpansion = false;
});
} }
} }
@@ -419,9 +470,7 @@ Rectangle {
id: delegateRect id: delegateRect
width: parent.width width: parent.width
readonly property bool isAdjacentToSwipe: root.swipingNotificationIndex !== -1 && readonly property bool isAdjacentToSwipe: root.swipingNotificationIndex !== -1 && (expandedDelegateWrapper.index === root.swipingNotificationIndex - 1 || expandedDelegateWrapper.index === root.swipingNotificationIndex + 1)
(expandedDelegateWrapper.index === root.swipingNotificationIndex - 1 ||
expandedDelegateWrapper.index === root.swipingNotificationIndex + 1)
readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? root.swipingNotificationOffset * 0.10 : 0 readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? root.swipingNotificationOffset * 0.10 : 0
readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(root.swipingNotificationOffset) / width * 0.02 : 1.0 readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(root.swipingNotificationOffset) / width * 0.02 : 1.0
@@ -605,7 +654,12 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) { if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
root.userInitiatedExpansion = true;
NotificationService.toggleMessageExpansion(modelData?.notification?.id || ""); NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
Qt.callLater(() => {
if (root && !root.isAnimating)
root.userInitiatedExpansion = false;
});
} }
} }
@@ -7,15 +7,22 @@ DankPopout {
id: root id: root
layerNamespace: "dms:notification-center-popout" layerNamespace: "dms:notification-center-popout"
fullHeightSurface: true fullHeightSurface: false
property bool notificationHistoryVisible: false property bool notificationHistoryVisible: false
property var triggerScreen: null property var triggerScreen: null
property real stablePopupHeight: 400 property real stablePopupHeight: 400
property real _lastAlignedContentHeight: -1 property real _lastAlignedContentHeight: -1
property bool _pendingSizedOpen: false
function updateStablePopupHeight() { function updateStablePopupHeight() {
const item = contentLoader.item; const item = contentLoader.item;
if (item && !root.shouldBeVisible) {
const notificationList = findChild(item, "notificationList");
if (notificationList && typeof notificationList.forceLayout === "function") {
notificationList.forceLayout();
}
}
const target = item ? Theme.px(item.implicitHeight, dpr) : 400; const target = item ? Theme.px(item.implicitHeight, dpr) : 400;
if (Math.abs(target - _lastAlignedContentHeight) < 0.5) if (Math.abs(target - _lastAlignedContentHeight) < 0.5)
return; return;
@@ -26,7 +33,7 @@ DankPopout {
NotificationKeyboardController { NotificationKeyboardController {
id: keyboardController id: keyboardController
listView: null listView: null
isOpen: notificationHistoryVisible isOpen: root.shouldBeVisible
onClose: () => { onClose: () => {
notificationHistoryVisible = false; notificationHistoryVisible = false;
} }
@@ -40,20 +47,42 @@ DankPopout {
suspendShadowWhileResizing: false suspendShadowWhileResizing: false
screen: triggerScreen screen: triggerScreen
shouldBeVisible: notificationHistoryVisible
function toggle() { function toggle() {
notificationHistoryVisible = !notificationHistoryVisible; notificationHistoryVisible = !notificationHistoryVisible;
} }
function openSized() {
if (!notificationHistoryVisible)
return;
primeContent();
if (contentLoader.item) {
updateStablePopupHeight();
_pendingSizedOpen = false;
Qt.callLater(() => {
if (!notificationHistoryVisible)
return;
updateStablePopupHeight();
open();
clearPrimedContent();
});
return;
}
_pendingSizedOpen = true;
}
onBackgroundClicked: { onBackgroundClicked: {
notificationHistoryVisible = false; notificationHistoryVisible = false;
} }
onNotificationHistoryVisibleChanged: { onNotificationHistoryVisibleChanged: {
if (notificationHistoryVisible) { if (notificationHistoryVisible) {
open(); openSized();
} else { } else {
_pendingSizedOpen = false;
clearPrimedContent();
close(); close();
} }
} }
@@ -82,6 +111,17 @@ DankPopout {
target: contentLoader target: contentLoader
function onLoaded() { function onLoaded() {
root.updateStablePopupHeight(); root.updateStablePopupHeight();
if (root._pendingSizedOpen && root.notificationHistoryVisible) {
Qt.callLater(() => {
if (!root._pendingSizedOpen || !root.notificationHistoryVisible)
return;
root.updateStablePopupHeight();
root._pendingSizedOpen = false;
root.open();
root.clearPrimedContent();
});
return;
}
if (root.shouldBeVisible) if (root.shouldBeVisible)
Qt.callLater(root.setupKeyboardNavigation); Qt.callLater(root.setupKeyboardNavigation);
} }
@@ -139,7 +179,8 @@ DankPopout {
baseHeight += Theme.spacingM * 2; baseHeight += Theme.spacingM * 2;
const settingsHeight = notificationSettings.expanded ? notificationSettings.contentHeight : 0; const settingsHeight = notificationSettings.expanded ? notificationSettings.contentHeight : 0;
let listHeight = notificationHeader.currentTab === 0 ? notificationList.stableContentHeight : Math.max(200, NotificationService.historyList.length * 80); const currentListHeight = root.shouldBeVisible ? notificationList.stableContentHeight : notificationList.listContentHeight;
let listHeight = notificationHeader.currentTab === 0 ? currentListHeight : Math.max(200, NotificationService.historyList.length * 80);
if (notificationHeader.currentTab === 0 && NotificationService.groupedNotifications.length === 0) { if (notificationHeader.currentTab === 0 && NotificationService.groupedNotifications.length === 0) {
listHeight = 200; listHeight = 200;
} }
@@ -233,13 +274,21 @@ DankPopout {
expanded: notificationHeader.showSettings expanded: notificationHeader.showSettings
} }
KeyboardNavigatedNotificationList { Item {
id: notificationList
objectName: "notificationList"
visible: notificationHeader.currentTab === 0 visible: notificationHeader.currentTab === 0
width: parent.width width: parent.width
height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2 height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2
cardAnimateExpansion: true
KeyboardNavigatedNotificationList {
id: notificationList
objectName: "notificationList"
anchors.fill: parent
anchors.leftMargin: -shadowHorizontalGutter
anchors.rightMargin: -shadowHorizontalGutter
anchors.topMargin: -(shadowVerticalGutter + delegateShadowGutter / 2)
anchors.bottomMargin: -(shadowVerticalGutter + delegateShadowGutter / 2)
cardAnimateExpansion: true
}
} }
HistoryNotificationList { HistoryNotificationList {
@@ -118,8 +118,8 @@ PanelWindow {
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
implicitWidth: screen ? Math.min(400, Math.max(320, screen.width * 0.23)) : 380 readonly property real contentImplicitWidth: screen ? Math.min(400, Math.max(320, screen.width * 0.23)) : 380
implicitHeight: { readonly property real contentImplicitHeight: {
if (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded) if (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded)
return basePopupHeightPrivacy; return basePopupHeightPrivacy;
if (!descriptionExpanded) if (!descriptionExpanded)
@@ -130,6 +130,8 @@ PanelWindow {
return basePopupHeight + bodyTextHeight - collapsedBodyHeight; return basePopupHeight + bodyTextHeight - collapsedBodyHeight;
return basePopupHeight; return basePopupHeight;
} }
implicitWidth: contentImplicitWidth + (windowShadowPad * 2)
implicitHeight: contentImplicitHeight + (windowShadowPad * 2)
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: !exiting && !_isDestroying enabled: !exiting && !_isDestroying
@@ -182,11 +184,15 @@ PanelWindow {
property bool isTopCenter: SettingsData.notificationPopupPosition === -1 property bool isTopCenter: SettingsData.notificationPopupPosition === -1
property bool isBottomCenter: SettingsData.notificationPopupPosition === SettingsData.Position.BottomCenter property bool isBottomCenter: SettingsData.notificationPopupPosition === SettingsData.Position.BottomCenter
property bool isCenterPosition: isTopCenter || isBottomCenter property bool isCenterPosition: isTopCenter || isBottomCenter
readonly property real maxPopupShadowBlurPx: Math.max((Theme.elevationLevel3 && Theme.elevationLevel3.blurPx !== undefined) ? Theme.elevationLevel3.blurPx : 12, (Theme.elevationLevel4 && Theme.elevationLevel4.blurPx !== undefined) ? Theme.elevationLevel4.blurPx : 16)
readonly property real maxPopupShadowOffsetXPx: Math.max(Math.abs(Theme.elevationOffsetX(Theme.elevationLevel3)), Math.abs(Theme.elevationOffsetX(Theme.elevationLevel4)))
readonly property real maxPopupShadowOffsetYPx: Math.max(Math.abs(Theme.elevationOffsetY(Theme.elevationLevel3, 6)), Math.abs(Theme.elevationOffsetY(Theme.elevationLevel4, 8)))
readonly property real windowShadowPad: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled ? Theme.snap(Math.max(16, maxPopupShadowBlurPx + Math.max(maxPopupShadowOffsetXPx, maxPopupShadowOffsetYPx) + 8), dpr) : 0
anchors.top: true anchors.top: true
anchors.bottom: true anchors.left: true
anchors.left: SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom anchors.bottom: false
anchors.right: SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Right anchors.right: false
mask: contentInputMask mask: contentInputMask
@@ -205,10 +211,10 @@ PanelWindow {
} }
margins { margins {
top: _storedTopMargin top: getWindowTopMargin()
bottom: _storedBottomMargin bottom: 0
left: getLeftMargin() left: getWindowLeftMargin()
right: getRightMargin() right: 0
} }
function getBarInfo() { function getBarInfo() {
@@ -250,7 +256,7 @@ PanelWindow {
function getLeftMargin() { function getLeftMargin() {
if (isCenterPosition) if (isCenterPosition)
return screen ? (screen.width - implicitWidth) / 2 : 0; return screen ? (screen.width - alignedWidth) / 2 : 0;
const popupPos = SettingsData.notificationPopupPosition; const popupPos = SettingsData.notificationPopupPosition;
const isLeft = popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom; const isLeft = popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom;
@@ -274,23 +280,56 @@ PanelWindow {
return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance; return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance;
} }
function getContentX() {
if (!screen)
return 0;
const popupPos = SettingsData.notificationPopupPosition;
const barLeft = getLeftMargin();
const barRight = getRightMargin();
if (isCenterPosition)
return Theme.snap((screen.width - alignedWidth) / 2, dpr);
if (popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom)
return Theme.snap(barLeft, dpr);
return Theme.snap(screen.width - alignedWidth - barRight, dpr);
}
function getContentY() {
if (!screen)
return 0;
const popupPos = SettingsData.notificationPopupPosition;
const barTop = getTopMargin();
const barBottom = getBottomMargin();
const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left;
if (isTop)
return Theme.snap(barTop, dpr);
return Theme.snap(screen.height - alignedHeight - barBottom, dpr);
}
function getWindowLeftMargin() {
if (!screen)
return 0;
return Theme.snap(getContentX() - windowShadowPad, dpr);
}
function getWindowTopMargin() {
if (!screen)
return 0;
return Theme.snap(getContentY() - windowShadowPad, dpr);
}
readonly property bool screenValid: win.screen && !_isDestroying readonly property bool screenValid: win.screen && !_isDestroying
readonly property real dpr: screenValid ? CompositorService.getScreenScale(win.screen) : 1 readonly property real dpr: screenValid ? CompositorService.getScreenScale(win.screen) : 1
readonly property real alignedWidth: Theme.px(implicitWidth, dpr) readonly property real alignedWidth: Theme.px(Math.max(0, implicitWidth - (windowShadowPad * 2)), dpr)
readonly property real alignedHeight: Theme.px(implicitHeight, dpr) readonly property real alignedHeight: Theme.px(Math.max(0, implicitHeight - (windowShadowPad * 2)), dpr)
Item { Item {
id: content id: content
x: Theme.snap((win.width - alignedWidth) / 2, dpr) x: Theme.snap(windowShadowPad, dpr)
y: { y: Theme.snap(windowShadowPad, dpr)
const isTop = isTopCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Left;
if (isTop) {
return Theme.snap(screenY, dpr);
} else {
return Theme.snap(win.height - alignedHeight - screenY, dpr);
}
}
width: alignedWidth width: alignedWidth
height: alignedHeight height: alignedHeight
visible: !win._finalized visible: !win._finalized
@@ -313,12 +352,13 @@ PanelWindow {
readonly property bool swipeActive: swipeDragHandler.active readonly property bool swipeActive: swipeDragHandler.active
property bool swipeDismissing: false property bool swipeDismissing: false
readonly property real radiusForShadow: Theme.cornerRadius readonly property bool shadowsAllowed: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled
property real shadowBlurPx: SettingsData.notificationPopupShadowEnabled ? ((2 + radiusForShadow * 0.2) * (cardHoverHandler.hovered ? 1.2 : 1)) : 0 readonly property var elevLevel: cardHoverHandler.hovered ? Theme.elevationLevel4 : Theme.elevationLevel3
property real shadowSpreadPx: SettingsData.notificationPopupShadowEnabled ? (radiusForShadow * (cardHoverHandler.hovered ? 0.06 : 0)) : 0 readonly property real cardInset: Theme.snap(4, win.dpr)
property real shadowBaseAlpha: 0.35 readonly property real shadowRenderPadding: shadowsAllowed ? Theme.snap(Math.max(16, shadowBlurPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)) + 8), win.dpr) : 0
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency property real shadowBlurPx: shadowsAllowed ? (elevLevel && elevLevel.blurPx !== undefined ? elevLevel.blurPx : 12) : 0
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha)) property real shadowOffsetX: shadowsAllowed ? Theme.elevationOffsetX(elevLevel) : 0
property real shadowOffsetY: shadowsAllowed ? Theme.elevationOffsetY(elevLevel, 6) : 0
Behavior on shadowBlurPx { Behavior on shadowBlurPx {
NumberAnimation { NumberAnimation {
@@ -327,50 +367,50 @@ PanelWindow {
} }
} }
Behavior on shadowSpreadPx { Behavior on shadowOffsetX {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Item { Behavior on shadowOffsetY {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
ElevationShadow {
id: bgShadowLayer id: bgShadowLayer
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.snap(4, win.dpr) anchors.margins: -content.shadowRenderPadding
layer.enabled: !win._isDestroying && win.screenValid level: content.elevLevel
layer.smooth: false fallbackOffset: 6
shadowBlurPx: content.shadowBlurPx
shadowOffsetX: content.shadowOffsetX
shadowOffsetY: content.shadowOffsetY
shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent"
shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed
layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr)) layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically
readonly property int blurMax: 64 sourceRect.anchors.fill: undefined
sourceRect.x: content.shadowRenderPadding + content.cardInset
layer.effect: MultiEffect { sourceRect.y: content.shadowRenderPadding + content.cardInset
id: shadowFx sourceRect.width: Math.max(0, content.width - (content.cardInset * 2))
autoPaddingEnabled: true sourceRect.height: Math.max(0, content.height - (content.cardInset * 2))
shadowEnabled: SettingsData.notificationPopupShadowEnabled sourceRect.radius: Theme.cornerRadius
blurEnabled: false sourceRect.color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
maskEnabled: false sourceRect.border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08)
shadowBlur: Math.max(0, Math.min(1, content.shadowBlurPx / bgShadowLayer.blurMax)) sourceRect.border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
shadowScale: 1 + (2 * content.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, content.effectiveShadowAlpha);
}
}
Rectangle { Rectangle {
id: shadowShapeSource x: bgShadowLayer.sourceRect.x
anchors.fill: parent y: bgShadowLayer.sourceRect.y
radius: Theme.cornerRadius width: bgShadowLayer.sourceRect.width
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) height: bgShadowLayer.sourceRect.height
border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08) radius: bgShadowLayer.sourceRect.radius
border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
}
Rectangle {
anchors.fill: parent
radius: shadowShapeSource.radius
visible: notificationData && notificationData.urgency === NotificationUrgency.Critical visible: notificationData && notificationData.urgency === NotificationUrgency.Critical
opacity: 1 opacity: 1
clip: true clip: true
@@ -399,7 +439,7 @@ PanelWindow {
Item { Item {
id: backgroundContainer id: backgroundContainer
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.snap(4, win.dpr) anchors.margins: content.cardInset
clip: true clip: true
HoverHandler { HoverHandler {
@@ -351,6 +351,7 @@ Item {
Loader { Loader {
id: contentLoader id: contentLoader
anchors.fill: parent anchors.fill: parent
active: root.widgetEnabled && root.activeComponent !== null
sourceComponent: root.activeComponent sourceComponent: root.activeComponent
function reloadComponent() { function reloadComponent() {
+11 -6
View File
@@ -878,12 +878,17 @@ Item {
x: hoveredButton ? hoveredButton.mapToItem(aboutTab, hoveredButton.width / 2, 0).x - width / 2 : 0 x: hoveredButton ? hoveredButton.mapToItem(aboutTab, hoveredButton.width / 2, 0).x - width / 2 : 0
y: hoveredButton ? communityIcons.mapToItem(aboutTab, 0, 0).y - height - 8 : 0 y: hoveredButton ? communityIcons.mapToItem(aboutTab, 0, 0).y - height - 8 : 0
layer.enabled: true ElevationShadow {
layer.effect: MultiEffect { anchors.fill: parent
shadowEnabled: true z: -1
shadowOpacity: 0.15 level: Theme.elevationLevel1
shadowVerticalOffset: 2 fallbackOffset: 1
shadowBlur: 0.5 targetRadius: communityTooltip.radius
targetColor: communityTooltip.color
borderColor: communityTooltip.border.color
borderWidth: communityTooltip.border.width
shadowOpacity: Theme.elevationLevel1 && Theme.elevationLevel1.alpha !== undefined ? Theme.elevationLevel1.alpha : 0.2
shadowEnabled: Theme.elevationEnabled
} }
StyledText { StyledText {
+242 -133
View File
@@ -52,9 +52,11 @@ Item {
} }
function _isBarActive(c) { function _isBarActive(c) {
if (!c.enabled) return false; if (!c.enabled)
return false;
const prefs = c.screenPreferences || ["all"]; const prefs = c.screenPreferences || ["all"];
if (prefs.length > 0) return true; if (prefs.length > 0)
return true;
return (c.showOnLastDisplay ?? true) && Quickshell.screens.length === 1; return (c.showOnLastDisplay ?? true) && Quickshell.screens.length === 1;
} }
@@ -64,7 +66,8 @@ Item {
return; return;
const hasHorizontal = configs.some(c => { const hasHorizontal = configs.some(c => {
if (!_isBarActive(c)) return false; if (!_isBarActive(c))
return false;
const p = c.position ?? SettingsData.Position.Top; const p = c.position ?? SettingsData.Position.Top;
return p === SettingsData.Position.Top || p === SettingsData.Position.Bottom; return p === SettingsData.Position.Top || p === SettingsData.Position.Bottom;
}); });
@@ -72,7 +75,8 @@ Item {
return; return;
const hasVertical = configs.some(c => { const hasVertical = configs.some(c => {
if (!_isBarActive(c)) return false; if (!_isBarActive(c))
return false;
const p = c.position ?? SettingsData.Position.Top; const p = c.position ?? SettingsData.Position.Top;
return p === SettingsData.Position.Left || p === SettingsData.Position.Right; return p === SettingsData.Position.Left || p === SettingsData.Position.Right;
}); });
@@ -136,7 +140,9 @@ Item {
scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace", scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace",
shadowIntensity: defaultBar.shadowIntensity ?? 0, shadowIntensity: defaultBar.shadowIntensity ?? 0,
shadowOpacity: defaultBar.shadowOpacity ?? 60, shadowOpacity: defaultBar.shadowOpacity ?? 60,
shadowColorMode: defaultBar.shadowColorMode ?? "text", shadowDirectionMode: defaultBar.shadowDirectionMode ?? "inherit",
shadowDirection: defaultBar.shadowDirection ?? "top",
shadowColorMode: defaultBar.shadowColorMode ?? "default",
shadowCustomColor: defaultBar.shadowCustomColor ?? "#000000" shadowCustomColor: defaultBar.shadowCustomColor ?? "#000000"
}; };
SettingsData.addBarConfig(newBar); SettingsData.addBarConfig(newBar);
@@ -1038,6 +1044,237 @@ Item {
} }
} }
SettingsCard {
id: shadowCard
iconName: "layers"
title: I18n.tr("Shadow Override", "bar shadow settings card")
settingKey: "barShadow"
collapsible: true
expanded: true
visible: selectedBarConfig?.enabled
readonly property bool shadowActive: (selectedBarConfig?.shadowIntensity ?? 0) > 0
readonly property bool isCustomColor: (selectedBarConfig?.shadowColorMode ?? "default") === "custom"
readonly property string directionSource: selectedBarConfig?.shadowDirectionMode ?? "inherit"
StyledText {
width: parent.width
text: I18n.tr("Enable a custom override below to set per-bar shadow intensity, opacity, and color.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignLeft
}
SettingsToggleRow {
text: I18n.tr("Custom Shadow Override")
description: I18n.tr("Override the global shadow with per-bar settings")
checked: shadowCard.shadowActive
onToggled: checked => {
if (checked) {
SettingsData.updateBarConfig(selectedBarId, {
shadowIntensity: 12,
shadowOpacity: 60
});
} else {
SettingsData.updateBarConfig(selectedBarId, {
shadowIntensity: 0
});
}
}
}
SettingsSliderRow {
visible: shadowCard.shadowActive
text: I18n.tr("Intensity", "shadow intensity slider")
minimum: 0
maximum: 100
unit: "px"
defaultValue: 12
value: selectedBarConfig?.shadowIntensity ?? 0
onSliderValueChanged: newValue => SettingsData.updateBarConfig(selectedBarId, {
shadowIntensity: newValue
})
}
SettingsSliderRow {
visible: shadowCard.shadowActive
text: I18n.tr("Opacity")
minimum: 10
maximum: 100
unit: "%"
defaultValue: 60
value: selectedBarConfig?.shadowOpacity ?? 60
onSliderValueChanged: newValue => SettingsData.updateBarConfig(selectedBarId, {
shadowOpacity: newValue
})
}
SettingsDropdownRow {
visible: shadowCard.shadowActive
text: I18n.tr("Direction Source", "bar shadow direction source")
description: I18n.tr("Choose how this bar resolves shadow direction")
settingKey: "barShadowDirectionSource"
options: [I18n.tr("Inherit Global (Default)", "bar shadow direction source option"), I18n.tr("Auto (Bar-aware)", "bar shadow direction source option"), I18n.tr("Manual", "bar shadow direction source option")]
currentValue: {
switch (shadowCard.directionSource) {
case "autoBar":
return I18n.tr("Auto (Bar-aware)", "bar shadow direction source option");
case "manual":
return I18n.tr("Manual", "bar shadow direction source option");
default:
return I18n.tr("Inherit Global (Default)", "bar shadow direction source option");
}
}
onValueChanged: value => {
if (value === I18n.tr("Auto (Bar-aware)", "bar shadow direction source option")) {
SettingsData.updateBarConfig(selectedBarId, {
shadowDirectionMode: "autoBar"
});
} else if (value === I18n.tr("Manual", "bar shadow direction source option")) {
SettingsData.updateBarConfig(selectedBarId, {
shadowDirectionMode: "manual"
});
} else {
SettingsData.updateBarConfig(selectedBarId, {
shadowDirectionMode: "inherit"
});
}
}
}
SettingsDropdownRow {
visible: shadowCard.shadowActive && shadowCard.directionSource === "manual"
text: I18n.tr("Manual Direction", "bar manual shadow direction")
description: I18n.tr("Use a fixed shadow direction for this bar")
settingKey: "barShadowDirectionManual"
options: [I18n.tr("Top", "shadow direction option"), I18n.tr("Top Left", "shadow direction option"), I18n.tr("Top Right", "shadow direction option"), I18n.tr("Bottom", "shadow direction option")]
currentValue: {
switch (selectedBarConfig?.shadowDirection) {
case "topLeft":
return I18n.tr("Top Left", "shadow direction option");
case "topRight":
return I18n.tr("Top Right", "shadow direction option");
case "bottom":
return I18n.tr("Bottom", "shadow direction option");
default:
return I18n.tr("Top", "shadow direction option");
}
}
onValueChanged: value => {
if (value === I18n.tr("Top Left", "shadow direction option")) {
SettingsData.updateBarConfig(selectedBarId, {
shadowDirection: "topLeft"
});
} else if (value === I18n.tr("Top Right", "shadow direction option")) {
SettingsData.updateBarConfig(selectedBarId, {
shadowDirection: "topRight"
});
} else if (value === I18n.tr("Bottom", "shadow direction option")) {
SettingsData.updateBarConfig(selectedBarId, {
shadowDirection: "bottom"
});
} else {
SettingsData.updateBarConfig(selectedBarId, {
shadowDirection: "top"
});
}
}
}
Column {
visible: shadowCard.shadowActive
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Color")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignLeft
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
}
Item {
width: parent.width
height: shadowColorGroup.implicitHeight
DankButtonGroup {
id: shadowColorGroup
anchors.horizontalCenter: parent.horizontalCenter
buttonPadding: parent.width < 420 ? Theme.spacingXS : Theme.spacingS
minButtonWidth: parent.width < 420 ? 36 : 56
textSize: parent.width < 420 ? Theme.fontSizeSmall : Theme.fontSizeMedium
model: [I18n.tr("Default (Black)"), I18n.tr("Surface", "shadow color option"), I18n.tr("Primary"), I18n.tr("Secondary"), I18n.tr("Custom")]
selectionMode: "single"
currentIndex: {
switch (selectedBarConfig?.shadowColorMode || "default") {
case "surface":
return 1;
case "primary":
return 2;
case "secondary":
return 3;
case "custom":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
let mode = "default";
switch (index) {
case 1:
mode = "surface";
break;
case 2:
mode = "primary";
break;
case 3:
mode = "secondary";
break;
case 4:
mode = "custom";
break;
}
SettingsData.updateBarConfig(selectedBarId, {
shadowColorMode: mode
});
}
}
}
Rectangle {
visible: selectedBarConfig?.shadowColorMode === "custom"
width: 32
height: 32
radius: 16
color: selectedBarConfig?.shadowCustomColor ?? "#000000"
border.color: Theme.outline
border.width: 1
anchors.horizontalCenter: parent.horizontalCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
PopoutService.colorPickerModal.selectedColor = selectedBarConfig?.shadowCustomColor ?? "#000000";
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Color");
PopoutService.colorPickerModal.onColorSelectedCallback = function (color) {
SettingsData.updateBarConfig(selectedBarId, {
shadowCustomColor: color.toString()
});
};
PopoutService.colorPickerModal.show();
}
}
}
}
}
SettingsCard { SettingsCard {
iconName: "rounded_corner" iconName: "rounded_corner"
title: I18n.tr("Corners & Background") title: I18n.tr("Corners & Background")
@@ -1140,134 +1377,6 @@ Item {
} }
} }
SettingsCard {
id: shadowCard
iconName: "layers"
title: I18n.tr("Shadow", "bar shadow settings card")
settingKey: "barShadow"
collapsible: true
expanded: false
visible: selectedBarConfig?.enabled
readonly property bool shadowActive: (selectedBarConfig?.shadowIntensity ?? 0) > 0
readonly property bool isCustomColor: (selectedBarConfig?.shadowColorMode ?? "text") === "custom"
SettingsSliderRow {
text: I18n.tr("Intensity", "shadow intensity slider")
minimum: 0
maximum: 100
unit: "%"
value: selectedBarConfig?.shadowIntensity ?? 0
onSliderValueChanged: newValue => SettingsData.updateBarConfig(selectedBarId, {
shadowIntensity: newValue
})
}
SettingsSliderRow {
visible: shadowCard.shadowActive
text: I18n.tr("Opacity")
minimum: 10
maximum: 100
unit: "%"
value: selectedBarConfig?.shadowOpacity ?? 60
onSliderValueChanged: newValue => SettingsData.updateBarConfig(selectedBarId, {
shadowOpacity: newValue
})
}
Column {
visible: shadowCard.shadowActive
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Color")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignLeft
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
}
Item {
width: parent.width
height: shadowColorGroup.implicitHeight
DankButtonGroup {
id: shadowColorGroup
anchors.horizontalCenter: parent.horizontalCenter
buttonPadding: parent.width < 420 ? Theme.spacingXS : Theme.spacingS
minButtonWidth: parent.width < 420 ? 36 : 56
textSize: parent.width < 420 ? Theme.fontSizeSmall : Theme.fontSizeMedium
model: [I18n.tr("Text", "shadow color option"), I18n.tr("Surface", "shadow color option"), I18n.tr("Primary"), I18n.tr("Secondary"), I18n.tr("Custom")]
selectionMode: "single"
currentIndex: {
switch (selectedBarConfig?.shadowColorMode || "text") {
case "surface":
return 1;
case "primary":
return 2;
case "secondary":
return 3;
case "custom":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
let mode = "text";
switch (index) {
case 1:
mode = "surface";
break;
case 2:
mode = "primary";
break;
case 3:
mode = "secondary";
break;
case 4:
mode = "custom";
break;
}
SettingsData.updateBarConfig(selectedBarId, {
shadowColorMode: mode
});
}
}
}
Rectangle {
visible: selectedBarConfig?.shadowColorMode === "custom"
width: 32
height: 32
radius: 16
color: selectedBarConfig?.shadowCustomColor ?? "#000000"
border.color: Theme.outline
border.width: 1
anchors.horizontalCenter: parent.horizontalCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
PopoutService.colorPickerModal.selectedColor = selectedBarConfig?.shadowCustomColor ?? "#000000";
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Color");
PopoutService.colorPickerModal.onColorSelectedCallback = function (color) {
SettingsData.updateBarConfig(selectedBarId, {
shadowCustomColor: color.toString()
});
};
PopoutService.colorPickerModal.show();
}
}
}
}
}
SettingsToggleCard { SettingsToggleCard {
iconName: "border_style" iconName: "border_style"
title: I18n.tr("Border") title: I18n.tr("Border")
@@ -1281,6 +1281,15 @@ Item {
} }
} }
DankActionButton {
iconName: "qr_code"
buttonSize: 28
visible: modelData.secured && modelData.saved
onClicked: {
PopoutService.showWifiQRCodeModal(modelData.ssid);
}
}
DankActionButton { DankActionButton {
iconName: isPinned ? "push_pin" : "push_pin" iconName: isPinned ? "push_pin" : "push_pin"
buttonSize: 28 buttonSize: 28
@@ -274,7 +274,7 @@ Item {
settingKey: "notificationPopupShadowEnabled" settingKey: "notificationPopupShadowEnabled"
tags: ["notification", "popup", "shadow", "radius", "rounded"] tags: ["notification", "popup", "shadow", "radius", "rounded"]
text: I18n.tr("Popup Shadow") text: I18n.tr("Popup Shadow")
description: I18n.tr("Show drop shadow on notification popups") description: I18n.tr("Show drop shadow on notification popups. Requires M3 Elevation to be enabled in Theme & Colors.")
checked: SettingsData.notificationPopupShadowEnabled checked: SettingsData.notificationPopupShadowEnabled
onToggled: checked => SettingsData.set("notificationPopupShadowEnabled", checked) onToggled: checked => SettingsData.set("notificationPopupShadowEnabled", checked)
} }
+220 -26
View File
@@ -126,6 +126,15 @@ Item {
return Theme.warning; return Theme.warning;
} }
function openM3ShadowColorPicker() {
PopoutService.colorPickerModal.selectedColor = SettingsData.m3ElevationCustomColor ?? "#000000";
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Shadow Color");
PopoutService.colorPickerModal.onColorSelectedCallback = function (color) {
SettingsData.set("m3ElevationCustomColor", color.toString());
};
PopoutService.colorPickerModal.show();
}
function formatThemeAutoTime(isoString) { function formatThemeAutoTime(isoString) {
if (!isoString) if (!isoString)
return ""; return "";
@@ -1592,6 +1601,189 @@ Item {
defaultValue: 12 defaultValue: 12
onSliderValueChanged: newValue => SettingsData.setCornerRadius(newValue) onSliderValueChanged: newValue => SettingsData.setCornerRadius(newValue)
} }
SettingsToggleRow {
tab: "theme"
tags: ["elevation", "shadow", "lift", "m3", "material"]
settingKey: "m3ElevationEnabled"
text: I18n.tr("Shadows")
description: I18n.tr("Material inspired shadows and elevation on modals, popouts, and dialogs")
checked: SettingsData.m3ElevationEnabled ?? true
onToggled: checked => SettingsData.set("m3ElevationEnabled", checked)
}
SettingsSliderRow {
tab: "theme"
tags: ["elevation", "shadow", "intensity", "blur", "m3"]
settingKey: "m3ElevationIntensity"
text: I18n.tr("Shadow Intensity")
description: I18n.tr("Controls the base blur radius and offset of shadows")
value: SettingsData.m3ElevationIntensity ?? 12
minimum: 0
maximum: 100
unit: "px"
defaultValue: 12
visible: SettingsData.m3ElevationEnabled ?? true
onSliderValueChanged: newValue => SettingsData.set("m3ElevationIntensity", newValue)
}
SettingsSliderRow {
tab: "theme"
tags: ["elevation", "shadow", "opacity", "transparency", "m3"]
settingKey: "m3ElevationOpacity"
text: I18n.tr("Shadow Opacity")
description: I18n.tr("Controls the transparency of the shadow")
value: SettingsData.m3ElevationOpacity ?? 30
minimum: 0
maximum: 100
unit: "%"
defaultValue: 30
visible: SettingsData.m3ElevationEnabled ?? true
onSliderValueChanged: newValue => SettingsData.set("m3ElevationOpacity", newValue)
}
SettingsDropdownRow {
tab: "theme"
tags: ["elevation", "shadow", "color", "m3"]
settingKey: "m3ElevationColorMode"
text: I18n.tr("Shadow Color")
description: I18n.tr("Base color for shadows (opacity is applied automatically)")
options: [I18n.tr("Default (Black)", "shadow color option"), I18n.tr("Text Color", "shadow color option"), I18n.tr("Primary", "shadow color option"), I18n.tr("Surface Variant", "shadow color option"), I18n.tr("Custom", "shadow color option")]
currentValue: {
switch (SettingsData.m3ElevationColorMode) {
case "text":
return I18n.tr("Text Color", "shadow color option");
case "primary":
return I18n.tr("Primary", "shadow color option");
case "surfaceVariant":
return I18n.tr("Surface Variant", "shadow color option");
case "custom":
return I18n.tr("Custom", "shadow color option");
default:
return I18n.tr("Default (Black)", "shadow color option");
}
}
visible: SettingsData.m3ElevationEnabled ?? true
onValueChanged: value => {
if (value === I18n.tr("Primary", "shadow color option")) {
SettingsData.set("m3ElevationColorMode", "primary");
} else if (value === I18n.tr("Surface Variant", "shadow color option")) {
SettingsData.set("m3ElevationColorMode", "surfaceVariant");
} else if (value === I18n.tr("Custom", "shadow color option")) {
SettingsData.set("m3ElevationColorMode", "custom");
openM3ShadowColorPicker();
} else if (value === I18n.tr("Text Color", "shadow color option")) {
SettingsData.set("m3ElevationColorMode", "text");
} else {
SettingsData.set("m3ElevationColorMode", "default");
}
}
}
SettingsDropdownRow {
tab: "theme"
tags: ["elevation", "shadow", "direction", "light", "advanced", "m3"]
settingKey: "m3ElevationLightDirection"
text: I18n.tr("Light Direction")
description: I18n.tr("Controls shadow cast direction for elevation layers")
options: [I18n.tr("Auto (Bar-aware)", "shadow direction option"), I18n.tr("Top (Default)", "shadow direction option"), I18n.tr("Top Left", "shadow direction option"), I18n.tr("Top Right", "shadow direction option"), I18n.tr("Bottom", "shadow direction option")]
currentValue: {
switch (SettingsData.m3ElevationLightDirection) {
case "autoBar":
return I18n.tr("Auto (Bar-aware)", "shadow direction option");
case "topLeft":
return I18n.tr("Top Left", "shadow direction option");
case "topRight":
return I18n.tr("Top Right", "shadow direction option");
case "bottom":
return I18n.tr("Bottom", "shadow direction option");
default:
return I18n.tr("Top (Default)", "shadow direction option");
}
}
visible: SettingsData.m3ElevationEnabled ?? true
onValueChanged: value => {
if (value === I18n.tr("Auto (Bar-aware)", "shadow direction option")) {
SettingsData.set("m3ElevationLightDirection", "autoBar");
} else if (value === I18n.tr("Top Left", "shadow direction option")) {
SettingsData.set("m3ElevationLightDirection", "topLeft");
} else if (value === I18n.tr("Top Right", "shadow direction option")) {
SettingsData.set("m3ElevationLightDirection", "topRight");
} else if (value === I18n.tr("Bottom", "shadow direction option")) {
SettingsData.set("m3ElevationLightDirection", "bottom");
} else {
SettingsData.set("m3ElevationLightDirection", "top");
}
}
}
Item {
visible: (SettingsData.m3ElevationEnabled ?? true) && SettingsData.m3ElevationColorMode === "custom"
width: parent.width
implicitHeight: 36
height: implicitHeight
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
StyledText {
text: I18n.tr("Custom Shadow Color")
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
verticalAlignment: Text.AlignVCenter
}
Rectangle {
width: 26
height: 26
radius: 13
color: SettingsData.m3ElevationCustomColor ?? "#000000"
border.color: Theme.outline
border.width: 1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: openM3ShadowColorPicker()
}
}
}
}
SettingsToggleRow {
tab: "theme"
tags: ["elevation", "shadow", "modal", "dialog", "m3"]
settingKey: "modalElevationEnabled"
text: I18n.tr("Modal Shadows")
description: I18n.tr("Shadow elevation on modals and dialogs")
checked: SettingsData.modalElevationEnabled ?? true
visible: SettingsData.m3ElevationEnabled ?? true
onToggled: checked => SettingsData.set("modalElevationEnabled", checked)
}
SettingsToggleRow {
tab: "theme"
tags: ["elevation", "shadow", "popout", "popup", "osd", "dropdown", "m3"]
settingKey: "popoutElevationEnabled"
text: I18n.tr("Popout Shadows")
description: I18n.tr("Shadow elevation on popouts, OSDs, and dropdowns")
checked: SettingsData.popoutElevationEnabled ?? true
visible: SettingsData.m3ElevationEnabled ?? true
onToggled: checked => SettingsData.set("popoutElevationEnabled", checked)
}
SettingsToggleRow {
tab: "theme"
tags: ["elevation", "shadow", "bar", "panel", "navigation", "m3"]
settingKey: "barElevationEnabled"
text: I18n.tr("Bar Shadows")
description: I18n.tr("Shadow elevation on bars and panels")
checked: SettingsData.barElevationEnabled ?? true
visible: SettingsData.m3ElevationEnabled ?? true
onToggled: checked => SettingsData.set("barElevationEnabled", checked)
}
} }
SettingsCard { SettingsCard {
@@ -2137,12 +2329,40 @@ Item {
} }
} }
SettingsCard {
tab: "theme"
tags: ["icon", "theme", "system"]
title: I18n.tr("Icon Theme")
settingKey: "iconTheme"
SettingsDropdownRow {
tab: "theme"
tags: ["icon", "theme", "system"]
settingKey: "iconTheme"
text: I18n.tr("Icon Theme")
description: I18n.tr("DankShell & System Icons (requires restart)")
currentValue: SettingsData.iconTheme
enableFuzzySearch: true
popupWidthOffset: 100
maxPopupHeight: 236
options: cachedIconThemes
onValueChanged: value => {
SettingsData.setIconTheme(value);
if (Quickshell.env("QT_QPA_PLATFORMTHEME") != "gtk3" && Quickshell.env("QT_QPA_PLATFORMTHEME") != "qt6ct" && Quickshell.env("QT_QPA_PLATFORMTHEME_QT6") != "qt6ct") {
ToastService.showError(I18n.tr("Missing Environment Variables", "qt theme env error title"), I18n.tr("You need to set either:\nQT_QPA_PLATFORMTHEME=gtk3 OR\nQT_QPA_PLATFORMTHEME=qt6ct\nas environment variables, and then restart the shell.\n\nqt6ct requires qt6ct-kde to be installed.", "qt theme env error body"));
}
}
}
}
SettingsCard { SettingsCard {
tab: "theme" tab: "theme"
tags: ["matugen", "templates", "theming"] tags: ["matugen", "templates", "theming"]
title: I18n.tr("Matugen Templates") title: I18n.tr("Matugen Templates")
settingKey: "matugenTemplates" settingKey: "matugenTemplates"
iconName: "auto_awesome" iconName: "auto_awesome"
collapsible: true
expanded: false
visible: Theme.matugenAvailable visible: Theme.matugenAvailable
SettingsToggleRow { SettingsToggleRow {
@@ -2447,32 +2667,6 @@ Item {
} }
} }
SettingsCard {
tab: "theme"
tags: ["icon", "theme", "system"]
title: I18n.tr("Icon Theme")
settingKey: "iconTheme"
SettingsDropdownRow {
tab: "theme"
tags: ["icon", "theme", "system"]
settingKey: "iconTheme"
text: I18n.tr("Icon Theme")
description: I18n.tr("DankShell & System Icons (requires restart)")
currentValue: SettingsData.iconTheme
enableFuzzySearch: true
popupWidthOffset: 100
maxPopupHeight: 236
options: cachedIconThemes
onValueChanged: value => {
SettingsData.setIconTheme(value);
if (Quickshell.env("QT_QPA_PLATFORMTHEME") != "gtk3" && Quickshell.env("QT_QPA_PLATFORMTHEME") != "qt6ct" && Quickshell.env("QT_QPA_PLATFORMTHEME_QT6") != "qt6ct") {
ToastService.showError(I18n.tr("Missing Environment Variables", "qt theme env error title"), I18n.tr("You need to set either:\nQT_QPA_PLATFORMTHEME=gtk3 OR\nQT_QPA_PLATFORMTHEME=qt6ct\nas environment variables, and then restart the shell.\n\nqt6ct requires qt6ct-kde to be installed.", "qt theme env error body"));
}
}
}
}
SettingsCard { SettingsCard {
tab: "theme" tab: "theme"
tags: ["system", "app", "theming", "gtk", "qt"] tags: ["system", "app", "theming", "gtk", "qt"]
@@ -663,14 +663,15 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
layer.enabled: true layer.enabled: Theme.elevationEnabled
layer.effect: MultiEffect { layer.effect: MultiEffect {
shadowEnabled: true shadowEnabled: Theme.elevationEnabled
shadowHorizontalOffset: 0 shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel1)
shadowVerticalOffset: 4 shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel1, 1)
shadowBlur: 0.8 shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel1 && Theme.elevationLevel1.blurPx !== undefined ? Theme.elevationLevel1.blurPx : 4) / Theme.elevationBlurMax)) : 0
shadowColor: Qt.rgba(0, 0, 0, 0.2) blurMax: Theme.elevationBlurMax
shadowOpacity: 0.2 shadowColor: Theme.elevationShadowColor(Theme.elevationLevel1)
shadowOpacity: Theme.elevationLevel1 && Theme.elevationLevel1.alpha !== undefined ? Theme.elevationLevel1.alpha : 0.2
} }
} }
@@ -216,6 +216,15 @@ Rectangle {
onToggled: checked => root.updateConfig("showMemoryGraph", checked) 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 {} SettingsDivider {}
DankToggle { DankToggle {
+52 -2
View File
@@ -398,10 +398,14 @@ Item {
widgetObj.runningAppsCurrentWorkspace = SettingsData.runningAppsCurrentWorkspace; widgetObj.runningAppsCurrentWorkspace = SettingsData.runningAppsCurrentWorkspace;
widgetObj.runningAppsCurrentMonitor = false; widgetObj.runningAppsCurrentMonitor = false;
} }
if (widgetId === "diskUsage") if (widgetId === "diskUsage") {
widgetObj.mountPath = "/"; widgetObj.mountPath = "/";
widgetObj.diskUsageMode = 0;
}
if (widgetId === "cpuUsage" || widgetId === "memUsage" || widgetId === "cpuTemp" || widgetId === "gpuTemp") if (widgetId === "cpuUsage" || widgetId === "memUsage" || widgetId === "cpuTemp" || widgetId === "gpuTemp")
widgetObj.minimumWidth = true; widgetObj.minimumWidth = true;
if (widgetId === "memUsage")
widgetObj.showInGb = false;
var widgets = getWidgetsForSection(targetSection).slice(); var widgets = getWidgetsForSection(targetSection).slice();
widgets.push(widgetObj); widgets.push(widgetObj);
@@ -425,7 +429,7 @@ Item {
"id": widget.id, "id": widget.id,
"enabled": widget.enabled "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++) { for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined) if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]]; result[keys[i]] = widget[keys[i]];
@@ -536,6 +540,30 @@ Item {
setWidgetsForSection(sectionId, widgets); 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) { function handleOverflowSettingChanged(sectionId, widgetIndex, settingName, value) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) { if (widgetIndex < 0 || widgetIndex >= widgets.length) {
@@ -601,6 +629,8 @@ Item {
item.pciId = widget.pciId; item.pciId = widget.pciId;
if (widget.mountPath !== undefined) if (widget.mountPath !== undefined)
item.mountPath = widget.mountPath; item.mountPath = widget.mountPath;
if (widget.diskUsageMode !== undefined)
item.diskUsageMode = widget.diskUsageMode;
if (widget.showNetworkIcon !== undefined) if (widget.showNetworkIcon !== undefined)
item.showNetworkIcon = widget.showNetworkIcon; item.showNetworkIcon = widget.showNetworkIcon;
if (widget.showBluetoothIcon !== undefined) if (widget.showBluetoothIcon !== undefined)
@@ -629,6 +659,8 @@ Item {
item.minimumWidth = widget.minimumWidth; item.minimumWidth = widget.minimumWidth;
if (widget.showSwap !== undefined) if (widget.showSwap !== undefined)
item.showSwap = widget.showSwap; item.showSwap = widget.showSwap;
if (widget.showInGb !== undefined)
item.showInGb = widget.showInGb;
if (widget.mediaSize !== undefined) if (widget.mediaSize !== undefined)
item.mediaSize = widget.mediaSize; item.mediaSize = widget.mediaSize;
if (widget.clockCompactMode !== undefined) if (widget.clockCompactMode !== undefined)
@@ -925,6 +957,12 @@ Item {
onShowSwapChanged: (sectionId, index, enabled) => { onShowSwapChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowSwapChanged(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) => { onCompactModeChanged: (widgetId, value) => {
widgetsTab.handleCompactModeChanged(sectionId, widgetId, value); widgetsTab.handleCompactModeChanged(sectionId, widgetId, value);
} }
@@ -983,6 +1021,12 @@ Item {
onShowSwapChanged: (sectionId, index, enabled) => { onShowSwapChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowSwapChanged(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) => { onCompactModeChanged: (widgetId, value) => {
widgetsTab.handleCompactModeChanged(sectionId, widgetId, value); widgetsTab.handleCompactModeChanged(sectionId, widgetId, value);
} }
@@ -1041,6 +1085,12 @@ Item {
onShowSwapChanged: (sectionId, index, enabled) => { onShowSwapChanged: (sectionId, index, enabled) => {
widgetsTab.handleShowSwapChanged(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) => { onCompactModeChanged: (widgetId, value) => {
widgetsTab.handleCompactModeChanged(sectionId, widgetId, value); widgetsTab.handleCompactModeChanged(sectionId, widgetId, value);
} }
+311 -63
View File
@@ -30,6 +30,8 @@ Column {
signal privacySettingChanged(string sectionId, int widgetIndex, string settingName, bool value) signal privacySettingChanged(string sectionId, int widgetIndex, string settingName, bool value)
signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled) signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled)
signal showSwapChanged(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) signal overflowSettingChanged(string sectionId, int widgetIndex, string settingName, var value)
function cloneWidgetData(widget) { function cloneWidgetData(widget) {
@@ -37,7 +39,7 @@ Column {
"id": widget.id, "id": widget.id,
"enabled": widget.enabled "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++) { for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined) if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]]; result[keys[i]] = widget[keys[i]];
@@ -67,53 +69,6 @@ Column {
color: Theme.surfaceText color: Theme.surfaceText
Layout.alignment: Qt.AlignVCenter 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 { Column {
@@ -135,6 +90,7 @@ Column {
height: 70 height: 70
z: held ? 2 : 1 z: held ? 2 : 1
Rectangle { Rectangle {
id: itemBackground 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 { Item {
width: 32 width: 32
height: 32 height: 32
@@ -354,23 +338,36 @@ Column {
} }
DankActionButton { DankActionButton {
id: showSwapButton id: memMenuButton
buttonSize: 28
visible: modelData.id === "memUsage" visible: modelData.id === "memUsage"
iconName: "swap_horiz" buttonSize: 32
iconSize: 16 iconName: "more_vert"
iconColor: (modelData.showSwap !== undefined ? modelData.showSwap : false) ? Theme.primary : Theme.outline iconSize: 18
iconColor: Theme.outline
onClicked: { onClicked: {
var currentEnabled = modelData.showSwap !== undefined ? modelData.showSwap : false; memUsageContextMenu.widgetData = modelData;
root.showSwapChanged(root.sectionId, index, !currentEnabled); memUsageContextMenu.sectionId = root.sectionId;
} memUsageContextMenu.widgetIndex = index;
onEntered: {
var currentEnabled = modelData.showSwap !== undefined ? modelData.showSwap : false; var buttonPos = memMenuButton.mapToItem(root, 0, 0);
const tooltipText = currentEnabled ? "Hide Swap" : "Show Swap"; var popupWidth = memUsageContextMenu.width;
sharedTooltip.show(tooltipText, showSwapButton, 0, 0, "bottom"); var popupHeight = memUsageContextMenu.height;
}
onExited: { var xPos = buttonPos.x - popupWidth - Theme.spacingS;
sharedTooltip.hide(); 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 { Popup {
id: controlCenterContextMenu id: controlCenterContextMenu
+9 -9
View File
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
@@ -96,7 +95,6 @@ PanelWindow {
} }
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
layer.enabled: true
opacity: shouldBeVisible ? 1 : 0 opacity: shouldBeVisible ? 1 : 0
Column { Column {
@@ -406,13 +404,15 @@ PanelWindow {
onClicked: ToastService.hideToast() onClicked: ToastService.hideToast()
} }
layer.effect: MultiEffect { ElevationShadow {
shadowEnabled: true anchors.fill: parent
shadowHorizontalOffset: 0 z: -1
shadowVerticalOffset: 4 level: Theme.elevationLevel3
shadowBlur: 0.8 fallbackOffset: 6
shadowColor: Qt.rgba(0, 0, 0, 0.3) targetRadius: toast.radius
shadowOpacity: 0.3 targetColor: toast.color
shadowOpacity: Theme.elevationLevel3 && Theme.elevationLevel3.alpha !== undefined ? Theme.elevationLevel3.alpha : 0.3
shadowEnabled: Theme.elevationEnabled
} }
Behavior on opacity { Behavior on opacity {
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
@@ -17,59 +16,61 @@ Item {
readonly property var allWorkspaces: Hyprland.workspaces?.values || [] readonly property var allWorkspaces: Hyprland.workspaces?.values || []
readonly property var allWorkspaceIds: { readonly property var allWorkspaceIds: {
const workspaces = allWorkspaces const workspaces = allWorkspaces;
if (!workspaces || workspaces.length === 0) return [] if (!workspaces || workspaces.length === 0)
return [];
try { try {
const ids = workspaces.map(ws => ws?.id).filter(id => id !== null && id !== undefined) const ids = workspaces.map(ws => ws?.id).filter(id => id !== null && id !== undefined);
return ids.sort((a, b) => a - b) return ids.sort((a, b) => a - b);
} catch (e) { } catch (e) {
return [] return [];
} }
} }
readonly property var thisMonitorWorkspaceIds: { readonly property var thisMonitorWorkspaceIds: {
const workspaces = allWorkspaces const workspaces = allWorkspaces;
const mon = monitor const mon = monitor;
if (!workspaces || workspaces.length === 0 || !mon) return [] if (!workspaces || workspaces.length === 0 || !mon)
return [];
try { try {
const filtered = workspaces.filter(ws => ws?.monitor?.name === mon.name) const filtered = workspaces.filter(ws => ws?.monitor?.name === mon.name);
return filtered.map(ws => ws?.id).filter(id => id !== null && id !== undefined).sort((a, b) => a - b) return filtered.map(ws => ws?.id).filter(id => id !== null && id !== undefined).sort((a, b) => a - b);
} catch (e) { } catch (e) {
return [] return [];
} }
} }
readonly property var displayedWorkspaceIds: { readonly property var displayedWorkspaceIds: {
if (!allWorkspaceIds || allWorkspaceIds.length === 0) { if (!allWorkspaceIds || allWorkspaceIds.length === 0) {
const result = [] const result = [];
for (let i = 1; i <= workspacesShown; i++) { for (let i = 1; i <= workspacesShown; i++) {
result.push(i) result.push(i);
} }
return result return result;
} }
try { try {
const maxExisting = Math.max(...allWorkspaceIds) const maxExisting = Math.max(...allWorkspaceIds);
const totalNeeded = Math.max(workspacesShown, allWorkspaceIds.length) const totalNeeded = Math.max(workspacesShown, allWorkspaceIds.length);
const result = [] const result = [];
for (let i = 1; i <= maxExisting; i++) { for (let i = 1; i <= maxExisting; i++) {
result.push(i) result.push(i);
} }
let nextId = maxExisting + 1 let nextId = maxExisting + 1;
while (result.length < totalNeeded) { while (result.length < totalNeeded) {
result.push(nextId) result.push(nextId);
nextId++ nextId++;
} }
return result return result;
} catch (e) { } catch (e) {
const result = [] const result = [];
for (let i = 1; i <= workspacesShown; i++) { for (let i = 1; i <= workspacesShown; i++) {
result.push(i) result.push(i);
} }
return result return result;
} }
} }
@@ -81,24 +82,27 @@ Item {
readonly property int effectiveRows: Math.max(SettingsData.overviewRows, Math.ceil(displayWorkspaceCount / effectiveColumns)) readonly property int effectiveRows: Math.max(SettingsData.overviewRows, Math.ceil(displayWorkspaceCount / effectiveColumns))
function getWorkspaceMonitorName(workspaceId) { function getWorkspaceMonitorName(workspaceId) {
if (!allWorkspaces || !workspaceId) return "" if (!allWorkspaces || !workspaceId)
return "";
try { try {
const ws = allWorkspaces.find(w => w?.id === workspaceId) const ws = allWorkspaces.find(w => w?.id === workspaceId);
return ws?.monitor?.name ?? "" return ws?.monitor?.name ?? "";
} catch (e) { } catch (e) {
return "" return "";
} }
} }
function workspaceHasWindows(workspaceId) { function workspaceHasWindows(workspaceId) {
if (!workspaceId) return false if (!workspaceId)
return false;
try { try {
const workspace = allWorkspaces.find(ws => ws?.id === workspaceId) const workspace = allWorkspaces.find(ws => ws?.id === workspaceId);
if (!workspace) return false if (!workspace)
const toplevels = workspace?.toplevels?.values || [] return false;
return toplevels.length > 0 const toplevels = workspace?.toplevels?.values || [];
return toplevels.length > 0;
} catch (e) { } catch (e) {
return false return false;
} }
} }
@@ -124,16 +128,16 @@ Item {
implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2 implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2
Component.onCompleted: { Component.onCompleted: {
Hyprland.refreshToplevels() Hyprland.refreshToplevels();
Hyprland.refreshWorkspaces() Hyprland.refreshWorkspaces();
Hyprland.refreshMonitors() Hyprland.refreshMonitors();
} }
onOverviewOpenChanged: { onOverviewOpenChanged: {
if (overviewOpen) { if (overviewOpen) {
Hyprland.refreshToplevels() Hyprland.refreshToplevels();
Hyprland.refreshWorkspaces() Hyprland.refreshWorkspaces();
Hyprland.refreshMonitors() Hyprland.refreshMonitors();
} }
} }
@@ -148,15 +152,15 @@ Item {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainer color: Theme.surfaceContainer
layer.enabled: true ElevationShadow {
layer.effect: MultiEffect { anchors.fill: parent
shadowEnabled: true z: -1
shadowBlur: 0.5 level: Theme.elevationLevel2
shadowHorizontalOffset: 0 fallbackOffset: 4
shadowVerticalOffset: 4 targetRadius: Theme.cornerRadius
shadowColor: Theme.shadowStrong targetColor: Theme.surfaceContainer
shadowOpacity: 1 shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
blurMax: 32 shadowEnabled: Theme.elevationEnabled
} }
ColumnLayout { ColumnLayout {
@@ -217,8 +221,8 @@ Item {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onClicked: { onClicked: {
if (root.draggingTargetWorkspace === -1) { if (root.draggingTargetWorkspace === -1) {
root.overviewOpen = false root.overviewOpen = false;
Hyprland.dispatch(`workspace ${workspaceValue}`) Hyprland.dispatch(`workspace ${workspaceValue}`);
} }
} }
} }
@@ -226,13 +230,15 @@ Item {
DropArea { DropArea {
anchors.fill: parent anchors.fill: parent
onEntered: { onEntered: {
root.draggingTargetWorkspace = workspaceValue root.draggingTargetWorkspace = workspaceValue;
if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return if (root.draggingFromWorkspace == root.draggingTargetWorkspace)
hoveredWhileDragging = true return;
hoveredWhileDragging = true;
} }
onExited: { onExited: {
hoveredWhileDragging = false hoveredWhileDragging = false;
if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 if (root.draggingTargetWorkspace == workspaceValue)
root.draggingTargetWorkspace = -1;
} }
} }
} }
@@ -250,27 +256,28 @@ Item {
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
values: { values: {
const workspaces = root.allWorkspaces const workspaces = root.allWorkspaces;
const minId = root.minWorkspaceId const minId = root.minWorkspaceId;
const maxId = root.maxWorkspaceId const maxId = root.maxWorkspaceId;
if (!workspaces || workspaces.length === 0) return [] if (!workspaces || workspaces.length === 0)
return [];
try { try {
const result = [] const result = [];
for (const workspace of workspaces) { for (const workspace of workspaces) {
const wsId = workspace?.id ?? -1 const wsId = workspace?.id ?? -1;
if (wsId >= minId && wsId <= maxId) { if (wsId >= minId && wsId <= maxId) {
const toplevels = workspace?.toplevels?.values || [] const toplevels = workspace?.toplevels?.values || [];
for (const toplevel of toplevels) { for (const toplevel of toplevels) {
result.push(toplevel) result.push(toplevel);
} }
} }
} }
return result return result;
} catch (e) { } catch (e) {
console.error("OverviewWidget filter error:", e) console.error("OverviewWidget filter error:", e);
return [] return [];
} }
} }
} }
@@ -282,17 +289,19 @@ Item {
readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1 readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1
function getWorkspaceIndex() { function getWorkspaceIndex() {
if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0) return 0 if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0)
if (!windowWorkspaceId || windowWorkspaceId < 0) return 0 return 0;
if (!windowWorkspaceId || windowWorkspaceId < 0)
return 0;
try { try {
for (let i = 0; i < root.displayedWorkspaceIds.length; i++) { for (let i = 0; i < root.displayedWorkspaceIds.length; i++) {
if (root.displayedWorkspaceIds[i] === windowWorkspaceId) { if (root.displayedWorkspaceIds[i] === windowWorkspaceId) {
return i return i;
} }
} }
return 0 return 0;
} catch (e) { } catch (e) {
return 0 return 0;
} }
} }
@@ -325,48 +334,48 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.MiddleButton
drag.target: parent drag.target: parent
onPressed: (mouse) => { onPressed: mouse => {
root.draggingFromWorkspace = windowData?.workspace.id root.draggingFromWorkspace = windowData?.workspace.id;
window.pressed = true window.pressed = true;
window.Drag.active = true window.Drag.active = true;
window.Drag.source = window window.Drag.source = window;
window.Drag.hotSpot.x = mouse.x window.Drag.hotSpot.x = mouse.x;
window.Drag.hotSpot.y = mouse.y window.Drag.hotSpot.y = mouse.y;
} }
onReleased: { onReleased: {
const targetWorkspace = root.draggingTargetWorkspace const targetWorkspace = root.draggingTargetWorkspace;
window.pressed = false window.pressed = false;
window.Drag.active = false window.Drag.active = false;
root.draggingFromWorkspace = -1 root.draggingFromWorkspace = -1;
root.draggingTargetWorkspace = -1 root.draggingTargetWorkspace = -1;
if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {
Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace},address:${windowData?.address}`) Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace},address:${windowData?.address}`);
Qt.callLater(() => { Qt.callLater(() => {
Hyprland.refreshToplevels() Hyprland.refreshToplevels();
Hyprland.refreshWorkspaces() Hyprland.refreshWorkspaces();
Qt.callLater(() => { Qt.callLater(() => {
window.x = window.initX window.x = window.initX;
window.y = window.initY window.y = window.initY;
}) });
}) });
} else { } else {
window.x = window.initX window.x = window.initX;
window.y = window.initY window.y = window.initY;
} }
} }
onClicked: (event) => { onClicked: event => {
if (!windowData || !windowData.address) return if (!windowData || !windowData.address)
return;
if (event.button === Qt.LeftButton) { if (event.button === Qt.LeftButton) {
root.overviewOpen = false root.overviewOpen = false;
Hyprland.dispatch(`focuswindow address:${windowData.address}`) Hyprland.dispatch(`focuswindow address:${windowData.address}`);
event.accepted = true event.accepted = true;
} else if (event.button === Qt.MiddleButton) { } else if (event.button === Qt.MiddleButton) {
Hyprland.dispatch(`closewindow address:${windowData.address}`) Hyprland.dispatch(`closewindow address:${windowData.address}`);
event.accepted = true event.accepted = true;
} }
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@ Singleton {
id: root id: root
readonly property string currentVersion: "1.4" 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 configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell"
readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion
+78 -38
View File
@@ -1,8 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtCore
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -13,6 +11,9 @@ Singleton {
property bool dsearchAvailable: false property bool dsearchAvailable: false
property int searchIdCounter: 0 property int searchIdCounter: 0
property int indexVersion: 0
property bool supportsTypeFilter: false
property bool versionChecked: false
signal searchResultsReceived(var results) signal searchResultsReceived(var results)
signal statsReceived(var stats) signal statsReceived(var stats)
@@ -26,118 +27,157 @@ Singleton {
stdout: SplitParser { stdout: SplitParser {
onRead: line => { onRead: line => {
if (line && line.trim().length > 0) { if (line && line.trim().length > 0) {
root.dsearchAvailable = true root.dsearchAvailable = true;
} }
} }
} }
onExited: exitCode => { onExited: exitCode => {
if (exitCode !== 0) { 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) { function ping(callback) {
if (!dsearchAvailable) { if (!dsearchAvailable) {
if (callback) { if (callback) {
callback({ "error": "dsearch not available" }) callback({
"error": "dsearch not available"
});
} }
return return;
} }
Proc.runCommand("dsearch-ping", ["dsearch", "ping", "--json"], (stdout, exitCode) => { Proc.runCommand("dsearch-ping", ["dsearch", "ping", "--json"], (stdout, exitCode) => {
if (callback) { if (callback) {
if (exitCode === 0) { if (exitCode === 0) {
try { try {
const response = JSON.parse(stdout) const response = JSON.parse(stdout);
callback({ "result": response }) callback({
"result": response
});
} catch (e) { } catch (e) {
callback({ "error": "failed to parse ping response" }) callback({
"error": "failed to parse ping response"
});
} }
} else { } else {
callback({ "error": "ping failed" }) callback({
"error": "ping failed"
});
} }
} }
}) });
} }
function search(query, params, callback) { function search(query, params, callback) {
if (!query || query.length === 0) { if (!query || query.length === 0) {
if (callback) { if (callback) {
callback({ "error": "query is required" }) callback({
"error": "query is required"
});
} }
return return;
} }
if (!dsearchAvailable) { if (!dsearchAvailable) {
if (callback) { 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) {
if (params.limit !== undefined) { 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) { if (params.ext) {
args.push("-e", params.ext) args.push("-e", params.ext);
}
if (params.folder) {
args.push("--folder", params.folder);
} }
if (params.field) { if (params.field) {
args.push("-f", params.field) args.push("-f", params.field);
} }
if (params.fuzzy) { if (params.fuzzy) {
args.push("--fuzzy") args.push("--fuzzy");
} }
if (params.sort) { if (params.sort) {
args.push("--sort", params.sort) args.push("--sort", params.sort);
} }
if (params.desc !== undefined) { if (params.desc !== undefined) {
args.push("--desc=" + (params.desc ? "true" : "false")) args.push("--desc=" + (params.desc ? "true" : "false"));
} }
if (params.minSize !== undefined) { if (params.minSize !== undefined) {
args.push("--min-size", String(params.minSize)) args.push("--min-size", String(params.minSize));
} }
if (params.maxSize !== undefined) { 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) => { Proc.runCommand("dsearch-search", args, (stdout, exitCode) => {
if (exitCode === 0) { if (exitCode === 0) {
try { try {
const response = JSON.parse(stdout) const response = JSON.parse(stdout);
searchResultsReceived(response) searchResultsReceived(response);
if (callback) { if (callback) {
callback({ "result": response }) callback({
"result": response
});
} }
} catch (e) { } catch (e) {
const error = "failed to parse search response" const error = "failed to parse search response";
errorOccurred(error) errorOccurred(error);
if (callback) { if (callback) {
callback({ "error": error }) callback({
"error": error
});
} }
} }
} else if (exitCode === 124) { } else if (exitCode === 124) {
const error = "search timed out" const error = "search timed out";
errorOccurred(error) errorOccurred(error);
if (callback) { if (callback) {
callback({ "error": error }) callback({
"error": error
});
} }
} else { } else {
const error = "search failed" const error = "search failed";
errorOccurred(error) errorOccurred(error);
if (callback) { if (callback) {
callback({ "error": error }) callback({
"error": error
});
} }
} }
}, 100, 5000) }, 100, 5000);
} }
function rediscover() { function rediscover() {
checkProcess.running = true checkProcess.running = true;
} }
} }
+9
View File
@@ -41,6 +41,8 @@ Singleton {
property var notificationModal: null property var notificationModal: null
property var wifiPasswordModal: null property var wifiPasswordModal: null
property var wifiPasswordModalLoader: null property var wifiPasswordModalLoader: null
property var wifiQRCodeModal: null
property var wifiQRCodeModalLoader: null
property var polkitAuthModal: null property var polkitAuthModal: null
property var polkitAuthModalLoader: null property var polkitAuthModalLoader: null
property var bluetoothPairingModal: null property var bluetoothPairingModal: null
@@ -661,6 +663,13 @@ Singleton {
wifiPasswordModal.show(ssid); wifiPasswordModal.show(ssid);
} }
function showWifiQRCodeModal(ssid) {
if (wifiQRCodeModalLoader)
wifiQRCodeModalLoader.active = true;
if (wifiQRCodeModal)
wifiQRCodeModal.show(ssid);
}
function showHiddenNetworkModal() { function showHiddenNetworkModal() {
if (wifiPasswordModalLoader) if (wifiPasswordModalLoader)
wifiPasswordModalLoader.active = true; wifiPasswordModalLoader.active = true;
+1 -1
View File
@@ -1 +1 @@
v1.4.3 v1.5-beta
+1 -1
View File
@@ -98,7 +98,7 @@ Item {
sourceComponent: IconImage { sourceComponent: IconImage {
anchors.fill: parent anchors.fill: parent
source: root.iconPath source: root.iconPath
backer.sourceSize: Qt.size(root.iconSize, root.iconSize) backer.sourceSize: Qt.size(root.iconSize * 2, root.iconSize * 2)
mipmap: true mipmap: true
asynchronous: true asynchronous: true
visible: status === Image.Ready visible: status === Image.Ready
+24
View File
@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Window
import QtQuick.Effects import QtQuick.Effects
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
@@ -18,9 +19,30 @@ Rectangle {
signal imageSaved(string filePath) 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) { function saveImageToFile(filePath) {
if (activeImage.status !== Image.Ready) if (activeImage.status !== Image.Ready)
return false; return false;
if (!activeImage.Window.window) {
_pendingSavePath = filePath;
return true;
}
activeImage.grabToImage(function (result) { activeImage.grabToImage(function (result) {
if (result && result.saveToFile(filePath)) { if (result && result.saveToFile(filePath)) {
root.imageSaved(filePath); root.imageSaved(filePath);
@@ -59,6 +81,8 @@ Rectangle {
mipmap: true mipmap: true
cache: true cache: true
visible: false visible: false
sourceSize.width: Math.max(width * 2, 128)
sourceSize.height: Math.max(height * 2, 128)
source: !root.shouldProbe ? root.imageSource : "" source: !root.shouldProbe ? root.imageSource : ""
} }
+13 -7
View File
@@ -1,7 +1,6 @@
import "../Common/fzf.js" as Fzf import "../Common/fzf.js" as Fzf
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import Quickshell import Quickshell
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
@@ -254,6 +253,8 @@ Item {
} }
contentItem: Rectangle { contentItem: Rectangle {
id: contentSurface
LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true LayoutMirroring.childrenInherit: true
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
@@ -261,12 +262,17 @@ Item {
border.width: 2 border.width: 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
layer.enabled: true ElevationShadow {
layer.effect: MultiEffect { id: shadowLayer
shadowEnabled: true anchors.fill: parent
shadowBlur: 0.4 z: -1
shadowColor: Theme.shadowStrong level: Theme.elevationLevel2
shadowVerticalOffset: 4 fallbackOffset: 4
targetRadius: contentSurface.radius
targetColor: contentSurface.color
borderColor: contentSurface.border.color
borderWidth: contentSurface.border.width
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
} }
Column { Column {
+11 -8
View File
@@ -1,6 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
@@ -132,16 +131,20 @@ Rectangle {
} }
contentItem: Rectangle { contentItem: Rectangle {
id: contentSurface
color: Theme.surface color: Theme.surface
radius: Theme.cornerRadius radius: Theme.cornerRadius
layer.enabled: true ElevationShadow {
layer.effect: MultiEffect { id: shadowLayer
shadowEnabled: true anchors.fill: parent
shadowColor: Theme.shadowStrong z: -1
shadowBlur: 0.8 level: Theme.elevationLevel2
shadowHorizontalOffset: 0 fallbackOffset: 4
shadowVerticalOffset: 4 targetRadius: contentSurface.radius
targetColor: contentSurface.color
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
shadowEnabled: Theme.elevationEnabled
} }
Rectangle { Rectangle {
+9 -32
View File
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
@@ -257,11 +256,7 @@ PanelWindow {
scale: shouldBeVisible ? 1 : 0.9 scale: shouldBeVisible ? 1 : 0.9
property bool childHovered: false property bool childHovered: false
property real shadowBlurPx: 10
property real shadowSpreadPx: 0
property real shadowBaseAlpha: 0.60
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
readonly property real effectiveShadowAlpha: shouldBeVisible ? Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha)) : 0
Rectangle { Rectangle {
id: background id: background
@@ -273,38 +268,20 @@ PanelWindow {
z: -1 z: -1
} }
Item { ElevationShadow {
id: bgShadowLayer id: bgShadowLayer
anchors.fill: parent anchors.fill: parent
visible: osdContainer.popupSurfaceAlpha >= 0.95 visible: osdContainer.popupSurfaceAlpha >= 0.95
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" z: -1
layer.smooth: false level: Theme.elevationLevel3
fallbackOffset: 6
targetRadius: Theme.cornerRadius
targetColor: Theme.surfaceContainer
borderColor: Theme.outlineMedium
borderWidth: 1
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr)) layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically
readonly property int blurMax: 64
layer.effect: MultiEffect {
id: shadowFx
autoPaddingEnabled: true
shadowEnabled: true
blurEnabled: false
maskEnabled: false
shadowBlur: Math.max(0, Math.min(1, osdContainer.shadowBlurPx / bgShadowLayer.blurMax))
shadowScale: 1 + (2 * osdContainer.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, osdContainer.effectiveShadowAlpha);
}
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outlineMedium
border.width: 1
}
} }
MouseArea { MouseArea {
+71 -47
View File
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
@@ -31,6 +30,7 @@ Item {
property bool backgroundInteractive: true property bool backgroundInteractive: true
property bool contentHandlesKeys: false property bool contentHandlesKeys: false
property bool fullHeightSurface: false property bool fullHeightSurface: false
property bool _primeContent: false
property bool _resizeActive: false property bool _resizeActive: false
property real _surfaceMarginLeft: 0 property real _surfaceMarginLeft: 0
property real _surfaceW: 0 property real _surfaceW: 0
@@ -77,6 +77,38 @@ Item {
property int effectiveBarPosition: 0 property int effectiveBarPosition: 0
property real effectiveBarBottomGap: 0 property real effectiveBarBottomGap: 0
readonly property string autoBarShadowDirection: {
const section = triggerSection || "center";
switch (effectiveBarPosition) {
case SettingsData.Position.Top:
if (section === "left")
return "topLeft";
if (section === "right")
return "topRight";
return "top";
case SettingsData.Position.Bottom:
if (section === "left")
return "bottomLeft";
if (section === "right")
return "bottomRight";
return "bottom";
case SettingsData.Position.Left:
if (section === "left")
return "topLeft";
if (section === "right")
return "bottomLeft";
return "left";
case SettingsData.Position.Right:
if (section === "left")
return "topRight";
if (section === "right")
return "bottomRight";
return "right";
default:
return "top";
}
}
readonly property string effectiveShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
// Snapshot mask geometry to prevent background damage on bar updates // Snapshot mask geometry to prevent background damage on bar updates
property real _frozenMaskX: 0 property real _frozenMaskX: 0
@@ -89,6 +121,14 @@ Item {
effectiveBarBottomGap = bottomGap !== undefined ? bottomGap : 0; effectiveBarBottomGap = bottomGap !== undefined ? bottomGap : 0;
} }
function primeContent() {
_primeContent = true;
}
function clearPrimedContent() {
_primeContent = false;
}
function setTriggerPosition(x, y, width, section, targetScreen, barPosition, barThickness, barSpacing, barConfig) { function setTriggerPosition(x, y, width, section, targetScreen, barPosition, barThickness, barSpacing, barConfig) {
triggerX = x; triggerX = x;
triggerY = y; triggerY = y;
@@ -152,6 +192,7 @@ Item {
function close() { function close() {
shouldBeVisible = false; shouldBeVisible = false;
_primeContent = false;
PopoutManager.popoutChanged(); PopoutManager.popoutChanged();
closeTimer.restart(); closeTimer.restart();
} }
@@ -197,7 +238,11 @@ Item {
readonly property real screenHeight: screen ? screen.height : 0 readonly property real screenHeight: screen ? screen.height : 0
readonly property real dpr: screen ? screen.devicePixelRatio : 1 readonly property real dpr: screen ? screen.devicePixelRatio : 1
readonly property real shadowBuffer: 5 readonly property var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: Math.max(0, animationOffset)
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
readonly property real alignedWidth: Theme.px(popupWidth, dpr) readonly property real alignedWidth: Theme.px(popupWidth, dpr)
readonly property real alignedHeight: Theme.px(popupHeight, dpr) readonly property real alignedHeight: Theme.px(popupHeight, dpr)
@@ -257,29 +302,30 @@ Item {
} }
})(), dpr) })(), dpr)
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
readonly property real maskX: { readonly property real maskX: {
const triggeringBarX = (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? barWidth : 0;
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
return Math.max(triggeringBarX, adjacentLeftBar); return Math.max(triggeringBarLeftExclusion, adjacentLeftBar);
} }
readonly property real maskY: { readonly property real maskY: {
const triggeringBarY = (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? barHeight : 0;
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
return Math.max(triggeringBarY, adjacentTopBar); return Math.max(triggeringBarTopExclusion, adjacentTopBar);
} }
readonly property real maskWidth: { readonly property real maskWidth: {
const triggeringBarRight = (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? barWidth : 0;
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar); const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar);
return Math.max(100, screenWidth - maskX - rightExclusion); return Math.max(100, screenWidth - maskX - rightExclusion);
} }
readonly property real maskHeight: { readonly property real maskHeight: {
const triggeringBarBottom = (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? barHeight : 0;
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar); const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar);
return Math.max(100, screenHeight - maskY - bottomExclusion); return Math.max(100, screenHeight - maskY - bottomExclusion);
} }
@@ -289,7 +335,7 @@ Item {
visible: false visible: false
color: "transparent" color: "transparent"
Component.onCompleted: { Component.onCompleted: {
if (typeof updatesEnabled !== "undefined") if (typeof updatesEnabled !== "undefined" && !root.overlayContent)
updatesEnabled = false; updatesEnabled = false;
} }
@@ -395,7 +441,7 @@ Item {
implicitWidth: useBackgroundWindow ? root._surfaceW : 0 implicitWidth: useBackgroundWindow ? root._surfaceW : 0
implicitHeight: (useBackgroundWindow && !_fullHeight) ? (root.alignedHeight + shadowBuffer * 2) : 0 implicitHeight: (useBackgroundWindow && !_fullHeight) ? (root.alignedHeight + shadowBuffer * 2) : 0
mask: (useBackgroundWindow && _fullHeight) ? contentInputMask : null mask: useBackgroundWindow ? contentInputMask : null
Region { Region {
id: contentInputMask id: contentInputMask
@@ -405,10 +451,10 @@ Item {
Item { Item {
id: contentMaskRect id: contentMaskRect
visible: false visible: false
x: contentContainer.x - root.shadowBuffer x: contentContainer.x
y: contentContainer.y - root.shadowBuffer y: contentContainer.y
width: root.alignedWidth + root.shadowBuffer * 2 width: root.alignedWidth
height: root.alignedHeight + root.shadowBuffer * 2 height: root.alignedHeight
} }
MouseArea { MouseArea {
@@ -480,42 +526,20 @@ Item {
} }
} }
Rectangle { ElevationShadow {
id: shadowSource id: shadowSource
anchors.centerIn: parent
width: parent.width width: parent.width
height: parent.height height: parent.height
radius: Theme.cornerRadius
color: "black"
visible: false
opacity: contentWrapper.opacity opacity: contentWrapper.opacity
scale: contentWrapper.scale scale: contentWrapper.scale
x: contentWrapper.x x: contentWrapper.x
y: contentWrapper.y y: contentWrapper.y
level: root.shadowLevel
property real shadowBlurPx: 10 direction: root.effectiveShadowDirection
property real shadowSpreadPx: 0 fallbackOffset: root.shadowFallbackOffset
property real shadowBaseAlpha: 0.60 targetRadius: Theme.cornerRadius
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha)) shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
readonly property int blurMax: 64
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
layer.smooth: false
layer.effect: MultiEffect {
id: shadowFx
autoPaddingEnabled: true
shadowEnabled: true
blurEnabled: false
maskEnabled: false
shadowBlur: Math.max(0, Math.min(1, shadowSource.shadowBlurPx / shadowSource.blurMax))
shadowScale: 1 + (2 * shadowSource.shadowSpreadPx) / Math.max(1, Math.min(shadowSource.width, shadowSource.height))
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, shadowSource.effectiveShadowAlpha);
}
}
} }
Item { Item {
@@ -546,13 +570,13 @@ Item {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: Theme.outlineMedium border.color: Theme.outlineMedium
border.width: 1 border.width: 0
} }
Loader { Loader {
id: contentLoader id: contentLoader
anchors.fill: parent anchors.fill: parent
active: shouldBeVisible || contentWindow.visible active: root._primeContent || shouldBeVisible || contentWindow.visible
asynchronous: false asynchronous: false
} }
} }
@@ -53,8 +53,11 @@
--m3-radius: 12px; --m3-radius: 12px;
--m3-radius-sm: 10px; --m3-radius-sm: 10px;
--m3-elev-0: none; --m3-elev-0: none;
--m3-elev-1: 0 1px 2px rgba(0,0,0,.08), 0 1px 3px rgba(0,0,0,.06); --m3-elev-1: 0 1px 2px color-mix(in srgb, var(--md-sys-color-shadow) 8%, transparent), 0 1px 3px color-mix(in srgb, var(--md-sys-color-shadow) 6%, transparent);
--m3-elev-2: 0 2px 6px rgba(0,0,0,.10), 0 1px 3px rgba(0,0,0,.06); --m3-elev-2: 0 2px 6px color-mix(in srgb, var(--md-sys-color-shadow) 10%, transparent), 0 1px 3px color-mix(in srgb, var(--md-sys-color-shadow) 6%, transparent);
--m3-elev-3: 0 11px 7px color-mix(in srgb, var(--md-sys-color-shadow) 19%, transparent), 0 13px 25px color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent);
--m3-elev-4: 0 14px 12px color-mix(in srgb, var(--md-sys-color-shadow) 17%, transparent), 0 20px 40px color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent);
--m3-elev-5: 0 17px 17px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent), 0 27px 55px color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent);
--tab-height: 34px; --tab-height: 34px;
--urlbar-height: 38px; --urlbar-height: 38px;
@@ -118,8 +121,11 @@
--md-sys-color-surface-container-high: {{colors.surface_container_high.dark.hex}}; --md-sys-color-surface-container-high: {{colors.surface_container_high.dark.hex}};
--md-sys-color-surface-container-highest: {{colors.surface_container_highest.dark.hex}}; --md-sys-color-surface-container-highest: {{colors.surface_container_highest.dark.hex}};
--m3-elev-1: 0 1px 2px rgba(0,0,0,.50), 0 1px 3px rgba(0,0,0,.35); --m3-elev-1: 0 1px 2px color-mix(in srgb, var(--md-sys-color-shadow) 50%, transparent), 0 1px 3px color-mix(in srgb, var(--md-sys-color-shadow) 35%, transparent);
--m3-elev-2: 0 4px 10px rgba(0,0,0,.55), 0 1px 3px rgba(0,0,0,.35); --m3-elev-2: 0 4px 10px color-mix(in srgb, var(--md-sys-color-shadow) 55%, transparent), 0 1px 3px color-mix(in srgb, var(--md-sys-color-shadow) 35%, transparent);
--m3-elev-3: 0 11px 7px color-mix(in srgb, var(--md-sys-color-shadow) 45%, transparent), 0 13px 25px color-mix(in srgb, var(--md-sys-color-shadow) 55%, transparent);
--m3-elev-4: 0 14px 12px color-mix(in srgb, var(--md-sys-color-shadow) 42%, transparent), 0 20px 40px color-mix(in srgb, var(--md-sys-color-shadow) 55%, transparent);
--m3-elev-5: 0 17px 17px color-mix(in srgb, var(--md-sys-color-shadow) 40%, transparent), 0 27px 55px color-mix(in srgb, var(--md-sys-color-shadow) 55%, transparent);
--state-hover: color-mix(in srgb, var(--md-sys-color-on-surface) 6%, transparent); --state-hover: color-mix(in srgb, var(--md-sys-color-on-surface) 6%, transparent);
--state-press: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent); --state-press: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);
+6 -57
View File
@@ -4,10 +4,12 @@ return {
priority = 1000, priority = 1000,
config = function() config = function()
require('base16-colorscheme').setup({ require('base16-colorscheme').setup({
base00 = '{{dank16.color0.default.hex}}',
base01 = '{{dank16.color0.default.hex}}', base00 = '{{colors.background.dark.hex}}',
base02 = '{{dank16.color8.default.hex}}', base01 = '{{colors.surface_container_low.dark.hex}}',
base03 = '{{dank16.color8.default.hex}}', base02 = '{{colors.surface_container.dark.hex}}',
base03 = '{{dank16.color8.dark.hex}}',
base0B = '{{dank16.color3.dark.hex}}',
base04 = '{{dank16.color7.default.hex}}', base04 = '{{dank16.color7.default.hex}}',
base05 = '{{dank16.color15.default.hex}}', base05 = '{{dank16.color15.default.hex}}',
base06 = '{{dank16.color15.default.hex}}', base06 = '{{dank16.color15.default.hex}}',
@@ -15,65 +17,12 @@ return {
base08 = '{{dank16.color9.default.hex}}', base08 = '{{dank16.color9.default.hex}}',
base09 = '{{dank16.color9.default.hex}}', base09 = '{{dank16.color9.default.hex}}',
base0A = '{{dank16.color12.default.hex}}', base0A = '{{dank16.color12.default.hex}}',
base0B = '{{dank16.color10.default.hex}}',
base0C = '{{dank16.color14.default.hex}}', base0C = '{{dank16.color14.default.hex}}',
base0D = '{{dank16.color12.default.hex}}', base0D = '{{dank16.color12.default.hex}}',
base0E = '{{dank16.color13.default.hex}}', base0E = '{{dank16.color13.default.hex}}',
base0F = '{{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" local current_file_path = vim.fn.stdpath("config") .. "/lua/plugins/dankcolors.lua"
if not _G._matugen_theme_watcher then if not _G._matugen_theme_watcher then
local uv = vim.uv or vim.loop local uv = vim.uv or vim.loop
@@ -2400,6 +2400,74 @@
], ],
"description": "0 = square corners" "description": "0 = square corners"
}, },
{
"section": "m3ElevationEnabled",
"label": "M3 Elevation & Shadows",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"appearance",
"elevation",
"lift",
"material",
"m3",
"shadow",
"shadows",
"theme"
],
"description": "Material Design 3 shadows and elevation on modals, popouts, and dialogs"
},
{
"section": "modalElevationEnabled",
"label": "Modal Shadows",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"dialog",
"elevation",
"m3",
"material",
"modal",
"shadow",
"shadows"
],
"description": "Shadow elevation on modals and dialogs"
},
{
"section": "popoutElevationEnabled",
"label": "Popout Shadows",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"dropdown",
"elevation",
"m3",
"material",
"osd",
"popout",
"popup",
"shadow",
"shadows"
],
"description": "Shadow elevation on popouts, OSDs, and dropdowns"
},
{
"section": "barElevationEnabled",
"label": "Bar Shadows",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"bar",
"elevation",
"m3",
"material",
"navigation",
"panel",
"shadow",
"shadows"
],
"description": "Shadow elevation on bars and panels"
},
{ {
"section": "cursorSize", "section": "cursorSize",
"label": "Cursor Size", "label": "Cursor Size",