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

Compare commits

...

95 Commits

Author SHA1 Message Date
github-actions[bot]
e1406875aa Update VERSION to v0.2.3 (from DMS) 2025-10-25 18:56:06 +00:00
github-actions[bot]
6ab96898a3 i18n: update translations 2025-10-25 18:35:07 +00:00
github-actions[bot]
7973b2f3da i18n: update source strings from codebase 2025-10-25 18:35:00 +00:00
bbedward
90284625af lock/greetd: fix 12-hour time 2025-10-25 14:34:25 -04:00
bbedward
6b3442512a net: only show eth/wifi on networkmanager backend 2025-10-25 14:23:12 -04:00
github-actions[bot]
f9208136af i18n: update source strings from codebase 2025-10-25 18:03:54 +00:00
bbedward
9895fbde0d dock: dynamic indicator positions 2025-10-25 14:03:20 -04:00
bbedward
30370e4985 lock/greeter: fixed clock widths 2025-10-25 14:00:02 -04:00
github-actions[bot]
7a45f370b5 i18n: update source strings from codebase 2025-10-25 16:43:28 +00:00
Massimo Branchini
38068eeaac better checks before saying SUCCESS (#550) 2025-10-25 12:43:09 -04:00
bbedward
62df30ed6c apps: fix sorting and reactivity 2025-10-25 12:42:54 -04:00
bbedward
7e75c9e510 dankbar: fix widget hover effects 2025-10-25 12:42:54 -04:00
github-actions[bot]
42f9edf566 i18n: update translations 2025-10-25 15:48:30 +00:00
github-actions[bot]
c72b6144a5 i18n: update source strings from codebase 2025-10-25 15:48:23 +00:00
bbedward
032777e32e net: switch to native VPN backend 2025-10-25 11:47:46 -04:00
Jack Grahn
607b5320fd Add clipboard history command to niri spawn-at-startup options (#545) 2025-10-25 10:08:48 -04:00
Massimo Branchini
959766b265 external system update trigger (#546) 2025-10-25 10:07:56 -04:00
github-actions[bot]
9774991b56 i18n: update translations 2025-10-25 06:01:33 +00:00
github-actions[bot]
e1587995d0 i18n: update source strings from codebase 2025-10-25 06:01:26 +00:00
bbedward
08e6e22046 net: lose fail tracking 2025-10-25 02:00:53 -04:00
github-actions[bot]
454d8bdc88 i18n: update source strings from codebase 2025-10-25 03:59:51 +00:00
bbedward
d0ae7431eb net: updates to accomodate iwd + other backends 2025-10-24 23:59:23 -04:00
purian23
d88dc17b21 Format spec 2025-10-24 22:28:27 -04:00
purian23
705f569571 Update dms-greeter colors path 2025-10-24 19:01:58 -04:00
purian23
abc7badfa9 Print the final message 2025-10-24 18:44:28 -04:00
purian23
e8c2469227 Simplify upgrade message 2025-10-24 18:37:30 -04:00
purian23
1e5e8cd246 Fix scope 2025-10-24 18:26:26 -04:00
purian23
adc81cfb95 Moar Copr DMS restart logic 2025-10-24 18:18:21 -04:00
github-actions[bot]
e175fa64cb i18n: update translations 2025-10-24 21:21:52 +00:00
bbedward
f9994d0e42 add turkish 2025-10-24 17:21:15 -04:00
github-actions[bot]
3e5d1c514a i18n: update translations 2025-10-24 21:17:00 +00:00
purian23
6310394034 Add 'dms restart' to Copr upgrades 2025-10-24 17:16:29 -04:00
purian23
f32596053b Update Copr default dir to usr/share 2025-10-24 13:27:48 -04:00
bbedward
cf4a6969d3 plugins: fix set ToggleSetting not saving
fixes #541
2025-10-24 13:12:57 -04:00
github-actions[bot]
0918412916 i18n: update translations 2025-10-24 16:38:47 +00:00
github-actions[bot]
41b1718587 i18n: update source strings from codebase 2025-10-24 16:38:43 +00:00
bbedward
ca2acbc704 niri: ability to blur wallpaper on overview + add a separate layer for
blurred wallpapers
2025-10-24 12:37:57 -04:00
bbedward
1abd3ef8b1 greeter: search /usr/share path for qml files 2025-10-24 09:10:38 -04:00
bbedward
cedba3770c clock: baseline text relative to date
fixes #535
2025-10-24 08:47:09 -04:00
bbedward
f733be1fd1 lock/greeter: seconds precision on clock
fixes #540
2025-10-24 08:38:41 -04:00
github-actions[bot]
01e02232d7 i18n: update source strings from codebase 2025-10-24 03:16:41 +00:00
bbedward
771920b38b dankbar: fix focusedapp & media text clipping
fixes #537
2025-10-23 23:15:53 -04:00
bbedward
0f55bbc148 dankbar: remove hardcoded font weights
fixes #539
2025-10-23 23:11:20 -04:00
bbedward
ab4e9646ad workspace: don't wrap on mousewheel
fixes #538
2025-10-23 23:04:17 -04:00
bbedward
884b73599a dd: add file name to wallpaper tab 2025-10-23 22:56:53 -04:00
bbedward
492c0e7ef7 dankbar: fix separator 2025-10-23 22:44:49 -04:00
purian23
0865ae000b Remove Notepad indicator 2025-10-23 22:13:53 -04:00
purian23
049c9b44e4 Add req accountsservice to Copr 2025-10-23 19:04:49 -04:00
github-actions[bot]
199edd3771 i18n: update translations 2025-10-23 22:17:51 +00:00
bbedward
8806217d25 gh: use workflow_run trigger for copr 2025-10-23 18:17:09 -04:00
github-actions[bot]
cc1588debd Update VERSION to v0.2.2 (from DMS) 2025-10-23 22:11:22 +00:00
github-actions[bot]
d2ba4b32fe i18n: update translations 2025-10-23 20:53:46 +00:00
github-actions[bot]
b3d5054966 i18n: update source strings from codebase 2025-10-23 20:53:42 +00:00
bbedward
57a921425c settings: about About page 2025-10-23 16:53:00 -04:00
github-actions[bot]
061aaeb933 i18n: update source strings from codebase 2025-10-23 20:14:00 +00:00
bbedward
0c7af9c740 meta: log level re-work 2025-10-23 16:13:27 -04:00
bbedward
d5c4b990dc accessibility: widen click targets to bar edge 2025-10-23 15:51:59 -04:00
Aleksandr Lebedev
a650a79dfc Fixed bugs in Workspace Switcher (#534)
* Fixed bugs with workspace switcher:

- fixed bug that, when moving existing windows/moving focus from one
window to another, information about positions and active windows on
workspace switcher was not updated
- fixed bug that hovering/clicking on app icons didn't work, because
of missplaced MouseArea

* Added comment
2025-10-23 14:46:29 -04:00
github-actions[bot]
7ac6e94348 i18n: update translations 2025-10-23 18:31:58 +00:00
github-actions[bot]
b4abdf3d51 i18n: update source strings from codebase 2025-10-23 18:31:54 +00:00
bbedward
b59b87d84e bluetooth+plugins: some repairs for bad references and dialogs plugins: switch to ID-based references 2025-10-23 14:31:12 -04:00
github-actions[bot]
799ae1a20e i18n: update source strings from codebase 2025-10-23 16:58:27 +00:00
bbedward
1e58e69c59 fix dms subscription 2025-10-23 12:57:50 -04:00
github-actions[bot]
c667bab5ca i18n: update source strings from codebase 2025-10-23 16:25:01 +00:00
bbedward
2a744fb174 battery: hide secondary text on no battery 2025-10-23 12:24:20 -04:00
bbedward
a9744a0cad readme: document accountsservice dependency 2025-10-23 12:15:13 -04:00
github-actions[bot]
0aab22f242 i18n: update translations 2025-10-23 15:55:55 +00:00
github-actions[bot]
b0f65225a9 i18n: update source strings from codebase 2025-10-23 15:55:50 +00:00
bbedward
1311da7258 bluetooth: integrate with DMS API v9 - Supports proper pairing with an agent & pin, passcode, etc. 2025-10-23 11:55:07 -04:00
bbedward
61d68b1f76 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-23 11:54:56 -04:00
github-actions[bot]
5dd1769536 i18n: update translations 2025-10-23 15:19:35 +00:00
Zsolt Donca
a45a9bda9e Fixed path to dms-colors.json in Modules/Greetd/README.md (#532)
Fixed the path to dms-colors.json, as the previous path did not actually exist, resulting in a broken symbolic link and the colors not actually being right in the login screen.
2025-10-23 11:19:02 -04:00
bbedward
4e43c797e2 niri: improve toplevel sorting 2025-10-23 10:33:53 -04:00
github-actions[bot]
beab1a7b01 i18n: update translations 2025-10-23 13:31:18 +00:00
bbedward
85c00a9c4e dankbar: prevent double widget instances for horiz/vertical 2025-10-23 09:30:37 -04:00
Moraxyc Xu
b2e5565110 nix: use standard way to remove option (#529) 2025-10-22 22:46:15 -04:00
github-actions[bot]
95785afec9 i18n: update source strings from codebase 2025-10-23 02:44:56 +00:00
bbedward
d9d16eccfe displays: default show on last display to true, always 2025-10-22 22:44:26 -04:00
github-actions[bot]
26900c9b62 i18n: update translations 2025-10-23 02:40:18 +00:00
github-actions[bot]
8113ddc809 i18n: update source strings from codebase 2025-10-23 02:40:13 +00:00
bbedward
3cd6a1a558 displays: add "show on last display" for some components.
- Lets the components migrate when un-docked to the otehr monitor,
  basically
2025-10-22 22:38:59 -04:00
purian23
9128141be0 Release tag to Copr 2025-10-22 21:35:36 -04:00
bbedward
0b0af20a84 matugen: add dark/light kcolorschemes 2025-10-22 19:02:38 -04:00
github-actions[bot]
225144cb46 i18n: update source strings from codebase 2025-10-22 20:55:17 +00:00
bbedward
bbe802037e lock: send lockerReady after frame rendered 2025-10-22 16:54:34 -04:00
bbedward
1db4e92779 suppress brightness OSD when operating from cc 2025-10-22 16:38:20 -04:00
github-actions[bot]
072883dcd4 i18n: update source strings from codebase 2025-10-22 20:31:27 +00:00
bbedward
a25e929200 cc: allow pinning brightness device per-monitor 2025-10-22 16:30:51 -04:00
bbedward
6c4d27be8a dock: fix indicator positions 2025-10-22 14:39:21 -04:00
github-actions[bot]
8825382502 i18n: update translations 2025-10-22 18:08:52 +00:00
github-actions[bot]
9ce3c5bd73 i18n: update source strings from codebase 2025-10-22 18:08:46 +00:00
bbedward
771346c8fa dock: add dot indicator style 2025-10-22 14:08:13 -04:00
github-actions[bot]
a56b2d6a9f Update VERSION to v0.2.1 (from DMS) 2025-10-22 17:38:31 +00:00
github-actions[bot]
342f980bad i18n: update translations 2025-10-22 17:35:23 +00:00
Roni Laukkarinen
dbc1bdeb3b Fix WorkspaceSwitcher crash: replace undefined parentScreen?.name with screenName (#527) 2025-10-22 13:34:39 -04:00
103 changed files with 5877 additions and 1554 deletions

View File

@@ -1,11 +1,10 @@
name: DMS Copr Stable Release
on:
push:
tags:
- 'v*'
release:
types: [published]
workflow_run:
workflows: ["Create Release from DMS"]
types: [completed]
branches: [master]
workflow_dispatch:
inputs:
version:
@@ -16,7 +15,8 @@ on:
jobs:
build-and-upload:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -27,20 +27,14 @@ jobs:
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
echo "Using manual version: $VERSION"
elif [ "${{ github.event_name }}" = "release" ]; then
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "Using release version: $VERSION"
elif [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
echo "Using tag version: $VERSION"
elif [ "${{ github.event_name }}" = "workflow_run" ]; then
VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Using latest release version from workflow_run: $VERSION"
else
# Fallback to latest release
VERSION=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | jq -r '.tag_name' | sed 's/^v//')
VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Using latest release version: $VERSION"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "✅ Building DMS stable version: $VERSION"
@@ -94,6 +88,7 @@ jobs:
BuildRequires: wget
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli
Requires: dgop
Requires: fira-code-fonts
@@ -179,18 +174,59 @@ jobs:
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.git*
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.github
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/*.spec
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -f %{buildroot}%{_datadir}/quickshell/dms/*.spec
%posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
# Restart DMS for active users after upgrade
if [ "$1" -ge 2 ]; then
# Find all quickshell DMS processes (PID and username)
while read pid cmd; do
username=$(ps -o user= -p "$pid" 2>/dev/null)
[ "$username" = "root" ] && continue
[ -z "$username" ] && continue
# Get user's UID and validate session
user_uid=$(id -u "$username" 2>/dev/null)
[ -z "$user_uid" ] && continue
[ ! -d "/run/user/$user_uid" ] && continue
wayland_display=$(tr '\0' '\n' < /proc/$pid/environ 2>/dev/null | grep '^WAYLAND_DISPLAY=' | cut -d= -f2)
[ -z "$wayland_display" ] && continue
echo "Restarting DMS for user: $username"
# Run as user with full Wayland session environment
runuser -u "$username" -- /bin/sh -c "
export XDG_RUNTIME_DIR=/run/user/$user_uid
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$user_uid/bus
export WAYLAND_DISPLAY=$wayland_display
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:\$PATH
dms restart >/dev/null 2>&1
" 2>/dev/null || true
break
done < <(pgrep -a -f 'quickshell.*dms' 2>/dev/null)
fi
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_sysconfdir}/xdg/quickshell/dms/
%{_datadir}/quickshell/dms/
%files -n dms-cli
%{_bindir}/dms

View File

@@ -113,6 +113,7 @@ jobs:
"ja:translations/poexports/ja.json"
"zh-Hans:translations/poexports/zh_CN.json"
"pt-br:translations/poexports/pt.json"
"tr:translations/poexports/tr.json"
)
ANY_CHANGED=false

View File

@@ -7,6 +7,7 @@ on:
permissions:
contents: write
actions: write
concurrency:
group: release-${{ github.event.client_payload.tag }}
@@ -48,9 +49,9 @@ jobs:
set -e
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50)
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" --author='^(?!github-actions\[bot\])' | head -50)
else
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" --author='^(?!github-actions\[bot\])' "${PREVIOUS_TAG}..${TAG}")
fi
cat > RELEASE_BODY.md << 'EOF'
@@ -216,4 +217,5 @@ jobs:
tag_name: ${{ env.TAG }}
files: _release_assets/**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -67,7 +67,7 @@ Singleton {
}
function migrateFromUndefinedToV1(cache) {
console.log("CacheData: Migrating configuration from undefined to version 1")
console.info("CacheData: Migrating configuration from undefined to version 1")
}
function cleanupUnusedKeys() {
@@ -115,7 +115,7 @@ Singleton {
}
onLoadFailed: error => {
if (!isGreeterMode) {
console.log("CacheData: No cache file found, starting fresh")
console.info("CacheData: No cache file found, starting fresh")
}
}
}

View File

@@ -43,7 +43,7 @@ Singleton {
try {
root.translations = JSON.parse(text())
root.translationsLoaded = true
console.log(`I18n: Loaded translations for '${root.currentLocale}' ` +
console.info(`I18n: Loaded translations for '${root.currentLocale}' ` +
`(${Object.keys(root.translations).length} contexts)`)
} catch (e) {
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
@@ -84,7 +84,7 @@ Singleton {
_selectedPath = fileUrl
translationsLoaded = false
translations = ({})
console.log(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
console.info(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
}
function _fallbackToEnglish() {

View File

@@ -196,7 +196,7 @@ Singleton {
}
function migrateFromUndefinedToV1(settings) {
console.log("SessionData: Migrating configuration from undefined to version 1")
console.info("SessionData: Migrating configuration from undefined to version 1")
if (typeof SettingsData !== "undefined") {
if (settings.acMonitorTimeout !== undefined) {
SettingsData.setAcMonitorTimeout(settings.acMonitorTimeout)
@@ -707,7 +707,7 @@ Singleton {
running: false
onExited: exitCode => {
if (exitCode === 0) {
console.log("Copied default-session.json to session.json")
console.info("Copied default-session.json to session.json")
settingsFile.reload()
}
}

View File

@@ -66,6 +66,8 @@ Singleton {
property int animationSpeed: SettingsData.AnimationSpeed.Short
property int customAnimationDuration: 500
property string wallpaperFillMode: "Fill"
property bool blurredWallpaperLayer: false
property bool blurWallpaperOnOverview: false
property bool showLauncherButton: true
property bool showWorkspaceSwitcher: true
@@ -189,6 +191,7 @@ Singleton {
property bool lockBeforeSuspend: false
property bool loginctlLockIntegration: true
property string launchPrefix: ""
property var brightnessDevicePins: ({})
property bool gtkThemingEnabled: false
property bool qtThemingEnabled: false
@@ -202,6 +205,7 @@ Singleton {
property real dockSpacing: 4
property real dockBottomGap: 0
property real dockIconSize: 40
property string dockIndicatorStyle: "circle"
property bool notificationOverlayEnabled: false
property bool dankBarAutoHide: false
@@ -257,6 +261,7 @@ Singleton {
property string updaterTerminalAdditionalParams: ""
property var screenPreferences: ({})
property var showOnLastDisplay: ({})
signal forceDankBarLayoutRefresh
signal forceDockLayoutRefresh
@@ -318,7 +323,7 @@ Singleton {
} else if (settings.themeIndex >= 0 && settings.themeIndex < themeNames.length) {
currentThemeName = themeNames[settings.themeIndex]
}
console.log("Auto-migrated theme from index", settings.themeIndex, "to", currentThemeName)
console.info("Auto-migrated theme from index", settings.themeIndex, "to", currentThemeName)
} else {
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue"
}
@@ -450,6 +455,7 @@ Singleton {
dockSpacing = settings.dockSpacing !== undefined ? settings.dockSpacing : 4
dockBottomGap = settings.dockBottomGap !== undefined ? settings.dockBottomGap : 0
dockIconSize = settings.dockIconSize !== undefined ? settings.dockIconSize : 40
dockIndicatorStyle = settings.dockIndicatorStyle !== undefined ? settings.dockIndicatorStyle : "circle"
cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12
notificationOverlayEnabled = settings.notificationOverlayEnabled !== undefined ? settings.notificationOverlayEnabled : false
dankBarAutoHide = settings.dankBarAutoHide !== undefined ? settings.dankBarAutoHide : (settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false)
@@ -491,7 +497,10 @@ Singleton {
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch"
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
showOnLastDisplay = settings.showOnLastDisplay !== undefined ? settings.showOnLastDisplay : ({})
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill"
blurredWallpaperLayer = settings.blurredWallpaperLayer !== undefined ? settings.blurredWallpaperLayer : false
blurWallpaperOnOverview = settings.blurWallpaperOnOverview !== undefined ? settings.blurWallpaperOnOverview : false
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short
customAnimationDuration = settings.customAnimationDuration !== undefined ? settings.customAnimationDuration : 500
acMonitorTimeout = settings.acMonitorTimeout !== undefined ? settings.acMonitorTimeout : 0
@@ -505,6 +514,7 @@ Singleton {
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
loginctlLockIntegration = settings.loginctlLockIntegration !== undefined ? settings.loginctlLockIntegration : true
launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : ""
brightnessDevicePins = settings.brightnessDevicePins !== undefined ? settings.brightnessDevicePins : ({})
if (settings.configVersion === undefined) {
migrateFromUndefinedToV1(settings)
@@ -631,6 +641,7 @@ Singleton {
"dockSpacing": dockSpacing,
"dockBottomGap": dockBottomGap,
"dockIconSize": dockIconSize,
"dockIndicatorStyle": dockIndicatorStyle,
"cornerRadius": cornerRadius,
"notificationOverlayEnabled": notificationOverlayEnabled,
"dankBarAutoHide": dankBarAutoHide,
@@ -656,6 +667,8 @@ Singleton {
"widgetBackgroundColor": widgetBackgroundColor,
"surfaceBase": surfaceBase,
"wallpaperFillMode": wallpaperFillMode,
"blurredWallpaperLayer": blurredWallpaperLayer,
"blurWallpaperOnOverview": blurWallpaperOnOverview,
"notificationTimeoutLow": notificationTimeoutLow,
"notificationTimeoutNormal": notificationTimeoutNormal,
"notificationTimeoutCritical": notificationTimeoutCritical,
@@ -672,6 +685,7 @@ Singleton {
"updaterCustomCommand": updaterCustomCommand,
"updaterTerminalAdditionalParams": updaterTerminalAdditionalParams,
"screenPreferences": screenPreferences,
"showOnLastDisplay": showOnLastDisplay,
"animationSpeed": animationSpeed,
"customAnimationDuration": customAnimationDuration,
"acMonitorTimeout": acMonitorTimeout,
@@ -685,6 +699,7 @@ Singleton {
"lockBeforeSuspend": lockBeforeSuspend,
"loginctlLockIntegration": loginctlLockIntegration,
"launchPrefix": launchPrefix,
"brightnessDevicePins": brightnessDevicePins,
"configVersion": settingsConfigVersion
}, null, 2))
}
@@ -696,7 +711,7 @@ Singleton {
}
function migrateFromUndefinedToV1(settings) {
console.log("SettingsData: Migrating configuration from undefined to version 1")
console.info("SettingsData: Migrating configuration from undefined to version 1")
}
function cleanupUnusedKeys() {
@@ -724,7 +739,7 @@ Singleton {
"notepadTransparencyOverride", "notepadLastCustomTransparency", "soundsEnabled",
"useSystemSoundTheme", "soundNewNotification", "soundVolumeChanged", "soundPluggedIn", "gtkThemingEnabled",
"qtThemingEnabled", "syncModeWithPortal", "showDock", "dockAutoHide", "dockGroupByApp",
"dockOpenOnOverview", "dockPosition", "dockSpacing", "dockBottomGap", "dockIconSize",
"dockOpenOnOverview", "dockPosition", "dockSpacing", "dockBottomGap", "dockIconSize", "dockIndicatorStyle",
"cornerRadius", "notificationOverlayEnabled", "dankBarAutoHide",
"dankBarOpenOnOverview", "dankBarVisible", "dankBarSpacing", "dankBarBottomGap",
"dankBarInnerPadding", "dankBarSquareCorners", "dankBarNoBackground",
@@ -732,15 +747,15 @@ Singleton {
"dankBarBorderOpacity", "dankBarBorderThickness", "popupGapsAuto", "popupGapsManual",
"dankBarPosition", "lockScreenShowPowerActions", "enableFprint", "maxFprintTries",
"hideBrightnessSlider", "widgetBackgroundColor", "surfaceBase", "wallpaperFillMode",
"notificationTimeoutLow", "notificationTimeoutNormal", "notificationTimeoutCritical",
"blurredWallpaperLayer", "blurWallpaperOnOverview", "notificationTimeoutLow", "notificationTimeoutNormal", "notificationTimeoutCritical",
"notificationPopupPosition", "osdAlwaysShowValue", "powerActionConfirm",
"customPowerActionLock", "customPowerActionLogout", "customPowerActionSuspend",
"customPowerActionHibernate", "customPowerActionReboot", "customPowerActionPowerOff",
"updaterUseCustomCommand", "updaterCustomCommand", "updaterTerminalAdditionalParams",
"screenPreferences", "animationSpeed", "customAnimationDuration", "acMonitorTimeout", "acLockTimeout",
"screenPreferences", "showOnLastDisplay", "animationSpeed", "customAnimationDuration", "acMonitorTimeout", "acLockTimeout",
"acSuspendTimeout", "acHibernateTimeout", "batteryMonitorTimeout", "batteryLockTimeout",
"batterySuspendTimeout", "batteryHibernateTimeout", "lockBeforeSuspend",
"loginctlLockIntegration", "launchPrefix", "configVersion"
"loginctlLockIntegration", "launchPrefix", "brightnessDevicePins", "configVersion"
]
try {
@@ -937,7 +952,11 @@ Singleton {
if (prefs.includes("all")) {
return Quickshell.screens
}
return Quickshell.screens.filter(screen => prefs.includes(screen.name))
var filtered = Quickshell.screens.filter(screen => prefs.includes(screen.name))
if (filtered.length === 0 && showOnLastDisplay && showOnLastDisplay[componentId] && Quickshell.screens.length === 1) {
return Quickshell.screens
}
return filtered
}
function sendTestNotifications() {
@@ -1037,6 +1056,7 @@ Singleton {
function setCornerRadius(radius) {
cornerRadius = radius
saveSettings()
NiriService.generateNiriLayoutConfig()
}
function setClockFormat(use24Hour) {
@@ -1074,6 +1094,16 @@ Singleton {
saveSettings()
}
function setBlurredWallpaperLayer(enabled) {
blurredWallpaperLayer = enabled
saveSettings()
}
function setBlurWallpaperOnOverview(enabled) {
blurWallpaperOnOverview = enabled
saveSettings()
}
function setShowLauncherButton(enabled) {
showLauncherButton = enabled
saveSettings()
@@ -1607,6 +1637,11 @@ Singleton {
saveSettings()
}
function setDockIndicatorStyle(style) {
dockIndicatorStyle = style
saveSettings()
}
function setNotificationOverlayEnabled(enabled) {
notificationOverlayEnabled = enabled
saveSettings()
@@ -1801,6 +1836,16 @@ Singleton {
saveSettings()
}
function setShowOnLastDisplay(prefs) {
showOnLastDisplay = prefs
saveSettings()
}
function setBrightnessDevicePins(pins) {
brightnessDevicePins = pins
saveSettings()
}
function getPluginSetting(pluginId, key, defaultValue) {
if (!pluginSettings[pluginId]) {
return defaultValue
@@ -1995,7 +2040,7 @@ Singleton {
running: false
onExited: exitCode => {
if (exitCode === 0) {
console.log("Copied default-settings.json to settings.json")
console.info("Copied default-settings.json to settings.json")
settingsFile.reload()
} else {
applyStoredTheme()

View File

@@ -92,7 +92,7 @@ Singleton {
}
if (colorsFileLoadFailed && currentTheme === dynamic && wallpaperPath) {
console.log("Theme: Matugen now available, regenerating colors for dynamic theme")
console.info("Theme: Matugen now available, regenerating colors for dynamic theme")
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
@@ -701,7 +701,7 @@ Singleton {
return
}
console.log("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", "type:", matugenType)
console.info("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", "type:", matugenType)
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
NiriService.suppressNextToast()
@@ -908,7 +908,7 @@ Singleton {
workerRunning = false
if (exitCode === 0) {
console.log("Theme: Matugen worker completed successfully")
console.info("Theme: Matugen worker completed successfully")
if (currentTheme === dynamic) {
console.log("Theme: Reloading dynamic colors file")
dynamicColorsFileView.reload()
@@ -983,7 +983,7 @@ Singleton {
onLoaded: {
if (currentTheme === dynamic) {
console.log("Theme: Dynamic colors file loaded successfully")
console.info("Theme: Dynamic colors file loaded successfully")
colorsFileLoadFailed = false
parseAndLoadColors()
}
@@ -997,7 +997,7 @@ Singleton {
onLoadFailed: function (error) {
if (currentTheme === dynamic) {
console.log("Theme: Dynamic colors file load failed, marking for regeneration")
console.warn("Theme: Dynamic colors file load failed, marking for regeneration")
colorsFileLoadFailed = true
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!isGreeterMode && matugenAvailable && wallpaperPath) {

View File

@@ -46,12 +46,20 @@ Item {
item.popoutService = PopoutService
}
item.pluginId = pluginId
console.log("Daemon plugin loaded:", pluginId)
console.info("Daemon plugin loaded:", pluginId)
}
}
}
}
Loader {
id: blurredWallpaperBackgroundLoader
active: SettingsData.blurredWallpaperLayer
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
WallpaperBackground {}
Lock {
@@ -208,8 +216,8 @@ Item {
Connections {
target: NetworkService
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason) {
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason)
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService)
}
}

View File

@@ -0,0 +1,362 @@
import QtQuick
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property string deviceName: ""
property string deviceAddress: ""
property string requestType: ""
property string token: ""
property int passkey: 0
property string pinInput: ""
property string passkeyInput: ""
function show(pairingData) {
token = pairingData.token || ""
deviceName = pairingData.deviceName || ""
deviceAddress = pairingData.deviceAddr || ""
requestType = pairingData.requestType || ""
passkey = pairingData.passkey || 0
pinInput = ""
passkeyInput = ""
open()
Qt.callLater(() => {
if (contentLoader.item) {
if (requestType === "pin" && contentLoader.item.pinInputField) {
contentLoader.item.pinInputField.forceActiveFocus()
} else if (requestType === "passkey" && contentLoader.item.passkeyInputField) {
contentLoader.item.passkeyInputField.forceActiveFocus()
}
}
})
}
shouldBeVisible: false
width: 420
height: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 240
onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) {
pinInput = ""
passkeyInput = ""
}
}
onOpened: {
Qt.callLater(() => {
if (contentLoader.item) {
if (requestType === "pin" && contentLoader.item.pinInputField) {
contentLoader.item.pinInputField.forceActiveFocus()
} else if (requestType === "passkey" && contentLoader.item.passkeyInputField) {
contentLoader.item.passkeyInputField.forceActiveFocus()
}
}
})
}
onBackgroundClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
content: Component {
FocusScope {
id: pairingContent
property alias pinInputField: pinInputField
property alias passkeyInputField: passkeyInputField
anchors.fill: parent
focus: true
implicitHeight: mainColumn.implicitHeight
Keys.onEscapePressed: event => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
event.accepted = true
}
Column {
id: mainColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingM
spacing: requestType === "pin" || requestType === "passkey" ? Theme.spacingM : Theme.spacingS
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Pair Bluetooth Device")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: {
if (requestType === "confirm")
return I18n.tr("Confirm passkey for ") + deviceName
if (requestType === "authorize")
return I18n.tr("Authorize pairing with ") + deviceName
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize service for ") + deviceName
if (requestType === "pin")
return I18n.tr("Enter PIN for ") + deviceName
if (requestType === "passkey")
return I18n.tr("Enter passkey for ") + deviceName
return deviceName
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width - 40
elide: Text.ElideRight
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: pinInputField.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: pinInputField.activeFocus ? 2 : 1
visible: requestType === "pin"
MouseArea {
anchors.fill: parent
onClicked: () => {
pinInputField.forceActiveFocus()
}
}
DankTextField {
id: pinInputField
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: pinInput
placeholderText: I18n.tr("Enter PIN")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
pinInput = text
}
onAccepted: () => {
submitPairing()
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: passkeyInputField.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: passkeyInputField.activeFocus ? 2 : 1
visible: requestType === "passkey"
MouseArea {
anchors.fill: parent
onClicked: () => {
passkeyInputField.forceActiveFocus()
}
}
DankTextField {
id: passkeyInputField
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: passkeyInput
placeholderText: I18n.tr("Enter 6-digit passkey")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
passkeyInput = text
}
onAccepted: () => {
submitPairing()
}
}
}
Rectangle {
width: parent.width
height: 56
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
visible: requestType === "confirm"
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: I18n.tr("Passkey:")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: String(passkey).padStart(6, "0")
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Item {
width: parent.width
height: 36
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
StyledText {
id: cancelText
anchors.centerIn: parent
text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
}
}
Rectangle {
width: Math.max(80, pairText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: pairArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: {
if (requestType === "pin")
return pinInput.length > 0
if (requestType === "passkey")
return passkeyInput.length === 6
return true
}
opacity: enabled ? 1 : 0.5
StyledText {
id: pairText
anchors.centerIn: parent
text: {
if (requestType === "confirm")
return I18n.tr("Confirm")
if (requestType === "authorize" || requestType.startsWith("authorize-service"))
return I18n.tr("Authorize")
return I18n.tr("Pair")
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: pairArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
submitPairing()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
DankActionButton {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
}
}
}
function submitPairing() {
const secrets = {}
if (requestType === "pin") {
secrets["pin"] = pinInput
} else if (requestType === "passkey") {
secrets["passkey"] = passkeyInput
} else if (requestType === "confirm" || requestType === "authorize" || requestType.startsWith("authorize-service")) {
secrets["decision"] = "yes"
}
DMSService.bluetoothSubmitPairing(token, secrets, true, response => {
if (response.error) {
ToastService.showError(I18n.tr("Pairing failed"), response.error)
}
})
close()
pinInput = ""
passkeyInput = ""
}
}

View File

@@ -21,6 +21,11 @@ DankModal {
property var promptFields: []
property string promptSetting: ""
property bool isVpnPrompt: false
property string connectionName: ""
property string vpnServiceType: ""
property string connectionType: ""
function show(ssid) {
wifiPasswordSSID = ssid
wifiPasswordInput = ""
@@ -32,6 +37,10 @@ DankModal {
promptReason = ""
promptFields = []
promptSetting = ""
isVpnPrompt = false
connectionName = ""
vpnServiceType = ""
connectionType = ""
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
requiresEnterprise = network?.enterprise || false
@@ -48,13 +57,18 @@ DankModal {
})
}
function showFromPrompt(token, ssid, setting, fields, hints, reason) {
wifiPasswordSSID = ssid
function showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
isPromptMode = true
promptToken = token
promptReason = reason
promptFields = fields || []
promptSetting = setting || "802-11-wireless-security"
connectionType = connType || "802-11-wireless"
connectionName = connName || ssid || ""
vpnServiceType = vpnService || ""
isVpnPrompt = (connectionType === "vpn" || connectionType === "wireguard")
wifiPasswordSSID = isVpnPrompt ? connectionName : ssid
requiresEnterprise = setting === "802-1x"
@@ -163,7 +177,12 @@ DankModal {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Connect to Wi-Fi")
text: {
if (isVpnPrompt) {
return I18n.tr("Connect to VPN")
}
return I18n.tr("Connect to Wi-Fi")
}
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
@@ -175,6 +194,9 @@ DankModal {
StyledText {
text: {
if (isVpnPrompt) {
return I18n.tr("Enter password for ") + wifiPasswordSSID
}
const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ")
return prefix + wifiPasswordSSID
}
@@ -218,7 +240,7 @@ DankModal {
color: Theme.surfaceHover
border.color: usernameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: usernameInput.activeFocus ? 2 : 1
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
MouseArea {
anchors.fill: parent
@@ -271,7 +293,7 @@ DankModal {
textColor: Theme.surfaceText
text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
placeholderText: requiresEnterprise ? I18n.tr("Password") : ""
placeholderText: (requiresEnterprise && !isVpnPrompt) ? I18n.tr("Password") : ""
backgroundColor: "transparent"
focus: !requiresEnterprise
enabled: root.shouldBeVisible
@@ -281,7 +303,9 @@ DankModal {
onAccepted: () => {
if (isPromptMode) {
const secrets = {}
if (promptSetting === "802-11-wireless-security") {
if (isVpnPrompt) {
if (passwordInput.text) secrets["password"] = passwordInput.text
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text
} else if (promptSetting === "802-1x") {
if (usernameInput.text) secrets["identity"] = usernameInput.text
@@ -340,7 +364,7 @@ DankModal {
}
Rectangle {
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
@@ -372,7 +396,7 @@ DankModal {
}
Rectangle {
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
@@ -495,7 +519,12 @@ DankModal {
height: 36
radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
enabled: {
if (isVpnPrompt) {
return passwordInput.text.length > 0
}
return requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
}
opacity: enabled ? 1 : 0.5
StyledText {
@@ -518,7 +547,9 @@ DankModal {
onClicked: () => {
if (isPromptMode) {
const secrets = {}
if (promptSetting === "802-11-wireless-security") {
if (isVpnPrompt) {
if (passwordInput.text) secrets["password"] = passwordInput.text
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text
} else if (promptSetting === "802-1x") {
if (usernameInput.text) secrets["identity"] = usernameInput.text

View File

@@ -0,0 +1,135 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules
Variants {
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens
}
return SettingsData.getFilteredScreens("wallpaper")
}
PanelWindow {
id: blurWallpaperWindow
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.namespace: "dms:blurwallpaper"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
anchors.top: true
anchors.bottom: true
anchors.left: true
anchors.right: true
color: "transparent"
Item {
id: root
anchors.fill: parent
property string source: SessionData.getMonitorWallpaper(modelData.name) || ""
property bool isColorSource: source.startsWith("#")
Connections {
target: SessionData
function onIsLightModeChanged() {
if (SessionData.perModeWallpaper) {
var newSource = SessionData.getMonitorWallpaper(modelData.name) || ""
if (newSource !== root.source) {
root.source = newSource
}
}
}
}
function getFillMode(modeName) {
switch(modeName) {
case "Stretch": return Image.Stretch
case "Fit":
case "PreserveAspectFit": return Image.PreserveAspectFit
case "Fill":
case "PreserveAspectCrop": return Image.PreserveAspectCrop
case "Tile": return Image.Tile
case "TileVertically": return Image.TileVertically
case "TileHorizontally": return Image.TileHorizontally
case "Pad": return Image.Pad
default: return Image.PreserveAspectCrop
}
}
WallpaperEngineProc {
id: weProc
monitor: modelData.name
}
Component.onCompleted: {
if (source) {
const formattedSource = source.startsWith("file://") ? source : "file://" + source
wallpaperImage.source = formattedSource
}
}
Component.onDestruction: {
weProc.stop()
}
onSourceChanged: {
const isWE = source.startsWith("we:")
const isColor = source.startsWith("#")
if (isWE) {
wallpaperImage.source = ""
weProc.start(source.substring(3))
} else {
weProc.stop()
if (!source) {
wallpaperImage.source = ""
} else if (isColor) {
wallpaperImage.source = ""
} else {
wallpaperImage.source = source.startsWith("file://") ? source : "file://" + source
}
}
}
Loader {
anchors.fill: parent
active: !root.source || root.isColorSource
asynchronous: true
sourceComponent: DankBackdrop {
screenName: modelData.name
}
}
Image {
id: wallpaperImage
anchors.fill: parent
visible: false
asynchronous: true
smooth: true
cache: true
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
}
MultiEffect {
anchors.fill: parent
source: wallpaperImage
blurEnabled: true
blur: 0.8
blurMax: 48
}
}
}
}

View File

@@ -10,26 +10,26 @@ PluginComponent {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
ccWidgetIcon: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetSecondaryText: {
if (!VpnService.connected)
if (!DMSNetworkService.connected)
return "Disconnected"
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return names[0] || "Connected"
return names[0] + " +" + (names.length - 1)
}
ccWidgetIsActive: VpnService.connected
ccWidgetIsActive: DMSNetworkService.connected
onCcWidgetToggled: {
if (VpnService.connected) {
VpnService.disconnectAllActive()
} else if (VpnService.profiles.length > 0) {
VpnService.connect(VpnService.profiles[0].uuid)
if (DMSNetworkService.connected) {
DMSNetworkService.disconnectAllActive()
} else if (DMSNetworkService.profiles.length > 0) {
DMSNetworkService.connect(DMSNetworkService.profiles[0].uuid)
}
}
@@ -52,9 +52,9 @@ PluginComponent {
StyledText {
text: {
if (!VpnService.connected)
if (!DMSNetworkService.connected)
return "Active: None"
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return "Active: " + (names[0] || "VPN")
return "Active: " + names[0] + " +" + (names.length - 1)
@@ -72,7 +72,7 @@ PluginComponent {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
visible: DMSNetworkService.connected
width: 110
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
@@ -99,7 +99,7 @@ PluginComponent {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
onClicked: DMSNetworkService.disconnectAllActive()
}
}
}
@@ -123,7 +123,7 @@ PluginComponent {
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
@@ -154,7 +154,7 @@ PluginComponent {
}
Repeater {
model: VpnService.profiles
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
@@ -162,9 +162,9 @@ PluginComponent {
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
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
@@ -174,9 +174,9 @@ PluginComponent {
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
@@ -187,7 +187,7 @@ PluginComponent {
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
@@ -234,7 +234,7 @@ PluginComponent {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}
}

View File

@@ -10,6 +10,7 @@ Item {
property string expandedSection: ""
property var expandedWidgetData: null
property var bluetoothCodecSelector: null
property string screenName: ""
property var pluginDetailInstance: null
property var widgetModel: null
@@ -205,8 +206,9 @@ Item {
Component {
id: brightnessDetailComponent
BrightnessDetail {
currentDeviceName: root.expandedWidgetData?.deviceName || ""
initialDeviceName: root.expandedWidgetData?.deviceName || ""
instanceId: root.expandedWidgetData?.instanceId || ""
screenName: root.screenName
}
}
}

View File

@@ -15,6 +15,8 @@ Column {
property var expandedWidgetData: null
property var bluetoothCodecSelector: null
property bool darkModeTransitionPending: false
property string screenName: ""
property var parentScreen: null
signal expandClicked(var widgetData, int globalIndex)
signal removeWidget(int index)
@@ -182,6 +184,7 @@ Column {
bluetoothCodecSelector: root.bluetoothCodecSelector
widgetModel: root.model
collapseCallback: root.requestCollapse
screenName: root.screenName
}
}
}
@@ -329,7 +332,10 @@ Column {
return "Select device"
if (AudioService.sink.audio.muted)
return "Muted"
return Math.round(AudioService.sink.audio.volume * 100) + "%"
const volume = AudioService.sink.audio.volume
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
}
case "audioInput":
{
@@ -337,7 +343,10 @@ Column {
return "Select device"
if (AudioService.source.audio.muted)
return "Muted"
return Math.round(AudioService.source.audio.volume * 100) + "%"
const volume = AudioService.source.audio.volume
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
}
default:
return widgetDef?.description || ""
@@ -472,6 +481,8 @@ Column {
height: 14
deviceName: widgetData.deviceName || ""
instanceId: widgetData.instanceId || ""
screenName: root.screenName
parentScreen: root.parentScreen
property color sliderTrackColor: Theme.surfaceContainerHigh
onIconClicked: {

View File

@@ -154,6 +154,8 @@ DankPopout {
model: widgetModel
bluetoothCodecSelector: bluetoothCodecSelector
colorPickerModal: root.colorPickerModal
screenName: root.triggerScreen?.name || ""
parentScreen: root.triggerScreen
onExpandClicked: (widgetData, globalIndex) => {
root.expandedWidgetIndex = globalIndex
root.expandedWidgetData = widgetData

View File

@@ -5,8 +5,11 @@ import Quickshell.Bluetooth
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modals
Rectangle {
id: root
implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
@@ -14,9 +17,33 @@ Rectangle {
border.width: 0
property var bluetoothCodecModalRef: null
property var devicesBeingPaired: new Set()
signal showCodecSelector(var device)
function isDeviceBeingPaired(deviceAddress) {
return devicesBeingPaired.has(deviceAddress)
}
function handlePairDevice(device) {
if (!device) return
const deviceAddr = device.address
devicesBeingPaired.add(deviceAddr)
devicesBeingPairedChanged()
BluetoothService.pairDevice(device, function(response) {
devicesBeingPaired.delete(deviceAddr)
devicesBeingPairedChanged()
if (response.error) {
ToastService.showError(I18n.tr("Pairing failed"), response.error)
} else if (!BluetoothService.enhancedPairingAvailable) {
ToastService.showSuccess(I18n.tr("Device paired"))
}
})
}
function updateDeviceCodecDisplay(deviceAddress, codecName) {
for (let i = 0; i < pairedRepeater.count; i++) {
let item = pairedRepeater.itemAt(i)
@@ -327,7 +354,7 @@ Rectangle {
required property int index
property bool canConnect: BluetoothService.canConnect(modelData)
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
property bool isBusy: BluetoothService.isDeviceBusy(modelData) || isDeviceBeingPaired(modelData.address)
width: parent.width
height: 50
@@ -335,7 +362,7 @@ Rectangle {
color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0
opacity: canConnect ? 1 : 0.6
opacity: (canConnect && !isBusy) ? 1 : 0.6
Row {
anchors.left: parent.left
@@ -367,7 +394,7 @@ Rectangle {
StyledText {
text: {
if (modelData.pairing) return "Pairing..."
if (modelData.pairing || isBusy) return "Pairing..."
if (modelData.blocked) return "Blocked"
return BluetoothService.getSignalStrength(modelData)
}
@@ -390,12 +417,12 @@ Rectangle {
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
text: {
if (modelData.pairing) return "Pairing..."
if (isBusy) return "Pairing..."
if (!canConnect) return "Cannot pair"
return "Pair"
}
font.pixelSize: Theme.fontSizeSmall
color: canConnect ? Theme.primary : Theme.surfaceVariantText
color: (canConnect && !isBusy) ? Theme.primary : Theme.surfaceVariantText
font.weight: Font.Medium
}
@@ -406,9 +433,7 @@ Rectangle {
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: canConnect && !isBusy
onClicked: {
if (modelData) {
BluetoothService.connectDeviceWithTrust(modelData)
}
root.handlePairDevice(modelData)
}
}
@@ -516,10 +541,30 @@ Rectangle {
onTriggered: {
if (bluetoothContextMenu.currentDevice) {
bluetoothContextMenu.currentDevice.forget()
if (BluetoothService.enhancedPairingAvailable) {
const devicePath = BluetoothService.getDevicePath(bluetoothContextMenu.currentDevice)
DMSService.bluetoothRemove(devicePath, response => {
if (response.error) {
ToastService.showError(I18n.tr("Failed to remove device"), response.error)
}
})
} else {
bluetoothContextMenu.currentDevice.forget()
}
}
}
}
}
BluetoothPairingModal {
id: bluetoothPairingModal
}
Connections {
target: DMSService
function onBluetoothPairingRequest(data) {
bluetoothPairingModal.show(data)
}
}
}

View File

@@ -8,11 +8,76 @@ import qs.Widgets
Rectangle {
id: root
property string currentDeviceName: ""
property string initialDeviceName: ""
property string instanceId: ""
property string screenName: ""
signal deviceNameChanged(string newDeviceName)
property string currentDeviceName: ""
function resolveDeviceName() {
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
return ""
}
if (screenName && screenName.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}
const pinnedDevice = pins[screenName]
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
if (found) {
return found.name
}
}
}
if (initialDeviceName && initialDeviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName)
if (found) {
return found.name
}
}
const currentDeviceNameFromService = DisplayService.currentDevice
if (currentDeviceNameFromService) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService)
if (found) {
return found.name
}
}
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
}
Component.onCompleted: {
currentDeviceName = resolveDeviceName()
}
property bool isPinnedToScreen: {
if (!screenName || screenName.length === 0) {
return false
}
const pins = SettingsData.brightnessDevicePins || {}
return pins[screenName] === currentDeviceName
}
function togglePinToScreen() {
if (!screenName || screenName.length === 0 || !currentDeviceName || currentDeviceName.length === 0) {
return
}
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
if (isPinnedToScreen) {
delete pins[screenName]
} else {
pins[screenName] = currentDeviceName
}
SettingsData.setBrightnessDevicePins(pins)
}
implicitHeight: brightnessContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
@@ -61,6 +126,74 @@ Rectangle {
}
}
Rectangle {
width: parent.width
height: 40
visible: screenName && screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
Item {
anchors.fill: parent
anchors.margins: Theme.spacingM
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "monitor"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: screenName || "Unknown Monitor"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: pinRow.width + Theme.spacingS * 2
height: 28
radius: height / 2
color: isPinnedToScreen ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
Row {
id: pinRow
anchors.centerIn: parent
spacing: 4
DankIcon {
name: isPinnedToScreen ? "push_pin" : "push_pin"
size: 16
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: isPinnedToScreen ? "Pinned" : "Pin"
font.pixelSize: Theme.fontSizeSmall
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.togglePinToScreen()
}
}
}
}
Repeater {
model: DisplayService.devices || []
delegate: Rectangle {
@@ -90,7 +223,7 @@ Rectangle {
const deviceName = modelData.name || ""
if (deviceClass === "backlight" || deviceClass === "ddc") {
const brightness = modelData.percentage || 50
const brightness = DisplayService.getDeviceBrightness(modelData.name)
if (brightness <= 33) return "brightness_low"
if (brightness <= 66) return "brightness_medium"
return "brightness_high"
@@ -106,7 +239,7 @@ Rectangle {
}
StyledText {
text: (modelData.percentage || 50) + "%"
text: Math.round(DisplayService.getDeviceBrightness(modelData.name)) + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -34,6 +34,10 @@ Rectangle {
return 1
}
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
return 1
}
const pref = NetworkService.userPreference
const status = NetworkService.networkStatus
let index = 1
@@ -76,7 +80,7 @@ Rectangle {
DankButtonGroup {
id: preferenceControls
anchors.verticalCenter: parent.verticalCenter
visible: DMSService.apiVersion >= 5
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex
@@ -196,7 +200,7 @@ Rectangle {
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
visible: currentPreferenceIndex === 0 && DMSService.apiVersion >= 5
visible: currentPreferenceIndex === 0 && NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
contentHeight: wiredColumn.height
clip: true

View File

@@ -143,8 +143,8 @@ QtObject {
"description": "VPN connections",
"icon": "vpn_key",
"type": "builtin_plugin",
"enabled": VpnService.available,
"warning": !VpnService.available ? "VPN not available" : undefined,
"enabled": DMSNetworkService.available,
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
"isBuiltinPlugin": true
}]

View File

@@ -1,63 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSource: AudioService.source
iconName: {
if (!defaultSource) return "mic_off"
let volume = defaultSource.audio.volume
let muted = defaultSource.audio.muted
if (muted || volume === 0.0) return "mic_off"
return "mic"
}
isActive: defaultSource && !defaultSource.audio.muted
primaryText: {
if (!defaultSource) {
return "No input device"
}
return defaultSource.description || "Audio Input"
}
secondaryText: {
if (!defaultSource) {
return "Select device"
}
if (defaultSource.audio.muted) {
return "Muted"
}
return Math.round(defaultSource.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSource && defaultSource.audio) {
defaultSource.audio.muted = !defaultSource.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSource || !defaultSource.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSource.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSource.audio.muted = false
defaultSource.audio.volume = newVolume / 100
wheelEvent.accepted = true
}
}

View File

@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSink: AudioService.sink
iconName: {
if (!defaultSink) return "volume_off"
let volume = defaultSink.audio.volume
let muted = defaultSink.audio.muted
if (muted || volume === 0.0) return "volume_off"
if (volume <= 0.33) return "volume_down"
if (volume <= 0.66) return "volume_up"
return "volume_up"
}
isActive: defaultSink && !defaultSink.audio.muted
primaryText: {
if (!defaultSink) {
return "No output device"
}
return defaultSink.description || "Audio Output"
}
secondaryText: {
if (!defaultSink) {
return "Select device"
}
if (defaultSink.audio.muted) {
return "Muted"
}
return Math.round(defaultSink.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSink && defaultSink.audio) {
defaultSink.audio.muted = !defaultSink.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSink || !defaultSink.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSink.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSink.audio.muted = false
defaultSink.audio.volume = newVolume / 100
AudioService.volumeChanged()
wheelEvent.accepted = true
}
}

View File

@@ -10,6 +10,8 @@ Row {
property string deviceName: ""
property string instanceId: ""
property string screenName: ""
property var parentScreen: null
signal iconClicked()
@@ -21,6 +23,17 @@ Row {
return ""
}
if (screenName && screenName.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}
const pinnedDevice = pins[screenName]
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
if (found) {
return found.name
}
}
}
if (deviceName && deviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === deviceName)
return found ? found.name : ""
@@ -76,8 +89,10 @@ Row {
tooltipLoader.active = true
if (tooltipLoader.item) {
const tooltipText = targetDevice ? "bl device: " + targetDevice.name : "Backlight Control"
const p = iconArea.mapToItem(null, iconArea.width / 2, 0)
tooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
const globalPos = iconArea.mapToGlobal(iconArea.width / 2, iconArea.height / 2)
const screenY = root.parentScreen?.y ?? 0
const relativeY = globalPos.y - screenY - 55
tooltipLoader.item.show(tooltipText, globalPos.x, relativeY, root.parentScreen)
}
}
@@ -121,7 +136,7 @@ Row {
value: targetBrightness
onSliderValueChanged: function(newValue) {
if (DisplayService.brightnessAvailable && targetDeviceName) {
DisplayService.setBrightness(newValue, targetDeviceName)
DisplayService.setBrightness(newValue, targetDeviceName, true)
}
}
thumbOutlineColor: Theme.surfaceContainer

View File

@@ -13,8 +13,10 @@ Item {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
readonly property real spacing: noBackground ? 2 : Theme.spacingXS
property var centerWidgets: []
@@ -396,8 +398,47 @@ Item {
if ("barThickness" in item) {
item.barThickness = Qt.binding(() => root.barThickness)
}
if ("sectionSpacing" in item) {
item.sectionSpacing = Qt.binding(() => root.spacing)
}
if ("isFirst" in item) {
item.isFirst = Qt.binding(() => {
for (var i = 0; i < centerRepeater.count; i++) {
const checkItem = centerRepeater.itemAt(i)
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item
}
}
return false
})
}
if ("isLast" in item) {
item.isLast = Qt.binding(() => {
for (var i = centerRepeater.count - 1; i >= 0; i--) {
const checkItem = centerRepeater.itemAt(i)
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item
}
}
return false
})
}
if ("isLeftBarEdge" in item) {
item.isLeftBarEdge = false
}
if ("isRightBarEdge" in item) {
item.isRightBarEdge = false
}
if ("isTopBarEdge" in item) {
item.isTopBarEdge = false
}
if ("isBottomBarEdge" in item) {
item.isBottomBarEdge = false
}
// Inject PluginService for plugin widgets
if (item.pluginService !== undefined) {
var parts = model.widgetId.split(":")
var pluginId = parts[0]

View File

@@ -235,11 +235,11 @@ Item {
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
console.log("DankBar: Plugin loaded:", pluginId)
console.info("DankBar: Plugin loaded:", pluginId)
SettingsData.widgetDataChanged()
}
function onPluginUnloaded(pluginId) {
console.log("DankBar: Plugin unloaded:", pluginId)
console.info("DankBar: Plugin unloaded:", pluginId)
SettingsData.widgetDataChanged()
}
}
@@ -585,18 +585,18 @@ Item {
function getWidgetSection(parentItem) {
let current = parentItem
while (current) {
if (current.objectName === "leftSection" || current === hLeftSection || current === vLeftSection) {
if (current.objectName === "leftSection") {
return "left"
}
if (current.objectName === "centerSection" || current === hCenterSection || current === vCenterSection) {
if (current.objectName === "centerSection") {
return "center"
}
if (current.objectName === "rightSection" || current === hRightSection || current === vRightSection) {
if (current.objectName === "rightSection") {
return "right"
}
current = current.parent
}
return "left" // fallback
return "left"
}
readonly property var widgetVisibility: ({
@@ -695,6 +695,9 @@ Item {
LeftSection {
id: hLeftSection
objectName: "leftSection"
overrideAxisLayout: true
forceVerticalLayout: false
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
@@ -710,6 +713,9 @@ Item {
RightSection {
id: hRightSection
objectName: "rightSection"
overrideAxisLayout: true
forceVerticalLayout: false
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
@@ -725,6 +731,9 @@ Item {
CenterSection {
id: hCenterSection
objectName: "centerSection"
overrideAxisLayout: true
forceVerticalLayout: false
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
@@ -746,6 +755,9 @@ Item {
LeftSection {
id: vLeftSection
objectName: "leftSection"
overrideAxisLayout: true
forceVerticalLayout: true
width: parent.width
anchors {
top: parent.top
@@ -762,6 +774,9 @@ Item {
CenterSection {
id: vCenterSection
objectName: "centerSection"
overrideAxisLayout: true
forceVerticalLayout: true
width: parent.width
anchors {
verticalCenter: parent.verticalCenter
@@ -778,6 +793,9 @@ Item {
RightSection {
id: vRightSection
objectName: "rightSection"
overrideAxisLayout: true
forceVerticalLayout: true
width: parent.width
height: implicitHeight
anchors {
@@ -814,6 +832,7 @@ Item {
id: launcherButtonComponent
LauncherButton {
id: launcherButton
isActive: false
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
@@ -823,6 +842,12 @@ Item {
hyprlandOverviewLoader: root.hyprlandOverviewLoader
onClicked: {
appDrawerLoader.active = true
if (appDrawerLoader.item && appDrawerLoader.item.setTriggerPosition) {
const globalPos = launcherButton.visualContent.mapToGlobal(0, 0)
const currentScreen = barWindow.screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barWindow.effectiveBarThickness, launcherButton.visualWidth)
appDrawerLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, launcherButton.section, currentScreen)
}
appDrawerLoader.item?.toggle()
}
}
@@ -1214,13 +1239,19 @@ Item {
Component {
id: separatorComponent
Rectangle {
width: barWindow.isVertical ? barWindow.widgetThickness * 0.67 : 1
height: barWindow.isVertical ? 1 : barWindow.widgetThickness * 0.67
Item {
width: barWindow.isVertical ? parent.barThickness : 1
height: barWindow.isVertical ? 1 : parent.barThickness
implicitWidth: width
implicitHeight: height
color: Theme.outline
opacity: 0.3
Rectangle {
width: barWindow.isVertical ? parent.width * 0.6 : 1
height: barWindow.isVertical ? 1 : parent.height * 0.6
anchors.centerIn: parent
color: Theme.outline
opacity: 0.3
}
}
}

View File

@@ -11,8 +11,10 @@ Item {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
implicitHeight: layoutLoader.item ? (layoutLoader.item.implicitHeight || layoutLoader.item.height) : 0
implicitWidth: layoutLoader.item ? (layoutLoader.item.implicitWidth || layoutLoader.item.width) : 0
@@ -26,10 +28,13 @@ Item {
Component {
id: rowComp
Row {
spacing: noBackground ? 2 : Theme.spacingXS
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater {
id: rowRepeater
model: root.widgetsModel
Item {
readonly property real rowSpacing: parent.widgetSpacing
width: widgetLoader.item ? widgetLoader.item.width : 0
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
@@ -45,6 +50,11 @@ Item {
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
isLeftBarEdge: true
isRightBarEdge: false
}
}
}
@@ -55,10 +65,13 @@ Item {
id: columnComp
Column {
width: Math.max(parent.width, 200)
spacing: noBackground ? 2 : Theme.spacingXS
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater {
id: columnRepeater
model: root.widgetsModel
Item {
readonly property real columnSpacing: parent.widgetSpacing
width: parent.width
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
@@ -74,6 +87,11 @@ Item {
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing
isTopBarEdge: true
isBottomBarEdge: false
}
}
}

View File

@@ -235,7 +235,7 @@ DankPopout {
}
StyledText {
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
text: BatteryService.batteryStatus
font.pixelSize: Theme.fontSizeLarge
color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
@@ -247,6 +247,7 @@ DankPopout {
return Theme.surfaceText;
}
font.weight: Font.Medium
visible: BatteryService.batteryAvailable
anchors.verticalCenter: parent.verticalCenter
}
}

View File

@@ -14,7 +14,7 @@ DankPopout {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
property var triggerScreen: null
@@ -161,11 +161,11 @@ DankPopout {
StyledText {
text: {
if (!VpnService.connected) {
if (!DMSNetworkService.connected) {
return "Active: None";
}
const names = VpnService.activeNames || [];
const names = DMSNetworkService.activeNames || [];
if (names.length <= 1) {
return "Active: " + (names[0] || "VPN");
}
@@ -193,7 +193,7 @@ DankPopout {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
visible: DMSNetworkService.connected
width: 130
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
border.width: 0
@@ -224,7 +224,7 @@ DankPopout {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
onClicked: DMSNetworkService.disconnectAllActive()
}
}
@@ -251,7 +251,7 @@ DankPopout {
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
@@ -284,7 +284,7 @@ DankPopout {
}
Repeater {
model: VpnService.profiles
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
@@ -292,9 +292,9 @@ DankPopout {
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
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
@@ -304,9 +304,9 @@ DankPopout {
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
@@ -317,7 +317,7 @@ DankPopout {
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
@@ -392,7 +392,7 @@ DankPopout {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}

View File

@@ -11,8 +11,10 @@ Item {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
@@ -27,11 +29,14 @@ Item {
Component {
id: rowComp
Row {
spacing: noBackground ? 2 : Theme.spacingXS
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
anchors.right: parent ? parent.right : undefined
Repeater {
id: rowRepeater
model: root.widgetsModel
Item {
readonly property real rowSpacing: parent.widgetSpacing
width: widgetLoader.item ? widgetLoader.item.width : 0
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
@@ -47,6 +52,11 @@ Item {
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
isLeftBarEdge: false
isRightBarEdge: true
}
}
}
@@ -57,10 +67,13 @@ Item {
id: columnComp
Column {
width: parent ? parent.width : 0
spacing: noBackground ? 2 : Theme.spacingXS
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater {
id: columnRepeater
model: root.widgetsModel
Item {
readonly property real columnSpacing: parent.widgetSpacing
width: parent.width
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
@@ -76,6 +89,11 @@ Item {
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing
isTopBarEdge: false
isBottomBarEdge: true
}
}
}

View File

@@ -15,10 +15,20 @@ Loader {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool isFirst: false
property bool isLast: false
property real sectionSpacing: 0
property bool isLeftBarEdge: false
property bool isRightBarEdge: false
property bool isTopBarEdge: false
property bool isBottomBarEdge: false
asynchronous: false
active: getWidgetVisible(widgetId, DgopService.dgopAvailable) &&
readonly property bool orientationMatches: (axis?.isVertical ?? false) === isInColumn
active: orientationMatches &&
getWidgetVisible(widgetId, DgopService.dgopAvailable) &&
(widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: getWidgetComponent(widgetId, components)
opacity: getWidgetEnabled(widgetData?.enabled) ? 1 : 0
@@ -73,6 +83,62 @@ Loader {
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isFirst" in root.item
property: "isFirst"
value: root.isFirst
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isLast" in root.item
property: "isLast"
value: root.isLast
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "sectionSpacing" in root.item
property: "sectionSpacing"
value: root.sectionSpacing
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isLeftBarEdge" in root.item
property: "isLeftBarEdge"
value: root.isLeftBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isRightBarEdge" in root.item
property: "isRightBarEdge"
value: root.isRightBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isTopBarEdge" in root.item
property: "isTopBarEdge"
value: root.isTopBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isBottomBarEdge" in root.item
property: "isBottomBarEdge"
value: root.isBottomBarEdge
restoreMode: Binding.RestoreNone
}
onLoaded: {
if (item) {
contentItemReady(item)

View File

@@ -50,7 +50,6 @@ BasePill {
StyledText {
text: BatteryService.batteryLevel.toString()
font.pixelSize: Theme.barTextSize(battery.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: BatteryService.batteryAvailable
@@ -87,7 +86,6 @@ BasePill {
StyledText {
text: `${BatteryService.batteryLevel}%`
font.pixelSize: Theme.barTextSize(battery.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable
@@ -97,8 +95,10 @@ BasePill {
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
x: -battery.leftMargin
y: -battery.topMargin
width: battery.width + battery.leftMargin + battery.rightMargin
height: battery.height + battery.topMargin + battery.bottomMargin
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
@@ -108,7 +108,7 @@ BasePill {
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, battery.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
toggleBatteryPopup();
toggleBatteryPopup()
}
}
}

View File

@@ -37,9 +37,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -54,9 +54,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -68,18 +68,18 @@ BasePill {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -92,18 +92,18 @@ BasePill {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -134,9 +134,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -149,9 +149,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -169,9 +169,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -184,9 +184,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
}
@@ -198,28 +198,30 @@ BasePill {
spacing: Theme.spacingS
StyledText {
id: timeText
text: {
return systemClock?.date?.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
anchors.baseline: dateText.baseline
}
StyledText {
id: middleDot
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
anchors.baseline: dateText.baseline
visible: !SettingsData.clockCompactMode
}
StyledText {
id: dateText
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat)
}
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
}
font.pixelSize: Theme.barTextSize(root.barThickness)
@@ -237,15 +239,16 @@ BasePill {
}
MouseArea {
id: clockMouseArea
anchors.fill: parent
hoverEnabled: true
x: -root.leftMargin
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin
cursorShape: Qt.PointingHandCursor
onPressed: {
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
const globalPos = root.visualContent.mapToGlobal(0, 0)
const currentScreen = root.parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, width)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, root.visualWidth)
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
}
root.clockClicked()

View File

@@ -27,7 +27,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.colorPickerRequested()

View File

@@ -86,7 +86,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y
@@ -188,7 +187,6 @@ BasePill {
id: audioWheelArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y;
@@ -228,8 +226,10 @@ BasePill {
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
x: -root.leftMargin
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -59,7 +59,6 @@ BasePill {
return DgopService.cpuUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -97,7 +96,6 @@ BasePill {
return DgopService.cpuUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -106,7 +104,6 @@ BasePill {
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100%"
}
@@ -125,7 +122,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -59,7 +59,6 @@ BasePill {
return Math.round(DgopService.cpuTemperature).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -97,7 +96,6 @@ BasePill {
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -106,7 +104,6 @@ BasePill {
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100°"
}
@@ -125,7 +122,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -10,6 +10,7 @@ BasePill {
property var widgetData: null
property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/"
property bool isHovered: mouseArea.containsMouse
property var selectedMount: {
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
@@ -114,7 +115,6 @@ BasePill {
return root.diskUsagePercent.toFixed(0)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -149,7 +149,6 @@ BasePill {
return root.selectedMount.mount
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -164,7 +163,6 @@ BasePill {
return root.diskUsagePercent.toFixed(0) + "%"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -173,7 +171,6 @@ BasePill {
StyledTextMetrics {
id: diskBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100%"
}
@@ -197,6 +194,7 @@ BasePill {
}
MouseArea {
id: mouseArea
z: 1
anchors.fill: parent
hoverEnabled: root.isVerticalOrientation

View File

@@ -17,6 +17,7 @@ BasePill {
readonly property int maxCompactWidth: 288
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property var activeDesktopEntry: null
property bool isHovered: mouseArea.containsMouse
Component.onCompleted: {
updateDesktopEntry()
@@ -98,7 +99,7 @@ BasePill {
return compactMode ? Math.min(baseWidth, maxCompactWidth - root.horizontalPadding * 2) : Math.min(baseWidth, maxNormalWidth - root.horizontalPadding * 2)
}
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
clip: true
clip: false
IconImage {
id: appIcon
@@ -146,7 +147,6 @@ BasePill {
}
font.pixelSize: 10
color: Theme.surfaceText
font.weight: Font.Medium
}
Row {
@@ -166,7 +166,6 @@ BasePill {
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
@@ -203,7 +202,6 @@ BasePill {
return title;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight

View File

@@ -123,7 +123,6 @@ BasePill {
return Math.round(root.displayTemp).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -161,7 +160,6 @@ BasePill {
return Math.round(root.displayTemp) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -170,7 +168,6 @@ BasePill {
StyledTextMetrics {
id: gpuTempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100°"
}
@@ -189,7 +186,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -26,7 +26,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
SessionService.toggleIdleInhibit()

View File

@@ -42,7 +42,6 @@ BasePill {
return root.currentLayout.substring(0, 2).toUpperCase()
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -67,7 +66,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (CompositorService.isNiri) {

View File

@@ -84,7 +84,6 @@ BasePill {
MouseArea {
id: customMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.RightButton
onPressed: function (mouse){

View File

@@ -45,53 +45,26 @@ BasePill {
implicitHeight: root.playerAvailable ? root.currentContentHeight : 0
opacity: root.playerAvailable ? 1 : 0
states: [
State {
name: "shown"
when: root.playerAvailable
PropertyChanges {
target: parent
opacity: 1
implicitWidth: root.currentContentWidth
implicitHeight: root.currentContentHeight
}
},
State {
name: "hidden"
when: !root.playerAvailable
PropertyChanges {
target: parent
opacity: 0
implicitWidth: 0
implicitHeight: 0
}
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
]
transitions: [
Transition {
from: "shown"
to: "hidden"
SequentialAnimation {
PauseAnimation {
duration: 500
}
NumberAnimation {
properties: "opacity,implicitWidth,implicitHeight"
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
},
Transition {
from: "hidden"
to: "shown"
NumberAnimation {
properties: "opacity,implicitWidth,implicitHeight"
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on implicitWidth {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
]
}
Behavior on implicitHeight {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Column {
id: verticalLayout
@@ -189,7 +162,7 @@ BasePill {
anchors.verticalCenter: parent.verticalCenter
width: textWidth
height: 20
height: root.widgetThickness
visible: SettingsData.mediaSize > 0
clip: true
color: "transparent"
@@ -203,7 +176,6 @@ BasePill {
text: textContainer.displayText
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Medium
wrapMode: Text.NoWrap
x: needsScrolling ? -scrollOffset : 0
onTextChanged: {
@@ -284,7 +256,6 @@ BasePill {
id: prevArea
anchors.fill: parent
enabled: root.playerAvailable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
@@ -342,7 +313,6 @@ BasePill {
id: nextArea
anchors.fill: parent
enabled: root.playerAvailable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {

View File

@@ -54,7 +54,6 @@ BasePill {
return (rate / (1024 * 1024)).toFixed(0) + "M"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.info
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -67,7 +66,6 @@ BasePill {
return (rate / (1024 * 1024)).toFixed(0) + "M"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -99,7 +97,6 @@ BasePill {
StyledText {
text: DgopService.networkRxRate > 0 ? root.formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -109,7 +106,6 @@ BasePill {
StyledTextMetrics {
id: rxBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "88.8 MB/s"
}
@@ -137,7 +133,6 @@ BasePill {
StyledText {
text: DgopService.networkTxRate > 0 ? root.formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -147,7 +142,6 @@ BasePill {
StyledTextMetrics {
id: txBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "88.8 MB/s"
}

View File

@@ -47,19 +47,6 @@ BasePill {
size: Theme.barIconSize(root.barThickness, -4)
color: root.isActive ? Theme.primary : Theme.surfaceText
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.primary
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 4
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 4
visible: NotepadStorageService.tabs && NotepadStorageService.tabs.length > 0
opacity: 0.8
}
}
}

View File

@@ -59,7 +59,6 @@ BasePill {
return DgopService.memoryUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -97,7 +96,6 @@ BasePill {
return DgopService.memoryUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -106,7 +104,6 @@ BasePill {
StyledTextMetrics {
id: ramBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100%"
}
@@ -125,7 +122,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
@@ -20,16 +21,27 @@ Item {
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
property int _workspaceUpdateTrigger: 0
property int _desktopEntriesUpdateTrigger: 0
readonly property var sortedToplevels: {
_workspaceUpdateTrigger
const toplevels = CompositorService.sortedToplevels
if (!toplevels)
if (!toplevels || toplevels.length === 0)
return []
if (SettingsData.runningAppsCurrentWorkspace) {
return CompositorService.filterCurrentWorkspace(toplevels, parentScreen?.name) || []
const filtered = CompositorService.filterCurrentWorkspace(toplevels, parentScreen?.name)
return filtered || []
}
return toplevels
}
Connections {
target: DesktopEntries
function onApplicationsChanged() {
_desktopEntriesUpdateTrigger++
}
}
readonly property var groupedWindows: {
if (!SettingsData.runningAppsGroupByApp) {
return []
@@ -77,6 +89,37 @@ Item {
height: isVertical ? calculatedSize : barThickness
visible: windowCount > 0
Connections {
target: NiriService
function onAllWorkspacesChanged() {
_workspaceUpdateTrigger++
}
function onWindowsChanged() {
_workspaceUpdateTrigger++
}
}
Connections {
target: Hyprland
function onFocusedWorkspaceChanged() {
_workspaceUpdateTrigger++
}
}
Connections {
target: Hyprland.workspaces
function onValuesChanged() {
_workspaceUpdateTrigger++
}
}
Connections {
target: Hyprland.toplevels
function onValuesChanged() {
_workspaceUpdateTrigger++
}
}
Rectangle {
id: visualBackground
width: root.isVertical ? root.widgetThickness : root.calculatedSize
@@ -212,6 +255,7 @@ Item {
property var toplevelObject: toplevelData
property int windowCount: isGrouped ? modelData.windows.length : 1
property string tooltipText: {
root._desktopEntriesUpdateTrigger
let appName = "Unknown"
if (appId) {
const desktopEntry = DesktopEntries.heuristicLookup(appId)
@@ -250,6 +294,7 @@ Item {
width: 18
height: 18
source: {
root._desktopEntriesUpdateTrigger
const moddedId = Paths.moddedAppId(appId)
if (moddedId.toLowerCase().includes("steam_app")) {
return ""
@@ -284,6 +329,7 @@ Item {
return !iconImg.visible && !isSteamApp
}
text: {
root._desktopEntriesUpdateTrigger
if (!appId) {
return "?"
}
@@ -297,7 +343,6 @@ Item {
}
font.pixelSize: 10
color: Theme.surfaceText
font.weight: Font.Medium
}
Rectangle {
@@ -317,7 +362,6 @@ Item {
text: windowCount > 9 ? "9+" : windowCount
font.pixelSize: 9
color: Theme.surface
font.weight: Font.Bold
}
}
@@ -332,7 +376,6 @@ Item {
text: windowTitle
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
@@ -441,6 +484,7 @@ Item {
property var toplevelObject: toplevelData
property int windowCount: isGrouped ? modelData.windows.length : 1
property string tooltipText: {
root._desktopEntriesUpdateTrigger
let appName = "Unknown"
if (appId) {
const desktopEntry = DesktopEntries.heuristicLookup(appId)
@@ -478,6 +522,7 @@ Item {
width: 18
height: 18
source: {
root._desktopEntriesUpdateTrigger
const moddedId = Paths.moddedAppId(appId)
if (moddedId.toLowerCase().includes("steam_app")) {
return ""
@@ -511,6 +556,7 @@ Item {
return !iconImg.visible && !isSteamApp
}
text: {
root._desktopEntriesUpdateTrigger
if (!appId) {
return "?"
}
@@ -524,7 +570,6 @@ Item {
}
font.pixelSize: 10
color: Theme.surfaceText
font.weight: Font.Medium
}
Rectangle {
@@ -544,7 +589,6 @@ Item {
text: windowCount > 9 ? "9+" : windowCount
font.pixelSize: 9
color: Theme.surface
font.weight: Font.Bold
}
}
@@ -558,7 +602,6 @@ Item {
text: windowTitle
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
@@ -745,7 +788,6 @@ Item {
text: I18n.tr("Close")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
}
MouseArea {

View File

@@ -131,7 +131,6 @@ Item {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (!delegateRoot.trayItem) {
@@ -218,7 +217,6 @@ Item {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (!delegateRoot.trayItem) {
@@ -486,7 +484,6 @@ Item {
MouseArea {
id: backArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: menuRoot.goBack()
}
@@ -522,7 +519,6 @@ Item {
id: itemArea
anchors.fill: parent
enabled: !menuEntry?.isSeparator && (menuEntry?.enabled !== false)
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {

View File

@@ -112,7 +112,6 @@ BasePill {
anchors.verticalCenter: parent.verticalCenter
text: SystemUpdateService.updateCount.toString()
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
visible: root.hasUpdates && !root.isChecking
}
@@ -123,7 +122,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
if (popoutTarget && popoutTarget.setTriggerPosition) {

View File

@@ -9,10 +9,11 @@ BasePill {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
property var popoutTarget: null
property bool isHovered: clickArea.containsMouse
signal toggleVpnPopup()
@@ -24,9 +25,9 @@ BasePill {
DankIcon {
id: icon
name: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
name: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
size: Theme.barIconSize(root.barThickness, -4)
color: VpnService.connected ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.connected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
}
@@ -59,10 +60,10 @@ BasePill {
tooltipLoader.active = true
if (tooltipLoader.item) {
let tooltipText = ""
if (!VpnService.connected) {
if (!DMSNetworkService.connected) {
tooltipText = "VPN Disconnected"
} else {
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1) {
tooltipText = "VPN Connected • " + (names[0] || "")
} else {

View File

@@ -18,7 +18,7 @@ Item {
property var hyprlandOverviewLoader: null
property var parentScreen: null
readonly property var sortedToplevels: {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, parentScreen?.name);
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
}
property int currentWorkspace: {
if (CompositorService.isNiri) {
@@ -224,12 +224,28 @@ Item {
const currentIndex = realWorkspaces.findIndex(ws => ws === root.currentWorkspace)
const validIndex = currentIndex === -1 ? 0 : currentIndex
const nextIndex = direction > 0 ? (validIndex + 1) % realWorkspaces.length : (validIndex - 1 + realWorkspaces.length) % realWorkspaces.length
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0)
if (nextIndex === validIndex) {
return
}
NiriService.switchToWorkspace(realWorkspaces[nextIndex] - 1)
} else if (CompositorService.isHyprland) {
const command = direction > 0 ? "workspace r+1" : "workspace r-1"
Hyprland.dispatch(command)
const realWorkspaces = getRealWorkspaces()
if (realWorkspaces.length < 2) {
return
}
const currentIndex = realWorkspaces.findIndex(ws => ws.id === root.currentWorkspace)
const validIndex = currentIndex === -1 ? 0 : currentIndex
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0)
if (nextIndex === validIndex) {
return
}
Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`)
}
}
@@ -423,6 +439,26 @@ Item {
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5
}
}
//DO NOT move this MouseArea. It should be on this level in order for the appMouseArea to work
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: !isPlaceholder
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
enabled: !isPlaceholder
onClicked: {
if (isPlaceholder) {
return
}
if (CompositorService.isNiri) {
NiriService.switchToWorkspace(modelData - 1)
} else if (CompositorService.isHyprland && modelData?.id) {
Hyprland.dispatch(`workspace ${modelData.id}`)
}
}
}
Timer {
id: dataUpdateTimer
@@ -552,7 +588,6 @@ Item {
MouseArea {
id: appMouseArea
hoverEnabled: true
anchors.fill: parent
enabled: isActive
cursorShape: Qt.PointingHandCursor
@@ -621,7 +656,6 @@ Item {
MouseArea {
id: appMouseArea
hoverEnabled: true
anchors.fill: parent
enabled: isActive
cursorShape: Qt.PointingHandCursor
@@ -715,25 +749,6 @@ Item {
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: !isPlaceholder
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
enabled: !isPlaceholder
onClicked: {
if (isPlaceholder) {
return
}
if (CompositorService.isNiri) {
NiriService.switchToWorkspace(modelData - 1)
} else if (CompositorService.isHyprland && modelData?.id) {
Hyprland.dispatch(`workspace ${modelData.id}`)
}
}
}
Component.onCompleted: updateAllData()
Connections {
@@ -745,6 +760,7 @@ Item {
enabled: CompositorService.isNiri
function onAllWorkspacesChanged() { delegateRoot.updateAllData() }
function onWindowUrgentChanged() { delegateRoot.updateAllData() }
function onWindowsChanged() { delegateRoot.updateAllData() }
}
Connections {
target: SettingsData

View File

@@ -1,10 +1,10 @@
import Qt.labs.folderlistmodel
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals.FileBrowser
import qs.Services
@@ -16,11 +16,10 @@ Item {
implicitWidth: 700
implicitHeight: 410
property var wallpaperList: []
property string wallpaperDir: ""
property int currentPage: 0
property int itemsPerPage: 16
property int totalPages: Math.max(1, Math.ceil(wallpaperList.length / itemsPerPage))
property int totalPages: Math.max(1, Math.ceil(wallpaperFolderModel.count / itemsPerPage))
property bool active: false
property Item focusTarget: wallpaperGrid
property Item tabBarItem: null
@@ -28,6 +27,8 @@ Item {
property Item keyForwardTarget: null
property int lastPage: 0
property bool enableAnimation: false
property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation)
property string selectedFileName: ""
signal requestTabChange(int newIndex)
@@ -36,6 +37,11 @@ Item {
enableAnimation = false
lastPage = currentPage
}
updateSelectedFileName()
}
onGridIndexChanged: {
updateSelectedFileName()
}
onVisibleChanged: {
@@ -45,10 +51,7 @@ Item {
}
Component.onCompleted: {
loadWallpapers()
if (visible && active) {
setInitialSelection()
}
loadWallpaperDirectory()
}
onActiveChanged: {
@@ -59,16 +62,17 @@ Item {
function handleKeyEvent(event) {
const columns = 4
const rows = 4
const currentRow = Math.floor(gridIndex / columns)
const currentCol = gridIndex % columns
const visibleCount = wallpaperGrid.model.length
const visibleCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (gridIndex >= 0) {
const item = wallpaperGrid.currentItem
if (item && item.wallpaperPath) {
SessionData.setWallpaper(item.wallpaperPath)
if (gridIndex >= 0 && gridIndex < visibleCount) {
const absoluteIndex = currentPage * itemsPerPage + gridIndex
if (absoluteIndex < wallpaperFolderModel.count) {
const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath")
if (filePath) {
SessionData.setWallpaper(filePath.toString().replace(/^file:\/\//, ''))
}
}
}
return true
@@ -76,10 +80,8 @@ Item {
if (event.key === Qt.Key_Right) {
if (gridIndex + 1 < visibleCount) {
// Move right within current page
gridIndex++
} else if (gridIndex === visibleCount - 1 && currentPage < totalPages - 1) {
// At last item in page, go to next page
} else if (currentPage < totalPages - 1) {
gridIndex = 0
currentPage++
}
@@ -88,22 +90,19 @@ Item {
if (event.key === Qt.Key_Left) {
if (gridIndex > 0) {
// Move left within current page
gridIndex--
} else if (gridIndex === 0 && currentPage > 0) {
// At first item in page, go to previous page (last item)
} else if (currentPage > 0) {
currentPage--
gridIndex = Math.min(itemsPerPage - 1, wallpaperList.length - currentPage * itemsPerPage - 1)
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
gridIndex = prevPageCount - 1
}
return true
}
if (event.key === Qt.Key_Down) {
if (gridIndex + columns < visibleCount) {
// Move down within current page
gridIndex += columns
} else if (gridIndex >= visibleCount - columns && currentPage < totalPages - 1) {
// In last row, go to next page
} else if (currentPage < totalPages - 1) {
gridIndex = currentCol
currentPage++
}
@@ -112,12 +111,10 @@ Item {
if (event.key === Qt.Key_Up) {
if (gridIndex >= columns) {
// Move up within current page
gridIndex -= columns
} else if (gridIndex < columns && currentPage > 0) {
// In first row, go to previous page (last row)
} else if (currentPage > 0) {
currentPage--
const prevPageCount = Math.min(itemsPerPage, wallpaperList.length - currentPage * itemsPerPage)
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
const prevPageRows = Math.ceil(prevPageCount / columns)
gridIndex = (prevPageRows - 1) * columns + currentCol
gridIndex = Math.min(gridIndex, prevPageCount - 1)
@@ -144,8 +141,9 @@ Item {
}
if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) {
gridIndex = 0
currentPage = totalPages - 1
const lastPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
gridIndex = Math.max(0, lastPageCount - 1)
return true
}
@@ -153,40 +151,38 @@ Item {
}
function setInitialSelection() {
if (!SessionData.wallpaperPath) {
if (!SessionData.wallpaperPath || wallpaperFolderModel.count === 0) {
gridIndex = 0
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
return
}
const startIndex = currentPage * itemsPerPage
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length)
const pageWallpapers = wallpaperList.slice(startIndex, endIndex)
for (let i = 0; i < pageWallpapers.length; i++) {
if (pageWallpapers[i] === SessionData.wallpaperPath) {
gridIndex = i
for (let i = 0; i < wallpaperFolderModel.count; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
if (filePath && filePath.toString().replace(/^file:\/\//, '') === SessionData.wallpaperPath) {
const targetPage = Math.floor(i / itemsPerPage)
const targetIndex = i % itemsPerPage
currentPage = targetPage
gridIndex = targetIndex
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
return
}
}
gridIndex = 0
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
}
onWallpaperListChanged: {
if (visible && active) {
setInitialSelection()
}
}
function loadWallpapers() {
function loadWallpaperDirectory() {
const currentWallpaper = SessionData.wallpaperPath
// Try current wallpaper path / fallback to wallpaperLastPath
if (!currentWallpaper || currentWallpaper.startsWith("#") || currentWallpaper.startsWith("we:")) {
if (CacheData.wallpaperLastPath && CacheData.wallpaperLastPath !== "") {
wallpaperDir = CacheData.wallpaperLastPath
} else {
wallpaperDir = ""
wallpaperList = []
}
return
}
@@ -194,44 +190,51 @@ Item {
wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
}
function updateWallpaperList() {
if (!wallpaperFolderModel || wallpaperFolderModel.count === 0) {
wallpaperList = []
currentPage = 0
gridIndex = 0
function updateSelectedFileName() {
if (wallpaperFolderModel.count === 0) {
selectedFileName = ""
return
}
// Build list from FolderListModel
const files = []
for (let i = 0; i < wallpaperFolderModel.count; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
const absoluteIndex = currentPage * itemsPerPage + gridIndex
if (absoluteIndex < wallpaperFolderModel.count) {
const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath")
if (filePath) {
// Remove file:// prefix if present
const cleanPath = filePath.toString().replace(/^file:\/\//, '')
files.push(cleanPath)
const pathStr = filePath.toString().replace(/^file:\/\//, '')
selectedFileName = pathStr.substring(pathStr.lastIndexOf('/') + 1)
return
}
}
wallpaperList = files
const currentPath = SessionData.wallpaperPath
const selectedIndex = currentPath ? wallpaperList.indexOf(currentPath) : -1
if (selectedIndex >= 0) {
currentPage = Math.floor(selectedIndex / itemsPerPage)
gridIndex = selectedIndex % itemsPerPage
} else {
const maxPage = Math.max(0, Math.ceil(files.length / itemsPerPage) - 1)
currentPage = Math.min(Math.max(0, currentPage), maxPage)
gridIndex = 0
}
selectedFileName = ""
}
Connections {
target: SessionData
function onWallpaperPathChanged() {
loadWallpapers()
loadWallpaperDirectory()
if (visible && active) {
setInitialSelection()
}
}
}
Connections {
target: wallpaperFolderModel
function onCountChanged() {
if (wallpaperFolderModel.status === FolderListModel.Ready) {
if (visible && active) {
setInitialSelection()
}
updateSelectedFileName()
}
}
function onStatusChanged() {
if (wallpaperFolderModel.status === FolderListModel.Ready && wallpaperFolderModel.count > 0) {
if (visible && active) {
setInitialSelection()
}
updateSelectedFileName()
}
}
}
@@ -246,18 +249,6 @@ Item {
showDirs: false
sortField: FolderListModel.Name
folder: wallpaperDir ? "file://" + wallpaperDir : ""
onStatusChanged: {
if (status === FolderListModel.Ready) {
updateWallpaperList()
}
}
onCountChanged: {
if (status === FolderListModel.Ready) {
updateWallpaperList()
}
}
}
Loader {
@@ -275,13 +266,11 @@ Item {
showHiddenFiles: false
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
allowStacking: true
onFileSelected: (path) => {
// Set the selected wallpaper
const cleanPath = path.replace(/^file:\/\//, '')
SessionData.setWallpaper(cleanPath)
// Extract directory from the selected file and load all wallpapers
const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/'))
if (dirPath) {
wallpaperDir = dirPath
@@ -290,7 +279,7 @@ Item {
}
close()
}
onDialogClosed: {
Qt.callLater(() => wallpaperBrowserLoader.active = false)
}
@@ -336,8 +325,15 @@ Item {
model: {
const startIndex = currentPage * itemsPerPage
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length)
return wallpaperList.slice(startIndex, endIndex)
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperFolderModel.count)
const items = []
for (let i = startIndex; i < endIndex; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
if (filePath) {
items.push(filePath.toString().replace(/^file:\/\//, ''))
}
}
return items
}
onModelChanged: {
@@ -359,8 +355,11 @@ Item {
Connections {
target: root
function onGridIndexChanged() {
if (enableAnimation && wallpaperGrid.count > 0) {
if (wallpaperGrid.count > 0) {
wallpaperGrid.currentIndex = gridIndex
if (!enableAnimation) {
wallpaperGrid.positionViewAtIndex(gridIndex, GridView.Contain)
}
}
}
}
@@ -440,7 +439,6 @@ Item {
if (modelData) {
SessionData.setWallpaper(modelData)
}
// Don't steal focus - let mainContainer keep it for keyboard nav
}
}
}
@@ -449,7 +447,7 @@ Item {
StyledText {
anchors.centerIn: parent
visible: wallpaperList.length === 0
visible: wallpaperFolderModel.count === 0
text: "No wallpapers found\n\nClick the folder icon below to browse"
font.pixelSize: 14
color: Theme.outline
@@ -457,67 +455,84 @@ Item {
}
}
Row {
Column {
width: parent.width
height: 50
spacing: Theme.spacingS
Item {
width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2
height: parent.height
}
Row {
id: controlsRow
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 32
spacing: Theme.spacingS
DankActionButton {
Item {
width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2
height: parent.height
}
Row {
id: controlsRow
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_previous"
iconSize: 20
buttonSize: 32
enabled: currentPage > 0
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage > 0) {
currentPage--
spacing: Theme.spacingS
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_previous"
iconSize: 20
buttonSize: 32
enabled: currentPage > 0
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage > 0) {
currentPage--
}
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: wallpaperFolderModel.count > 0 ? `${wallpaperFolderModel.count} wallpapers ${currentPage + 1} / ${totalPages}` : "No wallpapers"
font.pixelSize: 14
color: Theme.surfaceText
opacity: 0.7
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_next"
iconSize: 20
buttonSize: 32
enabled: currentPage < totalPages - 1
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage < totalPages - 1) {
currentPage++
}
}
}
}
StyledText {
DankActionButton {
id: browseButton
anchors.verticalCenter: parent.verticalCenter
text: wallpaperList.length > 0 ? `${wallpaperList.length} wallpapers ${currentPage + 1} / ${totalPages}` : "No wallpapers"
font.pixelSize: 14
color: Theme.surfaceText
iconName: "folder_open"
iconSize: 20
buttonSize: 32
opacity: 0.7
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_next"
iconSize: 20
buttonSize: 32
enabled: currentPage < totalPages - 1
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage < totalPages - 1) {
currentPage++
}
}
onClicked: wallpaperBrowserLoader.active = true
}
}
DankActionButton {
id: browseButton
anchors.verticalCenter: parent.verticalCenter
iconName: "folder_open"
iconSize: 20
buttonSize: 32
opacity: 0.7
onClicked: wallpaperBrowserLoader.active = true
StyledText {
width: parent.width
height: 18
text: selectedFileName
font.pixelSize: 12
color: Theme.surfaceText
opacity: 0.5
visible: selectedFileName !== ""
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}

View File

@@ -452,10 +452,10 @@ Item {
anchors.top: SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined
anchors.left: SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined
anchors.right: SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -2 : 0
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -2 : 0
anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -2 : 0
anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -2 : 0
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -(SettingsData.dockSpacing / 2) : 0
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -(SettingsData.dockSpacing / 2) : 0
anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -(SettingsData.dockSpacing / 2) : 0
anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -(SettingsData.dockSpacing / 2) : 0
sourceComponent: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? columnIndicator : rowIndicator
@@ -486,9 +486,19 @@ Item {
}
Rectangle {
width: appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
height: Math.max(2, actualIconSize * 0.05)
radius: Theme.cornerRadius
width: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
}
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
}
height: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
}
return Math.max(2, actualIconSize * 0.05)
}
radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
color: {
if (!appData) {
return "transparent"
@@ -533,9 +543,19 @@ Item {
}
Rectangle {
width: Math.max(2, actualIconSize * 0.05)
height: appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
radius: Theme.cornerRadius
width: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
}
return Math.max(2, actualIconSize * 0.05)
}
height: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
}
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
}
radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
color: {
if (!appData) {
return "transparent"

View File

@@ -20,6 +20,7 @@ Singleton {
property string customThemeFile: ""
property string matugenScheme: "scheme-tonal-spot"
property bool use24HourClock: true
property bool showSeconds: false
property bool useFahrenheit: false
property bool nightModeEnabled: false
property string weatherLocation: "New York, NY"
@@ -54,6 +55,7 @@ Singleton {
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot"
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY"

View File

@@ -186,7 +186,7 @@ Item {
SystemClock {
id: systemClock
precision: SystemClock.Minutes
precision: SystemClock.Seconds
}
Rectangle {
@@ -196,40 +196,136 @@ Item {
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: -100
width: 400
width: parent.width
height: 140
StyledText {
Row {
id: clockText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
text: {
const format = GreetdSettings.use24HourClock ? "HH:mm" : "h:mm AP"
spacing: 0
property string fullTimeStr: {
const format = GreetdSettings.use24HourClock
? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm")
: (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP")
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
}
font.pixelSize: 120
font.weight: Font.Light
color: "white"
lineHeight: 0.8
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: clockText.bottom
anchors.topMargin: -20
text: {
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || ""
property string minutes: timeParts[1] || ""
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
property string ampm: {
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i)
return match ? match[0].trim() : ""
}
property bool hasSeconds: timeParts.length > 2
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: ":"
font.pixelSize: 120
font.weight: Font.Light
color: "white"
}
StyledText {
width: 75
text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: clockText.hasSeconds ? ":" : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 20
text: " "
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
StyledText {
text: clockText.ampm
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
}
StyledText {
anchors.centerIn: parent
anchors.verticalCenterOffset: -10
text: {
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: 80

View File

@@ -52,7 +52,7 @@ sudo chmod -R g+rX ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell
# Create symlinks
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/colors.json
sudo ln -sf ~/.cache/DankMaterialShell/dms-colors.json /var/cache/dms-greeter/colors.json
# Logout and login for group membership to take effect
```
@@ -251,11 +251,11 @@ sudo chmod -R g+rX ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell
# Create symlinks for theme files
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/colors.json
sudo ln -sf ~/.cache/DankMaterialShell/dms-colors.json /var/cache/dms-greeter/colors.json
# Logout and login for group membership to take effect
```
**Advanced:** 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`.
The cache directory should be owned by `greeter:greeter` with `770` permissions.
The cache directory should be owned by `greeter:greeter` with `770` permissions.

View File

@@ -68,6 +68,33 @@ if [[ -z "$COMPOSITOR" ]]; then
exit 1
fi
locate_dms_config() {
local config_name="$1"
local search_paths=()
local config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
search_paths+=("$config_home/quickshell/$config_name")
search_paths+=("/usr/share/quickshell/$config_name")
local config_dirs="${XDG_CONFIG_DIRS:-/etc/xdg}"
IFS=':' read -ra dirs <<< "$config_dirs"
for dir in "${dirs[@]}"; do
if [[ -n "$dir" ]]; then
search_paths+=("$dir/quickshell/$config_name")
fi
done
for path in "${search_paths[@]}"; do
if [[ -f "$path/shell.qml" ]]; then
echo "$path"
return 0
fi
done
return 1
}
export XDG_SESSION_TYPE=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
@@ -81,7 +108,14 @@ QS_CMD="qs"
if [[ "$DMS_PATH" == /* ]]; then
QS_CMD="qs -p $DMS_PATH"
else
QS_CMD="qs -c $DMS_PATH"
RESOLVED_PATH=$(locate_dms_config "$DMS_PATH")
if [[ $? -eq 0 && -n "$RESOLVED_PATH" ]]; then
echo "Located DMS config at: $RESOLVED_PATH" >&2
QS_CMD="qs -p $RESOLVED_PATH"
else
echo "Error: Could not find DMS config '$DMS_PATH' (shell.qml) in any valid config path" >&2
exit 1
fi
fi
case "$COMPOSITOR" in

View File

@@ -18,7 +18,7 @@ Item {
keyboard.target = keyboard_controller.target;
isKeyboardActive = true;
} else
console.info("The keyboard is already shown");
console.log("The keyboard is already shown");
}
function hide() {
@@ -26,7 +26,7 @@ Item {
keyboard.destroy();
isKeyboardActive = false;
} else
console.info("The keyboard is already hidden");
console.log("The keyboard is already hidden");
}
// private

View File

@@ -4,6 +4,7 @@ import QtCore
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Window
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
@@ -23,6 +24,8 @@ Item {
property string hyprlandCurrentLayout: ""
property string hyprlandKeyboard: ""
property int hyprlandLayoutCount: 0
property bool lockerReadySent: false
property bool lockerReadyArmed: false
signal unlockRequested
@@ -43,15 +46,7 @@ Item {
hyprlandLayoutUpdateTimer.start()
}
if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) {
DMSService.sendRequest("loginctl.lockerReady", null, response => {
if (response.error) {
console.warn("LockScreenContent: Failed to signal locker ready:", response.error)
} else {
console.log("LockScreenContent: Locker ready signaled, inhibitor released")
}
})
}
lockerReadyArmed = true
}
onDemoModeChanged: {
if (demoMode) {
@@ -65,6 +60,37 @@ Item {
}
}
function sendLockerReadyOnce() {
if (lockerReadySent) return;
lockerReadySent = true;
if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) {
DMSService.sendRequest("loginctl.lockerReady", null, resp => {
if (resp?.error) console.warn("lockerReady failed:", resp.error)
else console.log("lockerReady sent (afterAnimating/afterRendering)");
});
}
}
function maybeSend() {
if (!lockerReadyArmed) return;
if (!root.visible || root.opacity <= 0) return;
Qt.callLater(() => {
if (root.visible && root.opacity > 0)
sendLockerReadyOnce();
});
}
Connections {
target: root.Window.window
enabled: target !== null
function onAfterAnimating() { maybeSend(); }
function onAfterRendering() { maybeSend(); }
}
onVisibleChanged: maybeSend()
onOpacityChanged: maybeSend()
function updateHyprlandLayout() {
if (CompositorService.isHyprland) {
hyprlandLayoutProcess.running = true
@@ -171,7 +197,7 @@ Item {
SystemClock {
id: systemClock
precision: SystemClock.Minutes
precision: SystemClock.Seconds
}
Rectangle {
@@ -181,39 +207,134 @@ Item {
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: -100
width: 400
width: parent.width
height: 140
StyledText {
Row {
id: clockText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
text: {
return systemClock.date.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
}
font.pixelSize: 120
font.weight: Font.Light
color: "white"
lineHeight: 0.8
}
spacing: 0
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: clockText.bottom
anchors.topMargin: -20
text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
property string fullTimeStr: {
const format = SettingsData.getEffectiveTimeFormat()
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
}
property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || ""
property string minutes: timeParts[1] || ""
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
property string ampm: {
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i)
return match ? match[0].trim() : ""
}
property bool hasSeconds: timeParts.length > 2
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: ":"
font.pixelSize: 120
font.weight: Font.Light
color: "white"
}
StyledText {
width: 75
text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: clockText.hasSeconds ? ":" : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 20
text: " "
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
StyledText {
text: clockText.ampm
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
}
StyledText {
anchors.centerIn: parent
anchors.verticalCenterOffset: -25
text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
ColumnLayout {
anchors.centerIn: parent
anchors.verticalCenterOffset: 50

View File

@@ -14,10 +14,23 @@ Item {
property real barThickness: 48
property alias content: contentLoader.sourceComponent
property bool isVerticalOrientation: axis?.isVertical ?? false
property bool isFirst: false
property bool isLast: false
property real sectionSpacing: 0
property bool isLeftBarEdge: false
property bool isRightBarEdge: false
property bool isTopBarEdge: false
property bool isBottomBarEdge: false
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
readonly property real visualWidth: isVerticalOrientation ? widgetThickness : (contentLoader.item ? (contentLoader.item.implicitWidth + horizontalPadding * 2) : 0)
readonly property real visualHeight: isVerticalOrientation ? (contentLoader.item ? (contentLoader.item.implicitHeight + horizontalPadding * 2) : 0) : widgetThickness
readonly property alias visualContent: visualContent
readonly property real barEdgeExtension: 1000
readonly property real gapExtension: sectionSpacing
readonly property real leftMargin: !isVerticalOrientation ? (isLeftBarEdge && isFirst ? barEdgeExtension : (isFirst ? gapExtension : gapExtension / 2)) : 0
readonly property real rightMargin: !isVerticalOrientation ? (isRightBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0
readonly property real topMargin: isVerticalOrientation ? (isTopBarEdge && isFirst ? barEdgeExtension : (isFirst ? gapExtension : gapExtension / 2)) : 0
readonly property real bottomMargin: isVerticalOrientation ? (isBottomBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0
signal clicked()
@@ -35,7 +48,8 @@ Item {
return "transparent"
}
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor
const isHovered = mouseArea.containsMouse || (root.isHovered ?? false)
const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
}
@@ -49,17 +63,21 @@ Item {
MouseArea {
id: mouseArea
z: -1
anchors.fill: parent
x: -root.leftMargin
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.clicked()
acceptedButtons: Qt.LeftButton
onPressed: {
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
const globalPos = root.visualContent.mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
root.clicked()
}
}
}

View File

@@ -14,14 +14,23 @@ Row {
width: parent.width
spacing: Theme.spacingM
Component.onCompleted: {
property bool isInitialized: false
function loadValue() {
const settings = findSettings()
if (settings) {
value = settings.loadValue(settingKey, defaultValue)
if (settings && settings.pluginService) {
const loadedValue = settings.loadValue(settingKey, defaultValue)
value = loadedValue
isInitialized = true
}
}
Component.onCompleted: {
loadValue()
}
onValueChanged: {
if (!isInitialized) return
const settings = findSettings()
if (settings) {
settings.saveValue(settingKey, value)

View File

@@ -278,7 +278,6 @@ Item {
}
}
// Technical Details
StyledRect {
width: parent.width
height: techSection.implicitHeight + Theme.spacingL * 2
@@ -307,7 +306,7 @@ Item {
}
StyledText {
text: I18n.tr("Technical Details")
text: I18n.tr("Resources")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
@@ -322,14 +321,14 @@ Item {
rowSpacing: Theme.spacingS
StyledText {
text: I18n.tr("Framework:")
text: I18n.tr("Website:")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: `<a href="https://quickshell.org" style="text-decoration:none; color:${Theme.primary};">Quickshell</a>`
text: `<a href="https://danklinux.com" style="text-decoration:none; color:${Theme.primary};">danklinux.com</a>`
linkColor: Theme.primary
textFormat: Text.RichText
onLinkActivated: url => Qt.openUrlExternally(url)
@@ -345,67 +344,25 @@ Item {
}
StyledText {
text: I18n.tr("Language:")
text: I18n.tr("Plugins:")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("QML, JavaScript, Go")
text: `<a href="https://plugins.danklinux.com" style="text-decoration:none; color:${Theme.primary};">plugins.danklinux.com</a>`
linkColor: Theme.primary
textFormat: Text.RichText
onLinkActivated: url => Qt.openUrlExternally(url)
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
}
StyledText {
text: I18n.tr("Compositor:")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Row {
spacing: 4
StyledText {
text: `<a href="https://github.com/YaLTeR/niri" style="text-decoration:none; color:${Theme.primary};">niri</a>`
font.pixelSize: Theme.fontSizeMedium
linkColor: Theme.primary
textFormat: Text.RichText
color: Theme.surfaceVariantText
onLinkActivated: url => Qt.openUrlExternally(url)
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
}
}
StyledText {
text: "&"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: `<a href="https://github.com/hyprwm/Hyprland" style="text-decoration:none; color:${Theme.primary};">hyprland</a>`
font.pixelSize: Theme.fontSizeMedium
linkColor: Theme.primary
textFormat: Text.RichText
color: Theme.surfaceVariantText
onLinkActivated: url => Qt.openUrlExternally(url)
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
}
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
}
}
@@ -478,34 +435,61 @@ Item {
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
// Support Section
StyledRect {
width: parent.width
height: supportSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 0
Row {
id: supportSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
spacing: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "volunteer_activism"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Dank Suite:")
font.pixelSize: Theme.fontSizeMedium
text: I18n.tr("Support Development")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: 4
Item {
width: parent.width - parent.spacing - kofiButton.width - supportSection.children[0].width
height: 1
}
StyledText {
text: `<a href="https://danklinux.com" style="text-decoration:none; color:${Theme.primary};">danklinux.com</a>`
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
linkColor: Theme.primary
textFormat: Text.RichText
onLinkActivated: url => Qt.openUrlExternally(url)
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
}
}
}
DankButton {
id: kofiButton
text: I18n.tr("Donate on Ko-fi")
iconName: "favorite"
iconSize: 20
backgroundColor: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
textColor: Theme.primary
anchors.verticalCenter: parent.verticalCenter
onClicked: Qt.openUrlExternally("https://ko-fi.com/danklinux")
}
}
}

View File

@@ -56,10 +56,21 @@ Item {
}
function setScreenPreferences(componentId, screenNames) {
var prefs = SettingsData.screenPreferences || {
};
prefs[componentId] = screenNames;
SettingsData.setScreenPreferences(prefs);
var prefs = SettingsData.screenPreferences || {};
var newPrefs = Object.assign({}, prefs);
newPrefs[componentId] = screenNames;
SettingsData.setScreenPreferences(newPrefs);
}
function getShowOnLastDisplay(componentId) {
return SettingsData.showOnLastDisplay && SettingsData.showOnLastDisplay[componentId] || false;
}
function setShowOnLastDisplay(componentId, enabled) {
var prefs = SettingsData.showOnLastDisplay || {};
var newPrefs = Object.assign({}, prefs);
newPrefs[componentId] = enabled;
SettingsData.setShowOnLastDisplay(newPrefs);
}
DankFlickable {
@@ -660,7 +671,6 @@ Item {
Column {
property string componentId: modelData.id
property var selectedScreens: displaysTab.getScreenPreferences(componentId)
width: parent.width
spacing: Theme.spacingXS
@@ -669,12 +679,27 @@ Item {
width: parent.width
text: I18n.tr("All displays")
description: I18n.tr("Show on all connected displays")
checked: parent.selectedScreens.includes("all")
checked: displaysTab.getScreenPreferences(parent.componentId).includes("all")
onToggled: (checked) => {
if (checked)
if (checked) {
displaysTab.setScreenPreferences(parent.componentId, ["all"]);
else
} else {
displaysTab.setScreenPreferences(parent.componentId, []);
if (["dankBar", "dock", "notifications", "osd", "toast"].includes(parent.componentId)) {
displaysTab.setShowOnLastDisplay(parent.componentId, true);
}
}
}
}
DankToggle {
width: parent.width
text: I18n.tr("Show on Last Display")
description: I18n.tr("Always show when there's only one connected display")
checked: displaysTab.getShowOnLastDisplay(parent.componentId)
visible: !displaysTab.getScreenPreferences(parent.componentId).includes("all") && ["dankBar", "dock", "notifications", "osd", "toast"].includes(parent.componentId)
onToggled: (checked) => {
displaysTab.setShowOnLastDisplay(parent.componentId, checked);
}
}
@@ -683,13 +708,13 @@ Item {
height: 1
color: Theme.outline
opacity: 0.2
visible: !parent.selectedScreens.includes("all")
visible: !displaysTab.getScreenPreferences(parent.componentId).includes("all")
}
Column {
width: parent.width
spacing: Theme.spacingXS
visible: !parent.selectedScreens.includes("all")
visible: !displaysTab.getScreenPreferences(parent.componentId).includes("all")
Repeater {
model: Quickshell.screens

View File

@@ -329,6 +329,72 @@ Item {
}
}
// Indicator Style Section
StyledRect {
width: parent.width
height: indicatorStyleSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 0
visible: SettingsData.showDock
opacity: visible ? 1 : 0
Column {
id: indicatorStyleSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "fiber_manual_record"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
id: indicatorStyleText
text: I18n.tr("Indicator Style")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - Theme.iconSize - Theme.spacingM - indicatorStyleText.width - indicatorStyleButtonGroup.width - Theme.spacingM * 2
anchors.verticalCenter: parent.verticalCenter
}
DankButtonGroup {
id: indicatorStyleButtonGroup
anchors.verticalCenter: parent.verticalCenter
model: ["Circle", "Line"]
currentIndex: SettingsData.dockIndicatorStyle === "circle" ? 0 : 1
onSelectionChanged: (index, selected) => {
if (selected) {
SettingsData.setDockIndicatorStyle(index === 0 ? "circle" : "line")
}
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
// Icon Size Section
StyledRect {
width: parent.width

View File

@@ -460,6 +460,58 @@ Item {
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.2
visible: CompositorService.isNiri
}
Row {
width: parent.width
spacing: Theme.spacingM
visible: CompositorService.isNiri
DankIcon {
name: "blur_on"
size: Theme.iconSize
color: SettingsData.blurWallpaperOnOverview ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - blurOverviewToggle.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Blur on Overview")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Blur wallpaper when niri overview is open")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
DankToggle {
id: blurOverviewToggle
anchors.verticalCenter: parent.verticalCenter
checked: SettingsData.blurWallpaperOnOverview
onToggled: checked => {
SettingsData.setBlurWallpaperOnOverview(checked)
}
}
}
// Per-Mode Wallpaper Section - Full Width
Rectangle {
width: parent.width
@@ -959,6 +1011,67 @@ Item {
}
}
StyledRect {
width: parent.width
height: blurLayerColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
visible: CompositorService.isNiri
Column {
id: blurLayerColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "blur_on"
size: Theme.iconSize
color: SettingsData.blurredWallpaperLayer ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - blurLayerToggle.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Blur Layer")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
DankToggle {
id: blurLayerToggle
anchors.verticalCenter: parent.verticalCenter
checked: SettingsData.blurredWallpaperLayer
onToggled: checked => {
SettingsData.setBlurredWallpaperLayer(checked)
}
}
}
}
}
StyledRect {
width: parent.width
height: lightModeRow.implicitHeight + Theme.spacingL * 2

View File

@@ -247,8 +247,8 @@ FocusScope {
property bool hasSettings: pluginData && pluginData.settings !== undefined && pluginData.settings !== ""
property bool isExpanded: pluginsTab.expandedPluginId === pluginId
property bool hasUpdate: {
if (DMSService.apiVersion < 8) return true
return pluginsTab.installedPluginsData[pluginDirectoryName] || pluginsTab.installedPluginsData[pluginId] || pluginsTab.installedPluginsData[pluginName] || false
if (DMSService.apiVersion < 8) return false
return pluginsTab.installedPluginsData[pluginId] || pluginsTab.installedPluginsData[pluginName] || false
}
@@ -346,10 +346,9 @@ FocusScope {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const currentPluginDirName = pluginDelegate.pluginDirectoryName
const currentPluginName = pluginDelegate.pluginName
const currentPluginId = pluginDelegate.pluginId
DMSService.update(currentPluginDirName, response => {
DMSService.update(currentPluginName, response => {
if (response.error) {
ToastService.showError("Update failed: " + response.error)
} else {
@@ -397,9 +396,8 @@ FocusScope {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const currentPluginDirName = pluginDelegate.pluginDirectoryName
const currentPluginName = pluginDelegate.pluginName
DMSService.uninstall(currentPluginDirName, response => {
DMSService.uninstall(currentPluginName, response => {
if (response.error) {
ToastService.showError("Uninstall failed: " + response.error)
} else {
@@ -677,8 +675,8 @@ FocusScope {
for (var i = 0; i < plugins.length; i++) {
var plugin = plugins[i]
var hasUpdate = plugin.hasUpdate || false
if (plugin.path) {
pluginMap[plugin.path] = hasUpdate
if (plugin.id) {
pluginMap[plugin.id] = hasUpdate
}
if (plugin.name) {
pluginMap[plugin.name] = hasUpdate

View File

@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
@@ -6,6 +7,7 @@ import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules
import qs.Services
Variants {
model: {
@@ -467,6 +469,15 @@ Variants {
})
}
}
MultiEffect {
anchors.fill: parent
source: effectLoader.active ? effectLoader.item : (root.actualTransitionType === "none" ? currentWallpaper : null)
visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview
blurEnabled: true
blur: 0.8
blurMax: 48
}
}
}
}

View File

@@ -13,7 +13,7 @@ Item {
signal itemsChanged()
Component.onCompleted: {
console.log("LauncherExample: Plugin loaded")
console.info("LauncherExample: Plugin loaded")
// Load custom trigger from settings
if (pluginService) {

View File

@@ -57,10 +57,10 @@ PluginComponent {
}
Component.onCompleted: {
console.log("WallpaperWatcherDaemon: Started monitoring wallpaper changes")
console.info("WallpaperWatcherDaemon: Started monitoring wallpaper changes")
}
Component.onDestruction: {
console.log("WallpaperWatcherDaemon: Stopped monitoring wallpaper changes")
console.info("WallpaperWatcherDaemon: Stopped monitoring wallpaper changes")
}
}

View File

@@ -324,11 +324,11 @@ make && sudo make install
#### 4.1 Core optional dependencies
```bash
# Arch Linux
sudo pacman -S cava wl-clipboard cliphist brightnessctl qt6-multimedia
sudo pacman -S cava wl-clipboard cliphist brightnessctl qt6-multimedia accountsservice
paru -S matugen-bin dgop
# Fedora
sudo dnf install cava wl-clipboard brightnessctl qt6-qtmultimedia
sudo dnf install cava wl-clipboard brightnessctl qt6-qtmultimedia accountsservice
sudo dnf copr enable avengemedia/danklinux && sudo dnf install cliphist ghostty hyprpicker material-symbols-fonts matugen
```
Note: by enabling and installing the avengemedia/dms copr above, these core dependencies will automatically be available for use.
@@ -352,6 +352,7 @@ sudo sh -c "curl -L https://github.com/AvengeMedia/dgop/releases/latest/download
- `cava`: Audio visualizer
- `cliphist`: Clipboard history
- `qt6-multimedia`: System sound support
- `accountsservice`: Ability to sync
## Compositor Configuration
@@ -438,6 +439,37 @@ binds {
spawn "dms" "ipc" "call" "night" "toggle";
}
}
// You probably want this too
debug {
honor-xdg-activation-with-invalid-serial
}
```
#### niri - place wallpaper on overview (blurred or non-blurred)
Place the wallpaper on backdrop using a layer-rule for a contiguous surface.
```kdl
layer-rule {
match namespace="quickshell"
place-within-backdrop true
}
```
If using "Blur Layer" option, you may want the blurred layer to appear on overview only, that can be done with some layer rules:
```kdl
layer-rule {
match namespace="dms:blurwallpaper"
opacity 0.0
}
layer-rule {
match namespace="dms:blurwallpaper"
place-within-backdrop true
opacity 1.0
}
```
#### niri theming

View File

@@ -573,7 +573,7 @@ Singleton {
if (!detectSoundsAvailability()) {
console.warn("AudioService: QtMultimedia not available - sound effects disabled")
} else {
console.log("AudioService: Sound effects enabled")
console.info("AudioService: Sound effects enabled")
checkGsettings()
Qt.callLater(createSoundPlayers)
}

View File

@@ -6,6 +6,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Bluetooth
import qs.Services
Singleton {
id: root
@@ -15,6 +16,7 @@ Singleton {
readonly property bool enabled: (adapter && adapter.enabled) ?? false
readonly property bool discovering: (adapter && adapter.discovering) ?? false
readonly property var devices: adapter ? adapter.devices : null
readonly property bool enhancedPairingAvailable: DMSService.dmsAvailable && DMSService.apiVersion >= 9 && DMSService.capabilities.includes("bluetooth")
readonly property bool connected: {
if (!adapter || !adapter.devices) {
return false
@@ -173,6 +175,25 @@ Singleton {
device.connect()
}
function pairDevice(device, callback) {
if (!device) {
if (callback) callback({error: "Invalid device"})
return
}
// The DMS backend actually implements a bluez agent, so we can pair anything
if (enhancedPairingAvailable) {
const devicePath = getDevicePath(device)
DMSService.bluetoothPair(devicePath, callback)
return
}
// Quickshell does not implement a bluez agent, so we can try to pair but only with devices that don't require a passcode
device.trusted = true
device.connect()
if (callback) callback({success: true})
}
function getCardName(device) {
if (!device) {
return ""
@@ -180,6 +201,14 @@ Singleton {
return `bluez_card.${device.address.replace(/:/g, "_")}`
}
function getDevicePath(device) {
if (!device || !device.address) {
return ""
}
const adapterPath = adapter ? "/org/bluez/hci0" : "/org/bluez/hci0"
return `${adapterPath}/dev_${device.address.replace(/:/g, "_")}`
}
function isAudioDevice(device) {
if (!device) {
return false

View File

@@ -76,6 +76,10 @@ Singleton {
target: Hyprland
function onFocusedWorkspaceChanged() { root.scheduleSort() }
}
Connections {
target: NiriService
function onWindowsChanged() { root.scheduleSort() }
}
Component.onCompleted: {
detectCompositor()
@@ -328,7 +332,7 @@ Singleton {
isHyprland = true
isNiri = false
compositor = "hyprland"
console.log("CompositorService: Detected Hyprland")
console.info("CompositorService: Detected Hyprland")
try {
Hyprland.refreshToplevels()
} catch(e) {}
@@ -341,7 +345,7 @@ Singleton {
isNiri = true
isHyprland = false
compositor = "niri"
console.log("CompositorService: Detected Niri with socket:", niriSocket)
console.info("CompositorService: Detected Niri with socket:", niriSocket)
NiriService.generateNiriBinds()
} else {
isHyprland = false

View File

@@ -11,6 +11,7 @@ Singleton {
id: root
property bool networkAvailable: false
property string backend: ""
property string networkStatus: "disconnected"
property string primaryConnection: ""
@@ -68,6 +69,24 @@ Singleton {
property string wifiPassword: ""
property string forgetSSID: ""
property var vpnProfiles: []
property var vpnActive: []
property bool vpnAvailable: false
property bool vpnIsBusy: false
property alias profiles: root.vpnProfiles
property alias activeConnections: root.vpnActive
property var activeUuids: vpnActive.map(v => v.uuid).filter(u => !!u)
property var activeNames: vpnActive.map(v => v.name).filter(n => !!n)
property string activeUuid: activeUuids.length > 0 ? activeUuids[0] : ""
property string activeName: activeNames.length > 0 ? activeNames[0] : ""
property string activeDevice: vpnActive.length > 0 ? (vpnActive[0].device || "") : ""
property string activeState: vpnActive.length > 0 ? (vpnActive[0].state || "") : ""
property bool vpnConnected: activeUuids.length > 0
property alias available: root.vpnAvailable
property alias isBusy: root.vpnIsBusy
property alias connected: root.vpnConnected
property string networkInfoSSID: ""
property string networkInfoDetails: ""
property bool networkInfoLoading: false
@@ -93,7 +112,7 @@ Singleton {
signal networksUpdated
signal connectionChanged
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason)
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason, string connType, string connName, string vpnService)
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
@@ -108,10 +127,8 @@ Singleton {
target: DMSService
function onNetworkStateUpdate(data) {
if (DMSService.verboseLogs) {
const networksCount = data.wifiNetworks?.length ?? "null"
console.log("NetworkManagerService: Subscription update received, networks:", networksCount)
}
const networksCount = data.wifiNetworks?.length ?? "null"
console.log("DMSNetworkService: Subscription update received, networks:", networksCount)
updateState(data)
}
}
@@ -150,10 +167,6 @@ Singleton {
networkAvailable = DMSService.capabilities.includes("network")
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Network available:", networkAvailable)
}
if (networkAvailable && !stateInitialized) {
stateInitialized = true
getState()
@@ -169,7 +182,11 @@ Singleton {
credentialsReason = data.reason || "Credentials required"
credentialsRequested = true
credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason)
const connType = data.connType || ""
const connName = data.name || data.connectionId || ""
const vpnService = data.vpnService || ""
credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason, connType, connName, vpnService)
}
function addRef() {
@@ -195,9 +212,6 @@ Singleton {
if (response.result) {
updateState(response.result)
if (!initialStateFetched && response.result.wifiEnabled && (!response.result.wifiNetworks || response.result.wifiNetworks.length === 0)) {
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Initial state has no networks, triggering scan")
}
initialStateFetched = true
Qt.callLater(() => scanWifi())
}
@@ -209,6 +223,8 @@ Singleton {
const previousConnecting = isConnecting
const previousConnectingSSID = connectingSSID
backend = state.backend || ""
vpnAvailable = networkAvailable && backend === "networkmanager"
networkStatus = state.networkStatus || "disconnected"
primaryConnection = state.primaryConnection || ""
@@ -251,6 +267,12 @@ Singleton {
networksUpdated()
}
if (state.vpnProfiles) {
vpnProfiles = state.vpnProfiles
}
vpnActive = state.vpnActive || []
userPreference = state.preference || "auto"
isConnecting = state.isConnecting || false
connectingSSID = state.connectingSSID || ""
@@ -259,10 +281,8 @@ Singleton {
if (pendingConnectionSSID) {
if (wifiConnected && currentWifiSSID === pendingConnectionSSID && wifiIP) {
if (DMSService.verboseLogs) {
const elapsed = Date.now() - pendingConnectionStartTime
console.log("NetworkManagerService: Successfully connected to", pendingConnectionSSID, "in", elapsed, "ms")
}
const elapsed = Date.now() - pendingConnectionStartTime
console.info("DMSNetworkService: Successfully connected to", pendingConnectionSSID, "in", elapsed, "ms")
ToastService.showInfo(`Connected to ${pendingConnectionSSID}`)
if (userPreference === "wifi" || userPreference === "auto") {
@@ -272,20 +292,17 @@ Singleton {
pendingConnectionSSID = ""
connectionStatus = "connected"
} else if (previousConnecting && !isConnecting && !wifiConnected) {
const elapsed = Date.now() - pendingConnectionStartTime
const isCancellationError = connectionError === "user-canceled"
const isBadCredentials = connectionError === "bad-credentials"
if (elapsed < 5000) {
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Quick connection failure, likely authentication error")
}
if (isCancellationError) {
connectionStatus = "cancelled"
pendingConnectionSSID = ""
} else if (isBadCredentials) {
connectionStatus = "invalid_password"
pendingConnectionSSID = ""
} else {
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Connection failed for", pendingConnectionSSID)
}
if (connectionError === "connection-failed") {
ToastService.showError(I18n.tr("Connection failed. Check password and try again."))
} else if (connectionError) {
if (connectionError) {
ToastService.showError(I18n.tr("Failed to connect to ") + pendingConnectionSSID)
}
connectionStatus = "failed"
@@ -327,18 +344,12 @@ Singleton {
function scanWifi() {
if (!networkAvailable || isScanning || !wifiEnabled) return
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Starting WiFi scan...")
}
isScanning = true
DMSService.sendRequest("network.wifi.scan", null, response => {
isScanning = false
if (response.error) {
console.warn("NetworkManagerService: WiFi scan failed:", response.error)
console.warn("DMSNetworkService: WiFi scan failed:", response.error)
} else {
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Scan completed")
}
Qt.callLater(() => getState())
}
})
@@ -378,8 +389,8 @@ Singleton {
DMSService.sendRequest("network.wifi.connect", params, response => {
if (response.error) {
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Connection request failed:", response.error)
if (connectionStatus === "cancelled") {
return
}
connectionError = response.error
@@ -387,10 +398,6 @@ Singleton {
pendingConnectionSSID = ""
connectionStatus = "failed"
ToastService.showError(I18n.tr("Failed to start connection to ") + ssid)
} else {
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Connection request sent for", ssid)
}
}
})
}
@@ -410,7 +417,12 @@ Singleton {
}
function submitCredentials(token, secrets, save) {
if (!networkAvailable || DMSService.apiVersion < 7) return
console.log("submitCredentials: networkAvailable=" + networkAvailable + " apiVersion=" + DMSService.apiVersion)
if (!networkAvailable || DMSService.apiVersion < 7) {
console.warn("submitCredentials: Aborting - networkAvailable=" + networkAvailable + " apiVersion=" + DMSService.apiVersion)
return
}
const params = {
token: token,
@@ -418,15 +430,11 @@ Singleton {
save: save || false
}
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Submitting credentials for token", token)
}
credentialsRequested = false
DMSService.sendRequest("network.credentials.submit", params, response => {
if (response.error) {
console.warn("NetworkManagerService: Failed to submit credentials:", response.error)
console.warn("DMSNetworkService: Failed to submit credentials:", response.error)
}
})
}
@@ -435,21 +443,16 @@ Singleton {
if (!networkAvailable || DMSService.apiVersion < 7) return
const params = {
token: token,
cancel: true
}
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Cancelling credentials for token", token)
token: token
}
credentialsRequested = false
pendingConnectionSSID = ""
connectionStatus = "cancelled"
DMSService.sendRequest("network.credentials.submit", params, response => {
DMSService.sendRequest("network.credentials.cancel", params, response => {
if (response.error) {
console.warn("NetworkManagerService: Failed to cancel credentials:", response.error)
console.warn("DMSNetworkService: Failed to cancel credentials:", response.error)
}
})
}
@@ -703,31 +706,125 @@ Singleton {
}
}
function refreshVpnProfiles() {
if (!vpnAvailable) return
DMSService.sendRequest("network.vpn.profiles", null, response => {
if (response.result) {
vpnProfiles = response.result
}
})
}
function refreshVpnActive() {
if (!vpnAvailable) return
DMSService.sendRequest("network.vpn.active", null, response => {
if (response.result) {
vpnActive = response.result
}
})
}
function connectVpn(uuidOrName, singleActive = false) {
if (!vpnAvailable || vpnIsBusy) return
vpnIsBusy = true
const params = {
uuidOrName: uuidOrName,
singleActive: singleActive
}
DMSService.sendRequest("network.vpn.connect", params, response => {
vpnIsBusy = false
if (response.error) {
ToastService.showError(I18n.tr("Failed to connect VPN"))
} else {
Qt.callLater(() => getState())
}
})
}
function connect(uuidOrName, singleActive = false) {
connectVpn(uuidOrName, singleActive)
}
function disconnectVpn(uuidOrName) {
if (!vpnAvailable || vpnIsBusy) return
vpnIsBusy = true
const params = {
uuidOrName: uuidOrName
}
DMSService.sendRequest("network.vpn.disconnect", params, response => {
vpnIsBusy = false
if (response.error) {
ToastService.showError(I18n.tr("Failed to disconnect VPN"))
} else {
Qt.callLater(() => getState())
}
})
}
function disconnect(uuidOrName) {
disconnectVpn(uuidOrName)
}
function disconnectAllVpns() {
if (!vpnAvailable || vpnIsBusy) return
vpnIsBusy = true
DMSService.sendRequest("network.vpn.disconnectAll", null, response => {
vpnIsBusy = false
if (response.error) {
ToastService.showError(I18n.tr("Failed to disconnect VPNs"))
} else {
Qt.callLater(() => getState())
}
})
}
function disconnectAllActive() {
disconnectAllVpns()
}
function toggleVpn(uuid) {
if (uuid) {
if (isActiveVpnUuid(uuid)) {
disconnectVpn(uuid)
} else {
connectVpn(uuid)
}
return
}
if (vpnProfiles.length > 0) {
connectVpn(vpnProfiles[0].uuid)
}
}
function toggle(uuid) {
toggleVpn(uuid)
}
function isActiveVpnUuid(uuid) {
return activeUuids && activeUuids.indexOf(uuid) !== -1
}
function isActiveUuid(uuid) {
return isActiveVpnUuid(uuid)
}
function refreshNetworkState() {
if (networkAvailable) {
getState()
}
}
function splitNmcliFields(line) {
const parts = []
let cur = ""
let escape = false
for (var i = 0; i < line.length; i++) {
const ch = line[i]
if (escape) {
cur += ch
escape = false
} else if (ch === '\\') {
escape = true
} else if (ch === ':') {
parts.push(cur)
cur = ""
} else {
cur += ch
}
}
parts.push(cur)
return parts
}
}

View File

@@ -22,7 +22,6 @@ Singleton {
property bool subscribeConnected: false
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
readonly property bool verboseLogs: Quickshell.env("DMS_VERBOSE_LOGS") === "1"
property var pendingRequests: ({})
property int requestIdCounter: 0
@@ -42,6 +41,7 @@ Singleton {
signal loginctlEvent(var event)
signal capabilitiesReceived()
signal credentialsRequest(var data)
signal bluetoothPairingRequest(var data)
Component.onCompleted: {
if (socketPath && socketPath.length > 0) {
@@ -169,9 +169,7 @@ Singleton {
return
}
if (root.verboseLogs) {
console.log("DMSService: Request socket <<", line)
}
console.log("DMSService: Request socket <<", line)
try {
const response = JSON.parse(line)
@@ -201,9 +199,7 @@ Singleton {
return
}
if (root.verboseLogs) {
console.log("DMSService: Subscribe socket <<", line)
}
console.log("DMSService: Subscribe socket <<", line)
try {
const response = JSON.parse(line)
@@ -220,9 +216,7 @@ Singleton {
"method": "subscribe"
}
if (verboseLogs) {
console.log("DMSService: Subscribing to all services")
}
console.log("DMSService: Subscribing to all services")
subscribeSocket.send(request)
}
@@ -253,7 +247,7 @@ Singleton {
apiVersion = data.apiVersion || 0
capabilities = data.capabilities || []
console.log("DMSService: Connected (API v" + apiVersion + ") -", JSON.stringify(capabilities))
console.info("DMSService: Connected (API v" + apiVersion + ") -", JSON.stringify(capabilities))
if (apiVersion < expectedApiVersion) {
ToastService.showError("DMS server is outdated (API v" + apiVersion + ", expected v" + expectedApiVersion + ")")
@@ -270,11 +264,14 @@ Singleton {
} else {
loginctlStateUpdate(data)
}
} else if (service === "bluetooth.pairing") {
bluetoothPairingRequest(data)
}
}
function sendRequest(method, params, callback) {
if (!isConnected) {
console.warn("DMSService.sendRequest: Not connected, method:", method)
if (callback) {
callback({
"error": "not connected to DMS socket"
@@ -298,6 +295,7 @@ Singleton {
pendingRequests[id] = callback
}
console.log("DMSService.sendRequest: Sending request id=" + id + " method=" + method)
requestSocket.send(request)
}
@@ -408,4 +406,48 @@ Singleton {
function unlockSession(callback) {
sendRequest("loginctl.unlock", null, callback)
}
function bluetoothPair(devicePath, callback) {
sendRequest("bluetooth.pair", {
"device": devicePath
}, callback)
}
function bluetoothConnect(devicePath, callback) {
sendRequest("bluetooth.connect", {
"device": devicePath
}, callback)
}
function bluetoothDisconnect(devicePath, callback) {
sendRequest("bluetooth.disconnect", {
"device": devicePath
}, callback)
}
function bluetoothRemove(devicePath, callback) {
sendRequest("bluetooth.remove", {
"device": devicePath
}, callback)
}
function bluetoothTrust(devicePath, callback) {
sendRequest("bluetooth.trust", {
"device": devicePath
}, callback)
}
function bluetoothSubmitPairing(token, secrets, accept, callback) {
sendRequest("bluetooth.pairing.submit", {
"token": token,
"secrets": secrets,
"accept": accept
}, callback)
}
function bluetoothCancelPairing(token, callback) {
sendRequest("bluetooth.pairing.cancel", {
"token": token
}, callback)
}
}

View File

@@ -682,7 +682,7 @@ Singleton {
// Prefer PRETTY_NAME, fallback to NAME
const distroName = prettyName || name || "Linux"
distribution = distroName
console.log("Detected distribution:", distroName)
console.info("Detected distribution:", distroName)
} catch (e) {
console.warn("Failed to parse /etc/os-release:", e)
distribution = "Linux"

View File

@@ -66,9 +66,11 @@ Singleton {
}
}
function setBrightness(percentage, device) {
function setBrightness(percentage, device, suppressOsd) {
setBrightnessInternal(percentage, device)
brightnessChanged()
if (!suppressOsd) {
brightnessChanged()
}
}
function setCurrentDevice(deviceName, saveToSession = false) {
@@ -509,7 +511,7 @@ Singleton {
if (ddcAvailable) {
ddcDisplayDetectionProcess.running = true
} else {
console.log("DisplayService: ddcutil not available")
console.info("DisplayService: ddcutil not available")
}
}
}
@@ -545,7 +547,7 @@ Singleton {
}
ddcDevices = newDdcDevices
console.log("DisplayService: Found", ddcDevices.length, "DDC displays")
console.info("DisplayService: Found", ddcDevices.length, "DDC displays")
// Queue initial brightness readings for DDC devices
ddcInitQueue = []

View File

@@ -55,7 +55,7 @@ Singleton {
function createIdleMonitors() {
if (!idleMonitorAvailable) {
console.log("IdleService: IdleMonitor not available, skipping creation")
console.info("IdleService: IdleMonitor not available, skipping creation")
return
}
@@ -149,7 +149,7 @@ Singleton {
if (!idleMonitorAvailable) {
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.")
} else {
console.log("IdleService: Initialized with idle monitoring support")
console.info("IdleService: Initialized with idle monitoring support")
createIdleMonitors()
}
}

View File

@@ -116,7 +116,7 @@ Singleton {
function activate() {
if (!isActive) {
isActive = true
console.log("LegacyNetworkService: Activating...")
console.info("LegacyNetworkService: Activating...")
initializeDBusMonitors()
}
}

View File

@@ -11,6 +11,7 @@ Singleton {
id: root
property bool networkAvailable: activeService !== null
property string backend: activeService?.backend ?? ""
property string networkStatus: activeService?.networkStatus ?? "disconnected"
property string primaryConnection: activeService?.primaryConnection ?? ""
@@ -79,7 +80,7 @@ Singleton {
signal networksUpdated
signal connectionChanged
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason)
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason, string connType, string connName, string vpnService)
property bool usingLegacy: false
property var activeService: null
@@ -87,9 +88,9 @@ Singleton {
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
Component.onCompleted: {
console.log("NetworkService: Initializing...")
console.info("NetworkService: Initializing...")
if (!socketPath || socketPath.length === 0) {
console.log("NetworkService: DMS_SOCKET not set, using LegacyNetworkService")
console.info("NetworkService: DMS_SOCKET not set, using LegacyNetworkService")
useLegacyService()
} else {
console.log("NetworkService: DMS_SOCKET found, waiting for capabilities...")
@@ -97,17 +98,17 @@ Singleton {
}
Connections {
target: NetworkManagerService
target: DMSNetworkService
function onNetworkAvailableChanged() {
if (!activeService && NetworkManagerService.networkAvailable) {
console.log("NetworkService: Network capability detected, using NetworkManagerService")
activeService = NetworkManagerService
if (!activeService && DMSNetworkService.networkAvailable) {
console.info("NetworkService: Network capability detected, using DMSNetworkService")
activeService = DMSNetworkService
usingLegacy = false
console.log("NetworkService: Switched to NetworkManagerService, networkAvailable:", networkAvailable)
console.info("NetworkService: Switched to DMSNetworkService, networkAvailable:", networkAvailable)
connectSignals()
} else if (!activeService && !NetworkManagerService.networkAvailable && socketPath && socketPath.length > 0) {
console.log("NetworkService: Network capability not available in DMS, using LegacyNetworkService")
} else if (!activeService && !DMSNetworkService.networkAvailable && socketPath && socketPath.length > 0) {
console.info("NetworkService: Network capability not available in DMS, using LegacyNetworkService")
useLegacyService()
}
}
@@ -116,7 +117,7 @@ Singleton {
function useLegacyService() {
activeService = LegacyNetworkService
usingLegacy = true
console.log("NetworkService: Switched to LegacyNetworkService, networkAvailable:", networkAvailable)
console.info("NetworkService: Switched to LegacyNetworkService, networkAvailable:", networkAvailable)
if (LegacyNetworkService.activate) {
LegacyNetworkService.activate()
}

View File

@@ -1,6 +1,6 @@
pragma Singleton
pragma ComponentBehavior: Bound
pragma ComponentBehavior
import QtCore
import QtQuick
@@ -37,7 +37,7 @@ Singleton {
property bool matugenSuppression: false
property bool configGenerationPending: false
signal windowUrgentChanged()
signal windowUrgentChanged
Component.onCompleted: fetchOutputs()
@@ -89,7 +89,7 @@ Singleton {
onExited: exitCode => {
if (exitCode === 0) {
console.log("NiriService: Generated layout config at", configPath)
console.info("NiriService: Generated layout config at", configPath)
return
}
console.warn("NiriService: Failed to write layout config, exit code:", exitCode)
@@ -102,7 +102,7 @@ Singleton {
onExited: exitCode => {
if (exitCode === 0) {
console.log("NiriService: Generated binds config at", bindsPath)
console.info("NiriService: Generated binds config at", bindsPath)
return
}
console.warn("NiriService: Failed to write binds config, exit code:", exitCode)
@@ -140,28 +140,30 @@ Singleton {
}
function fetchOutputs() {
if (!CompositorService.isNiri) return
if (!CompositorService.isNiri)
return
Proc.runCommand("niri-fetch-outputs", ["niri", "msg", "-j", "outputs"], (output, exitCode) => {
if (exitCode !== 0) {
console.warn("NiriService: Failed to fetch outputs, exit code:", exitCode)
return
}
try {
const outputsData = JSON.parse(output)
outputs = outputsData
console.log("NiriService: Loaded", Object.keys(outputsData).length, "outputs")
updateDisplayScales()
if (windows.length > 0) {
windows = sortWindowsByLayout(windows)
}
} catch (e) {
console.warn("NiriService: Failed to parse outputs:", e)
}
})
if (exitCode !== 0) {
console.warn("NiriService: Failed to fetch outputs, exit code:", exitCode)
return
}
try {
const outputsData = JSON.parse(output)
outputs = outputsData
console.info("NiriService: Loaded", Object.keys(outputsData).length, "outputs")
updateDisplayScales()
if (windows.length > 0) {
windows = sortWindowsByLayout(windows)
}
} catch (e) {
console.warn("NiriService: Failed to parse outputs:", e)
}
})
}
function updateDisplayScales() {
if (!outputs || Object.keys(outputs).length === 0) return
if (!outputs || Object.keys(outputs).length === 0)
return
const scales = {}
for (const outputName in outputs) {
@@ -175,49 +177,47 @@ Singleton {
}
function sortWindowsByLayout(windowList) {
return [...windowList].sort((a, b) => {
const aWorkspace = workspaces[a.workspace_id]
const bWorkspace = workspaces[b.workspace_id]
if (aWorkspace && bWorkspace) {
const aOutput = aWorkspace.output
const bOutput = bWorkspace.output
const aOutputInfo = outputs[aOutput]
const bOutputInfo = outputs[bOutput]
if (aOutputInfo && bOutputInfo && aOutputInfo.logical && bOutputInfo.logical) {
if (aOutputInfo.logical.x !== bOutputInfo.logical.x) {
return aOutputInfo.logical.x - bOutputInfo.logical.x
}
if (aOutputInfo.logical.y !== bOutputInfo.logical.y) {
return aOutputInfo.logical.y - bOutputInfo.logical.y
}
}
if (aOutput === bOutput && aWorkspace.idx !== bWorkspace.idx) {
return aWorkspace.idx - bWorkspace.idx
const enriched = windowList.map(w => {
const ws = workspaces[w.workspace_id]
if (!ws) {
return {
window: w,
outputX: 999999,
outputY: 999999,
wsIdx: 999999,
col: 999999,
row: 999999
}
}
if (a.workspace_id === b.workspace_id && a.layout && b.layout) {
if (a.layout.pos_in_scrolling_layout && b.layout.pos_in_scrolling_layout) {
const aPos = a.layout.pos_in_scrolling_layout
const bPos = b.layout.pos_in_scrolling_layout
const outputInfo = outputs[ws.output]
const outputX = (outputInfo && outputInfo.logical) ? outputInfo.logical.x : 999999
const outputY = (outputInfo && outputInfo.logical) ? outputInfo.logical.y : 999999
if (aPos.length > 1 && bPos.length > 1) {
if (aPos[0] !== bPos[0]) {
return aPos[0] - bPos[0]
}
if (aPos[1] !== bPos[1]) {
return aPos[1] - bPos[1]
}
}
}
const pos = w.layout?.pos_in_scrolling_layout
const col = (pos && pos.length >= 2) ? pos[0] : 999999
const row = (pos && pos.length >= 2) ? pos[1] : 999999
return {
window: w,
outputX: outputX,
outputY: outputY,
wsIdx: ws.idx,
col: col,
row: row
}
return a.id - b.id
})
enriched.sort((a, b) => {
if (a.outputX !== b.outputX) return a.outputX - b.outputX
if (a.outputY !== b.outputY) return a.outputY - b.outputY
if (a.wsIdx !== b.wsIdx) return a.wsIdx - b.wsIdx
if (a.col !== b.col) return a.col - b.col
if (a.row !== b.row) return a.row - b.row
return a.window.id - b.window.id
})
return enriched.map(e => e.window)
}
function handleNiriEvent(event) {
@@ -233,6 +233,9 @@ Singleton {
case 'WorkspaceActiveWindowChanged':
handleWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged)
break
case 'WindowFocusChanged':
handleWindowFocusChanged(event.WindowFocusChanged)
break
case 'WindowsChanged':
handleWindowsChanged(event.WindowsChanged)
break
@@ -267,14 +270,18 @@ Singleton {
}
function handleWorkspacesChanged(data) {
const workspaces = {}
const newWorkspaces = {}
for (const ws of data.workspaces) {
workspaces[ws.id] = ws
const oldWs = root.workspaces[ws.id]
newWorkspaces[ws.id] = ws
if (oldWs && oldWs.active_window_id !== undefined) {
newWorkspaces[ws.id].active_window_id = oldWs.active_window_id
}
}
root.workspaces = workspaces
allWorkspaces = [...data.workspaces].sort((a, b) => a.idx - b.idx)
root.workspaces = newWorkspaces
allWorkspaces = Object.values(newWorkspaces).sort((a, b) => a.idx - b.idx)
focusedWorkspaceIndex = allWorkspaces.findIndex(w => w.is_focused)
if (focusedWorkspaceIndex >= 0) {
@@ -296,33 +303,101 @@ Singleton {
}
const output = ws.output
const updatedWorkspaces = {}
for (const id in root.workspaces) {
const workspace = root.workspaces[id]
const got_activated = workspace.id === data.id
const updatedWs = {}
for (let prop in workspace) {
updatedWs[prop] = workspace[prop]
}
if (workspace.output === output) {
workspace.is_active = got_activated
updatedWs.is_active = got_activated
}
if (data.focused) {
workspace.is_focused = got_activated
updatedWs.is_focused = got_activated
}
updatedWorkspaces[id] = updatedWs
}
root.workspaces = updatedWorkspaces
focusedWorkspaceId = data.id
focusedWorkspaceIndex = allWorkspaces.findIndex(w => w.id === data.id)
focusedWorkspaceIndex = Object.values(updatedWorkspaces).findIndex(w => w.id === data.id)
if (focusedWorkspaceIndex >= 0) {
currentOutput = allWorkspaces[focusedWorkspaceIndex].output || ""
const ws = Object.values(updatedWorkspaces)[focusedWorkspaceIndex]
currentOutput = ws.output || ""
}
allWorkspaces = Object.values(root.workspaces).sort((a, b) => a.idx - b.idx)
allWorkspaces = Object.values(updatedWorkspaces).sort((a, b) => a.idx - b.idx)
updateCurrentOutputWorkspaces()
workspacesChanged()
}
function handleWindowFocusChanged(data) {
const focusedWindowId = data.id
let focusedWindow = null
const updatedWindows = []
for (var i = 0; i < windows.length; i++) {
const w = windows[i]
const updatedWindow = {}
for (let prop in w) {
updatedWindow[prop] = w[prop]
}
updatedWindow.is_focused = (w.id === focusedWindowId)
if (updatedWindow.is_focused) {
focusedWindow = updatedWindow
}
updatedWindows.push(updatedWindow)
}
windows = updatedWindows
if (focusedWindow) {
const ws = root.workspaces[focusedWindow.workspace_id]
if (ws && ws.active_window_id !== focusedWindowId) {
const updatedWs = {}
for (let prop in ws) {
updatedWs[prop] = ws[prop]
}
updatedWs.active_window_id = focusedWindowId
const updatedWorkspaces = {}
for (const id in root.workspaces) {
updatedWorkspaces[id] = id === focusedWindow.workspace_id ? updatedWs : root.workspaces[id]
}
root.workspaces = updatedWorkspaces
}
}
}
function handleWorkspaceActiveWindowChanged(data) {
const ws = root.workspaces[data.workspace_id]
if (ws) {
const updatedWs = {}
for (let prop in ws) {
updatedWs[prop] = ws[prop]
}
updatedWs.active_window_id = data.active_window_id
const updatedWorkspaces = {}
for (const id in root.workspaces) {
updatedWorkspaces[id] = id === data.workspace_id ? updatedWs : root.workspaces[id]
}
root.workspaces = updatedWorkspaces
}
const updatedWindows = []
for (var i = 0; i < windows.length; i++) {
@@ -354,7 +429,8 @@ Singleton {
}
function handleWindowOpenedOrChanged(data) {
if (!data.window) return
if (!data.window)
return
const window = data.window
const existingIndex = windows.findIndex(w => w.id === window.id)
@@ -363,14 +439,14 @@ Singleton {
const updatedWindows = [...windows]
updatedWindows[existingIndex] = window
windows = sortWindowsByLayout(updatedWindows)
return
} else {
windows = sortWindowsByLayout([...windows, window])
}
windows = sortWindowsByLayout([...windows, window])
}
function handleWindowLayoutsChanged(data) {
if (!data.changes) return
if (!data.changes)
return
const updatedWindows = [...windows]
let hasChanges = false
@@ -380,7 +456,8 @@ Singleton {
const layoutData = change[1]
const windowIndex = updatedWindows.findIndex(w => w.id === windowId)
if (windowIndex < 0) continue
if (windowIndex < 0)
continue
const updatedWindow = {}
for (var prop in updatedWindows[windowIndex]) {
@@ -391,14 +468,15 @@ Singleton {
hasChanges = true
}
if (!hasChanges) return
if (!hasChanges)
return
windows = sortWindowsByLayout(updatedWindows)
windowsChanged()
}
function handleOutputsChanged(data) {
if (!data.outputs) return
if (!data.outputs)
return
outputs = data.outputs
updateDisplayScales()
windows = sortWindowsByLayout(windows)
@@ -442,14 +520,22 @@ Singleton {
function handleWorkspaceUrgencyChanged(data) {
const ws = root.workspaces[data.id]
if (!ws) return
if (!ws)
return
ws.is_urgent = data.urgent
const idx = allWorkspaces.findIndex(w => w.id === data.id)
if (idx >= 0) {
allWorkspaces[idx].is_urgent = data.urgent
const updatedWs = {}
for (let prop in ws) {
updatedWs[prop] = ws[prop]
}
updatedWs.is_urgent = data.urgent
const updatedWorkspaces = {}
for (const id in root.workspaces) {
updatedWorkspaces[id] = id === data.id ? updatedWs : root.workspaces[id]
}
root.workspaces = updatedWorkspaces
allWorkspaces = Object.values(updatedWorkspaces).sort((a, b) => a.idx - b.idx)
windowUrgentChanged()
}
@@ -465,41 +551,86 @@ Singleton {
}
function send(request) {
if (!CompositorService.isNiri || !requestSocket.connected) return false
if (!CompositorService.isNiri || !requestSocket.connected)
return false
requestSocket.send(request)
return true
}
function doScreenTransition() {
return send({"Action": {"DoScreenTransition": {"delay_ms": 0}}})
return send({
"Action": {
"DoScreenTransition": {
"delay_ms": 0
}
}
})
}
function toggleOverview() {
return send({"Action": {"ToggleOverview": {}}})
return send({
"Action": {
"ToggleOverview": {}
}
})
}
function switchToWorkspace(workspaceIndex) {
return send({"Action": {"FocusWorkspace": {"reference": {"Index": workspaceIndex}}}})
return send({
"Action": {
"FocusWorkspace": {
"reference": {
"Index": workspaceIndex
}
}
}
})
}
function focusWindow(windowId) {
return send({"Action": {"FocusWindow": {"id": windowId}}})
return send({
"Action": {
"FocusWindow": {
"id": windowId
}
}
})
}
function powerOffMonitors() {
return send({"Action": {"PowerOffMonitors": {}}})
return send({
"Action": {
"PowerOffMonitors": {}
}
})
}
function powerOnMonitors() {
return send({"Action": {"PowerOnMonitors": {}}})
return send({
"Action": {
"PowerOnMonitors": {}
}
})
}
function cycleKeyboardLayout() {
return send({"Action": {"SwitchLayout": {"layout": "Next"}}})
return send({
"Action": {
"SwitchLayout": {
"layout": "Next"
}
}
})
}
function quit() {
return send({"Action": {"Quit": {"skip_confirmation": true}}})
return send({
"Action": {
"Quit": {
"skip_confirmation": true
}
}
})
}
function getCurrentOutputWorkspaceNumbers() {
@@ -526,13 +657,17 @@ Singleton {
}
function findNiriWindow(toplevel) {
if (!toplevel.appId) return null
if (!toplevel.appId)
return null
for (var j = 0; j < windows.length; j++) {
const niriWindow = windows[j]
if (niriWindow.app_id === toplevel.appId) {
if (!niriWindow.title || niriWindow.title === toplevel.title) {
return {"niriIndex": j, "niriWindow": niriWindow}
return {
"niriIndex": j,
"niriWindow": niriWindow
}
}
}
}
@@ -549,35 +684,50 @@ Singleton {
for (const niriWindow of sortWindowsByLayout(windows)) {
let bestMatch = null
let bestScore = -1
for (const toplevel of toplevels) {
if (usedToplevels.has(toplevel)) continue
if (usedToplevels.has(toplevel))
continue
if (toplevel.appId === niriWindow.app_id) {
if (niriWindow.title && toplevel.title === niriWindow.title) {
bestMatch = toplevel
break
let score = 1
if (niriWindow.title && toplevel.title) {
if (toplevel.title === niriWindow.title) {
score = 3
} else if (toplevel.title.includes(niriWindow.title) || niriWindow.title.includes(toplevel.title)) {
score = 2
}
}
if (!niriWindow.title && !bestMatch) {
if (score > bestScore) {
bestScore = score
bestMatch = toplevel
if (score === 3)
break
}
}
}
if (!bestMatch) continue
if (!bestMatch)
continue
usedToplevels.add(bestMatch)
const workspace = workspaces[niriWindow.workspace_id]
const isFocused = niriWindow.is_focused ?? (workspace && workspace.active_window_id === niriWindow.id) ?? false
const enrichedToplevel = {
appId: bestMatch.appId,
title: bestMatch.title,
activated: bestMatch.activated,
niriWindowId: niriWindow.id,
niriWorkspaceId: niriWindow.workspace_id,
activate: function() {
"appId": bestMatch.appId,
"title": bestMatch.title,
"activated": isFocused,
"niriWindowId": niriWindow.id,
"niriWorkspaceId": niriWindow.workspace_id,
"activate": function () {
return NiriService.focusWindow(niriWindow.id)
},
close: function() {
"close": function () {
if (bestMatch.close) {
return bestMatch.close()
}
@@ -614,7 +764,8 @@ Singleton {
}
}
if (currentWorkspaceId === null) return toplevels
if (currentWorkspaceId === null)
return toplevels
const workspaceWindows = windows.filter(niriWindow => niriWindow.workspace_id === currentWorkspaceId)
const usedToplevels = new Set()
@@ -622,35 +773,50 @@ Singleton {
for (const niriWindow of workspaceWindows) {
let bestMatch = null
let bestScore = -1
for (const toplevel of toplevels) {
if (usedToplevels.has(toplevel)) continue
if (usedToplevels.has(toplevel))
continue
if (toplevel.appId === niriWindow.app_id) {
if (niriWindow.title && toplevel.title === niriWindow.title) {
bestMatch = toplevel
break
let score = 1
if (niriWindow.title && toplevel.title) {
if (toplevel.title === niriWindow.title) {
score = 3
} else if (toplevel.title.includes(niriWindow.title) || niriWindow.title.includes(toplevel.title)) {
score = 2
}
}
if (!niriWindow.title && !bestMatch) {
if (score > bestScore) {
bestScore = score
bestMatch = toplevel
if (score === 3)
break
}
}
}
if (!bestMatch) continue
if (!bestMatch)
continue
usedToplevels.add(bestMatch)
const workspace = workspaces[niriWindow.workspace_id]
const isFocused = niriWindow.is_focused ?? (workspace && workspace.active_window_id === niriWindow.id) ?? false
const enrichedToplevel = {
appId: bestMatch.appId,
title: bestMatch.title,
activated: bestMatch.activated,
niriWindowId: niriWindow.id,
niriWorkspaceId: niriWindow.workspace_id,
activate: function() {
"appId": bestMatch.appId,
"title": bestMatch.title,
"activated": isFocused,
"niriWindowId": niriWindow.id,
"niriWorkspaceId": niriWindow.workspace_id,
"activate": function () {
return NiriService.focusWindow(niriWindow.id)
},
close: function() {
"close": function () {
if (bestMatch.close) {
return bestMatch.close()
}
@@ -671,10 +837,10 @@ Singleton {
}
function generateNiriLayoutConfig() {
const niriSocket = Quickshell.env("NIRI_SOCKET")
if (!niriSocket || niriSocket.length === 0) return
if (configGenerationPending) return
if (!CompositorService.isNiri || configGenerationPending)
return
suppressNextToast()
configGenerationPending = true
configGenerationDebounce.restart()
}
@@ -696,7 +862,6 @@ Singleton {
width 2
}
}
window-rule {
geometry-corner-radius ${cornerRadius}
clip-to-geometry true
@@ -727,4 +892,4 @@ window-rule {
writeBindsProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cp --no-preserve=mode "${sourceBindsPath}" "${bindsPath}"`]
writeBindsProcess.running = true
}
}
}

View File

@@ -1,6 +1,6 @@
pragma Singleton
pragma ComponentBehavior
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
@@ -174,7 +174,7 @@ Singleton {
if (socketPath && socketPath.length > 0) {
checkDMSCapabilities()
} else {
console.log("PortalService: DMS_SOCKET not set")
console.info("PortalService: DMS_SOCKET not set")
}
colorSchemeDetector.running = true
}
@@ -212,7 +212,7 @@ Singleton {
checkAccountsService()
checkSettingsPortal()
} else {
console.log("PortalService: freedesktop capability not available in DMS")
console.info("PortalService: freedesktop capability not available in DMS")
}
}

View File

@@ -23,7 +23,6 @@ Singleton {
if ((node.type & PwNodeType.AudioInStream) === PwNodeType.AudioInStream) {
if (!looksLikeSystemVirtualMic(node)) {
console.log(node.audio)
if (node.audio && node.audio.muted) {
return false
}

View File

@@ -59,7 +59,7 @@ Singleton {
detectElogindProcess.running = true
detectHibernateProcess.running = true
detectPrimeRunProcess.running = true
console.log("SessionService: Native inhibitor available:", nativeInhibitorAvailable)
console.info("SessionService: Native inhibitor available:", nativeInhibitorAvailable)
if (!SettingsData.loginctlLockIntegration) {
console.log("SessionService: loginctl lock integration disabled by user")
return

View File

@@ -263,4 +263,22 @@ Singleton {
running: refCount > 0 && distributionSupported && (pkgManager || updChecker)
onTriggered: checkForUpdates()
}
IpcHandler {
target: "systemupdater"
function updatestatus(): string {
if (root.isChecking) {
return "ERROR: already checking"
}
if (!distributionSupported) {
return "ERROR: distribution not supported"
}
if (!pkgManager && !updChecker) {
return "ERROR: update checker not available"
}
root.checkForUpdates()
return "SUCCESS: Now checking..."
}
}
}

View File

@@ -1,246 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
// Minimal VPN controller backed by NetworkManager (nmcli + D-Bus monitor)
Singleton {
id: root
property int refCount: 0
onRefCountChanged: {
console.log("VpnService: refCount changed to", refCount)
if (refCount > 0 && !nmMonitor.running) {
console.log("VpnService: Starting nmMonitor")
nmMonitor.running = true
refreshAll()
} else if (refCount === 0 && nmMonitor.running) {
console.log("VpnService: Stopping nmMonitor")
nmMonitor.running = false
}
}
// State
property bool available: true
property bool isBusy: false
property string errorMessage: ""
// Profiles discovered on the system
// [{ name, uuid, type }]
property var profiles: []
// Allow multiple active VPNs (set true to allow concurrent connections)
// Default: allow multiple, to align with NetworkManager capability
property bool singleActive: false
// Active VPN connections (may be multiple)
// Full list and convenience projections
property var activeConnections: [] // [{ name, uuid, device, state }]
property var activeUuids: []
property var activeNames: []
// Back-compat single values (first active if present)
property string activeUuid: activeUuids.length > 0 ? activeUuids[0] : ""
property string activeName: activeNames.length > 0 ? activeNames[0] : ""
property string activeDevice: activeConnections.length > 0 ? (activeConnections[0].device || "") : ""
property string activeState: activeConnections.length > 0 ? (activeConnections[0].state || "") : ""
property bool connected: activeUuids.length > 0
// Use implicit property notify signals (profilesChanged, activeUuidChanged, etc.)
function refreshAll() {
listProfiles()
refreshActive()
}
// Monitor NetworkManager changes and refresh on activity
Process {
id: nmMonitor
command: ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.NetworkManager"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (line.includes("ActiveConnection") || line.includes("PropertiesChanged") || line.includes("StateChanged")) {
refreshAll()
}
}
}
}
// Query all VPN profiles
function listProfiles() {
getProfiles.running = true
}
Process {
id: getProfiles
command: ["bash", "-lc", "nmcli -t -f NAME,UUID,TYPE connection show | while IFS=: read -r name uuid type; do case \"$type\" in vpn) svc=$(nmcli -g vpn.service-type connection show uuid \"$uuid\" 2>/dev/null); echo \"$name:$uuid:$type:$svc\" ;; wireguard) echo \"$name:$uuid:$type:\" ;; *) : ;; esac; done"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().length ? text.trim().split('\n') : []
const out = []
for (const line of lines) {
const parts = line.split(':')
if (parts.length >= 3 && (parts[2] === "vpn" || parts[2] === "wireguard")) {
const svc = parts.length >= 4 ? parts[3] : ""
out.push({ name: parts[0], uuid: parts[1], type: parts[2], serviceType: svc })
}
}
root.profiles = out
}
}
}
// Query active VPN connection
function refreshActive() {
getActive.running = true
}
Process {
id: getActive
command: ["nmcli", "-t", "-f", "NAME,UUID,TYPE,DEVICE,STATE", "connection", "show", "--active"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().length ? text.trim().split('\n') : []
let act = []
for (const line of lines) {
const parts = line.split(':')
if (parts.length >= 5 && (parts[2] === "vpn" || parts[2] === "wireguard")) {
act.push({ name: parts[0], uuid: parts[1], device: parts[3], state: parts[4] })
}
}
root.activeConnections = act
root.activeUuids = act.map(a => a.uuid).filter(u => !!u)
root.activeNames = act.map(a => a.name).filter(n => !!n)
}
}
}
function isActiveUuid(uuid) {
return root.activeUuids && root.activeUuids.indexOf(uuid) !== -1
}
function _looksLikeUuid(s) {
// Very loose check for UUID pattern
return s && s.indexOf('-') !== -1 && s.length >= 8
}
function connect(uuidOrName) {
if (root.isBusy) return
root.isBusy = true
root.errorMessage = ""
if (root.singleActive) {
// Bring down all active VPNs, then bring up the requested one
const isUuid = _looksLikeUuid(uuidOrName)
const escaped = ('' + uuidOrName).replace(/'/g, "'\\''")
const upCmd = isUuid ? `nmcli connection up uuid '${escaped}'` : `nmcli connection up id '${escaped}'`
const script = `set -e\n` +
`nmcli -t -f UUID,TYPE connection show --active | awk -F: '$2 ~ /^(vpn|wireguard)$/ {print $1}' | while read u; do [ -n \"$u\" ] && nmcli connection down uuid \"$u\" || true; done\n` +
upCmd + `\n`
vpnSwitch.command = ["bash", "-lc", script]
vpnSwitch.running = true
} else {
if (_looksLikeUuid(uuidOrName)) {
vpnUp.command = ["nmcli", "connection", "up", "uuid", uuidOrName]
} else {
vpnUp.command = ["nmcli", "connection", "up", "id", uuidOrName]
}
vpnUp.running = true
}
}
function disconnect(uuidOrName) {
if (root.isBusy) return
root.isBusy = true
root.errorMessage = ""
if (_looksLikeUuid(uuidOrName)) {
vpnDown.command = ["nmcli", "connection", "down", "uuid", uuidOrName]
} else {
vpnDown.command = ["nmcli", "connection", "down", "id", uuidOrName]
}
vpnDown.running = true
}
function toggle(uuid) {
if (uuid) {
if (isActiveUuid(uuid)) disconnect(uuid)
else connect(uuid)
return
}
if (root.profiles.length > 0) {
connect(root.profiles[0].uuid)
}
}
Process {
id: vpnUp
running: false
stdout: StdioCollector {
onStreamFinished: {
root.isBusy = false
if (!text.toLowerCase().includes("successfully")) {
root.errorMessage = text.trim()
}
refreshAll()
}
}
onExited: exitCode => {
root.isBusy = false
if (exitCode !== 0 && root.errorMessage === "") {
root.errorMessage = "Failed to connect VPN"
}
}
}
Process {
id: vpnDown
running: false
stdout: StdioCollector {
onStreamFinished: {
root.isBusy = false
if (!text.toLowerCase().includes("deactivated") && !text.toLowerCase().includes("successfully")) {
root.errorMessage = text.trim()
}
refreshAll()
}
}
onExited: exitCode => {
root.isBusy = false
if (exitCode !== 0 && root.errorMessage === "") {
root.errorMessage = "Failed to disconnect VPN"
}
}
}
function disconnectAllActive() {
if (root.isBusy) return
root.isBusy = true
const script = `nmcli -t -f UUID,TYPE connection show --active | awk -F: '$2 ~ /^(vpn|wireguard)$/ {print $1}' | while read u; do [ -n \"$u\" ] && nmcli connection down uuid \"$u\" || true; done`
vpnSwitch.command = ["bash", "-lc", script]
vpnSwitch.running = true
}
// Sequenced down/up using a single shell for exclusive switch
Process {
id: vpnSwitch
running: false
stdout: StdioCollector {
onStreamFinished: {
root.isBusy = false
refreshAll()
}
}
onExited: exitCode => {
root.isBusy = false
if (exitCode !== 0 && root.errorMessage === "") {
root.errorMessage = "Failed to switch VPN"
}
}
}
}

View File

@@ -1 +1 @@
v0.2.0
v0.2.3

View File

@@ -46,9 +46,9 @@ authentication, and dynamic theming.
# QML-based application
%install
# Install greeter files to XDG config location
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter
cp -r * %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/
# Install greeter files to shared data location
install -dm755 %{buildroot}%{_datadir}/quickshell/dms-greeter
cp -r * %{buildroot}%{_datadir}/quickshell/dms-greeter/
# Install launcher script
install -Dm755 Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
@@ -123,7 +123,7 @@ echo "Creating symlinks to sync theme..."
declare -A links=(
["$HOME/.config/DankMaterialShell/settings.json"]="$CACHE_DIR/settings.json"
["$HOME/.local/state/DankMaterialShell/session.json"]="$CACHE_DIR/session.json"
["$HOME/.cache/quickshell/dankshell/dms-colors.json"]="$CACHE_DIR/colors.json"
["$HOME/.cache/DankMaterialShell/dms-colors.json"]="$CACHE_DIR/colors.json"
)
for source in "${!links[@]}"; do
@@ -160,19 +160,28 @@ install -dm755 %{buildroot}%{_sharedstatedir}/greeter
# Instead, we verify/fix it in %post if needed
# Remove build and development files
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/.git*
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/.gitignore
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/.github
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/*.spec
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/dms.spec
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/dms-greeter.spec
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.github
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/*.spec
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/dms.spec
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/dms-greeter.spec
%posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms-greeter" ]; then
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms-greeter" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
%files
%license LICENSE
%doc %{_docdir}/dms-greeter/README.md
%{_bindir}/dms-greeter
%{_bindir}/dms-greeter-sync
%{_sysconfdir}/xdg/quickshell/dms-greeter/
%{_datadir}/quickshell/dms-greeter/
%dir %attr(0750,greeter,greeter) %{_localstatedir}/cache/dms-greeter
%dir %attr(0755,greeter,greeter) %{_sharedstatedir}/greeter
@@ -200,9 +209,9 @@ if [ -x /usr/sbin/semanage ] && [ -x /usr/sbin/restorecon ]; then
semanage fcontext -a -t cache_home_t '%{_localstatedir}/cache/dms-greeter(/.*)?' >/dev/null 2>&1 || true
restorecon -R %{_localstatedir}/cache/dms-greeter >/dev/null 2>&1 || true
# Config directory
semanage fcontext -a -t etc_t '%{_sysconfdir}/xdg/quickshell/dms-greeter(/.*)?' >/dev/null 2>&1 || true
restorecon -R %{_sysconfdir}/xdg/quickshell/dms-greeter >/dev/null 2>&1 || true
# Shared data directory
semanage fcontext -a -t usr_t '%{_datadir}/quickshell/dms-greeter(/.*)?' >/dev/null 2>&1 || true
restorecon -R %{_datadir}/quickshell/dms-greeter >/dev/null 2>&1 || true
# PAM configuration
restorecon %{_sysconfdir}/pam.d/greetd >/dev/null 2>&1 || true
@@ -300,17 +309,19 @@ fi
# Only show banner on initial install
if [ "$1" -eq 1 ]; then
cat << EOF
cat << 'EOF'
===============================================================================
DMS Greeter Installation Complete!
===============================================================================
=========================================================================
DMS Greeter Installation Complete!
=========================================================================
Status:
- Greeter user: Created
- Greeter directories: /var/cache/dms-greeter, /var/lib/greeter
- SELinux contexts: Applied
- Greetd config: $CONFIG_STATUS
Greeter user: Created
Greeter directories: /var/cache/dms-greeter, /var/lib/greeter
SELinux contexts: Applied
EOF
echo " Greetd config: $CONFIG_STATUS"
cat << 'EOF'
Next steps:
@@ -327,7 +338,7 @@ Next steps:
Ready to test? Reboot or run: sudo systemctl start greetd
Documentation: /usr/share/doc/dms-greeter/README.md
===============================================================================
=========================================================================
EOF
fi
@@ -338,7 +349,7 @@ if [ "$1" -eq 0 ] && [ -x /usr/sbin/semanage ]; then
semanage fcontext -d '%{_bindir}/dms-greeter' 2>/dev/null || true
semanage fcontext -d '%{_sharedstatedir}/greeter(/.*)?' 2>/dev/null || true
semanage fcontext -d '%{_localstatedir}/cache/dms-greeter(/.*)?' 2>/dev/null || true
semanage fcontext -d '%{_sysconfdir}/xdg/quickshell/dms-greeter(/.*)?' 2>/dev/null || true
semanage fcontext -d '%{_datadir}/quickshell/dms-greeter(/.*)?' 2>/dev/null || true
fi
%changelog

View File

@@ -27,6 +27,7 @@ BuildRequires: wget
# Core requirements
Requires: (quickshell-git or quickshell)
Requires: accountsservice
Requires: dms-cli
Requires: dgop
Requires: fira-code-fonts
@@ -128,20 +129,61 @@ install -Dm755 %{_builddir}/danklinux-master/bin/${DMS_BINARY} %{buildroot}%{_bi
# Install dgop binary
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
# Install shell files to XDG config location
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms
cp -r * %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/
# Install shell files to shared data location
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r * %{buildroot}%{_datadir}/quickshell/dms/
# Remove build files
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.git*
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.github
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/*.spec
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -f %{buildroot}%{_datadir}/quickshell/dms/*.spec
%posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
# Restart DMS for active users after upgrade
if [ "$1" -ge 2 ]; then
# Find all quickshell DMS processes (PID and username)
while read pid cmd; do
username=$(ps -o user= -p "$pid" 2>/dev/null)
[ "$username" = "root" ] && continue
[ -z "$username" ] && continue
# Get user's UID and validate session
user_uid=$(id -u "$username" 2>/dev/null)
[ -z "$user_uid" ] && continue
[ ! -d "/run/user/$user_uid" ] && continue
wayland_display=$(tr '\0' '\n' < /proc/$pid/environ 2>/dev/null | grep '^WAYLAND_DISPLAY=' | cut -d= -f2)
[ -z "$wayland_display" ] && continue
echo "Restarting DMS for user: $username"
# Run as user with full Wayland session environment
runuser -u "$username" -- /bin/sh -c "
export XDG_RUNTIME_DIR=/run/user/$user_uid
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$user_uid/bus
export WAYLAND_DISPLAY=$wayland_display
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:\$PATH
dms restart >/dev/null 2>&1
" 2>/dev/null || true
break
done < <(pgrep -a -f 'quickshell.*dms' 2>/dev/null)
fi
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_sysconfdir}/xdg/quickshell/dms/
%{_datadir}/quickshell/dms/
%files -n dms-cli
%{_bindir}/dms

View File

@@ -407,6 +407,21 @@ dms ipc call bar hide
dms ipc call bar status
```
## Target: `systemupdater`
System updater external check request.
### Functions
**`updatestatus`**
- Trigger a system update check
- Returns: Success confirmation
### Examples
```bash
dms ipc call systemupdater updatestatus
```
## Modal Controls
These targets control various modal windows and overlays.

View File

@@ -1,33 +0,0 @@
[config]
[templates.dmsgtk3]
input_path = './templates/gtk-colors.css'
output_path = '~/.config/gtk-3.0/dank-colors.css'
[templates.dmsgtk4]
input_path = './templates/gtk-colors.css'
output_path = '~/.config/gtk-4.0/dank-colors.css'
[templates.dmsqt5ct]
input_path = './templates/qtct-colors.conf'
output_path = '~/.config/qt5ct/colors/matugen.conf'
[templates.dmsqt6ct]
input_path = './templates/qtct-colors.conf'
output_path = '~/.config/qt6ct/colors/matugen.conf'
[templates.dmskcolorscheme]
input_path = './templates/matugen-kcolorscheme.colors'
output_path = '~/.local/share/color-schemes/DankMatugen.colors'
[templates.dmsdgop]
input_path = './templates/dgop.json'
output_path = '~/.config/dgop/colors.json'
[templates.dmsniri]
input_path = './templates/niri-colors.kdl'
output_path = '~/.config/niri/dankshell-colors.kdl'
[templates.dmsghostty]
input_path = './templates/ghostty-colors.conf'
output_path = '~/.config/ghostty/config-dankcolors'

View File

@@ -5,5 +5,13 @@ input_path = './matugen/templates/gtk-colors.css'
output_path = '~/.config/gtk-4.0/dank-colors.css'
[templates.dmskcolorscheme]
input_path = './matugen/templates/matugen-kcolorscheme.colors'
output_path = '~/.local/share/color-schemes/DankMatugen.colors'
input_path = './matugen/templates/kcolorscheme.colors'
output_path = '~/.local/share/color-schemes/DankMatugen.colors'
[templates.dmslightkcolorscheme]
input_path = './matugen/templates/light-kcolorscheme.colors'
output_path = '~/.local/share/color-schemes/DankMatugenLight.colors'
[templates.dmsdarkkcolorscheme]
input_path = './matugen/templates/dark-kcolorscheme.colors'
output_path = '~/.local/share/color-schemes/DankMatugenDark.colors'

View File

@@ -0,0 +1,146 @@
[KDE]
contrast=4
[General]
ColorScheme=DankMatugenDark
Name=Dank Shell (matugen-dark)
[ColorEffects:Disabled]
Color={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
ContrastEffect=1
IntensityAmount=0.1
IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=true
Color={{colors.outline.dark.red}},{{colors.outline.dark.green}},{{colors.outline.dark.blue}}
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
ContrastEffect=2
Enable=false
IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate={{colors.surface_container_high.dark.red}},{{colors.surface_container_high.dark.green}},{{colors.surface_container_high.dark.blue}}
BackgroundNormal={{colors.surface_container.dark.red}},{{colors.surface_container.dark.green}},{{colors.surface_container.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Complementary]
BackgroundAlternate={{colors.surface_container_high.dark.red}},{{colors.surface_container_high.dark.green}},{{colors.surface_container_high.dark.blue}}
BackgroundNormal={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Header]
BackgroundAlternate={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
BackgroundNormal={{colors.surface_container.dark.red}},{{colors.surface_container.dark.green}},{{colors.surface_container.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Header][Inactive]
BackgroundAlternate={{colors.surface_container.dark.red}},{{colors.surface_container.dark.green}},{{colors.surface_container.dark.blue}}
BackgroundNormal={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Selection]
BackgroundAlternate={{colors.primary_container.dark.red}},{{colors.primary_container.dark.green}},{{colors.primary_container.dark.blue}}
BackgroundNormal={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.on_primary.dark.red}},{{colors.on_primary.dark.green}},{{colors.on_primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_primary.dark.red}},{{colors.on_primary.dark.green}},{{colors.on_primary.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Tooltip]
BackgroundAlternate={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
BackgroundNormal={{colors.surface_container.dark.red}},{{colors.surface_container.dark.green}},{{colors.surface_container.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:View]
BackgroundAlternate={{colors.surface_container_low.dark.red}},{{colors.surface_container_low.dark.green}},{{colors.surface_container_low.dark.blue}}
BackgroundNormal={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Window]
BackgroundAlternate={{colors.surface_container.dark.red}},{{colors.surface_container.dark.green}},{{colors.surface_container.dark.blue}}
BackgroundNormal={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[WM]
activeBackground={{colors.surface_container.dark.red}},{{colors.surface_container.dark.green}},{{colors.surface_container.dark.blue}}
activeBlend={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
activeForeground={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
inactiveBackground={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
inactiveBlend={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
inactiveForeground={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}

View File

@@ -0,0 +1,146 @@
[KDE]
contrast=4
[General]
ColorScheme=DankMatugenLight
Name=Dank Shell (matugen-light)
[ColorEffects:Disabled]
Color={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
ContrastEffect=1
IntensityAmount=0.1
IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=true
Color={{colors.outline.light.red}},{{colors.outline.light.green}},{{colors.outline.light.blue}}
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
ContrastEffect=2
Enable=false
IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate={{colors.surface_container_high.light.red}},{{colors.surface_container_high.light.green}},{{colors.surface_container_high.light.blue}}
BackgroundNormal={{colors.surface_container.light.red}},{{colors.surface_container.light.green}},{{colors.surface_container.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Complementary]
BackgroundAlternate={{colors.surface_container_high.light.red}},{{colors.surface_container_high.light.green}},{{colors.surface_container_high.light.blue}}
BackgroundNormal={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Header]
BackgroundAlternate={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
BackgroundNormal={{colors.surface_container.light.red}},{{colors.surface_container.light.green}},{{colors.surface_container.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Header][Inactive]
BackgroundAlternate={{colors.surface_container.light.red}},{{colors.surface_container.light.green}},{{colors.surface_container.light.blue}}
BackgroundNormal={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Selection]
BackgroundAlternate={{colors.primary_container.light.red}},{{colors.primary_container.light.green}},{{colors.primary_container.light.blue}}
BackgroundNormal={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.on_primary.light.red}},{{colors.on_primary.light.green}},{{colors.on_primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_primary.light.red}},{{colors.on_primary.light.green}},{{colors.on_primary.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Tooltip]
BackgroundAlternate={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
BackgroundNormal={{colors.surface_container.light.red}},{{colors.surface_container.light.green}},{{colors.surface_container.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:View]
BackgroundAlternate={{colors.surface_container_low.light.red}},{{colors.surface_container_low.light.green}},{{colors.surface_container_low.light.blue}}
BackgroundNormal={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Window]
BackgroundAlternate={{colors.surface_container.light.red}},{{colors.surface_container.light.green}},{{colors.surface_container.light.blue}}
BackgroundNormal={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[WM]
activeBackground={{colors.surface_container.light.red}},{{colors.surface_container.light.green}},{{colors.surface_container.light.blue}}
activeBlend={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
activeForeground={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
inactiveBackground={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
inactiveBlend={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
inactiveForeground={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}

View File

@@ -8,6 +8,9 @@
cfg = config.programs.dankMaterialShell;
jsonFormat = pkgs.formats.json { };
in {
imports = [
(lib.mkRemovedOptionModule ["programs" "dankMaterialShell" "enableNightMode"] "Night mode is now always available.")
];
options.programs.dankMaterialShell = with lib.types; {
enable = lib.mkEnableOption "DankMaterialShell";

View File

@@ -90,9 +90,15 @@ in {
})
(lib.mkIf cfg.niri.enableSpawn {
spawn-at-startup = [
{command = ["dms" "run"];}
];
spawn-at-startup =
[
{command = ["dms" "run"];}
]
++ lib.optionals cfg.enableClipboard [
{
command = ["wl-paste" "--watch" "cliphist" "store"];
}
];
})
];
};

File diff suppressed because it is too large Load Diff

View File

@@ -137,6 +137,15 @@
"Audio Output Devices (": {
"Audio Output Devices (": "オーディオ出力デバイス("
},
"Authorize": {
"Authorize": "許可"
},
"Authorize pairing with ": {
"Authorize pairing with ": "ペアリングを許可 "
},
"Authorize service for ": {
"Authorize service for ": "サービスを許可 "
},
"Auto Location": {
"Auto Location": "自動位置検出"
},
@@ -218,6 +227,15 @@
"Bluetooth Settings": {
"Bluetooth Settings": "Bluetooth設定"
},
"Blur Layer": {
"Blur Layer": "ぼかしレイヤー"
},
"Blur on Overview": {
"Blur on Overview": "概要でぼかす"
},
"Blur wallpaper when niri overview is open": {
"Blur wallpaper when niri overview is open": "Niri概要が開いているときに壁紙をぼかす"
},
"Border": {
"Border": "ボーダー"
},
@@ -362,9 +380,18 @@
"Configure which displays show shell components": {
"Configure which displays show shell components": "シェルコンポーネントを表示するディスプレイを配置"
},
"Confirm": {
"Confirm": "確認"
},
"Confirm passkey for ": {
"Confirm passkey for ": "パスキーを確認 "
},
"Connect": {
"Connect": "接続"
},
"Connect to VPN": {
"Connect to VPN": ""
},
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Wi-Fiに接続"
},
@@ -491,6 +518,9 @@
"Device": {
"Device": "デバイス"
},
"Device paired": {
"Device paired": "デバイスがペアリングされました"
},
"Disconnect": {
"Disconnect": "切断"
},
@@ -545,6 +575,9 @@
"Domain (optional)": {
"Domain (optional)": "ドメイン (オプション)"
},
"Donate on Ko-fi": {
"Donate on Ko-fi": "Ko-fiで寄付"
},
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "ウィジェットをドラッグしてセクション内で順序を変更できます。目のアイコンでウィジェットを表示/非表示にスペースは維持、Xで完全に削除できます。"
},
@@ -575,6 +608,9 @@
"Enable WiFi": {
"Enable WiFi": "WiFiを有効にする"
},
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": {
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": "コンポジターをターゲットに設定できるぼかしレイヤー(名前空間dms:blurwallpaper)を有効にします。Niri を手動で設定する必要があります。"
},
"Enable fingerprint authentication": {
"Enable fingerprint authentication": "指紋認証を有効に"
},
@@ -584,6 +620,15 @@
"End": {
"End": "終わり"
},
"Enter 6-digit passkey": {
"Enter 6-digit passkey": "6桁のパスキーを入力してください"
},
"Enter PIN": {
"Enter PIN": "PINを入力してください"
},
"Enter PIN for ": {
"Enter PIN for ": "PINを入力してください "
},
"Enter credentials for ": {
"Enter credentials for ": "資格情報を入力"
},
@@ -596,6 +641,9 @@
"Enter filename...": {
"Enter filename...": "ファイル名を入力してください..."
},
"Enter passkey for ": {
"Enter passkey for ": "パスキーを入力してください "
},
"Enter password for ": {
"Enter password for ": "パスワードを入力"
},
@@ -611,15 +659,27 @@
"Failed to activate configuration": {
"Failed to activate configuration": "設定が適用できませんでした"
},
"Failed to connect VPN": {
"Failed to connect VPN": ""
},
"Failed to connect to ": {
"Failed to connect to ": "接続ができませんでした "
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": ""
},
"Failed to disconnect VPNs": {
"Failed to disconnect VPNs": ""
},
"Failed to disconnect WiFi": {
"Failed to disconnect WiFi": "WiFiの切断ができませんでした"
},
"Failed to enable WiFi": {
"Failed to enable WiFi": "WiFiを有効化にできませんでした"
},
"Failed to remove device": {
"Failed to remove device": "デバイスの削除に失敗しました"
},
"Failed to set profile image": {
"Failed to set profile image": "プロフィール画像の設定に失敗しました"
},
@@ -782,6 +842,9 @@
"Incorrect password": {
"Incorrect password": "パスワードが間違っています"
},
"Indicator Style": {
"Indicator Style": "インジケータースタイル"
},
"Individual Batteries": {
"Individual Batteries": "バッテリーごと"
},
@@ -1100,6 +1163,18 @@
"Padding": {
"Padding": "パディング"
},
"Pair": {
"Pair": "ペアリング"
},
"Pair Bluetooth Device": {
"Pair Bluetooth Device": "Bluetoothデバイスのペアリング"
},
"Pairing failed": {
"Pairing failed": "ペアリングが失敗しました"
},
"Passkey:": {
"Passkey:": "パスキー:"
},
"Password": {
"Password": "パスワード"
},
@@ -1157,6 +1232,9 @@
"Plugins": {
"Plugins": "プラグイン"
},
"Plugins:": {
"Plugins:": "プラグイン:"
},
"Popup Position": {
"Popup Position": "ポップアップの位置"
},
@@ -1233,7 +1311,7 @@
"Refresh": "リフレッシュ"
},
"Reload Plugin": {
"Reload Plugin": ""
"Reload Plugin": "プラグインをリロード"
},
"Remove": {
"Remove": "削除"
@@ -1244,6 +1322,9 @@
"Reset": {
"Reset": "リセット"
},
"Resources": {
"Resources": "リソース"
},
"Right": {
"Right": "右"
},
@@ -1364,6 +1445,9 @@
"Show Workspace Apps": {
"Show Workspace Apps": "ワークスペースアプリを表示"
},
"Show on Last Display": {
"Show on Last Display": "最後のディスプレイに表示"
},
"Show on Overview": {
"Show on Overview": "概要に表示"
},
@@ -1436,6 +1520,9 @@
"Storage & Disks": {
"Storage & Disks": "ストレージとディスク"
},
"Support Development": {
"Support Development": "開発をサポート"
},
"Surface": {
"Surface": "表面"
},
@@ -1575,7 +1662,7 @@
"Turn off monitors after": "後にモニターの電源を切る"
},
"Uninstall Plugin": {
"Uninstall Plugin": ""
"Uninstall Plugin": "プラグインをアンインストール"
},
"Unpin from Dock": {
"Unpin from Dock": "ドックから固定を解除"
@@ -1596,7 +1683,7 @@
"Update All": "すべて更新"
},
"Update Plugin": {
"Update Plugin": ""
"Update Plugin": "プラグインを更新"
},
"Use 24-hour time format instead of 12-hour AM/PM": {
"Use 24-hour time format instead of 12-hour AM/PM": "12時間制のAM/PMではなく、24時間表記を使用"
@@ -1694,6 +1781,9 @@
"Weather Widget": {
"Weather Widget": "天気ウィジェット"
},
"Website:": {
"Website:": "ウェブサイト:"
},
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "有効にすると、アプリはアルファベット順に並べ替えられます。無効にすると、アプリは使用頻度で並べ替えられます。"
},

View File

@@ -137,6 +137,15 @@
"Audio Output Devices (": {
"Audio Output Devices (": "Dispositivos de Saída de Áudio ("
},
"Authorize": {
"Authorize": ""
},
"Authorize pairing with ": {
"Authorize pairing with ": ""
},
"Authorize service for ": {
"Authorize service for ": ""
},
"Auto Location": {
"Auto Location": "Localização Automática"
},
@@ -218,6 +227,15 @@
"Bluetooth Settings": {
"Bluetooth Settings": "Configurações do Bluetooth"
},
"Blur Layer": {
"Blur Layer": ""
},
"Blur on Overview": {
"Blur on Overview": ""
},
"Blur wallpaper when niri overview is open": {
"Blur wallpaper when niri overview is open": ""
},
"Border": {
"Border": "Borda"
},
@@ -362,9 +380,18 @@
"Configure which displays show shell components": {
"Configure which displays show shell components": "Configurar em quais telas serão mostrados os componentes do shell"
},
"Confirm": {
"Confirm": ""
},
"Confirm passkey for ": {
"Confirm passkey for ": ""
},
"Connect": {
"Connect": "Conectar"
},
"Connect to VPN": {
"Connect to VPN": ""
},
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Conectar ao Wi-Fi"
},
@@ -491,6 +518,9 @@
"Device": {
"Device": "Dispositivo"
},
"Device paired": {
"Device paired": ""
},
"Disconnect": {
"Disconnect": "Desconectar"
},
@@ -545,6 +575,9 @@
"Domain (optional)": {
"Domain (optional)": ""
},
"Donate on Ko-fi": {
"Donate on Ko-fi": ""
},
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Arraste Widgets para reordená-los nas seções. Use o ícone de olho para esconder ou mostrá-los (manter o espaçamento), ou clique no X para removê-los por completo."
},
@@ -575,6 +608,9 @@
"Enable WiFi": {
"Enable WiFi": "Ativar Wi-fi "
},
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": {
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": ""
},
"Enable fingerprint authentication": {
"Enable fingerprint authentication": "Habilitar autenticação por impressão digital"
},
@@ -584,6 +620,15 @@
"End": {
"End": "Fim"
},
"Enter 6-digit passkey": {
"Enter 6-digit passkey": ""
},
"Enter PIN": {
"Enter PIN": ""
},
"Enter PIN for ": {
"Enter PIN for ": ""
},
"Enter credentials for ": {
"Enter credentials for ": "Insira as credenciais para "
},
@@ -596,6 +641,9 @@
"Enter filename...": {
"Enter filename...": "Insira nome do arquivo..."
},
"Enter passkey for ": {
"Enter passkey for ": ""
},
"Enter password for ": {
"Enter password for ": "Insira senha para "
},
@@ -611,15 +659,27 @@
"Failed to activate configuration": {
"Failed to activate configuration": ""
},
"Failed to connect VPN": {
"Failed to connect VPN": ""
},
"Failed to connect to ": {
"Failed to connect to ": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": ""
},
"Failed to disconnect VPNs": {
"Failed to disconnect VPNs": ""
},
"Failed to disconnect WiFi": {
"Failed to disconnect WiFi": ""
},
"Failed to enable WiFi": {
"Failed to enable WiFi": ""
},
"Failed to remove device": {
"Failed to remove device": ""
},
"Failed to set profile image": {
"Failed to set profile image": ""
},
@@ -782,6 +842,9 @@
"Incorrect password": {
"Incorrect password": ""
},
"Indicator Style": {
"Indicator Style": ""
},
"Individual Batteries": {
"Individual Batteries": "Baterias Individuais"
},
@@ -1100,6 +1163,18 @@
"Padding": {
"Padding": "Preenchimento"
},
"Pair": {
"Pair": ""
},
"Pair Bluetooth Device": {
"Pair Bluetooth Device": ""
},
"Pairing failed": {
"Pairing failed": ""
},
"Passkey:": {
"Passkey:": ""
},
"Password": {
"Password": "Senha"
},
@@ -1157,6 +1232,9 @@
"Plugins": {
"Plugins": "Plugins"
},
"Plugins:": {
"Plugins:": ""
},
"Popup Position": {
"Popup Position": "Posição do Popup"
},
@@ -1244,6 +1322,9 @@
"Reset": {
"Reset": "Resetar"
},
"Resources": {
"Resources": ""
},
"Right": {
"Right": ""
},
@@ -1364,6 +1445,9 @@
"Show Workspace Apps": {
"Show Workspace Apps": "Mostrar Aplicativos da Área de Trabalho Virtual"
},
"Show on Last Display": {
"Show on Last Display": ""
},
"Show on Overview": {
"Show on Overview": "Mostrar na Visão Geral"
},
@@ -1436,6 +1520,9 @@
"Storage & Disks": {
"Storage & Disks": "Armazenamento & Discos"
},
"Support Development": {
"Support Development": ""
},
"Surface": {
"Surface": "Superfície"
},
@@ -1694,6 +1781,9 @@
"Weather Widget": {
"Weather Widget": "Widget de Clima"
},
"Website:": {
"Website:": ""
},
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "Quando ativado, apps são ordenados alfabeticamente. Quando desativado, apps são ordenados por frequência de uso"
},

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