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

Compare commits

..

66 Commits

Author SHA1 Message Date
bbedward
dca07a70f8 desktop widgets: put grid on bottom layer 2025-12-20 09:58:01 -05:00
bbedward
02936c97fd desktop widget: handle key events in widget 2025-12-20 09:34:52 -05:00
Ethan Todd
8f7e732827 notifications: add modal IPC command for dismissing all popups. rename clearAllPopups() to dismissAllPopups(), since clear is otherwise used to mean eliminated entirely rather than just sent to the notification center. (#1100) 2025-12-20 08:13:26 -05:00
johngalt
5ffe563b7d adding gruvbox material custom theme varieties (#1098) 2025-12-19 22:38:22 -05:00
Joaquim S.
6ef08c3d54 matugen/template: Added neovim to matugen pipeline (#1097) 2025-12-19 14:16:45 -05:00
bbedward
908b4b58cd desktop widgets: add grid/grid size hints 2025-12-19 14:05:04 -05:00
purian23
f2611e0de0 fedora: Remove cliphist on dms-git 2025-12-18 23:55:09 -05:00
purian23
ea75a9d351 distro: Convert DMS Greeter to Stable on Fedora Copr 2025-12-18 23:00:13 -05:00
bbedward
3a744d7d68 core: new line on version 2025-12-18 22:12:50 -05:00
purian23
195d312ae2 distro: Decople Fedora DMS Stable spec 2025-12-18 20:03:23 -05:00
Omar Abragh
76006a7377 matugen: Set cursor color for theme (#1088) 2025-12-18 17:23:42 -05:00
bbedward
11536da512 fix missing import 2025-12-18 17:22:01 -05:00
bbedward
2a91bc41f7 i18n: general term cleanup, add missing terms, interpolate some 2025-12-18 16:19:27 -05:00
bbedward
baf23157fc i18n: sync translations 2025-12-18 14:51:57 -05:00
bbedward
83b81be825 keybinds: add log if ShortcutInhibitor is missing 2025-12-18 14:15:54 -05:00
bbedward
4aefa0f1f7 core: skip replacing niri/dms configs
fixes #1072
2025-12-18 11:58:45 -05:00
bbedward
e53a7cee97 matugen: wrap pywalfox in sh 2025-12-18 11:51:37 -05:00
bbedward
8437e1aa7b desktop widgets: use preview window instead of margin shift for non-niri 2025-12-18 10:05:38 -05:00
bbedward
632f40cc0a desktop plugins: use mapToGlobal on moving widgets 2025-12-18 08:57:58 -05:00
Ethan Todd
7d81445341 notifications: add modal function for clearing all (#1082) 2025-12-18 08:28:58 -05:00
bbedward
78a5f401d7 core: remove ascii art from version 2025-12-18 00:14:02 -05:00
bbedward
8745f98c95 matugen: fix vscode editor color reload 2025-12-17 23:40:09 -05:00
bbedward
f0f5bcc630 matugen: add color reload capability to vscode theme 2025-12-17 23:30:06 -05:00
purian23
8a3c513605 distro: Relocate Ubuntu dgop/dsearch to danklinux 2025-12-17 23:08:37 -05:00
bbedward
145d2636dd clock: make desktop clock not use precision seconds always 2025-12-17 21:00:04 -05:00
bbedward
f2b9dc8988 displays: add adaptiveSyncSupported to wlroutput API 2025-12-17 20:36:54 -05:00
bbedward
2e4d56728b niri: track open modals in modal manager for focus transfers 2025-12-17 20:21:34 -05:00
bbedward
18231ed324 niri: don't rely on text field length for launching 2025-12-17 16:40:54 -05:00
bbedward
d0b61d8ed1 niri: release focus for popouts on overview 2025-12-17 16:17:44 -05:00
bbedward
d385a44949 notifications: attempt to minimize rapid window creation/destruction 2025-12-17 16:10:25 -05:00
bbedward
d97392d46e clipboard: remove ownership option 2025-12-17 15:34:40 -05:00
bbedward
6abb2c73fd desktop: fix widget display toggle 2025-12-17 14:24:13 -05:00
bbedward
7e141c6b36 dankbar/vpn: right click to quick connect 2025-12-17 14:16:11 -05:00
bbedward
53553c1f62 clock: add analog seconds option for desktop widget 2025-12-17 14:04:14 -05:00
bbedward
523ccc6bf8 i18n: WIP initial RTL support
- notifications
- color picker
- process list
- settings
- control center, dash
- launcher

part of #1059
2025-12-17 13:50:06 -05:00
bbedward
811e89fcfa matugen: change pywalfox post hook 2025-12-17 12:43:05 -05:00
bbedward
5d5be4d9d7 lock: different pam fallback 2025-12-17 12:40:28 -05:00
bbedward
88457ab139 lock: add pam login fallback locally 2025-12-17 12:31:45 -05:00
bbedward
0034926df7 plugins/desktop-widgets: create a new "desktop" widget plugin type
- Draggable per-monitor background layer widgets
- Add basic dms version checks on plugins
- Clock: built-in clock desktop plugin
- dgop: built-in system monitor desktop plugin
2025-12-17 12:08:03 -05:00
musjj
d082d41ab9 nix: refactor module structure and flake output (#1014)
- The program module is now called dank-material-shell
- The homeModules structure is flattened
2025-12-17 08:12:30 -03:00
purian23
b7911475b6 distros: Prefer stable quickshell 2025-12-16 23:52:37 -05:00
bbedward
672754b0b5 dankdash: fix weather tooltips
fixes #1065
2025-12-16 15:27:44 -05:00
bbedward
0d1553123b binds: accidentally deleted import 2025-12-16 15:16:44 -05:00
bbedward
ba6c51c102 core: exit non-zero when SIGUSR1 is received (for systemd r estart) 2025-12-16 14:47:46 -05:00
bbedward
d64206a9ff core: detect quickshell crash on SIGTERM 2025-12-16 14:44:22 -05:00
bbedward
d9a1089039 displays: add hyprland HDR options 2025-12-16 14:12:51 -05:00
bbedward
55fe463405 displays: break monolith config down and allow floats/fix integer
writing (niri)
2025-12-16 13:36:00 -05:00
bbedward
e84210e962 displays: fix niri hot corner config 2025-12-16 12:54:26 -05:00
bbedward
ff506548d3 displays: add niri-specific layout options to configurator 2025-12-16 12:23:34 -05:00
arfan
f6b09751e9 fix: update getWorkspaceIndex function to include index parameter also fix workspace padding number (#1062) 2025-12-16 11:32:21 -05:00
bbedward
3d863979c4 core: preserve quickshell exit code 2025-12-16 09:01:13 -05:00
purian23
2947ff4131 distro: Revise server side file handling 2025-12-16 01:08:12 -05:00
purian23
b8fca10896 Remove auto run on tags 2025-12-16 00:17:13 -05:00
purian23
33e45794d2 No run on push 2025-12-15 23:29:36 -05:00
purian23
42cc88ca65 Workflow update 2025-12-15 23:24:16 -05:00
purian23
0b7f2416ca distro: Bring up Stable 2025-12-15 23:10:24 -05:00
purian23
5d5c745ee5 Push the logs 2025-12-15 22:18:35 -05:00
purian23
e0429e4c60 distro: Re-add suffix 2025-12-15 21:31:13 -05:00
bbedward
0bece5287e dock: improve pinned app re-ordering feedback, fix vertical dock
ordering
fixes #1046
fixes #938
2025-12-15 20:46:36 -05:00
purian23
60b5e47836 update gitignore env 2025-12-15 19:06:43 -05:00
purian23
aa75b44790 distro: OBS version matching 2025-12-15 18:03:58 -05:00
bbedward
769f58caa9 displays: fix reverted state for position 2025-12-15 17:43:52 -05:00
bbedward
e7facf740d update CHANGELOG 2025-12-15 17:18:59 -05:00
Austin Farmer
04921eef62 Move Ghostty Application Theming (#1047)
* Moved ghostty config

First test. Seems to work but probably broke something.

* Updated test
2025-12-15 17:16:46 -05:00
Oliver Portee
8863c42879 fix light mode/dark mode switch for stock themes (#1057) 2025-12-15 17:16:23 -05:00
bbedward
2745116ac5 displays: add configurator for niri, Hyprland, and MangoWC
- Configure position, VRR, orientation, resolution, refresh rate
- Split Display section into Configuration, Gamma, and Widgets
- MangoWC omits VRR because it doesnt have per-display VRR
- HDR configuration not present for Hyprland
2025-12-15 16:36:14 -05:00
191 changed files with 20077 additions and 7016 deletions

View File

@@ -398,297 +398,3 @@ jobs:
prerelease: ${{ contains(env.TAG, '-') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# trigger-obs-update:
# runs-on: ubuntu-latest
# needs: release
# env:
# TAG: ${{ inputs.tag }}
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# with:
# ref: ${{ inputs.tag }}
# - name: Install OSC
# run: |
# sudo apt-get update
# sudo apt-get install -y osc
# mkdir -p ~/.config/osc
# cat > ~/.config/osc/oscrc << EOF
# [general]
# apiurl = https://api.opensuse.org
# [https://api.opensuse.org]
# user = ${{ secrets.OBS_USERNAME }}
# pass = ${{ secrets.OBS_PASSWORD }}
# EOF
# chmod 600 ~/.config/osc/oscrc
# - name: Update OBS packages
# run: |
# cd distro
# bash scripts/obs-upload.sh dms "Update to ${TAG}"
# trigger-ppa-update:
# runs-on: ubuntu-latest
# needs: release
# env:
# TAG: ${{ inputs.tag }}
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# with:
# ref: ${{ inputs.tag }}
# - name: Install build dependencies
# run: |
# sudo apt-get update
# sudo apt-get install -y \
# debhelper \
# devscripts \
# dput \
# lftp \
# build-essential \
# fakeroot \
# dpkg-dev
# - name: Configure GPG
# env:
# GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
# run: |
# echo "$GPG_KEY" | gpg --import
# GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | awk '{print $2}' | cut -d'/' -f2)
# echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV
# - name: Upload to PPA
# run: |
# cd distro/ubuntu/ppa
# bash create-and-upload.sh ../dms dms questing
# copr-build:
# runs-on: ubuntu-latest
# needs: release
# env:
# TAG: ${{ inputs.tag }}
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# with:
# ref: ${{ inputs.tag }}
# - name: Determine version
# id: version
# run: |
# VERSION="${TAG#v}"
# echo "version=$VERSION" >> $GITHUB_OUTPUT
# echo "Building DMS stable version: $VERSION"
# - name: Setup build environment
# run: |
# sudo apt-get update
# sudo apt-get install -y rpm wget curl jq gzip
# mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
# - name: Download release assets
# run: |
# VERSION="${{ steps.version.outputs.version }}"
# cd ~/rpmbuild/SOURCES
# wget "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
# echo "Failed to download dms-qml.tar.gz for v${VERSION}"
# exit 1
# }
# - name: Generate stable spec file
# run: |
# VERSION="${{ steps.version.outputs.version }}"
# CHANGELOG_DATE="$(date '+%a %b %d %Y')"
# cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
# # Spec for DMS stable releases - Generated by GitHub Actions
# %global debug_package %{nil}
# %global version VERSION_PLACEHOLDER
# %global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
# Name: dms
# Version: %{version}
# Release: 1%{?dist}
# Summary: %{pkg_summary}
# License: MIT
# URL: https://github.com/AvengeMedia/DankMaterialShell
# Source0: dms-qml.tar.gz
# BuildRequires: gzip
# BuildRequires: wget
# BuildRequires: systemd-rpm-macros
# Requires: (quickshell or quickshell-git)
# Requires: accountsservice
# Requires: dms-cli = %{version}-%{release}
# Requires: dgop
# Recommends: cava
# Recommends: cliphist
# Recommends: danksearch
# Recommends: matugen
# Recommends: wl-clipboard
# Recommends: NetworkManager
# Recommends: qt6-qtmultimedia
# Suggests: qt6ct
# %description
# DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
# and optimized for the niri and hyprland compositors. Features notifications,
# app launcher, wallpaper customization, and fully customizable with plugins.
# Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
# process monitoring, notification center, clipboard history, dock, control center,
# lock screen, and comprehensive plugin system.
# %package -n dms-cli
# Summary: DankMaterialShell CLI tool
# License: MIT
# URL: https://github.com/AvengeMedia/DankMaterialShell
# %description -n dms-cli
# Command-line interface for DankMaterialShell configuration and management.
# Provides native DBus bindings, NetworkManager integration, and system utilities.
# %prep
# %setup -q -c -n dms-qml
# # Download architecture-specific binaries during build
# case "%{_arch}" in
# x86_64)
# ARCH_SUFFIX="amd64"
# ;;
# aarch64)
# ARCH_SUFFIX="arm64"
# ;;
# *)
# echo "Unsupported architecture: %{_arch}"
# exit 1
# ;;
# esac
# wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
# echo "Failed to download dms-cli for architecture %{_arch}"
# exit 1
# }
# gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
# chmod +x %{_builddir}/dms-cli
# %build
# %install
# install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
# install -d %{buildroot}%{_datadir}/bash-completion/completions
# install -d %{buildroot}%{_datadir}/zsh/site-functions
# install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
# %{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
# %{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
# %{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
# install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
# install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
# install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
# install -dm755 %{buildroot}%{_datadir}/quickshell/dms
# cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
# rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
# echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
# %posttrans
# if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
# 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
# # Signal running DMS instances to reload
# pkill -USR1 -x dms >/dev/null 2>&1 || :
# %files
# %license LICENSE
# %doc README.md CONTRIBUTING.md
# %{_datadir}/quickshell/dms/
# %{_userunitdir}/dms.service
# %{_datadir}/applications/dms-open.desktop
# %{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
# %files -n dms-cli
# %{_bindir}/dms
# %{_datadir}/bash-completion/completions/dms
# %{_datadir}/zsh/site-functions/_dms
# %{_datadir}/fish/vendor_completions.d/dms.fish
# %changelog
# * CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
# - Stable release VERSION_PLACEHOLDER
# - Built from GitHub release
# SPECEOF
# sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
# sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
# - name: Build SRPM
# id: build
# run: |
# cd ~/rpmbuild/SPECS
# rpmbuild -bs dms.spec
# SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
# SRPM_NAME=$(basename "$SRPM")
# echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
# echo "srpm_name=$SRPM_NAME" >> $GITHUB_OUTPUT
# echo "SRPM built: $SRPM_NAME"
# - name: Upload SRPM artifact
# uses: actions/upload-artifact@v4
# with:
# name: dms-stable-srpm-${{ steps.version.outputs.version }}
# path: ${{ steps.build.outputs.srpm_path }}
# retention-days: 90
# - name: Install Copr CLI
# run: |
# sudo apt-get install -y python3-pip
# pip3 install copr-cli
# mkdir -p ~/.config
# cat > ~/.config/copr << EOF
# [copr-cli]
# login = ${{ secrets.COPR_LOGIN }}
# username = avengemedia
# token = ${{ secrets.COPR_TOKEN }}
# copr_url = https://copr.fedorainfracloud.org
# EOF
# chmod 600 ~/.config/copr
# - name: Upload to Copr
# run: |
# SRPM="${{ steps.build.outputs.srpm_path }}"
# VERSION="${{ steps.version.outputs.version }}"
# echo "Uploading SRPM to avengemedia/dms..."
# BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
# echo "$BUILD_OUTPUT"
# BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
# if [ "$BUILD_ID" != "unknown" ]; then
# echo "Build submitted: https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
# fi

View File

@@ -3,8 +3,17 @@ name: DMS Copr Stable Release
on:
workflow_dispatch:
inputs:
package:
description: 'Package to build (dms, dms-greeter, or both)'
required: false
default: 'dms'
type: choice
options:
- dms
- dms-greeter
- both
version:
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
description: 'Versioning (e.g., 1.0.3, leave empty for latest release)'
required: false
default: ''
release:
@@ -13,8 +22,27 @@ on:
default: '1'
jobs:
build-and-upload:
determine-packages:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.set-packages.outputs.packages }}
steps:
- name: Set package list
id: set-packages
run: |
PACKAGE_INPUT="${{ github.event.inputs.package || 'dms' }}"
if [ "$PACKAGE_INPUT" = "both" ]; then
echo 'packages=["dms","dms-greeter"]' >> $GITHUB_OUTPUT
else
echo "packages=[\"$PACKAGE_INPUT\"]" >> $GITHUB_OUTPUT
fi
build-and-upload:
needs: determine-packages
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJSON(needs.determine-packages.outputs.packages) }}
steps:
- name: Checkout repository
@@ -39,7 +67,7 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release=$RELEASE" >> $GITHUB_OUTPUT
echo "✅ Building DMS hotfix version: $VERSION-$RELEASE"
echo "✅ Building ${{ matrix.package }} version: $VERSION-$RELEASE"
- name: Setup build environment
run: |
@@ -70,157 +98,31 @@ jobs:
VERSION="${{ steps.version.outputs.version }}"
RELEASE="${{ steps.version.outputs.release }}"
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
PACKAGE="${{ matrix.package }}"
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
# Spec for DMS stable releases - Generated by GitHub Actions
# Copy spec file from repository
cp distro/fedora/${PACKAGE}.spec ~/rpmbuild/SPECS/${PACKAGE}.spec
%global debug_package %{nil}
%global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
# Replace placeholders with actual values
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/${PACKAGE}.spec
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/${PACKAGE}.spec
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/${PACKAGE}.spec
Name: dms
Version: %{version}
Release: RELEASE_PLACEHOLDER%{?dist}
Summary: %{pkg_summary}
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
Source0: dms-qml.tar.gz
BuildRequires: gzip
BuildRequires: wget
BuildRequires: systemd-rpm-macros
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli = %{version}-%{release}
Requires: dgop
Recommends: cava
Recommends: cliphist
Recommends: danksearch
Recommends: hyprpicker
Recommends: matugen
Recommends: wl-clipboard
Recommends: NetworkManager
Recommends: qt6-qtmultimedia
Suggests: qt6ct
%description
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
and optimized for the niri and hyprland compositors. Features notifications,
app launcher, wallpaper customization, and fully customizable with plugins.
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
process monitoring, notification center, clipboard history, dock, control center,
lock screen, and comprehensive plugin system.
%package -n dms-cli
Summary: DankMaterialShell CLI tool
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
%description -n dms-cli
Command-line interface for DankMaterialShell configuration and management.
Provides native DBus bindings, NetworkManager integration, and system utilities.
%prep
%setup -q -c -n dms-qml
# Download architecture-specific binaries during build
# This ensures the correct architecture is used for each build target
case "%{_arch}" in
x86_64)
ARCH_SUFFIX="amd64"
;;
aarch64)
ARCH_SUFFIX="arm64"
;;
*)
echo "Unsupported architecture: %{_arch}"
exit 1
;;
esac
# Download dms-cli for target architecture
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
echo "Failed to download dms-cli for architecture %{_arch}"
exit 1
}
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
chmod +x %{_builddir}/dms-cli
%build
%install
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
# Shell completions
install -d %{buildroot}%{_datadir}/bash-completion/completions
install -d %{buildroot}%{_datadir}/zsh/site-functions
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
%{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
%{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
%{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
install -Dm644 %{_builddir}/dms-qml/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
%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
# Signal running DMS instances to reload (harmless if none running)
pkill -USR1 -x dms >/dev/null 2>&1 || :
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_datadir}/quickshell/dms/
%{_userunitdir}/dms.service
%files -n dms-cli
%{_bindir}/dms
%{_datadir}/bash-completion/completions/dms
%{_datadir}/zsh/site-functions/_dms
%{_datadir}/fish/vendor_completions.d/dms.fish
%changelog
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
- Stable release VERSION_PLACEHOLDER
- Built from GitHub release
SPECEOF
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
echo "✅ Spec file generated for v${VERSION}-${RELEASE}"
echo "✅ Spec file generated for ${PACKAGE} v${VERSION}-${RELEASE}"
echo ""
echo "=== Spec file preview ==="
head -40 ~/rpmbuild/SPECS/dms.spec
head -40 ~/rpmbuild/SPECS/${PACKAGE}.spec
- name: Build SRPM
id: build
run: |
cd ~/rpmbuild/SPECS
PACKAGE="${{ matrix.package }}"
echo "🔨 Building SRPM..."
rpmbuild -bs dms.spec
echo "🔨 Building SRPM for ${PACKAGE}..."
rpmbuild -bs ${PACKAGE}.spec
SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
SRPM=$(ls ~/rpmbuild/SRPMS/${PACKAGE}-*.src.rpm | tail -n 1)
SRPM_NAME=$(basename "$SRPM")
echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
@@ -234,7 +136,7 @@ jobs:
- name: Upload SRPM artifact
uses: actions/upload-artifact@v4
with:
name: dms-stable-srpm-${{ steps.version.outputs.version }}
name: ${{ matrix.package }}-stable-srpm-${{ steps.version.outputs.version }}
path: ${{ steps.build.outputs.srpm_path }}
retention-days: 90
@@ -255,23 +157,40 @@ jobs:
echo "✅ Copr CLI configured"
- name: Determine Copr project
id: copr_project
run: |
PACKAGE="${{ matrix.package }}"
if [ "$PACKAGE" = "dms" ]; then
COPR_PROJECT="avengemedia/dms"
elif [ "$PACKAGE" = "dms-greeter" ]; then
COPR_PROJECT="avengemedia/danklinux"
else
echo "❌ Unknown package: $PACKAGE"
exit 1
fi
echo "copr_project=$COPR_PROJECT" >> $GITHUB_OUTPUT
echo "✅ Copr project: $COPR_PROJECT"
- name: Upload to Copr
run: |
SRPM="${{ steps.build.outputs.srpm_path }}"
VERSION="${{ steps.version.outputs.version }}"
COPR_PROJECT="${{ steps.copr_project.outputs.copr_project }}"
PACKAGE="${{ matrix.package }}"
echo "🚀 Uploading SRPM to avengemedia/dms..."
echo "🚀 Uploading ${PACKAGE} SRPM to ${COPR_PROJECT}..."
echo " SRPM: $(basename $SRPM)"
echo " Version: $VERSION"
BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
BUILD_OUTPUT=$(copr-cli build "$COPR_PROJECT" "$SRPM" --nowait 2>&1)
echo "$BUILD_OUTPUT"
BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
if [ "$BUILD_ID" != "unknown" ]; then
echo "✅ Build submitted successfully!"
echo "🔗 https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
echo "🔗 https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/build/$BUILD_ID/"
else
echo "⚠️ Could not extract build ID, but upload may have succeeded"
fi
@@ -279,10 +198,13 @@ jobs:
- name: Build summary
if: always()
run: |
echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY
PACKAGE="${{ matrix.package }}"
COPR_PROJECT="${{ steps.copr_project.outputs.copr_project }}"
echo "### 🎉 ${PACKAGE} Stable Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Package:** ${PACKAGE}" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ steps.version.outputs.version }}-${{ steps.version.outputs.release }}" >> $GITHUB_STEP_SUMMARY
echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Stable release has been built and uploaded to Copr!" >> $GITHUB_STEP_SUMMARY

View File

@@ -7,13 +7,14 @@ on:
description: "Package to update (dms, dms-git, or all)"
required: false
default: "all"
tag_version:
description: "Specific tag version for dms stable (e.g., v1.0.2). Leave empty to auto-detect latest release."
required: false
default: ""
rebuild_release:
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
required: false
default: ""
push:
tags:
- "v*"
schedule:
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
@@ -97,7 +98,7 @@ jobs:
# Rebuild requested - always proceed
echo "packages=$PKG" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "🔄 Manual rebuild requested: $PKG (ppa$REBUILD)"
echo "🔄 Manual rebuild requested: $PKG (db$REBUILD)"
elif [[ "$PKG" == "all" ]]; then
# Check each package and build list of those needing updates
@@ -161,16 +162,51 @@ jobs:
id: packages
run: |
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
# Tag push event - use the pushed tag
echo "packages=dms" >> $GITHUB_OUTPUT
VERSION="${GITHUB_REF#refs/tags/}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Triggered by tag: $VERSION"
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
# Scheduled run - dms-git only
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "Triggered by schedule: updating git package"
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
# Use filtered packages from check-updates when package="all" and no rebuild requested
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]]; then
# Manual workflow dispatch
# Determine version for dms stable
if [[ "${{ github.event.inputs.package }}" == "dms" ]]; then
# For explicit dms selection, require tag_version
if [[ -n "${{ github.event.inputs.tag_version }}" ]]; then
VERSION="${{ github.event.inputs.tag_version }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Using specified tag: $VERSION"
else
echo "ERROR: tag_version is required when package=dms"
echo "Please specify a tag version (e.g., v1.0.2) or use package=all for auto-detection"
exit 1
fi
elif [[ "${{ github.event.inputs.package }}" == "all" ]]; then
# For "all", auto-detect if tag_version not specified
if [[ -n "${{ github.event.inputs.tag_version }}" ]]; then
VERSION="${{ github.event.inputs.tag_version }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Using specified tag: $VERSION"
else
# Auto-detect latest release for "all"
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "")
if [[ -n "$LATEST_TAG" ]]; then
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "Auto-detected latest release: $LATEST_TAG"
else
echo "ERROR: Could not auto-detect latest release"
exit 1
fi
fi
fi
# Use filtered packages from check-updates when package="all" and no rebuild/tag specified
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]] && [[ -z "${{ github.event.inputs.tag_version }}" ]]; then
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
else
@@ -186,7 +222,7 @@ jobs:
run: |
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD)
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "1.0.2")
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
@@ -207,14 +243,14 @@ jobs:
run: |
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD)
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "1.0.2")
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
# Single changelog entry (git snapshots don't need history)
CHANGELOG_DATE=$(date -R)
{
echo "dms-git ($NEW_VERSION) nightly; urgency=medium"
echo "dms-git (${NEW_VERSION}db1) nightly; urgency=medium"
echo ""
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
echo ""
@@ -226,10 +262,15 @@ jobs:
run: |
VERSION="${{ steps.packages.outputs.version }}"
VERSION_NO_V="${VERSION#v}"
echo "Updating packaging to version $VERSION_NO_V"
echo "==> Updating packaging files to version: $VERSION_NO_V"
# Update spec file
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
# Verify the update
UPDATED_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1)
echo "✓ Spec file now shows Version: $UPDATED_VERSION"
# Single changelog entry (full history on OBS website)
DATE_STR=$(date "+%a %b %d %Y")
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
@@ -256,13 +297,13 @@ jobs:
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
CHANGELOG_DATE=$(date -R)
{
echo "dms ($VERSION_NO_V) stable; urgency=medium"
echo "dms (${VERSION_NO_V}db1) stable; urgency=medium"
echo ""
echo " * Update to $VERSION stable release"
echo ""
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
} > "distro/debian/dms/debian/changelog"
echo "✓ Updated Debian changelog to $VERSION_NO_V"
echo "✓ Updated Debian changelog to ${VERSION_NO_V}db1"
fi
- name: Install Go
@@ -289,6 +330,7 @@ jobs:
- name: Upload to OBS
env:
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
TAG_VERSION: ${{ steps.packages.outputs.version }}
run: |
PACKAGES="${{ steps.packages.outputs.packages }}"
@@ -300,6 +342,7 @@ jobs:
MESSAGE="Automated update from GitHub Actions"
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
echo "==> Version being uploaded: ${{ steps.packages.outputs.version }}"
fi
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
@@ -309,7 +352,7 @@ jobs:
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Uploading $PKG to OBS..."
if [[ -n "$REBUILD_RELEASE" ]]; then
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
echo "🔄 Using rebuild release number: db$REBUILD_RELEASE"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -350,7 +393,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
echo "**Rebuild Number:** ppa${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
echo "**Rebuild Number:** db${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi

