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

Compare commits

...

62 Commits

Author SHA1 Message Date
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
189 changed files with 20762 additions and 5889 deletions

View File

@@ -398,297 +398,3 @@ jobs:
prerelease: ${{ contains(env.TAG, '-') }} prerelease: ${{ contains(env.TAG, '-') }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
package:
description: 'Package to build (dms, dms-greeter, or both)'
required: false
default: 'dms'
type: choice
options:
- dms
- dms-greeter
- both
version: 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 required: false
default: '' default: ''
release: release:
@@ -13,8 +22,27 @@ on:
default: '1' default: '1'
jobs: jobs:
build-and-upload: determine-packages:
runs-on: ubuntu-latest 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: steps:
- name: Checkout repository - name: Checkout repository
@@ -39,7 +67,7 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release=$RELEASE" >> $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 - name: Setup build environment
run: | run: |
@@ -70,157 +98,31 @@ jobs:
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
RELEASE="${{ steps.version.outputs.release }}" RELEASE="${{ steps.version.outputs.release }}"
CHANGELOG_DATE="$(date '+%a %b %d %Y')" CHANGELOG_DATE="$(date '+%a %b %d %Y')"
PACKAGE="${{ matrix.package }}"
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF' # Copy spec file from repository
# Spec for DMS stable releases - Generated by GitHub Actions cp distro/fedora/${PACKAGE}.spec ~/rpmbuild/SPECS/${PACKAGE}.spec
%global debug_package %{nil} # Replace placeholders with actual values
%global version VERSION_PLACEHOLDER sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/${PACKAGE}.spec
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors 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 echo "✅ Spec file generated for ${PACKAGE} v${VERSION}-${RELEASE}"
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 "" echo ""
echo "=== Spec file preview ===" echo "=== Spec file preview ==="
head -40 ~/rpmbuild/SPECS/dms.spec head -40 ~/rpmbuild/SPECS/${PACKAGE}.spec
- name: Build SRPM - name: Build SRPM
id: build id: build
run: | run: |
cd ~/rpmbuild/SPECS cd ~/rpmbuild/SPECS
PACKAGE="${{ matrix.package }}"
echo "🔨 Building SRPM..." echo "🔨 Building SRPM for ${PACKAGE}..."
rpmbuild -bs dms.spec 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") SRPM_NAME=$(basename "$SRPM")
echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
@@ -234,7 +136,7 @@ jobs:
- name: Upload SRPM artifact - name: Upload SRPM artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: dms-stable-srpm-${{ steps.version.outputs.version }} name: ${{ matrix.package }}-stable-srpm-${{ steps.version.outputs.version }}
path: ${{ steps.build.outputs.srpm_path }} path: ${{ steps.build.outputs.srpm_path }}
retention-days: 90 retention-days: 90
@@ -255,23 +157,40 @@ jobs:
echo "✅ Copr CLI configured" 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 - name: Upload to Copr
run: | run: |
SRPM="${{ steps.build.outputs.srpm_path }}" SRPM="${{ steps.build.outputs.srpm_path }}"
VERSION="${{ steps.version.outputs.version }}" 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 " SRPM: $(basename $SRPM)"
echo " Version: $VERSION" 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" echo "$BUILD_OUTPUT"
BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown") BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
if [ "$BUILD_ID" != "unknown" ]; then if [ "$BUILD_ID" != "unknown" ]; then
echo "✅ Build submitted successfully!" 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 else
echo "⚠️ Could not extract build ID, but upload may have succeeded" echo "⚠️ Could not extract build ID, but upload may have succeeded"
fi fi
@@ -279,10 +198,13 @@ jobs:
- name: Build summary - name: Build summary
if: always() if: always()
run: | 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 "" >> $GITHUB_STEP_SUMMARY
echo "- **Package:** ${PACKAGE}" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ steps.version.outputs.version }}-${{ steps.version.outputs.release }}" >> $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 "- **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 "" >> $GITHUB_STEP_SUMMARY
echo "Stable release has been built and uploaded to Copr!" >> $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)" description: "Package to update (dms, dms-git, or all)"
required: false required: false
default: "all" 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: rebuild_release:
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)" description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
required: false required: false
default: "" default: ""
push:
tags:
- "v*"
schedule: schedule:
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds - cron: "0 */3 * * *" # Every 3 hours for dms-git builds
@@ -97,7 +98,7 @@ jobs:
# Rebuild requested - always proceed # Rebuild requested - always proceed
echo "packages=$PKG" >> $GITHUB_OUTPUT echo "packages=$PKG" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $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 elif [[ "$PKG" == "all" ]]; then
# Check each package and build list of those needing updates # Check each package and build list of those needing updates
@@ -161,16 +162,51 @@ jobs:
id: packages id: packages
run: | run: |
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
# Tag push event - use the pushed tag
echo "packages=dms" >> $GITHUB_OUTPUT echo "packages=dms" >> $GITHUB_OUTPUT
VERSION="${GITHUB_REF#refs/tags/}" VERSION="${GITHUB_REF#refs/tags/}"
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Triggered by tag: $VERSION" echo "Triggered by tag: $VERSION"
elif [[ "${{ github.event_name }}" == "schedule" ]]; then elif [[ "${{ github.event_name }}" == "schedule" ]]; then
# Scheduled run - dms-git only
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "Triggered by schedule: updating git package" echo "Triggered by schedule: updating git package"
elif [[ -n "${{ github.event.inputs.package }}" ]]; then elif [[ -n "${{ github.event.inputs.package }}" ]]; then
# Use filtered packages from check-updates when package="all" and no rebuild requested # Manual workflow dispatch
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]]; then
# 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 "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})" echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
else else
@@ -186,7 +222,7 @@ jobs:
run: | run: |
COMMIT_HASH=$(git rev-parse --short=8 HEAD) COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD) COMMIT_COUNT=$(git rev-list --count HEAD)
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}" NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating dms-git.spec to version: $NEW_VERSION" echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
@@ -207,14 +243,14 @@ jobs:
run: | run: |
COMMIT_HASH=$(git rev-parse --short=8 HEAD) COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD) COMMIT_COUNT=$(git rev-list --count HEAD)
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}" NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION" echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
# Single changelog entry (git snapshots don't need history) # Single changelog entry (git snapshots don't need history)
CHANGELOG_DATE=$(date -R) CHANGELOG_DATE=$(date -R)
{ {
echo "dms-git ($NEW_VERSION) nightly; urgency=medium" echo "dms-git (${NEW_VERSION}db1) nightly; urgency=medium"
echo "" echo ""
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)" echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
echo "" echo ""
@@ -226,10 +262,15 @@ jobs:
run: | run: |
VERSION="${{ steps.packages.outputs.version }}" VERSION="${{ steps.packages.outputs.version }}"
VERSION_NO_V="${VERSION#v}" 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 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) # Single changelog entry (full history on OBS website)
DATE_STR=$(date "+%a %b %d %Y") DATE_STR=$(date "+%a %b %d %Y")
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec) 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 if [[ -f "distro/debian/dms/debian/changelog" ]]; then
CHANGELOG_DATE=$(date -R) CHANGELOG_DATE=$(date -R)
{ {
echo "dms ($VERSION_NO_V) stable; urgency=medium" echo "dms (${VERSION_NO_V}db1) stable; urgency=medium"
echo "" echo ""
echo " * Update to $VERSION stable release" echo " * Update to $VERSION stable release"
echo "" echo ""
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE" echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
} > "distro/debian/dms/debian/changelog" } > "distro/debian/dms/debian/changelog"
echo "✓ Updated Debian changelog to $VERSION_NO_V" echo "✓ Updated Debian changelog to ${VERSION_NO_V}db1"
fi fi
- name: Install Go - name: Install Go
@@ -289,6 +330,7 @@ jobs:
- name: Upload to OBS - name: Upload to OBS
env: env:
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }} REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
TAG_VERSION: ${{ steps.packages.outputs.version }}
run: | run: |
PACKAGES="${{ steps.packages.outputs.packages }}" PACKAGES="${{ steps.packages.outputs.packages }}"
@@ -300,6 +342,7 @@ jobs:
MESSAGE="Automated update from GitHub Actions" MESSAGE="Automated update from GitHub Actions"
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
MESSAGE="Update to ${{ steps.packages.outputs.version }}" MESSAGE="Update to ${{ steps.packages.outputs.version }}"
echo "==> Version being uploaded: ${{ steps.packages.outputs.version }}"
fi fi
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check) # PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
@@ -309,7 +352,7 @@ jobs:
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Uploading $PKG to OBS..." echo "Uploading $PKG to OBS..."
if [[ -n "$REBUILD_RELEASE" ]]; then if [[ -n "$REBUILD_RELEASE" ]]; then
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE" echo "🔄 Using rebuild release number: db$REBUILD_RELEASE"
fi fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -350,7 +393,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then 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 echo "" >> $GITHUB_STEP_SUMMARY
fi fi

3
.gitignore vendored
View File

@@ -96,11 +96,12 @@ go.work
go.work.sum go.work.sum
# env file # env file
.env .env*
# Editor/IDE # Editor/IDE
# .idea/ # .idea/
# .vscode/ # .vscode/
vim/
bin/ 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 # 1.2.0
- Added clipboard and clipboard history integration - Added clipboard and clipboard history integration
- Added swipe to dismiss notification popups and from center - Added swipe to dismiss notification popups and from center
- Added paste from clipboard history view - requires wtype - Added paste from clipboard history view - requires wtype
- Optimize surface damage of OSD & Toast - 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"; inputs.dms.url = "github:AvengeMedia/DankMaterialShell";
# Use in home-manager or NixOS configuration # 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 BusName=org.freedesktop.Notifications
ExecStart=/usr/bin/dms run --session ExecStart=/usr/bin/dms run --session
ExecReload=/usr/bin/pkill -USR1 -x dms ExecReload=/usr/bin/pkill -USR1 -x dms
Restart=always Restart=on-failure
RestartSec=1.23 RestartSec=1.23
TimeoutStopSec=10 TimeoutStopSec=10

View File

