1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

Compare commits

...

135 Commits

Author SHA1 Message Date
purian23
04fdfa2a35 simplify Fedora Spec 2025-10-09 18:52:27 -04:00
bbedward
8f3085290d Update wrapper for consistency 2025-10-09 17:59:16 -04:00
bbedward
0839fe45f5 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 17:54:07 -04:00
bbedward
18f4795fda add dms-greeter wrapper 2025-10-09 17:53:56 -04:00
Parthiv Seetharaman
55d9fa622a Re-introduce default settings option and fix dms-cli build (#366)
* Reapply "Add default configuration option to home-manager (#356)"

This reverts commit bc23109f99.

* fix multiple xdg.configFile definitions error

* update dms-cli flake to fix build
2025-10-09 16:11:35 -04:00
bbedward
7dc723c764 Middle clock to close window on dock 2025-10-09 15:16:22 -04:00
bbedward
5a63205972 Fix light mode binding 2025-10-09 15:08:21 -04:00
bbedward
a4ceeafb1e Add ability to disable loginctl lock integration 2025-10-09 14:58:16 -04:00
bbedward
242e05cc0e Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 14:47:18 -04:00
bbedward
065dddbe6e Fix lock before suspend 2025-10-09 14:44:55 -04:00
purian23
fa6825252b prep Fedora Copr support 2025-10-09 14:42:09 -04:00
bbedward
b06e48a444 FolderListModel filters 2025-10-09 14:30:43 -04:00
bbedward
97dbd40f07 remove unused fileURL property 2025-10-09 14:17:29 -04:00
bbedward
bc23109f99 Revert "Add default configuration option to home-manager (#356)"
This reverts commit 67a4e3074e.
2025-10-09 13:53:27 -04:00
bbedward
ecb9675e9c Try more plugin loading things 2025-10-09 13:51:43 -04:00
bbedward
e1f9b9e7a4 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 13:37:46 -04:00
bbedward
067b485bb3 Bind perms directly to availablePlugins map 2025-10-09 13:37:34 -04:00
bokicoder
67a4e3074e Add default configuration option to home-manager (#356) 2025-10-09 13:23:44 -04:00
bokicoder
010bc4e8c3 fix systemd startup (#364) 2025-10-09 13:23:33 -04:00
bbedward
9de5e3253e Re-do plugin scanning 2025-10-09 13:22:33 -04:00
bbedward
e32622ac48 Some lockscreen restructure 2025-10-09 11:30:02 -04:00
bbedward
5e2371c2cb Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 09:01:39 -04:00
bbedward
a6ce26ee87 i18n: update loading + add zh_CN 2025-10-09 09:01:20 -04:00
Parthiv Seetharaman
2a72c126f1 Add nix home-manager option for adding plugins (#354)
* add plugins nix hm option

* add nix hm plugins option usage to readme
2025-10-09 08:34:05 -04:00
bbedward
36e1a5d379 Update greeter docs 2025-10-08 23:37:45 -04:00
bbedward
c12eafa1db Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-08 23:18:07 -04:00
bbedward
9e26d8755c Add os keyboard to greeter 2025-10-08 23:17:52 -04:00
Lukas Krejci
90bd30e351 add support for system updates on fedora (#353) 2025-10-08 22:19:40 -04:00
bbedward
3fb5d5c4f3 i18n: don't load en json - move animated terms to properties 2025-10-08 20:31:41 -04:00
bbedward
9f3685e4d5 clip spotlight result listview 2025-10-08 18:58:14 -04:00
bbedward
6565988952 Cleanup some of the backwards compat crap 2025-10-08 18:28:46 -04:00
bbedward
10b7a0875b Crop CircularImages not fit 2025-10-08 18:16:16 -04:00
bbedward
bb4b9f1a58 Only ignore left/right keys in grid launcher mode 2025-10-08 17:55:24 -04:00
bbedward
981e527560 Remove artifacts of search fields overriding key inputs 2025-10-08 17:36:12 -04:00
bbedward
80b2ee719c Restore ipc target 2025-10-08 17:31:26 -04:00
bbedward
6103d6196f override cachyos logo 2025-10-08 16:35:49 -04:00
bbedward
ed1a5bfded i18n: Add japanese + i18n service 2025-10-08 16:25:06 -04:00
bbedward
3909ce3350 Remove minimum menu size constraint 2025-10-08 15:23:09 -04:00
bbedward
d64cd0b8a4 Fix launcher button mask 2025-10-08 15:12:47 -04:00
bbedward
676aa9f93f Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-08 14:51:59 -04:00
bbedward
ebd48e2556 Many more logo customization options 2025-10-08 14:51:38 -04:00
bokicoder
ad5871aae4 Update flake.lock (#347) 2025-10-08 14:15:38 -04:00
bbedward
e327b1ca5b Add proxy service for legacy nmcli version 2025-10-08 14:00:51 -04:00
bbedward
ad43ca11eb Insta fallback 2025-10-08 13:41:37 -04:00
omar
41ba76e2e2 Change lock activation to use lockLoader (#346)
Fixes broken lock button
2025-10-08 13:40:27 -04:00
bbedward
15e773434e Portal/Loginctl fallbacks for DMS_SOCKET unavail 2025-10-08 13:37:03 -04:00
bbedward
ed118f4e7a Dropbox icon workaround 2025-10-08 12:54:06 -04:00
bbedward
3c420f2e30 Remove useless getter 2025-10-08 12:33:00 -04:00
bbedward
4e271d4f0e Fix search when appusage is unavailable 2025-10-08 12:07:46 -04:00
bbedward
27f9b3cd0b native NetworkManager + all native dbus bindings via dms
- Scrap janky NetworkService in favor of, dms' native NM integration
  socket
- Scrap all gdbus usage in favor of native dbus bindings in dms
  (loginctl, freedesktop)

It means that - some features won't work if running without dms wrapper.

But the trade off is certainly worth it, in the long-run for efficiency
improvements.
2025-10-08 12:03:50 -04:00
bbedward
1ed4abd347 Attempts to improve startup time 2025-10-08 10:02:54 -04:00
bbedward
f71dd1ed54 No DankIcon in notifications 2025-10-08 09:05:33 -04:00
bokicoder
19828d3b06 fix preStart script for greeter (#339) 2025-10-08 08:12:41 -04:00
bokicoder
d242e729f0 fix default-settings.json and default-session.json (#341) 2025-10-08 08:11:55 -04:00
Mirza Esaaf Shuja
8cd0d5faa5 fix: use correct hyprland config template for greeter (#338) 2025-10-07 20:48:27 -04:00
bbedward
a741d892a9 Remove MultiEffect opacity 2025-10-07 16:01:05 -04:00
bbedward
9add3361e0 DankModal pixel snapping 2025-10-07 15:32:21 -04:00
bbedward
4aac70ab5f pass config_dir as an argument to matugen-worker 2025-10-07 13:46:41 -04:00
bbedward
980aec714a Fix typo in workspace mousearea 2025-10-07 13:39:53 -04:00
bbedward
af8ee5af0f Switch to dispatch-style release workflow 2025-10-07 12:50:09 -04:00
bbedward
32d9aa0cf2 Separate bar font scale 2025-10-07 08:29:48 -04:00
bbedward
7e49631912 Migrate plugin settings to separate file 2025-10-07 08:22:21 -04:00
bbedward
43970d34aa Fix keyboard navi in file browser 2025-10-07 00:14:56 -04:00
bbedward
abb3c40697 Thread font loading 2025-10-06 23:51:49 -04:00
bbedward
2757a41102 Add invert on mode change for launcher logo 2025-10-06 23:24:39 -04:00
bbedward
1f8bddaa5e Fix dank bar on overview clicks 2025-10-06 22:35:30 -04:00
bbedward
4c3b7ca60f Support prime-run 2025-10-06 20:50:36 -04:00
bbedward
d9d83e5767 spotlight: remove categories 2025-10-06 20:42:21 -04:00
bbedward
b4ebde47be Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-06 20:40:20 -04:00
bbedward
ef9f76190d Implement sizing mechanism for bar widget content 2025-10-06 20:40:02 -04:00
Nek
71b96efca0 Allow user's matugen config to be ran by dms (#328)
* Allow user's matugen config to be ran by dms

matugen-workers.sh now also loads the configs and templates from `~/.config/matugen/dms` and handles them the same way dms does its own matugen configs.

* Do not auto create user's matugen directories
2025-10-06 20:33:44 -04:00
bbedward
7158e09b0e Customizable launcher logo 2025-10-06 20:08:21 -04:00
bbedward
8ef125bed2 Fix toast width 2025-10-06 19:42:22 -04:00
bbedward
0b11fb2fd5 Fix variant plugins in center section 2025-10-06 17:57:01 -04:00
bbedward
3871f3cf3d Aggresive focus restoration 2025-10-06 17:52:48 -04:00
bbedward
7c5d1ec0f6 launcher + dock menu improvements 2025-10-06 17:46:19 -04:00
bbedward
b507b08e34 Re-org niri service & handle reconnects to socket 2025-10-06 16:46:05 -04:00
bbedward
1b06090f72 Remove terms that shouldnt be localized 2025-10-06 16:06:19 -04:00
bbedward
5460c20ac3 Localization framework 2025-10-06 16:00:50 -04:00
bbedward
2ccec607a0 Hide third party plugins, by default 2025-10-06 15:36:33 -04:00
bbedward
8a99fcf188 Set exec working directory to home when not defined 2025-10-06 15:28:58 -04:00
bbedward
1e2489ca76 Add support for plugin registry + install + management 2025-10-06 15:26:44 -04:00
bbedward
89793d2d62 plugins: support for multiple widgets per-plugin (variants) 2025-10-06 12:33:58 -04:00
bbedward
11a1af89f4 Allow removing force-padding on monitor widgets + plugin load fixes 2025-10-06 11:32:25 -04:00
bbedward
e24ddb804d Filter out scratch_term ws on hyprland 2025-10-06 09:38:10 -04:00
bbedward
3524d365be Add Desktop Actions to launcher + dock 2025-10-06 09:35:50 -04:00
bbedward
2b3b9d037c re-add transitions 2025-10-06 09:10:58 -04:00
Bruno Cesar Rocha
5140cd9d7f fix: use correct icon on CPU temp on bar (#327) 2025-10-06 09:05:44 -04:00
Parthiv Seetharaman
2df9437b39 add nixos support for greeter (#298)
* add nixos support for greeter

* fix greeter config file access

* fix wallpaper perms and allow for adding extra compositor config

* fix greeter config files ownership

* set default for compositor.extraConfig

* update option docs about copying instead of symlinking

* explain configHome in doc further

* add nixos option to redirect greeter logs

* prevent possible errors in greetd preStart
2025-10-06 08:42:36 -04:00
purian23
db440b8a14 feat: Add a 1px border to Dankbar w/edge detection
-  No edge gap (spacing = 0 AND corner radius = 0): Draws 1px border only on the exposed edge (bottom for top bar, top for bottom bar, right for left bar, left for right bar)
- Has edge gap (spacing > 0 OR corner radius > 0): Draws 1px border around all sides
2025-10-05 23:56:40 -04:00
purian23
c3dd70bc99 fix: Color selection state 2025-10-05 23:21:00 -04:00
purian23
223e783bbc Update Notepad theme corner radius to match user settings 2025-10-05 22:43:49 -04:00
purian23
ca086dbf16 ColorPicker visual feedback upon invaid hex code 2025-10-05 22:35:27 -04:00
purian23
523422cf6c feat: Rebuilt DankColorPicker w/Color Sampling
- Fully custom built ColorDialog
- Replaces previous Wallpaper background color tool
- Requires hyprpicker for color sampling, thanks @Vaxry
2025-10-05 22:18:26 -04:00
bbedward
2dc310dcbc Add VPN widget for control center 2025-10-05 21:28:52 -04:00
bbedward
c092cd2921 plugins: support control center plugins 2025-10-05 21:09:29 -04:00
bbedward
2b14ef76c9 resolve dms-colors dir dynamically in matugen-worker 2025-10-05 19:22:17 -04:00
bbedward
fbbf10078f Fix focus issues with add widget dialog 2025-10-05 19:17:11 -04:00
Abhinav Chalise
2315d423c4 remove signal strength from ssidName (#323) 2025-10-05 19:01:03 -04:00
bbedward
9a43465ebf Fix initial light/dark mode state in cc 2025-10-05 15:28:46 -04:00
bbedward
fc1444763d Update dms-colors matugen 2025-10-05 15:07:08 -04:00
purian23
804bf879ed Default to Zenity Color picker
- QT 6.9.3 removed the previous color picker we used
- Zenity is default in gnome based distros
- Kcolorchooser can be installed separately and will be preferred over Zenity
2025-10-04 22:59:02 -04:00
purian23
ad44f09421 feat: Display persistent OSD percentage option 2025-10-04 22:02:25 -04:00
purian23
df2469468b Refactor Notepad search 2025-10-04 14:38:30 -04:00
bbedward
f8f4fe11eb Explicit null 2025-10-04 12:10:03 -04:00
bbedward
039a370add Explicit battery override 2025-10-04 12:07:59 -04:00
bbedward
6feebc086d Update readme 2025-10-04 09:47:28 -04:00
bbedward
bc335c7d72 Remove arbitrary height limit on notification settings 2025-10-04 08:46:35 -04:00
bbedward
a6dd7254b2 Clip system tab flickable 2025-10-04 08:40:17 -04:00
bbedward
7b1026c624 Include env to override battery device 2025-10-04 08:23:49 -04:00
bbedward
4758393cc1 Fix dock hide with padding 2025-10-04 08:17:41 -04:00
bbedward
52373a3a7d Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-04 08:12:51 -04:00
bbedward
c30f9a2841 use wayland idle-inhibit when available 2025-10-04 08:12:18 -04:00
Abhinav Chalise
44d6f8f15c Fix toggle function to return correct theme mode (#309) 2025-10-04 07:36:23 -04:00
purian23
5ada12f989 feat: Add Notepad search function 2025-10-04 02:25:42 -04:00
bbedward
d213045168 default instead of prefer-light 2025-10-04 01:18:43 -04:00
bbedward
d83478239e plugins: add pillClickAction + PopoutService 2025-10-04 01:12:17 -04:00
purian23
3869955357 Update Notepad entry text color 2025-10-04 00:50:47 -04:00
bbedward
345d37edf8 Shift urgent workspace color 2025-10-04 00:11:49 -04:00
bbedward
2788ef28cf Update README 2025-10-03 23:21:46 -04:00
bbedward
0d5c1bb3df Add "daemon" type of plugins 2025-10-03 22:55:07 -04:00
bbedward
c3d505cdad better proc usage 2025-10-03 19:35:13 -04:00
bbedward
90854e1dd4 version in about tab and ci 2025-10-03 19:31:07 -04:00
bbedward
f96e3b04be Disable powermenu bg on cc 2025-10-03 18:29:06 -04:00
bbedward
44449e26a0 Handle urgent workspaces 2025-10-03 18:17:24 -04:00
bbedward
ddc88fd360 Fix positioning of power menu 2025-10-03 17:21:43 -04:00
bbedward
fedec450cb Keep the modal, but relatively positioned 2025-10-03 17:16:34 -04:00
bbedward
04ea742830 Reapply "Always use power menu modal"
This reverts commit 5a5c860cef.
2025-10-03 17:14:43 -04:00
bbedward
5a5c860cef Revert "Always use power menu modal"
This reverts commit 55d06a43f8.

mm, not sure how I feel about it
2025-10-03 17:14:08 -04:00
bbedward
55d06a43f8 Always use power menu modal 2025-10-03 16:54:10 -04:00
bbedward
71eecd6e7b Revert "betterbird/thunderbird matugen template"
This reverts commit 6f3019f84b.
2025-10-03 16:20:21 -04:00
bbedward
6f3019f84b betterbird/thunderbird matugen template 2025-10-03 16:16:47 -04:00
bbedward
e95d3126b2 Modal/Popout layout alterations 2025-10-03 15:15:44 -04:00
bbedward
5da265bf0b Instruct fonts to be global (makes sense for greeter) 2025-10-03 13:52:39 -04:00
bbedward
2ce9c43b8c disable layer debug opt 2025-10-03 11:21:43 -04:00
206 changed files with 21870 additions and 4292 deletions

View File

@@ -1,59 +1,97 @@
name: Create Release # Release from a dispatch event from the danklinux repo
name: Create Release from DMS
on: on:
push: repository_dispatch:
tags: types: [dms_release]
- 'v*'
permissions:
contents: write
concurrency: concurrency:
group: release-${{ github.ref_name }} group: release-${{ github.event.client_payload.tag }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
create_release: create_release_from_dms:
name: 📦 Create GitHub Release
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
env:
TAG: ${{ github.event.client_payload.tag }}
DMS_REPO: ${{ github.event.client_payload.dms_repo }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Fetch full history for changelog generation fetch-depth: 0
- name: Ensure VERSION and tag
run: |
set -euxo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# create/update VERSION file to match incoming tag
echo "${TAG}" > VERSION
if ! git diff --quiet -- VERSION; then
git add VERSION
git commit -m "Add VERSION file for ${TAG} (from DMS)"
fi
# If tag doesn't exist (or differs), (re)create it
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
echo "Tag ${TAG} already exists"
else
git tag "${TAG}"
fi
# Push commit (if any) and tag
git push --follow-tags origin HEAD
# Generate changelog
- name: Generate Changelog - name: Generate Changelog
id: changelog id: changelog
run: | run: |
# Get the previous tag set -e
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then if [ -z "$PREVIOUS_TAG" ]; then
echo "No previous tag found, using all commits"
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50) CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50)
else else
echo "Generating changelog from $PREVIOUS_TAG to HEAD" CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" $PREVIOUS_TAG..HEAD)
fi fi
# Create the changelog with proper formatting
cat > CHANGELOG.md << EOF cat > CHANGELOG.md << EOF
## What's Changed ## What's Changed
$CHANGELOG $CHANGELOG
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ github.ref_name }} **Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${TAG}
EOF EOF
# Set output for use in release step
echo "changelog<<EOF" >> $GITHUB_OUTPUT echo "changelog<<EOF" >> $GITHUB_OUTPUT
cat CHANGELOG.md >> $GITHUB_OUTPUT cat CHANGELOG.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
# Create GitHub Release - name: Create/Update DankMaterialShell Release
- name: Create GitHub Release uses: softprops/action-gh-release@v2
uses: comnoco/create-release-action@v2.0.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref_name }} tag_name: ${{ env.TAG }}
release_name: Release ${{ github.ref_name }} name: Release ${{ env.TAG }}
body: ${{ steps.changelog.outputs.changelog }} body: ${{ steps.changelog.outputs.changelog }}
draft: false draft: false
prerelease: ${{ contains(github.ref_name, '-') }} prerelease: ${{ contains(env.TAG, '-') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download DMS release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euxo pipefail
# gh is preinstalled on ubuntu-24.04; auth via GH_TOKEN env
gh release download "${TAG}" -R "${DMS_REPO}" --dir ./_dms_assets
- name: Attach DMS assets to DankMaterialShell release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG }}
files: _dms_assets/**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -448,7 +448,13 @@ When modifying the shell:
### Creating Plugins ### Creating Plugins
Plugins are external, dynamically-loaded components that extend DankBar functionality. Plugins are stored in `~/.config/DankMaterialShell/plugins/` and have their settings isolated from core DMS settings. Plugins are external, dynamically-loaded components that extend DankMaterialShell functionality. Plugins are stored in `~/.config/DankMaterialShell/plugins/` and have their settings isolated from core DMS settings.
**Plugin Types:**
- **Widget plugins** (`"type": "widget"` or omit type field): Display UI components in DankBar
- **Daemon plugins** (`"type": "daemon"`): Run invisibly in the background without UI
#### Widget Plugins
1. **Create plugin directory**: 1. **Create plugin directory**:
```bash ```bash
@@ -464,6 +470,7 @@ Plugins are external, dynamically-loaded components that extend DankBar function
"version": "1.0.0", "version": "1.0.0",
"author": "Your Name", "author": "Your Name",
"icon": "extension", "icon": "extension",
"type": "widget",
"component": "./YourWidget.qml", "component": "./YourWidget.qml",
"settings": "./YourSettings.qml", "settings": "./YourSettings.qml",
"permissions": ["settings_read", "settings_write"] "permissions": ["settings_read", "settings_write"]
@@ -545,6 +552,65 @@ Plugins are external, dynamically-loaded components that extend DankBar function
- Toggle plugin to enable - Toggle plugin to enable
- Add plugin ID to DankBar widget list - Add plugin ID to DankBar widget list
#### Daemon Plugins
Daemon plugins run invisibly in the background without any UI components. They're useful for monitoring system events, background tasks, or data synchronization.
1. **Create plugin directory**:
```bash
mkdir -p ~/.config/DankMaterialShell/plugins/YourDaemon
```
2. **Create manifest** (`plugin.json`):
```json
{
"id": "yourDaemon",
"name": "Your Daemon",
"description": "Background daemon description",
"version": "1.0.0",
"author": "Your Name",
"icon": "settings_applications",
"type": "daemon",
"component": "./YourDaemon.qml",
"permissions": ["settings_read", "settings_write"]
}
```
3. **Create daemon component** (`YourDaemon.qml`):
```qml
import QtQuick
import qs.Common
import qs.Services
Item {
id: root
property var pluginService: null
Connections {
target: SessionData
function onWallpaperPathChanged() {
console.log("Wallpaper changed:", SessionData.wallpaperPath)
if (pluginService) {
pluginService.savePluginData("yourDaemon", "lastEvent", Date.now())
}
}
}
Component.onCompleted: {
console.log("Daemon started")
}
}
```
4. **Enable daemon**:
- Open Settings → Plugins
- Click "Scan for Plugins"
- Toggle daemon to enable
- Daemon runs automatically in background
**Example**: See `PLUGINS/WallpaperWatcherDaemon/` for a complete daemon plugin that monitors wallpaper changes
**Plugin Directory Structure:** **Plugin Directory Structure:**
``` ```
~/.config/DankMaterialShell/ ~/.config/DankMaterialShell/

View File

@@ -72,10 +72,6 @@ Singleton {
saveSettings() saveSettings()
} }
function getAppUsageRanking() {
return appUsageRanking
}
function getRankedApps() { function getRankedApps() {
var apps = [] var apps = []
for (var appId in appUsageRanking) { for (var appId in appUsageRanking) {

62
Common/DankSocket.qml Normal file
View File

@@ -0,0 +1,62 @@
import QtQuick
import Quickshell.Io
Item {
id: root
property alias path: socket.path
property alias parser: socket.parser
property bool connected: false
property int reconnectBaseMs: 400
property int reconnectMaxMs: 15000
property int _reconnectAttempt: 0
signal connectionStateChanged()
onConnectedChanged: {
socket.connected = connected
}
Socket {
id: socket
onConnectionStateChanged: {
root.connectionStateChanged()
if (connected) {
root._reconnectAttempt = 0
return
}
if (root.connected) {
root._scheduleReconnect()
}
}
}
Timer {
id: reconnectTimer
interval: 0
repeat: false
onTriggered: {
socket.connected = false
Qt.callLater(() => socket.connected = true)
}
}
function send(data) {
const json = typeof data === "string" ? data : JSON.stringify(data)
const message = json.endsWith("\n") ? json : json + "\n"
socket.write(message)
socket.flush()
}
function _scheduleReconnect() {
const pow = Math.min(_reconnectAttempt, 10)
const base = Math.min(reconnectBaseMs * Math.pow(2, pow), reconnectMaxMs)
const jitter = Math.floor(Math.random() * Math.floor(base / 4))
reconnectTimer.interval = base + jitter
reconnectTimer.restart()
_reconnectAttempt++
}
}

113
Common/I18n.qml Normal file
View File

@@ -0,0 +1,113 @@
import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
readonly property string _rawLocale: Qt.locale().name
readonly property string _lang: _rawLocale.split(/[_-]/)[0]
readonly property var _candidates: {
const fullUnderscore = _rawLocale;
const fullHyphen = _rawLocale.replace("_", "-");
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
}
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
property string currentLocale: "en"
property var translations: ({})
property bool translationsLoaded: false
property url _selectedPath: ""
FolderListModel {
id: dir
folder: root.translationsFolder
nameFilters: ["*.json"]
showDirs: false
showDotAndDotDot: false
onStatusChanged: if (status === FolderListModel.Ready) root._pickTranslation()
}
FileView {
id: translationLoader
path: root._selectedPath
onLoaded: {
try {
root.translations = JSON.parse(text())
root.translationsLoaded = true
console.log(`I18n: Loaded translations for '${root.currentLocale}' ` +
`(${Object.keys(root.translations).length} contexts)`)
} catch (e) {
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
"- falling back to English")
root._fallbackToEnglish()
}
}
onLoadFailed: (error) => {
console.warn(`I18n: Failed to load '${root.currentLocale}' (${error}), ` +
"falling back to English")
root._fallbackToEnglish()
}
}
function _pickTranslation() {
const present = new Set()
for (let i = 0; i < dir.count; i++) {
const name = dir.get(i, "fileName") // e.g. "zh_CN.json"
if (name && name.endsWith(".json")) {
present.add(name.slice(0, -5))
}
}
for (let i = 0; i < _candidates.length; i++) {
const cand = _candidates[i]
if (present.has(cand)) {
_useLocale(cand, dir.folder + "/" + cand + ".json")
return
}
}
_fallbackToEnglish()
}
function _useLocale(localeTag, fileUrl) {
currentLocale = localeTag
_selectedPath = fileUrl
translationsLoaded = false
translations = ({})
console.log(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
}
function _fallbackToEnglish() {
currentLocale = "en"
_selectedPath = ""
translationsLoaded = false
translations = ({})
console.warn("I18n: Falling back to built-in English strings")
}
function tr(term, context) {
if (!translationsLoaded || !translations) return term
const ctx = context || term
if (translations[ctx] && translations[ctx][term]) return translations[ctx][term]
for (const c in translations) {
if (translations[c] && translations[c][term]) return translations[c][term]
}
return term
}
function trContext(context, term) {
if (!translationsLoaded || !translations) return term
if (translations[context] && translations[context][term]) return translations[context][term]
return term
}
}

View File

@@ -38,6 +38,10 @@ Singleton {
return stringify(path).replace("file://", "") return stringify(path).replace("file://", "")
} }
function toFileUrl(path: string): string {
return path.startsWith("file://") ? path : "file://" + path
}
function mkdir(path: url): void { function mkdir(path: url): void {
Quickshell.execDetached(["mkdir", "-p", strip(path)]) Quickshell.execDetached(["mkdir", "-p", strip(path)])
} }

View File

@@ -70,6 +70,9 @@ Singleton {
property int batteryHibernateTimeout: 0 // Never property int batteryHibernateTimeout: 0 // Never
property bool lockBeforeSuspend: false property bool lockBeforeSuspend: false
property bool loginctlLockIntegration: true
property var recentColors: []
property bool showThirdPartyPlugins: false
Component.onCompleted: { Component.onCompleted: {
@@ -150,6 +153,9 @@ Singleton {
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0 batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0 batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
loginctlLockIntegration = settings.loginctlLockIntegration !== undefined ? settings.loginctlLockIntegration : true
recentColors = settings.recentColors !== undefined ? settings.recentColors : []
showThirdPartyPlugins = settings.showThirdPartyPlugins !== undefined ? settings.showThirdPartyPlugins : false
if (!isGreeterMode) { if (!isGreeterMode) {
if (typeof Theme !== "undefined") { if (typeof Theme !== "undefined") {
@@ -210,7 +216,10 @@ Singleton {
"batteryLockTimeout": batteryLockTimeout, "batteryLockTimeout": batteryLockTimeout,
"batterySuspendTimeout": batterySuspendTimeout, "batterySuspendTimeout": batterySuspendTimeout,
"batteryHibernateTimeout": batteryHibernateTimeout, "batteryHibernateTimeout": batteryHibernateTimeout,
"lockBeforeSuspend": lockBeforeSuspend "lockBeforeSuspend": lockBeforeSuspend,
"loginctlLockIntegration": loginctlLockIntegration,
"recentColors": recentColors,
"showThirdPartyPlugins": showThirdPartyPlugins
}, null, 2)) }, null, 2))
} }
@@ -359,6 +368,16 @@ Singleton {
saveSettings() saveSettings()
} }
function addRecentColor(color) {
const colorStr = color.toString()
let recent = recentColors.slice()
recent = recent.filter(c => c !== colorStr)
recent.unshift(colorStr)
if (recent.length > 5) recent = recent.slice(0, 5)
recentColors = recent
saveSettings()
}
function addPinnedApp(appId) { function addPinnedApp(appId) {
if (!appId) if (!appId)
return return
@@ -627,6 +646,16 @@ Singleton {
saveSettings() saveSettings()
} }
function setLoginctlLockIntegration(enabled) {
loginctlLockIntegration = enabled
saveSettings()
}
function setShowThirdPartyPlugins(enabled) {
showThirdPartyPlugins = enabled
saveSettings()
}
FileView { FileView {
id: settingsFile id: settingsFile
@@ -641,8 +670,8 @@ Singleton {
} }
} }
onLoadFailed: error => { onLoadFailed: error => {
if (!isGreeterMode && !hasTriedDefaultSettings) { if (!isGreeterMode && !hasTriedDefaultSession) {
hasTriedDefaultSettings = true hasTriedDefaultSession = true
defaultSessionCheckProcess.running = true defaultSessionCheckProcess.running = true
} }
} }
@@ -671,7 +700,7 @@ Singleton {
id: defaultSessionCheckProcess id: defaultSessionCheckProcess
command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir
+ "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"] + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"]
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {
@@ -817,4 +846,4 @@ Singleton {
} }
} }
} }
} }

View File

@@ -103,15 +103,19 @@ Singleton {
property bool qt5ctAvailable: false property bool qt5ctAvailable: false
property bool qt6ctAvailable: false property bool qt6ctAvailable: false
property bool gtkAvailable: false property bool gtkAvailable: false
property bool useOSLogo: false property string launcherLogoMode: "apps"
property string osLogoColorOverride: "" property string launcherLogoCustomPath: ""
property real osLogoBrightness: 0.5 property string launcherLogoColorOverride: ""
property real osLogoContrast: 1 property bool launcherLogoColorInvertOnMode: false
property real launcherLogoBrightness: 0.5
property real launcherLogoContrast: 1
property int launcherLogoSizeOffset: 0
property bool weatherEnabled: true property bool weatherEnabled: true
property string fontFamily: "Inter Variable" property string fontFamily: "Inter Variable"
property string monoFontFamily: "Fira Code" property string monoFontFamily: "Fira Code"
property int fontWeight: Font.Normal property int fontWeight: Font.Normal
property real fontScale: 1.0 property real fontScale: 1.0
property real dankBarFontScale: 1.0
property bool notepadUseMonospace: true property bool notepadUseMonospace: true
property string notepadFontFamily: "" property string notepadFontFamily: ""
property real notepadFontSize: 14 property real notepadFontSize: 14
@@ -150,6 +154,7 @@ Singleton {
property bool dankBarSquareCorners: false property bool dankBarSquareCorners: false
property bool dankBarNoBackground: false property bool dankBarNoBackground: false
property bool dankBarGothCornersEnabled: false property bool dankBarGothCornersEnabled: false
property bool dankBarBorderEnabled: false
property int dankBarPosition: SettingsData.Position.Top property int dankBarPosition: SettingsData.Position.Top
property bool dankBarIsVertical: dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right property bool dankBarIsVertical: dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right
property bool lockScreenShowPowerActions: true property bool lockScreenShowPowerActions: true
@@ -160,6 +165,7 @@ Singleton {
property int notificationTimeoutNormal: 5000 property int notificationTimeoutNormal: 5000
property int notificationTimeoutCritical: 0 property int notificationTimeoutCritical: 0
property int notificationPopupPosition: SettingsData.Position.Top property int notificationPopupPosition: SettingsData.Position.Top
property bool osdAlwaysShowValue: false
property var screenPreferences: ({}) property var screenPreferences: ({})
property int animationSpeed: SettingsData.AnimationSpeed.Short property int animationSpeed: SettingsData.AnimationSpeed.Short
readonly property string defaultFontFamily: "Inter Variable" readonly property string defaultFontFamily: "Inter Variable"
@@ -167,6 +173,7 @@ Singleton {
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation) readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation) readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
readonly property string _configDir: Paths.strip(_configUrl) readonly property string _configDir: Paths.strip(_configUrl)
readonly property string pluginSettingsPath: _configDir + "/DankMaterialShell/plugin_settings.json"
signal forceDankBarLayoutRefresh signal forceDankBarLayoutRefresh
signal forceDockLayoutRefresh signal forceDockLayoutRefresh
@@ -174,6 +181,7 @@ Singleton {
signal workspaceIconsUpdated signal workspaceIconsUpdated
property bool _loading: false property bool _loading: false
property bool _pluginSettingsLoading: false
property var pluginSettings: ({}) property var pluginSettings: ({})
@@ -202,7 +210,8 @@ Singleton {
"size": 20, "size": 20,
"selectedGpuIndex": 0, "selectedGpuIndex": 0,
"pciId": "", "pciId": "",
"mountPath": "/" "mountPath": "/",
"minimumWidth": true
} }
leftWidgetsModel.append(dummyItem) leftWidgetsModel.append(dummyItem)
centerWidgetsModel.append(dummyItem) centerWidgetsModel.append(dummyItem)
@@ -217,13 +226,41 @@ Singleton {
_loading = true _loading = true
parseSettings(settingsFile.text()) parseSettings(settingsFile.text())
_loading = false _loading = false
loadPluginSettings()
}
function loadPluginSettings() {
_pluginSettingsLoading = true
parsePluginSettings(pluginSettingsFile.text())
_pluginSettingsLoading = false
}
function parsePluginSettings(content) {
_pluginSettingsLoading = true
try {
if (content && content.trim()) {
pluginSettings = JSON.parse(content)
} else {
pluginSettings = {}
}
} catch (e) {
console.warn("SettingsData: Failed to parse plugin settings:", e.message)
pluginSettings = {}
} finally {
_pluginSettingsLoading = false
}
} }
function parseSettings(content) { function parseSettings(content) {
_loading = true _loading = true
var shouldMigrate = false
try { try {
if (content && content.trim()) { if (content && content.trim()) {
var settings = JSON.parse(content) var settings = JSON.parse(content)
if (settings.pluginSettings !== undefined) {
pluginSettings = settings.pluginSettings
shouldMigrate = true
}
// Auto-migrate from old theme system // Auto-migrate from old theme system
if (settings.themeIndex !== undefined || settings.themeIsDynamic !== undefined) { if (settings.themeIndex !== undefined || settings.themeIsDynamic !== undefined) {
const themeNames = ["blue", "deepBlue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral"] const themeNames = ["blue", "deepBlue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral"]
@@ -319,14 +356,25 @@ Singleton {
spotlightModalViewMode = settings.spotlightModalViewMode !== undefined ? settings.spotlightModalViewMode : "list" spotlightModalViewMode = settings.spotlightModalViewMode !== undefined ? settings.spotlightModalViewMode : "list"
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto" networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto"
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default" iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default"
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false if (settings.useOSLogo !== undefined) {
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : "" launcherLogoMode = settings.useOSLogo ? "os" : "apps"
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5 launcherLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : ""
osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1 launcherLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5
launcherLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1
} else {
launcherLogoMode = settings.launcherLogoMode !== undefined ? settings.launcherLogoMode : "apps"
launcherLogoCustomPath = settings.launcherLogoCustomPath !== undefined ? settings.launcherLogoCustomPath : ""
launcherLogoColorOverride = settings.launcherLogoColorOverride !== undefined ? settings.launcherLogoColorOverride : ""
launcherLogoColorInvertOnMode = settings.launcherLogoColorInvertOnMode !== undefined ? settings.launcherLogoColorInvertOnMode : false
launcherLogoBrightness = settings.launcherLogoBrightness !== undefined ? settings.launcherLogoBrightness : 0.5
launcherLogoContrast = settings.launcherLogoContrast !== undefined ? settings.launcherLogoContrast : 1
launcherLogoSizeOffset = settings.launcherLogoSizeOffset !== undefined ? settings.launcherLogoSizeOffset : 0
}
fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : defaultFontFamily fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : defaultFontFamily
monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal
fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0 fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0
dankBarFontScale = settings.dankBarFontScale !== undefined ? settings.dankBarFontScale : 1.0
notepadUseMonospace = settings.notepadUseMonospace !== undefined ? settings.notepadUseMonospace : true notepadUseMonospace = settings.notepadUseMonospace !== undefined ? settings.notepadUseMonospace : true
notepadFontFamily = settings.notepadFontFamily !== undefined ? settings.notepadFontFamily : "" notepadFontFamily = settings.notepadFontFamily !== undefined ? settings.notepadFontFamily : ""
notepadFontSize = settings.notepadFontSize !== undefined ? settings.notepadFontSize : 14 notepadFontSize = settings.notepadFontSize !== undefined ? settings.notepadFontSize : 14
@@ -351,19 +399,20 @@ Singleton {
notificationTimeoutNormal = settings.notificationTimeoutNormal !== undefined ? settings.notificationTimeoutNormal : 5000 notificationTimeoutNormal = settings.notificationTimeoutNormal !== undefined ? settings.notificationTimeoutNormal : 5000
notificationTimeoutCritical = settings.notificationTimeoutCritical !== undefined ? settings.notificationTimeoutCritical : 0 notificationTimeoutCritical = settings.notificationTimeoutCritical !== undefined ? settings.notificationTimeoutCritical : 0
notificationPopupPosition = settings.notificationPopupPosition !== undefined ? settings.notificationPopupPosition : SettingsData.Position.Top notificationPopupPosition = settings.notificationPopupPosition !== undefined ? settings.notificationPopupPosition : SettingsData.Position.Top
osdAlwaysShowValue = settings.osdAlwaysShowValue !== undefined ? settings.osdAlwaysShowValue : false
dankBarSpacing = settings.dankBarSpacing !== undefined ? settings.dankBarSpacing : (settings.topBarSpacing !== undefined ? settings.topBarSpacing : 4) dankBarSpacing = settings.dankBarSpacing !== undefined ? settings.dankBarSpacing : (settings.topBarSpacing !== undefined ? settings.topBarSpacing : 4)
dankBarBottomGap = settings.dankBarBottomGap !== undefined ? settings.dankBarBottomGap : (settings.topBarBottomGap !== undefined ? settings.topBarBottomGap : 0) dankBarBottomGap = settings.dankBarBottomGap !== undefined ? settings.dankBarBottomGap : (settings.topBarBottomGap !== undefined ? settings.topBarBottomGap : 0)
dankBarInnerPadding = settings.dankBarInnerPadding !== undefined ? settings.dankBarInnerPadding : (settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 4) dankBarInnerPadding = settings.dankBarInnerPadding !== undefined ? settings.dankBarInnerPadding : (settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 4)
dankBarSquareCorners = settings.dankBarSquareCorners !== undefined ? settings.dankBarSquareCorners : (settings.topBarSquareCorners !== undefined ? settings.topBarSquareCorners : false) dankBarSquareCorners = settings.dankBarSquareCorners !== undefined ? settings.dankBarSquareCorners : (settings.topBarSquareCorners !== undefined ? settings.topBarSquareCorners : false)
dankBarNoBackground = settings.dankBarNoBackground !== undefined ? settings.dankBarNoBackground : (settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false) dankBarNoBackground = settings.dankBarNoBackground !== undefined ? settings.dankBarNoBackground : (settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false)
dankBarGothCornersEnabled = settings.dankBarGothCornersEnabled !== undefined ? settings.dankBarGothCornersEnabled : (settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : false) dankBarGothCornersEnabled = settings.dankBarGothCornersEnabled !== undefined ? settings.dankBarGothCornersEnabled : (settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : false)
dankBarBorderEnabled = settings.dankBarBorderEnabled !== undefined ? settings.dankBarBorderEnabled : false
dankBarPosition = settings.dankBarPosition !== undefined ? settings.dankBarPosition : (settings.dankBarAtBottom !== undefined ? (settings.dankBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : (settings.topBarAtBottom !== undefined ? (settings.topBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : SettingsData.Position.Top)) dankBarPosition = settings.dankBarPosition !== undefined ? settings.dankBarPosition : (settings.dankBarAtBottom !== undefined ? (settings.dankBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : (settings.topBarAtBottom !== undefined ? (settings.topBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : SettingsData.Position.Top))
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true
hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch" widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch"
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s" surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({}) screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
pluginSettings = settings.pluginSettings !== undefined ? settings.pluginSettings : ({})
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short
applyStoredTheme() applyStoredTheme()
detectAvailableIconThemes() detectAvailableIconThemes()
@@ -378,6 +427,11 @@ Singleton {
} finally { } finally {
_loading = false _loading = false
} }
if (shouldMigrate) {
savePluginSettings()
saveSettings()
}
} }
function saveSettings() { function saveSettings() {
@@ -440,14 +494,18 @@ Singleton {
"spotlightModalViewMode": spotlightModalViewMode, "spotlightModalViewMode": spotlightModalViewMode,
"networkPreference": networkPreference, "networkPreference": networkPreference,
"iconTheme": iconTheme, "iconTheme": iconTheme,
"useOSLogo": useOSLogo, "launcherLogoMode": launcherLogoMode,
"osLogoColorOverride": osLogoColorOverride, "launcherLogoCustomPath": launcherLogoCustomPath,
"osLogoBrightness": osLogoBrightness, "launcherLogoColorOverride": launcherLogoColorOverride,
"osLogoContrast": osLogoContrast, "launcherLogoColorInvertOnMode": launcherLogoColorInvertOnMode,
"launcherLogoBrightness": launcherLogoBrightness,
"launcherLogoContrast": launcherLogoContrast,
"launcherLogoSizeOffset": launcherLogoSizeOffset,
"fontFamily": fontFamily, "fontFamily": fontFamily,
"monoFontFamily": monoFontFamily, "monoFontFamily": monoFontFamily,
"fontWeight": fontWeight, "fontWeight": fontWeight,
"fontScale": fontScale, "fontScale": fontScale,
"dankBarFontScale": dankBarFontScale,
"notepadUseMonospace": notepadUseMonospace, "notepadUseMonospace": notepadUseMonospace,
"notepadFontFamily": notepadFontFamily, "notepadFontFamily": notepadFontFamily,
"notepadFontSize": notepadFontSize, "notepadFontSize": notepadFontSize,
@@ -474,6 +532,7 @@ Singleton {
"dankBarSquareCorners": dankBarSquareCorners, "dankBarSquareCorners": dankBarSquareCorners,
"dankBarNoBackground": dankBarNoBackground, "dankBarNoBackground": dankBarNoBackground,
"dankBarGothCornersEnabled": dankBarGothCornersEnabled, "dankBarGothCornersEnabled": dankBarGothCornersEnabled,
"dankBarBorderEnabled": dankBarBorderEnabled,
"dankBarPosition": dankBarPosition, "dankBarPosition": dankBarPosition,
"lockScreenShowPowerActions": lockScreenShowPowerActions, "lockScreenShowPowerActions": lockScreenShowPowerActions,
"hideBrightnessSlider": hideBrightnessSlider, "hideBrightnessSlider": hideBrightnessSlider,
@@ -483,12 +542,18 @@ Singleton {
"notificationTimeoutNormal": notificationTimeoutNormal, "notificationTimeoutNormal": notificationTimeoutNormal,
"notificationTimeoutCritical": notificationTimeoutCritical, "notificationTimeoutCritical": notificationTimeoutCritical,
"notificationPopupPosition": notificationPopupPosition, "notificationPopupPosition": notificationPopupPosition,
"osdAlwaysShowValue": osdAlwaysShowValue,
"screenPreferences": screenPreferences, "screenPreferences": screenPreferences,
"pluginSettings": pluginSettings,
"animationSpeed": animationSpeed "animationSpeed": animationSpeed
}, null, 2)) }, null, 2))
} }
function savePluginSettings() {
if (_pluginSettingsLoading)
return
pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2))
}
function setShowWorkspaceIndex(enabled) { function setShowWorkspaceIndex(enabled) {
showWorkspaceIndex = enabled showWorkspaceIndex = enabled
saveSettings() saveSettings()
@@ -805,6 +870,7 @@ Singleton {
var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex
var pciId = typeof order[i] === "string" ? undefined : order[i].pciId var pciId = typeof order[i] === "string" ? undefined : order[i].pciId
var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath
var minimumWidth = typeof order[i] === "string" ? undefined : order[i].minimumWidth
var item = { var item = {
"widgetId": widgetId, "widgetId": widgetId,
"enabled": enabled "enabled": enabled
@@ -817,6 +883,8 @@ Singleton {
item.pciId = pciId item.pciId = pciId
if (mountPath !== undefined) if (mountPath !== undefined)
item.mountPath = mountPath item.mountPath = mountPath
if (minimumWidth !== undefined)
item.minimumWidth = minimumWidth
listModel.append(item) listModel.append(item)
} }
@@ -939,23 +1007,38 @@ Singleton {
updateQtIconTheme(iconTheme) updateQtIconTheme(iconTheme)
} }
function setUseOSLogo(enabled) { function setLauncherLogoMode(mode) {
useOSLogo = enabled launcherLogoMode = mode
saveSettings() saveSettings()
} }
function setOSLogoColorOverride(color) { function setLauncherLogoCustomPath(path) {
osLogoColorOverride = color launcherLogoCustomPath = path
saveSettings() saveSettings()
} }
function setOSLogoBrightness(brightness) { function setLauncherLogoColorOverride(color) {
osLogoBrightness = brightness launcherLogoColorOverride = color
saveSettings() saveSettings()
} }
function setOSLogoContrast(contrast) { function setLauncherLogoColorInvertOnMode(invert) {
osLogoContrast = contrast launcherLogoColorInvertOnMode = invert
saveSettings()
}
function setLauncherLogoBrightness(brightness) {
launcherLogoBrightness = brightness
saveSettings()
}
function setLauncherLogoContrast(contrast) {
launcherLogoContrast = contrast
saveSettings()
}
function setLauncherLogoSizeOffset(offset) {
launcherLogoSizeOffset = offset
saveSettings() saveSettings()
} }
@@ -979,6 +1062,11 @@ Singleton {
saveSettings() saveSettings()
} }
function setDankBarFontScale(scale) {
dankBarFontScale = scale
saveSettings()
}
function setGtkThemingEnabled(enabled) { function setGtkThemingEnabled(enabled) {
gtkThemingEnabled = enabled gtkThemingEnabled = enabled
saveSettings() saveSettings()
@@ -1083,6 +1171,11 @@ Singleton {
saveSettings() saveSettings()
} }
function setOsdAlwaysShowValue(enabled) {
osdAlwaysShowValue = enabled
saveSettings()
}
function sendTestNotifications() { function sendTestNotifications() {
sendTestNotification(0) sendTestNotification(0)
testNotifTimer1.start() testNotifTimer1.start()
@@ -1091,9 +1184,9 @@ Singleton {
function sendTestNotification(index) { function sendTestNotification(index) {
const notifications = [ const notifications = [
["Notification Position Test", "DMS test notification 1 of 3 ~ Hi there!", "dialog-information"], ["Notification Position Test", "DMS test notification 1 of 3 ~ Hi there!", "preferences-system"],
["Second Test", "DMS Notification 2 of 3 ~ Check it out!", "emblem-default"], ["Second Test", "DMS Notification 2 of 3 ~ Check it out!", "applications-graphics"],
["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "emblem-favorite"] ["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "face-smile"]
] ]
if (index < 0 || index >= notifications.length) { if (index < 0 || index >= notifications.length) {
@@ -1158,6 +1251,11 @@ Singleton {
saveSettings() saveSettings()
} }
function setDankBarBorderEnabled(enabled) {
dankBarBorderEnabled = enabled
saveSettings()
}
function setDankBarPosition(position) { function setDankBarPosition(position) {
dankBarPosition = position dankBarPosition = position
if (position === SettingsData.Position.Bottom && dockPosition === SettingsData.Position.Bottom && showDock) { if (position === SettingsData.Position.Bottom && dockPosition === SettingsData.Position.Bottom && showDock) {
@@ -1283,13 +1381,13 @@ Singleton {
pluginSettings[pluginId] = {} pluginSettings[pluginId] = {}
} }
pluginSettings[pluginId][key] = value pluginSettings[pluginId][key] = value
saveSettings() savePluginSettings()
} }
function removePluginSettings(pluginId) { function removePluginSettings(pluginId) {
if (pluginSettings[pluginId]) { if (pluginSettings[pluginId]) {
delete pluginSettings[pluginId] delete pluginSettings[pluginId]
saveSettings() savePluginSettings()
} }
} }
@@ -1373,6 +1471,26 @@ Singleton {
} }
} }
FileView {
id: pluginSettingsFile
path: isGreeterMode ? "" : pluginSettingsPath
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
onLoaded: {
if (!isGreeterMode) {
parsePluginSettings(pluginSettingsFile.text())
}
}
onLoadFailed: error => {
if (!isGreeterMode) {
pluginSettings = {}
}
}
}
Process { Process {
id: systemDefaultDetectionProcess id: systemDefaultDetectionProcess
@@ -1437,7 +1555,7 @@ Singleton {
id: defaultSettingsCheckProcess id: defaultSettingsCheckProcess
command: ["sh", "-c", "CONFIG_DIR=\"" + _configDir command: ["sh", "-c", "CONFIG_DIR=\"" + _configDir
+ "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-settings.json\" ] && [ ! -f \"$CONFIG_DIR/settings.json\" ]; then cp \"$CONFIG_DIR/default-settings.json\" \"$CONFIG_DIR/settings.json\" && echo 'copied'; else echo 'not_found'; fi"] + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-settings.json\" ] && [ ! -f \"$CONFIG_DIR/settings.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-settings.json\" \"$CONFIG_DIR/settings.json\" && echo 'copied'; else echo 'not_found'; fi"]
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {

View File

@@ -74,7 +74,6 @@ Singleton {
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
property var workerRunning: false property var workerRunning: false
property var matugenColors: ({}) property var matugenColors: ({})
property int colorUpdateTrigger: 0
property var customThemeData: null property var customThemeData: null
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell" readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell"
@@ -84,7 +83,6 @@ Singleton {
matugenCheck.running = true matugenCheck.running = true
if (typeof SessionData !== "undefined") { if (typeof SessionData !== "undefined") {
SessionData.isLightModeChanged.connect(root.onLightModeChanged) SessionData.isLightModeChanged.connect(root.onLightModeChanged)
isLightMode = SessionData.isLightMode
} }
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) { if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
@@ -100,7 +98,6 @@ Singleton {
} }
function getMatugenColor(path, fallback) { function getMatugenColor(path, fallback) {
colorUpdateTrigger
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode] let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode]
for (const part of path.split(".")) { for (const part of path.split(".")) {
@@ -473,6 +470,19 @@ Singleton {
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5 return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
} }
function barIconSize(barThickness, offset) {
const defaultOffset = offset !== undefined ? offset : -6
return Math.round((barThickness / 48) * (iconSize + defaultOffset))
}
function barTextSize(barThickness) {
const scale = barThickness / 48
const dankBarScale = (typeof SettingsData !== "undefined" ? SettingsData.dankBarFontScale : 1.0)
if (scale <= 0.75) return fontSizeSmall * 0.9 * dankBarScale
if (scale >= 1.25) return fontSizeMedium * dankBarScale
return fontSizeSmall * dankBarScale
}
function getBatteryIcon(level, isCharging, batteryAvailable) { function getBatteryIcon(level, isCharging, batteryAvailable) {
if (!batteryAvailable) if (!batteryAvailable)
return _getBatteryPowerProfileIcon() return _getBatteryPowerProfileIcon()
@@ -565,10 +575,6 @@ Singleton {
function onLightModeChanged() { function onLightModeChanged() {
if (matugenColors && Object.keys(matugenColors).length > 0) {
colorUpdateTrigger++
}
if (currentTheme === "custom" && customThemeFileView.path) { if (currentTheme === "custom" && customThemeFileView.path) {
customThemeFileView.reload() customThemeFileView.reload()
} }
@@ -602,10 +608,10 @@ Singleton {
console.log("calling matugen worker") console.log("calling matugen worker")
systemThemeGenerator.command = [ systemThemeGenerator.command = [
"sh", "-c", "sh", "-c",
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' --run` `sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' --run`
] ]
} else { } else {
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, "--run"] systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, "--run"]
} }
systemThemeGenerator.running = true systemThemeGenerator.running = true
} }
@@ -678,6 +684,59 @@ Singleton {
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); } function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
function snap(value, dpr) {
return Math.round(value * dpr) / dpr
}
function invertHex(hex) {
hex = hex.replace('#', '');
if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
return hex;
}
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
const invR = (255 - r).toString(16).padStart(2, '0');
const invG = (255 - g).toString(16).padStart(2, '0');
const invB = (255 - b).toString(16).padStart(2, '0');
return `#${invR}${invG}${invB}`;
}
property string baseLogoColor: {
if (typeof SettingsData === "undefined") return ""
const colorOverride = SettingsData.launcherLogoColorOverride
if (!colorOverride || colorOverride === "") return ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
return colorOverride
}
property string effectiveLogoColor: {
if (typeof SettingsData === "undefined") return ""
const colorOverride = SettingsData.launcherLogoColorOverride
if (!colorOverride || colorOverride === "") return ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
if (!SettingsData.launcherLogoColorInvertOnMode) {
return colorOverride
}
if (isLightMode) {
return invertHex(colorOverride)
}
return colorOverride
}
Process { Process {
id: matugenCheck id: matugenCheck
command: ["which", "matugen"] command: ["which", "matugen"]
@@ -839,7 +898,6 @@ Singleton {
const colorsText = dynamicColorsFileView.text() const colorsText = dynamicColorsFileView.text()
if (colorsText) { if (colorsText) {
root.matugenColors = JSON.parse(colorsText) root.matugenColors = JSON.parse(colorsText)
root.colorUpdateTrigger++
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.clearWallpaperError() ToastService.clearWallpaperError()
} }
@@ -877,7 +935,7 @@ Singleton {
function toggle(): string { function toggle(): string {
root.toggleLightMode() root.toggleLightMode()
return root.isLightMode ? "light" : "dark" return root.isLightMode ? "dark" : "light"
} }
function light(): string { function light(): string {

View File

@@ -5,7 +5,7 @@ import Quickshell.Services.Greetd
import qs.Common import qs.Common
import qs.Modules.Greetd import qs.Modules.Greetd
ShellRoot { Item {
id: root id: root
WlSessionLock { WlSessionLock {

View File

@@ -1,6 +1,3 @@
//@ pragma Env QSG_RENDER_LOOP=threaded
//@ pragma UseQApplication
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -29,25 +26,54 @@ import qs.Modules.Plugins
import qs.Services import qs.Services
ShellRoot { Item {
id: root id: root
Component.onCompleted: { Instantiator {
PortalService.init() id: daemonPluginInstantiator
// Initialize DisplayService night mode functionality asynchronous: true
DisplayService.nightModeEnabled model: Object.keys(PluginService.pluginDaemonComponents)
// Initialize WallpaperCyclingService
WallpaperCyclingService.cyclingActive delegate: Loader {
// Initialize PluginService by accessing its properties id: daemonLoader
PluginService.pluginDirectory property string pluginId: modelData
sourceComponent: PluginService.pluginDaemonComponents[pluginId]
onLoaded: {
if (item) {
item.pluginService = PluginService
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
item.pluginId = pluginId
console.log("Daemon plugin loaded:", pluginId)
}
}
}
} }
WallpaperBackground {} WallpaperBackground {}
Lock { LazyLoader {
id: lock id: lockLoader
active: false
anchors.fill: parent Lock {
id: lock
anchors.fill: parent
Component.onCompleted: {
IdleService.lockComponent = lock
}
}
}
Timer {
id: lockInitTimer
interval: 100
running: true
repeat: false
onTriggered: lockLoader.active = true
} }
Loader { Loader {
@@ -55,17 +81,22 @@ ShellRoot {
asynchronous: false asynchronous: false
property var currentPosition: SettingsData.dankBarPosition property var currentPosition: SettingsData.dankBarPosition
property bool initialized: false
sourceComponent: DankBar { sourceComponent: DankBar {
onColorPickerRequested: colorPickerModal.show() onColorPickerRequested: colorPickerModal.show()
} }
Component.onCompleted: {
initialized = true
}
onCurrentPositionChanged: { onCurrentPositionChanged: {
if (!initialized) return
const component = sourceComponent const component = sourceComponent
sourceComponent = null sourceComponent = null
Qt.callLater(() => { sourceComponent = component
sourceComponent = component
})
} }
} }
@@ -75,6 +106,7 @@ ShellRoot {
asynchronous: false asynchronous: false
property var currentPosition: SettingsData.dockPosition property var currentPosition: SettingsData.dockPosition
property bool initialized: false
sourceComponent: Dock { sourceComponent: Dock {
contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null
@@ -86,13 +118,17 @@ ShellRoot {
} }
} }
Component.onCompleted: {
initialized = true
}
onCurrentPositionChanged: { onCurrentPositionChanged: {
if (!initialized) return
console.log("DEBUG: Dock position changed to:", currentPosition, "- recreating dock") console.log("DEBUG: Dock position changed to:", currentPosition, "- recreating dock")
const comp = sourceComponent const comp = sourceComponent
sourceComponent = null sourceComponent = null
Qt.callLater(() => { sourceComponent = comp
sourceComponent = comp
})
} }
} }
@@ -105,6 +141,10 @@ ShellRoot {
sourceComponent: Component { sourceComponent: Component {
DankDashPopout { DankDashPopout {
id: dankDashPopout id: dankDashPopout
Component.onCompleted: {
PopoutService.dankDashPopout = dankDashPopout
}
} }
} }
} }
@@ -126,6 +166,10 @@ ShellRoot {
NotificationCenterPopout { NotificationCenterPopout {
id: notificationCenter id: notificationCenter
Component.onCompleted: {
PopoutService.notificationCenterPopout = notificationCenter
}
} }
} }
@@ -143,38 +187,19 @@ ShellRoot {
active: false active: false
property var modalRef: colorPickerModal property var modalRef: colorPickerModal
property LazyLoader powerModalLoaderRef: powerMenuModalLoader
ControlCenterPopout { ControlCenterPopout {
id: controlCenterPopout id: controlCenterPopout
colorPickerModal: controlCenterLoader.modalRef colorPickerModal: controlCenterLoader.modalRef
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
onPowerActionRequested: (action, title, message) => {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, function () {
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}, function () {})
}
}
onLockRequested: { onLockRequested: {
lock.activate() lockLoader.item.activate()
}
Component.onCompleted: {
PopoutService.controlCenterPopout = controlCenterPopout
} }
} }
} }
@@ -186,6 +211,10 @@ ShellRoot {
WifiPasswordModal { WifiPasswordModal {
id: wifiPasswordModal id: wifiPasswordModal
Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModal
}
} }
} }
@@ -196,6 +225,10 @@ ShellRoot {
NetworkInfoModal { NetworkInfoModal {
id: networkInfoModal id: networkInfoModal
Component.onCompleted: {
PopoutService.networkInfoModal = networkInfoModal
}
} }
} }
@@ -206,6 +239,10 @@ ShellRoot {
BatteryPopout { BatteryPopout {
id: batteryPopout id: batteryPopout
Component.onCompleted: {
PopoutService.batteryPopout = batteryPopout
}
} }
} }
@@ -216,6 +253,10 @@ ShellRoot {
VpnPopout { VpnPopout {
id: vpnPopout id: vpnPopout
Component.onCompleted: {
PopoutService.vpnPopout = vpnPopout
}
} }
} }
@@ -272,11 +313,19 @@ ShellRoot {
ProcessListPopout { ProcessListPopout {
id: processListPopout id: processListPopout
Component.onCompleted: {
PopoutService.processListPopout = processListPopout
}
} }
} }
SettingsModal { SettingsModal {
id: settingsModal id: settingsModal
Component.onCompleted: {
PopoutService.settingsModal = settingsModal
}
} }
LazyLoader { LazyLoader {
@@ -286,22 +335,43 @@ ShellRoot {
AppDrawerPopout { AppDrawerPopout {
id: appDrawerPopout id: appDrawerPopout
Component.onCompleted: {
PopoutService.appDrawerPopout = appDrawerPopout
}
} }
} }
SpotlightModal { SpotlightModal {
id: spotlightModal id: spotlightModal
Component.onCompleted: {
PopoutService.spotlightModal = spotlightModal
}
} }
ClipboardHistoryModal { ClipboardHistoryModal {
id: clipboardHistoryModalPopup id: clipboardHistoryModalPopup
Component.onCompleted: {
PopoutService.clipboardHistoryModal = clipboardHistoryModalPopup
}
} }
NotificationModal { NotificationModal {
id: notificationModal id: notificationModal
Component.onCompleted: {
PopoutService.notificationModal = notificationModal
}
} }
ColorPickerModal {
DankColorPickerModal {
id: colorPickerModal id: colorPickerModal
Component.onCompleted: {
PopoutService.colorPickerModal = colorPickerModal
}
} }
LazyLoader { LazyLoader {
@@ -311,6 +381,10 @@ ShellRoot {
ProcessListModal { ProcessListModal {
id: processListModal id: processListModal
Component.onCompleted: {
PopoutService.processListModal = processListModal
}
} }
} }
@@ -321,6 +395,10 @@ ShellRoot {
SystemUpdatePopout { SystemUpdatePopout {
id: systemUpdatePopout id: systemUpdatePopout
Component.onCompleted: {
PopoutService.systemUpdatePopout = systemUpdatePopout
}
} }
} }
@@ -331,7 +409,7 @@ ShellRoot {
delegate: DankSlideout { delegate: DankSlideout {
id: notepadSlideout id: notepadSlideout
modelData: item modelData: item
title: qsTr("Notepad") title: I18n.tr("Notepad")
slideoutWidth: 480 slideoutWidth: 480
expandable: true expandable: true
expandedWidthValue: 960 expandedWidthValue: 960
@@ -388,219 +466,19 @@ ShellRoot {
}, function () {}) }, function () {})
} }
} }
Component.onCompleted: {
PopoutService.powerMenuModal = powerMenuModal
}
} }
} }
IpcHandler { DMSShellIPC {
function open() { powerMenuModalLoader: powerMenuModalLoader
powerMenuModalLoader.active = true processListModalLoader: processListModalLoader
if (powerMenuModalLoader.item) controlCenterLoader: controlCenterLoader
powerMenuModalLoader.item.open() dankDashPopoutLoader: dankDashPopoutLoader
notepadSlideoutVariants: notepadSlideoutVariants
return "POWERMENU_OPEN_SUCCESS"
}
function close() {
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.close()
return "POWERMENU_CLOSE_SUCCESS"
}
function toggle() {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.toggle()
return "POWERMENU_TOGGLE_SUCCESS"
}
target: "powermenu"
}
IpcHandler {
function open(): string {
processListModalLoader.active = true
if (processListModalLoader.item)
processListModalLoader.item.show()
return "PROCESSLIST_OPEN_SUCCESS"
}
function close(): string {
if (processListModalLoader.item)
processListModalLoader.item.hide()
return "PROCESSLIST_CLOSE_SUCCESS"
}
function toggle(): string {
processListModalLoader.active = true
if (processListModalLoader.item)
processListModalLoader.item.toggle()
return "PROCESSLIST_TOGGLE_SUCCESS"
}
target: "processlist"
}
IpcHandler {
function open(): string {
controlCenterLoader.active = true
if (controlCenterLoader.item) {
controlCenterLoader.item.open()
return "CONTROL_CENTER_OPEN_SUCCESS"
}
return "CONTROL_CENTER_OPEN_FAILED"
}
function close(): string {
if (controlCenterLoader.item) {
controlCenterLoader.item.close()
return "CONTROL_CENTER_CLOSE_SUCCESS"
}
return "CONTROL_CENTER_CLOSE_FAILED"
}
function toggle(): string {
controlCenterLoader.active = true
if (controlCenterLoader.item) {
controlCenterLoader.item.toggle()
return "CONTROL_CENTER_TOGGLE_SUCCESS"
}
return "CONTROL_CENTER_TOGGLE_FAILED"
}
target: "control-center"
}
IpcHandler {
function open(tab: string): string {
dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
dankDashPopoutLoader.item.currentTabIndex = 0
break
}
dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
dankDashPopoutLoader.item.dashVisible = true
return "DASH_OPEN_SUCCESS"
}
return "DASH_OPEN_FAILED"
}
function close(): string {
if (dankDashPopoutLoader.item) {
dankDashPopoutLoader.item.dashVisible = false
return "DASH_CLOSE_SUCCESS"
}
return "DASH_CLOSE_FAILED"
}
function toggle(tab: string): string {
dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) {
if (dankDashPopoutLoader.item.dashVisible) {
dankDashPopoutLoader.item.dashVisible = false
} else {
switch (tab.toLowerCase()) {
case "media":
dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
dankDashPopoutLoader.item.currentTabIndex = 0
break
}
dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
dankDashPopoutLoader.item.dashVisible = true
}
return "DASH_TOGGLE_SUCCESS"
}
return "DASH_TOGGLE_FAILED"
}
target: "dash"
}
IpcHandler {
function getFocusedScreenName() {
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
return Hyprland.focusedWorkspace.monitor.name
}
if (CompositorService.isNiri && NiriService.currentOutput) {
return NiriService.currentOutput
}
return ""
}
function getActiveNotepadInstance() {
if (notepadSlideoutVariants.instances.length === 0) {
return null
}
if (notepadSlideoutVariants.instances.length === 1) {
return notepadSlideoutVariants.instances[0]
}
var focusedScreen = getFocusedScreenName()
if (focusedScreen && notepadSlideoutVariants.instances.length > 0) {
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
var slideout = notepadSlideoutVariants.instances[i]
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
return slideout
}
}
}
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
var slideout = notepadSlideoutVariants.instances[i]
if (slideout.isVisible) {
return slideout
}
}
return notepadSlideoutVariants.instances[0]
}
function open(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.show()
return "NOTEPAD_OPEN_SUCCESS"
}
return "NOTEPAD_OPEN_FAILED"
}
function close(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.hide()
return "NOTEPAD_CLOSE_SUCCESS"
}
return "NOTEPAD_CLOSE_FAILED"
}
function toggle(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.toggle()
return "NOTEPAD_TOGGLE_SUCCESS"
}
return "NOTEPAD_TOGGLE_FAILED"
}
target: "notepad"
} }
Variants { Variants {

265
DMSShellIPC.qml Normal file
View File

@@ -0,0 +1,265 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import qs.Common
import qs.Services
Item {
id: root
required property var powerMenuModalLoader
required property var processListModalLoader
required property var controlCenterLoader
required property var dankDashPopoutLoader
required property var notepadSlideoutVariants
IpcHandler {
function open() {
root.powerMenuModalLoader.active = true
if (root.powerMenuModalLoader.item)
root.powerMenuModalLoader.item.openCentered()
return "POWERMENU_OPEN_SUCCESS"
}
function close() {
if (root.powerMenuModalLoader.item)
root.powerMenuModalLoader.item.close()
return "POWERMENU_CLOSE_SUCCESS"
}
function toggle() {
root.powerMenuModalLoader.active = true
if (root.powerMenuModalLoader.item) {
if (root.powerMenuModalLoader.item.shouldBeVisible) {
root.powerMenuModalLoader.item.close()
} else {
root.powerMenuModalLoader.item.openCentered()
}
}
return "POWERMENU_TOGGLE_SUCCESS"
}
target: "powermenu"
}
IpcHandler {
function open(): string {
root.processListModalLoader.active = true
if (root.processListModalLoader.item)
root.processListModalLoader.item.show()
return "PROCESSLIST_OPEN_SUCCESS"
}
function close(): string {
if (root.processListModalLoader.item)
root.processListModalLoader.item.hide()
return "PROCESSLIST_CLOSE_SUCCESS"
}
function toggle(): string {
root.processListModalLoader.active = true
if (root.processListModalLoader.item)
root.processListModalLoader.item.toggle()
return "PROCESSLIST_TOGGLE_SUCCESS"
}
target: "processlist"
}
IpcHandler {
function open(): string {
root.controlCenterLoader.active = true
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.open()
return "CONTROL_CENTER_OPEN_SUCCESS"
}
return "CONTROL_CENTER_OPEN_FAILED"
}
function close(): string {
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.close()
return "CONTROL_CENTER_CLOSE_SUCCESS"
}
return "CONTROL_CENTER_CLOSE_FAILED"
}
function toggle(): string {
root.controlCenterLoader.active = true
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.toggle()
return "CONTROL_CENTER_TOGGLE_SUCCESS"
}
return "CONTROL_CENTER_TOGGLE_FAILED"
}
target: "control-center"
}
IpcHandler {
function open(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.dankDashPopoutLoader.item.dashVisible = true
return "DASH_OPEN_SUCCESS"
}
return "DASH_OPEN_FAILED"
}
function close(): string {
if (root.dankDashPopoutLoader.item) {
root.dankDashPopoutLoader.item.dashVisible = false
return "DASH_CLOSE_SUCCESS"
}
return "DASH_CLOSE_FAILED"
}
function toggle(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
if (root.dankDashPopoutLoader.item.dashVisible) {
root.dankDashPopoutLoader.item.dashVisible = false
} else {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.dankDashPopoutLoader.item.dashVisible = true
}
return "DASH_TOGGLE_SUCCESS"
}
return "DASH_TOGGLE_FAILED"
}
target: "dash"
}
IpcHandler {
function getFocusedScreenName() {
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
return Hyprland.focusedWorkspace.monitor.name
}
if (CompositorService.isNiri && NiriService.currentOutput) {
return NiriService.currentOutput
}
return ""
}
function getActiveNotepadInstance() {
if (root.notepadSlideoutVariants.instances.length === 0) {
return null
}
if (root.notepadSlideoutVariants.instances.length === 1) {
return root.notepadSlideoutVariants.instances[0]
}
var focusedScreen = getFocusedScreenName()
if (focusedScreen && root.notepadSlideoutVariants.instances.length > 0) {
for (var i = 0; i < root.notepadSlideoutVariants.instances.length; i++) {
var slideout = root.notepadSlideoutVariants.instances[i]
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
return slideout
}
}
}
for (var i = 0; i < root.notepadSlideoutVariants.instances.length; i++) {
var slideout = root.notepadSlideoutVariants.instances[i]
if (slideout.isVisible) {
return slideout
}
}
return root.notepadSlideoutVariants.instances[0]
}
function open(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.show()
return "NOTEPAD_OPEN_SUCCESS"
}
return "NOTEPAD_OPEN_FAILED"
}
function close(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.hide()
return "NOTEPAD_CLOSE_SUCCESS"
}
return "NOTEPAD_CLOSE_FAILED"
}
function toggle(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.toggle()
return "NOTEPAD_TOGGLE_SUCCESS"
}
return "NOTEPAD_TOGGLE_FAILED"
}
target: "notepad"
}
IpcHandler {
function toggle(): string {
SessionService.toggleIdleInhibit()
return SessionService.idleInhibited ? "Idle inhibit enabled" : "Idle inhibit disabled"
}
function enable(): string {
SessionService.enableIdleInhibit()
return "Idle inhibit enabled"
}
function disable(): string {
SessionService.disableIdleInhibit()
return "Idle inhibit disabled"
}
function status(): string {
return SessionService.idleInhibited ? "Idle inhibit is enabled" : "Idle inhibit is disabled"
}
function reason(newReason: string): string {
if (!newReason) {
return `Current reason: ${SessionService.inhibitReason}`
}
SessionService.setInhibitReason(newReason)
return `Inhibit reason set to: ${newReason}`
}
target: "inhibit"
}
}

View File

@@ -30,7 +30,7 @@ Item {
showKeyboardHints: modal.showKeyboardHints showKeyboardHints: modal.showKeyboardHints
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
onClearAllClicked: { onClearAllClicked: {
clearConfirmDialog.show("Clear All History?", "This will permanently delete all clipboard history.", function () { clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () {
modal.clearAll() modal.clearAll()
modal.hide() modal.hide()
}, function () {}) }, function () {})
@@ -46,7 +46,6 @@ Item {
leftIconName: "search" leftIconName: "search"
showClearButton: true showClearButton: true
focus: true focus: true
ignoreLeftRightKeys: true
keyForwardTargets: [modal.modalFocusScope] keyForwardTargets: [modal.modalFocusScope]
onTextChanged: { onTextChanged: {
modal.searchText = text modal.searchText = text
@@ -116,7 +115,7 @@ Item {
} }
StyledText { StyledText {
text: "No clipboard entries found" text: I18n.tr("No clipboard entries found")
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText

View File

@@ -80,11 +80,11 @@ Rectangle {
text: { text: {
switch (entryType) { switch (entryType) {
case "image": case "image":
return "Image • " + entryPreview return I18n.tr("Image") + " • " + entryPreview
case "long_text": case "long_text":
return "Long Text" return I18n.tr("Long Text")
default: default:
return "Text" return I18n.tr("Text")
} }
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall

View File

@@ -28,7 +28,7 @@ Item {
} }
StyledText { StyledText {
text: `Clipboard History (${totalCount})` text: I18n.tr("Clipboard History") + ` (${totalCount})`
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -91,7 +91,7 @@ DankModal {
function copyEntry(entry) { function copyEntry(entry) {
const entryId = entry.split('\t')[0] const entryId = entry.split('\t')[0]
Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`]) Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`])
ToastService.showInfo("Copied to clipboard") ToastService.showInfo(I18n.tr("Copied to clipboard"))
hide() hide()
} }
@@ -153,7 +153,7 @@ DankModal {
ConfirmModal { ConfirmModal {
id: clearConfirmDialog id: clearConfirmDialog
confirmButtonText: "Clear All" confirmButtonText: I18n.tr("Clear All")
confirmButtonColor: Theme.primary confirmButtonColor: Theme.primary
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {

View File

@@ -6,6 +6,8 @@ import qs.Modals.Clipboard
Rectangle { Rectangle {
id: keyboardHints id: keyboardHints
readonly property string hintsText: I18n.tr("Shift+Del: Clear All • Esc: Close")
height: ClipboardConstants.keyboardHintsHeight height: ClipboardConstants.keyboardHintsHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
@@ -26,7 +28,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "Shift+Del: Clear All • Esc: Close" text: keyboardHints.hintsText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -1,37 +0,0 @@
import QtQuick
import Qt.labs.platform
import Quickshell
import qs.Common
import qs.Services
Item {
id: colorPickerModal
signal colorSelected(color selectedColor)
function show() {
colorDialog.open()
}
function hide() {
colorDialog.close()
}
function copyColorToClipboard(colorValue) {
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
ToastService.showInfo(`Color ${colorValue} copied to clipboard`)
console.log("Copied color to clipboard:", colorValue)
}
ColorDialog {
id: colorDialog
title: "Color Picker - Select and copy color"
color: Theme.primary
onAccepted: {
const colorString = color.toString()
copyColorToClipboard(colorString)
colorSelected(color)
}
}
}

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
@@ -15,6 +14,15 @@ PanelWindow {
property real height: 300 property real height: 300
readonly property real screenWidth: screen ? screen.width : 1920 readonly property real screenWidth: screen ? screen.width : 1920
readonly property real screenHeight: screen ? screen.height : 1080 readonly property real screenHeight: screen ? screen.height : 1080
readonly property real dpr: (screen && screen.devicePixelRatio) || 1
function snap(v) {
return Math.round(v * dpr) / dpr
}
function px(v) {
return Math.round(v)
}
property bool showBackground: true property bool showBackground: true
property real backgroundOpacity: 0.5 property real backgroundOpacity: 0.5
property string positioning: "center" property string positioning: "center"
@@ -134,22 +142,26 @@ PanelWindow {
Rectangle { Rectangle {
id: contentContainer id: contentContainer
width: root.width width: px(root.width)
height: root.height height: px(root.height)
anchors.centerIn: positioning === "center" ? parent : undefined anchors.centerIn: undefined
x: { x: {
if (positioning === "top-right") { if (positioning === "center") {
return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL) return snap((root.screenWidth - width) / 2)
} else if (positioning === "top-right") {
return px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL))
} else if (positioning === "custom") { } else if (positioning === "custom") {
return root.customPosition.x return snap(root.customPosition.x)
} }
return 0 return 0
} }
y: { y: {
if (positioning === "top-right") { if (positioning === "center") {
return Theme.barHeight + Theme.spacingXS return snap((root.screenHeight - height) / 2)
} else if (positioning === "top-right") {
return px(Theme.barHeight + Theme.spacingXS)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return root.customPosition.y return snap(root.customPosition.y)
} }
return 0 return 0
} }
@@ -157,39 +169,47 @@ PanelWindow {
radius: root.cornerRadius radius: root.cornerRadius
border.color: root.borderColor border.color: root.borderColor
border.width: root.borderWidth border.width: root.borderWidth
layer.enabled: root.enableShadow clip: false
opacity: root.shouldBeVisible ? 1 : 0 opacity: root.shouldBeVisible ? 1 : 0
transform: root.animationType === "slide" ? slideTransform : null transform: root.animationType === "slide" ? slideTransform : null
Translate { Translate {
id: slideTransform id: slideTransform
x: root.shouldBeVisible ? 0 : 15 readonly property real rawX: root.shouldBeVisible ? 0 : 15
y: root.shouldBeVisible ? 0 : -30 readonly property real rawY: root.shouldBeVisible ? 0 : -30
}
Loader { x: snap(rawX)
id: contentLoader y: snap(rawY)
anchors.fill: parent
active: root.keepContentLoaded || root.shouldBeVisible || root.visible
asynchronous: false
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: root.animationDuration duration: animationDuration
easing.type: root.animationEasing easing.type: animationEasing
} }
} }
layer.effect: MultiEffect { FocusScope {
shadowEnabled: true anchors.fill: parent
shadowHorizontalOffset: 0 focus: root.shouldBeVisible
shadowVerticalOffset: 8 clip: false
shadowBlur: 1
shadowColor: Theme.shadowStrong Loader {
shadowOpacity: 0.3 id: contentLoader
anchors.fill: parent
active: root.keepContentLoaded || root.shouldBeVisible || root.visible
asynchronous: false
focus: true
clip: false
onLoaded: {
if (item) {
Qt.callLater(() => item.forceActiveFocus())
}
}
}
} }
} }

View File

@@ -0,0 +1,580 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
PanelWindow {
id: root
property string pickerTitle: "Choose Color"
property color selectedColor: Theme.primary
property bool shouldBeVisible: false
property var onColorSelectedCallback: null
signal colorSelected(color selectedColor)
property color currentColor: Theme.primary
property real hue: 0
property real saturation: 1
property real value: 1
property real alpha: 1
property real gradientX: 0
property real gradientY: 0
function open() {
currentColor = selectedColor
updateFromColor(currentColor)
shouldBeVisible = true
Qt.callLater(() => colorContent.forceActiveFocus())
}
function close() {
shouldBeVisible = false
onColorSelectedCallback = null
}
function show() {
open()
}
function hide() {
close()
}
onColorSelected: (color) => {
if (onColorSelectedCallback) {
onColorSelectedCallback(color)
}
}
function copyColorToClipboard(colorValue) {
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
ToastService.showInfo(`Color ${colorValue} copied`)
SessionData.addRecentColor(currentColor)
}
function updateFromColor(color) {
hue = color.hsvHue
saturation = color.hsvSaturation
value = color.hsvValue
alpha = color.a
gradientX = saturation
gradientY = 1 - value
}
function updateColor() {
currentColor = Qt.hsva(hue, saturation, value, alpha)
}
function updateColorFromGradient(x, y) {
saturation = Math.max(0, Math.min(1, x))
value = Math.max(0, Math.min(1, 1 - y))
updateColor()
}
function pickColorFromScreen() {
close()
hyprpickerProcess.running = true
}
Process {
id: hyprpickerProcess
running: false
command: ["hyprpicker", "--format=hex"]
stdout: SplitParser {
onRead: data => {
const colorStr = data.trim()
if (colorStr.length >= 7 && colorStr.startsWith('#')) {
root.currentColor = colorStr
root.updateFromColor(root.currentColor)
hexInput.text = root.currentColor.toString()
copyColorToClipboard(colorStr)
root.open()
}
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
console.warn("hyprpicker exited with code:", exitCode)
}
root.open()
}
}
readonly property var standardColors: [
"#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4",
"#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722",
"#d32f2f", "#c2185b", "#7b1fa2", "#512da8", "#303f9f", "#1976d2", "#0288d1", "#0097a7",
"#00796b", "#388e3c", "#689f38", "#afb42b", "#fbc02d", "#ffa000", "#f57c00", "#e64a19",
"#c62828", "#ad1457", "#6a1b9a", "#4527a0", "#283593", "#1565c0", "#0277bd", "#00838f",
"#00695c", "#2e7d32", "#558b2f", "#9e9d24", "#f9a825", "#ff8f00", "#ef6c00", "#d84315",
"#ffffff", "#9e9e9e", "#212121"
]
visible: shouldBeVisible
WlrLayershell.namespace: "quickshell:color-picker"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
MouseArea {
anchors.fill: parent
onClicked: root.close()
Rectangle {
color: "#80000000"
anchors.fill: parent
}
}
Rectangle {
anchors.centerIn: parent
width: 680
height: 680
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outlineMedium
border.width: 1
MouseArea {
anchors.fill: parent
onClicked: {} // Prevent clicks from propagating to background
}
FocusScope {
id: colorContent
anchors.fill: parent
focus: root.shouldBeVisible
Keys.onEscapePressed: event => {
root.close()
event.accepted = true
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingS
Column {
width: parent.width - 90
spacing: Theme.spacingXS
StyledText {
text: root.pickerTitle
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: I18n.tr("Select a color from the palette or use custom sliders")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
}
}
DankActionButton {
iconName: "colorize"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
pickColorFromScreen()
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
root.close()
}
}
}
Row {
width: parent.width
spacing: Theme.spacingM
Rectangle {
id: gradientPicker
width: parent.width - 70
height: 280
radius: Theme.cornerRadius
border.color: Theme.outlineStrong
border.width: 1
clip: true
Rectangle {
anchors.fill: parent
color: Qt.hsva(root.hue, 1, 1, 1)
Rectangle {
anchors.fill: parent
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "#ffffff" }
GradientStop { position: 1.0; color: "transparent" }
}
}
Rectangle {
anchors.fill: parent
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: "#000000" }
}
}
}
Rectangle {
id: pickerCircle
width: 16
height: 16
radius: 8
border.color: "white"
border.width: 2
color: "transparent"
x: root.gradientX * parent.width - width / 2
y: root.gradientY * parent.height - height / 2
Rectangle {
anchors.centerIn: parent
width: parent.width - 4
height: parent.height - 4
radius: width / 2
border.color: "black"
border.width: 1
color: "transparent"
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.CrossCursor
onPressed: mouse => {
const x = Math.max(0, Math.min(1, mouse.x / width))
const y = Math.max(0, Math.min(1, mouse.y / height))
root.gradientX = x
root.gradientY = y
root.updateColorFromGradient(x, y)
}
onPositionChanged: mouse => {
if (pressed) {
const x = Math.max(0, Math.min(1, mouse.x / width))
const y = Math.max(0, Math.min(1, mouse.y / height))
root.gradientX = x
root.gradientY = y
root.updateColorFromGradient(x, y)
}
}
}
}
Rectangle {
id: hueSlider
width: 50
height: 280
radius: Theme.cornerRadius
border.color: Theme.outlineStrong
border.width: 1
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.00; color: "#ff0000" }
GradientStop { position: 0.17; color: "#ffff00" }
GradientStop { position: 0.33; color: "#00ff00" }
GradientStop { position: 0.50; color: "#00ffff" }
GradientStop { position: 0.67; color: "#0000ff" }
GradientStop { position: 0.83; color: "#ff00ff" }
GradientStop { position: 1.00; color: "#ff0000" }
}
Rectangle {
id: hueIndicator
width: parent.width
height: 4
color: "white"
border.color: "black"
border.width: 1
y: root.hue * parent.height - height / 2
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeVerCursor
onPressed: mouse => {
const h = Math.max(0, Math.min(1, mouse.y / height))
root.hue = h
root.updateColor()
}
onPositionChanged: mouse => {
if (pressed) {
const h = Math.max(0, Math.min(1, mouse.y / height))
root.hue = h
root.updateColor()
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Material Colors")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
GridView {
width: parent.width
height: 140
cellWidth: 38
cellHeight: 38
clip: true
interactive: false
model: root.standardColors
delegate: Rectangle {
width: 36
height: 36
color: modelData
radius: 4
border.color: Theme.outlineStrong
border.width: 1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: () => {
root.currentColor = modelData
root.updateFromColor(root.currentColor)
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
Column {
width: 210
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Recent Colors")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Row {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: 5
Rectangle {
width: 36
height: 36
radius: 4
border.color: Theme.outlineStrong
border.width: 1
color: {
if (index < SessionData.recentColors.length) {
return SessionData.recentColors[index]
}
return Theme.surfaceContainerHigh
}
opacity: index < SessionData.recentColors.length ? 1.0 : 0.3
MouseArea {
anchors.fill: parent
cursorShape: index < SessionData.recentColors.length ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: index < SessionData.recentColors.length
onClicked: () => {
if (index < SessionData.recentColors.length) {
root.currentColor = SessionData.recentColors[index]
root.updateFromColor(root.currentColor)
}
}
}
}
}
}
}
Column {
width: parent.width - 330
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Opacity")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
DankSlider {
width: parent.width
value: Math.round(root.alpha * 100)
minimum: 0
maximum: 100
showValue: false
onSliderValueChanged: (newValue) => {
root.alpha = newValue / 100
root.updateColor()
}
}
}
Rectangle {
width: 100
height: 50
radius: Theme.cornerRadius
color: root.currentColor
border.color: Theme.outlineStrong
border.width: 2
anchors.verticalCenter: parent.verticalCenter
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Hex:")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
id: hexInput
width: 120
height: 38
text: root.currentColor.toString()
font.pixelSize: Theme.fontSizeMedium
textColor: {
if (text.length === 0) return Theme.surfaceText
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
return hexPattern.test(text) ? Theme.surfaceText : Theme.error
}
placeholderText: "#000000"
backgroundColor: Theme.surfaceHover
borderWidth: 1
focusedBorderWidth: 2
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
onAccepted: () => {
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
if (!hexPattern.test(text)) return
const color = Qt.color(text)
if (color) {
root.currentColor = color
root.updateFromColor(color)
}
}
}
DankButton {
width: 80
buttonHeight: 36
text: I18n.tr("Apply")
backgroundColor: Theme.primary
textColor: Theme.background
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
if (!hexPattern.test(hexInput.text)) return
const color = Qt.color(hexInput.text)
if (color) {
root.currentColor = color
root.updateFromColor(color)
root.selectedColor = root.currentColor
colorSelected(root.currentColor)
SessionData.addRecentColor(root.currentColor)
root.close()
}
}
}
Item {
width: parent.width - 460
height: 1
}
DankButton {
width: 70
buttonHeight: 36
text: I18n.tr("Cancel")
backgroundColor: "transparent"
textColor: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
onClicked: root.close()
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
z: -1
}
}
DankButton {
width: 70
buttonHeight: 36
text: I18n.tr("Copy")
backgroundColor: Theme.primary
textColor: Theme.background
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const colorString = root.currentColor.toString()
copyColorToClipboard(colorString)
}
}
}
}
}
}
}

View File

@@ -35,6 +35,7 @@ DankModal {
property bool weAvailable: false property bool weAvailable: false
property string wePath: "" property string wePath: ""
property bool weMode: false property bool weMode: false
property var parentModal: null
signal fileSelected(string path) signal fileSelected(string path)
@@ -131,6 +132,8 @@ DankModal {
objectName: "fileBrowserModal" objectName: "fileBrowserModal"
allowStacking: true allowStacking: true
closeOnEscapeKey: false
shouldHaveFocus: shouldBeVisible
Component.onCompleted: { Component.onCompleted: {
currentPath = getLastPath() currentPath = getLastPath()
} }
@@ -165,10 +168,23 @@ DankModal {
visible: false visible: false
onBackgroundClicked: close() onBackgroundClicked: close()
onOpened: { onOpened: {
modalFocusScope.forceActiveFocus() if (parentModal) {
parentModal.shouldHaveFocus = false
parentModal.allowFocusOverride = true
}
Qt.callLater(() => {
if (contentLoader && contentLoader.item) {
contentLoader.item.forceActiveFocus()
}
})
} }
modalFocusScope.Keys.onPressed: function (event) { onDialogClosed: {
keyboardController.handleKey(event) if (parentModal) {
parentModal.allowFocusOverride = false
parentModal.shouldHaveFocus = Qt.binding(() => {
return parentModal.shouldBeVisible
})
}
} }
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
@@ -455,6 +471,16 @@ DankModal {
Item { Item {
anchors.fill: parent anchors.fill: parent
Keys.onPressed: event => {
keyboardController.handleKey(event)
}
onVisibleChanged: {
if (visible) {
forceActiveFocus()
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
@@ -595,7 +621,6 @@ DankModal {
required property bool fileIsDir required property bool fileIsDir
required property string filePath required property string filePath
required property string fileName required property string fileName
required property url fileURL
required property int index required property int index
width: weMode ? 245 : 140 width: weMode ? 245 : 140
@@ -755,7 +780,7 @@ DankModal {
width: parent.width - saveButton.width - Theme.spacingM width: parent.width - saveButton.width - Theme.spacingM
height: 40 height: 40
text: defaultFileName text: defaultFileName
placeholderText: "Enter filename..." placeholderText: I18n.tr("Enter filename...")
ignoreLeftRightKeys: false ignoreLeftRightKeys: false
focus: saveMode focus: saveMode
topPadding: Theme.spacingS topPadding: Theme.spacingS
@@ -788,7 +813,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "Save" text: I18n.tr("Save")
color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
} }
@@ -892,7 +917,7 @@ DankModal {
spacing: Theme.spacingM spacing: Theme.spacingM
StyledText { StyledText {
text: qsTr("File Already Exists") text: I18n.tr("File Already Exists")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -900,7 +925,7 @@ DankModal {
} }
StyledText { StyledText {
text: qsTr("A file with this name already exists. Do you want to overwrite it?") text: I18n.tr("A file with this name already exists. Do you want to overwrite it?")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
width: parent.width width: parent.width
@@ -922,7 +947,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("Cancel") text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -948,7 +973,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("Overwrite") text: I18n.tr("Overwrite")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -134,7 +134,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "File Information" text: I18n.tr("File Information")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -197,7 +197,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "F1/I: Toggle • F10: Help" text: I18n.tr("F1/I: Toggle • F10: Help")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
anchors.bottom: parent.bottom anchors.bottom: parent.bottom

View File

@@ -23,7 +23,7 @@ Rectangle {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select" text: I18n.tr("Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width
@@ -32,7 +32,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close" text: I18n.tr("Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width

View File

@@ -56,7 +56,7 @@ DankModal {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Network Information" text: I18n.tr("Network Information")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -126,7 +126,7 @@ DankModal {
id: closeText id: closeText
anchors.centerIn: parent anchors.centerIn: parent
text: "Close" text: I18n.tr("Close")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -9,31 +9,47 @@ DankModal {
property int selectedIndex: 0 property int selectedIndex: 0
property int optionCount: SessionService.hibernateSupported ? 5 : 4 property int optionCount: SessionService.hibernateSupported ? 5 : 4
property rect parentBounds: Qt.rect(0, 0, 0, 0)
property var parentScreen: null
signal powerActionRequested(string action, string title, string message) signal powerActionRequested(string action, string title, string message)
function openCentered() {
parentBounds = Qt.rect(0, 0, 0, 0)
parentScreen = null
backgroundOpacity = 0.5
open()
}
function openFromControlCenter(bounds, targetScreen) {
parentBounds = bounds
parentScreen = targetScreen
backgroundOpacity = 0
open()
}
function selectOption(action) { function selectOption(action) {
close(); close();
const actions = { const actions = {
"logout": { "logout": {
"title": "Log Out", "title": I18n.tr("Log Out"),
"message": "Are you sure you want to log out?" "message": I18n.tr("Are you sure you want to log out?")
}, },
"suspend": { "suspend": {
"title": "Suspend", "title": I18n.tr("Suspend"),
"message": "Are you sure you want to suspend the system?" "message": I18n.tr("Are you sure you want to suspend the system?")
}, },
"hibernate": { "hibernate": {
"title": "Hibernate", "title": I18n.tr("Hibernate"),
"message": "Are you sure you want to hibernate the system?" "message": I18n.tr("Are you sure you want to hibernate the system?")
}, },
"reboot": { "reboot": {
"title": "Reboot", "title": I18n.tr("Reboot"),
"message": "Are you sure you want to reboot the system?" "message": I18n.tr("Are you sure you want to reboot the system?")
}, },
"poweroff": { "poweroff": {
"title": "Power Off", "title": I18n.tr("Power Off"),
"message": "Are you sure you want to power off the system?" "message": I18n.tr("Are you sure you want to power off the system?")
} }
} }
const selected = actions[action] const selected = actions[action]
@@ -47,6 +63,16 @@ DankModal {
width: 320 width: 320
height: contentLoader.item ? contentLoader.item.implicitHeight : 300 height: contentLoader.item ? contentLoader.item.implicitHeight : 300
enableShadow: true enableShadow: true
screen: parentScreen
positioning: parentBounds.width > 0 ? "custom" : "center"
customPosition: {
if (parentBounds.width > 0) {
const centerX = parentBounds.x + (parentBounds.width - width) / 2
const centerY = parentBounds.y + (parentBounds.height - height) / 2
return Qt.point(centerX, centerY)
}
return Qt.point(0, 0)
}
onBackgroundClicked: () => { onBackgroundClicked: () => {
return close(); return close();
} }
@@ -118,7 +144,7 @@ DankModal {
width: parent.width width: parent.width
StyledText { StyledText {
text: "Power Options" text: I18n.tr("Power Options")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -175,7 +201,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Log Out" text: I18n.tr("Log Out")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -228,7 +254,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Suspend" text: I18n.tr("Suspend")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -282,7 +308,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Hibernate" text: I18n.tr("Hibernate")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -336,7 +362,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Reboot" text: I18n.tr("Reboot")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -390,7 +416,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Power Off" text: I18n.tr("Power Off")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -123,7 +123,7 @@ DankModal {
} }
StyledText { StyledText {
text: "System Monitor Unavailable" text: I18n.tr("System Monitor Unavailable")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.error color: Theme.error
@@ -131,7 +131,7 @@ DankModal {
} }
StyledText { StyledText {
text: "The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature." text: I18n.tr("The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature.")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -154,7 +154,7 @@ DankModal {
height: 40 height: 40
StyledText { StyledText {
text: "System Monitor" text: I18n.tr("System Monitor")
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.surfaceText color: Theme.surfaceText

View File

@@ -19,7 +19,7 @@ Item {
spacing: Theme.spacingXL spacing: Theme.spacingXL
StyledText { StyledText {
text: "Battery not detected - only AC power settings available" text: I18n.tr("Battery not detected - only AC power settings available")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: !BatteryService.batteryAvailable visible: !BatteryService.batteryAvailable
@@ -51,7 +51,7 @@ Item {
} }
StyledText { StyledText {
text: "Idle Settings" text: I18n.tr("Idle Settings")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -79,7 +79,7 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: "Automatically lock after" text: I18n.tr("Automatically lock after")
options: timeoutOptions options: timeoutOptions
Connections { Connections {
@@ -115,7 +115,7 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: "Turn off monitors after" text: I18n.tr("Turn off monitors after")
options: timeoutOptions options: timeoutOptions
Connections { Connections {
@@ -151,7 +151,7 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: "Suspend system after" text: I18n.tr("Suspend system after")
options: timeoutOptions options: timeoutOptions
Connections { Connections {
@@ -187,7 +187,7 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: "Hibernate system after" text: I18n.tr("Hibernate system after")
options: timeoutOptions options: timeoutOptions
visible: SessionService.hibernateSupported visible: SessionService.hibernateSupported
@@ -221,14 +221,23 @@ Item {
DankToggle { DankToggle {
width: parent.width width: parent.width
text: "Lock before suspend" text: I18n.tr("Enable loginctl lock integration")
description: "Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen."
checked: SessionData.loginctlLockIntegration
onToggled: checked => SessionData.setLoginctlLockIntegration(checked)
}
DankToggle {
width: parent.width
text: I18n.tr("Lock before suspend")
description: "Automatically lock the screen when the system prepares to suspend" description: "Automatically lock the screen when the system prepares to suspend"
checked: SessionData.lockBeforeSuspend checked: SessionData.lockBeforeSuspend
visible: SessionData.loginctlLockIntegration
onToggled: checked => SessionData.setLockBeforeSuspend(checked) onToggled: checked => SessionData.setLockBeforeSuspend(checked)
} }
StyledText { StyledText {
text: "Idle monitoring not supported - requires newer Quickshell version" text: I18n.tr("Idle monitoring not supported - requires newer Quickshell version")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.error color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -2,11 +2,12 @@ import QtQuick
import qs.Common import qs.Common
import qs.Modules.Settings import qs.Modules.Settings
Item { FocusScope {
id: root id: root
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
focus: true
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -68,6 +69,7 @@ Item {
asynchronous: true asynchronous: true
sourceComponent: DankBarTab { sourceComponent: DankBarTab {
parentModal: root.parentModal
} }
} }
@@ -162,6 +164,7 @@ Item {
asynchronous: true asynchronous: true
sourceComponent: PluginsTab { sourceComponent: PluginsTab {
parentModal: root.parentModal
} }
} }

View File

@@ -78,6 +78,7 @@ DankModal {
id: profileBrowser id: profileBrowser
allowStacking: true allowStacking: true
parentModal: settingsModal
browserTitle: "Select Profile Image" browserTitle: "Select Profile Image"
browserIcon: "person" browserIcon: "person"
browserType: "profile" browserType: "profile"
@@ -87,12 +88,6 @@ DankModal {
close(); close();
} }
onDialogClosed: () => { onDialogClosed: () => {
if (settingsModal) {
settingsModal.allowFocusOverride = false;
settingsModal.shouldHaveFocus = Qt.binding(() => {
return settingsModal.shouldBeVisible;
});
}
allowStacking = true; allowStacking = true;
} }
} }
@@ -101,6 +96,7 @@ DankModal {
id: wallpaperBrowser id: wallpaperBrowser
allowStacking: true allowStacking: true
parentModal: settingsModal
browserTitle: "Select Wallpaper" browserTitle: "Select Wallpaper"
browserIcon: "wallpaper" browserIcon: "wallpaper"
browserType: "wallpaper" browserType: "wallpaper"
@@ -115,7 +111,7 @@ DankModal {
} }
settingsContent: Component { settingsContent: Component {
Item { FocusScope {
anchors.fill: parent anchors.fill: parent
focus: true focus: true
@@ -144,7 +140,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Settings" text: I18n.tr("Settings")
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -9,40 +9,40 @@ Rectangle {
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
readonly property var sidebarItems: [{ readonly property var sidebarItems: [{
"text": "Personalization", "text": I18n.tr("Personalization"),
"icon": "person" "icon": "person"
}, { }, {
"text": "Time & Date", "text": I18n.tr("Time & Date"),
"icon": "schedule" "icon": "schedule"
}, { }, {
"text": "Weather", "text": I18n.tr("Weather"),
"icon": "cloud" "icon": "cloud"
}, { }, {
"text": "Dank Bar", "text": I18n.tr("Dank Bar"),
"icon": "toolbar" "icon": "toolbar"
}, { }, {
"text": "Widgets", "text": I18n.tr("Widgets"),
"icon": "widgets" "icon": "widgets"
}, { }, {
"text": "Dock", "text": I18n.tr("Dock"),
"icon": "dock_to_bottom" "icon": "dock_to_bottom"
}, { }, {
"text": "Displays", "text": I18n.tr("Displays"),
"icon": "monitor" "icon": "monitor"
}, { }, {
"text": "Launcher", "text": I18n.tr("Launcher"),
"icon": "apps" "icon": "apps"
}, { }, {
"text": "Theme & Colors", "text": I18n.tr("Theme & Colors"),
"icon": "palette" "icon": "palette"
}, { }, {
"text": "Power", "text": I18n.tr("Power"),
"icon": "power_settings_new" "icon": "power_settings_new"
}, { }, {
"text": "Plugins", "text": I18n.tr("Plugins"),
"icon": "extension" "icon": "extension"
}, { }, {
"text": "About", "text": I18n.tr("About"),
"icon": "info" "icon": "info"
}] }]

View File

@@ -13,8 +13,13 @@ Item {
property alias searchField: searchField property alias searchField: searchField
property var parentModal: null property var parentModal: null
function resetScroll() {
resultsView.resetScroll()
}
anchors.fill: parent anchors.fill: parent
focus: true focus: true
clip: false
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
if (parentModal) if (parentModal)
@@ -76,10 +81,6 @@ Item {
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
appLauncher.launchSelected() appLauncher.launchSelected()
event.accepted = true event.accepted = true
} else if (!searchField.activeFocus && event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
searchField.forceActiveFocus()
searchField.insertText(event.text)
event.accepted = true
} }
} }
@@ -101,27 +102,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
spacing: Theme.spacingM spacing: Theme.spacingM
clip: false
Rectangle {
width: parent.width
height: categorySelector.height + Theme.spacingS * 2
radius: Theme.cornerRadius
color: "transparent"
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
CategorySelector {
id: categorySelector
anchors.centerIn: parent
width: parent.width - Theme.spacingS * 2
categories: appLauncher.categories
selectedCategory: appLauncher.selectedCategory
compact: false
onCategorySelected: category => {
appLauncher.setCategory(category)
}
}
}
Row { Row {
width: parent.width width: parent.width
@@ -146,7 +127,7 @@ Item {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: parentModal ? parentModal.spotlightOpen : true enabled: parentModal ? parentModal.spotlightOpen : true
placeholderText: "" placeholderText: ""
ignoreLeftRightKeys: true ignoreLeftRightKeys: appLauncher.viewMode !== "list"
keyForwardTargets: [spotlightKeyHandler] keyForwardTargets: [spotlightKeyHandler]
text: appLauncher.searchQuery text: appLauncher.searchQuery
onTextEdited: () => { onTextEdited: () => {
@@ -228,6 +209,7 @@ Item {
} }
SpotlightResults { SpotlightResults {
id: resultsView
appLauncher: spotlightKeyHandler.appLauncher appLauncher: spotlightKeyHandler.appLauncher
contextMenu: contextMenu contextMenu: contextMenu
} }
@@ -245,7 +227,7 @@ Item {
visible: contextMenu.visible visible: contextMenu.visible
z: 999 z: 999
onClicked: () => { onClicked: () => {
contextMenu.close() contextMenu.hide()
} }
MouseArea { MouseArea {

View File

@@ -1,65 +1,72 @@
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Rectangle { Popup {
id: contextMenu id: contextMenu
property var currentApp: null property var currentApp: null
property bool menuVisible: false
property var appLauncher: null property var appLauncher: null
property var parentHandler: null property var parentHandler: null
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
const menuWidth = 180 contextMenu.x = x + 4
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2 contextMenu.y = y + 4
let finalX = x + 8 contextMenu.open()
let finalY = y + 8
if (parentHandler) {
if (finalX + menuWidth > parentHandler.width)
finalX = x - menuWidth - 8
if (finalY + menuHeight > parentHandler.height)
finalY = y - menuHeight - 8
finalX = Math.max(8, Math.min(finalX, parentHandler.width - menuWidth - 8))
finalY = Math.max(8, Math.min(finalY, parentHandler.height - menuHeight - 8))
}
contextMenu.x = finalX
contextMenu.y = finalY
contextMenu.visible = true
contextMenu.menuVisible = true
} }
function close() { function hide() {
contextMenu.menuVisible = false contextMenu.close()
Qt.callLater(() => {
contextMenu.visible = false
})
} }
visible: false width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius padding: 0
color: Theme.popupBackground() closePolicy: Popup.CloseOnPressOutside
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) modal: false
border.width: 1 dim: false
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
Rectangle { background: Rectangle {
anchors.fill: parent radius: Theme.cornerRadius
anchors.topMargin: 4 color: Theme.popupBackground()
anchors.leftMargin: 2 border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
anchors.rightMargin: -2 border.width: 1
anchors.bottomMargin: -4
radius: parent.radius Rectangle {
color: Qt.rgba(0, 0, 0, 0.15) anchors.fill: parent
z: parent.z - 1 anchors.topMargin: 4
anchors.leftMargin: 2
anchors.rightMargin: -2
anchors.bottomMargin: -4
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15)
z: -1
}
}
enter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
exit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
} }
Column { Column {
@@ -98,10 +105,10 @@ Rectangle {
StyledText { StyledText {
text: { text: {
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
return "Pin to Dock" return I18n.tr("Pin to Dock")
const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || "" const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
return SessionData.isPinnedApp(appId) ? "Unpin from Dock" : "Pin to Dock" return SessionData.isPinnedApp(appId) ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
@@ -125,7 +132,7 @@ Rectangle {
SessionData.removePinnedApp(appId) SessionData.removePinnedApp(appId)
else else
SessionData.addPinnedApp(appId) SessionData.addPinnedApp(appId)
contextMenu.close() contextMenu.hide()
} }
} }
} }
@@ -144,6 +151,82 @@ Rectangle {
} }
} }
Repeater {
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: actionMouseArea.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.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Item {
anchors.verticalCenter: parent.verticalCenter
width: Theme.iconSize - 2
height: Theme.iconSize - 2
visible: modelData.icon && modelData.icon !== ""
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
}
StyledText {
text: modelData.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: parent.width - (modelData.icon && modelData.icon !== "" ? (Theme.iconSize - 2 + Theme.spacingS) : 0)
}
}
MouseArea {
id: actionMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
SessionService.launchDesktopAction(contextMenu.currentApp.desktopEntry, modelData)
if (appLauncher) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
}
}
Rectangle {
visible: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions && contextMenu.currentApp.desktopEntry.actions.length > 0
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
@@ -165,7 +248,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "Launch" text: I18n.tr("Launch")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -183,23 +266,72 @@ Rectangle {
if (contextMenu.currentApp && appLauncher) if (contextMenu.currentApp && appLauncher)
appLauncher.launchApp(contextMenu.currentApp) appLauncher.launchApp(contextMenu.currentApp)
contextMenu.close() contextMenu.hide()
}
}
}
Rectangle {
visible: SessionService.hasPrimeRun
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle {
visible: SessionService.hasPrimeRun
width: parent.width
height: 32
radius: Theme.cornerRadius
color: primeRunMouseArea.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: "memory"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: primeRunMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
if (contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
SessionService.launchDesktopEntry(contextMenu.currentApp.desktopEntry, true)
if (appLauncher) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
} }
} }
} }
} }
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
} }

View File

@@ -18,21 +18,33 @@ DankModal {
function show() { function show() {
spotlightOpen = true spotlightOpen = true
open() open()
if (contentLoader.item && contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
}
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) { if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.forceActiveFocus() contentLoader.item.searchField.forceActiveFocus()
} }
}) })
} }
function hide() { function hide() {
spotlightOpen = false spotlightOpen = false
close() close()
cleanupTimer.restart() }
onDialogClosed: {
if (contentLoader.item) {
if (contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
contentLoader.item.appLauncher.selectedIndex = 0
contentLoader.item.appLauncher.setCategory(I18n.tr("All"))
}
if (contentLoader.item.resetScroll) {
contentLoader.item.resetScroll()
}
if (contentLoader.item.searchField) {
contentLoader.item.searchField.text = ""
}
}
} }
function toggle() { function toggle() {
@@ -45,7 +57,7 @@ DankModal {
shouldBeVisible: spotlightOpen shouldBeVisible: spotlightOpen
width: 550 width: 550
height: 600 height: 700
backgroundColor: Theme.popupBackground() backgroundColor: Theme.popupBackground()
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
@@ -69,19 +81,6 @@ DankModal {
} }
content: spotlightContent content: spotlightContent
Timer {
id: cleanupTimer
interval: animationDuration + 50
onTriggered: {
if (contentLoader.item && contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
contentLoader.item.appLauncher.selectedIndex = 0
contentLoader.item.appLauncher.setCategory("All")
}
}
}
Connections { Connections {
function onCloseAllModalsExcept(excludedModal) { function onCloseAllModalsExcept(excludedModal) {
if (excludedModal !== spotlightModal && !allowStacking && spotlightOpen) { if (excludedModal !== spotlightModal && !allowStacking && spotlightOpen) {

View File

@@ -10,10 +10,16 @@ Rectangle {
property var appLauncher: null property var appLauncher: null
property var contextMenu: null property var contextMenu: null
function resetScroll() {
resultsList.contentY = 0
resultsGrid.contentY = 0
}
width: parent.width width: parent.width
height: parent.height - y height: parent.height - y
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: "transparent" color: "transparent"
clip: true
DankListView { DankListView {
id: resultsList id: resultsList
@@ -157,7 +163,8 @@ Rectangle {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
resultsList.itemClicked(index, model) resultsList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y) const globalPos = mapToItem(null, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y) resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y)
} }
} }
@@ -308,7 +315,8 @@ Rectangle {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
resultsGrid.itemClicked(index, model) resultsGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y) const globalPos = mapToItem(null, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y) resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y)
} }
} }

View File

@@ -9,33 +9,53 @@ DankModal {
property string wifiPasswordSSID: "" property string wifiPasswordSSID: ""
property string wifiPasswordInput: "" property string wifiPasswordInput: ""
property string wifiUsernameInput: ""
property bool requiresEnterprise: false
function show(ssid) { function show(ssid) {
wifiPasswordSSID = ssid wifiPasswordSSID = ssid
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
requiresEnterprise = network?.enterprise || false
open() open()
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordInput) if (contentLoader.item) {
contentLoader.item.passwordInput.forceActiveFocus() if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus()
}
}
}) })
} }
shouldBeVisible: false shouldBeVisible: false
width: 420 width: 420
height: 230 height: requiresEnterprise ? 310 : 230
onShouldBeVisibleChanged: () => { onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) if (!shouldBeVisible) {
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
}
} }
onOpened: { onOpened: {
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordInput) if (contentLoader.item) {
contentLoader.item.passwordInput.forceActiveFocus() if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus()
}
}
}) })
} }
onBackgroundClicked: () => { onBackgroundClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
} }
Connections { Connections {
@@ -55,6 +75,7 @@ DankModal {
FocusScope { FocusScope {
id: wifiContent id: wifiContent
property alias usernameInput: usernameInput
property alias passwordInput: passwordInput property alias passwordInput: passwordInput
anchors.fill: parent anchors.fill: parent
@@ -62,6 +83,7 @@ DankModal {
Keys.onEscapePressed: event => { Keys.onEscapePressed: event => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
event.accepted = true event.accepted = true
} }
@@ -78,14 +100,14 @@ DankModal {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Connect to Wi-Fi" text: I18n.tr("Connect to Wi-Fi")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
StyledText { StyledText {
text: `Enter password for "${wifiPasswordSSID}"` text: requiresEnterprise ? `Enter credentials for "${wifiPasswordSSID}"` : `Enter password for "${wifiPasswordSSID}"`
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
width: parent.width width: parent.width
@@ -100,10 +122,48 @@ DankModal {
onClicked: () => { onClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
} }
} }
} }
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: usernameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: usernameInput.activeFocus ? 2 : 1
visible: requiresEnterprise
MouseArea {
anchors.fill: parent
onClicked: () => {
usernameInput.forceActiveFocus()
}
}
DankTextField {
id: usernameInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiUsernameInput
placeholderText: "Username"
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiUsernameInput = text
}
onAccepted: () => {
if (passwordInput) {
passwordInput.forceActiveFocus()
}
}
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
@@ -127,21 +187,24 @@ DankModal {
textColor: Theme.surfaceText textColor: Theme.surfaceText
text: wifiPasswordInput text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
placeholderText: "" placeholderText: requiresEnterprise ? "Password" : ""
backgroundColor: "transparent" backgroundColor: "transparent"
focus: true focus: !requiresEnterprise
enabled: root.shouldBeVisible enabled: root.shouldBeVisible
onTextEdited: () => { onTextEdited: () => {
wifiPasswordInput = text wifiPasswordInput = text
} }
onAccepted: () => { onAccepted: () => {
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text) const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username)
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
passwordInput.text = "" passwordInput.text = ""
if (requiresEnterprise) usernameInput.text = ""
} }
Component.onCompleted: () => { Component.onCompleted: () => {
if (root.shouldBeVisible) if (root.shouldBeVisible && !requiresEnterprise)
focusDelayTimer.start() focusDelayTimer.start()
} }
@@ -151,8 +214,13 @@ DankModal {
interval: 100 interval: 100
repeat: false repeat: false
onTriggered: () => { onTriggered: () => {
if (root.shouldBeVisible) if (root.shouldBeVisible) {
passwordInput.forceActiveFocus() if (requiresEnterprise && usernameInput) {
usernameInput.forceActiveFocus()
} else {
passwordInput.forceActiveFocus()
}
}
} }
} }
@@ -201,7 +269,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Show password" text: I18n.tr("Show password")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -229,7 +297,7 @@ DankModal {
id: cancelText id: cancelText
anchors.centerIn: parent anchors.centerIn: parent
text: "Cancel" text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -244,6 +312,7 @@ DankModal {
onClicked: () => { onClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
} }
} }
} }
@@ -253,14 +322,14 @@ DankModal {
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: passwordInput.text.length > 0 enabled: requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
opacity: enabled ? 1 : 0.5 opacity: enabled ? 1 : 0.5
StyledText { StyledText {
id: connectText id: connectText
anchors.centerIn: parent anchors.centerIn: parent
text: "Connect" text: I18n.tr("Connect")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium
@@ -274,10 +343,13 @@ DankModal {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: () => { onClicked: () => {
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text) const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username)
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
passwordInput.text = "" passwordInput.text = ""
if (requiresEnterprise) usernameInput.text = ""
} }
} }

View File

@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -41,7 +42,7 @@ DankPopout {
if (shouldBeVisible) { if (shouldBeVisible) {
appLauncher.searchQuery = "" appLauncher.searchQuery = ""
appLauncher.selectedIndex = 0 appLauncher.selectedIndex = 0
appLauncher.setCategory("All") appLauncher.setCategory(I18n.tr("All"))
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) { if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.text = "" contentLoader.item.searchField.text = ""
@@ -165,11 +166,6 @@ DankPopout {
} }
} }
if (!searchField.activeFocus && event.text && /[a-zA-Z0-9\s]/.test(event.text)) {
searchField.forceActiveFocus()
searchField.insertText(event.text)
event.accepted = true
}
} }
Column { Column {
@@ -179,25 +175,23 @@ DankPopout {
y: Theme.spacingS y: Theme.spacingS
spacing: Theme.spacingS spacing: Theme.spacingS
Row { Item {
width: parent.width width: parent.width
height: 40 height: 40
leftPadding: Theme.spacingS
StyledText { StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: "Applications" text: I18n.tr("Applications")
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.surfaceText color: Theme.surfaceText
} }
Item {
width: parent.width - 200
height: 1
}
StyledText { StyledText {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: appLauncher.model.count + " apps" text: appLauncher.model.count + " apps"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -222,7 +216,7 @@ DankPopout {
showClearButton: true showClearButton: true
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: appDrawerPopout.shouldBeVisible enabled: appDrawerPopout.shouldBeVisible
ignoreLeftRightKeys: true ignoreLeftRightKeys: appLauncher.viewMode !== "list"
keyForwardTargets: [keyHandler] keyForwardTargets: [keyHandler]
onTextEdited: { onTextEdited: {
appLauncher.searchQuery = text appLauncher.searchQuery = text
@@ -488,7 +482,8 @@ DankPopout {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appList.itemClicked(index, model) appList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y) var globalPos = mapToItem(null, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appList.itemRightClicked(index, model, panelPos.x, panelPos.y) appList.itemRightClicked(index, model, panelPos.x, panelPos.y)
} }
} }
@@ -649,7 +644,8 @@ DankPopout {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appGrid.itemClicked(index, model) appGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y) var globalPos = mapToItem(null, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y) appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y)
} }
} }
@@ -662,68 +658,68 @@ DankPopout {
} }
} }
Rectangle { Popup {
id: contextMenu id: contextMenu
property var currentApp: null property var currentApp: null
property bool menuVisible: false
readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : "" readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : ""
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId) readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
contextMenu.x = x + 4
const menuWidth = 180 contextMenu.y = y + 4
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2 contextMenu.open()
let finalX = x + 8
let finalY = y + 8
if (finalX + menuWidth > appDrawerPopout.popupWidth) {
finalX = x - menuWidth - 8
}
if (finalY + menuHeight > appDrawerPopout.popupHeight) {
finalY = y - menuHeight - 8
}
finalX = Math.max(8, Math.min(finalX, appDrawerPopout.popupWidth - menuWidth - 8))
finalY = Math.max(8, Math.min(finalY, appDrawerPopout.popupHeight - menuHeight - 8))
contextMenu.x = finalX
contextMenu.y = finalY
contextMenu.visible = true
contextMenu.menuVisible = true
} }
function close() { function hide() {
contextMenu.menuVisible = false contextMenu.close()
Qt.callLater(() => {
contextMenu.visible = false
})
} }
visible: false width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius padding: 0
color: Theme.popupBackground() closePolicy: Popup.CloseOnPressOutside
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) modal: false
border.width: 0 dim: false
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
Rectangle { background: Rectangle {
anchors.fill: parent radius: Theme.cornerRadius
anchors.topMargin: 4 color: Theme.popupBackground()
anchors.leftMargin: 2 border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
anchors.rightMargin: -2 border.width: 1
anchors.bottomMargin: -4
radius: parent.radius Rectangle {
color: Qt.rgba(0, 0, 0, 0.15) anchors.fill: parent
z: parent.z - 1 anchors.topMargin: 4
anchors.leftMargin: 2
anchors.rightMargin: -2
anchors.bottomMargin: -4
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15)
z: -1
}
}
enter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
exit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
} }
Column { Column {
@@ -754,7 +750,7 @@ DankPopout {
} }
StyledText { StyledText {
text: contextMenu.isPinned ? "Unpin from Dock" : "Pin to Dock" text: contextMenu.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -778,7 +774,7 @@ DankPopout {
} else { } else {
SessionData.addPinnedApp(contextMenu.appId) SessionData.addPinnedApp(contextMenu.appId)
} }
contextMenu.close() contextMenu.hide()
} }
} }
} }
@@ -797,6 +793,76 @@ DankPopout {
} }
} }
Repeater {
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: actionMouseArea.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
Item {
anchors.verticalCenter: parent.verticalCenter
width: Theme.iconSize - 2
height: Theme.iconSize - 2
visible: modelData.icon && modelData.icon !== ""
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
}
StyledText {
text: modelData.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: actionMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
SessionService.launchDesktopAction(contextMenu.currentApp.desktopEntry, modelData)
appLauncher.appLaunched(contextMenu.currentApp)
}
contextMenu.hide()
}
}
}
}
Rectangle {
visible: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions && contextMenu.currentApp.desktopEntry.actions.length > 0
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
@@ -818,7 +884,7 @@ DankPopout {
} }
StyledText { StyledText {
text: "Launch" text: I18n.tr("Launch")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -836,23 +902,70 @@ DankPopout {
if (contextMenu.currentApp) if (contextMenu.currentApp)
appLauncher.launchApp(contextMenu.currentApp) appLauncher.launchApp(contextMenu.currentApp)
contextMenu.close() contextMenu.hide()
} }
} }
} }
}
Behavior on opacity { Rectangle {
NumberAnimation { visible: SessionService.hasPrimeRun
duration: Theme.mediumDuration width: parent.width - Theme.spacingS * 2
easing.type: Theme.emphasizedEasing height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
} }
}
Behavior on scale { Rectangle {
NumberAnimation { visible: SessionService.hasPrimeRun
duration: Theme.mediumDuration width: parent.width
easing.type: Theme.emphasizedEasing height: 32
radius: Theme.cornerRadius
color: primeRunMouseArea.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: "memory"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: primeRunMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
SessionService.launchDesktopEntry(contextMenu.currentApp.desktopEntry, true)
appLauncher.appLaunched(contextMenu.currentApp)
}
contextMenu.hide()
}
}
} }
} }
} }
@@ -862,7 +975,7 @@ DankPopout {
visible: contextMenu.visible visible: contextMenu.visible
z: 999 z: 999
onClicked: { onClicked: {
contextMenu.close() contextMenu.hide()
} }
MouseArea { MouseArea {

View File

@@ -9,7 +9,7 @@ Item {
id: root id: root
property string searchQuery: "" property string searchQuery: ""
property string selectedCategory: "All" property string selectedCategory: I18n.tr("All")
property string viewMode: "list" // "list" or "grid" property string viewMode: "list" // "list" or "grid"
property int selectedIndex: 0 property int selectedIndex: 0
property int maxResults: 50 property int maxResults: 50
@@ -20,8 +20,8 @@ Item {
property bool suppressUpdatesWhileLaunching: false property bool suppressUpdatesWhileLaunching: false
readonly property var categories: { readonly property var categories: {
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science") const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
const result = ["All"] const result = [I18n.tr("All")]
return result.concat(allCategories.filter(cat => cat !== "All")) return result.concat(allCategories.filter(cat => cat !== I18n.tr("All")))
} }
readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category)) readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {} property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
@@ -42,10 +42,11 @@ Item {
keyboardNavigationActive = false keyboardNavigationActive = false
let apps = [] let apps = []
const allCategory = I18n.tr("All")
if (searchQuery.length === 0) { if (searchQuery.length === 0) {
apps = selectedCategory === "All" ? AppSearchService.getAppsInCategory("All") : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults) apps = selectedCategory === allCategory ? AppSearchService.getAppsInCategory(allCategory) : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
} else { } else {
if (selectedCategory === "All") { if (selectedCategory === allCategory) {
apps = AppSearchService.searchApplications(searchQuery) apps = AppSearchService.searchApplications(searchQuery)
} else { } else {
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory) const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)

View File

@@ -1,13 +1,14 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Services
import qs.Widgets import qs.Widgets
Item { Item {
id: root id: root
property var categories: [] property var categories: []
property string selectedCategory: "All" property string selectedCategory: I18n.tr("All")
property bool compact: false property bool compact: false
signal categorySelected(string category) signal categorySelected(string category)
@@ -18,7 +19,6 @@ Item {
readonly property color unselectedBorderColor: "transparent" readonly property color unselectedBorderColor: "transparent"
function handleCategoryClick(category) { function handleCategoryClick(category) {
selectedCategory = category
categorySelected(category) categorySelected(category)
} }

View File

@@ -0,0 +1,246 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
Ref {
service: VpnService
}
ccWidgetIcon: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetSecondaryText: {
if (!VpnService.connected)
return "Disconnected"
const names = VpnService.activeNames || []
if (names.length <= 1)
return names[0] || "Connected"
return names[0] + " +" + (names.length - 1)
}
ccWidgetIsActive: VpnService.connected
onCcWidgetToggled: {
if (VpnService.connected) {
VpnService.disconnectAllActive()
} else if (VpnService.profiles.length > 0) {
VpnService.connect(VpnService.profiles[0].uuid)
}
}
ccDetailContent: Component {
Rectangle {
id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: detailColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
RowLayout {
spacing: Theme.spacingS
width: parent.width
StyledText {
text: {
if (!VpnService.connected)
return "Active: None"
const names = VpnService.activeNames || []
if (names.length <= 1)
return "Active: " + (names[0] || "VPN")
return "Active: " + names[0] + " +" + (names.length - 1)
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Item {
Layout.fillWidth: true
}
Rectangle {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
width: 110
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "link_off"
size: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Disconnect")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: discAllArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
}
}
}
Rectangle {
height: 1
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
DankFlickable {
width: parent.width
height: 160
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: Theme.spacingXS
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "playlist_remove"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No VPN profiles found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("Add a VPN in NetworkManager")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Repeater {
model: VpnService.profiles
delegate: Rectangle {
required property var modelData
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (VpnService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: VpnService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
text: {
if (modelData.type === "wireguard")
return "WireGuard"
const svc = modelData.serviceType || ""
if (svc.indexOf("openvpn") !== -1)
return "OpenVPN"
if (svc.indexOf("wireguard") !== -1)
return "WireGuard (plugin)"
if (svc.indexOf("openconnect") !== -1)
return "OpenConnect"
if (svc.indexOf("fortissl") !== -1 || svc.indexOf("forti") !== -1)
return "Fortinet"
if (svc.indexOf("strongswan") !== -1)
return "IPsec (strongSwan)"
if (svc.indexOf("libreswan") !== -1)
return "IPsec (Libreswan)"
if (svc.indexOf("l2tp") !== -1)
return "L2TP/IPsec"
if (svc.indexOf("pptp") !== -1)
return "PPTP"
if (svc.indexOf("vpnc") !== -1)
return "Cisco (vpnc)"
if (svc.indexOf("sstp") !== -1)
return "SSTP"
if (svc)
return svc.split('.').pop()
return "VPN"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
}
}
}
}
}
}
}
}
}

View File

@@ -1,6 +1,8 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services
import qs.Modules.ControlCenter.Details import qs.Modules.ControlCenter.Details
import qs.Modules.ControlCenter.Models
Item { Item {
id: root id: root
@@ -9,31 +11,97 @@ Item {
property var expandedWidgetData: null property var expandedWidgetData: null
property var bluetoothCodecSelector: null property var bluetoothCodecSelector: null
property var pluginDetailInstance: null
property var widgetModel: null
Loader { Loader {
id: pluginDetailLoader
width: parent.width width: parent.width
height: 250 height: 250
y: Theme.spacingS y: Theme.spacingS
active: parent.height > 0 active: false
property string sectionKey: root.expandedSection sourceComponent: null
sourceComponent: { }
switch (root.expandedSection) {
case "network": Loader {
case "wifi": return networkDetailComponent id: coreDetailLoader
case "bluetooth": return bluetoothDetailComponent width: parent.width
case "audioOutput": return audioOutputDetailComponent height: 250
case "audioInput": return audioInputDetailComponent y: Theme.spacingS
case "battery": return batteryDetailComponent active: false
default: sourceComponent: null
if (root.expandedSection.startsWith("diskUsage_")) { }
return diskUsageDetailComponent
onExpandedSectionChanged: {
if (pluginDetailInstance) {
pluginDetailInstance.destroy()
pluginDetailInstance = null
}
pluginDetailLoader.active = false
coreDetailLoader.active = false
if (!root.expandedSection) {
return
}
if (root.expandedSection.startsWith("builtin_")) {
const builtinId = root.expandedSection
let builtinInstance = null
if (builtinId === "builtin_vpn") {
if (widgetModel?.vpnLoader) {
widgetModel.vpnLoader.active = true
} }
return null builtinInstance = widgetModel.vpnBuiltinInstance
} }
if (!builtinInstance || !builtinInstance.ccDetailContent) {
return
}
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent
pluginDetailLoader.active = parent.height > 0
return
} }
onSectionKeyChanged: {
active = false if (root.expandedSection.startsWith("plugin_")) {
active = true const pluginId = root.expandedSection.replace("plugin_", "")
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
if (!pluginComponent) {
return
}
pluginDetailInstance = pluginComponent.createObject(null)
if (!pluginDetailInstance || !pluginDetailInstance.ccDetailContent) {
if (pluginDetailInstance) {
pluginDetailInstance.destroy()
pluginDetailInstance = null
}
return
}
pluginDetailLoader.sourceComponent = pluginDetailInstance.ccDetailContent
pluginDetailLoader.active = parent.height > 0
return
} }
if (root.expandedSection.startsWith("diskUsage_")) {
coreDetailLoader.sourceComponent = diskUsageDetailComponent
coreDetailLoader.active = parent.height > 0
return
}
switch (root.expandedSection) {
case "network":
case "wifi": coreDetailLoader.sourceComponent = networkDetailComponent; break
case "bluetooth": coreDetailLoader.sourceComponent = bluetoothDetailComponent; break
case "audioOutput": coreDetailLoader.sourceComponent = audioOutputDetailComponent; break
case "audioInput": coreDetailLoader.sourceComponent = audioInputDetailComponent; break
case "battery": coreDetailLoader.sourceComponent = batteryDetailComponent; break
default: return
}
coreDetailLoader.active = parent.height > 0
} }
Component { Component {

View File

@@ -62,7 +62,8 @@ Column {
property var rowWidgets: modelData property var rowWidgets: modelData
property bool isSliderOnlyRow: { property bool isSliderOnlyRow: {
const widgets = rowWidgets || [] const widgets = rowWidgets || []
if (widgets.length === 0) return false if (widgets.length === 0)
return false
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider") return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
} }
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0 topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
@@ -121,7 +122,11 @@ Column {
widgetComponent: { widgetComponent: {
const id = modelData.id || "" const id = modelData.id || ""
if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") { if (id.startsWith("builtin_")) {
return builtinPluginWidgetComponent
} else if (id.startsWith("plugin_")) {
return pluginWidgetComponent
} else if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
return compoundPillComponent return compoundPillComponent
} else if (id === "volumeSlider") { } else if (id === "volumeSlider") {
return audioSliderComponent return audioSliderComponent
@@ -151,7 +156,8 @@ Column {
width: parent.width width: parent.width
height: active ? (250 + Theme.spacingS) : 0 height: active ? (250 + Theme.spacingS) : 0
property bool active: { property bool active: {
if (root.expandedSection === "") return false if (root.expandedSection === "")
return false
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) { if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
const expandedInstanceId = root.expandedWidgetData.instanceId const expandedInstanceId = root.expandedWidgetData.instanceId
@@ -164,10 +170,27 @@ Column {
expandedSection: root.expandedSection expandedSection: root.expandedSection
expandedWidgetData: root.expandedWidgetData expandedWidgetData: root.expandedWidgetData
bluetoothCodecSelector: root.bluetoothCodecSelector bluetoothCodecSelector: root.bluetoothCodecSelector
widgetModel: root.model
} }
} }
} }
Component {
id: errorPillComponent
ErrorPill {
property var widgetData: parent.widgetData || {}
width: parent.width
height: 60
primaryMessage: {
if (!DMSService.dmsAvailable) {
return I18n.tr("DMS_SOCKET not available")
}
return I18n.tr("NM not supported")
}
secondaryMessage: I18n.tr("update dms for NM integration.")
}
}
Component { Component {
id: compoundPillComponent id: compoundPillComponent
CompoundPill { CompoundPill {
@@ -345,7 +368,8 @@ Column {
} }
enabled: widgetDef?.enabled ?? true enabled: widgetDef?.enabled ?? true
onToggled: { onToggled: {
if (root.editMode) return if (root.editMode)
return
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": case "wifi":
{ {
@@ -378,11 +402,13 @@ Column {
} }
} }
onExpandClicked: { onExpandClicked: {
if (root.editMode) return if (root.editMode)
return
root.expandClicked(widgetData, widgetIndex) root.expandClicked(widgetData, widgetIndex)
} }
onWheelEvent: function (wheelEvent) { onWheelEvent: function (wheelEvent) {
if (root.editMode) return if (root.editMode)
return
const id = widgetData.id || "" const id = widgetData.id || ""
if (id === "audioOutput") { if (id === "audioOutput") {
if (!AudioService.sink || !AudioService.sink.audio) if (!AudioService.sink || !AudioService.sink.audio)
@@ -537,9 +563,10 @@ Column {
} }
iconRotation: { iconRotation: {
if (widgetData.id !== "darkMode") return 0 if (widgetData.id !== "darkMode")
return 0
if (darkModeTransitionPending) { if (darkModeTransitionPending) {
return SessionData.isLightMode ? 0 : 180 return SessionData.isLightMode ? 180 : 0
} }
return SessionData.isLightMode ? 180 : 0 return SessionData.isLightMode ? 180 : 0
} }
@@ -549,7 +576,7 @@ Column {
case "nightMode": case "nightMode":
return DisplayService.nightModeEnabled || false return DisplayService.nightModeEnabled || false
case "darkMode": case "darkMode":
return !SessionData.isLightMode return SessionData.isLightMode
case "doNotDisturb": case "doNotDisturb":
return SessionData.doNotDisturb || false return SessionData.doNotDisturb || false
case "idleInhibitor": case "idleInhibitor":
@@ -559,15 +586,7 @@ Column {
} }
} }
enabled: !root.editMode enabled: !root.editMode
onIconRotationCompleted: {
if (root.darkModeTransitionPending && widgetData.id === "darkMode") {
root.darkModeTransitionPending = false
Theme.screenTransition()
Theme.toggleLightMode()
}
}
onClicked: { onClicked: {
if (root.editMode) if (root.editMode)
@@ -581,7 +600,8 @@ enabled: !root.editMode
} }
case "darkMode": case "darkMode":
{ {
root.darkModeTransitionPending = true Theme.screenTransition()
Theme.setLightMode(!SessionData.isLightMode)
break break
} }
case "doNotDisturb": case "doNotDisturb":
@@ -623,9 +643,10 @@ enabled: !root.editMode
} }
iconRotation: { iconRotation: {
if (widgetData.id !== "darkMode") return 0 if (widgetData.id !== "darkMode")
return 0
if (darkModeTransitionPending) { if (darkModeTransitionPending) {
return SessionData.isLightMode ? 0 : 180 return SessionData.isLightMode ? 180 : 0
} }
return SessionData.isLightMode ? 180 : 0 return SessionData.isLightMode ? 180 : 0
} }
@@ -635,7 +656,7 @@ enabled: !root.editMode
case "nightMode": case "nightMode":
return DisplayService.nightModeEnabled || false return DisplayService.nightModeEnabled || false
case "darkMode": case "darkMode":
return !SessionData.isLightMode return SessionData.isLightMode
case "doNotDisturb": case "doNotDisturb":
return SessionData.doNotDisturb || false return SessionData.doNotDisturb || false
case "idleInhibitor": case "idleInhibitor":
@@ -645,15 +666,7 @@ enabled: !root.editMode
} }
} }
enabled: !root.editMode enabled: !root.editMode
onIconRotationCompleted: {
if (root.darkModeTransitionPending && widgetData.id === "darkMode") {
root.darkModeTransitionPending = false
Theme.screenTransition()
Theme.toggleLightMode()
}
}
onClicked: { onClicked: {
if (root.editMode) if (root.editMode)
@@ -667,7 +680,8 @@ enabled: !root.editMode
} }
case "darkMode": case "darkMode":
{ {
root.darkModeTransitionPending = true Theme.screenTransition()
Theme.setLightMode(!SessionData.isLightMode)
break break
} }
case "doNotDisturb": case "doNotDisturb":
@@ -715,4 +729,251 @@ enabled: !root.editMode
colorPickerModal: root.colorPickerModal colorPickerModal: root.colorPickerModal
} }
} }
Component {
id: builtinPluginWidgetComponent
Loader {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property int widgetWidth: widgetData.width || 50
width: parent.width
height: 60
property var builtinInstance: null
Component.onCompleted: {
const id = widgetData.id || ""
if (id === "builtin_vpn") {
if (root.model?.vpnLoader) {
root.model.vpnLoader.active = true
}
builtinInstance = Qt.binding(() => root.model?.vpnBuiltinInstance)
}
}
sourceComponent: {
if (!builtinInstance)
return null
const hasDetail = builtinInstance.ccDetailContent !== null
if (widgetWidth <= 25) {
return builtinSmallToggleComponent
} else if (hasDetail) {
return builtinCompoundPillComponent
} else {
return builtinToggleComponent
}
}
}
}
Component {
id: builtinCompoundPillComponent
CompoundPill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
primaryText: builtinInstance?.ccWidgetPrimaryText || "Built-in"
secondaryText: builtinInstance?.ccWidgetSecondaryText || ""
isActive: builtinInstance?.ccWidgetIsActive || false
onToggled: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
onExpandClicked: {
if (root.editMode)
return
root.expandClicked(widgetData, widgetIndex)
}
}
}
Component {
id: builtinToggleComponent
ToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
text: builtinInstance?.ccWidgetPrimaryText || "Built-in"
isActive: builtinInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
}
}
Component {
id: builtinSmallToggleComponent
SmallToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
isActive: builtinInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
}
}
Component {
id: pluginWidgetComponent
Loader {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property int widgetWidth: widgetData.width || 50
width: parent.width
height: 60
property var pluginInstance: null
property string pluginId: widgetData.id?.replace("plugin_", "") || ""
sourceComponent: {
if (!pluginInstance)
return null
const hasDetail = pluginInstance.ccDetailContent !== null
if (widgetWidth <= 25) {
return pluginSmallToggleComponent
} else if (hasDetail) {
return pluginCompoundPillComponent
} else {
return pluginToggleComponent
}
}
Component.onCompleted: {
Qt.callLater(() => {
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
if (pluginComponent) {
const instance = pluginComponent.createObject(null, {
"pluginId": pluginId,
"pluginService": PluginService,
"visible": false,
"width": 0,
"height": 0
})
if (instance) {
pluginInstance = instance
}
}
})
}
Connections {
target: PluginService
function onPluginDataChanged(changedPluginId) {
if (changedPluginId === pluginId && pluginInstance) {
pluginInstance.loadPluginData()
}
}
}
Component.onDestruction: {
if (pluginInstance) {
pluginInstance.destroy()
}
}
}
}
Component {
id: pluginCompoundPillComponent
CompoundPill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var pluginInstance: parent.pluginInstance
iconName: pluginInstance?.ccWidgetIcon || "extension"
primaryText: pluginInstance?.ccWidgetPrimaryText || "Plugin"
secondaryText: pluginInstance?.ccWidgetSecondaryText || ""
isActive: pluginInstance?.ccWidgetIsActive || false
onToggled: {
if (root.editMode)
return
if (pluginInstance) {
pluginInstance.ccWidgetToggled()
}
}
onExpandClicked: {
if (root.editMode)
return
root.expandClicked(widgetData, widgetIndex)
}
}
}
Component {
id: pluginToggleComponent
ToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var pluginInstance: parent.pluginInstance
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
iconName: pluginInstance?.ccWidgetIcon || widgetDef?.icon || "extension"
text: pluginInstance?.ccWidgetPrimaryText || widgetDef?.text || "Plugin"
secondaryText: pluginInstance?.ccWidgetSecondaryText || ""
isActive: pluginInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (pluginInstance) {
pluginInstance.ccWidgetToggled()
}
}
}
}
Component {
id: pluginSmallToggleComponent
SmallToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var pluginInstance: parent.pluginInstance
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
iconName: pluginInstance?.ccWidgetIcon || widgetDef?.icon || "extension"
isActive: pluginInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (pluginInstance && pluginInstance.ccDetailContent) {
root.expandClicked(widgetData, widgetIndex)
} else if (pluginInstance) {
pluginInstance.ccWidgetToggled()
}
}
}
}
} }

View File

@@ -55,7 +55,7 @@ Row {
} }
Typography { Typography {
text: "Add Widget" text: I18n.tr("Add Widget")
style: Typography.Style.Subtitle style: Typography.Style.Subtitle
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -155,7 +155,7 @@ Row {
} }
Typography { Typography {
text: "Add Widget" text: I18n.tr("Add Widget")
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -189,7 +189,7 @@ Row {
} }
Typography { Typography {
text: "Defaults" text: I18n.tr("Defaults")
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.warning color: Theme.warning
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -223,7 +223,7 @@ Row {
} }
Typography { Typography {
text: "Reset" text: I18n.tr("Reset")
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.error color: Theme.error
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -6,10 +6,9 @@ import qs.Widgets
Rectangle { Rectangle {
id: root id: root
property bool powerOptionsExpanded: false
property bool editMode: false property bool editMode: false
signal powerActionRequested(string action, string title, string message) signal powerButtonClicked()
signal lockRequested() signal lockRequested()
signal editModeToggled() signal editModeToggled()
@@ -83,13 +82,11 @@ Rectangle {
DankActionButton { DankActionButton {
buttonSize: 36 buttonSize: 36
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new" iconName: "power_settings_new"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4
iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText iconColor: Theme.surfaceText
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: { onClicked: root.powerButtonClicked()
root.powerOptionsExpanded = !root.powerOptionsExpanded
}
} }
DankActionButton { DankActionButton {

View File

@@ -1,70 +0,0 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property bool expanded: false
signal powerActionRequested(string action, string title, string message)
implicitHeight: expanded ? 60 : 0
height: implicitHeight
clip: true
Rectangle {
width: parent.width
height: 60
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: root.expanded ? 1 : 0
opacity: root.expanded ? 1 : 0
clip: true
Row {
anchors.centerIn: parent
spacing: SessionService.hibernateSupported ? Theme.spacingS : Theme.spacingL
visible: root.expanded
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "logout"
text: "Logout"
onPressed: root.powerActionRequested("logout", "Logout", "Are you sure you want to logout?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "restart_alt"
text: "Restart"
onPressed: root.powerActionRequested("reboot", "Restart", "Are you sure you want to restart?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "bedtime"
text: "Suspend"
onPressed: root.powerActionRequested("suspend", "Suspend", "Are you sure you want to suspend?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "ac_unit"
text: "Hibernate"
visible: SessionService.hibernateSupported
onPressed: root.powerActionRequested("hibernate", "Hibernate", "Are you sure you want to hibernate?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "power_settings_new"
text: "Shutdown"
onPressed: root.powerActionRequested("poweroff", "Shutdown", "Are you sure you want to shutdown?")
}
}
}
}

View File

@@ -21,13 +21,11 @@ DankPopout {
id: root id: root
property string expandedSection: "" property string expandedSection: ""
property bool powerOptionsExpanded: false
property var triggerScreen: null property var triggerScreen: null
property bool editMode: false property bool editMode: false
property int expandedWidgetIndex: -1 property int expandedWidgetIndex: -1
property var expandedWidgetData: null property var expandedWidgetData: null
signal powerActionRequested(string action, string title, string message)
signal lockRequested signal lockRequested
function collapseAll() { function collapseAll() {
@@ -75,13 +73,17 @@ DankPopout {
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (shouldBeVisible) { if (shouldBeVisible) {
Qt.callLater(() => { Qt.callLater(() => {
NetworkService.autoRefreshEnabled = NetworkService.wifiEnabled if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled
}
if (UserInfoService) if (UserInfoService)
UserInfoService.getUptime() UserInfoService.getUptime()
}) })
} else { } else {
Qt.callLater(() => { Qt.callLater(() => {
NetworkService.autoRefreshEnabled = false if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = false
}
if (BluetoothService.adapter && BluetoothService.adapter.discovering) if (BluetoothService.adapter && BluetoothService.adapter.discovering)
BluetoothService.adapter.discovering = false BluetoothService.adapter.discovering = false
editMode = false editMode = false
@@ -122,28 +124,24 @@ DankPopout {
HeaderPane { HeaderPane {
id: headerPane id: headerPane
width: parent.width width: parent.width
powerOptionsExpanded: root.powerOptionsExpanded
editMode: root.editMode editMode: root.editMode
onPowerOptionsExpandedChanged: root.powerOptionsExpanded = powerOptionsExpanded
onEditModeToggled: root.editMode = !root.editMode onEditModeToggled: root.editMode = !root.editMode
onPowerActionRequested: (action, title, message) => root.powerActionRequested(action, title, message) onPowerButtonClicked: {
if (powerMenuModalLoader) {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item) {
const popoutPos = controlContent.mapToItem(null, 0, 0)
const bounds = Qt.rect(popoutPos.x, popoutPos.y, controlContent.width, controlContent.height)
powerMenuModalLoader.item.openFromControlCenter(bounds, root.triggerScreen)
}
}
}
onLockRequested: { onLockRequested: {
root.close() root.close()
root.lockRequested() root.lockRequested()
} }
} }
PowerOptionsPane {
id: powerOptionsPane
width: parent.width
expanded: root.powerOptionsExpanded
onPowerActionRequested: (action, title, message) => {
root.powerOptionsExpanded = false
root.close()
root.powerActionRequested(action, title, message)
}
}
DragDropGrid { DragDropGrid {
id: widgetGrid id: widgetGrid
width: parent.width width: parent.width
@@ -173,8 +171,10 @@ DankPopout {
visible: editMode visible: editMode
popoutContent: controlContent popoutContent: controlContent
availableWidgets: { availableWidgets: {
if (!editMode) return []
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id) const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
return widgetModel.baseWidgetDefinitions.filter(w => w.allowMultiple || !existingIds.includes(w.id)) const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets())
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id))
} }
onAddWidget: (widgetId) => widgetModel.addWidget(widgetId) onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
onResetToDefault: () => widgetModel.resetToDefault() onResetToDefault: () => widgetModel.resetToDefault()
@@ -226,4 +226,5 @@ DankPopout {
} }
property var colorPickerModal: null property var colorPickerModal: null
property var powerMenuModalLoader: null
} }

View File

@@ -30,7 +30,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: "Input Devices" text: I18n.tr("Input Devices")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -30,7 +30,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: "Audio Devices" text: I18n.tr("Audio Devices")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -133,7 +133,7 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Health" text: I18n.tr("Health")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -168,7 +168,7 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Capacity" text: I18n.tr("Capacity")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -237,7 +237,7 @@ Rectangle {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: "Power Profile Degradation" text: I18n.tr("Power Profile Degradation")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.error color: Theme.error
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -170,7 +170,7 @@ Item {
} }
StyledText { StyledText {
text: "Audio Codec Selection" text: I18n.tr("Audio Codec Selection")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
} }

View File

@@ -39,7 +39,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: "Bluetooth Settings" text: I18n.tr("Bluetooth Settings")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -422,7 +422,7 @@ Rectangle {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "No Bluetooth adapter found" text: I18n.tr("No Bluetooth adapter found")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
@@ -473,7 +473,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: "Audio Codec" text: I18n.tr("Audio Codec")
height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0 height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0
visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected
@@ -498,7 +498,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: "Forget Device" text: I18n.tr("Forget Device")
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {

View File

@@ -20,12 +20,9 @@ Rectangle {
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0 border.width: 0
Component.onCompleted: { Component.onCompleted: {
NetworkService.addRef() NetworkService.addRef()
if (NetworkService.wifiEnabled) {
NetworkService.scanWifi()
}
} }
Component.onDestruction: { Component.onDestruction: {
@@ -44,7 +41,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: "Network Settings" text: I18n.tr("Network Settings")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -59,15 +56,30 @@ Rectangle {
DankButtonGroup { DankButtonGroup {
id: preferenceControls id: preferenceControls
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.ethernetConnected && NetworkService.wifiConnected visible: NetworkService.ethernetConnected
property int currentPreferenceIndex: NetworkService.userPreference === "ethernet" ? 0 : 1 property int currentPreferenceIndex: {
const pref = NetworkService.userPreference
const status = NetworkService.networkStatus
let index = 1
if (pref === "ethernet") {
index = 0
} else if (pref === "wifi") {
index = 1
} else {
index = status === "ethernet" ? 0 : 1
}
return index
}
model: ["Ethernet", "WiFi"] model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex currentIndex: currentPreferenceIndex
selectionMode: "single" selectionMode: "single"
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (!selected) return if (!selected) return
console.log("NetworkDetail: Setting preference to", index === 0 ? "ethernet" : "wifi")
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi") NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi")
} }
} }
@@ -82,17 +94,17 @@ Rectangle {
anchors.topMargin: Theme.spacingM anchors.topMargin: Theme.spacingM
visible: NetworkService.wifiToggling visible: NetworkService.wifiToggling
height: visible ? 80 : 0 height: visible ? 80 : 0
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingM spacing: Theme.spacingM
DankIcon { DankIcon {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
name: "sync" name: "sync"
size: 32 size: 32
color: Theme.primary color: Theme.primary
RotationAnimation on rotation { RotationAnimation on rotation {
running: NetworkService.wifiToggling running: NetworkService.wifiToggling
loops: Animation.Infinite loops: Animation.Infinite
@@ -101,7 +113,7 @@ Rectangle {
duration: 1000 duration: 1000
} }
} }
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..." text: NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
@@ -136,7 +148,7 @@ Rectangle {
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: "WiFi is off" text: I18n.tr("WiFi is off")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -154,7 +166,7 @@ Rectangle {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "Enable WiFi" text: I18n.tr("Enable WiFi")
color: Theme.primary color: Theme.primary
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
@@ -192,16 +204,16 @@ Rectangle {
Item { Item {
width: parent.width width: parent.width
height: 200 height: 200
visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling && NetworkService.isScanning
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: "refresh" name: "refresh"
size: 48 size: 48
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3) color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3)
RotationAnimation on rotation { RotationAnimation on rotation {
running: true running: NetworkService.isScanning
loops: Animation.Infinite loops: Animation.Infinite
from: 0 from: 0
to: 360 to: 360
@@ -211,14 +223,18 @@ Rectangle {
} }
Repeater { Repeater {
model: { model: sortedNetworks
let networks = [...NetworkService.wifiNetworks]
networks.sort((a, b) => { property var sortedNetworks: {
if (a.ssid === NetworkService.currentWifiSSID) return -1 const ssid = NetworkService.currentWifiSSID
if (b.ssid === NetworkService.currentWifiSSID) return 1 const networks = NetworkService.wifiNetworks
let sorted = [...networks]
sorted.sort((a, b) => {
if (a.ssid === ssid) return -1
if (b.ssid === ssid) return 1
return b.signal - a.signal return b.signal - a.signal
}) })
return networks return sorted
} }
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
@@ -261,10 +277,10 @@ Rectangle {
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected" : (modelData.secured ? "Secured" : "Open") text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected" : (modelData.secured ? "Secured" : "Open")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -380,7 +396,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: "Network Info" text: I18n.tr("Network Info")
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {
@@ -403,7 +419,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: "Forget Network" text: I18n.tr("Forget Network")
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0 height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0
visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected

View File

@@ -1,13 +1,38 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Modules.ControlCenter.BuiltinPlugins
import "../utils/widgets.js" as WidgetUtils import "../utils/widgets.js" as WidgetUtils
QtObject { QtObject {
id: root id: root
readonly property var baseWidgetDefinitions: [ property var vpnBuiltinInstance: null
{
property var vpnLoader: Loader {
active: false
sourceComponent: Component {
VpnWidget {}
}
onItemChanged: {
root.vpnBuiltinInstance = item
}
Connections {
target: SettingsData
function onControlCenterWidgetsChanged() {
const widgets = SettingsData.controlCenterWidgets || []
const hasVpnWidget = widgets.some(w => w.id === "builtin_vpn")
if (!hasVpnWidget && vpnLoader.active) {
console.log("VpnWidget: No VPN widget in control center, deactivating loader")
vpnLoader.active = false
}
}
}
}
readonly property var coreWidgetDefinitions: [{
"id": "nightMode", "id": "nightMode",
"text": "Night Mode", "text": "Night Mode",
"description": "Blue light filter", "description": "Blue light filter",
@@ -15,32 +40,28 @@ QtObject {
"type": "toggle", "type": "toggle",
"enabled": DisplayService.automationAvailable, "enabled": DisplayService.automationAvailable,
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined "warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
}, }, {
{
"id": "darkMode", "id": "darkMode",
"text": "Dark Mode", "text": "Dark Mode",
"description": "System theme toggle", "description": "System theme toggle",
"icon": "contrast", "icon": "contrast",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "doNotDisturb", "id": "doNotDisturb",
"text": "Do Not Disturb", "text": "Do Not Disturb",
"description": "Block notifications", "description": "Block notifications",
"icon": "do_not_disturb_on", "icon": "do_not_disturb_on",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "idleInhibitor", "id": "idleInhibitor",
"text": "Keep Awake", "text": "Keep Awake",
"description": "Prevent screen timeout", "description": "Prevent screen timeout",
"icon": "motion_sensor_active", "icon": "motion_sensor_active",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "wifi", "id": "wifi",
"text": "Network", "text": "Network",
"description": "Wi-Fi and Ethernet connection", "description": "Wi-Fi and Ethernet connection",
@@ -48,8 +69,7 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": NetworkService.wifiAvailable, "enabled": NetworkService.wifiAvailable,
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined "warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
}, }, {
{
"id": "bluetooth", "id": "bluetooth",
"text": "Bluetooth", "text": "Bluetooth",
"description": "Device connections", "description": "Device connections",
@@ -57,32 +77,28 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": BluetoothService.available, "enabled": BluetoothService.available,
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined "warning": !BluetoothService.available ? "Bluetooth not available" : undefined
}, }, {
{
"id": "audioOutput", "id": "audioOutput",
"text": "Audio Output", "text": "Audio Output",
"description": "Speaker settings", "description": "Speaker settings",
"icon": "volume_up", "icon": "volume_up",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, }, {
{
"id": "audioInput", "id": "audioInput",
"text": "Audio Input", "text": "Audio Input",
"description": "Microphone settings", "description": "Microphone settings",
"icon": "mic", "icon": "mic",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, }, {
{
"id": "volumeSlider", "id": "volumeSlider",
"text": "Volume Slider", "text": "Volume Slider",
"description": "Audio volume control", "description": "Audio volume control",
"icon": "volume_up", "icon": "volume_up",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, }, {
{
"id": "brightnessSlider", "id": "brightnessSlider",
"text": "Brightness Slider", "text": "Brightness Slider",
"description": "Display brightness control", "description": "Display brightness control",
@@ -90,24 +106,21 @@ QtObject {
"type": "slider", "type": "slider",
"enabled": DisplayService.brightnessAvailable, "enabled": DisplayService.brightnessAvailable,
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined "warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined
}, }, {
{
"id": "inputVolumeSlider", "id": "inputVolumeSlider",
"text": "Input Volume Slider", "text": "Input Volume Slider",
"description": "Microphone volume control", "description": "Microphone volume control",
"icon": "mic", "icon": "mic",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, }, {
{
"id": "battery", "id": "battery",
"text": "Battery", "text": "Battery",
"description": "Battery and power management", "description": "Battery and power management",
"icon": "battery_std", "icon": "battery_std",
"type": "action", "type": "action",
"enabled": true "enabled": true
}, }, {
{
"id": "diskUsage", "id": "diskUsage",
"text": "Disk Usage", "text": "Disk Usage",
"description": "Filesystem usage monitoring", "description": "Filesystem usage monitoring",
@@ -116,16 +129,68 @@ QtObject {
"enabled": DgopService.dgopAvailable, "enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined, "warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
"allowMultiple": true "allowMultiple": true
}, }, {
{
"id": "colorPicker", "id": "colorPicker",
"text": "Color Picker", "text": "Color Picker",
"description": "Choose colors from palette", "description": "Choose colors from palette",
"icon": "palette", "icon": "palette",
"type": "action", "type": "action",
"enabled": true "enabled": true
}, {
"id": "builtin_vpn",
"text": "VPN",
"description": "VPN connections",
"icon": "vpn_key",
"type": "builtin_plugin",
"enabled": VpnService.available,
"warning": !VpnService.available ? "VPN not available" : undefined,
"isBuiltinPlugin": true
}]
function getPluginWidgets() {
const plugins = []
const loadedPlugins = PluginService.getLoadedPlugins()
for (var i = 0; i < loadedPlugins.length; i++) {
const plugin = loadedPlugins[i]
if (plugin.type === "daemon") {
continue
}
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id]
if (!pluginComponent) {
continue
}
const tempInstance = pluginComponent.createObject(null)
if (!tempInstance) {
continue
}
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0
tempInstance.destroy()
if (!hasCCWidget) {
continue
}
plugins.push({
"id": "plugin_" + plugin.id,
"pluginId": plugin.id,
"text": plugin.name || "Plugin",
"description": plugin.description || "",
"icon": plugin.icon || "extension",
"type": "plugin",
"enabled": true,
"isPlugin": true
})
} }
]
return plugins
}
readonly property var baseWidgetDefinitions: coreWidgetDefinitions
function getWidgetForId(widgetId) { function getWidgetForId(widgetId) {
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId) return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)
@@ -154,4 +219,4 @@ QtObject {
function clearAll() { function clearAll() {
WidgetUtils.clearAll() WidgetUtils.clearAll()
} }
} }

View File

@@ -10,6 +10,12 @@ import qs.Widgets
PanelWindow { PanelWindow {
id: root id: root
readonly property string powerOptionsText: I18n.tr("Power Options")
readonly property string logOutText: I18n.tr("Log Out")
readonly property string suspendText: I18n.tr("Suspend")
readonly property string rebootText: I18n.tr("Reboot")
readonly property string powerOffText: I18n.tr("Power Off")
property bool powerMenuVisible: false property bool powerMenuVisible: false
signal powerActionRequested(string action, string title, string message) signal powerActionRequested(string action, string title, string message)
@@ -65,7 +71,7 @@ PanelWindow {
width: parent.width width: parent.width
StyledText { StyledText {
text: "Power Options" text: root.powerOptionsText
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -118,7 +124,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: "Log Out" text: root.logOutText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -168,7 +174,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: "Suspend" text: root.suspendText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -218,7 +224,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: "Reboot" text: root.rebootText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -268,7 +274,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: "Power Off" text: root.powerOffText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -69,9 +69,6 @@ Row {
valueOverride: actualVolumePercent valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
onIsDraggingChanged: {
AudioService.suppressOSD = isDragging
}
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
if (defaultSink) { if (defaultSink) {
defaultSink.audio.volume = newValue / 100.0 defaultSink.audio.volume = newValue / 100.0

View File

@@ -0,0 +1,51 @@
import QtQuick
import qs.Common
import qs.Widgets
StyledRect {
id: root
property string primaryMessage: ""
property string secondaryMessage: ""
radius: Theme.cornerRadius
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.1)
border.color: Theme.warning
border.width: 1
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
DankIcon {
name: "warning"
size: 16
color: Theme.warning
anchors.top: parent.top
anchors.topMargin: 2
}
Column {
width: parent.width - 16 - parent.spacing
spacing: Theme.spacingXS
StyledText {
width: parent.width
text: root.primaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
font.weight: Font.Medium
wrapMode: Text.WordWrap
}
StyledText {
width: parent.width
text: root.secondaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
visible: text.length > 0
}
}
}
}

View File

@@ -1,78 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
isActive: {
if (NetworkService.wifiToggling) {
return false
}
if (NetworkService.networkStatus === "ethernet") {
return true
}
if (NetworkService.networkStatus === "wifi") {
return true
}
return NetworkService.wifiEnabled
}
iconName: {
if (NetworkService.wifiToggling) {
return "sync"
}
if (NetworkService.networkStatus === "ethernet") {
return "settings_ethernet"
}
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalIcon
}
if (NetworkService.wifiEnabled) {
return "wifi_off"
}
return "wifi_off"
}
primaryText: {
if (NetworkService.wifiToggling) {
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
}
if (NetworkService.networkStatus === "ethernet") {
return "Ethernet"
}
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID) {
return NetworkService.currentWifiSSID
}
if (NetworkService.wifiEnabled) {
return "Not connected"
}
return "WiFi off"
}
secondaryText: {
if (NetworkService.wifiToggling) {
return "Please wait..."
}
if (NetworkService.networkStatus === "ethernet") {
return "Connected"
}
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
}
if (NetworkService.wifiEnabled) {
return "Select network"
}
return ""
}
onToggled: {
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
NetworkService.toggleWifiRadio()
}
}
}

View File

@@ -207,4 +207,121 @@ Item {
ctx.fill() ctx.fill()
} }
} }
Canvas {
id: barBorder
anchors.fill: parent
antialiasing: true
visible: SettingsData.dankBarBorderEnabled
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative
readonly property real correctWidth: root.width
readonly property real correctHeight: root.height
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
property bool borderEnabled: SettingsData.dankBarBorderEnabled
onWingChanged: requestPaint()
onRtChanged: requestPaint()
onBorderEnabledChanged: requestPaint()
onCorrectWidthChanged: requestPaint()
onCorrectHeightChanged: requestPaint()
onVisibleChanged: if (visible) requestPaint()
Component.onCompleted: requestPaint()
Connections {
target: barWindow
function on_DprChanged() { barBorder.requestPaint() }
}
Connections {
target: Theme
function onSecondaryChanged() { barBorder.requestPaint() }
}
Connections {
target: SettingsData
function onDankBarSpacingChanged() { barBorder.requestPaint() }
function onDankBarSquareCornersChanged() { barBorder.requestPaint() }
function onCornerRadiusChanged() { barBorder.requestPaint() }
}
onPaint: {
if (!borderEnabled) return
const ctx = getContext("2d")
const scale = barWindow._dpr
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
const R = barWindow.px(wing)
const RT = barWindow.px(rt)
const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
const spacing = SettingsData.dankBarSpacing
const hasEdgeGap = spacing > 0 || RT > 0
ctx.scale(scale, scale)
function drawTopBorder() {
ctx.beginPath()
if (!hasEdgeGap) {
ctx.moveTo(0, H)
ctx.lineTo(W, H)
} else {
ctx.moveTo(RT, 0)
ctx.lineTo(W - RT, 0)
ctx.arcTo(W, 0, W, RT, RT)
ctx.lineTo(W, H)
if (R > 0) {
ctx.lineTo(W, H + R)
ctx.arc(W - R, H + R, R, 0, -Math.PI / 2, true)
ctx.lineTo(R, H)
ctx.arc(R, H + R, R, -Math.PI / 2, -Math.PI, true)
ctx.lineTo(0, H + R)
} else {
ctx.lineTo(W, H - RT)
ctx.arcTo(W, H, W - RT, H, RT)
ctx.lineTo(RT, H)
ctx.arcTo(0, H, 0, H - RT, RT)
}
ctx.lineTo(0, RT)
ctx.arcTo(0, 0, RT, 0, RT)
}
ctx.closePath()
}
ctx.reset()
ctx.clearRect(0, 0, W, H_raw)
ctx.save()
if (isBottom) {
ctx.translate(W, H_raw)
ctx.rotate(Math.PI)
} else if (isLeft) {
ctx.translate(0, W)
ctx.rotate(-Math.PI / 2)
} else if (isRight) {
ctx.translate(H_raw, 0)
ctx.rotate(Math.PI / 2)
}
drawTopBorder()
ctx.restore()
ctx.lineWidth = 1
ctx.strokeStyle = Theme.secondary
ctx.stroke()
}
}
} }

View File

@@ -308,8 +308,10 @@ Item {
} }
// For plugin components, get from PluginService // For plugin components, get from PluginService
let pluginMap = PluginService.getWidgetComponents() var parts = widgetId.split(":")
return pluginMap[widgetId] || null var pluginId = parts[0]
let pluginComponents = PluginService.getWidgetComponents()
return pluginComponents[pluginId] || null
} }
height: parent.height height: parent.height
@@ -397,12 +399,26 @@ Item {
// Inject PluginService for plugin widgets // Inject PluginService for plugin widgets
if (item.pluginService !== undefined) { if (item.pluginService !== undefined) {
var parts = model.widgetId.split(":")
var pluginId = parts[0]
var variantId = parts.length > 1 ? parts[1] : null
if (item.pluginId !== undefined) { if (item.pluginId !== undefined) {
item.pluginId = model.widgetId item.pluginId = pluginId
}
if (item.variantId !== undefined) {
item.variantId = variantId
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
} }
item.pluginService = PluginService item.pluginService = PluginService
} }
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
layoutTimer.restart() layoutTimer.restart()
} }
@@ -426,8 +442,8 @@ Item {
// Force refresh of component lookups // Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) { for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i) var item = centerRepeater.itemAt(i)
if (item && item.widgetId === pluginId) { if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(pluginId) item.sourceComponent = root.getWidgetComponent(item.widgetId)
} }
} }
} }
@@ -435,8 +451,8 @@ Item {
// Force refresh of component lookups // Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) { for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i) var item = centerRepeater.itemAt(i)
if (item && item.widgetId === pluginId) { if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(pluginId) item.sourceComponent = root.getWidgetComponent(item.widgetId)
} }
} }
} }

View File

@@ -74,6 +74,8 @@ Item {
implicitWidth: isVertical ? px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0)) : 0 implicitWidth: isVertical ? px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0)) : 0
color: "transparent" color: "transparent"
property var nativeInhibitor: null
Component.onCompleted: { Component.onCompleted: {
const fonts = Qt.fontFamilies() const fonts = Qt.fontFamilies()
if (fonts.indexOf("Material Symbols Rounded") === -1) { if (fonts.indexOf("Material Symbols Rounded") === -1) {
@@ -93,6 +95,19 @@ Item {
updateGpuTempConfig() updateGpuTempConfig()
Qt.callLater(() => Qt.callLater(forceWidgetRefresh)) Qt.callLater(() => Qt.callLater(forceWidgetRefresh))
inhibitorInitTimer.start()
}
Timer {
id: inhibitorInitTimer
interval: 300
repeat: false
onTriggered: {
if (SessionService.nativeInhibitorAvailable) {
createNativeInhibitor()
}
}
} }
Connections { Connections {
@@ -124,6 +139,38 @@ Item {
DgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled DgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled
} }
function createNativeInhibitor() {
if (!SessionService.nativeInhibitorAvailable) {
return
}
try {
const qmlString = `
import QtQuick
import Quickshell.Wayland
IdleInhibitor {
enabled: false
}
`
nativeInhibitor = Qt.createQmlObject(qmlString, barWindow, "DankBar.NativeInhibitor")
nativeInhibitor.window = barWindow
nativeInhibitor.enabled = Qt.binding(() => SessionService.idleInhibited)
nativeInhibitor.enabledChanged.connect(function() {
console.log("DankBar: Native inhibitor enabled changed to:", nativeInhibitor.enabled)
if (SessionService.idleInhibited !== nativeInhibitor.enabled) {
SessionService.idleInhibited = nativeInhibitor.enabled
SessionService.inhibitorChanged()
}
})
console.log("DankBar: Created native Wayland IdleInhibitor for", barWindow.screenName)
} catch (e) {
console.warn("DankBar: Failed to create native IdleInhibitor:", e)
nativeInhibitor = null
}
}
Connections { Connections {
function onDankBarLeftWidgetsChanged() { function onDankBarLeftWidgetsChanged() {
barWindow.updateGpuTempConfig() barWindow.updateGpuTempConfig()
@@ -189,8 +236,10 @@ Item {
readonly property int barThickness: px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) readonly property int barThickness: px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing)
readonly property bool showing: SettingsData.dankBarVisible && (topBarCore.reveal readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview
|| (CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview) readonly property bool effectiveVisible: SettingsData.dankBarVisible || inOverviewWithShow
readonly property bool showing: effectiveVisible && (topBarCore.reveal
|| inOverviewWithShow
|| !topBarCore.autoHide) || !topBarCore.autoHide)
readonly property int maskThickness: showing ? barThickness : 1 readonly property int maskThickness: showing ? barThickness : 1
@@ -789,6 +838,7 @@ Item {
return processListPopoutLoader.item return processListPopoutLoader.item
} }
parentScreen: barWindow.screen parentScreen: barWindow.screen
widgetData: parent.widgetData
toggleProcessList: () => { toggleProcessList: () => {
processListPopoutLoader.active = true processListPopoutLoader.active = true
return processListPopoutLoader.item?.toggle() return processListPopoutLoader.item?.toggle()
@@ -808,6 +858,7 @@ Item {
return processListPopoutLoader.item return processListPopoutLoader.item
} }
parentScreen: barWindow.screen parentScreen: barWindow.screen
widgetData: parent.widgetData
toggleProcessList: () => { toggleProcessList: () => {
processListPopoutLoader.active = true processListPopoutLoader.active = true
return processListPopoutLoader.item?.toggle() return processListPopoutLoader.item?.toggle()
@@ -837,6 +888,7 @@ Item {
return processListPopoutLoader.item return processListPopoutLoader.item
} }
parentScreen: barWindow.screen parentScreen: barWindow.screen
widgetData: parent.widgetData
toggleProcessList: () => { toggleProcessList: () => {
processListPopoutLoader.active = true processListPopoutLoader.active = true
return processListPopoutLoader.item?.toggle() return processListPopoutLoader.item?.toggle()

View File

@@ -311,7 +311,7 @@ DankPopout {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Health" text: I18n.tr("Health")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -346,7 +346,7 @@ DankPopout {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Capacity" text: I18n.tr("Capacity")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -415,7 +415,7 @@ DankPopout {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: "Power Profile Degradation" text: I18n.tr("Power Profile Degradation")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.error color: Theme.error
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -13,6 +13,10 @@ import qs.Widgets
DankPopout { DankPopout {
id: root id: root
Ref {
service: VpnService
}
property var triggerScreen: null property var triggerScreen: null
function setTriggerPosition(x, y, width, section, screen) { function setTriggerPosition(x, y, width, section, screen) {
@@ -96,7 +100,7 @@ DankPopout {
height: 32 height: 32
StyledText { StyledText {
text: "VPN Connections" text: I18n.tr("VPN Connections")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -206,7 +210,7 @@ DankPopout {
} }
StyledText { StyledText {
text: "Disconnect" text: I18n.tr("Disconnect")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -262,14 +266,14 @@ DankPopout {
} }
StyledText { StyledText {
text: "No VPN profiles found" text: I18n.tr("No VPN profiles found")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: "Add a VPN in NetworkManager" text: I18n.tr("Add a VPN in NetworkManager")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -65,6 +65,14 @@ Loader {
restoreMode: Binding.RestoreNone restoreMode: Binding.RestoreNone
} }
Binding {
target: root.item
when: root.item && "widgetData" in root.item
property: "widgetData"
value: root.widgetData
restoreMode: Binding.RestoreNone
}
onLoaded: { onLoaded: {
if (item) { if (item) {
contentItemReady(item) contentItemReady(item)
@@ -79,11 +87,25 @@ Loader {
} }
if (item.pluginService !== undefined) { if (item.pluginService !== undefined) {
var parts = widgetId.split(":")
var pluginId = parts[0]
var variantId = parts.length > 1 ? parts[1] : null
if (item.pluginId !== undefined) { if (item.pluginId !== undefined) {
item.pluginId = widgetId item.pluginId = pluginId
}
if (item.variantId !== undefined) {
item.variantId = variantId
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
} }
item.pluginService = PluginService item.pluginService = PluginService
} }
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
} }
} }
@@ -122,8 +144,11 @@ Loader {
return componentMap[widgetId] return componentMap[widgetId]
} }
var parts = widgetId.split(":")
var pluginId = parts[0]
let pluginMap = PluginService.getWidgetComponents() let pluginMap = PluginService.getWidgetComponents()
return pluginMap[widgetId] || null return pluginMap[pluginId] || null
} }
function getWidgetVisible(widgetId, dgopAvailable) { function getWidgetVisible(widgetId, dgopAvailable) {

View File

@@ -40,7 +40,7 @@ Rectangle {
DankIcon { DankIcon {
name: BatteryService.getBatteryIcon() name: BatteryService.getBatteryIcon()
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (!BatteryService.batteryAvailable) { if (!BatteryService.batteryAvailable) {
return Theme.surfaceText return Theme.surfaceText
@@ -61,7 +61,7 @@ Rectangle {
StyledText { StyledText {
text: BatteryService.batteryLevel.toString() text: BatteryService.batteryLevel.toString()
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -77,7 +77,7 @@ Rectangle {
DankIcon { DankIcon {
name: BatteryService.getBatteryIcon() name: BatteryService.getBatteryIcon()
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: { color: {
if (!BatteryService.batteryAvailable) { if (!BatteryService.batteryAvailable) {
return Theme.surfaceText; return Theme.surfaceText;
@@ -98,7 +98,7 @@ Rectangle {
StyledText { StyledText {
text: `${BatteryService.batteryLevel}%` text: `${BatteryService.batteryLevel}%`
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -50,7 +50,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
name: "content_paste" name: "content_paste"
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
} }
} }

View File

@@ -50,7 +50,7 @@ Rectangle {
return String(display).padStart(2, '0').charAt(0) return String(display).padStart(2, '0').charAt(0)
} }
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
width: 9 width: 9
@@ -67,7 +67,7 @@ Rectangle {
return String(display).padStart(2, '0').charAt(1) return String(display).padStart(2, '0').charAt(1)
} }
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
width: 9 width: 9
@@ -81,7 +81,7 @@ Rectangle {
StyledText { StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0) text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
width: 9 width: 9
@@ -90,7 +90,7 @@ Rectangle {
StyledText { StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1) text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
width: 9 width: 9
@@ -123,7 +123,7 @@ Rectangle {
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0') const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
return value.charAt(0) return value.charAt(0)
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.primary color: Theme.primary
font.weight: Font.Light font.weight: Font.Light
width: 9 width: 9
@@ -138,7 +138,7 @@ Rectangle {
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0') const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
return value.charAt(1) return value.charAt(1)
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.primary color: Theme.primary
font.weight: Font.Light font.weight: Font.Light
width: 9 width: 9
@@ -158,7 +158,7 @@ Rectangle {
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0') const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(0) return value.charAt(0)
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.primary color: Theme.primary
font.weight: Font.Light font.weight: Font.Light
width: 9 width: 9
@@ -173,7 +173,7 @@ Rectangle {
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0') const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(1) return value.charAt(1)
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.primary color: Theme.primary
font.weight: Font.Light font.weight: Font.Light
width: 9 width: 9
@@ -194,7 +194,7 @@ Rectangle {
const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP" const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP"
return systemClock?.date?.toLocaleTimeString(Qt.locale(), format) return systemClock?.date?.toLocaleTimeString(Qt.locale(), format)
} }
font.pixelSize: Theme.fontSizeMedium - 1 font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -215,7 +215,7 @@ Rectangle {
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d") return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
} }
font.pixelSize: Theme.fontSizeMedium - 1 font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: !SettingsData.clockCompactMode visible: !SettingsData.clockCompactMode

View File

@@ -34,7 +34,7 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
name: "palette" name: "palette"
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: colorPickerArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText color: colorPickerArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
} }

View File

@@ -52,7 +52,7 @@ Rectangle {
return NetworkService.wifiSignalIcon return NetworkService.wifiSignalIcon
} }
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (NetworkService.wifiToggling) { if (NetworkService.wifiToggling) {
return Theme.primary return Theme.primary
@@ -61,12 +61,12 @@ Rectangle {
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
} }
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: root.showNetworkIcon visible: root.showNetworkIcon && NetworkService.networkAvailable
} }
DankIcon { DankIcon {
name: "bluetooth" name: "bluetooth"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
@@ -94,7 +94,7 @@ Rectangle {
} }
return "volume_up" return "volume_up"
} }
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -124,7 +124,7 @@ Rectangle {
DankIcon { DankIcon {
name: "settings" name: "settings"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
@@ -151,7 +151,7 @@ Rectangle {
return NetworkService.wifiSignalIcon; return NetworkService.wifiSignalIcon;
} }
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (NetworkService.wifiToggling) { if (NetworkService.wifiToggling) {
return Theme.primary; return Theme.primary;
@@ -160,7 +160,7 @@ Rectangle {
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton; return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: root.showNetworkIcon visible: root.showNetworkIcon && NetworkService.networkAvailable
} }
@@ -169,7 +169,7 @@ Rectangle {
id: bluetoothIcon id: bluetoothIcon
name: "bluetooth" name: "bluetooth"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
@@ -197,7 +197,7 @@ Rectangle {
} }
return "volume_up"; return "volume_up";
} }
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -230,7 +230,7 @@ Rectangle {
DankIcon { DankIcon {
name: "mic" name: "mic"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: false // TODO: Add mic detection visible: false // TODO: Add mic detection
@@ -239,7 +239,7 @@ Rectangle {
// Fallback settings icon when all other icons are hidden // Fallback settings icon when all other icons are hidden
DankIcon { DankIcon {
name: "settings" name: "settings"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon

View File

@@ -17,6 +17,8 @@ Rectangle {
property var parentScreen: null property var parentScreen: null
property real barThickness: 48 property real barThickness: 48
property real widgetThickness: 30 property real widgetThickness: 30
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
width: isVertical ? widgetThickness : (cpuContent.implicitWidth + horizontalPadding * 2) width: isVertical ? widgetThickness : (cpuContent.implicitWidth + horizontalPadding * 2)
@@ -66,7 +68,7 @@ Rectangle {
DankIcon { DankIcon {
name: "memory" name: "memory"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (DgopService.cpuUsage > 80) { if (DgopService.cpuUsage > 80) {
return Theme.tempDanger; return Theme.tempDanger;
@@ -89,7 +91,7 @@ Rectangle {
return DgopService.cpuUsage.toFixed(0); return DgopService.cpuUsage.toFixed(0);
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -104,7 +106,7 @@ Rectangle {
DankIcon { DankIcon {
name: "memory" name: "memory"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (DgopService.cpuUsage > 80) { if (DgopService.cpuUsage > 80) {
return Theme.tempDanger; return Theme.tempDanger;
@@ -127,7 +129,7 @@ Rectangle {
return DgopService.cpuUsage.toFixed(0) + "%"; return DgopService.cpuUsage.toFixed(0) + "%";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -136,12 +138,12 @@ Rectangle {
StyledTextMetrics { StyledTextMetrics {
id: cpuBaseline id: cpuBaseline
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
text: "100%" text: "100%"
} }
width: Math.max(cpuBaseline.width, paintedWidth) width: root.minimumWidth ? Math.max(cpuBaseline.width, paintedWidth) : paintedWidth
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {

View File

@@ -17,6 +17,8 @@ Rectangle {
property var parentScreen: null property var parentScreen: null
property real barThickness: 48 property real barThickness: 48
property real widgetThickness: 30 property real widgetThickness: 30
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
width: isVertical ? widgetThickness : (cpuTempContent.implicitWidth + horizontalPadding * 2) width: isVertical ? widgetThickness : (cpuTempContent.implicitWidth + horizontalPadding * 2)
@@ -65,8 +67,8 @@ Rectangle {
spacing: 1 spacing: 1
DankIcon { DankIcon {
name: "memory" name: "device_thermostat"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (DgopService.cpuTemperature > 85) { if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger; return Theme.tempDanger;
@@ -89,7 +91,7 @@ Rectangle {
return Math.round(DgopService.cpuTemperature).toString(); return Math.round(DgopService.cpuTemperature).toString();
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -103,8 +105,8 @@ Rectangle {
spacing: 3 spacing: 3
DankIcon { DankIcon {
name: "memory" name: "device_thermostat"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (DgopService.cpuTemperature > 85) { if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger; return Theme.tempDanger;
@@ -127,7 +129,7 @@ Rectangle {
return Math.round(DgopService.cpuTemperature) + "°"; return Math.round(DgopService.cpuTemperature) + "°";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -136,12 +138,12 @@ Rectangle {
StyledTextMetrics { StyledTextMetrics {
id: tempBaseline id: tempBaseline
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
text: "100°" text: "100°"
} }
width: Math.max(tempBaseline.width, paintedWidth) width: root.minimumWidth ? Math.max(tempBaseline.width, paintedWidth) : paintedWidth
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {

View File

@@ -12,6 +12,7 @@ Rectangle {
property var widgetData: null property var widgetData: null
property var parentScreen: null property var parentScreen: null
property real widgetThickness: 30 property real widgetThickness: 30
property real barThickness: 48
property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/" property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/"
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
@@ -144,7 +145,7 @@ Rectangle {
DankIcon { DankIcon {
name: "storage" name: "storage"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (root.diskUsagePercent > 90) { if (root.diskUsagePercent > 90) {
return Theme.tempDanger return Theme.tempDanger
@@ -164,7 +165,7 @@ Rectangle {
} }
return root.diskUsagePercent.toFixed(0) return root.diskUsagePercent.toFixed(0)
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -179,7 +180,7 @@ Rectangle {
DankIcon { DankIcon {
name: "storage" name: "storage"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (root.diskUsagePercent > 90) { if (root.diskUsagePercent > 90) {
return Theme.tempDanger return Theme.tempDanger
@@ -199,7 +200,7 @@ Rectangle {
} }
return root.selectedMount.mount return root.selectedMount.mount
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -214,7 +215,7 @@ Rectangle {
} }
return root.diskUsagePercent.toFixed(0) + "%" return root.diskUsagePercent.toFixed(0) + "%"
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -223,7 +224,7 @@ Rectangle {
StyledTextMetrics { StyledTextMetrics {
id: diskBaseline id: diskBaseline
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
text: "100%" text: "100%"
} }

View File

@@ -16,6 +16,7 @@ Rectangle {
property bool compactMode: SettingsData.focusedWindowCompactMode property bool compactMode: SettingsData.focusedWindowCompactMode
property int availableWidth: 400 property int availableWidth: 400
property real widgetThickness: 30 property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
readonly property int baseWidth: contentRow.implicitWidth + horizontalPadding * 2 readonly property int baseWidth: contentRow.implicitWidth + horizontalPadding * 2
readonly property int maxNormalWidth: 456 readonly property int maxNormalWidth: 456
@@ -171,7 +172,7 @@ Rectangle {
const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId); const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId);
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId; return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -183,7 +184,7 @@ Rectangle {
StyledText { StyledText {
text: "•" text: "•"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.outlineButton color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: !compactMode && appText.text && titleText.text visible: !compactMode && appText.text && titleText.text
@@ -209,7 +210,7 @@ Rectangle {
return title; return title;
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -19,6 +19,7 @@ Rectangle {
property real barThickness: 48 property real barThickness: 48
property real widgetThickness: 30 property real widgetThickness: 30
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0 property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
property real displayTemp: { property real displayTemp: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) { if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
@@ -134,7 +135,7 @@ Rectangle {
DankIcon { DankIcon {
name: "auto_awesome_mosaic" name: "auto_awesome_mosaic"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (root.displayTemp > 80) { if (root.displayTemp > 80) {
return Theme.tempDanger; return Theme.tempDanger;
@@ -157,7 +158,7 @@ Rectangle {
return Math.round(root.displayTemp).toString(); return Math.round(root.displayTemp).toString();
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -172,7 +173,7 @@ Rectangle {
DankIcon { DankIcon {
name: "auto_awesome_mosaic" name: "auto_awesome_mosaic"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (root.displayTemp > 80) { if (root.displayTemp > 80) {
return Theme.tempDanger; return Theme.tempDanger;
@@ -195,7 +196,7 @@ Rectangle {
return Math.round(root.displayTemp) + "°"; return Math.round(root.displayTemp) + "°";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -204,12 +205,12 @@ Rectangle {
StyledTextMetrics { StyledTextMetrics {
id: gpuTempBaseline id: gpuTempBaseline
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
text: "100°" text: "100°"
} }
width: Math.max(gpuTempBaseline.width, paintedWidth) width: root.minimumWidth ? Math.max(gpuTempBaseline.width, paintedWidth) : paintedWidth
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {

View File

@@ -33,7 +33,7 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle" name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: Theme.surfaceText color: Theme.surfaceText
} }

View File

@@ -13,6 +13,7 @@ Rectangle {
property bool isVertical: axis?.isVertical ?? false property bool isVertical: axis?.isVertical ?? false
property var axis: null property var axis: null
property real widgetThickness: 30 property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
property string currentLayout: "" property string currentLayout: ""
property string hyprlandKeyboard: "" property string hyprlandKeyboard: ""
@@ -59,7 +60,7 @@ Rectangle {
DankIcon { DankIcon {
name: "keyboard" name: "keyboard"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
@@ -73,7 +74,7 @@ Rectangle {
} }
return currentLayout.substring(0, 2).toUpperCase() return currentLayout.substring(0, 2).toUpperCase()
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -89,7 +90,7 @@ Rectangle {
StyledText { StyledText {
text: currentLayout text: currentLayout
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }

View File

@@ -1,4 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -53,22 +56,65 @@ Item {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }
SystemLogo {
visible: SettingsData.useOSLogo
anchors.centerIn: parent
width: Theme.iconSize - 3
height: Theme.iconSize - 3
colorOverride: SettingsData.osLogoColorOverride
brightnessOverride: SettingsData.osLogoBrightness
contrastOverride: SettingsData.osLogoContrast
}
DankIcon { DankIcon {
visible: !SettingsData.useOSLogo visible: SettingsData.launcherLogoMode === "apps"
anchors.centerIn: parent anchors.centerIn: parent
name: "apps" name: "apps"
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: Theme.surfaceText color: Theme.surfaceText
} }
SystemLogo {
visible: SettingsData.launcherLogoMode === "os"
anchors.centerIn: parent
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
colorOverride: Theme.effectiveLogoColor
brightnessOverride: SettingsData.launcherLogoBrightness
contrastOverride: SettingsData.launcherLogoContrast
}
IconImage {
visible: SettingsData.launcherLogoMode === "compositor"
anchors.centerIn: parent
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
smooth: true
asynchronous: true
source: {
if (CompositorService.isNiri) {
return "file://" + Theme.shellDir + "/assets/niri.svg"
} else if (CompositorService.isHyprland) {
return "file://" + Theme.shellDir + "/assets/hyprland.svg"
}
return ""
}
layer.enabled: Theme.effectiveLogoColor !== ""
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.effectiveLogoColor
brightness: SettingsData.launcherLogoBrightness
contrast: SettingsData.launcherLogoContrast
}
}
IconImage {
visible: SettingsData.launcherLogoMode === "custom" && SettingsData.launcherLogoCustomPath !== ""
anchors.centerIn: parent
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
smooth: true
asynchronous: true
source: SettingsData.launcherLogoCustomPath ? "file://" + SettingsData.launcherLogoCustomPath.replace("file://", "") : ""
layer.enabled: Theme.effectiveLogoColor !== ""
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.effectiveLogoColor
brightness: SettingsData.launcherLogoBrightness
contrast: SettingsData.launcherLogoContrast
}
}
} }
} }

View File

@@ -267,7 +267,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: textContainer.displayText text: textContainer.displayText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
wrapMode: Text.NoWrap wrapMode: Text.NoWrap

View File

@@ -14,6 +14,7 @@ Rectangle {
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2 readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
readonly property int maxNormalWidth: 456 readonly property int maxNormalWidth: 456
property real widgetThickness: 30 property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
function formatNetworkSpeed(bytesPerSec) { function formatNetworkSpeed(bytesPerSec) {
@@ -63,7 +64,7 @@ Rectangle {
DankIcon { DankIcon {
name: "network_check" name: "network_check"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
@@ -75,7 +76,7 @@ Rectangle {
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K" if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
return (rate / (1024 * 1024)).toFixed(0) + "M" return (rate / (1024 * 1024)).toFixed(0) + "M"
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.info color: Theme.info
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -88,7 +89,7 @@ Rectangle {
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K" if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
return (rate / (1024 * 1024)).toFixed(0) + "M" return (rate / (1024 * 1024)).toFixed(0) + "M"
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.error color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -104,7 +105,7 @@ Rectangle {
DankIcon { DankIcon {
name: "network_check" name: "network_check"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -115,13 +116,13 @@ Rectangle {
StyledText { StyledText {
text: "↓" text: "↓"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.info color: Theme.info
} }
StyledText { StyledText {
text: DgopService.networkRxRate > 0 ? formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s" text: DgopService.networkRxRate > 0 ? formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -131,7 +132,7 @@ Rectangle {
StyledTextMetrics { StyledTextMetrics {
id: rxBaseline id: rxBaseline
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
text: "88.8 MB/s" text: "88.8 MB/s"
} }
@@ -154,13 +155,13 @@ Rectangle {
StyledText { StyledText {
text: "↑" text: "↑"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.error color: Theme.error
} }
StyledText { StyledText {
text: DgopService.networkTxRate > 0 ? formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s" text: DgopService.networkTxRate > 0 ? formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -170,7 +171,7 @@ Rectangle {
StyledTextMetrics { StyledTextMetrics {
id: txBaseline id: txBaseline
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
text: "88.8 MB/s" text: "88.8 MB/s"
} }

View File

@@ -60,7 +60,7 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
name: "assignment" name: "assignment"
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: notepadArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText color: notepadArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
} }

View File

@@ -57,7 +57,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
name: SessionData.doNotDisturb ? "notifications_off" : "notifications" name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: SessionData.doNotDisturb ? Theme.error : (notificationArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText) color: SessionData.doNotDisturb ? Theme.error : (notificationArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText)
} }

View File

@@ -13,6 +13,7 @@ Rectangle {
property var popupTarget: null property var popupTarget: null
property var parentScreen: null property var parentScreen: null
property real widgetThickness: 30 property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
readonly property bool hasActivePrivacy: PrivacyService.anyPrivacyActive readonly property bool hasActivePrivacy: PrivacyService.anyPrivacyActive
readonly property int activeCount: PrivacyService.microphoneActive + PrivacyService.cameraActive + PrivacyService.screensharingActive readonly property int activeCount: PrivacyService.microphoneActive + PrivacyService.cameraActive + PrivacyService.screensharingActive
@@ -198,7 +199,7 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
text: PrivacyService.getPrivacySummary() text: PrivacyService.getPrivacySummary()
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
} }

View File

@@ -17,6 +17,8 @@ Rectangle {
property var parentScreen: null property var parentScreen: null
property real barThickness: 48 property real barThickness: 48
property real widgetThickness: 30 property real widgetThickness: 30
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
width: isVertical ? widgetThickness : (ramContent.implicitWidth + horizontalPadding * 2) width: isVertical ? widgetThickness : (ramContent.implicitWidth + horizontalPadding * 2)
@@ -30,6 +32,7 @@ Rectangle {
const baseColor = ramArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor; const baseColor = ramArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }
Component.onCompleted: { Component.onCompleted: {
DgopService.addRef(["memory"]); DgopService.addRef(["memory"]);
} }
@@ -66,7 +69,7 @@ Rectangle {
DankIcon { DankIcon {
name: "developer_board" name: "developer_board"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (DgopService.memoryUsage > 90) { if (DgopService.memoryUsage > 90) {
return Theme.tempDanger; return Theme.tempDanger;
@@ -89,7 +92,7 @@ Rectangle {
return DgopService.memoryUsage.toFixed(0); return DgopService.memoryUsage.toFixed(0);
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -104,7 +107,7 @@ Rectangle {
DankIcon { DankIcon {
name: "developer_board" name: "developer_board"
size: Theme.iconSize - 8 size: Theme.barIconSize(barThickness)
color: { color: {
if (DgopService.memoryUsage > 90) { if (DgopService.memoryUsage > 90) {
return Theme.tempDanger; return Theme.tempDanger;
@@ -127,7 +130,7 @@ Rectangle {
return DgopService.memoryUsage.toFixed(0) + "%"; return DgopService.memoryUsage.toFixed(0) + "%";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -136,12 +139,12 @@ Rectangle {
StyledTextMetrics { StyledTextMetrics {
id: ramBaseline id: ramBaseline
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
text: "100%" text: "100%"
} }
width: Math.max(ramBaseline.width, paintedWidth) width: root.minimumWidth ? Math.max(ramBaseline.width, paintedWidth) : paintedWidth
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {

View File

@@ -17,6 +17,7 @@ Rectangle {
property var hoveredItem: null property var hoveredItem: null
property var topBar: null property var topBar: null
property real widgetThickness: 30 property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
property Item windowRoot: (Window.window ? Window.window.contentItem : null) property Item windowRoot: (Window.window ? Window.window.contentItem : null)
readonly property var sortedToplevels: { readonly property var sortedToplevels: {
@@ -272,7 +273,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: !SettingsData.runningAppsCompactMode visible: !SettingsData.runningAppsCompactMode
text: windowTitle text: windowTitle
font.pixelSize: Theme.fontSizeMedium - 1 font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
@@ -463,7 +464,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: !SettingsData.runningAppsCompactMode visible: !SettingsData.runningAppsCompactMode
text: windowTitle text: windowTitle
font.pixelSize: Theme.fontSizeMedium - 1 font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
@@ -610,7 +611,7 @@ Rectangle {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "Close" text: I18n.tr("Close")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal

View File

@@ -66,7 +66,10 @@ Rectangle {
const name = split[0]; const name = split[0];
const path = split[1]; const path = split[1];
const fileName = name.substring(name.lastIndexOf("/") + 1); let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
}
return `file://${path}/${fileName}`; return `file://${path}/${fileName}`;
} }
if (icon.startsWith("/") && !icon.startsWith("file://")) { if (icon.startsWith("/") && !icon.startsWith("file://")) {
@@ -445,7 +448,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "Back" text: I18n.tr("Back")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -43,7 +43,7 @@ Rectangle {
if (hasUpdates) return "system_update_alt"; if (hasUpdates) return "system_update_alt";
return "check_circle"; return "check_circle";
} }
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: { color: {
if (SystemUpdateService.hasError) return Theme.error; if (SystemUpdateService.hasError) return Theme.error;
if (hasUpdates) return Theme.primary; if (hasUpdates) return Theme.primary;
@@ -97,7 +97,7 @@ Rectangle {
if (hasUpdates) return "system_update_alt"; if (hasUpdates) return "system_update_alt";
return "check_circle"; return "check_circle";
} }
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: { color: {
if (SystemUpdateService.hasError) return Theme.error; if (SystemUpdateService.hasError) return Theme.error;
if (hasUpdates) return Theme.primary; if (hasUpdates) return Theme.primary;
@@ -127,7 +127,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: SystemUpdateService.updateCount.toString() text: SystemUpdateService.updateCount.toString()
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
visible: hasUpdates && !isChecking visible: hasUpdates && !isChecking

View File

@@ -7,6 +7,10 @@ import qs.Widgets
Rectangle { Rectangle {
id: root id: root
Ref {
service: VpnService
}
property bool isVertical: axis?.isVertical ?? false property bool isVertical: axis?.isVertical ?? false
property var axis: null property var axis: null
property int widgetThickness: 28 property int widgetThickness: 28
@@ -34,7 +38,7 @@ Rectangle {
id: icon id: icon
name: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off") name: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
size: Theme.iconSize - 6 size: Theme.barIconSize(barThickness, -4)
color: VpnService.connected ? Theme.primary : Theme.surfaceText color: VpnService.connected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent anchors.centerIn: parent
} }

View File

@@ -42,7 +42,7 @@ Rectangle {
DankIcon { DankIcon {
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode) name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: Theme.iconSize - 4 size: Theme.barIconSize(barThickness, -6)
color: Theme.primary color: Theme.primary
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
@@ -55,7 +55,7 @@ Rectangle {
} }
return temp; return temp;
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
@@ -70,7 +70,7 @@ Rectangle {
DankIcon { DankIcon {
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode) name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: Theme.iconSize - 4 size: Theme.barIconSize(barThickness, -6)
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -84,7 +84,7 @@ Rectangle {
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C"); return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C");
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }

View File

@@ -14,6 +14,7 @@ Rectangle {
property var axis: null property var axis: null
property string screenName: "" property string screenName: ""
property real widgetHeight: 30 property real widgetHeight: 30
property real barThickness: 48
property int currentWorkspace: { property int currentWorkspace: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
return getNiriActiveWorkspace() return getNiriActiveWorkspace()
@@ -29,6 +30,8 @@ Rectangle {
} }
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
const baseList = getHyprlandWorkspaces() const baseList = getHyprlandWorkspaces()
// Filter out special:scratch_term
const filteredList = baseList.filter(ws => ws.name !== "special:scratch_term" && ws.id !== -98)
return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList
} }
return [1] return [1]
@@ -293,19 +296,30 @@ Rectangle {
property bool isHovered: mouseArea.containsMouse property bool isHovered: mouseArea.containsMouse
property var loadedWorkspaceData: null property var loadedWorkspaceData: null
property bool loadedIsUrgent: false
property bool isUrgent: {
if (CompositorService.isHyprland) {
return modelData?.urgent ?? false
}
if (CompositorService.isNiri) {
return loadedIsUrgent
}
return false
}
property var loadedIconData: null property var loadedIconData: null
property bool loadedHasIcon: false property bool loadedHasIcon: false
property var loadedIcons: [] property var loadedIcons: []
Timer { Timer {
id: dataUpdateTimer id: dataUpdateTimer
interval: 50 // Defer data calculation by 50ms interval: 50
onTriggered: { onTriggered: {
if (isPlaceholder) { if (isPlaceholder) {
delegateRoot.loadedWorkspaceData = null delegateRoot.loadedWorkspaceData = null
delegateRoot.loadedIconData = null delegateRoot.loadedIconData = null
delegateRoot.loadedHasIcon = false delegateRoot.loadedHasIcon = false
delegateRoot.loadedIcons = [] delegateRoot.loadedIcons = []
delegateRoot.loadedIsUrgent = false
return return
} }
@@ -316,6 +330,7 @@ Rectangle {
wsData = modelData; wsData = modelData;
} }
delegateRoot.loadedWorkspaceData = wsData; delegateRoot.loadedWorkspaceData = wsData;
delegateRoot.loadedIsUrgent = wsData?.is_urgent ?? false;
var icData = null; var icData = null;
if (wsData?.name) { if (wsData?.name) {
@@ -363,7 +378,10 @@ Rectangle {
} }
} }
radius: Math.min(width, height) / 2 radius: Math.min(width, height) / 2
color: isActive ? Theme.primary : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
border.width: isUrgent && !isActive ? 2 : 0
border.color: isUrgent && !isActive ? Theme.error : Theme.withAlpha(Theme.error, 0)
Behavior on width { Behavior on width {
enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3) enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
@@ -381,12 +399,26 @@ Rectangle {
} }
} }
Behavior on color {
ColorAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on border.width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.centerIn: parent anchors.centerIn: parent
width: root.isVertical ? parent.width + Theme.spacingXL : parent.width width: root.isVertical ? parent.width + Theme.spacingXL : parent.width
height: root.isVerical ? parent.height : parent.height + Theme.spacingXL height: root.isVertical ? parent.height : parent.height + Theme.spacingXL
hoverEnabled: !isPlaceholder hoverEnabled: !isPlaceholder
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
enabled: !isPlaceholder enabled: !isPlaceholder
@@ -580,7 +612,7 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
text: loadedIconData ? loadedIconData.value : "" // NULL CHECK text: loadedIconData ? loadedIconData.value : "" // NULL CHECK
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : Theme.surfaceTextMedium color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : Theme.surfaceTextMedium
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
} }
} }
@@ -601,8 +633,8 @@ Rectangle {
} }
return CompositorService.isHyprland ? (modelData?.id || "") : (modelData - 1); return CompositorService.isHyprland ? (modelData?.id || "") : (modelData - 1);
} }
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.barTextSize(barThickness)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
} }
} }
@@ -619,6 +651,7 @@ Rectangle {
target: NiriService target: NiriService
enabled: CompositorService.isNiri enabled: CompositorService.isNiri
function onAllWorkspacesChanged() { delegateRoot.updateAllData() } function onAllWorkspacesChanged() { delegateRoot.updateAllData() }
function onWindowUrgentChanged() { delegateRoot.updateAllData() }
} }
Connections { Connections {
target: SettingsData target: SettingsData

View File

@@ -131,15 +131,15 @@ DankPopout {
model: { model: {
let tabs = [ let tabs = [
{ icon: "dashboard", text: "Overview" }, { icon: "dashboard", text: I18n.tr("Overview") },
{ icon: "music_note", text: "Media" } { icon: "music_note", text: I18n.tr("Media") }
] ]
if (SettingsData.weatherEnabled) { if (SettingsData.weatherEnabled) {
tabs.push({ icon: "wb_sunny", text: "Weather" }) tabs.push({ icon: "wb_sunny", text: I18n.tr("Weather") })
} }
tabs.push({ icon: "settings", text: "Settings", isAction: true }) tabs.push({ icon: "settings", text: I18n.tr("Settings"), isAction: true })
return tabs return tabs
} }

View File

@@ -305,7 +305,7 @@ Item {
} }
StyledText { StyledText {
text: "No Active Players" text: I18n.tr("No Active Players")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -406,7 +406,7 @@ Item {
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
StyledText { StyledText {
text: "Audio Output Devices (" + audioDevicesDropdown.availableDevices.length + ")" text: I18n.tr("Audio Output Devices (") + audioDevicesDropdown.availableDevices.length + ")"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -491,7 +491,6 @@ Item {
} }
} }
Behavior on color { ColorAnimation { duration: Anims.durShort } }
Behavior on border.color { ColorAnimation { duration: Anims.durShort } } Behavior on border.color { ColorAnimation { duration: Anims.durShort } }
} }
} }
@@ -564,7 +563,7 @@ Item {
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
StyledText { StyledText {
text: "Media Players (" + (allPlayers?.length || 0) + ")" text: I18n.tr("Media Players (") + (allPlayers?.length || 0) + ")"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -675,14 +674,6 @@ Item {
} }
} }
Behavior on color {
ColorAnimation {
duration: Anims.durShort
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standard
}
}
Behavior on border.color { Behavior on border.color {
ColorAnimation { ColorAnimation {
duration: Anims.durShort duration: Anims.durShort
@@ -858,14 +849,6 @@ Item {
} }
} }
} }
Behavior on color {
ColorAnimation {
duration: Anims.durShort
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standard
}
}
} }
} }
@@ -1024,14 +1007,6 @@ Item {
} }
} }
} }
Behavior on color {
ColorAnimation {
duration: Anims.durShort
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standard
}
}
} }
} }
} }

View File

@@ -53,7 +53,7 @@ Card {
} }
StyledText { StyledText {
text: "No Media" text: I18n.tr("No Media")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -33,7 +33,7 @@ Card {
} }
Button { Button {
text: "Refresh" text: I18n.tr("Refresh")
flat: true flat: true
visible: !WeatherService.weather.loading visible: !WeatherService.weather.loading
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -24,7 +24,7 @@ Item {
} }
StyledText { StyledText {
text: "No Weather Data Available" text: I18n.tr("No Weather Data Available")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -257,7 +257,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Feels Like" text: I18n.tr("Feels Like")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -304,7 +304,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Humidity" text: I18n.tr("Humidity")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -351,7 +351,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Wind" text: I18n.tr("Wind")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -398,7 +398,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Pressure" text: I18n.tr("Pressure")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -445,7 +445,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Rain Chance" text: I18n.tr("Rain Chance")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -492,14 +492,14 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Visibility" text: I18n.tr("Visibility")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: "Good" text: I18n.tr("Good")
font.pixelSize: Theme.fontSizeSmall + 1 font.pixelSize: Theme.fontSizeSmall + 1
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -522,7 +522,7 @@ Item {
spacing: Theme.spacingS spacing: Theme.spacingS
StyledText { StyledText {
text: "7-Day Forecast" text: I18n.tr("7-Day Forecast")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -78,8 +78,8 @@ Variants {
} }
property bool reveal: { property bool reveal: {
if (CompositorService.isNiri && NiriService.inOverview) { if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
return SettingsData.dockOpenOnOverview return true
} }
return (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky) && !windowIsFullscreen return (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky) && !windowIsFullscreen
} }
@@ -99,7 +99,7 @@ Variants {
} }
screen: modelData screen: modelData
visible: SettingsData.showDock visible: SettingsData.showDock || (CompositorService.isNiri && SettingsData.dockOpenOnOverview && NiriService.inOverview)
color: "transparent" color: "transparent"
@@ -264,19 +264,21 @@ Variants {
x: { x: {
if (!dock.isVertical) return 0 if (!dock.isVertical) return 0
if (dock.reveal) return 0 if (dock.reveal) return 0
const hideDistance = 58 + SettingsData.dockSpacing + SettingsData.dockBottomGap + 10
if (SettingsData.dockPosition === SettingsData.Position.Right) { if (SettingsData.dockPosition === SettingsData.Position.Right) {
return 60 return hideDistance
} else { } else {
return -60 return -hideDistance
} }
} }
y: { y: {
if (dock.isVertical) return 0 if (dock.isVertical) return 0
if (dock.reveal) return 0 if (dock.reveal) return 0
const hideDistance = 58 + SettingsData.dockSpacing + SettingsData.dockBottomGap + 10
if (SettingsData.dockPosition === SettingsData.Position.Bottom) { if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
return 60 return hideDistance
} else { } else {
return -60 return -hideDistance
} }
} }

View File

@@ -316,14 +316,27 @@ Item {
console.warn("No toplevel found for grouped app") console.warn("No toplevel found for grouped app")
} }
} else { } else {
// For multiple windows, show context menu (hide pin option for left-click)
if (contextMenu) { if (contextMenu) {
contextMenu.showForButton(root, appData, 65, true) contextMenu.showForButton(root, appData, 65, true, cachedDesktopEntry)
} }
} }
} }
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
if (appData && appData.appId) { if (appData && appData.type === "window") {
const sortedToplevels = CompositorService.sortedToplevels
for (var i = 0; i < sortedToplevels.length; i++) {
const toplevel = sortedToplevels[i]
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
if (checkId === appData.uniqueId) {
toplevel.close()
break
}
}
} else if (appData && appData.type === "grouped") {
if (contextMenu) {
contextMenu.showForButton(root, appData, 40, false, cachedDesktopEntry)
}
} else if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry const desktopEntry = cachedDesktopEntry
if (desktopEntry) { if (desktopEntry) {
AppUsageHistoryData.addAppUsage({ AppUsageHistoryData.addAppUsage({
@@ -334,12 +347,11 @@ Item {
"comment": desktopEntry.comment || "" "comment": desktopEntry.comment || ""
}) })
} }
SessionService.launchDesktopEntry(desktopEntry) SessionService.launchDesktopEntry(desktopEntry)
} }
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
if (contextMenu && appData) { if (contextMenu && appData) {
console.log("Right-clicked on app:", appData.appId, "type:", appData.type, "windowCount:", appData.windowCount || 0) contextMenu.showForButton(root, appData, 40, false, cachedDesktopEntry)
contextMenu.showForButton(root, appData, 40, false)
} else { } else {
console.warn("No context menu or appData available") console.warn("No context menu or appData available")
} }

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
@@ -16,12 +15,14 @@ PanelWindow {
property real dockVisibleHeight: 40 property real dockVisibleHeight: 40
property int margin: 10 property int margin: 10
property bool hidePin: false property bool hidePin: false
property var desktopEntry: null
function showForButton(button, data, dockHeight, hidePinOption) { function showForButton(button, data, dockHeight, hidePinOption, entry) {
anchorItem = button anchorItem = button
appData = data appData = data
dockVisibleHeight = dockHeight || 40 dockVisibleHeight = dockHeight || 40
hidePin = hidePinOption || false hidePin = hidePinOption || false
desktopEntry = entry || null
const dockWindow = button.Window.window const dockWindow = button.Window.window
if (dockWindow) { if (dockWindow) {
@@ -134,9 +135,6 @@ PanelWindow {
Rectangle { Rectangle {
id: menuContainer id: menuContainer
width: Math.min(400, Math.max(200, menuColumn.implicitWidth + Theme.spacingS * 2))
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
x: { x: {
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
if (isVertical) { if (isVertical) {
@@ -169,12 +167,23 @@ PanelWindow {
} }
} }
} }
width: Math.min(400, Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2))
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
opacity: showContextMenu ? 1 : 0
scale: showContextMenu ? 1 : 0.85 opacity: root.showContextMenu ? 1 : 0
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -184,7 +193,7 @@ PanelWindow {
anchors.bottomMargin: -4 anchors.bottomMargin: -4
radius: parent.radius radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15) color: Qt.rgba(0, 0, 0, 0.15)
z: parent.z - 1 z: -1
} }
Column { Column {
@@ -223,7 +232,7 @@ PanelWindow {
anchors.right: closeButton.left anchors.right: closeButton.left
anchors.rightMargin: Theme.spacingXS anchors.rightMargin: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: (modelData && modelData.title) ? modelData.title : "(Unnamed)" text: (modelData && modelData.title) ? modelData.title: I18n.tr("(Unnamed)")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -289,6 +298,71 @@ PanelWindow {
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
Repeater {
model: root.desktopEntry && root.desktopEntry.actions ? root.desktopEntry.actions : []
Rectangle {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: actionArea.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.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
Item {
anchors.verticalCenter: parent.verticalCenter
width: 16
height: 16
visible: modelData.icon && modelData.icon !== ""
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: modelData.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
}
MouseArea {
id: actionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData) {
SessionService.launchDesktopAction(root.desktopEntry, modelData)
}
root.close()
}
}
}
}
Rectangle {
visible: root.desktopEntry && root.desktopEntry.actions && root.desktopEntry.actions.length > 0
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
Rectangle { Rectangle {
visible: !root.hidePin visible: !root.hidePin
width: parent.width width: parent.width
@@ -302,7 +376,7 @@ PanelWindow {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: root.appData && root.appData.isPinned ? "Unpin from Dock" : "Pin to Dock" text: root.appData && root.appData.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -330,14 +404,49 @@ PanelWindow {
} }
Rectangle { Rectangle {
visible: root.appData && root.appData.type === "window" visible: (root.appData && root.appData.type === "window") || (root.desktopEntry && SessionService.hasPrimeRun)
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
Rectangle { Rectangle {
visible: root.appData && root.appData.type === "window" visible: root.desktopEntry && SessionService.hasPrimeRun
width: parent.width
height: 28
radius: Theme.cornerRadius
color: primeRunArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
MouseArea {
id: primeRunArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.desktopEntry) {
SessionService.launchDesktopEntry(root.desktopEntry, true)
}
root.close()
}
}
}
Rectangle {
visible: root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0))
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
@@ -377,7 +486,6 @@ PanelWindow {
onClicked: { onClicked: {
const sortedToplevels = CompositorService.sortedToplevels const sortedToplevels = CompositorService.sortedToplevels
if (root.appData && root.appData.type === "window") { if (root.appData && root.appData.type === "window") {
// Find and close the specific window
for (var i = 0; i < sortedToplevels.length; i++) { for (var i = 0; i < sortedToplevels.length; i++) {
const toplevel = sortedToplevels[i] const toplevel = sortedToplevels[i]
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
@@ -387,7 +495,6 @@ PanelWindow {
} }
} }
} else if (root.appData && root.appData.type === "grouped") { } else if (root.appData && root.appData.type === "grouped") {
// Close all windows for this app
const allToplevels = ToplevelManager.toplevels.values const allToplevels = ToplevelManager.toplevels.values
for (let i = 0; i < allToplevels.length; i++) { for (let i = 0; i < allToplevels.length; i++) {
const toplevel = allToplevels[i] const toplevel = allToplevels[i]
@@ -401,27 +508,11 @@ PanelWindow {
} }
} }
} }
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
onClicked: { onClicked: root.close()
root.close()
}
} }
} }

View File

@@ -11,6 +11,7 @@ import Quickshell.Services.Mpris
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.Lock
Item { Item {
id: root id: root
@@ -322,6 +323,8 @@ Item {
TextInput { TextInput {
id: inputField id: inputField
property bool syncingFromState: false
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2 anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
anchors.rightMargin: { anchors.rightMargin: {
@@ -329,6 +332,9 @@ Item {
if (GreeterState.showPasswordInput && revealButton.visible) { if (GreeterState.showPasswordInput && revealButton.visible) {
margin += revealButton.width margin += revealButton.width
} }
if (virtualKeyboardButton.visible) {
margin += virtualKeyboardButton.width
}
if (enterButton.visible) { if (enterButton.visible) {
margin += enterButton.width + 2 margin += enterButton.width + 2
} }
@@ -338,6 +344,7 @@ Item {
focus: true focus: true
echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal
onTextChanged: { onTextChanged: {
if (syncingFromState) return
if (GreeterState.showPasswordInput) { if (GreeterState.showPasswordInput) {
GreeterState.passwordBuffer = text GreeterState.passwordBuffer = text
} else { } else {
@@ -355,13 +362,17 @@ Item {
GreeterState.showPasswordInput = true GreeterState.showPasswordInput = true
PortalService.getGreeterUserProfileImage(GreeterState.username) PortalService.getGreeterUserProfileImage(GreeterState.username)
GreeterState.passwordBuffer = "" GreeterState.passwordBuffer = ""
inputField.text = "" syncingFromState = true
text = ""
syncingFromState = false
} }
} }
} }
Component.onCompleted: { Component.onCompleted: {
syncingFromState = true
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput
syncingFromState = false
if (isPrimaryScreen) if (isPrimaryScreen)
forceActiveFocus() forceActiveFocus()
} }
@@ -371,12 +382,18 @@ Item {
} }
} }
KeyboardController {
id: keyboard_controller
target: inputField
rootObject: root
}
StyledText { StyledText {
id: placeholder id: placeholder
anchors.left: lockIcon.right anchors.left: lockIcon.right
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (enterButton.visible ? enterButton.left : parent.right)) anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))
anchors.rightMargin: 2 anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { text: {
@@ -413,7 +430,7 @@ Item {
StyledText { StyledText {
anchors.left: lockIcon.right anchors.left: lockIcon.right
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (enterButton.visible ? enterButton.left : parent.right)) anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))
anchors.rightMargin: 2 anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { text: {
@@ -441,8 +458,8 @@ Item {
DankActionButton { DankActionButton {
id: revealButton id: revealButton
anchors.right: enterButton.visible ? enterButton.left : parent.right anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)
anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS anchors.rightMargin: 0
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
iconName: parent.showPassword ? "visibility_off" : "visibility" iconName: parent.showPassword ? "visibility_off" : "visibility"
buttonSize: 32 buttonSize: 32
@@ -450,6 +467,24 @@ Item {
enabled: visible enabled: visible
onClicked: parent.showPassword = !parent.showPassword onClicked: parent.showPassword = !parent.showPassword
} }
DankActionButton {
id: virtualKeyboardButton
anchors.right: enterButton.visible ? enterButton.left : parent.right
anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
iconName: "keyboard"
buttonSize: 32
visible: Greetd.state === GreetdState.Inactive && !GreeterState.unlocking
enabled: visible
onClicked: {
if (keyboard_controller.isKeyboardActive) {
keyboard_controller.hide()
} else {
keyboard_controller.show()
}
}
}
DankActionButton { DankActionButton {
id: enterButton id: enterButton
@@ -549,7 +584,7 @@ Item {
} }
StyledText { StyledText {
text: "Switch User" text: I18n.tr("Switch User")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -1256,7 +1291,7 @@ Item {
if (sessionCmd) { if (sessionCmd) {
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex]) GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex])
GreetdMemory.setLastSuccessfulUser(GreeterState.username) GreetdMemory.setLastSuccessfulUser(GreeterState.username)
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"], true) Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"])
} }
} }
@@ -1325,7 +1360,7 @@ Item {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "Cancel" text: I18n.tr("Cancel")
color: Theme.surfaceText color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
} }

View File

@@ -18,34 +18,43 @@ The easiest thing is to run `dms greeter install` or `dms` for interactive insta
### Manual ### Manual
1. Install `greetd` (in most distro's standard repositories) 1. Install `greetd` (in most distro's standard repositories) and `quickshell`
2. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd` 2. Clone the dms project to `/etc/xdg/quickshell/dms-greeter`
- niri if you want to run the greeter under niri, hypr if you want to run the greeter under Hyprland ```bash
3. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/usr/local/bin/start-dms-greetd.sh` sudo git clone https://github.com/AvengeMedia/DankMaterialShell.git /etc/xdg/quickshell/dms-greeter
4. Edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` and replace `_DMS_PATH_` with the absolute path to dms, e.g. `/home/joecool/.config/quickshell/dms` ```
5. Edit or create `/etc/greetd/config.toml` 3. Copy `assets/dms-greeter` to `/usr/local/bin/dms-greeter`:
```bash
sudo cp assets/dms-greeter /usr/local/bin/dms-greeter
sudo chmod +x /usr/local/bin/dms-greeter
```
4. Create greeter cache directory with proper permissions:
```bash
sudo mkdir -p /var/cache/dms-greeter
sudo chown greeter:greeter /var/cache/dms-greeter
sudo chmod 770 /var/cache/dms-greeter
```
6. Edit or create `/etc/greetd/config.toml`:
```toml ```toml
[terminal] [terminal]
# The VT to run the greeter on. Can be "next", "current" or a number
# designating the VT.
vt = 1 vt = 1
# The default session, also known as the greeter.
[default_session] [default_session]
# `agreety` is the bundled agetty/login-lookalike. You can replace `/bin/sh`
# with whatever you want started, such as `sway`.
# The user to run the command as. The privileges this user must have depends
# on the greeter. A graphical greeter may for example require the user to be
# in the `video` group.
user = "greeter" user = "greeter"
# Change compositor to sway or hyprland if preferred
command = "/usr/local/bin/start-dms-greetd.sh" command = "/usr/local/bin/dms-greeter --command niri"
``` ```
Enable the greeter with `sudo systemctl enable greetd` Enable the greeter with `sudo systemctl enable greetd`
#### Legacy installation (deprecated)
If you prefer the old method with separate shell scripts and config files:
1. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd`
2. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/usr/local/bin/start-dms-greetd.sh`
3. Edit the config file and replace `_DMS_PATH_` with your DMS installation path
4. Configure greetd to use `/usr/local/bin/start-dms-greetd.sh`
### NixOS ### NixOS
To install the greeter on NixOS add the repo to your flake inputs as described in the readme. Then somewhere in your NixOS config add this to imports: To install the greeter on NixOS add the repo to your flake inputs as described in the readme. Then somewhere in your NixOS config add this to imports:
@@ -59,14 +68,37 @@ Enable the greeter with this in your NixOS config:
```nix ```nix
programs.dankMaterialShell.greeter = { programs.dankMaterialShell.greeter = {
enable = true; enable = true;
compositor = "niri"; # or set to hyprland compositor.name = "niri"; # or set to hyprland
configHome = "/home/user"; # optionally symlinks that users DMS settings to the greeters data directory configHome = "/home/user"; # optionally copyies that users DMS settings (and wallpaper if set) to the greeters data directory as root before greeter starts
}; };
``` ```
## Usage ## Usage
To run dms in greeter mode you just need to set `DMS_RUN_GREETER=1` in the environment. ### Using dms-greeter wrapper (recommended)
The `dms-greeter` wrapper simplifies running the greeter with any compositor:
```bash
dms-greeter --command niri
dms-greeter --command hyprland
dms-greeter --command sway
dms-greeter --command niri -C /path/to/custom-niri.kdl
```
Configure greetd to use it in `/etc/greetd/config.toml`:
```toml
[terminal]
vt = 1
[default_session]
user = "greeter"
command = "/usr/local/bin/dms-greeter --command niri"
```
### Manual usage
To run dms in greeter mode you can also manually set environment variables:
```bash ```bash
DMS_RUN_GREETER=1 qs -p /path/to/dms DMS_RUN_GREETER=1 qs -p /path/to/dms
@@ -86,15 +118,17 @@ Wallpapers and themes and weather and clock formats and things are a TODO on the
You can synchronize those configurations with a specific user if you want greeter settings to always mirror the shell. You can synchronize those configurations with a specific user if you want greeter settings to always mirror the shell.
The greeter uses the `dms-greeter` group for file access permissions, so ensure your user and the greeter user are both members of this group.
```bash ```bash
# For core settings (theme, clock formats, etc) # For core settings (theme, clock formats, etc)
sudo ln -sf ~/.config/DankMaterialShell/settings.json /etc/greetd/.dms/settings.json sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
# For state (mainly you would configure wallpaper in this file) # For state (mainly you would configure wallpaper in this file)
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /etc/greetd/.dms/session.json sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
# For wallpaper based theming # For wallpaper based theming
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /etc/greetd/.dms/dms-colors.json sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/dms-colors.json
``` ```
You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable, the default is `/etc/greetd/.dms` You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
It should be writable by the greeter user. The cache directory should be owned by `greeter:greeter` with `770` permissions.

174
Modules/Greetd/assets/dms-greeter Executable file
View File

@@ -0,0 +1,174 @@
#!/bin/bash
set -e
COMPOSITOR=""
COMPOSITOR_CONFIG=""
DMS_PATH="dms-greeter"
CACHE_DIR="/var/cache/dms-greeter"
show_help() {
cat << EOF
dms-greeter - DankMaterialShell greeter launcher
Usage: dms-greeter --command COMPOSITOR [OPTIONS]
Required:
--command COMPOSITOR Compositor to use (niri, hyprland, or sway)
Options:
-C, --config PATH Custom compositor config file
-p, --path PATH DMS path (config name or absolute path)
(default: dms-greeter)
--cache-dir PATH Cache directory for greeter data
(default: /var/cache/dms-greeter)
-h, --help Show this help message
Examples:
dms-greeter --command niri
dms-greeter --command hyprland -C /etc/greetd/custom-hypr.conf
dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms
dms-greeter --command niri --cache-dir /tmp/dmsgreeter
EOF
}
while [[ $# -gt 0 ]]; do
case $1 in
--command)
COMPOSITOR="$2"
shift 2
;;
-C|--config)
COMPOSITOR_CONFIG="$2"
shift 2
;;
-p|--path)
DMS_PATH="$2"
shift 2
;;
--cache-dir)
CACHE_DIR="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
*)
echo "Unknown option: $1" >&2
show_help
exit 1
;;
esac
done
if [[ -z "$COMPOSITOR" ]]; then
echo "Error: --command COMPOSITOR is required" >&2
show_help
exit 1
fi
export XDG_SESSION_TYPE=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
export EGL_PLATFORM=gbm
export DMS_RUN_GREETER=1
export DMS_GREET_CFG_DIR="$CACHE_DIR"
mkdir -p "$CACHE_DIR"
QS_CMD="qs"
if [[ "$DMS_PATH" == /* ]]; then
QS_CMD="qs -p $DMS_PATH"
else
QS_CMD="qs -c $DMS_PATH"
fi
case "$COMPOSITOR" in
niri)
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp)
cat > "$TEMP_CONFIG" << NIRI_EOF
hotkey-overlay {
skip-at-startup
}
environment {
DMS_RUN_GREETER "1"
}
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
debug {
keep-max-bpc-unchanged
}
gestures {
hot-corners {
off
}
}
layout {
background-color "#000000"
}
NIRI_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << NIRI_EOF
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
NIRI_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
fi
exec niri -c "$COMPOSITOR_CONFIG"
;;
hyprland)
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp)
cat > "$TEMP_CONFIG" << HYPRLAND_EOF
env = DMS_RUN_GREETER,1
exec = sh -c "$QS_CMD; hyprctl dispatch exit"
HYPRLAND_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << HYPRLAND_EOF
exec = sh -c "$QS_CMD; hyprctl dispatch exit"
HYPRLAND_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
fi
exec Hyprland -c "$COMPOSITOR_CONFIG"
;;
sway)
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp)
cat > "$TEMP_CONFIG" << SWAY_EOF
exec "$QS_CMD; swaymsg exit"
SWAY_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << SWAY_EOF
exec "$QS_CMD; swaymsg exit"
SWAY_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
fi
exec sway -c "$COMPOSITOR_CONFIG"
;;
*)
echo "Error: Unsupported compositor: $COMPOSITOR" >&2
echo "Supported compositors: niri, hyprland, sway" >&2
exit 1
;;
esac

View File

@@ -169,7 +169,7 @@ Rectangle {
], ],
"row_4": [ "row_4": [
{ {
text: '123', text: "123",
symbol: 'ABC', symbol: 'ABC',
width: 1.5 width: 1.5
}, },

View File

@@ -7,145 +7,55 @@ import qs.Services
Item { Item {
id: root id: root
property string sid: Quickshell.env("XDG_SESSION_ID") || "self"
property string sessionPath: ""
function activate() { property string sharedPasswordBuffer: ""
loader.activeAsync = true property bool shouldLock: false
}
Component.onCompleted: { Component.onCompleted: {
getSessionPath.running = true IdleService.lockComponent = root
} }
Component.onDestruction: { function activate() {
lockStateMonitor.running = false shouldLock = true
}
Connections {
target: SessionService
function onSessionLocked() {
shouldLock = true
}
function onSessionUnlocked() {
shouldLock = false
}
} }
Connections { Connections {
target: IdleService target: IdleService
function onLockRequested() { function onLockRequested() {
console.log("Lock: Received lock request from IdleService") shouldLock = true
activate()
} }
} }
Process { WlSessionLock {
id: getSessionPath id: sessionLock
command: ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", "/org/freedesktop/login1", "--method", "org.freedesktop.login1.Manager.GetSession", sid]
running: false
stdout: StdioCollector { locked: root.shouldLock
onStreamFinished: {
const match = text.match(/objectpath '([^']+)'/)
if (match) {
root.sessionPath = match[1]
console.log("Found session path:", root.sessionPath)
checkCurrentLockState.running = true
lockStateMonitor.running = true
} else {
console.warn("Could not determine session path")
}
}
}
onExited: (exitCode, exitStatus) => { WlSessionLockSurface {
if (exitCode !== 0) { color: "transparent"
console.warn("Failed to get session path, exit code:", exitCode)
}
}
}
Process {
id: checkCurrentLockState
command: root.sessionPath ? ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", root.sessionPath, "--method", "org.freedesktop.DBus.Properties.Get", "org.freedesktop.login1.Session", "LockedHint"] : []
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.includes("true")) {
console.log("Session is locked on startup, activating lock screen")
loader.activeAsync = true
}
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
console.warn("Failed to check initial lock state, exit code:", exitCode)
}
}
}
Process {
id: lockStateMonitor
command: root.sessionPath ? ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.login1"] : []
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (line.includes(root.sessionPath)) {
if (line.includes("org.freedesktop.login1.Session.Lock")) {
console.log("login1: Lock signal received -> show lock")
loader.activeAsync = true
return
}
if (line.includes("org.freedesktop.login1.Session.Unlock")) {
console.log("login1: Unlock signal received -> hide lock")
loader.active = false
return
}
if (line.includes("LockedHint") && line.includes("true")) {
console.log("login1: LockedHint=true -> show lock")
loader.activeAsync = true
return
}
if (line.includes("LockedHint") && line.includes("false")) {
console.log("login1: LockedHint=false -> hide lock")
loader.active = false
return
}
}
if (line.includes("PrepareForSleep") &&
line.includes("true") &&
SessionData.lockBeforeSuspend) {
loader.activeAsync = true
}
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
console.warn("gdbus monitor failed, exit code:", exitCode)
}
}
}
LazyLoader {
id: loader
WlSessionLock {
id: sessionLock
property bool unlocked: false
property string sharedPasswordBuffer: ""
locked: true
onLockedChanged: {
if (!locked) {
loader.active = false
}
}
LockSurface { LockSurface {
id: lockSurface anchors.fill: parent
lock: sessionLock lock: sessionLock
sharedPasswordBuffer: sessionLock.sharedPasswordBuffer sharedPasswordBuffer: root.sharedPasswordBuffer
onUnlockRequested: {
root.shouldLock = false
}
onPasswordChanged: newPassword => { onPasswordChanged: newPassword => {
sessionLock.sharedPasswordBuffer = newPassword root.sharedPasswordBuffer = newPassword
} }
} }
} }
@@ -159,17 +69,15 @@ Item {
target: "lock" target: "lock"
function lock() { function lock() {
console.log("Lock screen requested via IPC") shouldLock = true
loader.activeAsync = true
} }
function demo() { function demo() {
console.log("Lock screen DEMO mode requested via IPC")
demoWindow.showDemo() demoWindow.showDemo()
} }
function isLocked(): bool { function isLocked(): bool {
return loader.active return sessionLock.locked
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More