3
.gitignore vendored
View File

@@ -96,11 +96,12 @@ go.work
go.work.sum
# env file
.env
.env*
# Editor/IDE
# .idea/
# .vscode/
vim/
bin/

View File

@@ -1,6 +1,15 @@
This file is more of a quick reference so I know what to account for before next releases.
# 1.2.0
- Added clipboard and clipboard history integration
- Added swipe to dismiss notification popups and from center
- Added paste from clipboard history view - requires wtype
- Optimize surface damage of OSD & Toast
- Add monitor configurator (niri, Hyprland, MangoWC)
- **BREAKING** ghostty theme changed to ~/.config/ghostty/themes/danktheme
- requires intervention and doc update
- Added desktop widget plugins
- dev guidance available
- builtin clock & dgop widgets
- Initial RTL support/i18n

View File

@@ -163,7 +163,7 @@ quickshell -p quickshell/
inputs.dms.url = "github:AvengeMedia/DankMaterialShell";
# Use in home-manager or NixOS configuration
imports = [ inputs.dms.homeModules.dankMaterialShell.default ];
imports = [ inputs.dms.homeModules.dank-material-shell ];
}
```

View File

@@ -9,7 +9,7 @@ Type=dbus
BusName=org.freedesktop.Notifications
ExecStart=/usr/bin/dms run --session
ExecReload=/usr/bin/pkill -USR1 -x dms
Restart=always
Restart=on-failure
RestartSec=1.23
TimeoutStopSec=10

View File

@@ -14,34 +14,63 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
## System Integration
**Wayland Protocols**
- `wlr-gamma-control-unstable-v1` - Night mode and gamma control
- `wlr-screencopy-unstable-v1` - Screen capture for color picker
- `wlr-layer-shell-unstable-v1` - Overlay surfaces for color picker
- `wp-viewporter` - Fractional scaling support
- `dwl-ipc-unstable-v2` - dwl/MangoWC workspace integration
- `ext-workspace-v1` - Workspace protocol support
- `wlr-output-management-unstable-v1` - Display configuration
### Wayland Protocols (Client)
**DBus Interfaces**
- NetworkManager/iwd - Network management
- logind - Session control and inhibit locks
- accountsservice - User account information
- CUPS - Printer management
- Custom IPC via unix socket (JSON API)
All Wayland protocols are consumed as a client - connecting to the compositor.
**Hardware Control**
- DDC/CI protocol - External monitor brightness control (like `ddcutil`)
- Backlight control - Internal display brightness via `login1` or sysfs
- LED control - Keyboard/device LED management
- evdev input monitoring - Keyboard state tracking (caps lock, etc.)
| Protocol | Purpose |
| ----------------------------------------- | ----------------------------------------------------------- |
| `wlr-gamma-control-unstable-v1` | Night mode color temperature control |
| `wlr-screencopy-unstable-v1` | Screen capture for color picker/screenshot |
| `wlr-layer-shell-unstable-v1` | Overlay surfaces for color picker UI/screenshot |
| `wlr-output-management-unstable-v1` | Display configuration |
| `wlr-output-power-management-unstable-v1` | DPMS on/off CLI |
| `wp-viewporter` | Fractional scaling support (color picker/screenshot UIs) |
| `keyboard-shortcuts-inhibit-unstable-v1` | Inhibit compositor shortcuts during color picker/screenshot |
| `ext-data-control-v1` | Clipboard history and persistence |
| `ext-workspace-v1` | Workspace integration |
| `dwl-ipc-unstable-v2` | dwl/MangoWC IPC for tags, outputs, etc. |
### DBus Interfaces
**Client (consuming external services):**
| Interface | Purpose |
| -------------------------------- | --------------------------------------------- |
| `org.bluez` | Bluetooth management with pairing agent |
| `org.freedesktop.NetworkManager` | Network management |
| `net.connman.iwd` | iwd Wi-Fi backend |
| `org.freedesktop.network1` | systemd-networkd integration |
| `org.freedesktop.login1` | Session control, sleep inhibitors, brightness |
| `org.freedesktop.Accounts` | User account information |
| `org.freedesktop.portal.Desktop` | Desktop appearance settings (color scheme) |
| CUPS via IPP + D-Bus | Printer management with job notifications |
**Server (implementing interfaces):**
| Interface | Purpose |
| ----------------------------- | -------------------------------------- |
| `org.freedesktop.ScreenSaver` | Screensaver inhibit for video playback |
Custom IPC via unix socket (JSON API) for shell communication.
### Hardware Control
| Subsystem | Method | Purpose |
| --------- | ------------------- | ---------------------------------- |
| DDC/CI | I2C direct | External monitor brightness |
| Backlight | logind or sysfs | Internal display brightness |
| evdev | `/dev/input/event*` | Keyboard state (caps lock LED) |
| udev | netlink monitor | Backlight device updates (for OSD) |
### Plugin System
**Plugin System**
- Plugin registry integration
- Plugin lifecycle management
- Settings persistence
## CLI Commands
- `dms run [-d]` - Start shell (optionally as daemon)
- `dms restart` / `dms kill` - Manage running processes
- `dms ipc <command>` - Send IPC commands (toggle launcher, notifications, etc.)
@@ -70,6 +99,7 @@ The on-screen preview displays the selected format. JSON output includes hex, RG
Requires Go 1.24+
**Development build:**
```bash
make # Build dms CLI
make dankinstall # Build installer
@@ -77,6 +107,7 @@ make test # Run tests
```
**Distribution build:**
```bash
make dist # Build without update/greeter features
```
@@ -84,6 +115,7 @@ make dist # Build without update/greeter features
Produces `bin/dms-linux-amd64` and `bin/dms-linux-arm64`
**Installation:**
```bash
sudo make install # Install to /usr/local/bin/dms
```
@@ -91,6 +123,7 @@ sudo make install # Install to /usr/local/bin/dms
## Development
**Setup pre-commit hooks:**
```bash
git config core.hooksPath .githooks
```
@@ -98,6 +131,7 @@ git config core.hooksPath .githooks
This runs gofmt, golangci-lint, tests, and builds before each commit when `core/` files are staged.
**Regenerating Wayland Protocol Bindings:**
```bash
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest
go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
@@ -105,6 +139,7 @@ go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
```
**Module Structure:**
- `cmd/` - Binary entrypoints (dms, dankinstall)
- `internal/distros/` - Distribution-specific installation logic
- `internal/proto/` - Wayland protocol bindings

View File

@@ -144,8 +144,6 @@ var (
clipConfigEnabled bool
clipConfigDisableHistory bool
clipConfigEnableHistory bool
clipConfigDisablePersist bool
clipConfigEnablePersist bool
)
func init() {
@@ -173,8 +171,6 @@ func init() {
clipConfigSetCmd.Flags().BoolVar(&clipConfigEnabled, "enable", false, "Enable clipboard manager")
clipConfigSetCmd.Flags().BoolVar(&clipConfigDisableHistory, "disable-history", false, "Disable clipboard history persistence")
clipConfigSetCmd.Flags().BoolVar(&clipConfigEnableHistory, "enable-history", false, "Enable clipboard history persistence")
clipConfigSetCmd.Flags().BoolVar(&clipConfigDisablePersist, "disable-persist", false, "Disable clipboard ownership persistence")
clipConfigSetCmd.Flags().BoolVar(&clipConfigEnablePersist, "enable-persist", false, "Enable clipboard ownership persistence")
clipWatchCmd.Flags().BoolVarP(&clipWatchStore, "store", "s", false, "Store clipboard changes to history (no server required)")
@@ -597,12 +593,6 @@ func runClipConfigSet(cmd *cobra.Command, args []string) {
if clipConfigEnableHistory {
params["disableHistory"] = false
}
if clipConfigDisablePersist {
params["disablePersist"] = true
}
if clipConfigEnablePersist {
params["disablePersist"] = false
}
if len(params) == 0 {
fmt.Println("No config options specified")

View File

@@ -171,7 +171,6 @@ var pluginsUpdateCmd = &cobra.Command{
}
func runVersion(cmd *cobra.Command, args []string) {
printASCII()
fmt.Printf("%s\n", formatVersion(Version))
}
@@ -220,7 +219,7 @@ func getBaseVersion() string {
}
// Fallback
return "0.6.2"
return "1.0.2"
}
func startDebugServer() error {

View File

@@ -22,6 +22,7 @@ func init() {
dank16Cmd.Flags().Bool("json", false, "Output in JSON format")
dank16Cmd.Flags().Bool("kitty", false, "Output in Kitty terminal format")
dank16Cmd.Flags().Bool("foot", false, "Output in Foot terminal format")
dank16Cmd.Flags().Bool("neovim", false, "Output in Neovim plugin format")
dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format")
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
@@ -40,6 +41,7 @@ func runDank16(cmd *cobra.Command, args []string) {
isJson, _ := cmd.Flags().GetBool("json")
isKitty, _ := cmd.Flags().GetBool("kitty")
isFoot, _ := cmd.Flags().GetBool("foot")
isNeovim, _ := cmd.Flags().GetBool("neovim")
isAlacritty, _ := cmd.Flags().GetBool("alacritty")
isGhostty, _ := cmd.Flags().GetBool("ghostty")
isWezterm, _ := cmd.Flags().GetBool("wezterm")
@@ -116,6 +118,8 @@ func runDank16(cmd *cobra.Command, args []string) {
fmt.Print(dank16.GenerateGhosttyTheme(colors))
} else if isWezterm {
fmt.Print(dank16.GenerateWeztermTheme(colors))
} else if isNeovim {
fmt.Print(dank16.GenerateNeovimTheme(colors))
} else {
fmt.Print(dank16.GenerateGhosttyTheme(colors))
}

View File

@@ -18,6 +18,25 @@ import (
type ipcTargets map[string]map[string][]string
// getProcessExitCode returns the exit code from a ProcessState.
// For normal exits, returns the exit code directly.
// For signal termination, returns 128 + signal number (Unix convention).
func getProcessExitCode(state *os.ProcessState) int {
if state == nil {
return 1
}
if code := state.ExitCode(); code != -1 {
return code
}
// Process was killed by signal - extract signal number
if status, ok := state.Sys().(syscall.WaitStatus); ok {
if status.Signaled() {
return 128 + int(status.Signal())
}
}
return 1
}
var isSessionManaged bool
func execDetachedRestart(targetPID int) {
@@ -214,14 +233,28 @@ func runShellInteractive(session bool) {
for {
select {
case sig := <-sigChan:
// Handle SIGUSR1 restart for non-session managed processes
if sig == syscall.SIGUSR1 && !isSessionManaged {
if sig == syscall.SIGUSR1 {
if isSessionManaged {
log.Infof("Received SIGUSR1, exiting for systemd restart...")
cancel()
cmd.Process.Signal(syscall.SIGTERM)
os.Remove(socketPath)
os.Exit(1)
}
log.Infof("Received SIGUSR1, spawning detached restart process...")
execDetachedRestart(os.Getpid())
// Exit immediately to avoid race conditions with detached restart
return
}
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
select {
case <-errChan:
cancel()
os.Remove(socketPath)
os.Exit(getProcessExitCode(cmd.ProcessState))
case <-time.After(500 * time.Millisecond):
}
log.Infof("\nReceived signal %v, shutting down...", sig)
cancel()
cmd.Process.Signal(syscall.SIGTERM)
@@ -235,7 +268,7 @@ func runShellInteractive(session bool) {
cmd.Process.Signal(syscall.SIGTERM)
}
os.Remove(socketPath)
os.Exit(1)
os.Exit(getProcessExitCode(cmd.ProcessState))
}
}
}
@@ -440,15 +473,28 @@ func runShellDaemon(session bool) {
for {
select {
case sig := <-sigChan:
// Handle SIGUSR1 restart for non-session managed processes
if sig == syscall.SIGUSR1 && !isSessionManaged {
if sig == syscall.SIGUSR1 {
if isSessionManaged {
log.Infof("Received SIGUSR1, exiting for systemd restart...")
cancel()
cmd.Process.Signal(syscall.SIGTERM)
os.Remove(socketPath)
os.Exit(1)
}
log.Infof("Received SIGUSR1, spawning detached restart process...")
execDetachedRestart(os.Getpid())
// Exit immediately to avoid race conditions with detached restart
return
}
// All other signals: clean shutdown
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
select {
case <-errChan:
cancel()
os.Remove(socketPath)
os.Exit(getProcessExitCode(cmd.ProcessState))
case <-time.After(500 * time.Millisecond):
}
cancel()
cmd.Process.Signal(syscall.SIGTERM)
os.Remove(socketPath)
@@ -460,7 +506,7 @@ func runShellDaemon(session bool) {
cmd.Process.Signal(syscall.SIGTERM)
}
os.Remove(socketPath)
os.Exit(1)
os.Exit(getProcessExitCode(cmd.ProcessState))
}
}
}

View File

@@ -213,6 +213,11 @@ func (cd *ConfigDeployer) deployNiriDmsConfigs(dmsDir, terminalCommand string) e
for _, cfg := range configs {
path := filepath.Join(dmsDir, cfg.name)
// Skip if file already exists to preserve user modifications
if _, err := os.Stat(path); err == nil {
cd.log(fmt.Sprintf("Skipping %s (already exists)", cfg.name))
continue
}
if err := os.WriteFile(path, []byte(cfg.content), 0644); err != nil {
return fmt.Errorf("failed to write %s: %w", cfg.name, err)
}
@@ -265,7 +270,13 @@ func (cd *ConfigDeployer) deployGhosttyConfig() ([]DeploymentResult, error) {
colorResult := DeploymentResult{
ConfigType: "Ghostty Colors",
Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config-dankcolors"),
Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "themes", "dankcolors"),
}
themesDir := filepath.Dir(colorResult.Path)
if err := os.MkdirAll(themesDir, 0755); err != nil {
mainResult.Error = fmt.Errorf("failed to create themes directory: %w", err)
return []DeploymentResult{mainResult}, mainResult.Error
}
if err := os.WriteFile(colorResult.Path, []byte(GhosttyColorConfig), 0644); err != nil {

View File

@@ -468,7 +468,7 @@ func TestHyprlandConfigStructure(t *testing.T) {
func TestGhosttyConfigStructure(t *testing.T) {
assert.Contains(t, GhosttyConfig, "window-decoration = false")
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
assert.Contains(t, GhosttyConfig, "config-file = ./config-dankcolors")
assert.Contains(t, GhosttyConfig, "theme = dankcolors")
}
func TestGhosttyColorConfigStructure(t *testing.T) {

View File

@@ -48,4 +48,4 @@ keybind = shift+enter=text:\n
gtk-single-instance = true
# Dank color generation
config-file = ./config-dankcolors
theme = dankcolors

View File

@@ -112,3 +112,24 @@ func GenerateWeztermTheme(p Palette) string {
p.Color12.Hex, p.Color13.Hex, p.Color14.Hex, p.Color15.Hex)
return result.String()
}
func GenerateNeovimTheme(p Palette) string {
var result strings.Builder
fmt.Fprintf(&result, "vim.g.terminal_color_0 = \"%s\"\n", p.Color0.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_1 = \"%s\"\n", p.Color1.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_2 = \"%s\"\n", p.Color2.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_3 = \"%s\"\n", p.Color3.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_4 = \"%s\"\n", p.Color4.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_5 = \"%s\"\n", p.Color5.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_6 = \"%s\"\n", p.Color6.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_7 = \"%s\"\n", p.Color7.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_8 = \"%s\"\n", p.Color8.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_9 = \"%s\"\n", p.Color9.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_10 = \"%s\"\n", p.Color10.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_11 = \"%s\"\n", p.Color11.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_12 = \"%s\"\n", p.Color12.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_13 = \"%s\"\n", p.Color13.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_14 = \"%s\"\n", p.Color14.Hex)
fmt.Fprintf(&result, "vim.g.terminal_color_15 = \"%s\"\n", p.Color15.Hex)
return result.String()
}

View File

@@ -274,6 +274,9 @@ output_path = '%s'
if !opts.ShouldSkipTemplate("wezterm") {
appendTerminalConfig(opts, cfgFile, tmpDir, "wezterm", "wezterm.toml")
}
if !opts.ShouldSkipTemplate("nvim") {
appendTerminalConfig(opts, cfgFile, tmpDir, "nvim", "neovim.toml")
}
if !opts.ShouldSkipTemplate("dgop") {
appendConfig(opts, cfgFile, "dgop", "dgop.toml")

View File

@@ -208,9 +208,6 @@ func handleSetConfig(conn net.Conn, req models.Request, m *Manager) {
if v, ok := req.Params["disableHistory"].(bool); ok {
cfg.DisableHistory = v
}
if v, ok := req.Params["disablePersist"].(bool); ok {
cfg.DisablePersist = v
}
if err := m.SetConfig(cfg); err != nil {
models.RespondError(conn, req.ID, err.Error())

View File

@@ -319,10 +319,6 @@ func (m *Manager) readAndStore(r *os.File, mimeType string) {
m.storeClipboardEntry(data, mimeType)
}
if !cfg.DisablePersist {
m.persistClipboard([]string{mimeType}, map[string][]byte{mimeType: data})
}
m.updateState()
m.notifySubscribers()
}
@@ -348,105 +344,6 @@ func (m *Manager) storeClipboardEntry(data []byte, mimeType string) {
}
}
func (m *Manager) persistClipboard(mimeTypes []string, data map[string][]byte) {
m.persistMutex.Lock()
m.persistMimeTypes = mimeTypes
m.persistData = data
m.persistMutex.Unlock()
m.post(func() {
m.takePersistOwnership()
})
}
func (m *Manager) takePersistOwnership() {
if m.dataControlMgr == nil || m.dataDevice == nil {
return
}
if m.getConfig().DisablePersist {
return
}
m.persistMutex.RLock()
mimeTypes := m.persistMimeTypes
m.persistMutex.RUnlock()
if len(mimeTypes) == 0 {
return
}
dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
source, err := dataMgr.CreateDataSource()
if err != nil {
log.Errorf("Failed to create persist source: %v", err)
return
}
for _, mime := range mimeTypes {
if err := source.Offer(mime); err != nil {
log.Errorf("Failed to offer mime type %s: %v", mime, err)
}
}
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
fd := e.Fd
defer syscall.Close(fd)
m.persistMutex.RLock()
d := m.persistData[e.MimeType]
m.persistMutex.RUnlock()
if len(d) == 0 {
return
}
file := os.NewFile(uintptr(fd), "clipboard-pipe")
defer file.Close()
file.Write(d)
})
source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) {
m.ownerLock.Lock()
m.isOwner = false
m.ownerLock.Unlock()
})
if m.currentSource != nil {
oldSource := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
oldSource.Destroy()
}
m.currentSource = source
device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1)
if err := device.SetSelection(source); err != nil {
log.Errorf("Failed to set persist selection: %v", err)
return
}
m.ownerLock.Lock()
m.isOwner = true
m.ownerLock.Unlock()
}
func (m *Manager) releaseOwnership() {
m.ownerLock.Lock()
m.isOwner = false
m.ownerLock.Unlock()
m.persistMutex.Lock()
m.persistData = nil
m.persistMimeTypes = nil
m.persistMutex.Unlock()
if m.currentSource != nil {
source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
source.Destroy()
m.currentSource = nil
}
}
func (m *Manager) storeEntry(entry Entry) error {
if m.db == nil {
return fmt.Errorf("database not available")
@@ -495,6 +392,9 @@ func (m *Manager) deduplicateInTx(b *bolt.Bucket, hash uint64) error {
}
func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
if m.config.MaxHistory < 0 {
return nil
}
c := b.Cursor()
var count int
for k, _ := c.Last(); k != nil; k, _ = c.Prev() {
@@ -1309,13 +1209,7 @@ func (m *Manager) applyConfigChange(newCfg Config) {
}
}
if newCfg.DisablePersist && !oldCfg.DisablePersist {
log.Info("Clipboard persist disabled, releasing ownership")
m.releaseOwnership()
}
log.Infof("Clipboard config reloaded: disableHistory=%v disablePersist=%v",
newCfg.DisableHistory, newCfg.DisablePersist)
log.Infof("Clipboard config reloaded: disableHistory=%v", newCfg.DisableHistory)
m.updateState()
m.notifySubscribers()

View File

@@ -458,7 +458,6 @@ func TestDefaultConfig(t *testing.T) {
assert.False(t, cfg.ClearAtStartup)
assert.False(t, cfg.Disabled)
assert.False(t, cfg.DisableHistory)
assert.True(t, cfg.DisablePersist)
}
func TestManager_PostDelegatesToWlContext(t *testing.T) {

View File

@@ -21,7 +21,6 @@ type Config struct {
Disabled bool `json:"disabled"`
DisableHistory bool `json:"disableHistory"`
DisablePersist bool `json:"disablePersist"`
}
func DefaultConfig() Config {
@@ -30,7 +29,6 @@ func DefaultConfig() Config {
MaxEntrySize: 5 * 1024 * 1024,
AutoClearDays: 0,
ClearAtStartup: false,
DisablePersist: true,
}
}

View File

@@ -204,9 +204,6 @@ func handleClipboardSetConfig(conn net.Conn, req models.Request) {
if v, ok := req.Params["disableHistory"].(bool); ok {
cfg.DisableHistory = v
}
if v, ok := req.Params["disablePersist"].(bool); ok {
cfg.DisablePersist = v
}
if err := clipboard.SaveConfig(cfg); err != nil {
models.RespondError(conn, req.ID, err.Error())

View File

@@ -241,6 +241,7 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
handle.SetAdaptiveSyncHandler(func(e wlr_output_management.ZwlrOutputHeadV1AdaptiveSyncEvent) {
log.Debugf("WlrOutput: Head %d adaptive sync: %d", headID, e.State)
head.adaptiveSync = e.State
head.adaptiveSyncSupported = true
m.post(func() {
m.updateState()
})
@@ -360,22 +361,23 @@ func (m *Manager) updateState() {
}
output := Output{
Name: head.name,
Description: head.description,
Make: head.make,
Model: head.model,
SerialNumber: head.serialNumber,
PhysicalWidth: head.physicalWidth,
PhysicalHeight: head.physicalHeight,
Enabled: head.enabled,
X: head.x,
Y: head.y,
Transform: head.transform,
Scale: head.scale,
CurrentMode: currentMode,
Modes: modes,
AdaptiveSync: head.adaptiveSync,
ID: head.id,
Name: head.name,
Description: head.description,
Make: head.make,
Model: head.model,
SerialNumber: head.serialNumber,
PhysicalWidth: head.physicalWidth,
PhysicalHeight: head.physicalHeight,
Enabled: head.enabled,
X: head.x,
Y: head.y,
Transform: head.transform,
Scale: head.scale,
CurrentMode: currentMode,
Modes: modes,
AdaptiveSync: head.adaptiveSync,
AdaptiveSyncSupported: head.adaptiveSyncSupported,
ID: head.id,
}
outputs = append(outputs, output)
return true

View File

@@ -17,22 +17,23 @@ type OutputMode struct {
}
type Output struct {
Name string `json:"name"`
Description string `json:"description"`
Make string `json:"make"`
Model string `json:"model"`
SerialNumber string `json:"serialNumber"`
PhysicalWidth int32 `json:"physicalWidth"`
PhysicalHeight int32 `json:"physicalHeight"`
Enabled bool `json:"enabled"`
X int32 `json:"x"`
Y int32 `json:"y"`
Transform int32 `json:"transform"`
Scale float64 `json:"scale"`
CurrentMode *OutputMode `json:"currentMode"`
Modes []OutputMode `json:"modes"`
AdaptiveSync uint32 `json:"adaptiveSync"`
ID uint32 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Make string `json:"make"`
Model string `json:"model"`
SerialNumber string `json:"serialNumber"`
PhysicalWidth int32 `json:"physicalWidth"`
PhysicalHeight int32 `json:"physicalHeight"`
Enabled bool `json:"enabled"`
X int32 `json:"x"`
Y int32 `json:"y"`
Transform int32 `json:"transform"`
Scale float64 `json:"scale"`
CurrentMode *OutputMode `json:"currentMode"`
Modes []OutputMode `json:"modes"`
AdaptiveSync uint32 `json:"adaptiveSync"`
AdaptiveSyncSupported bool `json:"adaptiveSyncSupported"`
ID uint32 `json:"id"`
}
type State struct {
@@ -72,25 +73,26 @@ type Manager struct {
}
type headState struct {
id uint32
handle *wlr_output_management.ZwlrOutputHeadV1
name string
description string
make string
model string
serialNumber string
physicalWidth int32
physicalHeight int32
enabled bool
x int32
y int32
transform int32
scale float64
currentModeID uint32
modeIDs []uint32
adaptiveSync uint32
finished bool
ready bool
id uint32
handle *wlr_output_management.ZwlrOutputHeadV1
name string
description string
make string
model string
serialNumber string
physicalWidth int32
physicalHeight int32
enabled bool
x int32
y int32
transform int32
scale float64
currentModeID uint32
modeIDs []uint32
adaptiveSync uint32
adaptiveSyncSupported bool
finished bool
ready bool
}
type modeState struct {
@@ -169,7 +171,7 @@ func stateChanged(old, new *State) bool {
if oldOut.Transform != newOut.Transform || oldOut.Scale != newOut.Scale {
return true
}
if oldOut.AdaptiveSync != newOut.AdaptiveSync {
if oldOut.AdaptiveSync != newOut.AdaptiveSync || oldOut.AdaptiveSyncSupported != newOut.AdaptiveSyncSupported {
return true
}
if (oldOut.CurrentMode == nil) != (newOut.CurrentMode == nil) {

View File

@@ -1,4 +1,4 @@
dms-git (1.0.2+git2528.d336866f) nightly; urgency=medium
dms-git (1.0.2+git2528.d336866fdb1) nightly; urgency=medium
* Git snapshot (commit 2528: d336866f)
@@ -16,23 +16,6 @@ dms-git (1.0.2+git2518.a783d650) nightly; urgency=medium
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 15:11:40 +0000
dms-git (1.0.2+git2510.0f89886c) nightly; urgency=medium
* Git snapshot (commit 2510: 0f89886c)
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:46:43 +0000
dms-git (1.0.2+git2507.b2ac9c6c) nightly; urgency=medium
* Git snapshot (commit 2507: b2ac9c6c)
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:18:05 +0000
dms-git (1.0.2+git2505.82f881af) nightly; urgency=medium
* Git snapshot (commit 2505: 82f881af)
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 05:55:03 +0000
dms-git (1.0.0+git2419.993f14a3) nightly; urgency=medium

View File

@@ -3,19 +3,19 @@
<service name="download_url">
<param name="protocol">https</param>
<param name="host">github.com</param>
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.2.tar.gz</param>
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.3.tar.gz</param>
<param name="filename">dms-source.tar.gz</param>
</service>
<!-- Download amd64 binary -->
<service name="download_url">
<param name="protocol">https</param>
<param name="host">github.com</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-amd64.gz</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-amd64.gz</param>
</service>
<!-- Download arm64 binary -->
<service name="download_url">
<param name="protocol">https</param>
<param name="host">github.com</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-arm64.gz</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-arm64.gz</param>
</service>
</services>

View File

@@ -1,6 +1,12 @@
dms (1.0.2ppa6) unstable; urgency=medium
dms (1.0.3db1) unstable; urgency=medium
* Rebuild to fix repository metadata issues
* Update to v1.0.3 stable release
-- Avenge Media <AvengeMedia.US@gmail.com> Mon, 16 Dec 2025 10:00:00 +0000
dms (1.0.2db1) unstable; urgency=medium
* Update to v1.0.2 stable release
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:47:39 +0000

View File

@@ -11,7 +11,7 @@ Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
Package: dms
Architecture: amd64
Depends: ${misc:Depends},
quickshell-git | quickshell,
quickshell | quickshell-git,
accountsservice,
cava,
cliphist,

135
distro/fedora/dms-git.spec Normal file
View File

@@ -0,0 +1,135 @@
# Spec for DMS - uses rpkg macros for git builds
%global debug_package %{nil}
%global version {{{ git_repo_version }}}
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
Name: dms
Epoch: 2
Version: %{version}
Release: 1%{?dist}
Summary: %{pkg_summary}
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}}
BuildRequires: git-core
BuildRequires: gzip
BuildRequires: golang >= 1.24
BuildRequires: make
BuildRequires: wget
BuildRequires: systemd-rpm-macros
# Core requirements
Requires: (quickshell-git or quickshell)
Requires: accountsservice
Requires: dms-cli = %{epoch}:%{version}-%{release}
Requires: dgop
# Core utilities (Highly recommended for DMS functionality)
Recommends: cava
Recommends: danksearch
Recommends: matugen
Recommends: quickshell-git
Recommends: wl-clipboard
# Recommended system packages
Recommends: NetworkManager
Recommends: qt6-qtmultimedia
Suggests: qt6ct
%description
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
and optimized for the niri, hyprland, sway, and dwl (MangoWC) compositors. Features notifications,
app launcher, wallpaper customization, and fully customizable with plugins.
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
process monitoring, notification center, clipboard history, dock, control center,
lock screen, and comprehensive plugin system.
%package -n dms-cli
Summary: DankMaterialShell CLI tool
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
%description -n dms-cli
Command-line interface for DankMaterialShell configuration and management.
Provides native DBus bindings, NetworkManager integration, and system utilities.
%prep
{{{ git_repo_setup_macro }}}
%build
# Build DMS CLI from source (core/subdirectory)
VERSION="%{version}"
COMMIT=$(echo "%{version}" | grep -oP '[a-f0-9]{7,}' | head -n1 || echo "unknown")
cd core
make dist VERSION="$VERSION" COMMIT="$COMMIT"
%install
# Install dms-cli binary (built from source)
case "%{_arch}" in
x86_64)
DMS_BINARY="dms-linux-amd64"
;;
aarch64)
DMS_BINARY="dms-linux-arm64"
;;
*)
echo "Unsupported architecture: %{_arch}"
exit 1
;;
esac
install -Dm755 core/bin/${DMS_BINARY} %{buildroot}%{_bindir}/dms
# Shell completions
install -d %{buildroot}%{_datadir}/bash-completion/completions
install -d %{buildroot}%{_datadir}/zsh/site-functions
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
core/bin/${DMS_BINARY} completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
core/bin/${DMS_BINARY} completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
core/bin/${DMS_BINARY} completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
# Install systemd user service
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
# Install shell files to shared data location
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms/
# Remove build files
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
%posttrans
# Signal running DMS instances to reload
pkill -USR1 -x dms >/dev/null 2>&1 || :
%files
%license LICENSE
%doc CONTRIBUTING.md
%doc quickshell/README.md
%{_datadir}/quickshell/dms/
%{_userunitdir}/dms.service
%{_datadir}/applications/dms-open.desktop
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
%files -n dms-cli
%{_bindir}/dms
%{_datadir}/bash-completion/completions/dms
%{_datadir}/zsh/site-functions/_dms
%{_datadir}/fish/vendor_completions.d/dms.fish
%changelog
{{{ git_repo_changelog }}}

View File

@@ -1,22 +1,22 @@
# Spec for DMS Greeter - Git builds using rpkg macros
# Spec for DMS Greeter - Stable releases
%global debug_package %{nil}
%global version {{{ git_repo_version }}}
%global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell greeter for greetd
Name: dms-greeter
Version: %{version}
Release: 0.git%{?dist}
Release: RELEASE_PLACEHOLDER%{?dist}
Summary: %{pkg_summary}
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}}
BuildRequires: git-core
# For the _tmpfilesdir macro.
BuildRequires: systemd-rpm-macros
Source0: dms-qml.tar.gz
BuildRequires: gzip
BuildRequires: wget
BuildRequires: systemd-rpm-macros
Requires: greetd
Requires: (quickshell-git or quickshell)
@@ -24,14 +24,11 @@ Requires(post): /usr/sbin/useradd
Requires(post): /usr/sbin/groupadd
Recommends: policycoreutils-python-utils
Recommends: setfacl
Recommends: acl
Suggests: niri
Suggests: hyprland
Suggests: sway
# Provides: greetd-dms-greeter = %{version}-%{release}
# Conflicts: greetd-dms-greeter
%description
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
inspired greeter interface built with Quickshell for Wayland compositors.
@@ -41,31 +38,26 @@ compositor detection and configuration. Features session selection, user
authentication, and dynamic theming.
%prep
{{{ git_repo_setup_macro }}}
%setup -q -c -n dms-qml
%build
%install
# Install greeter files to shared data location (from quickshell/ subdirectory)
# Install greeter files to shared data location
install -dm755 %{buildroot}%{_datadir}/quickshell/dms-greeter
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms-greeter/
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms-greeter/
# Install launcher script
install -Dm755 quickshell/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
install -Dm755 %{_builddir}/dms-qml/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
# Install documentation
install -Dm644 quickshell/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
install -Dm644 %{_builddir}/dms-qml/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
# Create cache directory for greeter data
install -Dpm0644 quickshell/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
install -Dpm0644 %{_builddir}/dms-qml/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
# Install LICENSE file
install -Dm644 LICENSE %{buildroot}%{_docdir}/dms-greeter/LICENSE
install -Dm644 %{_builddir}/dms-qml/LICENSE %{buildroot}%{_docdir}/dms-greeter/LICENSE
# Create greeter home directory
install -dm755 %{buildroot}%{_sharedstatedir}/greeter
# Note: We do NOT install a PAM config here to avoid conflicting with greetd package
# Instead, we verify/fix it in %post if needed
# Note: We do NOT install a PAM config here to avoid conflicting w/greetd packages
# Remove build and development files
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/.gitignore
@@ -73,9 +65,8 @@ rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.github
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/distro
%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)
# Remove directories & 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
@@ -89,7 +80,7 @@ fi
%{_tmpfilesdir}/%{name}.conf
%pre
# Create greeter user/group if they don't exist (greetd expects this)
# Create greeter user/group if they don't exist
getent group greeter >/dev/null || groupadd -r greeter
getent passwd greeter >/dev/null || \
useradd -r -g greeter -d %{_sharedstatedir}/greeter -s /bin/bash \
@@ -127,7 +118,6 @@ chown -R greeter:greeter %{_sharedstatedir}/greeter 2>/dev/null || true
# Verify PAM configuration - only fix if insufficient
PAM_CONFIG="/etc/pam.d/greetd"
if [ ! -f "$PAM_CONFIG" ]; then
# PAM config doesn't exist - create it
cat > "$PAM_CONFIG" << 'PAM_EOF'
#%PAM-1.0
auth substack system-auth
@@ -149,7 +139,6 @@ PAM_EOF
# Only show message on initial install
[ "$1" -eq 1 ] && echo "Created PAM configuration for greetd"
elif ! grep -q "pam_systemd\|system-auth" "$PAM_CONFIG"; then
# PAM config exists but looks insufficient - back it up and replace
cp "$PAM_CONFIG" "$PAM_CONFIG.backup-dms-greeter"
cat > "$PAM_CONFIG" << 'PAM_EOF'
#%PAM-1.0
@@ -198,9 +187,8 @@ command = "/usr/bin/dms-greeter --command COMPOSITOR_PLACEHOLDER"
GREETD_EOF
sed -i "s|COMPOSITOR_PLACEHOLDER|$COMPOSITOR|" "$GREETD_CONFIG"
CONFIG_STATUS="Created new config with $COMPOSITOR "
# If config exists and doesn't have dms-greeter, update it
elif ! grep -q "dms-greeter" "$GREETD_CONFIG"; then
# Backup existing config
BACKUP_FILE="${GREETD_CONFIG}.backup-$(date +%%Y%%m%%d-%%H%%M%%S)"
cp "$GREETD_CONFIG" "$BACKUP_FILE" 2>/dev/null || true
@@ -267,4 +255,6 @@ if [ "$1" -eq 0 ] && [ -x /usr/sbin/semanage ]; then
fi
%changelog
{{{ git_repo_changelog }}}
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
- Stable release VERSION_PLACEHOLDER
- Built from GitHub release

View File

@@ -1,49 +1,40 @@
# Spec for DMS - uses rpkg macros for git builds
# Feodra spec for DMS stable releases
%global debug_package %{nil}
%global version {{{ git_repo_version }}}
%global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
Name: dms
Epoch: 2
Version: %{version}
Release: 1%{?dist}
Release: RELEASE_PLACEHOLDER%{?dist}
Summary: %{pkg_summary}
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}}
BuildRequires: git-core
Source0: dms-qml.tar.gz
BuildRequires: gzip
BuildRequires: golang >= 1.24
BuildRequires: make
BuildRequires: wget
BuildRequires: systemd-rpm-macros
# Core requirements
Requires: (quickshell-git or quickshell)
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli = %{epoch}:%{version}-%{release}
Requires: dms-cli = %{version}-%{release}
Requires: dgop
# Core utilities (Highly recommended for DMS functionality)
Recommends: cava
Recommends: cliphist
Recommends: danksearch
Recommends: matugen
Recommends: quickshell-git
Recommends: wl-clipboard
# Recommended system packages
Recommends: NetworkManager
Recommends: qt6-qtmultimedia
Suggests: qt6ct
%description
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
and optimized for the niri, hyprland, sway, and dwl (MangoWC) compositors. Features notifications,
and optimized for the niri and hyprland compositors. Features notifications,
app launcher, wallpaper customization, and fully customizable with plugins.
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
@@ -60,24 +51,14 @@ Command-line interface for DankMaterialShell configuration and management.
Provides native DBus bindings, NetworkManager integration, and system utilities.
%prep
{{{ git_repo_setup_macro }}}
%setup -q -c -n dms-qml
%build
# Build DMS CLI from source (core/subdirectory)
VERSION="%{version}"
COMMIT=$(echo "%{version}" | grep -oP '[a-f0-9]{7,}' | head -n1 || echo "unknown")
cd core
make dist VERSION="$VERSION" COMMIT="$COMMIT"
%install
# Install dms-cli binary (built from source)
case "%{_arch}" in
x86_64)
DMS_BINARY="dms-linux-amd64"
ARCH_SUFFIX="amd64"
;;
aarch64)
DMS_BINARY="dms-linux-arm64"
ARCH_SUFFIX="arm64"
;;
*)
echo "Unsupported architecture: %{_arch}"
@@ -85,27 +66,35 @@ case "%{_arch}" in
;;
esac
install -Dm755 core/bin/${DMS_BINARY} %{buildroot}%{_bindir}/dms
# Download dms-cli for target architecture
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
echo "Failed to download dms-cli for architecture %{_arch}"
exit 1
}
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
chmod +x %{_builddir}/dms-cli
%build
%install
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
# Shell completions
install -d %{buildroot}%{_datadir}/bash-completion/completions
install -d %{buildroot}%{_datadir}/zsh/site-functions
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
core/bin/${DMS_BINARY} completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
core/bin/${DMS_BINARY} completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
core/bin/${DMS_BINARY} completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
%{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
%{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
%{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
# Install systemd user service
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
install -Dm644 %{_builddir}/dms-qml/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
install -Dm644 %{_builddir}/dms-qml/assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
install -Dm644 %{_builddir}/dms-qml/assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
# Install shell files to shared data location
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms/
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
# Remove build files
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
@@ -119,8 +108,7 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
%files
%license LICENSE
%doc CONTRIBUTING.md
%doc quickshell/README.md
%doc README.md CONTRIBUTING.md
%{_datadir}/quickshell/dms/
%{_userunitdir}/dms.service
%{_datadir}/applications/dms-open.desktop
@@ -133,4 +121,6 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
%{_datadir}/fish/vendor_completions.d/dms.fish
%changelog
{{{ git_repo_changelog }}}
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
- Stable release VERSION_PLACEHOLDER
- Built from GitHub release

View File

@@ -6,7 +6,7 @@
...
}:
let
cfg = config.programs.dankMaterialShell;
cfg = config.programs.dank-material-shell;
in
{
packages = [

15
distro/nix/dms-rename.nix Normal file
View File

@@ -0,0 +1,15 @@
{ lib, ... }:
{
imports = [
(lib.mkRenamedOptionModule
[
"programs"
"dankMaterialShell"
]
[
"programs"
"dank-material-shell"
]
)
];
}

View File

@@ -7,7 +7,7 @@
}:
let
inherit (lib) types;
cfg = config.programs.dankMaterialShell.greeter;
cfg = config.programs.dank-material-shell.greeter;
inherit (config.services.greetd.settings.default_session) user;
@@ -44,19 +44,20 @@ in
{
imports =
let
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
msg = "The option 'programs.dank-material-shell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dank-material-shell.greeter.compositor.customConfig' instead.";
in
[
(lib.mkRemovedOptionModule [
"programs"
"dankMaterialShell"
"dank-material-shell"
"greeter"
"compositor"
"extraConfig"
] msg)
./dms-rename.nix
];
options.programs.dankMaterialShell.greeter = {
options.programs.dank-material-shell.greeter = {
enable = lib.mkEnableOption "DankMaterialShell greeter";
compositor.name = lib.mkOption {
type = types.enum [
@@ -177,7 +178,7 @@ in
mv dms-colors.json colors.json || :
chown ${user}: * || :
'';
programs.dankMaterialShell.greeter.configFiles = lib.mkIf (cfg.configHome != null) [
programs.dank-material-shell.greeter.configFiles = lib.mkIf (cfg.configHome != null) [
"${cfg.configHome}/.config/DankMaterialShell/settings.json"
"${cfg.configHome}/.local/state/DankMaterialShell/session.json"
"${cfg.configHome}/.cache/DankMaterialShell/dms-colors.json"

View File

@@ -6,7 +6,7 @@
...
}@args:
let
cfg = config.programs.dankMaterialShell;
cfg = config.programs.dank-material-shell;
jsonFormat = pkgs.formats.json { };
common = import ./common.nix {
inherit
@@ -22,16 +22,16 @@ in
(import ./options.nix args)
(lib.mkRemovedOptionModule [
"programs"
"dankMaterialShell"
"dank-material-shell"
"enableNightMode"
] "Night mode is now always available.")
(lib.mkRenamedOptionModule
[ "programs" "dankMaterialShell" "enableSystemd" ]
[ "programs" "dankMaterialShell" "systemd" "enable" ]
[ "programs" "dank-material-shell" "enableSystemd" ]
[ "programs" "dank-material-shell" "systemd" "enable" ]
)
];
options.programs.dankMaterialShell = with lib.types; {
options.programs.dank-material-shell = with lib.types; {
default = {
settings = lib.mkOption {
type = jsonFormat.type;

View File

@@ -4,10 +4,14 @@
...
}:
let
cfg = config.programs.dankMaterialShell;
cfg = config.programs.dank-material-shell;
in
{
options.programs.dankMaterialShell = {
imports = [
./dms-rename.nix
];
options.programs.dank-material-shell = {
niri = {
enableKeybinds = lib.mkEnableOption "DankMaterialShell niri keybinds";
enableSpawn = lib.mkEnableOption "DankMaterialShell niri spawn-at-startup";

View File

@@ -6,7 +6,7 @@
...
}@args:
let
cfg = config.programs.dankMaterialShell;
cfg = config.programs.dank-material-shell;
common = import ./common.nix {
inherit
config

View File

@@ -7,7 +7,7 @@ let
inherit (lib) types;
path = [
"programs"
"dankMaterialShell"
"dank-material-shell"
];
builtInRemovedMsg = "This is now built-in in DMS and doesn't need additional dependencies.";
@@ -20,16 +20,17 @@ in
(lib.mkRemovedOptionModule (
path ++ [ "enableSystemSound" ]
) "qtmultimedia is now included on dms-shell package.")
./dms-rename.nix
];
options.programs.dankMaterialShell = {
options.programs.dank-material-shell = {
enable = lib.mkEnableOption "DankMaterialShell";
systemd = {
enable = lib.mkEnableOption "DankMaterialShell systemd startup";
restartIfChanged = lib.mkOption {
type = types.bool;
default = true;
description = "Auto-restart dms.service when dankMaterialShell changes";
description = "Auto-restart dms.service when dank-material-shell changes";
};
};
enableSystemMonitoring = lib.mkOption {

View File

@@ -3,8 +3,8 @@
%global debug_package %{nil}
Name: dms
Version: 1.0.2
Release: 7%{?dist}
Version: 1.0.3
Release: 1%{?dist}
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
License: MIT
@@ -17,7 +17,7 @@ BuildRequires: gzip
BuildRequires: systemd-rpm-macros
# Core requirements
Requires: (quickshell-git or quickshell)
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dgop
@@ -105,6 +105,9 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
%changelog
* Mon Dec 16 2025 AvengeMedia <maintainer@avengemedia.com> - 1.0.3-1
- Update to stable v1.0.3 release
* Fri Dec 12 2025 AvengeMedia <maintainer@avengemedia.com> - 1.0.2-1
- Update to stable v1.0.2 release
- Bug fixes and improvements

View File

@@ -2,226 +2,121 @@
set -euo pipefail
# Build SRPM locally with correct tarball and upload to Copr
# Usage: ./create-upload-copr.sh VERSION [RELEASE]
# Example: ./create-upload-copr.sh 1.0.0 4
# Usage: ./copr-upload.sh [PACKAGE] [VERSION] [RELEASE]
# Examples:
# ./copr-upload.sh dms 1.0.3 1
# ./copr-upload.sh dms-greeter 1.0.3 1
PACKAGE="${1:-dms}"
VERSION="${2:-}"
RELEASE="${3:-1}"
VERSION="${1:-1.0.0}"
RELEASE="${2:-1}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
echo "Building DMS v${VERSION}-${RELEASE} SRPM for Copr..."
# Determine Copr project based on package
if [ "$PACKAGE" = "dms" ]; then
COPR_PROJECT="avengemedia/dms"
elif [ "$PACKAGE" = "dms-greeter" ]; then
COPR_PROJECT="avengemedia/danklinux"
else
echo "❌ Unknown package: $PACKAGE"
echo "Supported packages: dms, dms-greeter"
exit 1
fi
# Get version from latest release if not provided
if [ -z "$VERSION" ]; then
echo "📦 Determining latest version..."
VERSION=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | jq -r '.tag_name' | sed 's/^v//')
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "❌ Failed to determine version. Please specify manually."
exit 1
fi
echo "✅ Using latest version: $VERSION"
fi
echo "Building ${PACKAGE} v${VERSION}-${RELEASE} SRPM for Copr..."
# Setup build directories
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
cd ~/rpmbuild/SOURCES
# Create the corrected QML tarball locally
echo "Creating QML tarball with assets..."
TEMP_DIR=$(mktemp -d)
cd "$REPO_ROOT"
# Copy quickshell contents to temp
cp -r quickshell/* "$TEMP_DIR/"
# Copy root LICENSE and CONTRIBUTING.md
cp LICENSE CONTRIBUTING.md "$TEMP_DIR/"
# Copy root assets directory (this is what was missing!)
cp -r assets "$TEMP_DIR/"
# Create tarball
cd "$TEMP_DIR"
tar --exclude='.git' \
--exclude='.github' \
--exclude='*.tar.gz' \
-czf ~/rpmbuild/SOURCES/dms-qml.tar.gz .
cd ~/rpmbuild/SOURCES
echo "Created dms-qml.tar.gz with md5sum: $(md5sum dms-qml.tar.gz | awk '{print $1}')"
rm -rf "$TEMP_DIR"
# Generate spec file
echo "Generating spec file..."
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
cat >~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
# Spec for DMS stable releases - Built locally
%global debug_package %{nil}
%global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
Name: dms
Version: %{version}
Release: RELEASE_PLACEHOLDER%{?dist}
Summary: %{pkg_summary}
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
Source0: dms-qml.tar.gz
BuildRequires: gzip
BuildRequires: wget
BuildRequires: systemd-rpm-macros
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli = %{version}-%{release}
Requires: dgop
Recommends: cava
Recommends: cliphist
Recommends: danksearch
Recommends: matugen
Recommends: wl-clipboard
Recommends: NetworkManager
Recommends: qt6-qtmultimedia
Suggests: qt6ct
%description
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
and optimized for the niri and hyprland compositors. Features notifications,
app launcher, wallpaper customization, and fully customizable with plugins.
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
process monitoring, notification center, clipboard history, dock, control center,
lock screen, and comprehensive plugin system.
%package -n dms-cli
Summary: DankMaterialShell CLI tool
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
%description -n dms-cli
Command-line interface for DankMaterialShell configuration and management.
Provides native DBus bindings, NetworkManager integration, and system utilities.
%prep
%setup -q -c -n dms-qml
# Download architecture-specific binaries during build
case "%{_arch}" in
x86_64)
ARCH_SUFFIX="amd64"
;;
aarch64)
ARCH_SUFFIX="arm64"
;;
*)
echo "Unsupported architecture: %{_arch}"
exit 1
;;
esac
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v%{version}/dms-distropkg-${ARCH_SUFFIX}.gz" || {
echo "Failed to download dms-cli for architecture %{_arch}"
exit 1
}
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
chmod +x %{_builddir}/dms-cli
%build
%install
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
install -d %{buildroot}%{_datadir}/bash-completion/completions
install -d %{buildroot}%{_datadir}/zsh/site-functions
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
%{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
%{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
%{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
%posttrans
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
# Download source tarball from GitHub releases
echo "📦 Downloading source tarball for v${VERSION}..."
if [ ! -f ~/rpmbuild/SOURCES/dms-qml.tar.gz ]; then
wget -O ~/rpmbuild/SOURCES/dms-qml.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
echo "❌ Failed to download dms-qml.tar.gz for v${VERSION}"
exit 1
}
echo "✅ Source tarball downloaded"
else
echo "✅ Source tarball already exists"
fi
# Signal running DMS instances to reload
pkill -USR1 -x dms >/dev/null 2>&1 || :
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_datadir}/quickshell/dms/
%{_userunitdir}/dms.service
%{_datadir}/applications/dms-open.desktop
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
# Copy and prepare spec file
echo "📝 Preparing spec file..."
SPEC_FILE="$REPO_ROOT/distro/fedora/${PACKAGE}.spec"
if [ ! -f "$SPEC_FILE" ]; then
echo "❌ Spec file not found: $SPEC_FILE"
exit 1
fi
%files -n dms-cli
%{_bindir}/dms
%{_datadir}/bash-completion/completions/dms
%{_datadir}/zsh/site-functions/_dms
%{_datadir}/fish/vendor_completions.d/dms.fish
cp "$SPEC_FILE" ~/rpmbuild/SPECS/"${PACKAGE}".spec
%changelog
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
- Stable release VERSION_PLACEHOLDER
- Built locally with corrected tarball
SPECEOF
# Replace placeholders in spec file
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
echo "✅ Spec file prepared for ${PACKAGE} v${VERSION}-${RELEASE}"
# Build SRPM
echo "Building SRPM..."
echo "🔨 Building SRPM..."
cd ~/rpmbuild/SPECS
rpmbuild -bs dms.spec
rpmbuild -bs "${PACKAGE}".spec
SRPM=$(ls ~/rpmbuild/SRPMS/dms-"${VERSION}"-*.src.rpm | tail -n 1)
SRPM=$(ls ~/rpmbuild/SRPMS/"${PACKAGE}"-"${VERSION}"-*.src.rpm | tail -n 1)
if [ ! -f "$SRPM" ]; then
echo "Error: SRPM not found!"
echo "Error: SRPM not found!"
echo "Expected pattern: ${PACKAGE}-${VERSION}-*.src.rpm"
ls -la ~/rpmbuild/SRPMS/ || true
exit 1
fi
echo "SRPM built successfully: $SRPM"
echo "SRPM built successfully: $SRPM"
# Check if copr-cli is installed
if ! command -v copr-cli &>/dev/null; then
echo ""
echo "copr-cli is not installed. Install it with:"
echo "⚠️ copr-cli is not installed. Install it with:"
echo " pip install copr-cli"
echo ""
echo "Then configure it with your Copr API token in ~/.config/copr"
echo ""
echo "SRPM is ready at: $SRPM"
echo "Upload manually with: copr-cli build avengemedia/dms $SRPM"
echo "Upload manually with: copr-cli build $COPR_PROJECT $SRPM"
exit 0
fi
# Upload to Copr
echo ""
echo "Uploading to Copr..."
if copr-cli build avengemedia/dms "$SRPM" --nowait; then
echo "🚀 Uploading to Copr..."
if copr-cli build "$COPR_PROJECT" "$SRPM" --nowait; then
echo ""
echo "Build submitted successfully! Check status at:"
echo "https://copr.fedorainfracloud.org/coprs/avengemedia/dms/builds/"
echo "Build submitted successfully!"
echo "📊 Check status at:"
echo " https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/builds/"
echo ""
echo "📦 SRPM location: $SRPM"
else
echo ""
echo "Copr upload failed. You can manually upload the SRPM:"
echo " copr-cli build avengemedia/dms $SRPM"
echo "Copr upload failed. You can manually upload the SRPM:"
echo " copr-cli build $COPR_PROJECT $SRPM"
echo ""
echo "Or upload via web interface:"
echo " https://copr.fedorainfracloud.org/coprs/avengemedia/dms/builds/"
echo " https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/builds/"
echo ""
echo "SRPM location: $SRPM"
exit 1

View File

@@ -7,8 +7,8 @@
# ./distro/scripts/obs-upload.sh dms "Update to v1.0.2"
# ./distro/scripts/obs-upload.sh debian dms
# ./distro/scripts/obs-upload.sh opensuse dms-git
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with ppa2 suffix
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with ppa2 suffix (flag syntax)
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with db2 suffix
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with db2 suffix (flag syntax)
set -e
@@ -126,8 +126,8 @@ check_obs_version_exists() {
OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs)
# Commit hash check for -git packages
if [[ "$CHECK_MODE" == "commit" ]] && [[ "$PACKAGE" == *"-git" ]]; then
OBS_COMMIT=$(echo "$OBS_VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
NEW_COMMIT=$(echo "$VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
OBS_COMMIT=$(echo "$OBS_VERSION" | grep -oP '\.([a-f0-9]{8})(db[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
NEW_COMMIT=$(echo "$VERSION" | grep -oP '\.([a-f0-9]{8})(db[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
if [[ -n "$OBS_COMMIT" && -n "$NEW_COMMIT" && "$OBS_COMMIT" == "$NEW_COMMIT" ]]; then
echo "⚠️ Commit $NEW_COMMIT already exists in OBS (current version: $OBS_VERSION)"
@@ -279,7 +279,8 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
# Apply rebuild suffix if specified (must happen before API check)
if [[ -n "$REBUILD_RELEASE" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
CHANGELOG_VERSION="${CHANGELOG_VERSION}ppa${REBUILD_RELEASE}"
BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/db[0-9]*$//')
CHANGELOG_VERSION="${BASE_VERSION}db${REBUILD_RELEASE}"
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
fi
@@ -307,12 +308,16 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
else
# Rebuild number specified - check if this exact version already exists (exact mode)
if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION" "exact"; then
echo "==> Error: Version $CHANGELOG_VERSION already exists in OBS"
echo " This exact version (including ppa${REBUILD_RELEASE}) is already uploaded."
echo " To rebuild with a different release number, try incrementing:"
echo "==> Version $CHANGELOG_VERSION already exists in OBS"
echo " This exact version (including db${REBUILD_RELEASE}) is already uploaded."
echo " Skipping upload - nothing to do."
echo ""
echo " 💡 To rebuild with a different release number, try incrementing:"
NEXT_NUM=$((REBUILD_RELEASE + 1))
echo " ./distro/scripts/obs-upload.sh $PACKAGE $NEXT_NUM"
exit 1
echo " REBUILD_RELEASE=$NEXT_NUM"
echo ""
echo "✓ Exiting gracefully (no changes needed)"
exit 0
fi
fi
fi
@@ -511,7 +516,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
if [[ -n "$URL_PROTOCOL" && -n "$URL_HOST" && -n "$URL_PATH" ]]; then
SOURCE_URL="${URL_PROTOCOL}://${URL_HOST}${URL_PATH}"
echo " Downloading source from: $SOURCE_URL"
echo "==> Downloading source from: $SOURCE_URL"
if wget -q -O "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null ||
curl -L -f -s -o "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null; then
@@ -534,9 +539,17 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
fi
SOURCE_DIR=$(cd "$SOURCE_DIR" && pwd)
cd "$REPO_ROOT"
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
echo "ERROR: Failed to return to REPO_ROOT. Expected: $REPO_ROOT, Got: $(pwd)"
exit 1
fi
else
echo "Error: Failed to download source from $SOURCE_URL"
echo "Tried both wget and curl. Please check the URL and network connectivity."
echo "ERROR: Failed to download source from $SOURCE_URL"
echo "Attempted both wget and curl"
echo "Please check:"
echo " 1. URL is accessible: $SOURCE_URL"
echo " 2. _service file has correct version"
echo " 3. GitHub releases are available"
exit 1
fi
fi
@@ -553,7 +566,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
exit 1
fi
echo " Found source directory: $SOURCE_DIR"
echo "==> Found source directory: $SOURCE_DIR"
# Vendor Go dependencies for dms-git
if [[ "$PACKAGE" == "dms-git" ]] && [[ -d "$SOURCE_DIR/core" ]]; then
@@ -712,6 +725,10 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
TARBALL_BASE=$(basename "$SOURCE_DIR")
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
cd "$REPO_ROOT"
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
echo "ERROR: Failed to return to REPO_ROOT after tarball creation"
exit 1
fi
if [[ "$PACKAGE" == "dms" ]]; then
TARBALL_DIR=$(tar -tzf "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null | head -1 | cut -d'/' -f1)
@@ -723,6 +740,10 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
rm -f "$WORK_DIR/$COMBINED_TARBALL"
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
cd "$REPO_ROOT"
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
echo "ERROR: Failed to return to REPO_ROOT after tarball recreation"
exit 1
fi
fi
fi
@@ -796,23 +817,29 @@ EOF
fi
fi
cd "$WORK_DIR"
echo "==> Ensuring we're in the OSC working directory"
cd "$WORK_DIR" || {
echo "ERROR: Cannot cd to WORK_DIR: $WORK_DIR"
echo "DEBUG: Current directory: $(pwd)"
echo "DEBUG: WORK_DIR exists: $(test -d "$WORK_DIR" && echo "yes" || echo "no")"
exit 1
}
echo "DEBUG: Successfully entered WORK_DIR: $(pwd)"
# Server-side cleanup via API
echo "==> Cleaning old tarballs from OBS server (prevents downloading 100+ old versions)"
OBS_FILES=$(osc api "/source/$OBS_PROJECT/$PACKAGE" 2>/dev/null || echo "")
if [[ -n "$OBS_FILES" ]]; then
DELETED_COUNT=0
KEEP_PATTERN=""
KEEP_CURRENT=""
if [[ -n "$CHANGELOG_VERSION" ]]; then
BASE_KEEP_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
KEEP_PATTERN="${PACKAGE}_${BASE_KEEP_VERSION}"
echo " Keeping tarballs matching: ${KEEP_PATTERN}*"
KEEP_CURRENT="${PACKAGE}_${CHANGELOG_VERSION}.tar.gz"
echo " Keeping only current version: ${KEEP_CURRENT}"
fi
for old_file in $(echo "$OBS_FILES" | grep -oP '(?<=name=")[^"]*\.(tar\.gz|tar\.xz|tar\.bz2)(?=")' || true); do
if [[ -n "$KEEP_PATTERN" ]] && [[ "$old_file" == ${KEEP_PATTERN}* ]]; then
echo " - Keeping current version: $old_file"
if [[ "$old_file" == "$KEEP_CURRENT" ]]; then
echo " - Keeping: $old_file"
continue
fi
@@ -835,14 +862,11 @@ else
echo " ⚠️ Could not fetch file list from server, skipping cleanup"
fi
# Fallback update with --server-side-source-service-files flag only syncs metadata (spec, dsc, _service)
# Update working copy to latest revision (without expanding service files to avoid revision conflicts)
echo "==> Updating working copy"
if ! osc up --server-side-source-service-files 2>/dev/null; then
echo " Note: Using regular update (--server-side-source-service-files not supported)"
if ! osc up; then
echo "Error: Failed to update working copy"
exit 1
fi
if ! osc up 2>/dev/null; then
echo "Error: Failed to update working copy"
exit 1
fi
# Ensure we're in WORK_DIR and it exists
@@ -882,6 +906,15 @@ elif [[ "$UPLOAD_OPENSUSE" == true ]]; then
fi
echo ""
if [[ "$(pwd)" != "$WORK_DIR" ]]; then
echo "ERROR: Lost directory context. Expected: $WORK_DIR, Got: $(pwd)"
cd "$WORK_DIR" || {
echo "FATAL: Cannot recover - unable to cd to WORK_DIR"
exit 1
}
echo "WARNING: Recovered directory context"
fi
osc addremove 2>&1 | grep -v "Git SCM package" || true
SOURCE_TARBALL="${PACKAGE}-source.tar.gz"
@@ -908,7 +941,7 @@ if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
else
echo "==> Committing to OBS"
set +e
osc commit -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
osc commit --skip-local-service-run -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
COMMIT_EXIT=${PIPESTATUS[0]}
set -e
if [[ $COMMIT_EXIT -ne 0 ]]; then

View File

@@ -1,5 +0,0 @@
danksearch (0.0.7ppa3) questing; urgency=medium
* Rebuild for packaging fixes (ppa3)
-- Avenge Media <AvengeMedia.US@gmail.com> Fri, 21 Nov 2025 14:19:58 -0500

View File

@@ -1,24 +0,0 @@
Source: danksearch
Section: utils
Priority: optional
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
Build-Depends: debhelper-compat (= 13)
Standards-Version: 4.6.2
Homepage: https://github.com/AvengeMedia/danksearch
Vcs-Browser: https://github.com/AvengeMedia/danksearch
Vcs-Git: https://github.com/AvengeMedia/danksearch.git
Package: danksearch
Architecture: amd64 arm64
Depends: ${misc:Depends}
Description: Fast file search utility for DMS
DankSearch is a fast file search utility designed for DankMaterialShell.
It provides efficient file and content search capabilities with minimal
dependencies. This package contains the pre-built binary from the official
GitHub release.
.
Features include:
- Fast file searching
- Lightweight and efficient
- Designed for DMS integration
- Minimal resource usage

View File

@@ -1,24 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: danksearch
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
Source: https://github.com/AvengeMedia/danksearch
Files: *
Copyright: 2025 Avenge Media LLC
License: GPL-3.0-only
License: GPL-3.0-only
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as
published by the Free Software Foundation.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".

View File

@@ -1,33 +0,0 @@
#!/usr/bin/make -f
export DH_VERBOSE = 1
# Detect architecture for selecting correct binary
DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
# Map Debian arch to binary filename
ifeq ($(DEB_HOST_ARCH),amd64)
BINARY_FILE := dsearch-amd64
else ifeq ($(DEB_HOST_ARCH),arm64)
BINARY_FILE := dsearch-arm64
else
$(error Unsupported architecture: $(DEB_HOST_ARCH))
endif
%:
dh $@
override_dh_auto_build:
# Binary is already included in source package (native format)
# Downloaded by build-source.sh before upload
# Just verify it exists and is executable
test -f $(BINARY_FILE) || (echo "ERROR: $(BINARY_FILE) not found!" && exit 1)
chmod +x $(BINARY_FILE)
override_dh_auto_install:
# Install binary as danksearch
install -Dm755 $(BINARY_FILE) debian/danksearch/usr/bin/danksearch
override_dh_auto_clean:
# Don't delete binaries - they're part of the source package (native format)
dh_auto_clean

View File

@@ -1 +0,0 @@
3.0 (native)

View File

@@ -1,9 +0,0 @@
dgop (0.1.11ppa2) questing; urgency=medium
* Rebuild for Questing (25.10) - Ubuntu 25.10+ only
* Stateless CPU/GPU monitoring tool
* Support for NVIDIA and AMD GPUs
* JSON output for integration
* Pre-built binary package for amd64 and arm64
-- Avenge Media <AvengeMedia.US@gmail.com> Sun, 16 Nov 2025 22:50:00 -0500

View File

@@ -1,27 +0,0 @@
Source: dgop
Section: utils
Priority: optional
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
Build-Depends: debhelper-compat (= 13),
wget,
gzip
Standards-Version: 4.6.2
Homepage: https://github.com/AvengeMedia/dgop
Vcs-Browser: https://github.com/AvengeMedia/dgop
Vcs-Git: https://github.com/AvengeMedia/dgop.git
Package: dgop
Architecture: amd64 arm64
Depends: ${misc:Depends}
Description: Stateless CPU/GPU monitor for DankMaterialShell
DGOP is a stateless system monitoring tool that provides CPU, GPU,
memory, and network statistics. Designed for integration with
DankMaterialShell but can be used standalone.
.
Features:
- CPU usage monitoring
- GPU usage and temperature (NVIDIA, AMD)
- Memory and swap statistics
- Network traffic monitoring
- Zero-state design (no background processes)
- JSON output for easy integration

View File

@@ -1,27 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: dgop
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
Source: https://github.com/AvengeMedia/dgop
Files: *
Copyright: 2025 Avenge Media LLC
License: MIT
License: MIT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,38 +0,0 @@
#!/usr/bin/make -f
export DH_VERBOSE = 1
# Extract version from debian/changelog
DEB_VERSION := $(shell dpkg-parsechangelog -S Version)
# Get upstream version (strip -1ppa1 suffix)
UPSTREAM_VERSION := $(shell echo $(DEB_VERSION) | sed 's/-[^-]*$$//')
# Detect architecture for downloading correct binary
DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
# Map Debian arch to GitHub release arch names
ifeq ($(DEB_HOST_ARCH),amd64)
GITHUB_ARCH := amd64
else ifeq ($(DEB_HOST_ARCH),arm64)
GITHUB_ARCH := arm64
else
$(error Unsupported architecture: $(DEB_HOST_ARCH))
endif
%:
dh $@
override_dh_auto_build:
# Binary is already included in source package (native format)
# Just verify it exists and is executable
test -f dgop || (echo "ERROR: dgop binary not found!" && exit 1)
chmod +x dgop
override_dh_auto_install:
# Install binary
install -Dm755 dgop debian/dgop/usr/bin/dgop
override_dh_auto_clean:
# Don't delete dgop binary - it's part of the source package (native format)
rm -f dgop.gz
dh_auto_clean

View File

@@ -1 +0,0 @@
3.0 (native)

View File

@@ -11,7 +11,7 @@ Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
Package: dms
Architecture: amd64
Depends: ${misc:Depends},
quickshell-git | quickshell,
quickshell | quickshell-git,
accountsservice,
cava,
cliphist,

View File

@@ -0,0 +1,46 @@
{
"dark": {
"name": "Gruvbox Material Dark Hard",
"surface": "#202020",
"surfaceText": "#fbf1c7",
"surfaceVariant": "#2a2827",
"surfaceVariantText": "#ebdbb2",
"background": "#2a2827",
"backgroundText": "#ddc7a1",
"outline": "#5a524c",
"surfaceContainer": "#2a2827",
"surfaceContainerHigh": "#504945",
"surfaceContainerHighest": "#5a524c",
"primary": "#a9b665",
"secondary": "#d8a657",
"primaryText": "#292828",
"primaryContainer": "#798247",
"surfaceTint": "#7daea3",
"error": "#ea6962",
"warning": "#d3869b",
"info": "#89b482",
"matugen_type": "scheme-tonal-spot"
},
"light": {
"name": "Gruvbox Material Light Hard",
"surface": "#f9f5d7",
"surfaceText": "#282828",
"surfaceVariant": "#fbf1c7",
"surfaceVariantText": "#3c3836",
"background": "#fbf1c7",
"backgroundText": "#654735",
"outline": "#a89984",
"surfaceContainer": "#fbf1c7",
"surfaceContainerHigh": "#e0cfa9",
"surfaceContainerHighest": "#a89984",
"primary": "#6c782e",
"secondary": "#b47109",
"primaryText": "#292828",
"primaryContainer": "#566026",
"surfaceTint": "#45707a",
"error": "#c14a4a",
"warning": "#945e80",
"info": "#4c7a5d",
"matugen_type": "scheme-tonal-spot"
}
}

View File

@@ -0,0 +1,46 @@
{
"dark": {
"name": "Gruvbox Material Dark Medium",
"surface": "#292828",
"surfaceText": "#fbf1c7",
"surfaceVariant": "#32302f",
"surfaceVariantText": "#ebdbb2",
"background": "#32302f",
"backgroundText": "#ddc7a1",
"outline": "#665c54",
"surfaceContainer": "#32302f",
"surfaceContainerHigh": "#504945",
"surfaceContainerHighest": "#665c54",
"primary": "#a9b665",
"secondary": "#d8a657",
"primaryText": "#292828",
"primaryContainer": "#798247",
"surfaceTint": "#7daea3",
"error": "#ea6962",
"warning": "#d3869b",
"info": "#89b482",
"matugen_type": "scheme-tonal-spot"
},
"light": {
"name": "Gruvbox Material Light Medium",
"surface": "#fbf1c7",
"surfaceText": "#282828",
"surfaceVariant": "#f2e5bc",
"surfaceVariantText": "#3c3836",
"background": "#f2e5bc",
"backgroundText": "#654735",
"outline": "#bdae93",
"surfaceContainer": "#f2e5bc",
"surfaceContainerHigh": "#d5c4a1",
"surfaceContainerHighest": "#bdae93",
"primary": "#6c782e",
"secondary": "#b47109",
"primaryText": "#292828",
"primaryContainer": "#535d23",
"surfaceTint": "#45707a",
"error": "#c14a4a",
"warning": "#945e80",
"info": "#6c782e",
"matugen_type": "scheme-tonal-spot"
}
}

View File

@@ -0,0 +1,46 @@
{
"dark": {
"name": "Gruvbox Material Dark Soft",
"surface": "#32302f",
"surfaceText": "#fbf1c7",
"surfaceVariant": "#3c3836",
"surfaceVariantText": "#ebdbb2",
"background": "#3c3836",
"backgroundText": "#ddc7a1",
"outline": "#7c6f64",
"surfaceContainer": "#3c3836",
"surfaceContainerHigh": "#5a524c",
"surfaceContainerHighest": "#7c6f64",
"primary": "#a9b665",
"secondary": "#d8a657",
"primaryText": "#292828",
"primaryContainer": "#798247",
"surfaceTint": "#7daea3",
"error": "#ea6962",
"warning": "#d3869b",
"info": "#89b482",
"matugen_type": "scheme-tonal-spot"
},
"light": {
"name": "Gruvbox Material Light Soft",
"surface": "#f2e5bc",
"surfaceText": "#282828",
"surfaceVariant": "#ebdbb2",
"surfaceVariantText": "#3c3836",
"background": "#ebdbb2",
"backgroundText": "#654735",
"outline": "#a89984",
"surfaceContainer": "#ebdbb2",
"surfaceContainerHigh": "#c9b99a",
"surfaceContainerHighest": "#a89984",
"primary": "#6c782e",
"secondary": "#b47109",
"primaryText": "#292828",
"primaryContainer": "#535d23",
"surfaceTint": "#45707a",
"error": "#c14a4a",
"warning": "#945e80",
"info": "#6c782e",
"matugen_type": "scheme-tonal-spot"
}
}

View File

@@ -149,14 +149,24 @@
}
);
homeModules.dankMaterialShell.default = mkModuleWithDmsPkgs ./distro/nix/home.nix;
homeModules.dank-material-shell = mkModuleWithDmsPkgs ./distro/nix/home.nix;
homeModules.dankMaterialShell.niri = import ./distro/nix/niri.nix;
homeModules.default = self.homeModules.dank-material-shell;
nixosModules.dankMaterialShell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix;
homeModules.niri = import ./distro/nix/niri.nix;
homeModules.dankMaterialShell.default = builtins.warn "dank-material-shell: flake output `homeModules.dankMaterialShell.default` has been renamed to `homeModules.dank-material-shell`" self.homeModules.dank-material-shell;
homeModules.dankMaterialShell.niri = builtins.warn "dank-material-shell: flake output `homeModules.dankMaterialShell.niri` has been renamed to `homeModules.niri`" self.homeModules.niri;
nixosModules.dank-material-shell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix;
nixosModules.default = self.nixosModules.dank-material-shell;
nixosModules.greeter = mkModuleWithDmsPkgs ./distro/nix/greeter.nix;
nixosModules.dankMaterialShell = builtins.warn "dank-material-shell: flake output `nixosModules.dankMaterialShell` has been renamed to `nixosModules.dank-material-shell`" self.nixosModules.dank-material-shell;
devShells = forEachSystem (
system: pkgs:
let

View File

@@ -16,6 +16,9 @@ Singleton {
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
}
readonly property var _rtlLanguages: ["ar", "he", "iw", "fa", "ur", "ps", "sd", "dv", "yi", "ku"]
readonly property bool isRtl: _rtlLanguages.includes(_lang)
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
property string currentLocale: "en"

View File

@@ -8,6 +8,9 @@ Singleton {
id: modalManager
signal closeAllModalsExcept(var excludedModal)
signal modalChanged
property var currentModalsByScreen: ({})
function openModal(modal) {
if (!modal.allowStacking) {
@@ -17,5 +20,17 @@ Singleton {
PopoutManager.closeAllPopouts();
}
TrayMenuManager.closeAllMenus();
const screenName = modal.effectiveScreen?.name ?? "unknown";
currentModalsByScreen[screenName] = modal;
modalChanged();
}
function closeModal(modal) {
const screenName = modal.effectiveScreen?.name ?? "unknown";
if (currentModalsByScreen[screenName] === modal) {
delete currentModalsByScreen[screenName];
modalChanged();
}
}
}

View File

@@ -1,5 +1,5 @@
pragma Singleton
pragma ComponentBehavior: Bound
pragma ComponentBehavior
import QtCore
import QtQuick
@@ -282,6 +282,7 @@ Singleton {
property bool matugenTemplateGhostty: true
property bool matugenTemplateKitty: true
property bool matugenTemplateFoot: true
property bool matugenTemplateNeovim: true
property bool matugenTemplateAlacritty: true
property bool matugenTemplateWezterm: true
property bool matugenTemplateDgop: true
@@ -360,50 +361,190 @@ Singleton {
property string displayNameMode: "system"
property var screenPreferences: ({})
property var showOnLastDisplay: ({})
property var niriOutputSettings: ({})
property var hyprlandOutputSettings: ({})
property var barConfigs: [
{
id: "default",
name: "Main Bar",
enabled: true,
position: 0,
screenPreferences: ["all"],
showOnLastDisplay: true,
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
centerWidgets: ["music", "clock", "weather"],
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
spacing: 4,
innerPadding: 4,
bottomGap: 0,
transparency: 1.0,
widgetTransparency: 1.0,
squareCorners: false,
noBackground: false,
gothCornersEnabled: false,
gothCornerRadiusOverride: false,
gothCornerRadiusValue: 12,
borderEnabled: false,
borderColor: "surfaceText",
borderOpacity: 1.0,
borderThickness: 1,
widgetOutlineEnabled: false,
widgetOutlineColor: "primary",
widgetOutlineOpacity: 1.0,
widgetOutlineThickness: 1,
fontScale: 1.0,
autoHide: false,
autoHideDelay: 250,
openOnOverview: false,
visible: true,
popupGapsAuto: true,
popupGapsManual: 4,
maximizeDetection: true,
scrollEnabled: true,
scrollXBehavior: "column",
scrollYBehavior: "workspace"
"id": "default",
"name": "Main Bar",
"enabled": true,
"position": 0,
"screenPreferences": ["all"],
"showOnLastDisplay": true,
"leftWidgets": ["launcherButton", "workspaceSwitcher", "focusedWindow"],
"centerWidgets": ["music", "clock", "weather"],
"rightWidgets": ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
"spacing": 4,
"innerPadding": 4,
"bottomGap": 0,
"transparency": 1.0,
"widgetTransparency": 1.0,
"squareCorners": false,
"noBackground": false,
"gothCornersEnabled": false,
"gothCornerRadiusOverride": false,
"gothCornerRadiusValue": 12,
"borderEnabled": false,
"borderColor": "surfaceText",
"borderOpacity": 1.0,
"borderThickness": 1,
"widgetOutlineEnabled": false,
"widgetOutlineColor": "primary",
"widgetOutlineOpacity": 1.0,
"widgetOutlineThickness": 1,
"fontScale": 1.0,
"autoHide": false,
"autoHideDelay": 250,
"openOnOverview": false,
"visible": true,
"popupGapsAuto": true,
"popupGapsManual": 4,
"maximizeDetection": true,
"scrollEnabled": true,
"scrollXBehavior": "column",
"scrollYBehavior": "workspace"
}
]
property bool desktopClockEnabled: false
property string desktopClockStyle: "analog"
property real desktopClockTransparency: 0.8
property string desktopClockColorMode: "primary"
property color desktopClockCustomColor: "#ffffff"
property bool desktopClockShowDate: true
property bool desktopClockShowAnalogNumbers: false
property bool desktopClockShowAnalogSeconds: true
property real desktopClockX: -1
property real desktopClockY: -1
property real desktopClockWidth: 280
property real desktopClockHeight: 180
property var desktopClockDisplayPreferences: ["all"]
property bool systemMonitorEnabled: false
property bool systemMonitorShowHeader: true
property real systemMonitorTransparency: 0.8
property string systemMonitorColorMode: "primary"
property color systemMonitorCustomColor: "#ffffff"
property bool systemMonitorShowCpu: true
property bool systemMonitorShowCpuGraph: true
property bool systemMonitorShowCpuTemp: true
property bool systemMonitorShowGpuTemp: false
property string systemMonitorGpuPciId: ""
property bool systemMonitorShowMemory: true
property bool systemMonitorShowMemoryGraph: true
property bool systemMonitorShowNetwork: true
property bool systemMonitorShowNetworkGraph: true
property bool systemMonitorShowDisk: true
property bool systemMonitorShowTopProcesses: false
property int systemMonitorTopProcessCount: 3
property string systemMonitorTopProcessSortBy: "cpu"
property string systemMonitorLayoutMode: "auto"
property int systemMonitorGraphInterval: 60
property real systemMonitorX: -1
property real systemMonitorY: -1
property real systemMonitorWidth: 320
property real systemMonitorHeight: 480
property var systemMonitorDisplayPreferences: ["all"]
property var systemMonitorVariants: []
property var desktopWidgetPositions: ({})
property var desktopWidgetGridSettings: ({})
function getDesktopWidgetGridSetting(screenKey, property, defaultValue) {
const val = desktopWidgetGridSettings?.[screenKey]?.[property];
return val !== undefined ? val : defaultValue;
}
function setDesktopWidgetGridSetting(screenKey, property, value) {
const allSettings = JSON.parse(JSON.stringify(desktopWidgetGridSettings || {}));
if (!allSettings[screenKey])
allSettings[screenKey] = {};
allSettings[screenKey][property] = value;
desktopWidgetGridSettings = allSettings;
saveSettings();
}
function getDesktopWidgetPosition(pluginId, screenKey, property, defaultValue) {
const pos = desktopWidgetPositions?.[pluginId]?.[screenKey]?.[property];
return pos !== undefined ? pos : defaultValue;
}
function updateDesktopWidgetPosition(pluginId, screenKey, updates) {
const allPositions = JSON.parse(JSON.stringify(desktopWidgetPositions || {}));
if (!allPositions[pluginId])
allPositions[pluginId] = {};
allPositions[pluginId][screenKey] = Object.assign({}, allPositions[pluginId][screenKey] || {}, updates);
desktopWidgetPositions = allPositions;
saveSettings();
}
function getSystemMonitorVariants() {
return systemMonitorVariants || [];
}
function createSystemMonitorVariant(name, config) {
const id = "sysmon_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
const variant = {
id: id,
name: name,
config: config || getDefaultSystemMonitorConfig()
};
const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
variants.push(variant);
systemMonitorVariants = variants;
saveSettings();
return variant;
}
function updateSystemMonitorVariant(variantId, updates) {
const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
const idx = variants.findIndex(v => v.id === variantId);
if (idx === -1)
return;
Object.assign(variants[idx], updates);
systemMonitorVariants = variants;
saveSettings();
}
function removeSystemMonitorVariant(variantId) {
const variants = (systemMonitorVariants || []).filter(v => v.id !== variantId);
systemMonitorVariants = variants;
saveSettings();
}
function getSystemMonitorVariant(variantId) {
return (systemMonitorVariants || []).find(v => v.id === variantId) || null;
}
function getDefaultSystemMonitorConfig() {
return {
showHeader: true,
transparency: 0.8,
colorMode: "primary",
customColor: "#ffffff",
showCpu: true,
showCpuGraph: true,
showCpuTemp: true,
showGpuTemp: false,
gpuPciId: "",
showMemory: true,
showMemoryGraph: true,
showNetwork: true,
showNetworkGraph: true,
showDisk: true,
showTopProcesses: false,
topProcessCount: 3,
topProcessSortBy: "cpu",
layoutMode: "auto",
graphInterval: 60,
x: -1,
y: -1,
width: 320,
height: 480,
displayPreferences: ["all"]
};
}
signal forceDankBarLayoutRefresh
signal forceDockLayoutRefresh
signal widgetDataChanged
@@ -458,25 +599,25 @@ Singleton {
const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
settings_file="$config_dir/settings.ini"
if [ -f "$settings_file" ]; then
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
settings_file="$config_dir/settings.ini"
if [ -f "$settings_file" ]; then
if grep -q "^gtk-icon-theme-name=" "$settings_file"; then
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
else
if grep -q "\\[Settings\\]" "$settings_file"; then
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
else
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
fi
if grep -q "\\[Settings\\]" "$settings_file"; then
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
else
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
fi
else
fi
else
echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
fi
done
fi
done
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
pkill -HUP -f 'gtk' 2>/dev/null || true`;
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
pkill -HUP -f 'gtk' 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", configScript]);
}
@@ -489,36 +630,36 @@ pkill -HUP -f 'gtk' 2>/dev/null || true`;
const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
update_qt_icon_theme() {
local config_file="$1"
local theme_name="$2"
if [ -f "$config_file" ]; then
if grep -q "^\\[Appearance\\]" "$config_file"; then
if grep -q "^icon_theme=" "$config_file"; then
update_qt_icon_theme() {
local config_file="$1"
local theme_name="$2"
if [ -f "$config_file" ]; then
if grep -q "^\\[Appearance\\]" "$config_file"; then
if grep -q "^icon_theme=" "$config_file"; then
sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
else
else
sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
fi
else
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
fi
else
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
fi
}
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
fi
else
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
fi
else
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
fi
}
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", script]);
}
readonly property var _hooks: ({
applyStoredTheme: applyStoredTheme,
regenSystemThemes: regenSystemThemes,
updateNiriLayout: updateNiriLayout,
applyStoredIconTheme: applyStoredIconTheme,
updateBarConfigs: updateBarConfigs
"applyStoredTheme": applyStoredTheme,
"regenSystemThemes": regenSystemThemes,
"updateNiriLayout": updateNiriLayout,
"applyStoredIconTheme": applyStoredIconTheme,
"updateBarConfigs": updateBarConfigs
})
function set(key, value) {
@@ -543,7 +684,6 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
Store.parse(root, obj);
applyStoredTheme();
applyStoredIconTheme();
Processes.detectIcons();
Processes.detectQtTools();
} catch (e) {
console.warn("SettingsData: Failed to load settings:", e.message);
@@ -590,7 +730,42 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
}
function detectAvailableIconThemes() {
Processes.detectIcons();
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") || "";
const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation));
const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
const dataDirs = xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat([localData]) : ["/usr/share", "/usr/local/share", localData];
const iconPaths = dataDirs.map(d => d + "/icons").concat([homeDir + "/.icons"]);
const pathsArg = iconPaths.join(" ");
const script = `
echo "SYSDEFAULT:$(gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g" || echo '')"
for dir in ${pathsArg}; do
[ -d "$dir" ] || continue
for theme in "$dir"/*/; do
[ -d "$theme" ] || continue
basename "$theme"
done
done | grep -v '^icons$' | grep -v '^default$' | grep -v '^hicolor$' | grep -v '^locolor$' | sort -u
`;
Proc.runCommand("detectIconThemes", ["sh", "-c", script], (output, exitCode) => {
const themes = ["System Default"];
if (output && output.trim()) {
const lines = output.trim().split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith("SYSDEFAULT:")) {
systemDefaultIconTheme = line.substring(11).trim();
continue;
}
if (line)
themes.push(line);
}
}
availableIconThemes = themes;
});
}
function getEffectiveTimeFormat() {
@@ -723,7 +898,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
let leftBar = 0;
let rightBar = 0;
for (let i = 0; i < enabledBars.length; i++) {
for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barConfig.id)
continue;
@@ -793,7 +968,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
if (barConfig) {
const enabledBars = getEnabledBarConfigs();
for (let i = 0; i < enabledBars.length; i++) {
for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barConfig.id)
continue;
@@ -913,7 +1088,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
updateBarConfigs();
if (positionChanged) {
NotificationService.clearAllPopups();
NotificationService.dismissAllPopups();
}
}
@@ -925,7 +1100,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const conflicts = [];
const enabledBars = getEnabledBarConfigs();
for (let i = 0; i < enabledBars.length; i++) {
for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barId)
continue;
@@ -938,9 +1113,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const hasAll = barScreens.includes("all") || otherScreens.includes("all");
if (hasAll) {
conflicts.push({
barId: other.id,
barName: other.name,
reason: "Same position on all screens"
"barId": other.id,
"barName": other.name,
"reason": "Same position on all screens"
});
continue;
}
@@ -948,9 +1123,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const overlapping = barScreens.some(screen => otherScreens.includes(screen));
if (overlapping) {
conflicts.push({
barId: other.id,
barName: other.name,
reason: "Same position on overlapping screens"
"barId": other.id,
"barName": other.name,
"reason": "Same position on overlapping screens"
});
}
}
@@ -972,7 +1147,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
function getScreensSortedByPosition() {
const screens = [];
for (let i = 0; i < Quickshell.screens.length; i++) {
for (var i = 0; i < Quickshell.screens.length; i++) {
screens.push(Quickshell.screens[i]);
}
screens.sort((a, b) => {
@@ -989,7 +1164,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const sorted = getScreensSortedByPosition();
let modelCount = 0;
let screenIndex = -1;
for (let i = 0; i < sorted.length; i++) {
for (var i = 0; i < sorted.length; i++) {
if (sorted[i].model === screen.model) {
if (sorted[i].name === screen.name) {
screenIndex = modelCount;
@@ -1058,7 +1233,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
}
function sendTestNotifications() {
NotificationService.clearAllPopups();
NotificationService.dismissAllPopups();
sendTestNotification(0);
testNotifTimer1.start();
testNotifTimer2.start();
@@ -1187,7 +1362,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
spacing: spacing
"spacing": spacing
});
}
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
@@ -1216,7 +1391,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return;
}
updateBarConfig(defaultBar.id, {
position: position
"position": position
});
}
@@ -1224,7 +1399,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
leftWidgets: order
"leftWidgets": order
});
updateListModel(leftWidgetsModel, order);
}
@@ -1234,7 +1409,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
centerWidgets: order
"centerWidgets": order
});
updateListModel(centerWidgetsModel, order);
}
@@ -1244,7 +1419,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
rightWidgets: order
"rightWidgets": order
});
updateListModel(rightWidgetsModel, order);
}
@@ -1257,9 +1432,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
leftWidgets: defaultLeft,
centerWidgets: defaultCenter,
rightWidgets: defaultRight
"leftWidgets": defaultLeft,
"centerWidgets": defaultCenter,
"rightWidgets": defaultRight
});
}
updateListModel(leftWidgetsModel, defaultLeft);
@@ -1307,7 +1482,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
visible: !defaultBar.visible
"visible": !defaultBar.visible
});
}
}
@@ -1345,6 +1520,87 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function getNiriOutputSetting(outputId, key, defaultValue) {
if (!niriOutputSettings[outputId])
return defaultValue;
return niriOutputSettings[outputId][key] !== undefined ? niriOutputSettings[outputId][key] : defaultValue;
}
function setNiriOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
niriOutputSettings = updated;
saveSettings();
}
function getNiriOutputSettings(outputId) {
const settings = niriOutputSettings[outputId];
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function setNiriOutputSettings(outputId, settings) {
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
updated[outputId] = settings;
niriOutputSettings = updated;
saveSettings();
}
function removeNiriOutputSettings(outputId) {
if (!niriOutputSettings[outputId])
return;
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
delete updated[outputId];
niriOutputSettings = updated;
saveSettings();
}
function getHyprlandOutputSetting(outputId, key, defaultValue) {
if (!hyprlandOutputSettings[outputId])
return defaultValue;
return hyprlandOutputSettings[outputId][key] !== undefined ? hyprlandOutputSettings[outputId][key] : defaultValue;
}
function setHyprlandOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
hyprlandOutputSettings = updated;
saveSettings();
}
function removeHyprlandOutputSetting(outputId, key) {
if (!hyprlandOutputSettings[outputId] || !(key in hyprlandOutputSettings[outputId]))
return;
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
delete updated[outputId][key];
hyprlandOutputSettings = updated;
saveSettings();
}
function getHyprlandOutputSettings(outputId) {
const settings = hyprlandOutputSettings[outputId];
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function setHyprlandOutputSettings(outputId, settings) {
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
updated[outputId] = settings;
hyprlandOutputSettings = updated;
saveSettings();
}
function removeHyprlandOutputSettings(outputId) {
if (!hyprlandOutputSettings[outputId])
return;
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
delete updated[outputId];
hyprlandOutputSettings = updated;
saveSettings();
}
ListModel {
id: leftWidgetsModel
}

View File

@@ -829,7 +829,7 @@ Singleton {
if (typeof SettingsData !== "undefined") {
const skipTemplates = [];
if (!SettingsData.runDmsMatugenTemplates) {
skipTemplates.push("gtk", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "vesktop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
skipTemplates.push("gtk", "neovim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "vesktop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
} else {
if (!SettingsData.matugenTemplateGtk)
skipTemplates.push("gtk");
@@ -851,6 +851,8 @@ Singleton {
skipTemplates.push("kitty");
if (!SettingsData.matugenTemplateFoot)
skipTemplates.push("foot");
if (!SettingsData.matugenTemplateNeovim)
skipTemplates.push("nvim");
if (!SettingsData.matugenTemplateAlacritty)
skipTemplates.push("alacritty");
if (!SettingsData.matugenTemplateWezterm)
@@ -918,6 +920,7 @@ Singleton {
function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
const colors = {};
const isLight = SessionData !== "undefined" && SessionData.isLightMode;
function addColor(matugenKey, darkVal, lightVal) {
if (!darkVal && !lightVal)
@@ -930,7 +933,7 @@ Singleton {
"color": String(lightVal || darkVal)
},
"default": {
"color": String(darkVal || lightVal)
"color": String((isLight && lightVal) ? lightVal : darkVal)
}
};
}

View File

@@ -1,5 +1,4 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
@@ -11,61 +10,20 @@ Singleton {
property var settingsRoot: null
function detectIcons() {
systemDefaultDetectionProcess.running = true
}
function detectQtTools() {
qtToolsDetectionProcess.running = true
qtToolsDetectionProcess.running = true;
}
function detectFprintd() {
fprintdDetectionProcess.running = true
fprintdDetectionProcess.running = true;
}
function checkPluginSettings() {
pluginSettingsCheckProcess.running = true
pluginSettingsCheckProcess.running = true;
}
function checkDefaultSettings() {
defaultSettingsCheckProcess.running = true
}
property var systemDefaultDetectionProcess: Process {
command: ["sh", "-c", "gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed \"s/'//g\" || echo ''"]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
if (exitCode === 0 && stdout && stdout.length > 0) {
settingsRoot.systemDefaultIconTheme = stdout.trim();
} else {
settingsRoot.systemDefaultIconTheme = "";
}
iconThemeDetectionProcess.running = true;
}
}
property var iconThemeDetectionProcess: Process {
command: ["sh", "-c", "find /usr/share/icons ~/.local/share/icons ~/.icons -maxdepth 1 -type d 2>/dev/null | sed 's|.*/||' | grep -v '^icons$' | sort -u"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (!settingsRoot) return
var detectedThemes = ["System Default"]
if (text && text.trim()) {
var themes = text.trim().split('\n')
for (var i = 0; i < themes.length; i++) {
var theme = themes[i].trim()
if (theme && theme !== "" && theme !== "default" && theme !== "hicolor" && theme !== "locolor") {
detectedThemes.push(theme)
}
}
}
settingsRoot.availableIconThemes = detectedThemes
}
}
defaultSettingsCheckProcess.running = true;
}
property var qtToolsDetectionProcess: Process {
@@ -74,7 +32,8 @@ Singleton {
stdout: StdioCollector {
onStreamFinished: {
if (!settingsRoot) return;
if (!settingsRoot)
return;
if (text && text.trim()) {
var lines = text.trim().split('\n');
for (var i = 0; i < lines.length; i++) {
@@ -95,8 +54,9 @@ Singleton {
property var defaultSettingsCheckProcess: Process {
command: ["sh", "-c", "CONFIG_DIR=\"" + (settingsRoot?._configDir || "") + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-settings.json\" ] && [ ! -f \"$CONFIG_DIR/settings.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-settings.json\" \"$CONFIG_DIR/settings.json\" && echo 'copied'; else echo 'not_found'; fi"]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
onExited: function (exitCode) {
if (!settingsRoot)
return;
if (exitCode === 0) {
console.info("Copied default-settings.json to settings.json");
if (settingsRoot.settingsFile) {
@@ -113,8 +73,9 @@ Singleton {
property var fprintdDetectionProcess: Process {
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
onExited: function (exitCode) {
if (!settingsRoot)
return;
settingsRoot.fprintdAvailable = (exitCode === 0);
}
}
@@ -123,8 +84,9 @@ Singleton {
command: ["test", "-f", settingsRoot?.pluginSettingsPath || ""]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
onExited: function (exitCode) {
if (!settingsRoot)
return;
settingsRoot.pluginSettingsFileExists = (exitCode === 0);
}
}

View File

@@ -182,6 +182,7 @@ var SPEC = {
matugenTemplateKitty: { def: true },
matugenTemplateFoot: { def: true },
matugenTemplateAlacritty: { def: true },
matugenTemplateNeovim: { def: true },
matugenTemplateWezterm: { def: true },
matugenTemplateDgop: { def: true },
matugenTemplateKcolorscheme: { def: true },
@@ -258,6 +259,8 @@ var SPEC = {
displayNameMode: { def: "system" },
screenPreferences: { def: {} },
showOnLastDisplay: { def: {} },
niriOutputSettings: { def: {} },
hyprlandOutputSettings: { def: {} },
barConfigs: { def: [{
id: "default",
@@ -298,7 +301,49 @@ var SPEC = {
scrollEnabled: true,
scrollXBehavior: "column",
scrollYBehavior: "workspace"
}], onChange: "updateBarConfigs" }
}], onChange: "updateBarConfigs" },
desktopClockEnabled: { def: false },
desktopClockStyle: { def: "analog" },
desktopClockTransparency: { def: 0.8, coerce: percentToUnit },
desktopClockColorMode: { def: "primary" },
desktopClockCustomColor: { def: "#ffffff" },
desktopClockShowDate: { def: true },
desktopClockShowAnalogNumbers: { def: false },
desktopClockShowAnalogSeconds: { def: true },
desktopClockX: { def: -1 },
desktopClockY: { def: -1 },
desktopClockWidth: { def: 280 },
desktopClockHeight: { def: 180 },
desktopClockDisplayPreferences: { def: ["all"] },
systemMonitorEnabled: { def: false },
systemMonitorShowHeader: { def: true },
systemMonitorTransparency: { def: 0.8, coerce: percentToUnit },
systemMonitorColorMode: { def: "primary" },
systemMonitorCustomColor: { def: "#ffffff" },
systemMonitorShowCpu: { def: true },
systemMonitorShowCpuGraph: { def: true },
systemMonitorShowCpuTemp: { def: true },
systemMonitorShowGpuTemp: { def: false },
systemMonitorGpuPciId: { def: "" },
systemMonitorShowMemory: { def: true },
systemMonitorShowMemoryGraph: { def: true },
systemMonitorShowNetwork: { def: true },
systemMonitorShowNetworkGraph: { def: true },
systemMonitorShowDisk: { def: true },
systemMonitorShowTopProcesses: { def: false },
systemMonitorTopProcessCount: { def: 3 },
systemMonitorTopProcessSortBy: { def: "cpu" },
systemMonitorGraphInterval: { def: 60 },
systemMonitorX: { def: -1 },
systemMonitorY: { def: -1 },
systemMonitorWidth: { def: 320 },
systemMonitorHeight: { def: 480 },
systemMonitorDisplayPreferences: { def: ["all"] },
systemMonitorVariants: { def: [] },
desktopWidgetPositions: { def: {} },
desktopWidgetGridSettings: { def: {} }
};
function getValidKeys() {

View File

@@ -58,6 +58,8 @@ Item {
WallpaperBackground {}
DesktopWidgetLayer {}
Lock {
id: lock
}

View File

@@ -83,6 +83,7 @@ Item {
function close() {
shouldBeVisible = false;
shouldHaveFocus = false;
ModalManager.closeModal(root);
closeTimer.restart();
}
@@ -90,6 +91,7 @@ Item {
animationsEnabled = false;
shouldBeVisible = false;
shouldHaveFocus = false;
ModalManager.closeModal(root);
closeTimer.stop();
contentWindow.visible = false;
if (useBackgroundWindow)

View File

@@ -128,6 +128,9 @@ DankModal {
FocusScope {
id: colorContent
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property alias hexInput: hexInput
anchors.fill: parent
@@ -160,12 +163,14 @@ DankModal {
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.left: parent.left
}
StyledText {
text: I18n.tr("Select a color from the palette or use custom sliders")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
anchors.left: parent.left
}
}
@@ -360,6 +365,7 @@ DankModal {
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.left: parent.left
}
GridView {
@@ -410,6 +416,7 @@ DankModal {
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.left: parent.left
}
Row {
@@ -462,6 +469,7 @@ DankModal {
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.left: parent.left
}
DankSlider {
@@ -507,6 +515,7 @@ DankModal {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.left: parent.left
}
Row {
@@ -566,6 +575,7 @@ DankModal {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.left: parent.left
}
Row {
@@ -630,6 +640,7 @@ DankModal {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.left: parent.left
}
Row {

View File

@@ -24,26 +24,26 @@ DankModal {
repeat: true
running: root.shouldBeVisible
onTriggered: {
root.countdown--
root.countdown--;
if (root.countdown <= 0) {
root.reverted()
root.close()
root.reverted();
root.close();
}
}
}
onOpened: {
countdown = 10
countdownTimer.start()
countdown = 10;
countdownTimer.start();
}
onDialogClosed: {
countdownTimer.stop()
countdownTimer.stop();
}
onBackgroundClicked: {
root.reverted()
root.close()
root.reverted();
root.close();
}
content: Component {
@@ -55,15 +55,15 @@ DankModal {
implicitHeight: mainColumn.implicitHeight
Keys.onEscapePressed: event => {
root.reverted()
root.close()
event.accepted = true
root.reverted();
root.close();
event.accepted = true;
}
Keys.onReturnPressed: event => {
root.confirmed()
root.close()
event.accepted = true
root.confirmed();
root.close();
event.accepted = true;
}
Column {
@@ -149,8 +149,8 @@ DankModal {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.reverted()
root.close()
root.reverted();
root.close();
}
}
}
@@ -178,8 +178,8 @@ DankModal {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.confirmed()
root.close()
root.confirmed();
root.close();
}
}
@@ -203,8 +203,8 @@ DankModal {
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: {
root.reverted()
root.close()
root.reverted();
root.close();
}
}
}

View File

@@ -57,6 +57,14 @@ DankModal {
}
}
function clearAll() {
NotificationService.clearAllNotifications();
}
function dismissAllPopups () {
NotificationService.dismissAllPopups();
}
modalWidth: 500
modalHeight: 700
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
@@ -99,11 +107,21 @@ DankModal {
}
function toggleDoNotDisturb(): string {
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
return "NOTIFICATION_MODAL_TOGGLE_DND_SUCCESS";
}
function clearAll(): string {
notificationModal.clearAll();
return "NOTIFICATION_MODAL_CLEAR_ALL_SUCCESS";
}
function dismissAllPopups(): string {
notificationModal.dismissAllPopups();
return "NOTIFICATION_MODAL_DISMISS_ALL_POPUPS_SUCCESS";
}
target: "notifications"
}
@@ -111,6 +129,9 @@ DankModal {
Item {
id: notificationKeyHandler
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
anchors.fill: parent
Column {

View File

@@ -108,6 +108,9 @@ FloatingWindow {
FocusScope {
id: contentFocusScope
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
anchors.fill: parent
focus: true

View File

@@ -5,6 +5,9 @@ import qs.Modules.Settings
FocusScope {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property int currentIndex: 0
property var parentModal: null
@@ -32,9 +35,8 @@ FocusScope {
}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -48,9 +50,8 @@ FocusScope {
sourceComponent: TimeWeatherTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -66,9 +67,8 @@ FocusScope {
}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -84,9 +84,8 @@ FocusScope {
}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -100,9 +99,8 @@ FocusScope {
sourceComponent: WorkspacesTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -118,9 +116,8 @@ FocusScope {
}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -134,9 +131,8 @@ FocusScope {
sourceComponent: DisplayConfigTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -150,9 +146,8 @@ FocusScope {
sourceComponent: GammaControlTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -166,9 +161,8 @@ FocusScope {
sourceComponent: DisplayWidgetsTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -182,9 +176,8 @@ FocusScope {
sourceComponent: NetworkTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -198,9 +191,8 @@ FocusScope {
sourceComponent: PrinterTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -214,9 +206,8 @@ FocusScope {
sourceComponent: LauncherTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -230,9 +221,8 @@ FocusScope {
sourceComponent: ThemeColorsTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -246,9 +236,8 @@ FocusScope {
sourceComponent: LockScreenTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -264,9 +253,8 @@ FocusScope {
}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -280,9 +268,8 @@ FocusScope {
sourceComponent: AboutTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -296,9 +283,8 @@ FocusScope {
sourceComponent: TypographyMotionTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -312,9 +298,8 @@ FocusScope {
sourceComponent: SoundsTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -328,9 +313,8 @@ FocusScope {
sourceComponent: MediaPlayerTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -344,9 +328,8 @@ FocusScope {
sourceComponent: NotificationsTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -360,9 +343,8 @@ FocusScope {
sourceComponent: OSDTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -376,9 +358,8 @@ FocusScope {
sourceComponent: RunningAppsTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -392,9 +373,8 @@ FocusScope {
sourceComponent: SystemUpdaterTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -408,9 +388,8 @@ FocusScope {
sourceComponent: PowerSleepTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -426,9 +405,8 @@ FocusScope {
}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
@@ -442,9 +420,23 @@ FocusScope {
sourceComponent: ClipboardTab {}
onActiveChanged: {
if (active && item) {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
Loader {
id: desktopWidgetsLoader
anchors.fill: parent
active: root.currentIndex === 27
visible: active
focus: active
sourceComponent: DesktopWidgetsTab {}
onActiveChanged: {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
}

View File

@@ -135,6 +135,9 @@ FloatingWindow {
FocusScope {
id: contentFocusScope
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
anchors.fill: parent
focus: true
@@ -223,7 +226,7 @@ FloatingWindow {
SettingsSidebar {
id: sidebar
x: 0
anchors.left: parent.left
width: settingsModal.isCompactMode ? parent.width : 270
visible: settingsModal.isCompactMode ? settingsModal.menuVisible : true
parentModal: settingsModal
@@ -238,8 +241,8 @@ FloatingWindow {
}
Item {
x: settingsModal.isCompactMode ? (settingsModal.menuVisible ? parent.width : 0) : sidebar.width
width: settingsModal.isCompactMode ? parent.width : parent.width - sidebar.width
anchors.left: settingsModal.isCompactMode ? (settingsModal.menuVisible ? sidebar.right : parent.left) : sidebar.right
anchors.right: parent.right
height: parent.height
clip: true
@@ -250,14 +253,6 @@ FloatingWindow {
parentModal: settingsModal
currentIndex: settingsModal.currentTabIndex
}
Behavior on x {
enabled: settingsModal.enableAnimations
NumberAnimation {
duration: Theme.mediumDuration
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
}
}
}
}

View File

@@ -9,6 +9,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property int currentIndex: 0
property var parentModal: null
property var expandedCategories: ({})
@@ -114,6 +117,12 @@ Rectangle {
"text": I18n.tr("System Updater"),
"icon": "refresh",
"tabIndex": 20
},
{
"id": "desktop_widgets",
"text": I18n.tr("Desktop Widgets"),
"icon": "widgets",
"tabIndex": 27
}
]
},

View File

@@ -8,6 +8,9 @@ import qs.Widgets
Item {
id: spotlightKeyHandler
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property alias appLauncher: appLauncher
property alias searchField: searchField
property alias fileSearchController: fileSearchController
@@ -72,10 +75,10 @@ Item {
}
event.accepted = true;
} else if (event.key === Qt.Key_Right && searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow();
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
event.accepted = true;
} else if (event.key === Qt.Key_Left && searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow();
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
event.accepted = true;
} else if (event.key == Qt.Key_J && event.modifiers & Qt.ControlModifier) {
if (searchMode === "apps") {
@@ -92,10 +95,10 @@ Item {
}
event.accepted = true;
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow();
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
event.accepted = true;
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow();
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
event.accepted = true;
} else if (event.key === Qt.Key_Tab) {
if (searchMode === "apps") {

View File

@@ -95,6 +95,9 @@ DankPopout {
Rectangle {
id: launcherPanel
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property alias searchField: searchField
color: "transparent"
@@ -179,8 +182,8 @@ DankPopout {
mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious();
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow();
mappings[Qt.Key_Left] = () => appLauncher.selectPreviousInRow();
mappings[Qt.Key_Right] = () => I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
mappings[Qt.Key_Left] = () => I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
}
return mappings;
@@ -211,13 +214,13 @@ DankPopout {
return;
case Qt.Key_L:
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow();
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
event.accepted = true;
}
return;
case Qt.Key_H:
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow();
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
event.accepted = true;
}
return;

View File

@@ -0,0 +1,443 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Widgets
Item {
id: root
property real widgetWidth: 280
property real widgetHeight: 200
property string clockStyle: SettingsData.desktopClockStyle
property bool forceSquare: clockStyle === "analog"
property real defaultWidth: {
switch (clockStyle) {
case "analog":
return 200;
case "stacked":
return 160;
default:
return 280;
}
}
property real defaultHeight: {
switch (clockStyle) {
case "analog":
return 200;
case "stacked":
return 220;
default:
return 160;
}
}
property real minWidth: {
switch (clockStyle) {
case "analog":
return 120;
case "stacked":
return 100;
default:
return 140;
}
}
property real minHeight: {
switch (clockStyle) {
case "analog":
return 120;
case "stacked":
return 140;
default:
return 100;
}
}
property bool enabled: SettingsData.desktopClockEnabled
property real transparency: SettingsData.desktopClockTransparency
property string colorMode: SettingsData.desktopClockColorMode
property color customColor: SettingsData.desktopClockCustomColor
property bool showDate: SettingsData.desktopClockShowDate
property bool showAnalogNumbers: SettingsData.desktopClockShowAnalogNumbers
readonly property real scaleFactor: Math.min(width, height) / 200
readonly property color accentColor: {
if (colorMode === "primary")
return Theme.primary;
if (colorMode === "secondary")
return Theme.secondary;
if (colorMode === "custom")
return customColor;
return Theme.primary;
}
readonly property color handColor: accentColor
readonly property color handColorDim: Theme.withAlpha(accentColor, 0.65)
readonly property color textColor: Theme.onSurface
readonly property color subtleTextColor: Theme.onSurfaceVariant
readonly property color backgroundColor: Theme.withAlpha(Theme.surface, root.transparency)
readonly property bool needsSeconds: clockStyle === "analog" ? SettingsData.desktopClockShowAnalogSeconds : SettingsData.showSeconds
SystemClock {
id: systemClock
precision: root.needsSeconds ? SystemClock.Seconds : SystemClock.Minutes
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: root.backgroundColor
visible: root.clockStyle !== "analog"
}
OrganicBlobHourBulges {
anchors.fill: parent
fillColor: root.backgroundColor
visible: root.clockStyle === "analog"
lobes: 12
rotationDeg: -90
lobeAmount: 0.075
hillPower: 0.92
roundness: 0.22
paddingFrac: 0.02
segments: 144
}
Loader {
anchors.fill: parent
anchors.margins: Theme.spacingM
sourceComponent: {
if (root.clockStyle === "analog")
return analogClock;
if (root.clockStyle === "stacked")
return stackedClock;
return digitalClock;
}
}
Component {
id: analogClock
Item {
id: analogRoot
property real clockSize: Math.min(width, height)
property real centerX: width / 2
property real centerY: height / 2
property real faceRadius: clockSize / 2 - 12
property int hours: systemClock.date?.getHours() % 12 ?? 0
property int minutes: systemClock.date?.getMinutes() ?? 0
property int seconds: systemClock.date?.getSeconds() ?? 0
Repeater {
model: root.showAnalogNumbers ? 12 : 0
StyledText {
required property int index
property real angle: (index + 1) * 30 * Math.PI / 180
property real numRadius: analogRoot.faceRadius + 10
x: analogRoot.centerX + numRadius * Math.sin(angle) - width / 2
y: analogRoot.centerY - numRadius * Math.cos(angle) - height / 2
text: index + 1
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: root.accentColor
}
}
Rectangle {
id: hourHand
property real angle: (analogRoot.hours + analogRoot.minutes / 60) * 30
property real handWidth: Math.max(8, 12 * root.scaleFactor)
property real mainLength: analogRoot.faceRadius * 0.55
property real tailLength: handWidth * 0.5
x: analogRoot.centerX - width / 2
y: analogRoot.centerY - mainLength
width: handWidth
height: mainLength + tailLength
radius: width / 2
color: root.handColor
antialiasing: true
transform: Rotation {
origin.x: hourHand.width / 2
origin.y: hourHand.mainLength
angle: hourHand.angle
}
}
Rectangle {
id: minuteHand
property real angle: (analogRoot.minutes + analogRoot.seconds / 60) * 6
property real mainLength: analogRoot.faceRadius * 0.75
property real tailLength: hourHand.handWidth * 0.5
x: analogRoot.centerX - width / 2
y: analogRoot.centerY - mainLength
width: hourHand.handWidth
height: mainLength + tailLength
radius: width / 2
color: root.handColorDim
antialiasing: true
transform: Rotation {
origin.x: minuteHand.width / 2
origin.y: minuteHand.mainLength
angle: minuteHand.angle
}
}
Rectangle {
id: secondDot
visible: SettingsData.desktopClockShowAnalogSeconds
property real angle: analogRoot.seconds * 6 * Math.PI / 180
property real orbitRadius: analogRoot.faceRadius * 0.92
x: analogRoot.centerX + orbitRadius * Math.sin(angle) - width / 2
y: analogRoot.centerY - orbitRadius * Math.cos(angle) - height / 2
width: Math.max(10, analogRoot.clockSize * 0.07)
height: width
radius: width / 2
color: root.accentColor
Behavior on x {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on y {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
StyledText {
id: dateText
visible: root.showDate
property real hourAngle: (analogRoot.hours + analogRoot.minutes / 60) * 30
property real minuteAngle: analogRoot.minutes * 6
property string bestPosition: {
const hRad = hourAngle * Math.PI / 180;
const mRad = minuteAngle * Math.PI / 180;
const topWeight = Math.max(0, Math.cos(hRad)) + Math.max(0, Math.cos(mRad));
const bottomWeight = Math.max(0, -Math.cos(hRad)) + Math.max(0, -Math.cos(mRad));
const rightWeight = Math.max(0, Math.sin(hRad)) + Math.max(0, Math.sin(mRad));
const leftWeight = Math.max(0, -Math.sin(hRad)) + Math.max(0, -Math.sin(mRad));
const minWeight = Math.min(topWeight, bottomWeight, leftWeight, rightWeight);
if (minWeight === bottomWeight)
return "bottom";
if (minWeight === topWeight)
return "top";
if (minWeight === rightWeight)
return "right";
return "left";
}
x: {
if (bestPosition === "left")
return analogRoot.centerX - analogRoot.faceRadius * 0.5 - width / 2;
if (bestPosition === "right")
return analogRoot.centerX + analogRoot.faceRadius * 0.5 - width / 2;
return analogRoot.centerX - width / 2;
}
y: {
if (bestPosition === "top")
return analogRoot.centerY - analogRoot.faceRadius * 0.5 - height / 2;
if (bestPosition === "bottom")
return analogRoot.centerY + analogRoot.faceRadius * 0.5 - height / 2;
return analogRoot.centerY - height / 2;
}
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
}
font.pixelSize: Theme.fontSizeSmall
color: root.accentColor
Behavior on x {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on y {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
}
}
Component {
id: digitalClock
Item {
id: digitalRoot
property real baseSize: Math.max(28, height * 0.38)
property real smallSize: Math.max(12, baseSize * 0.32)
Column {
anchors.centerIn: parent
spacing: 4
StyledText {
visible: root.showDate
anchors.horizontalCenter: parent.horizontalCenter
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
}
font.pixelSize: digitalRoot.smallSize
color: root.accentColor
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: {
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
const minutes = String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0');
return hours + ":" + minutes;
}
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Normal
color: root.accentColor
}
Row {
visible: !SettingsData.use24HourClock || SettingsData.showSeconds
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingS
Row {
visible: SettingsData.showSeconds
spacing: Theme.spacingXS
DankIcon {
name: "timer"
size: Math.max(10, digitalRoot.baseSize * 0.25)
color: root.subtleTextColor
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
font.pixelSize: digitalRoot.smallSize
color: root.subtleTextColor
}
}
StyledText {
visible: !SettingsData.use24HourClock
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
font.pixelSize: digitalRoot.smallSize
font.weight: Font.Medium
color: root.accentColor
}
}
}
}
}
Component {
id: stackedClock
Item {
id: stackedRoot
property real baseSize: Math.max(32, height * 0.32)
property real smallSize: Math.max(12, baseSize * 0.28)
Column {
anchors.centerIn: parent
spacing: -baseSize * 0.1
StyledText {
visible: root.showDate
anchors.horizontalCenter: parent.horizontalCenter
bottomPadding: Theme.spacingS
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
}
font.pixelSize: stackedRoot.smallSize
color: root.accentColor
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: {
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
return String(hours).padStart(2, '0');
}
font.pixelSize: stackedRoot.baseSize
font.weight: Font.Normal
color: root.accentColor
lineHeight: 0.85
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0')
font.pixelSize: stackedRoot.baseSize
font.weight: Font.Normal
color: root.accentColor
lineHeight: 0.85
}
Row {
visible: SettingsData.showSeconds || !SettingsData.use24HourClock
anchors.horizontalCenter: parent.horizontalCenter
topPadding: Theme.spacingXS
spacing: Theme.spacingS
Row {
visible: SettingsData.showSeconds
spacing: Theme.spacingXS
DankIcon {
name: "timer"
size: Math.max(10, stackedRoot.baseSize * 0.28)
color: root.subtleTextColor
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
font.pixelSize: stackedRoot.smallSize
color: root.subtleTextColor
}
}
StyledText {
visible: !SettingsData.use24HourClock
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
font.pixelSize: stackedRoot.smallSize
font.weight: Font.Medium
color: root.accentColor
}
}
}
}
}
}

View File

@@ -0,0 +1,137 @@
import QtQuick
import QtQuick.Shapes
Item {
id: root
property color fillColor: "transparent"
property int lobes: 12
property real lobeAmount: 0.070
property real roundness: 0.22
property real hillPower: 0.78
property real paddingFrac: 0.020
property real inset: 0
property int segments: 120
property real rotationDeg: -90
layer.enabled: true
layer.samples: 4
function sgn(v) {
return v < 0 ? -1 : 1;
}
function clamp(v, a, b) {
return Math.max(a, Math.min(b, v));
}
function squircleMap(u, v, p) {
const ax = sgn(u) * Math.pow(Math.abs(u), p);
const ay = sgn(v) * Math.pow(Math.abs(v), p);
return {
x: ax,
y: ay
};
}
function buildPathD(w, h) {
const x0 = inset, y0 = inset;
const x1 = w - inset, y1 = h - inset;
const iw = Math.max(2, x1 - x0);
const ih = Math.max(2, y1 - y0);
const cx = x0 + iw * 0.5;
const cy = y0 + ih * 0.5;
const rx = iw * 0.5;
const ry = ih * 0.5;
const rMin = Math.min(rx, ry);
const amp = clamp(lobeAmount, 0.0, 0.14) * rMin;
const extraPad = paddingFrac * rMin + 1.5;
const pad = amp + extraPad;
const rxBase = Math.max(2, rx - pad);
const ryBase = Math.max(2, ry - pad);
const blend = clamp(roundness, 0.0, 0.45);
const squirclePow = 1.0 + blend * 2.8;
const N = Math.max(48, segments);
const rot = rotationDeg * Math.PI / 180.0;
const dt = (Math.PI * 2.0) / N;
function hillWave(a) {
const t = (Math.cos(lobes * a) + 1.0) * 0.5;
return Math.pow(t, hillPower);
}
function P(t) {
const a = t + rot;
const u = Math.cos(a);
const v = Math.sin(a);
const m = 1.0 + (amp / rMin) * hillWave(a);
const ex = u * rxBase * m;
const ey = v * ryBase * m;
const sm = squircleMap(u, v, 1.0 / squirclePow);
const sx = sm.x * rxBase * m;
const sy = sm.y * ryBase * m;
const x = ex * (1.0 - blend) + sx * blend;
const y = ey * (1.0 - blend) + sy * blend;
return {
x: cx + x,
y: cy + y
};
}
function dP(t) {
const eps = dt * 0.25;
const p1 = P(t - eps);
const p2 = P(t + eps);
return {
x: (p2.x - p1.x) / (2 * eps),
y: (p2.y - p1.y) / (2 * eps)
};
}
const p0 = P(0.0);
let d = `M ${p0.x.toFixed(2)} ${p0.y.toFixed(2)} `;
for (let i = 0; i < N; i++) {
const tA = i * dt;
const tB = (i + 1) * dt;
const A = P(tA);
const B = P(tB);
const dA = dP(tA);
const dB = dP(tB);
const c1x = A.x + (dt / 3.0) * dA.x;
const c1y = A.y + (dt / 3.0) * dA.y;
const c2x = B.x - (dt / 3.0) * dB.x;
const c2y = B.y - (dt / 3.0) * dB.y;
d += `C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${B.x.toFixed(2)} ${B.y.toFixed(2)} `;
}
d += "Z";
return d;
}
Shape {
anchors.fill: parent
ShapePath {
fillColor: root.fillColor
strokeColor: "transparent"
PathSvg {
path: root.buildPathD(root.width, root.height)
}
}
}
}

View File

@@ -0,0 +1,736 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property real widgetWidth: 320
property real widgetHeight: 480
property real defaultWidth: 320
property real defaultHeight: 480
property real minWidth: {
const tileCount = enabledTiles.length;
if (tileCount === 0)
return 80;
if (tileCount === 1)
return 100;
return 160;
}
property real minHeight: {
const tileCount = enabledTiles.length;
if (tileCount === 0)
return 60;
if (tileCount === 1)
return 80;
if (tileCount <= 2)
return 120;
return 180;
}
property string variantId: ""
property var variantData: null
readonly property var cfg: variantData?.config ?? null
readonly property bool isVariant: variantId !== "" && cfg !== null
property bool enabled: SettingsData.systemMonitorEnabled
property bool showHeader: isVariant ? (cfg.showHeader ?? true) : SettingsData.systemMonitorShowHeader
property real transparency: isVariant ? (cfg.transparency ?? 0.8) : SettingsData.systemMonitorTransparency
property string colorMode: isVariant ? (cfg.colorMode ?? "primary") : SettingsData.systemMonitorColorMode
property color customColor: isVariant ? (cfg.customColor ?? "#ffffff") : SettingsData.systemMonitorCustomColor
property bool showCpu: isVariant ? (cfg.showCpu ?? true) : SettingsData.systemMonitorShowCpu
property bool showCpuGraph: isVariant ? (cfg.showCpuGraph ?? true) : SettingsData.systemMonitorShowCpuGraph
property bool showCpuTemp: isVariant ? (cfg.showCpuTemp ?? true) : SettingsData.systemMonitorShowCpuTemp
property bool showGpuTemp: isVariant ? (cfg.showGpuTemp ?? false) : SettingsData.systemMonitorShowGpuTemp
property string selectedGpuPciId: isVariant ? (cfg.gpuPciId ?? "") : SettingsData.systemMonitorGpuPciId
property bool showMemory: isVariant ? (cfg.showMemory ?? true) : SettingsData.systemMonitorShowMemory
property bool showMemoryGraph: isVariant ? (cfg.showMemoryGraph ?? true) : SettingsData.systemMonitorShowMemoryGraph
property bool showNetwork: isVariant ? (cfg.showNetwork ?? true) : SettingsData.systemMonitorShowNetwork
property bool showNetworkGraph: isVariant ? (cfg.showNetworkGraph ?? true) : SettingsData.systemMonitorShowNetworkGraph
property bool showDisk: isVariant ? (cfg.showDisk ?? true) : SettingsData.systemMonitorShowDisk
property bool showTopProcesses: isVariant ? (cfg.showTopProcesses ?? false) : SettingsData.systemMonitorShowTopProcesses
property int topProcessCount: isVariant ? (cfg.topProcessCount ?? 3) : SettingsData.systemMonitorTopProcessCount
property string topProcessSortBy: isVariant ? (cfg.topProcessSortBy ?? "cpu") : SettingsData.systemMonitorTopProcessSortBy
property string layoutMode: isVariant ? (cfg.layoutMode ?? "auto") : SettingsData.systemMonitorLayoutMode
property int graphInterval: isVariant ? (cfg.graphInterval ?? 60) : SettingsData.systemMonitorGraphInterval
readonly property color accentColor: {
switch (colorMode) {
case "secondary":
return Theme.secondary;
case "custom":
return customColor;
default:
return Theme.primary;
}
}
readonly property color bgColor: Theme.withAlpha(Theme.surface, root.transparency)
readonly property color tileBg: Theme.withAlpha(Theme.surfaceContainerHigh, root.transparency)
readonly property color textColor: Theme.surfaceText
readonly property color dimColor: Theme.surfaceVariantText
property string currentGpuPciIdRef: ""
property var cpuHistory: []
property var memHistory: []
property var netRxHistory: []
property var netTxHistory: []
property var diskReadHistory: []
property var diskWriteHistory: []
readonly property int historySize: 60
readonly property int sampleInterval: {
switch (graphInterval) {
case 60:
return 1000;
case 300:
return 5000;
case 600:
return 10000;
case 900:
return 15000;
case 1800:
return 30000;
default:
return 1000;
}
}
readonly property var enabledTiles: {
var tiles = [];
if (showCpu)
tiles.push("cpu");
if (showMemory)
tiles.push("mem");
if (showNetwork)
tiles.push("net");
if (showDisk)
tiles.push("disk");
if (showGpuTemp && selectedGpuPciId)
tiles.push("gpu");
return tiles;
}
readonly property var sortedProcesses: {
if (!showTopProcesses || !DgopService.processes)
return [];
var procs = DgopService.processes.slice();
if (topProcessSortBy === "memory") {
procs.sort((a, b) => (b.memoryKB || 0) - (a.memoryKB || 0));
} else {
procs.sort((a, b) => (b.cpu || 0) - (a.cpu || 0));
}
return procs.slice(0, topProcessCount);
}
Component.onCompleted: {
var modules = ["system"];
if (showCpu || showCpuTemp || showCpuGraph)
modules.push("cpu");
if (showMemory || showMemoryGraph)
modules.push("memory");
if (showNetwork || showNetworkGraph)
modules.push("network");
if (showDisk)
modules.push("disk", "diskmounts");
if (showTopProcesses)
modules.push("processes");
DgopService.addRef(modules);
updateGpuRef();
}
Component.onDestruction: {
DgopService.removeRef();
if (currentGpuPciIdRef)
DgopService.removeGpuPciId(currentGpuPciIdRef);
}
onShowGpuTempChanged: updateGpuRef()
onSelectedGpuPciIdChanged: updateGpuRef()
onShowTopProcessesChanged: {
if (showTopProcesses)
DgopService.addRef(["processes"]);
}
function updateGpuRef() {
if (currentGpuPciIdRef && currentGpuPciIdRef !== selectedGpuPciId) {
DgopService.removeGpuPciId(currentGpuPciIdRef);
currentGpuPciIdRef = "";
}
if (!showGpuTemp || !selectedGpuPciId) {
if (currentGpuPciIdRef) {
DgopService.removeGpuPciId(currentGpuPciIdRef);
currentGpuPciIdRef = "";
}
return;
}
if (selectedGpuPciId && !currentGpuPciIdRef) {
DgopService.addGpuPciId(selectedGpuPciId);
currentGpuPciIdRef = selectedGpuPciId;
}
}
function getGpuInfo() {
if (!selectedGpuPciId || !DgopService.availableGpus)
return null;
return DgopService.availableGpus.find(g => g.pciId === selectedGpuPciId);
}
function formatBytes(bytes) {
if (bytes < 1024)
return bytes.toFixed(0) + "B";
if (bytes < 1024 * 1024)
return (bytes / 1024).toFixed(0) + "K";
if (bytes < 1024 * 1024 * 1024)
return (bytes / (1024 * 1024)).toFixed(1) + "M";
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + "G";
}
function formatMemKB(kb) {
if (kb < 1024)
return kb.toFixed(0) + "K";
if (kb < 1024 * 1024)
return (kb / 1024).toFixed(0) + "M";
return (kb / (1024 * 1024)).toFixed(1) + "G";
}
function addToHistory(arr, val) {
var newArr = arr.slice();
newArr.push(val);
if (newArr.length > historySize)
newArr.shift();
return newArr;
}
function sampleData() {
if (showCpuGraph)
cpuHistory = addToHistory(cpuHistory, DgopService.cpuUsage);
if (showMemoryGraph)
memHistory = addToHistory(memHistory, DgopService.memoryUsage);
if (showNetworkGraph) {
netRxHistory = addToHistory(netRxHistory, DgopService.networkRxRate);
netTxHistory = addToHistory(netTxHistory, DgopService.networkTxRate);
}
if (showDisk) {
diskReadHistory = addToHistory(diskReadHistory, DgopService.diskReadRate);
diskWriteHistory = addToHistory(diskWriteHistory, DgopService.diskWriteRate);
}
}
readonly property int sampleSeconds: sampleInterval / 1000
SystemClock {
id: sampleClock
precision: SystemClock.Seconds
onDateChanged: {
var sec = date.getSeconds();
if (sec % root.sampleSeconds === 0)
root.sampleData();
}
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: root.bgColor
border.width: 0
ColumnLayout {
anchors.fill: parent
anchors.margins: (root.enabledTiles.length === 1 && !root.showHeader) ? 0 : Theme.spacingS
spacing: Theme.spacingS
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingS
visible: root.showHeader
ColumnLayout {
spacing: 0
StyledText {
text: DgopService.cpuModel || DgopService.hostname || "System"
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.textColor
elide: Text.ElideRight
Layout.maximumWidth: root.width - Theme.spacingM * 2
}
StyledText {
visible: DgopService.shortUptime && DgopService.shortUptime.length > 0
text: DgopService.shortUptime
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
}
}
}
GridLayout {
id: tileGrid
Layout.fillWidth: true
Layout.fillHeight: true
columns: {
if (root.layoutMode === "list")
return 1;
if (root.layoutMode === "grid")
return 2;
// auto
if (root.width < 280)
return 1;
if (root.width < 500)
return 2;
return 3;
}
rowSpacing: Theme.spacingXS
columnSpacing: Theme.spacingXS
Repeater {
model: root.enabledTiles
Rectangle {
id: tile
readonly property int span: Layout.columnSpan
Layout.fillWidth: true
Layout.fillHeight: true
Layout.columnSpan: {
if (root.layoutMode === "list")
return 1;
var cols = tileGrid.columns;
if (cols <= 1)
return 1;
var count = root.enabledTiles.length;
var idx = index;
if (idx !== count - 1)
return 1;
var remainder = count % cols;
if (remainder === 0)
return 1;
if (!tile.hasGraph)
return 1;
return cols - remainder + 1;
}
Layout.minimumHeight: 60
radius: Theme.cornerRadius - 2
color: root.tileBg
border.width: 0
clip: true
readonly property string tileType: modelData
readonly property bool hasGraph: {
switch (tileType) {
case "cpu":
return root.showCpuGraph;
case "mem":
return root.showMemoryGraph;
case "net":
return root.showNetworkGraph;
case "disk":
return true;
default:
return false;
}
}
Canvas {
id: tileGraph
anchors.fill: parent
visible: tile.hasGraph
renderStrategy: Canvas.Cooperative
property var hist: {
switch (tile.tileType) {
case "cpu":
return root.cpuHistory;
case "mem":
return root.memHistory;
case "net":
return root.netRxHistory;
case "disk":
return root.diskReadHistory;
default:
return [];
}
}
property var hist2: {
switch (tile.tileType) {
case "net":
return root.netTxHistory;
case "disk":
return root.diskWriteHistory;
default:
return null;
}
}
onHistChanged: requestPaint()
onHist2Changed: requestPaint()
onWidthChanged: requestPaint()
onHeightChanged: requestPaint()
onPaint: {
var ctx = getContext("2d");
ctx.reset();
ctx.clearRect(0, 0, width, height);
if (!hist || hist.length < 2)
return;
var maxVal = 100;
if (tile.tileType === "net" || tile.tileType === "disk") {
maxVal = 1;
for (var k = 0; k < hist.length; k++)
maxVal = Math.max(maxVal, hist[k]);
if (hist2)
for (var l = 0; l < hist2.length; l++)
maxVal = Math.max(maxVal, hist2[l]);
}
var c = root.accentColor;
var grad = ctx.createLinearGradient(0, 0, 0, height);
grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.3));
grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.05));
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(0, height);
for (var i = 0; i < hist.length; i++) {
var x = (width / (root.historySize - 1)) * i;
var y = height - (hist[i] / maxVal) * height * 0.85;
ctx.lineTo(x, y);
}
ctx.lineTo((width / (root.historySize - 1)) * (hist.length - 1), height);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.6);
ctx.lineWidth = 1.5;
ctx.beginPath();
for (var j = 0; j < hist.length; j++) {
var px = (width / (root.historySize - 1)) * j;
var py = height - (hist[j] / maxVal) * height * 0.85;
j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
}
ctx.stroke();
if (hist2 && hist2.length >= 2) {
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.3);
ctx.lineWidth = 1;
ctx.beginPath();
for (var m = 0; m < hist2.length; m++) {
var sx = (width / (root.historySize - 1)) * m;
var sy = height - (hist2[m] / maxVal) * height * 0.85;
m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy);
}
ctx.stroke();
}
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 2
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingXS
StyledText {
text: tile.tileType.toUpperCase()
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: root.accentColor
}
Item {
Layout.fillWidth: true
}
StyledText {
visible: tile.tileType === "cpu" && root.showCpuTemp && DgopService.cpuTemperature > 0
text: DgopService.cpuTemperature.toFixed(0) + "°"
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: DgopService.cpuTemperature > 80 ? Theme.error : (DgopService.cpuTemperature > 60 ? Theme.warning : root.dimColor)
}
StyledText {
visible: tile.tileType === "mem"
text: DgopService.formatSystemMemory(DgopService.usedMemoryKB)
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
}
}
Item {
Layout.fillHeight: true
}
StyledText {
visible: tile.tileType === "cpu"
text: DgopService.cpuUsage.toFixed(0) + "%"
isMonospace: true
font.pixelSize: Theme.fontSizeXLarge
font.weight: Font.Medium
color: root.textColor
}
StyledText {
visible: tile.tileType === "mem"
text: DgopService.memoryUsage.toFixed(0) + "%"
isMonospace: true
font.pixelSize: Theme.fontSizeXLarge
font.weight: Font.Medium
color: root.textColor
}
RowLayout {
visible: tile.tileType === "net"
spacing: Theme.spacingM
ColumnLayout {
spacing: 0
StyledText {
text: "↓"
font.pixelSize: Theme.fontSizeSmall
color: root.accentColor
}
StyledText {
text: root.formatBytes(DgopService.networkRxRate) + "/s"
isMonospace: true
font.pixelSize: Theme.fontSizeMedium
color: root.textColor
}
}
ColumnLayout {
spacing: 0
StyledText {
text: "↑"
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
}
StyledText {
text: root.formatBytes(DgopService.networkTxRate) + "/s"
isMonospace: true
font.pixelSize: Theme.fontSizeMedium
color: root.textColor
}
}
}
RowLayout {
visible: tile.tileType === "disk"
spacing: Theme.spacingM
ColumnLayout {
spacing: 0
StyledText {
text: "R"
font.pixelSize: Theme.fontSizeSmall
color: root.accentColor
}
StyledText {
text: root.formatBytes(DgopService.diskReadRate) + "/s"
isMonospace: true
font.pixelSize: Theme.fontSizeMedium
color: root.textColor
}
}
ColumnLayout {
spacing: 0
StyledText {
text: "W"
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
}
StyledText {
text: root.formatBytes(DgopService.diskWriteRate) + "/s"
isMonospace: true
font.pixelSize: Theme.fontSizeMedium
color: root.textColor
}
}
}
ColumnLayout {
visible: tile.tileType === "gpu"
spacing: 0
property var gpu: root.getGpuInfo()
Layout.alignment: tile.span > 1 ? Qt.AlignHCenter : Qt.AlignLeft
StyledText {
property real temp: parent.gpu?.temperature ?? 0
text: temp > 0 ? temp.toFixed(0) + "°C" : "--"
isMonospace: true
font.pixelSize: Theme.fontSizeXLarge
font.weight: Font.Medium
color: root.textColor
Layout.alignment: tile.span > 1 ? Qt.AlignHCenter : Qt.AlignLeft
}
StyledText {
text: parent.gpu?.displayName ?? ""
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
Layout.fillWidth: true
horizontalAlignment: tile.span > 1 ? Text.AlignHCenter : Text.AlignLeft
elide: Text.ElideRight
}
}
Rectangle {
visible: tile.tileType === "cpu" || tile.tileType === "mem"
Layout.fillWidth: true
height: 4
radius: 2
color: Theme.withAlpha(Theme.outline, 0.2)
Rectangle {
property real pct: tile.tileType === "cpu" ? DgopService.cpuUsage / 100 : DgopService.memoryUsage / 100
width: parent.width * Math.min(1, pct)
height: parent.height
radius: 2
color: pct > 0.8 ? Theme.error : (pct > 0.6 ? Theme.warning : root.accentColor)
Behavior on width {
NumberAnimation {
duration: 150
}
}
}
}
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Theme.spacingXS
visible: root.showTopProcesses && root.sortedProcesses.length > 0
Rectangle {
Layout.fillWidth: true
height: 1
color: Theme.withAlpha(Theme.outline, 0.15)
}
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingXS
StyledText {
text: "TOP BY " + root.topProcessSortBy.toUpperCase()
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: root.accentColor
}
Item {
Layout.fillWidth: true
}
StyledText {
text: root.topProcessSortBy === "cpu" ? "CPU" : "MEM"
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
Layout.preferredWidth: 48
horizontalAlignment: Text.AlignRight
}
}
Repeater {
model: root.sortedProcesses
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingXS
StyledText {
text: modelData.command || "unknown"
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.textColor
Layout.fillWidth: true
elide: Text.ElideRight
}
StyledText {
text: root.topProcessSortBy === "cpu" ? (modelData.cpu || 0).toFixed(1) + "%" : root.formatMemKB(modelData.memoryKB || 0)
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
Layout.preferredWidth: 48
horizontalAlignment: Text.AlignRight
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Theme.spacingXS
visible: root.showDisk && DgopService.diskMounts.length > 0
Rectangle {
Layout.fillWidth: true
height: 1
color: Theme.withAlpha(Theme.outline, 0.15)
}
Repeater {
model: DgopService.diskMounts.filter(m => m.mountpoint === "/" || m.mountpoint === "/home")
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingXS
StyledText {
text: modelData.mountpoint
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
Layout.preferredWidth: 48
}
Rectangle {
Layout.fillWidth: true
height: 4
radius: 2
color: Theme.withAlpha(Theme.outline, 0.2)
Rectangle {
property real pct: (modelData.used || 0) / Math.max(1, modelData.total || 1)
width: parent.width * pct
height: parent.height
radius: 2
color: pct > 0.9 ? Theme.error : (pct > 0.75 ? Theme.warning : root.accentColor)
}
}
StyledText {
text: ((modelData.used || 0) / (modelData.total || 1) * 100).toFixed(0) + "%"
isMonospace: true
font.pixelSize: Theme.fontSizeSmall
color: root.textColor
Layout.preferredWidth: 32
horizontalAlignment: Text.AlignRight
}
}
}
}
}
}
}

View File

@@ -12,7 +12,7 @@ PluginComponent {
}
ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetPrimaryText: I18n.tr("VPN")
ccWidgetSecondaryText: {
if (!DMSNetworkService.connected)
return I18n.tr("Disconnected");

View File

@@ -13,30 +13,30 @@ Item {
readonly property bool active: expandedSection !== ""
Behavior on height {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Easing.OutCubic
}
}
Loader {
anchors.fill: parent
anchors.topMargin: Theme.spacingS
sourceComponent: {
if (!root.active) return null
if (!root.active)
return null;
if (expandedSection.startsWith("diskUsage_")) {
return diskUsageDetailComponent
return diskUsageDetailComponent;
}
switch (expandedSection) {
case "wifi": return networkDetailComponent
case "bluetooth": return bluetoothDetailComponent
case "audioOutput": return audioOutputDetailComponent
case "audioInput": return audioInputDetailComponent
case "battery": return batteryDetailComponent
default: return null
case "wifi":
return networkDetailComponent;
case "bluetooth":
return bluetoothDetailComponent;
case "audioOutput":
return audioOutputDetailComponent;
case "audioInput":
return audioInputDetailComponent;
case "battery":
return batteryDetailComponent;
default:
return null;
}
}
}
@@ -72,18 +72,18 @@ Item {
currentMountPath: root.expandedWidgetData?.mountPath || "/"
instanceId: root.expandedWidgetData?.instanceId || ""
onMountPathChanged: (newMountPath) => {
onMountPathChanged: newMountPath => {
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
const widgets = SettingsData.controlCenterWidgets || []
const widgets = SettingsData.controlCenterWidgets || [];
const newWidgets = widgets.map(w => {
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.mountPath = newMountPath
return updatedWidget
const updatedWidget = Object.assign({}, w);
updatedWidget.mountPath = newMountPath;
return updatedWidget;
}
return w
})
SettingsData.set("controlCenterWidgets", newWidgets)
return w;
});
SettingsData.set("controlCenterWidgets", newWidgets);
}
}
}

View File

@@ -51,13 +51,13 @@ Rectangle {
spacing: 2
Typography {
text: UserInfoService.fullName || UserInfoService.username || "User"
text: UserInfoService.fullName || UserInfoService.username || I18n.tr("User")
style: Typography.Style.Subtitle
color: Theme.surfaceText
}
Typography {
text: DgopService.uptime || "Unknown"
text: DgopService.uptime || I18n.tr("Unknown")
style: Typography.Style.Caption
color: Theme.surfaceVariantText
}

View File

@@ -110,6 +110,9 @@ DankPopout {
Rectangle {
id: controlContent
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
property alias bluetoothCodecSelector: bluetoothCodecSelector

View File

@@ -201,7 +201,7 @@ Rectangle {
}
StyledText {
text: modelData === AudioService.source ? "Active" : "Available"
text: modelData === AudioService.source ? I18n.tr("Active") : I18n.tr("Available")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
@@ -241,7 +241,7 @@ Rectangle {
StyledText {
text: {
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
return isThisDevicePinned ? "Pinned" : "Pin";
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
}
font.pixelSize: Theme.fontSizeSmall
color: {

View File

@@ -211,7 +211,7 @@ Rectangle {
}
StyledText {
text: modelData === AudioService.sink ? "Active" : "Available"
text: modelData === AudioService.sink ? I18n.tr("Active") : I18n.tr("Available")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
@@ -251,7 +251,7 @@ Rectangle {
StyledText {
text: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? "Pinned" : "Pin";
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
}
font.pixelSize: Theme.fontSizeSmall
color: {

View File

@@ -1,6 +1,4 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.UPower
import qs.Common
import qs.Services
@@ -66,7 +64,7 @@ Rectangle {
spacing: Theme.spacingS
StyledText {
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : "Power"
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : I18n.tr("Power")
font.pixelSize: Theme.fontSizeXLarge
color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
@@ -81,7 +79,7 @@ Rectangle {
}
StyledText {
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : I18n.tr("Management")
font.pixelSize: Theme.fontSizeLarge
color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
@@ -100,10 +98,10 @@ Rectangle {
StyledText {
text: {
if (!BatteryService.batteryAvailable)
return "Power profile management available";
return I18n.tr("Power profile management available");
const time = BatteryService.formatTimeRemaining();
if (time !== "Unknown") {
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`;
return BatteryService.isCharging ? I18n.tr("Time until full: %1").arg(time) : I18n.tr("Time remaining: %1").arg(time);
}
return "";
}
@@ -176,7 +174,7 @@ Rectangle {
}
StyledText {
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : "Unknown"
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : I18n.tr("Unknown")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Bold

View File

@@ -1,7 +1,4 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
@@ -42,7 +39,7 @@ Item {
if (!device)
return;
BluetoothService.getAvailableCodecs(device, function(codecs, current) {
BluetoothService.getAvailableCodecs(device, function (codecs, current) {
availableCodecs = codecs;
currentCodec = current;
isLoading = false;
@@ -60,7 +57,7 @@ Item {
}
isLoading = true;
BluetoothService.switchCodec(device, profileName, function(success, message) {
BluetoothService.switchCodec(device, profileName, function (success, message) {
isLoading = false;
if (success) {
ToastService.showToast(message, ToastService.levelInfo);
@@ -85,8 +82,12 @@ Item {
propagateComposedEvents: false
onClicked: root.hide()
onWheel: (wheel) => { wheel.accepted = true }
onPositionChanged: (mouse) => { mouse.accepted = true }
onWheel: wheel => {
wheel.accepted = true;
}
onPositionChanged: mouse => {
mouse.accepted = true;
}
}
Rectangle {
@@ -111,8 +112,8 @@ Item {
enabled: root.visible
Keys.onEscapePressed: {
root.hide()
event.accepted = true
root.hide();
event.accepted = true;
}
}
@@ -133,9 +134,15 @@ Item {
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
onClicked: (mouse) => { mouse.accepted = true }
onWheel: (wheel) => { wheel.accepted = true }
onPositionChanged: (mouse) => { mouse.accepted = true }
onClicked: mouse => {
mouse.accepted = true;
}
onWheel: wheel => {
wheel.accepted = true;
}
onPositionChanged: mouse => {
mouse.accepted = true;
}
}
Column {
@@ -174,9 +181,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
}
Rectangle {
@@ -186,7 +191,7 @@ Item {
}
StyledText {
text: isLoading ? "Loading codecs..." : `Current: ${currentCodec}`
text: isLoading ? I18n.tr("Loading codecs...") : I18n.tr("Current: %1").arg(currentCodec)
font.pixelSize: Theme.fontSizeSmall
color: isLoading ? Theme.primary : Theme.surfaceTextMedium
font.weight: Font.Medium
@@ -245,9 +250,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
}
DankIcon {
@@ -271,14 +274,9 @@ Item {
selectCodec(modelData.profile);
}
}
}
}
}
}
Behavior on opacity {
@@ -286,7 +284,6 @@ Item {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
@@ -294,8 +291,6 @@ Item {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
}

View File

@@ -113,7 +113,7 @@ Rectangle {
}
StyledText {
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Scanning" : "Scan"
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? I18n.tr("Scanning") : I18n.tr("Scan")
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
@@ -231,7 +231,7 @@ Rectangle {
width: 200
StyledText {
text: modelData.name || modelData.deviceName || "Unknown Device"
text: modelData.name || modelData.deviceName || I18n.tr("Unknown Device")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.connected ? Font.Medium : Font.Normal
@@ -245,15 +245,15 @@ Rectangle {
StyledText {
text: {
if (modelData.state === BluetoothDeviceState.Connecting)
return "Connecting..."
return I18n.tr("Connecting...")
if (modelData.connected) {
let status = "Connected"
let status = I18n.tr("Connected")
if (currentCodec) {
status += " • " + currentCodec
}
return status
}
return "Paired"
return I18n.tr("Paired")
}
font.pixelSize: Theme.fontSizeSmall
color: {
@@ -320,7 +320,7 @@ Rectangle {
StyledText {
text: {
const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address
return isThisDevicePinned ? "Pinned" : "Pin"
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin")
}
font.pixelSize: Theme.fontSizeSmall
color: {
@@ -458,7 +458,7 @@ Rectangle {
width: 200
StyledText {
text: modelData.name || modelData.deviceName || "Unknown Device"
text: modelData.name || modelData.deviceName || I18n.tr("Unknown Device")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
elide: Text.ElideRight
@@ -470,8 +470,8 @@ Rectangle {
StyledText {
text: {
if (modelData.pairing || isBusy) return "Pairing..."
if (modelData.blocked) return "Blocked"
if (modelData.pairing || isBusy) return I18n.tr("Pairing...")
if (modelData.blocked) return I18n.tr("Blocked")
return BluetoothService.getSignalStrength(modelData)
}
font.pixelSize: Theme.fontSizeSmall
@@ -493,9 +493,9 @@ Rectangle {
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
text: {
if (isBusy) return "Pairing..."
if (!canConnect) return "Cannot pair"
return "Pair"
if (isBusy) return I18n.tr("Pairing...")
if (!canConnect) return I18n.tr("Cannot pair")
return I18n.tr("Pair")
}
font.pixelSize: Theme.fontSizeSmall
color: (canConnect && !isBusy) ? Theme.primary : Theme.surfaceVariantText
@@ -546,7 +546,7 @@ Rectangle {
}
MenuItem {
text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? "Disconnect" : "Connect"
text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? I18n.tr("Disconnect") : I18n.tr("Connect")
height: 32
contentItem: StyledText {

View File

@@ -141,7 +141,7 @@ Rectangle {
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: DisplayService.brightnessAvailable ? "No brightness devices available" : "Brightness control not available"
text: DisplayService.brightnessAvailable ? I18n.tr("No brightness devices available") : I18n.tr("Brightness control not available")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
@@ -173,7 +173,7 @@ Rectangle {
}
StyledText {
text: root.getScreenPinKey() || "Unknown Monitor"
text: root.getScreenPinKey() || I18n.tr("Unknown Monitor")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
@@ -201,7 +201,7 @@ Rectangle {
}
StyledText {
text: isPinnedToScreen ? "Pinned" : "Pin"
text: isPinnedToScreen ? I18n.tr("Pinned") : I18n.tr("Pin")
font.pixelSize: Theme.fontSizeSmall
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
@@ -317,11 +317,11 @@ Rectangle {
text: {
const deviceClass = modelData.class || "";
if (deviceClass === "backlight")
return "Backlight device";
return I18n.tr("Backlight device");
if (deviceClass === "ddc")
return "DDC/CI monitor";
return I18n.tr("DDC/CI monitor");
if (deviceClass === "leds")
return "LED device";
return I18n.tr("LED device");
return deviceClass;
}
font.pixelSize: Theme.fontSizeSmall
@@ -430,7 +430,7 @@ Rectangle {
}
StyledText {
text: SessionData.getBrightnessExponential(modelData.name) ? "Exponential" : "Linear"
text: SessionData.getBrightnessExponential(modelData.name) ? I18n.tr("Exponential") : I18n.tr("Linear")
font.pixelSize: Theme.fontSizeSmall
color: SessionData.getBrightnessExponential(modelData.name) ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter

View File

@@ -1,6 +1,4 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
@@ -20,11 +18,11 @@ Rectangle {
border.width: 0
Component.onCompleted: {
DgopService.addRef(["diskmounts"])
DgopService.addRef(["diskmounts"]);
}
Component.onDestruction: {
DgopService.removeRef(["diskmounts"])
DgopService.removeRef(["diskmounts"]);
}
DankFlickable {
@@ -61,7 +59,7 @@ Rectangle {
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: DgopService.dgopAvailable ? "No disk data available" : "dgop not available"
text: DgopService.dgopAvailable ? I18n.tr("No disk data available") : I18n.tr("dgop not available")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
@@ -96,20 +94,22 @@ Rectangle {
name: "storage"
size: Theme.iconSize
color: {
const percentStr = modelData.percent?.replace("%", "") || "0"
const percent = parseFloat(percentStr) || 0
if (percent > 90) return Theme.error
if (percent > 75) return Theme.warning
return modelData.mount === currentMountPath ? Theme.primary : Theme.surfaceText
const percentStr = modelData.percent?.replace("%", "") || "0";
const percent = parseFloat(percentStr) || 0;
if (percent > 90)
return Theme.error;
if (percent > 75)
return Theme.warning;
return modelData.mount === currentMountPath ? Theme.primary : Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
const percentStr = modelData.percent?.replace("%", "") || "0"
const percent = parseFloat(percentStr) || 0
return percent.toFixed(0) + "%"
const percentStr = modelData.percent?.replace("%", "") || "0";
const percent = parseFloat(percentStr) || 0;
return percent.toFixed(0) + "%";
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
@@ -122,7 +122,7 @@ Rectangle {
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
StyledText {
text: modelData.mount === "/" ? "Root Filesystem" : modelData.mount
text: modelData.mount === "/" ? I18n.tr("Root Filesystem") : modelData.mount
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.mount === currentMountPath ? Font.Medium : Font.Normal
@@ -154,11 +154,10 @@ Rectangle {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
currentMountPath = modelData.mount
mountPathChanged(modelData.mount)
currentMountPath = modelData.mount;
mountPathChanged(modelData.mount);
}
}
}
}
}

View File

@@ -118,7 +118,7 @@ Rectangle {
buttonHeight: 28
textSize: Theme.fontSizeSmall
model: ["Ethernet", "WiFi"]
model: [I18n.tr("Ethernet"), I18n.tr("WiFi")]
currentIndex: currentPreferenceIndex
selectionMode: "single"
onSelectionChanged: (index, selected) => {
@@ -173,7 +173,7 @@ Rectangle {
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
text: NetworkService.wifiEnabled ? I18n.tr("Disabling WiFi...") : I18n.tr("Enabling WiFi...")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
@@ -303,7 +303,7 @@ Rectangle {
width: 200
StyledText {
text: modelData.id || "Unknown Config"
text: modelData.id || I18n.tr("Unknown Config")
font.pixelSize: Theme.fontSizeMedium
color: modelData.isActive ? Theme.primary : Theme.surfaceText
font.weight: modelData.isActive ? Font.Medium : Font.Normal
@@ -549,7 +549,7 @@ Rectangle {
width: 200
StyledText {
text: modelData.ssid || "Unknown Network"
text: modelData.ssid || I18n.tr("Unknown Network")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.ssid === NetworkService.currentWifiSSID ? Font.Medium : Font.Normal
@@ -561,13 +561,13 @@ Rectangle {
spacing: Theme.spacingXS
StyledText {
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected •" : (modelData.secured ? "Secured •" : "Open •")
text: modelData.ssid === NetworkService.currentWifiSSID ? I18n.tr("Connected") + " •" : (modelData.secured ? I18n.tr("Secured") + " •" : I18n.tr("Open") + " •")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: modelData.saved ? "Saved" : ""
text: modelData.saved ? I18n.tr("Saved") : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
visible: text.length > 0
@@ -635,7 +635,7 @@ Rectangle {
StyledText {
text: {
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
return isThisNetworkPinned ? "Pinned" : "Pin";
return isThisNetworkPinned ? I18n.tr("Pinned") : I18n.tr("Pin");
}
font.pixelSize: Theme.fontSizeSmall
color: {
@@ -714,7 +714,7 @@ Rectangle {
}
MenuItem {
text: networkContextMenu.currentConnected ? "Disconnect" : "Connect"
text: networkContextMenu.currentConnected ? I18n.tr("Disconnect") : I18n.tr("Connect")
height: 32
contentItem: StyledText {

View File

@@ -65,141 +65,141 @@ QtObject {
readonly property var coreWidgetDefinitions: [
{
"id": "nightMode",
"text": "Night Mode",
"description": "Blue light filter",
"text": I18n.tr("Night Mode"),
"description": I18n.tr("Blue light filter"),
"icon": "nightlight",
"type": "toggle",
"enabled": DisplayService.automationAvailable,
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
"warning": !DisplayService.automationAvailable ? I18n.tr("Requires night mode support") : undefined
},
{
"id": "darkMode",
"text": "Dark Mode",
"description": "System theme toggle",
"text": I18n.tr("Dark Mode"),
"description": I18n.tr("System theme toggle"),
"icon": "contrast",
"type": "toggle",
"enabled": true
},
{
"id": "doNotDisturb",
"text": "Do Not Disturb",
"description": "Block notifications",
"text": I18n.tr("Do Not Disturb"),
"description": I18n.tr("Block notifications"),
"icon": "do_not_disturb_on",
"type": "toggle",
"enabled": true
},
{
"id": "idleInhibitor",
"text": "Keep Awake",
"description": "Prevent screen timeout",
"text": I18n.tr("Keep Awake"),
"description": I18n.tr("Prevent screen timeout"),
"icon": "motion_sensor_active",
"type": "toggle",
"enabled": true
},
{
"id": "wifi",
"text": "Network",
"description": "Wi-Fi and Ethernet connection",
"text": I18n.tr("Network"),
"description": I18n.tr("Wi-Fi and Ethernet connection"),
"icon": "wifi",
"type": "connection",
"enabled": NetworkService.wifiAvailable,
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
"warning": !NetworkService.wifiAvailable ? I18n.tr("Wi-Fi not available") : undefined
},
{
"id": "bluetooth",
"text": "Bluetooth",
"description": "Device connections",
"text": I18n.tr("Bluetooth"),
"description": I18n.tr("Device connections"),
"icon": "bluetooth",
"type": "connection",
"enabled": BluetoothService.available,
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined
"warning": !BluetoothService.available ? I18n.tr("Bluetooth not available") : undefined
},
{
"id": "audioOutput",
"text": "Audio Output",
"description": "Speaker settings",
"text": I18n.tr("Audio Output"),
"description": I18n.tr("Speaker settings"),
"icon": "volume_up",
"type": "connection",
"enabled": true
},
{
"id": "audioInput",
"text": "Audio Input",
"description": "Microphone settings",
"text": I18n.tr("Audio Input"),
"description": I18n.tr("Microphone settings"),
"icon": "mic",
"type": "connection",
"enabled": true
},
{
"id": "volumeSlider",
"text": "Volume Slider",
"description": "Audio volume control",
"text": I18n.tr("Volume Slider"),
"description": I18n.tr("Audio volume control"),
"icon": "volume_up",
"type": "slider",
"enabled": true
},
{
"id": "brightnessSlider",
"text": "Brightness Slider",
"description": "Display brightness control",
"text": I18n.tr("Brightness Slider"),
"description": I18n.tr("Display brightness control"),
"icon": "brightness_6",
"type": "slider",
"enabled": DisplayService.brightnessAvailable,
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined,
"warning": !DisplayService.brightnessAvailable ? I18n.tr("Brightness control not available") : undefined,
"allowMultiple": true
},
{
"id": "inputVolumeSlider",
"text": "Input Volume Slider",
"description": "Microphone volume control",
"text": I18n.tr("Input Volume Slider"),
"description": I18n.tr("Microphone volume control"),
"icon": "mic",
"type": "slider",
"enabled": true
},
{
"id": "battery",
"text": "Battery",
"description": "Battery and power management",
"text": I18n.tr("Battery"),
"description": I18n.tr("Battery and power management"),
"icon": "battery_std",
"type": "action",
"enabled": true
},
{
"id": "diskUsage",
"text": "Disk Usage",
"description": "Filesystem usage monitoring",
"text": I18n.tr("Disk Usage"),
"description": I18n.tr("Filesystem usage monitoring"),
"icon": "storage",
"type": "action",
"enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
"warning": !DgopService.dgopAvailable ? I18n.tr("Requires 'dgop' tool") : undefined,
"allowMultiple": true
},
{
"id": "colorPicker",
"text": "Color Picker",
"description": "Choose colors from palette",
"text": I18n.tr("Color Picker"),
"description": I18n.tr("Choose colors from palette"),
"icon": "palette",
"type": "action",
"enabled": true
},
{
"id": "builtin_vpn",
"text": "VPN",
"description": "VPN connections",
"text": I18n.tr("VPN"),
"description": I18n.tr("VPN connections"),
"icon": "vpn_key",
"type": "builtin_plugin",
"enabled": DMSNetworkService.available,
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
"warning": !DMSNetworkService.available ? I18n.tr("VPN not available") : undefined,
"isBuiltinPlugin": true
},
{
"id": "builtin_cups",
"text": "Printers",
"description": "Print Server Management",
"text": I18n.tr("Printers"),
"description": I18n.tr("Print Server Management"),
"icon": "Print",
"type": "builtin_plugin",
"enabled": CupsService.available,
"warning": !CupsService.available ? "CUPS not available" : undefined,
"warning": !CupsService.available ? I18n.tr("CUPS not available") : undefined,
"isBuiltinPlugin": true
}
]
@@ -235,7 +235,7 @@ QtObject {
plugins.push({
"id": "plugin_" + plugin.id,
"pluginId": plugin.id,
"text": plugin.name || "Plugin",
"text": plugin.name || I18n.tr("Plugin"),
"description": plugin.description || "",
"icon": plugin.icon || "extension",
"type": "plugin",

View File

@@ -1,8 +1,6 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
@@ -14,35 +12,35 @@ CompoundPill {
primaryText: {
if (!BatteryService.batteryAvailable) {
return "No battery"
return I18n.tr("No battery");
}
return "Battery"
return I18n.tr("Battery");
}
secondaryText: {
if (!BatteryService.batteryAvailable) {
return "Not available"
return I18n.tr("Not available");
}
if (BatteryService.isCharging) {
return `${BatteryService.batteryLevel}% Charging`
return `${BatteryService.batteryLevel}% ` + I18n.tr("Charging");
}
if (BatteryService.isPluggedIn) {
return `${BatteryService.batteryLevel}% Plugged in`
return `${BatteryService.batteryLevel}% ` + I18n.tr("Plugged in");
}
return `${BatteryService.batteryLevel}%`
return `${BatteryService.batteryLevel}%`;
}
iconColor: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error
return Theme.error;
}
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary
return Theme.primary;
}
return Theme.surfaceText
return Theme.surfaceText;
}
onToggled: {
expandClicked()
expandClicked();
}
}

View File

@@ -1,9 +1,5 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
@@ -14,20 +10,20 @@ CompoundPill {
isActive: true
iconName: "palette"
iconColor: Theme.primary
primaryText: "Color Picker"
secondaryText: "Choose a color"
primaryText: I18n.tr("Color Picker")
secondaryText: I18n.tr("Choose a color")
onToggled: {
console.log("ColorPickerPill toggled, modal:", colorPickerModal)
console.log("ColorPickerPill toggled, modal:", colorPickerModal);
if (colorPickerModal) {
colorPickerModal.show()
colorPickerModal.show();
}
}
onExpandClicked: {
console.log("ColorPickerPill expandClicked, modal:", colorPickerModal)
console.log("ColorPickerPill expandClicked, modal:", colorPickerModal);
if (colorPickerModal) {
colorPickerModal.show()
colorPickerModal.show();
}
}
}

View File

@@ -1,8 +1,6 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
@@ -15,64 +13,64 @@ CompoundPill {
property var selectedMount: {
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
return null
return null;
}
const targetMount = DgopService.diskMounts.find(mount => mount.mount === mountPath)
return targetMount || DgopService.diskMounts.find(mount => mount.mount === "/") || DgopService.diskMounts[0]
const targetMount = DgopService.diskMounts.find(mount => mount.mount === mountPath);
return targetMount || DgopService.diskMounts.find(mount => mount.mount === "/") || DgopService.diskMounts[0];
}
property real usagePercent: {
if (!selectedMount || !selectedMount.percent) {
return 0
return 0;
}
const percentStr = selectedMount.percent.replace("%", "")
return parseFloat(percentStr) || 0
const percentStr = selectedMount.percent.replace("%", "");
return parseFloat(percentStr) || 0;
}
isActive: DgopService.dgopAvailable && selectedMount !== null
primaryText: {
if (!DgopService.dgopAvailable) {
return "Disk Usage"
return I18n.tr("Disk Usage");
}
if (!selectedMount) {
return "No disk data"
return I18n.tr("No disk data");
}
return selectedMount.mount
return selectedMount.mount;
}
secondaryText: {
if (!DgopService.dgopAvailable) {
return "dgop not available"
return I18n.tr("dgop not available");
}
if (!selectedMount) {
return "No disk data available"
return I18n.tr("No disk data available");
}
return `${selectedMount.used} / ${selectedMount.size} (${usagePercent.toFixed(0)}%)`
return `${selectedMount.used} / ${selectedMount.size} (${usagePercent.toFixed(0)}%)`;
}
iconColor: {
if (!DgopService.dgopAvailable || !selectedMount) {
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5);
}
if (usagePercent > 90) {
return Theme.error
return Theme.error;
}
if (usagePercent > 75) {
return Theme.warning
return Theme.warning;
}
return Theme.surfaceText
return Theme.surfaceText;
}
Component.onCompleted: {
DgopService.addRef(["diskmounts"])
DgopService.addRef(["diskmounts"]);
}
Component.onDestruction: {
DgopService.removeRef(["diskmounts"])
DgopService.removeRef(["diskmounts"]);
}
onToggled: {
expandClicked()
expandClicked();
}
}

View File

@@ -43,6 +43,9 @@ DankPopout {
Rectangle {
id: batteryContent
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
color: "transparent"
radius: Theme.cornerRadius

View File

@@ -1,5 +1,4 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Modules.Plugins
import qs.Services
@@ -20,21 +19,21 @@ BasePill {
readonly property real minTooltipY: {
if (!parentScreen || !isVerticalOrientation) {
return 0
return 0;
}
if (isAutoHideBar) {
return 0
return 0;
}
if (parentScreen.y > 0) {
return barThickness + barSpacing
return barThickness + barSpacing;
}
return 0
return 0;
}
signal toggleVpnPopup()
signal toggleVpnPopup
content: Component {
Item {
@@ -72,56 +71,63 @@ BasePill {
anchors.fill: parent
hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: !DMSNetworkService.isBusy
onPressed: {
root.toggleVpnPopup()
onPressed: event => {
switch (event.button) {
case Qt.RightButton:
DMSNetworkService.toggleVpn();
return;
case Qt.LeftButton:
root.toggleVpnPopup();
return;
}
}
onEntered: {
if (!root.parentScreen || (popoutTarget?.shouldBeVisible)) return
tooltipLoader.active = true
if (!tooltipLoader.item) return
let tooltipText = ""
if (!root.parentScreen || (popoutTarget?.shouldBeVisible))
return;
tooltipLoader.active = true;
if (!tooltipLoader.item)
return;
let tooltipText = "";
if (!DMSNetworkService.connected) {
tooltipText = "VPN Disconnected"
tooltipText = "VPN Disconnected";
} else {
const names = DMSNetworkService.activeNames || []
const names = DMSNetworkService.activeNames || [];
if (names.length <= 1) {
const name = names[0] || ""
const maxLength = 25
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name
tooltipText = "VPN Connected • " + displayName
const name = names[0] || "";
const maxLength = 25;
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name;
tooltipText = "VPN Connected • " + displayName;
} else {
const name = names[0]
const maxLength = 20
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name
tooltipText = "VPN Connected • " + displayName + " +" + (names.length - 1)
const name = names[0];
const maxLength = 20;
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name;
tooltipText = "VPN Connected • " + displayName + " +" + (names.length - 1);
}
}
if (root.isVerticalOrientation) {
const globalPos = mapToGlobal(width / 2, height / 2)
const currentScreen = root.parentScreen || Screen
const screenX = currentScreen ? currentScreen.x : 0
const screenY = currentScreen ? currentScreen.y : 0
const relativeY = globalPos.y - screenY
const adjustedY = relativeY + root.minTooltipY
const tooltipX = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (currentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS)
const isLeft = root.axis?.edge === "left"
tooltipLoader.item.show(tooltipText, screenX + tooltipX, adjustedY, currentScreen, isLeft, !isLeft)
const globalPos = mapToGlobal(width / 2, height / 2);
const currentScreen = root.parentScreen || Screen;
const screenX = currentScreen ? currentScreen.x : 0;
const screenY = currentScreen ? currentScreen.y : 0;
const relativeY = globalPos.y - screenY;
const adjustedY = relativeY + root.minTooltipY;
const tooltipX = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (currentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
const isLeft = root.axis?.edge === "left";
tooltipLoader.item.show(tooltipText, screenX + tooltipX, adjustedY, currentScreen, isLeft, !isLeft);
} else {
const globalPos = mapToGlobal(width / 2, height)
const tooltipY = root.barThickness + root.barSpacing + Theme.spacingXS
tooltipLoader.item.show(tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false)
const globalPos = mapToGlobal(width / 2, height);
const tooltipY = root.barThickness + root.barSpacing + Theme.spacingXS;
tooltipLoader.item.show(tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
}
}
onExited: {
if (tooltipLoader.item) {
tooltipLoader.item.hide()
tooltipLoader.item.hide();
}
tooltipLoader.active = false
tooltipLoader.active = false;
}
}
}

View File

@@ -548,7 +548,7 @@ Item {
}
}
function getWorkspaceIndex(modelData) {
function getWorkspaceIndex(modelData, index) {
let isPlaceholder;
if (root.useExtWorkspace) {
isPlaceholder = modelData?.hidden === true;
@@ -976,7 +976,7 @@ Item {
StyledText {
id: wsIndexText
anchors.verticalCenter: parent.verticalCenter
text: root.getWorkspaceIndex(modelData)
text: root.getWorkspaceIndex(modelData, index)
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
@@ -1203,12 +1203,12 @@ Item {
Loader {
id: indexLoader
anchors.fill: parent
active: !isPlaceholder && SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
active: SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
sourceComponent: Item {
StyledText {
anchors.centerIn: parent
text: {
return root.getWorkspaceIndex(modelData);
return root.getWorkspaceIndex(modelData, index);
}
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)

View File

@@ -166,6 +166,9 @@ DankPopout {
Rectangle {
id: mainContainer
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
implicitHeight: contentColumn.height + Theme.spacingM * 2
color: "transparent"
radius: Theme.cornerRadius

View File

@@ -8,6 +8,9 @@ import qs.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property int dropdownType: 0
property var activePlayer: null
property var allPlayers: []

View File

@@ -10,6 +10,9 @@ import qs.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property MprisPlayer activePlayer: MprisController.activePlayer
property var allPlayers: MprisController.availablePlayers
property var targetScreen: null

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