@@ -14,34 +14,63 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
## System Integration ## System Integration
**Wayland Protocols** ### Wayland Protocols (Client)
- `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
**DBus Interfaces** All Wayland protocols are consumed as a client - connecting to the compositor.
- NetworkManager/iwd - Network management
- logind - Session control and inhibit locks
- accountsservice - User account information
- CUPS - Printer management
- Custom IPC via unix socket (JSON API)
**Hardware Control** | Protocol | Purpose |
- DDC/CI protocol - External monitor brightness control (like `ddcutil`) | ----------------------------------------- | ----------------------------------------------------------- |
- Backlight control - Internal display brightness via `login1` or sysfs | `wlr-gamma-control-unstable-v1` | Night mode color temperature control |
- LED control - Keyboard/device LED management | `wlr-screencopy-unstable-v1` | Screen capture for color picker/screenshot |
- evdev input monitoring - Keyboard state tracking (caps lock, etc.) | `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 registry integration
- Plugin lifecycle management - Plugin lifecycle management
- Settings persistence - Settings persistence
## CLI Commands ## CLI Commands
- `dms run [-d]` - Start shell (optionally as daemon) - `dms run [-d]` - Start shell (optionally as daemon)
- `dms restart` / `dms kill` - Manage running processes - `dms restart` / `dms kill` - Manage running processes
- `dms ipc <command>` - Send IPC commands (toggle launcher, notifications, etc.) - `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+ Requires Go 1.24+
**Development build:** **Development build:**
```bash ```bash
make # Build dms CLI make # Build dms CLI
make dankinstall # Build installer make dankinstall # Build installer
@@ -77,6 +107,7 @@ make test # Run tests
``` ```
**Distribution build:** **Distribution build:**
```bash ```bash
make dist # Build without update/greeter features 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` Produces `bin/dms-linux-amd64` and `bin/dms-linux-arm64`
**Installation:** **Installation:**
```bash ```bash
sudo make install # Install to /usr/local/bin/dms sudo make install # Install to /usr/local/bin/dms
``` ```
@@ -91,6 +123,7 @@ sudo make install # Install to /usr/local/bin/dms
## Development ## Development
**Setup pre-commit hooks:** **Setup pre-commit hooks:**
```bash ```bash
git config core.hooksPath .githooks 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. This runs gofmt, golangci-lint, tests, and builds before each commit when `core/` files are staged.
**Regenerating Wayland Protocol Bindings:** **Regenerating Wayland Protocol Bindings:**
```bash ```bash
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest 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 \ 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:** **Module Structure:**
- `cmd/` - Binary entrypoints (dms, dankinstall) - `cmd/` - Binary entrypoints (dms, dankinstall)
- `internal/distros/` - Distribution-specific installation logic - `internal/distros/` - Distribution-specific installation logic
- `internal/proto/` - Wayland protocol bindings - `internal/proto/` - Wayland protocol bindings

View File

@@ -144,8 +144,6 @@ var (
clipConfigEnabled bool clipConfigEnabled bool
clipConfigDisableHistory bool clipConfigDisableHistory bool
clipConfigEnableHistory bool clipConfigEnableHistory bool
clipConfigDisablePersist bool
clipConfigEnablePersist bool
) )
func init() { func init() {
@@ -173,8 +171,6 @@ func init() {
clipConfigSetCmd.Flags().BoolVar(&clipConfigEnabled, "enable", false, "Enable clipboard manager") clipConfigSetCmd.Flags().BoolVar(&clipConfigEnabled, "enable", false, "Enable clipboard manager")
clipConfigSetCmd.Flags().BoolVar(&clipConfigDisableHistory, "disable-history", false, "Disable clipboard history persistence") 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(&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)") 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 { if clipConfigEnableHistory {
params["disableHistory"] = false params["disableHistory"] = false
} }
if clipConfigDisablePersist {
params["disablePersist"] = true
}
if clipConfigEnablePersist {
params["disablePersist"] = false
}
if len(params) == 0 { if len(params) == 0 {
fmt.Println("No config options specified") fmt.Println("No config options specified")

View File

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

View File

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

View File

@@ -18,6 +18,25 @@ import (
type ipcTargets map[string]map[string][]string 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 var isSessionManaged bool
func execDetachedRestart(targetPID int) { func execDetachedRestart(targetPID int) {
@@ -214,14 +233,28 @@ func runShellInteractive(session bool) {
for { for {
select { select {
case sig := <-sigChan: case sig := <-sigChan:
// Handle SIGUSR1 restart for non-session managed processes if sig == syscall.SIGUSR1 {
if sig == syscall.SIGUSR1 && !isSessionManaged { 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...") log.Infof("Received SIGUSR1, spawning detached restart process...")
execDetachedRestart(os.Getpid()) execDetachedRestart(os.Getpid())
// Exit immediately to avoid race conditions with detached restart
return 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) log.Infof("\nReceived signal %v, shutting down...", sig)
cancel() cancel()
cmd.Process.Signal(syscall.SIGTERM) cmd.Process.Signal(syscall.SIGTERM)
@@ -235,7 +268,7 @@ func runShellInteractive(session bool) {
cmd.Process.Signal(syscall.SIGTERM) cmd.Process.Signal(syscall.SIGTERM)
} }
os.Remove(socketPath) os.Remove(socketPath)
os.Exit(1) os.Exit(getProcessExitCode(cmd.ProcessState))
} }
} }
} }
@@ -440,15 +473,28 @@ func runShellDaemon(session bool) {
for { for {
select { select {
case sig := <-sigChan: case sig := <-sigChan:
// Handle SIGUSR1 restart for non-session managed processes if sig == syscall.SIGUSR1 {
if sig == syscall.SIGUSR1 && !isSessionManaged { 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...") log.Infof("Received SIGUSR1, spawning detached restart process...")
execDetachedRestart(os.Getpid()) execDetachedRestart(os.Getpid())
// Exit immediately to avoid race conditions with detached restart
return 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() cancel()
cmd.Process.Signal(syscall.SIGTERM) cmd.Process.Signal(syscall.SIGTERM)
os.Remove(socketPath) os.Remove(socketPath)
@@ -460,7 +506,7 @@ func runShellDaemon(session bool) {
cmd.Process.Signal(syscall.SIGTERM) cmd.Process.Signal(syscall.SIGTERM)
} }
os.Remove(socketPath) 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 { for _, cfg := range configs {
path := filepath.Join(dmsDir, cfg.name) 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 { if err := os.WriteFile(path, []byte(cfg.content), 0644); err != nil {
return fmt.Errorf("failed to write %s: %w", cfg.name, err) return fmt.Errorf("failed to write %s: %w", cfg.name, err)
} }
@@ -265,7 +270,13 @@ func (cd *ConfigDeployer) deployGhosttyConfig() ([]DeploymentResult, error) {
colorResult := DeploymentResult{ colorResult := DeploymentResult{
ConfigType: "Ghostty Colors", 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 { 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) { func TestGhosttyConfigStructure(t *testing.T) {
assert.Contains(t, GhosttyConfig, "window-decoration = false") assert.Contains(t, GhosttyConfig, "window-decoration = false")
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0") 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) { func TestGhosttyColorConfigStructure(t *testing.T) {

View File

@@ -48,4 +48,4 @@ keybind = shift+enter=text:\n
gtk-single-instance = true gtk-single-instance = true
# Dank color generation # 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) p.Color12.Hex, p.Color13.Hex, p.Color14.Hex, p.Color15.Hex)
return result.String() 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") { if !opts.ShouldSkipTemplate("wezterm") {
appendTerminalConfig(opts, cfgFile, tmpDir, "wezterm", "wezterm.toml") appendTerminalConfig(opts, cfgFile, tmpDir, "wezterm", "wezterm.toml")
} }
if !opts.ShouldSkipTemplate("nvim") {
appendTerminalConfig(opts, cfgFile, tmpDir, "nvim", "neovim.toml")
}
if !opts.ShouldSkipTemplate("dgop") { if !opts.ShouldSkipTemplate("dgop") {
appendConfig(opts, cfgFile, "dgop", "dgop.toml") appendConfig(opts, cfgFile, "dgop", "dgop.toml")

View File

@@ -238,7 +238,7 @@ func (i *ZwlrOutputManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
l := 0 l := 0
objectID := client.Uint32(data[l : l+4]) objectID := client.Uint32(data[l : l+4])
proxy := i.Context().GetProxy(objectID) proxy := i.Context().GetProxy(objectID)
if proxy == nil { if proxy == nil || proxy.IsZombie() {
head := &ZwlrOutputHeadV1{} head := &ZwlrOutputHeadV1{}
head.SetContext(i.Context()) head.SetContext(i.Context())
head.SetID(objectID) head.SetID(objectID)
@@ -723,7 +723,7 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
l := 0 l := 0
objectID := client.Uint32(data[l : l+4]) objectID := client.Uint32(data[l : l+4])
proxy := i.Context().GetProxy(objectID) proxy := i.Context().GetProxy(objectID)
if proxy == nil { if proxy == nil || proxy.IsZombie() {
mode := &ZwlrOutputModeV1{} mode := &ZwlrOutputModeV1{}
mode.SetContext(i.Context()) mode.SetContext(i.Context())
mode.SetID(objectID) mode.SetID(objectID)
@@ -761,8 +761,8 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
l := 0 l := 0
objectID := client.Uint32(data[l : l+4]) objectID := client.Uint32(data[l : l+4])
proxy := i.Context().GetProxy(objectID) proxy := i.Context().GetProxy(objectID)
if proxy == nil { if proxy == nil || proxy.IsZombie() {
// Mode not yet registered, create it // Mode not yet registered or zombie, create fresh
mode := &ZwlrOutputModeV1{} mode := &ZwlrOutputModeV1{}
mode.SetContext(i.Context()) mode.SetContext(i.Context())
mode.SetID(objectID) mode.SetID(objectID)

View File

@@ -208,9 +208,6 @@ func handleSetConfig(conn net.Conn, req models.Request, m *Manager) {
if v, ok := req.Params["disableHistory"].(bool); ok { if v, ok := req.Params["disableHistory"].(bool); ok {
cfg.DisableHistory = v cfg.DisableHistory = v
} }
if v, ok := req.Params["disablePersist"].(bool); ok {
cfg.DisablePersist = v
}
if err := m.SetConfig(cfg); err != nil { if err := m.SetConfig(cfg); err != nil {
models.RespondError(conn, req.ID, err.Error()) 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) m.storeClipboardEntry(data, mimeType)
} }
if !cfg.DisablePersist {
m.persistClipboard([]string{mimeType}, map[string][]byte{mimeType: data})
}
m.updateState() m.updateState()
m.notifySubscribers() 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 { func (m *Manager) storeEntry(entry Entry) error {
if m.db == nil { if m.db == nil {
return fmt.Errorf("database not available") 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 { func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
if m.config.MaxHistory < 0 {
return nil
}
c := b.Cursor() c := b.Cursor()
var count int var count int
for k, _ := c.Last(); k != nil; k, _ = c.Prev() { 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.Infof("Clipboard config reloaded: disableHistory=%v", newCfg.DisableHistory)
log.Info("Clipboard persist disabled, releasing ownership")
m.releaseOwnership()
}
log.Infof("Clipboard config reloaded: disableHistory=%v disablePersist=%v",
newCfg.DisableHistory, newCfg.DisablePersist)
m.updateState() m.updateState()
m.notifySubscribers() m.notifySubscribers()

View File

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

View File

@@ -21,7 +21,6 @@ type Config struct {
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
DisableHistory bool `json:"disableHistory"` DisableHistory bool `json:"disableHistory"`
DisablePersist bool `json:"disablePersist"`
} }
func DefaultConfig() Config { func DefaultConfig() Config {
@@ -30,7 +29,6 @@ func DefaultConfig() Config {
MaxEntrySize: 5 * 1024 * 1024, MaxEntrySize: 5 * 1024 * 1024,
AutoClearDays: 0, AutoClearDays: 0,
ClearAtStartup: false, 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 { if v, ok := req.Params["disableHistory"].(bool); ok {
cfg.DisableHistory = v cfg.DisableHistory = v
} }
if v, ok := req.Params["disablePersist"].(bool); ok {
cfg.DisablePersist = v
}
if err := clipboard.SaveConfig(cfg); err != nil { if err := clipboard.SaveConfig(cfg); err != nil {
models.RespondError(conn, req.ID, err.Error()) models.RespondError(conn, req.ID, err.Error())

View File

@@ -145,6 +145,7 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) { handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name) log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name)
head.name = e.Name head.name = e.Name
head.ready = true
m.post(func() { m.post(func() {
m.updateState() m.updateState()
}) })
@@ -240,6 +241,7 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
handle.SetAdaptiveSyncHandler(func(e wlr_output_management.ZwlrOutputHeadV1AdaptiveSyncEvent) { handle.SetAdaptiveSyncHandler(func(e wlr_output_management.ZwlrOutputHeadV1AdaptiveSyncEvent) {
log.Debugf("WlrOutput: Head %d adaptive sync: %d", headID, e.State) log.Debugf("WlrOutput: Head %d adaptive sync: %d", headID, e.State)
head.adaptiveSync = e.State head.adaptiveSync = e.State
head.adaptiveSyncSupported = true
m.post(func() { m.post(func() {
m.updateState() m.updateState()
}) })
@@ -251,11 +253,11 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
m.heads.Delete(headID) m.heads.Delete(headID)
m.post(func() { m.wlMutex.Lock()
m.wlMutex.Lock() handle.Release()
handle.Release() m.wlMutex.Unlock()
m.wlMutex.Unlock()
m.post(func() {
m.updateState() m.updateState()
}) })
}) })
@@ -310,11 +312,11 @@ func (m *Manager) handleMode(headID uint32, e wlr_output_management.ZwlrOutputHe
m.modes.Delete(modeID) m.modes.Delete(modeID)
m.post(func() { m.wlMutex.Lock()
m.wlMutex.Lock() handle.Release()
handle.Release() m.wlMutex.Unlock()
m.wlMutex.Unlock()
m.post(func() {
m.updateState() m.updateState()
}) })
}) })
@@ -328,6 +330,10 @@ func (m *Manager) updateState() {
return true return true
} }
if !head.ready {
return true
}
modes := make([]OutputMode, 0) modes := make([]OutputMode, 0)
var currentMode *OutputMode var currentMode *OutputMode
@@ -355,22 +361,23 @@ func (m *Manager) updateState() {
} }
output := Output{ output := Output{
Name: head.name, Name: head.name,
Description: head.description, Description: head.description,
Make: head.make, Make: head.make,
Model: head.model, Model: head.model,
SerialNumber: head.serialNumber, SerialNumber: head.serialNumber,
PhysicalWidth: head.physicalWidth, PhysicalWidth: head.physicalWidth,
PhysicalHeight: head.physicalHeight, PhysicalHeight: head.physicalHeight,
Enabled: head.enabled, Enabled: head.enabled,
X: head.x, X: head.x,
Y: head.y, Y: head.y,
Transform: head.transform, Transform: head.transform,
Scale: head.scale, Scale: head.scale,
CurrentMode: currentMode, CurrentMode: currentMode,
Modes: modes, Modes: modes,
AdaptiveSync: head.adaptiveSync, AdaptiveSync: head.adaptiveSync,
ID: head.id, AdaptiveSyncSupported: head.adaptiveSyncSupported,
ID: head.id,
} }
outputs = append(outputs, output) outputs = append(outputs, output)
return true return true

View File

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

View File

@@ -3,19 +3,19 @@
<service name="download_url"> <service name="download_url">
<param name="protocol">https</param> <param name="protocol">https</param>
<param name="host">github.com</param> <param name="host">github.com</param>
<param name="path">/AvengeMedia/DankMaterialShell/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> <param name="filename">dms-source.tar.gz</param>
</service> </service>
<!-- Download amd64 binary --> <!-- Download amd64 binary -->
<service name="download_url"> <service name="download_url">
<param name="protocol">https</param> <param name="protocol">https</param>
<param name="host">github.com</param> <param name="host">github.com</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-amd64.gz</param> <param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-amd64.gz</param>
</service> </service>
<!-- Download arm64 binary --> <!-- Download arm64 binary -->
<service name="download_url"> <service name="download_url">
<param name="protocol">https</param> <param name="protocol">https</param>
<param name="host">github.com</param> <param name="host">github.com</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-arm64.gz</param> <param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-arm64.gz</param>
</service> </service>
</services> </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 -- 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 Package: dms
Architecture: amd64 Architecture: amd64
Depends: ${misc:Depends}, Depends: ${misc:Depends},
quickshell-git | quickshell, quickshell | quickshell-git,
accountsservice, accountsservice,
cava, cava,
cliphist, 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 debug_package %{nil}
%global version {{{ git_repo_version }}} %global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell greeter for greetd %global pkg_summary DankMaterialShell greeter for greetd
Name: dms-greeter Name: dms-greeter
Version: %{version} Version: %{version}
Release: 0.git%{?dist} Release: RELEASE_PLACEHOLDER%{?dist}
Summary: %{pkg_summary} Summary: %{pkg_summary}
License: MIT License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell URL: https://github.com/AvengeMedia/DankMaterialShell
VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}}
BuildRequires: git-core Source0: dms-qml.tar.gz
# For the _tmpfilesdir macro.
BuildRequires: systemd-rpm-macros BuildRequires: gzip
BuildRequires: wget
BuildRequires: systemd-rpm-macros
Requires: greetd Requires: greetd
Requires: (quickshell-git or quickshell) Requires: (quickshell-git or quickshell)
@@ -24,14 +24,11 @@ Requires(post): /usr/sbin/useradd
Requires(post): /usr/sbin/groupadd Requires(post): /usr/sbin/groupadd
Recommends: policycoreutils-python-utils Recommends: policycoreutils-python-utils
Recommends: setfacl Recommends: acl
Suggests: niri Suggests: niri
Suggests: hyprland Suggests: hyprland
Suggests: sway Suggests: sway
# Provides: greetd-dms-greeter = %{version}-%{release}
# Conflicts: greetd-dms-greeter
%description %description
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3 DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
inspired greeter interface built with Quickshell for Wayland compositors. 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. authentication, and dynamic theming.
%prep %prep
{{{ git_repo_setup_macro }}} %setup -q -c -n dms-qml
%build
%install %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 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 %{_builddir}/dms-qml/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
install -Dm755 quickshell/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
# Install documentation install -Dm644 %{_builddir}/dms-qml/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
install -Dm644 quickshell/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
# Create cache directory for greeter data install -Dpm0644 %{_builddir}/dms-qml/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
install -Dpm0644 quickshell/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
# Install LICENSE file install -Dm644 %{_builddir}/dms-qml/LICENSE %{buildroot}%{_docdir}/dms-greeter/LICENSE
install -Dm644 LICENSE %{buildroot}%{_docdir}/dms-greeter/LICENSE
# Create greeter home directory
install -dm755 %{buildroot}%{_sharedstatedir}/greeter install -dm755 %{buildroot}%{_sharedstatedir}/greeter
# Note: We do NOT install a PAM config here to avoid conflicting with greetd package # Note: We do NOT install a PAM config here to avoid conflicting w/greetd packages
# Instead, we verify/fix it in %post if needed
# Remove build and development files # Remove build and development files
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.git* rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/.gitignore 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 rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/distro
%posttrans %posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms-greeter" ]; then 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/dms-greeter" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
@@ -89,7 +80,7 @@ fi
%{_tmpfilesdir}/%{name}.conf %{_tmpfilesdir}/%{name}.conf
%pre %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 group greeter >/dev/null || groupadd -r greeter
getent passwd greeter >/dev/null || \ getent passwd greeter >/dev/null || \
useradd -r -g greeter -d %{_sharedstatedir}/greeter -s /bin/bash \ 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 # Verify PAM configuration - only fix if insufficient
PAM_CONFIG="/etc/pam.d/greetd" PAM_CONFIG="/etc/pam.d/greetd"
if [ ! -f "$PAM_CONFIG" ]; then if [ ! -f "$PAM_CONFIG" ]; then
# PAM config doesn't exist - create it
cat > "$PAM_CONFIG" << 'PAM_EOF' cat > "$PAM_CONFIG" << 'PAM_EOF'
#%PAM-1.0 #%PAM-1.0
auth substack system-auth auth substack system-auth
@@ -149,7 +139,6 @@ PAM_EOF
# Only show message on initial install # Only show message on initial install
[ "$1" -eq 1 ] && echo "Created PAM configuration for greetd" [ "$1" -eq 1 ] && echo "Created PAM configuration for greetd"
elif ! grep -q "pam_systemd\|system-auth" "$PAM_CONFIG"; then 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" cp "$PAM_CONFIG" "$PAM_CONFIG.backup-dms-greeter"
cat > "$PAM_CONFIG" << 'PAM_EOF' cat > "$PAM_CONFIG" << 'PAM_EOF'
#%PAM-1.0 #%PAM-1.0
@@ -198,9 +187,8 @@ command = "/usr/bin/dms-greeter --command COMPOSITOR_PLACEHOLDER"
GREETD_EOF GREETD_EOF
sed -i "s|COMPOSITOR_PLACEHOLDER|$COMPOSITOR|" "$GREETD_CONFIG" sed -i "s|COMPOSITOR_PLACEHOLDER|$COMPOSITOR|" "$GREETD_CONFIG"
CONFIG_STATUS="Created new config with $COMPOSITOR " 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 elif ! grep -q "dms-greeter" "$GREETD_CONFIG"; then
# Backup existing config
BACKUP_FILE="${GREETD_CONFIG}.backup-$(date +%%Y%%m%%d-%%H%%M%%S)" BACKUP_FILE="${GREETD_CONFIG}.backup-$(date +%%Y%%m%%d-%%H%%M%%S)"
cp "$GREETD_CONFIG" "$BACKUP_FILE" 2>/dev/null || true cp "$GREETD_CONFIG" "$BACKUP_FILE" 2>/dev/null || true
@@ -267,4 +255,6 @@ if [ "$1" -eq 0 ] && [ -x /usr/sbin/semanage ]; then
fi fi
%changelog %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 debug_package %{nil}
%global version {{{ git_repo_version }}} %global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors %global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
Name: dms Name: dms
Epoch: 2
Version: %{version} Version: %{version}
Release: 1%{?dist} Release: RELEASE_PLACEHOLDER%{?dist}
Summary: %{pkg_summary} Summary: %{pkg_summary}
License: MIT License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell 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: gzip
BuildRequires: golang >= 1.24
BuildRequires: make
BuildRequires: wget BuildRequires: wget
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
# Core requirements Requires: (quickshell or quickshell-git)
Requires: (quickshell-git or quickshell)
Requires: accountsservice Requires: accountsservice
Requires: dms-cli = %{epoch}:%{version}-%{release} Requires: dms-cli = %{version}-%{release}
Requires: dgop Requires: dgop
# Core utilities (Highly recommended for DMS functionality)
Recommends: cava Recommends: cava
Recommends: cliphist Recommends: cliphist
Recommends: danksearch Recommends: danksearch
Recommends: matugen Recommends: matugen
Recommends: quickshell-git
Recommends: wl-clipboard Recommends: wl-clipboard
# Recommended system packages
Recommends: NetworkManager Recommends: NetworkManager
Recommends: qt6-qtmultimedia Recommends: qt6-qtmultimedia
Suggests: qt6ct Suggests: qt6ct
%description %description
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell 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. app launcher, wallpaper customization, and fully customizable with plugins.
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets, 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. Provides native DBus bindings, NetworkManager integration, and system utilities.
%prep %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 case "%{_arch}" in
x86_64) x86_64)
DMS_BINARY="dms-linux-amd64" ARCH_SUFFIX="amd64"
;; ;;
aarch64) aarch64)
DMS_BINARY="dms-linux-arm64" ARCH_SUFFIX="arm64"
;; ;;
*) *)
echo "Unsupported architecture: %{_arch}" echo "Unsupported architecture: %{_arch}"
@@ -85,27 +66,35 @@ case "%{_arch}" in
;; ;;
esac 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 # Shell completions
install -d %{buildroot}%{_datadir}/bash-completion/completions install -d %{buildroot}%{_datadir}/bash-completion/completions
install -d %{buildroot}%{_datadir}/zsh/site-functions install -d %{buildroot}%{_datadir}/zsh/site-functions
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
core/bin/${DMS_BINARY} completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || : %{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
core/bin/${DMS_BINARY} completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || : %{_builddir}/dms-cli 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 fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
# Install systemd user service install -Dm644 %{_builddir}/dms-qml/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.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 %{_builddir}/dms-qml/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/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
# Install shell files to shared data location
install -dm755 %{buildroot}%{_datadir}/quickshell/dms 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 -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
@@ -119,8 +108,7 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
%files %files
%license LICENSE %license LICENSE
%doc CONTRIBUTING.md %doc README.md CONTRIBUTING.md
%doc quickshell/README.md
%{_datadir}/quickshell/dms/ %{_datadir}/quickshell/dms/
%{_userunitdir}/dms.service %{_userunitdir}/dms.service
%{_datadir}/applications/dms-open.desktop %{_datadir}/applications/dms-open.desktop
@@ -133,4 +121,6 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
%{_datadir}/fish/vendor_completions.d/dms.fish %{_datadir}/fish/vendor_completions.d/dms.fish
%changelog %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 let
cfg = config.programs.dankMaterialShell; cfg = config.programs.dank-material-shell;
in in
{ {
packages = [ 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 let
inherit (lib) types; inherit (lib) types;
cfg = config.programs.dankMaterialShell.greeter; cfg = config.programs.dank-material-shell.greeter;
inherit (config.services.greetd.settings.default_session) user; inherit (config.services.greetd.settings.default_session) user;
@@ -44,19 +44,20 @@ in
{ {
imports = imports =
let 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 in
[ [
(lib.mkRemovedOptionModule [ (lib.mkRemovedOptionModule [
"programs" "programs"
"dankMaterialShell" "dank-material-shell"
"greeter" "greeter"
"compositor" "compositor"
"extraConfig" "extraConfig"
] msg) ] msg)
./dms-rename.nix
]; ];
options.programs.dankMaterialShell.greeter = { options.programs.dank-material-shell.greeter = {
enable = lib.mkEnableOption "DankMaterialShell greeter"; enable = lib.mkEnableOption "DankMaterialShell greeter";
compositor.name = lib.mkOption { compositor.name = lib.mkOption {
type = types.enum [ type = types.enum [
@@ -177,7 +178,7 @@ in
mv dms-colors.json colors.json || : mv dms-colors.json colors.json || :
chown ${user}: * || : 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}/.config/DankMaterialShell/settings.json"
"${cfg.configHome}/.local/state/DankMaterialShell/session.json" "${cfg.configHome}/.local/state/DankMaterialShell/session.json"
"${cfg.configHome}/.cache/DankMaterialShell/dms-colors.json" "${cfg.configHome}/.cache/DankMaterialShell/dms-colors.json"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,226 +2,121 @@
set -euo pipefail set -euo pipefail
# Build SRPM locally with correct tarball and upload to Copr # Build SRPM locally with correct tarball and upload to Copr
# Usage: ./create-upload-copr.sh VERSION [RELEASE] # Usage: ./copr-upload.sh [PACKAGE] [VERSION] [RELEASE]
# Example: ./create-upload-copr.sh 1.0.0 4 # 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)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && 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 # Setup build directories
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
cd ~/rpmbuild/SOURCES cd ~/rpmbuild/SOURCES
# Create the corrected QML tarball locally # Download source tarball from GitHub releases
echo "Creating QML tarball with assets..." echo "📦 Downloading source tarball for v${VERSION}..."
TEMP_DIR=$(mktemp -d) if [ ! -f ~/rpmbuild/SOURCES/dms-qml.tar.gz ]; then
cd "$REPO_ROOT" 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}"
# Copy quickshell contents to temp exit 1
cp -r quickshell/* "$TEMP_DIR/" }
echo "✅ Source tarball downloaded"
# Copy root LICENSE and CONTRIBUTING.md else
cp LICENSE CONTRIBUTING.md "$TEMP_DIR/" echo "✅ Source tarball already exists"
# 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
fi fi
# Signal running DMS instances to reload
pkill -USR1 -x dms >/dev/null 2>&1 || :
%files # Copy and prepare spec file
%license LICENSE echo "📝 Preparing spec file..."
%doc README.md CONTRIBUTING.md SPEC_FILE="$REPO_ROOT/distro/fedora/${PACKAGE}.spec"
%{_datadir}/quickshell/dms/ if [ ! -f "$SPEC_FILE" ]; then
%{_userunitdir}/dms.service echo "❌ Spec file not found: $SPEC_FILE"
%{_datadir}/applications/dms-open.desktop exit 1
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg fi
%files -n dms-cli cp "$SPEC_FILE" ~/rpmbuild/SPECS/"${PACKAGE}".spec
%{_bindir}/dms
%{_datadir}/bash-completion/completions/dms
%{_datadir}/zsh/site-functions/_dms
%{_datadir}/fish/vendor_completions.d/dms.fish
%changelog # Replace placeholders in spec file
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1 CHANGELOG_DATE="$(date '+%a %b %d %Y')"
- Stable release VERSION_PLACEHOLDER sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
- Built locally with corrected tarball sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
SPECEOF sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec echo "✅ Spec file prepared for ${PACKAGE} v${VERSION}-${RELEASE}"
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
# Build SRPM # Build SRPM
echo "Building SRPM..." echo "🔨 Building SRPM..."
cd ~/rpmbuild/SPECS 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 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 exit 1
fi fi
echo "SRPM built successfully: $SRPM" echo "SRPM built successfully: $SRPM"
# Check if copr-cli is installed # Check if copr-cli is installed
if ! command -v copr-cli &>/dev/null; then if ! command -v copr-cli &>/dev/null; then
echo "" 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 " pip install copr-cli"
echo "" echo ""
echo "Then configure it with your Copr API token in ~/.config/copr" echo "Then configure it with your Copr API token in ~/.config/copr"
echo "" echo ""
echo "SRPM is ready at: $SRPM" 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 exit 0
fi fi
# Upload to Copr # Upload to Copr
echo "" echo ""
echo "Uploading to Copr..." echo "🚀 Uploading to Copr..."
if copr-cli build avengemedia/dms "$SRPM" --nowait; then if copr-cli build "$COPR_PROJECT" "$SRPM" --nowait; then
echo "" echo ""
echo "Build submitted successfully! Check status at:" echo "Build submitted successfully!"
echo "https://copr.fedorainfracloud.org/coprs/avengemedia/dms/builds/" echo "📊 Check status at:"
echo " https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/builds/"
echo ""
echo "📦 SRPM location: $SRPM"
else else
echo "" echo ""
echo "Copr upload failed. You can manually upload the SRPM:" echo "Copr upload failed. You can manually upload the SRPM:"
echo " copr-cli build avengemedia/dms $SRPM" echo " copr-cli build $COPR_PROJECT $SRPM"
echo "" echo ""
echo "Or upload via web interface:" 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 ""
echo "SRPM location: $SRPM" echo "SRPM location: $SRPM"
exit 1 exit 1

View File

@@ -7,8 +7,8 @@
# ./distro/scripts/obs-upload.sh dms "Update to v1.0.2" # ./distro/scripts/obs-upload.sh dms "Update to v1.0.2"
# ./distro/scripts/obs-upload.sh debian dms # ./distro/scripts/obs-upload.sh debian dms
# ./distro/scripts/obs-upload.sh opensuse dms-git # ./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 debian dms-git 2 # Rebuild with db2 suffix
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with ppa2 suffix (flag syntax) # ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with db2 suffix (flag syntax)
set -e set -e
@@ -126,8 +126,8 @@ check_obs_version_exists() {
OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs) OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs)
# Commit hash check for -git packages # Commit hash check for -git packages
if [[ "$CHECK_MODE" == "commit" ]] && [[ "$PACKAGE" == *"-git" ]]; then 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 "") 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})(ppa[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 if [[ -n "$OBS_COMMIT" && -n "$NEW_COMMIT" && "$OBS_COMMIT" == "$NEW_COMMIT" ]]; then
echo "⚠️ Commit $NEW_COMMIT already exists in OBS (current version: $OBS_VERSION)" 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) # Apply rebuild suffix if specified (must happen before API check)
if [[ -n "$REBUILD_RELEASE" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then 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" echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
fi fi
@@ -307,12 +308,16 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
else else
# Rebuild number specified - check if this exact version already exists (exact mode) # Rebuild number specified - check if this exact version already exists (exact mode)
if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION" "exact"; then if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION" "exact"; then
echo "==> Error: Version $CHANGELOG_VERSION already exists in OBS" echo "==> Version $CHANGELOG_VERSION already exists in OBS"
echo " This exact version (including ppa${REBUILD_RELEASE}) is already uploaded." echo " This exact version (including db${REBUILD_RELEASE}) is already uploaded."
echo " To rebuild with a different release number, try incrementing:" echo " Skipping upload - nothing to do."
echo ""
echo " 💡 To rebuild with a different release number, try incrementing:"
NEXT_NUM=$((REBUILD_RELEASE + 1)) NEXT_NUM=$((REBUILD_RELEASE + 1))
echo " ./distro/scripts/obs-upload.sh $PACKAGE $NEXT_NUM" echo " REBUILD_RELEASE=$NEXT_NUM"
exit 1 echo ""
echo "✓ Exiting gracefully (no changes needed)"
exit 0
fi fi
fi 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 if [[ -n "$URL_PROTOCOL" && -n "$URL_HOST" && -n "$URL_PATH" ]]; then
SOURCE_URL="${URL_PROTOCOL}://${URL_HOST}${URL_PATH}" 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 || 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 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 fi
SOURCE_DIR=$(cd "$SOURCE_DIR" && pwd) SOURCE_DIR=$(cd "$SOURCE_DIR" && pwd)
cd "$REPO_ROOT" 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 else
echo "Error: Failed to download source from $SOURCE_URL" echo "ERROR: Failed to download source from $SOURCE_URL"
echo "Tried both wget and curl. Please check the URL and network connectivity." 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 exit 1
fi fi
fi fi
@@ -553,7 +566,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
exit 1 exit 1
fi fi
echo " Found source directory: $SOURCE_DIR" echo "==> Found source directory: $SOURCE_DIR"
# Vendor Go dependencies for dms-git # Vendor Go dependencies for dms-git
if [[ "$PACKAGE" == "dms-git" ]] && [[ -d "$SOURCE_DIR/core" ]]; then 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") 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" tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
cd "$REPO_ROOT" 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 if [[ "$PACKAGE" == "dms" ]]; then
TARBALL_DIR=$(tar -tzf "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null | head -1 | cut -d'/' -f1) 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" 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" tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
cd "$REPO_ROOT" cd "$REPO_ROOT"
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
echo "ERROR: Failed to return to REPO_ROOT after tarball recreation"
exit 1
fi
fi fi
fi fi
@@ -796,23 +817,29 @@ EOF
fi fi
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 # Server-side cleanup via API
echo "==> Cleaning old tarballs from OBS server (prevents downloading 100+ old versions)" echo "==> Cleaning old tarballs from OBS server (prevents downloading 100+ old versions)"
OBS_FILES=$(osc api "/source/$OBS_PROJECT/$PACKAGE" 2>/dev/null || echo "") OBS_FILES=$(osc api "/source/$OBS_PROJECT/$PACKAGE" 2>/dev/null || echo "")
if [[ -n "$OBS_FILES" ]]; then if [[ -n "$OBS_FILES" ]]; then
DELETED_COUNT=0 DELETED_COUNT=0
KEEP_PATTERN="" KEEP_CURRENT=""
if [[ -n "$CHANGELOG_VERSION" ]]; then if [[ -n "$CHANGELOG_VERSION" ]]; then
BASE_KEEP_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//') KEEP_CURRENT="${PACKAGE}_${CHANGELOG_VERSION}.tar.gz"
KEEP_PATTERN="${PACKAGE}_${BASE_KEEP_VERSION}" echo " Keeping only current version: ${KEEP_CURRENT}"
echo " Keeping tarballs matching: ${KEEP_PATTERN}*"
fi fi
for old_file in $(echo "$OBS_FILES" | grep -oP '(?<=name=")[^"]*\.(tar\.gz|tar\.xz|tar\.bz2)(?=")' || true); do 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 if [[ "$old_file" == "$KEEP_CURRENT" ]]; then
echo " - Keeping current version: $old_file" echo " - Keeping: $old_file"
continue continue
fi fi
@@ -835,14 +862,11 @@ else
echo " ⚠️ Could not fetch file list from server, skipping cleanup" echo " ⚠️ Could not fetch file list from server, skipping cleanup"
fi 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" echo "==> Updating working copy"
if ! osc up --server-side-source-service-files 2>/dev/null; then if ! osc up 2>/dev/null; then
echo " Note: Using regular update (--server-side-source-service-files not supported)" echo "Error: Failed to update working copy"
if ! osc up; then exit 1
echo "Error: Failed to update working copy"
exit 1
fi
fi fi
# Ensure we're in WORK_DIR and it exists # Ensure we're in WORK_DIR and it exists
@@ -882,6 +906,15 @@ elif [[ "$UPLOAD_OPENSUSE" == true ]]; then
fi fi
echo "" 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 osc addremove 2>&1 | grep -v "Git SCM package" || true
SOURCE_TARBALL="${PACKAGE}-source.tar.gz" SOURCE_TARBALL="${PACKAGE}-source.tar.gz"
@@ -908,7 +941,7 @@ if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
else else
echo "==> Committing to OBS" echo "==> Committing to OBS"
set +e 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]} COMMIT_EXIT=${PIPESTATUS[0]}
set -e set -e
if [[ $COMMIT_EXIT -ne 0 ]]; then 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 Package: dms
Architecture: amd64 Architecture: amd64
Depends: ${misc:Depends}, Depends: ${misc:Depends},
quickshell-git | quickshell, quickshell | quickshell-git,
accountsservice, accountsservice,
cava, cava,
cliphist, cliphist,

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.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 ( devShells = forEachSystem (
system: pkgs: system: pkgs:
let let

View File

@@ -16,6 +16,9 @@ Singleton {
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en"); 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") readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
property string currentLocale: "en" property string currentLocale: "en"

View File

@@ -8,6 +8,9 @@ Singleton {
id: modalManager id: modalManager
signal closeAllModalsExcept(var excludedModal) signal closeAllModalsExcept(var excludedModal)
signal modalChanged
property var currentModalsByScreen: ({})
function openModal(modal) { function openModal(modal) {
if (!modal.allowStacking) { if (!modal.allowStacking) {
@@ -17,5 +20,17 @@ Singleton {
PopoutManager.closeAllPopouts(); PopoutManager.closeAllPopouts();
} }
TrayMenuManager.closeAllMenus(); 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 Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior
import QtCore import QtCore
import QtQuick import QtQuick
@@ -282,6 +282,7 @@ Singleton {
property bool matugenTemplateGhostty: true property bool matugenTemplateGhostty: true
property bool matugenTemplateKitty: true property bool matugenTemplateKitty: true
property bool matugenTemplateFoot: true property bool matugenTemplateFoot: true
property bool matugenTemplateNeovim: true
property bool matugenTemplateAlacritty: true property bool matugenTemplateAlacritty: true
property bool matugenTemplateWezterm: true property bool matugenTemplateWezterm: true
property bool matugenTemplateDgop: true property bool matugenTemplateDgop: true
@@ -360,50 +361,190 @@ Singleton {
property string displayNameMode: "system" property string displayNameMode: "system"
property var screenPreferences: ({}) property var screenPreferences: ({})
property var showOnLastDisplay: ({}) property var showOnLastDisplay: ({})
property var niriOutputSettings: ({})
property var hyprlandOutputSettings: ({})
property var barConfigs: [ property var barConfigs: [
{ {
id: "default", "id": "default",
name: "Main Bar", "name": "Main Bar",
enabled: true, "enabled": true,
position: 0, "position": 0,
screenPreferences: ["all"], "screenPreferences": ["all"],
showOnLastDisplay: true, "showOnLastDisplay": true,
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"], "leftWidgets": ["launcherButton", "workspaceSwitcher", "focusedWindow"],
centerWidgets: ["music", "clock", "weather"], "centerWidgets": ["music", "clock", "weather"],
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"], "rightWidgets": ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
spacing: 4, "spacing": 4,
innerPadding: 4, "innerPadding": 4,
bottomGap: 0, "bottomGap": 0,
transparency: 1.0, "transparency": 1.0,
widgetTransparency: 1.0, "widgetTransparency": 1.0,
squareCorners: false, "squareCorners": false,
noBackground: false, "noBackground": false,
gothCornersEnabled: false, "gothCornersEnabled": false,
gothCornerRadiusOverride: false, "gothCornerRadiusOverride": false,
gothCornerRadiusValue: 12, "gothCornerRadiusValue": 12,
borderEnabled: false, "borderEnabled": false,
borderColor: "surfaceText", "borderColor": "surfaceText",
borderOpacity: 1.0, "borderOpacity": 1.0,
borderThickness: 1, "borderThickness": 1,
widgetOutlineEnabled: false, "widgetOutlineEnabled": false,
widgetOutlineColor: "primary", "widgetOutlineColor": "primary",
widgetOutlineOpacity: 1.0, "widgetOutlineOpacity": 1.0,
widgetOutlineThickness: 1, "widgetOutlineThickness": 1,
fontScale: 1.0, "fontScale": 1.0,
autoHide: false, "autoHide": false,
autoHideDelay: 250, "autoHideDelay": 250,
openOnOverview: false, "openOnOverview": false,
visible: true, "visible": true,
popupGapsAuto: true, "popupGapsAuto": true,
popupGapsManual: 4, "popupGapsManual": 4,
maximizeDetection: true, "maximizeDetection": true,
scrollEnabled: true, "scrollEnabled": true,
scrollXBehavior: "column", "scrollXBehavior": "column",
scrollYBehavior: "workspace" "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 forceDankBarLayoutRefresh
signal forceDockLayoutRefresh signal forceDockLayoutRefresh
signal widgetDataChanged signal widgetDataChanged
@@ -458,25 +599,25 @@ Singleton {
const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0 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 for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
settings_file="$config_dir/settings.ini" settings_file="$config_dir/settings.ini"
if [ -f "$settings_file" ]; then if [ -f "$settings_file" ]; then
if grep -q "^gtk-icon-theme-name=" "$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 else
if grep -q "\\[Settings\\]" "$settings_file"; then if grep -q "\\[Settings\\]" "$settings_file"; then
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file" sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
else else
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file" echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
fi
fi fi
else fi
else
echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file" echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
fi fi
done done
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
pkill -HUP -f 'gtk' 2>/dev/null || true`; pkill -HUP -f 'gtk' 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", configScript]); Quickshell.execDetached(["sh", "-lc", configScript]);
} }
@@ -489,36 +630,36 @@ pkill -HUP -f 'gtk' 2>/dev/null || true`;
const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''"); const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
update_qt_icon_theme() { update_qt_icon_theme() {
local config_file="$1" local config_file="$1"
local theme_name="$2" local theme_name="$2"
if [ -f "$config_file" ]; then if [ -f "$config_file" ]; then
if grep -q "^\\[Appearance\\]" "$config_file"; then if grep -q "^\\[Appearance\\]" "$config_file"; then
if grep -q "^icon_theme=" "$config_file"; then if grep -q "^icon_theme=" "$config_file"; then
sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file" sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
else else
sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file" sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
fi fi
else else
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file" printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
fi fi
else else
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file" printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
fi fi
} }
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}' update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}' update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`; rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", script]); Quickshell.execDetached(["sh", "-lc", script]);
} }
readonly property var _hooks: ({ readonly property var _hooks: ({
applyStoredTheme: applyStoredTheme, "applyStoredTheme": applyStoredTheme,
regenSystemThemes: regenSystemThemes, "regenSystemThemes": regenSystemThemes,
updateNiriLayout: updateNiriLayout, "updateNiriLayout": updateNiriLayout,
applyStoredIconTheme: applyStoredIconTheme, "applyStoredIconTheme": applyStoredIconTheme,
updateBarConfigs: updateBarConfigs "updateBarConfigs": updateBarConfigs
}) })
function set(key, value) { 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); Store.parse(root, obj);
applyStoredTheme(); applyStoredTheme();
applyStoredIconTheme(); applyStoredIconTheme();
Processes.detectIcons();
Processes.detectQtTools(); Processes.detectQtTools();
} catch (e) { } catch (e) {
console.warn("SettingsData: Failed to load settings:", e.message); 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() { 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() { function getEffectiveTimeFormat() {
@@ -723,7 +898,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
let leftBar = 0; let leftBar = 0;
let rightBar = 0; let rightBar = 0;
for (let i = 0; i < enabledBars.length; i++) { for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i]; const other = enabledBars[i];
if (other.id === barConfig.id) if (other.id === barConfig.id)
continue; continue;
@@ -793,7 +968,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
if (barConfig) { if (barConfig) {
const enabledBars = getEnabledBarConfigs(); const enabledBars = getEnabledBarConfigs();
for (let i = 0; i < enabledBars.length; i++) { for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i]; const other = enabledBars[i];
if (other.id === barConfig.id) if (other.id === barConfig.id)
continue; continue;
@@ -925,7 +1100,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const conflicts = []; const conflicts = [];
const enabledBars = getEnabledBarConfigs(); const enabledBars = getEnabledBarConfigs();
for (let i = 0; i < enabledBars.length; i++) { for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i]; const other = enabledBars[i];
if (other.id === barId) if (other.id === barId)
continue; 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"); const hasAll = barScreens.includes("all") || otherScreens.includes("all");
if (hasAll) { if (hasAll) {
conflicts.push({ conflicts.push({
barId: other.id, "barId": other.id,
barName: other.name, "barName": other.name,
reason: "Same position on all screens" "reason": "Same position on all screens"
}); });
continue; 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)); const overlapping = barScreens.some(screen => otherScreens.includes(screen));
if (overlapping) { if (overlapping) {
conflicts.push({ conflicts.push({
barId: other.id, "barId": other.id,
barName: other.name, "barName": other.name,
reason: "Same position on overlapping screens" "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() { function getScreensSortedByPosition() {
const screens = []; 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.push(Quickshell.screens[i]);
} }
screens.sort((a, b) => { screens.sort((a, b) => {
@@ -989,7 +1164,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const sorted = getScreensSortedByPosition(); const sorted = getScreensSortedByPosition();
let modelCount = 0; let modelCount = 0;
let screenIndex = -1; 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].model === screen.model) {
if (sorted[i].name === screen.name) { if (sorted[i].name === screen.name) {
screenIndex = modelCount; screenIndex = modelCount;
@@ -1187,7 +1362,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
spacing: spacing "spacing": spacing
}); });
} }
if (typeof NiriService !== "undefined" && CompositorService.isNiri) { if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
@@ -1216,7 +1391,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return; return;
} }
updateBarConfig(defaultBar.id, { 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"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
leftWidgets: order "leftWidgets": order
}); });
updateListModel(leftWidgetsModel, 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"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
centerWidgets: order "centerWidgets": order
}); });
updateListModel(centerWidgetsModel, 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"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
rightWidgets: order "rightWidgets": order
}); });
updateListModel(rightWidgetsModel, 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"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
leftWidgets: defaultLeft, "leftWidgets": defaultLeft,
centerWidgets: defaultCenter, "centerWidgets": defaultCenter,
rightWidgets: defaultRight "rightWidgets": defaultRight
}); });
} }
updateListModel(leftWidgetsModel, defaultLeft); 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"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { 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)) : {}; 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 { ListModel {
id: leftWidgetsModel id: leftWidgetsModel
} }

View File

@@ -829,7 +829,7 @@ Singleton {
if (typeof SettingsData !== "undefined") { if (typeof SettingsData !== "undefined") {
const skipTemplates = []; const skipTemplates = [];
if (!SettingsData.runDmsMatugenTemplates) { 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 { } else {
if (!SettingsData.matugenTemplateGtk) if (!SettingsData.matugenTemplateGtk)
skipTemplates.push("gtk"); skipTemplates.push("gtk");
@@ -851,6 +851,8 @@ Singleton {
skipTemplates.push("kitty"); skipTemplates.push("kitty");
if (!SettingsData.matugenTemplateFoot) if (!SettingsData.matugenTemplateFoot)
skipTemplates.push("foot"); skipTemplates.push("foot");
if (!SettingsData.matugenTemplateNeovim)
skipTemplates.push("nvim");
if (!SettingsData.matugenTemplateAlacritty) if (!SettingsData.matugenTemplateAlacritty)
skipTemplates.push("alacritty"); skipTemplates.push("alacritty");
if (!SettingsData.matugenTemplateWezterm) if (!SettingsData.matugenTemplateWezterm)
@@ -918,6 +920,7 @@ Singleton {
function buildMatugenColorsFromTheme(darkTheme, lightTheme) { function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
const colors = {}; const colors = {};
const isLight = SessionData !== "undefined" && SessionData.isLightMode;
function addColor(matugenKey, darkVal, lightVal) { function addColor(matugenKey, darkVal, lightVal) {
if (!darkVal && !lightVal) if (!darkVal && !lightVal)
@@ -930,7 +933,7 @@ Singleton {
"color": String(lightVal || darkVal) "color": String(lightVal || darkVal)
}, },
"default": { "default": {
"color": String(darkVal || lightVal) "color": String((isLight && lightVal) ? lightVal : darkVal)
} }
}; };
} }

View File

@@ -1,5 +1,4 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
@@ -11,61 +10,20 @@ Singleton {
property var settingsRoot: null property var settingsRoot: null
function detectIcons() {
systemDefaultDetectionProcess.running = true
}
function detectQtTools() { function detectQtTools() {
qtToolsDetectionProcess.running = true qtToolsDetectionProcess.running = true;
} }
function detectFprintd() { function detectFprintd() {
fprintdDetectionProcess.running = true fprintdDetectionProcess.running = true;
} }
function checkPluginSettings() { function checkPluginSettings() {
pluginSettingsCheckProcess.running = true pluginSettingsCheckProcess.running = true;
} }
function checkDefaultSettings() { function checkDefaultSettings() {
defaultSettingsCheckProcess.running = true 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
}
}
} }
property var qtToolsDetectionProcess: Process { property var qtToolsDetectionProcess: Process {
@@ -74,7 +32,8 @@ Singleton {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (!settingsRoot) return; if (!settingsRoot)
return;
if (text && text.trim()) { if (text && text.trim()) {
var lines = text.trim().split('\n'); var lines = text.trim().split('\n');
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
@@ -95,8 +54,9 @@ Singleton {
property var defaultSettingsCheckProcess: Process { 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"] 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 running: false
onExited: function(exitCode) { onExited: function (exitCode) {
if (!settingsRoot) return; if (!settingsRoot)
return;
if (exitCode === 0) { if (exitCode === 0) {
console.info("Copied default-settings.json to settings.json"); console.info("Copied default-settings.json to settings.json");
if (settingsRoot.settingsFile) { if (settingsRoot.settingsFile) {
@@ -113,8 +73,9 @@ Singleton {
property var fprintdDetectionProcess: Process { property var fprintdDetectionProcess: Process {
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"] command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"]
running: false running: false
onExited: function(exitCode) { onExited: function (exitCode) {
if (!settingsRoot) return; if (!settingsRoot)
return;
settingsRoot.fprintdAvailable = (exitCode === 0); settingsRoot.fprintdAvailable = (exitCode === 0);
} }
} }
@@ -123,8 +84,9 @@ Singleton {
command: ["test", "-f", settingsRoot?.pluginSettingsPath || ""] command: ["test", "-f", settingsRoot?.pluginSettingsPath || ""]
running: false running: false
onExited: function(exitCode) { onExited: function (exitCode) {
if (!settingsRoot) return; if (!settingsRoot)
return;
settingsRoot.pluginSettingsFileExists = (exitCode === 0); settingsRoot.pluginSettingsFileExists = (exitCode === 0);
} }
} }

View File

@@ -182,6 +182,7 @@ var SPEC = {
matugenTemplateKitty: { def: true }, matugenTemplateKitty: { def: true },
matugenTemplateFoot: { def: true }, matugenTemplateFoot: { def: true },
matugenTemplateAlacritty: { def: true }, matugenTemplateAlacritty: { def: true },
matugenTemplateNeovim: { def: true },
matugenTemplateWezterm: { def: true }, matugenTemplateWezterm: { def: true },
matugenTemplateDgop: { def: true }, matugenTemplateDgop: { def: true },
matugenTemplateKcolorscheme: { def: true }, matugenTemplateKcolorscheme: { def: true },
@@ -258,6 +259,8 @@ var SPEC = {
displayNameMode: { def: "system" }, displayNameMode: { def: "system" },
screenPreferences: { def: {} }, screenPreferences: { def: {} },
showOnLastDisplay: { def: {} }, showOnLastDisplay: { def: {} },
niriOutputSettings: { def: {} },
hyprlandOutputSettings: { def: {} },
barConfigs: { def: [{ barConfigs: { def: [{
id: "default", id: "default",
@@ -298,7 +301,49 @@ var SPEC = {
scrollEnabled: true, scrollEnabled: true,
scrollXBehavior: "column", scrollXBehavior: "column",
scrollYBehavior: "workspace" 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() { function getValidKeys() {

View File

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

View File

@@ -83,6 +83,7 @@ Item {
function close() { function close() {
shouldBeVisible = false; shouldBeVisible = false;
shouldHaveFocus = false; shouldHaveFocus = false;
ModalManager.closeModal(root);
closeTimer.restart(); closeTimer.restart();
} }
@@ -90,6 +91,7 @@ Item {
animationsEnabled = false; animationsEnabled = false;
shouldBeVisible = false; shouldBeVisible = false;
shouldHaveFocus = false; shouldHaveFocus = false;
ModalManager.closeModal(root);
closeTimer.stop(); closeTimer.stop();
contentWindow.visible = false; contentWindow.visible = false;
if (useBackgroundWindow) if (useBackgroundWindow)
@@ -273,8 +275,8 @@ Item {
anchors { anchors {
left: true left: true
top: true top: true
right: root.useSingleWindow ? true : undefined right: root.useSingleWindow
bottom: root.useSingleWindow ? true : undefined bottom: root.useSingleWindow
} }
WlrLayershell.margins { WlrLayershell.margins {
@@ -284,8 +286,8 @@ Item {
bottom: 0 bottom: 0
} }
implicitWidth: root.useSingleWindow ? undefined : root.alignedWidth + (shadowBuffer * 2) implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
implicitHeight: root.useSingleWindow ? undefined : root.alignedHeight + (shadowBuffer * 2) implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {

View File

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

View File

@@ -7,10 +7,11 @@ DankModal {
id: root id: root
property string outputName: "" property string outputName: ""
property var position: undefined property var changes: []
property var mode: undefined property int countdown: 10
property var vrr: undefined
property int countdown: 15 signal confirmed
signal reverted
shouldBeVisible: false shouldBeVisible: false
allowStacking: true allowStacking: true
@@ -23,23 +24,27 @@ DankModal {
repeat: true repeat: true
running: root.shouldBeVisible running: root.shouldBeVisible
onTriggered: { onTriggered: {
countdown--; root.countdown--;
if (countdown <= 0) { if (root.countdown <= 0) {
revert(); root.reverted();
root.close();
} }
} }
} }
onOpened: { onOpened: {
countdown = 15; countdown = 10;
countdownTimer.start(); countdownTimer.start();
} }
onClosed: { onDialogClosed: {
countdownTimer.stop(); countdownTimer.stop();
} }
onBackgroundClicked: revert onBackgroundClicked: {
root.reverted();
root.close();
}
content: Component { content: Component {
FocusScope { FocusScope {
@@ -50,12 +55,14 @@ DankModal {
implicitHeight: mainColumn.implicitHeight implicitHeight: mainColumn.implicitHeight
Keys.onEscapePressed: event => { Keys.onEscapePressed: event => {
revert(); root.reverted();
root.close();
event.accepted = true; event.accepted = true;
} }
Keys.onReturnPressed: event => { Keys.onReturnPressed: event => {
confirm(); root.confirmed();
root.close();
event.accepted = true; event.accepted = true;
} }
@@ -69,81 +76,42 @@ DankModal {
anchors.topMargin: Theme.spacingM anchors.topMargin: Theme.spacingM
spacing: Theme.spacingM spacing: Theme.spacingM
Column { StyledText {
width: parent.width text: I18n.tr("Confirm Display Changes")
spacing: Theme.spacingXS font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
StyledText { font.weight: Font.Medium
text: I18n.tr("Confirm Display Changes")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: I18n.tr("Display settings for ") + outputName
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
}
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 80 height: 70
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest color: Theme.surfaceContainerHighest
Column { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 4 text: root.countdown + "s"
font.pixelSize: Theme.fontSizeXLarge * 1.5
StyledText { color: Theme.primary
text: I18n.tr("Reverting in:") font.weight: Font.Bold
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: countdown + "s"
font.pixelSize: Theme.fontSizeXLarge * 1.5
color: Theme.primary
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
} }
} }
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: root.changes.length > 0
StyledText { Repeater {
text: I18n.tr("Changes:") model: root.changes
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
}
StyledText { StyledText {
visible: position !== undefined && position !== null required property var modelData
text: I18n.tr("Position: ") + (position ? position.x + ", " + position.y : "") text: modelData
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceVariantText
} }
StyledText {
visible: mode !== undefined && mode !== null && mode !== ""
text: I18n.tr("Mode: ") + (mode || "")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
visible: vrr !== undefined && vrr !== null
text: I18n.tr("VRR: ") + (vrr ? I18n.tr("Enabled") : I18n.tr("Disabled"))
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
} }
} }
@@ -180,7 +148,10 @@ DankModal {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: revert onClicked: {
root.reverted();
root.close();
}
} }
} }
@@ -206,7 +177,10 @@ DankModal {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: confirm onClicked: {
root.confirmed();
root.close();
}
} }
Behavior on color { Behavior on color {
@@ -228,18 +202,11 @@ DankModal {
iconName: "close" iconName: "close"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
onClicked: revert onClicked: {
root.reverted();
root.close();
}
} }
} }
} }
function confirm() {
displaysTab.confirmChanges();
close();
}
function revert() {
displaysTab.revertChanges();
close();
}
} }

View File

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

View File

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

View File

@@ -5,6 +5,9 @@ import qs.Modules.Settings
FocusScope { FocusScope {
id: root id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
@@ -32,9 +35,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -48,9 +50,8 @@ FocusScope {
sourceComponent: TimeWeatherTab {} sourceComponent: TimeWeatherTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -66,9 +67,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -84,9 +84,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -100,9 +99,8 @@ FocusScope {
sourceComponent: WorkspacesTab {} sourceComponent: WorkspacesTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -118,25 +116,53 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
Loader { Loader {
id: displaysLoader id: displayConfigLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 6 active: root.currentIndex === 24
visible: active visible: active
focus: active focus: active
sourceComponent: DisplaysTab {} sourceComponent: DisplayConfigTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
Loader {
id: gammaControlLoader
anchors.fill: parent
active: root.currentIndex === 25
visible: active
focus: active
sourceComponent: GammaControlTab {}
onActiveChanged: {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
Loader {
id: displayWidgetsLoader
anchors.fill: parent
active: root.currentIndex === 26
visible: active
focus: active
sourceComponent: DisplayWidgetsTab {}
onActiveChanged: {
if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -150,9 +176,8 @@ FocusScope {
sourceComponent: NetworkTab {} sourceComponent: NetworkTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -166,9 +191,8 @@ FocusScope {
sourceComponent: PrinterTab {} sourceComponent: PrinterTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -182,9 +206,8 @@ FocusScope {
sourceComponent: LauncherTab {} sourceComponent: LauncherTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -198,9 +221,8 @@ FocusScope {
sourceComponent: ThemeColorsTab {} sourceComponent: ThemeColorsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -214,9 +236,8 @@ FocusScope {
sourceComponent: LockScreenTab {} sourceComponent: LockScreenTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -232,9 +253,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -248,9 +268,8 @@ FocusScope {
sourceComponent: AboutTab {} sourceComponent: AboutTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -264,9 +283,8 @@ FocusScope {
sourceComponent: TypographyMotionTab {} sourceComponent: TypographyMotionTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -280,9 +298,8 @@ FocusScope {
sourceComponent: SoundsTab {} sourceComponent: SoundsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -296,9 +313,8 @@ FocusScope {
sourceComponent: MediaPlayerTab {} sourceComponent: MediaPlayerTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -312,9 +328,8 @@ FocusScope {
sourceComponent: NotificationsTab {} sourceComponent: NotificationsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -328,9 +343,8 @@ FocusScope {
sourceComponent: OSDTab {} sourceComponent: OSDTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -344,9 +358,8 @@ FocusScope {
sourceComponent: RunningAppsTab {} sourceComponent: RunningAppsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -360,9 +373,8 @@ FocusScope {
sourceComponent: SystemUpdaterTab {} sourceComponent: SystemUpdaterTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -376,9 +388,8 @@ FocusScope {
sourceComponent: PowerSleepTab {} sourceComponent: PowerSleepTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -394,9 +405,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -410,9 +420,23 @@ FocusScope {
sourceComponent: ClipboardTab {} sourceComponent: ClipboardTab {}
onActiveChanged: { 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()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
} }

View File

@@ -58,7 +58,7 @@ FloatingWindow {
objectName: "settingsModal" objectName: "settingsModal"
title: I18n.tr("Settings", "settings window title") title: I18n.tr("Settings", "settings window title")
minimumSize: Qt.size(500, 400) minimumSize: Qt.size(500, 400)
implicitWidth: 800 implicitWidth: 900
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940 implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
color: Theme.surfaceContainer color: Theme.surfaceContainer
visible: false visible: false
@@ -135,6 +135,9 @@ FloatingWindow {
FocusScope { FocusScope {
id: contentFocusScope id: contentFocusScope
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
anchors.fill: parent anchors.fill: parent
focus: true focus: true
@@ -223,7 +226,7 @@ FloatingWindow {
SettingsSidebar { SettingsSidebar {
id: sidebar id: sidebar
x: 0 anchors.left: parent.left
width: settingsModal.isCompactMode ? parent.width : 270 width: settingsModal.isCompactMode ? parent.width : 270
visible: settingsModal.isCompactMode ? settingsModal.menuVisible : true visible: settingsModal.isCompactMode ? settingsModal.menuVisible : true
parentModal: settingsModal parentModal: settingsModal
@@ -238,8 +241,8 @@ FloatingWindow {
} }
Item { Item {
x: settingsModal.isCompactMode ? (settingsModal.menuVisible ? parent.width : 0) : sidebar.width anchors.left: settingsModal.isCompactMode ? (settingsModal.menuVisible ? sidebar.right : parent.left) : sidebar.right
width: settingsModal.isCompactMode ? parent.width : parent.width - sidebar.width anchors.right: parent.right
height: parent.height height: parent.height
clip: true clip: true
@@ -250,14 +253,6 @@ FloatingWindow {
parentModal: settingsModal parentModal: settingsModal
currentIndex: settingsModal.currentTabIndex 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 { Rectangle {
id: root id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
property var expandedCategories: ({}) property var expandedCategories: ({})
@@ -114,6 +117,12 @@ Rectangle {
"text": I18n.tr("System Updater"), "text": I18n.tr("System Updater"),
"icon": "refresh", "icon": "refresh",
"tabIndex": 20 "tabIndex": 20
},
{
"id": "desktop_widgets",
"text": I18n.tr("Desktop Widgets"),
"icon": "widgets",
"tabIndex": 27
} }
] ]
}, },
@@ -144,6 +153,32 @@ Rectangle {
"tabIndex": 2, "tabIndex": 2,
"shortcutsOnly": true "shortcutsOnly": true
}, },
{
"id": "displays",
"text": I18n.tr("Displays"),
"icon": "monitor",
"collapsedByDefault": true,
"children": [
{
"id": "display_config",
"text": I18n.tr("Configuration") + " (Beta)",
"icon": "display_settings",
"tabIndex": 24
},
{
"id": "display_gamma",
"text": I18n.tr("Gamma Control"),
"icon": "brightness_6",
"tabIndex": 25
},
{
"id": "display_widgets",
"text": I18n.tr("Widgets", "settings_displays"),
"icon": "widgets",
"tabIndex": 26
}
]
},
{ {
"id": "network", "id": "network",
"text": I18n.tr("Network"), "text": I18n.tr("Network"),
@@ -157,12 +192,6 @@ Rectangle {
"icon": "computer", "icon": "computer",
"collapsedByDefault": true, "collapsedByDefault": true,
"children": [ "children": [
{
"id": "displays",
"text": I18n.tr("Displays"),
"icon": "monitor",
"tabIndex": 6
},
{ {
"id": "printers", "id": "printers",
"text": I18n.tr("Printers"), "text": I18n.tr("Printers"),

View File

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

View File

@@ -95,6 +95,9 @@ DankPopout {
Rectangle { Rectangle {
id: launcherPanel id: launcherPanel
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property alias searchField: searchField property alias searchField: searchField
color: "transparent" color: "transparent"
@@ -179,8 +182,8 @@ DankPopout {
mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious(); mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious();
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") { if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow(); mappings[Qt.Key_Right] = () => I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
mappings[Qt.Key_Left] = () => appLauncher.selectPreviousInRow(); mappings[Qt.Key_Left] = () => I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
} }
return mappings; return mappings;
@@ -211,13 +214,13 @@ DankPopout {
return; return;
case Qt.Key_L: case Qt.Key_L:
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") { if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow(); I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
event.accepted = true; event.accepted = true;
} }
return; return;
case Qt.Key_H: case Qt.Key_H:
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") { if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow(); I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
event.accepted = true; event.accepted = true;
} }
return; 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") ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN" ccWidgetPrimaryText: I18n.tr("VPN")
ccWidgetSecondaryText: { ccWidgetSecondaryText: {
if (!DMSNetworkService.connected) if (!DMSNetworkService.connected)
return I18n.tr("Disconnected"); return I18n.tr("Disconnected");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell
import qs.Common import qs.Common
import qs.Modules.Plugins import qs.Modules.Plugins
import qs.Services import qs.Services
@@ -20,21 +19,21 @@ BasePill {
readonly property real minTooltipY: { readonly property real minTooltipY: {
if (!parentScreen || !isVerticalOrientation) { if (!parentScreen || !isVerticalOrientation) {
return 0 return 0;
} }
if (isAutoHideBar) { if (isAutoHideBar) {
return 0 return 0;
} }
if (parentScreen.y > 0) { if (parentScreen.y > 0) {
return barThickness + barSpacing return barThickness + barSpacing;
} }
return 0 return 0;
} }
signal toggleVpnPopup() signal toggleVpnPopup
content: Component { content: Component {
Item { Item {
@@ -72,56 +71,63 @@ BasePill {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: !DMSNetworkService.isBusy enabled: !DMSNetworkService.isBusy
onPressed: { onPressed: event => {
root.toggleVpnPopup() switch (event.button) {
case Qt.RightButton:
DMSNetworkService.toggleVpn();
return;
case Qt.LeftButton:
root.toggleVpnPopup();
return;
}
} }
onEntered: { onEntered: {
if (!root.parentScreen || (popoutTarget?.shouldBeVisible)) return if (!root.parentScreen || (popoutTarget?.shouldBeVisible))
return;
tooltipLoader.active = true tooltipLoader.active = true;
if (!tooltipLoader.item) return if (!tooltipLoader.item)
return;
let tooltipText = "" let tooltipText = "";
if (!DMSNetworkService.connected) { if (!DMSNetworkService.connected) {
tooltipText = "VPN Disconnected" tooltipText = "VPN Disconnected";
} else { } else {
const names = DMSNetworkService.activeNames || [] const names = DMSNetworkService.activeNames || [];
if (names.length <= 1) { if (names.length <= 1) {
const name = names[0] || "" const name = names[0] || "";
const maxLength = 25 const maxLength = 25;
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name;
tooltipText = "VPN Connected • " + displayName tooltipText = "VPN Connected • " + displayName;
} else { } else {
const name = names[0] const name = names[0];
const maxLength = 20 const maxLength = 20;
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name;
tooltipText = "VPN Connected • " + displayName + " +" + (names.length - 1) tooltipText = "VPN Connected • " + displayName + " +" + (names.length - 1);
} }
} }
if (root.isVerticalOrientation) { if (root.isVerticalOrientation) {
const globalPos = mapToGlobal(width / 2, height / 2) const globalPos = mapToGlobal(width / 2, height / 2);
const currentScreen = root.parentScreen || Screen const currentScreen = root.parentScreen || Screen;
const screenX = currentScreen ? currentScreen.x : 0 const screenX = currentScreen ? currentScreen.x : 0;
const screenY = currentScreen ? currentScreen.y : 0 const screenY = currentScreen ? currentScreen.y : 0;
const relativeY = globalPos.y - screenY const relativeY = globalPos.y - screenY;
const adjustedY = relativeY + root.minTooltipY 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 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" const isLeft = root.axis?.edge === "left";
tooltipLoader.item.show(tooltipText, screenX + tooltipX, adjustedY, currentScreen, isLeft, !isLeft) tooltipLoader.item.show(tooltipText, screenX + tooltipX, adjustedY, currentScreen, isLeft, !isLeft);
} else { } else {
const globalPos = mapToGlobal(width / 2, height) const globalPos = mapToGlobal(width / 2, height);
const tooltipY = root.barThickness + root.barSpacing + Theme.spacingXS const tooltipY = root.barThickness + root.barSpacing + Theme.spacingXS;
tooltipLoader.item.show(tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false) tooltipLoader.item.show(tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
} }
} }
onExited: { onExited: {
if (tooltipLoader.item) { 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; let isPlaceholder;
if (root.useExtWorkspace) { if (root.useExtWorkspace) {
isPlaceholder = modelData?.hidden === true; isPlaceholder = modelData?.hidden === true;
@@ -976,7 +976,7 @@ Item {
StyledText { StyledText {
id: wsIndexText id: wsIndexText
anchors.verticalCenter: parent.verticalCenter 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 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.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
@@ -1203,12 +1203,12 @@ Item {
Loader { Loader {
id: indexLoader id: indexLoader
anchors.fill: parent anchors.fill: parent
active: !isPlaceholder && SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps active: SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
sourceComponent: Item { sourceComponent: Item {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: { 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 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.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,12 @@
import QtQuick import QtQuick
import QtQuick.Controls
import qs.Common import qs.Common
Rectangle { Rectangle {
id: card id: card
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property int pad: Theme.spacingM property int pad: Theme.spacingM
radius: Theme.cornerRadius radius: Theme.cornerRadius

View File

@@ -1,20 +1,19 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Common import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.DankDash.Overview import qs.Modules.DankDash.Overview
Item { Item {
id: root id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
implicitWidth: 700 implicitWidth: 700
implicitHeight: 410 implicitHeight: 410
signal switchToWeatherTab() signal switchToWeatherTab
signal switchToMediaTab() signal switchToMediaTab
signal closeDash() signal closeDash
Item { Item {
anchors.fill: parent anchors.fill: parent

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