1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

Compare commits

...

133 Commits

Author SHA1 Message Date
johngalt
c281bf3b53 Adding Zen Browser matugen template (#1181)
* Adding Zen Browser matugen template

* Fixing indentation for matugen.go edits

* Trying to fix linting again..

* Tweaking contrasting surface colors in css, renamed file to match how firefox userchrome is named, also changed output directory to DMS config directory (like firefox)

* Modifing Zen userChrome again: removing unused css stuff, tweaking colors to better align with how pywalfox handles backgrounds/toolbars

* Last few tweaks on CSS - changing url bar highlight color, changing contrast on selected urls in dropdown

* matugen.go: fix check command for zen browser

* search_index: add zen browser setting
2025-12-28 12:52:41 -05:00
bbedward
45b8b2a895 clipboard: don't store sensitive mime types in history
fixes #1185
2025-12-28 11:13:34 -05:00
Tacticalsmooth
7b9ba840fb fixed lambda issue on nixos (#1188) 2025-12-28 12:50:07 +01:00
xxyangyoulin
84fb567ff5 Notification: Right-click to toggle Do Not Disturb (#1174) 2025-12-28 00:34:52 -05:00
Lucas
bbd57e0673 nix: remove dgop input; expose quickshell package (#1183)
* nix: remove dgop input

* nix: update quickshell

* nix: expose quickshell in outputs
2025-12-27 20:41:26 -05:00
vha
b1632a0a03 Fix #1179 normal scrolling direction (#1182)
* added reverse scrolling to settings and widget

* added support for dankbar scrolling

* Better settings description

* removed isNiri conditional from search index

* fix #1179 breaking normal scroll direction
2025-12-27 18:23:31 -05:00
bbedward
7aff1182c8 desktop widgets: easier copy/delete 2025-12-27 14:13:29 -05:00
vha
fbe362cd20 feat: Added reverse workspace scrolling (#1179)
* added reverse scrolling to settings and widget

* added support for dankbar scrolling

* Better settings description

* removed isNiri conditional from search index
2025-12-27 13:47:50 -05:00
bbedward
e7f94c94cc i18n: fix RTL alignment of settings sidebar 2025-12-27 12:51:10 -05:00
bbedward
7523190b16 i18n: add farsi 2025-12-27 12:46:40 -05:00
bbedward
da37e16b6e window: remove unused signal 2025-12-27 12:32:42 -05:00
xxyangyoulin
5c420ab50b AppSearch: Add ID search fallback for non-English / non-standard apps (#1173) 2025-12-27 12:26:43 -05:00
Joaquim S.
4493b7c231 matugen/template: Improve on generated theme + using dank16 instead (#1177) 2025-12-27 11:14:18 -05:00
bbedward
40a96c6eaf vpn: initialize slices so they dont serialize as null 2025-12-27 10:52:34 -05:00
bbedward
23a93082c6 fix trailing whitespace 2025-12-27 09:43:42 -05:00
Tacticalsmooth
f7650b5e1f Added mirroring option to display configuration (#1156)
* <quickshell/Modules/Settings/DisplayConfig>: Added mirroring option to the displayconfiguration

* removed niri option for mirroring

* Fix trailing whitespace

* removed emty rows

---------

Co-authored-by: Postboote1 <stoessel.matthias>
2025-12-27 09:27:19 -05:00
bbedward
3ebdd5631c dankdash: fix binding breaking when clicking overview card
fixes #1170
2025-12-27 00:58:47 -05:00
Linken Quy Dinh
6c4caf121a add seconds to wallpaper cycling (#1169) 2025-12-27 00:53:48 -05:00
bbedward
89788e9ca7 workspace: chagne pill hover color 2025-12-27 00:53:01 -05:00
bbedward
0787c63fed bar: change widget base hover blend logic 2025-12-27 00:49:11 -05:00
bbedward
9fc0d5efff settings: add index extractor script for search 2025-12-26 22:21:20 -05:00
bbedward
6611dfbe05 settings: fix search height 2025-12-26 20:44:35 -05:00
bbedward
8a71ead51d themes: remove catpuccin, support accent colors 2025-12-26 20:28:55 -05:00
bbedward
d9d6ab5776 settings: add search
- because tabs arent loaded at runtime, we have to have a separate index
- Less ideal, but functional enough for now
2025-12-26 19:19:47 -05:00
bbedward
d6fe7bea27 vpn: remove redundant property definitions 2025-12-26 19:02:16 -05:00
xxyangyoulin
1194f3ffb8 media: add scroll wheel behavior configuration (#1160) 2025-12-26 14:43:47 -05:00
bbedward
5ac81e6dd6 dankbar: dont apply exclusive zone to popup positioning 2025-12-26 14:38:34 -05:00
Lucas
987856a1de nix: update flake inputs (#1161) 2025-12-26 14:15:22 -05:00
bbedward
ef52ce0990 themes: support for variants 2025-12-26 14:00:14 -05:00
bbedward
06b14a5869 dankinstall: fix plasma session collision 2025-12-26 13:06:21 -05:00
bbedward
fd839059c0 popout: use mapToItem instead of mapToGlobal for popout positioning
fixes #1152
2025-12-25 12:50:26 -05:00
bbedward
ec6db7962a i18n: sync terms 2025-12-25 12:09:40 -05:00
Aaron Tulino
adf92cbc46 Add battery charge limit (#1151) 2025-12-25 12:09:09 -05:00
Aaron Tulino
6b6f51cd1f Add volume and brightness percentages (#1148) 2025-12-25 12:07:29 -05:00
Aaron Tulino
df6c60213f Use volume_mute icon for volume==0 (#1150) 2025-12-25 11:31:05 -05:00
Aaron Tulino
6303304a10 Allow toggling mute with right-click on bar (#1147) 2025-12-24 16:12:47 -05:00
Aaron Tulino
8e76789119 Fix touchpad scrolling behavior (#1146)
* Fix touchpad scrolling behavior

* Make touchpad scroll smoothly
For a normal mouse wheel, adjusting by 5% per scroll makes sense. For a touchpad, however, it should adjust by the smallest increment possible for a smooth experience.
2025-12-24 16:12:34 -05:00
Aaron Tulino
10e81cfdd3 Clear lock screen textbox on Escape key press (#1139) 2025-12-24 09:47:55 -05:00
Aaron Tulino
03fd3a4f16 Add Do Not Disturb to IPC (#1140) 2025-12-24 09:47:44 -05:00
bbedward
8fdc748ed2 weather: fix icons 2025-12-24 00:33:54 -05:00
bbedward
6c56d23b93 themes: fix terminals always dark with custom themes 2025-12-23 22:33:33 -05:00
bbedward
45d34dcb5b themes: consistent usage of primaryPressed 2025-12-23 21:45:06 -05:00
bbedward
d7ac0d50fa launcher: use primaryPressed for hover 2025-12-23 21:36:59 -05:00
bbedward
1d4d145187 desktop plugins: enable by default 2025-12-23 14:10:55 -05:00
bbedward
a5b9ff98c0 displays: explicitly write scale 1 for niri
fixes #1116
2025-12-23 11:53:23 -05:00
bbedward
6feaecd92e niri: add gaps and radius override 2025-12-23 11:00:20 -05:00
bbedward
b066a25308 dankdash: use CachingImage in wallpaper tab
fixes #1130
2025-12-23 10:47:50 -05:00
bbedward
777a552b57 spotlight: restore darken background option
fixes #1126
2025-12-23 10:45:11 -05:00
bbedward
7dbe608c28 settings: fix theme application of default-settings json 2025-12-23 10:33:14 -05:00
bbedward
61630e447b desktop-widgets: add overlay IPC and overview option 2025-12-22 20:29:01 -05:00
bbedward
91385e7c83 dankbar: option to show when bar is hidden and no windows 2025-12-22 19:54:53 -05:00
bbedward
04648fcca7 spotlight: remove darken bg opt, improve performance 2025-12-22 16:07:40 -05:00
bbedward
080fc7e44e i18n: term update 2025-12-22 14:37:47 -05:00
bbedward
0b60da3d6d dock: add isolate runninig apps by display option 2025-12-22 14:35:04 -05:00
claymorwan
a4492b90e7 matugen: fix equibop theme not working (#1122)
* matugen: equibop theme

* style: withespace apparently

* matugen: fix equibop theme not working
2025-12-22 14:19:57 -05:00
bbedward
c9331b7338 dropdown: improve perf + add fuzzy search to printers 2025-12-22 14:18:24 -05:00
bbedward
4982ea53dd window: add support for startSystemMove, resize, maximize to floating
windows
2025-12-22 13:18:37 -05:00
claymorwan
c703cb6504 matugen: equibop theme (#1119)
* matugen: equibop theme

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

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

First test. Seems to work but probably broke something.

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

View File

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

View File

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

View File

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

View File

@@ -51,7 +51,8 @@ jobs:
check_stable_package() {
local PKG="$1"
local PPA_NAME="$2"
local LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "v\?\([^"]*\)".*/\1/' || echo "")
# Use git ls-remote to find the latest tag, sorted by version (descending)
local LATEST_TAG=$(git ls-remote --tags --refs --sort='-v:refname' https://github.com/AvengeMedia/DankMaterialShell.git | head -n1 | awk -F/ '{print $NF}' | sed 's/^v//')
local PPA_VERSION=$(curl -s "https://api.launchpad.net/1.0/~avengemedia/+archive/ubuntu/$PPA_NAME?ws.op=getPublishedSources&source_name=$PKG&status=Published" | grep -oP '"source_package_version":\s*"\K[^"]+' | head -1 || echo "")
local PPA_BASE_VERSION=$(echo "$PPA_VERSION" | sed 's/ppa[0-9]*$//')

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
[Desktop Entry]
Type=Application
Name=DMS Application Picker
Name=DMS
Comment=Select an application to open links and files
Exec=dms open %u
Icon=danklogo
Terminal=false
NoDisplay=true
MimeType=x-scheme-handler/http;x-scheme-handler/https;text/html;application/xhtml+xml;
MimeType=x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/dms;text/html;application/xhtml+xml;
Categories=Utility;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -131,6 +131,12 @@ func runOpen(target string) {
detectedRequestType = "url"
}
log.Infof("Detected HTTP(S) URL")
} else if strings.HasPrefix(target, "dms://") {
// Handle DMS internal URLs (theme/plugin install, etc.)
if detectedRequestType == "" {
detectedRequestType = "url"
}
log.Infof("Detected DMS internal URL")
} else if _, err := os.Stat(target); err == nil {
// Handle local file paths directly (not file:// URIs)
// Convert to absolute path
@@ -177,7 +183,7 @@ func runOpen(target string) {
}
method := "apppicker.open"
if detectedMimeType == "" && len(detectedCategories) == 0 && (strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://")) {
if detectedMimeType == "" && len(detectedCategories) == 0 && (strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") || strings.HasPrefix(target, "dms://")) {
method = "browser.open"
params["url"] = target
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -550,10 +550,7 @@ func (b *BaseDistribution) WriteEnvironmentConfig(terminal deps.Terminal) error
terminalCmd = "ghostty"
}
content := fmt.Sprintf(`QT_QPA_PLATFORM=wayland
ELECTRON_OZONE_PLATFORM_HINT=auto
QT_QPA_PLATFORMTHEME=gtk3
QT_QPA_PLATFORMTHEME_QT6=gtk3
content := fmt.Sprintf(`ELECTRON_OZONE_PLATFORM_HINT=auto
TERMINAL=%s
`, terminalCmd)
@@ -567,12 +564,6 @@ TERMINAL=%s
}
func (b *BaseDistribution) EnableDMSService(ctx context.Context, wm deps.WindowManager) error {
cmd := exec.CommandContext(ctx, "systemctl", "--user", "enable", "--now", "dms")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to enable dms service: %w", err)
}
b.log("Enabled dms systemd user service")
switch wm {
case deps.WindowManagerNiri:
if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "niri.service", "dms").Run(); err != nil {

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os/exec"
"runtime"
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
@@ -384,6 +385,8 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
debianVersion := "Debian_13"
if osInfo.VersionID == "testing" {
debianVersion = "Debian_Testing"
} else if osInfo.VersionCodename == "sid" || osInfo.VersionID == "sid" || strings.Contains(strings.ToLower(osInfo.PrettyName), "sid") || strings.Contains(strings.ToLower(osInfo.PrettyName), "unstable") {
debianVersion = "Debian_Unstable"
}
for _, pkg := range obsPkgs {
@@ -427,7 +430,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
}
// Add repository
repoLine := fmt.Sprintf("deb [signed-by=%s] %s/ /", keyringPath, baseURL)
repoLine := fmt.Sprintf("deb [signed-by=%s, arch=%s] %s/ /", keyringPath, runtime.GOARCH, baseURL)
progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages,

View File

@@ -15,6 +15,12 @@ func init() {
Register("opensuse-tumbleweed", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
return NewOpenSUSEDistribution(config, logChan)
})
Register("opensuse-leap", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
return NewOpenSUSEDistribution(config, logChan)
})
Register("opensuse-slowroll", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
return NewOpenSUSEDistribution(config, logChan)
})
}
type OpenSUSEDistribution struct {
@@ -434,6 +440,19 @@ func (o *OpenSUSEDistribution) extractPackageNames(packages []PackageMapping) []
func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
enabledRepos := make(map[string]bool)
osInfo, err := GetOSInfo()
if err != nil {
return fmt.Errorf("failed to get OS info: %w", err)
}
obsDistroVersion := "openSUSE_Tumbleweed"
switch osInfo.Distribution.ID {
case "opensuse-leap":
obsDistroVersion = fmt.Sprintf("openSUSE_Leap_%s", osInfo.VersionID)
case "opensuse-slowroll":
obsDistroVersion = "openSUSE_Slowroll"
}
for _, pkg := range obsPkgs {
if pkg.RepoURL != "" && !enabledRepos[pkg.RepoURL] {
o.log(fmt.Sprintf("Enabling OBS repository: %s", pkg.RepoURL))
@@ -441,8 +460,8 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
// RepoURL format: "home:AvengeMedia:danklinux"
repoPath := strings.ReplaceAll(pkg.RepoURL, ":", ":/")
repoName := strings.ReplaceAll(pkg.RepoURL, ":", "-")
repoURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/openSUSE_Tumbleweed/%s.repo",
repoPath, pkg.RepoURL)
repoURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/%s/%s.repo",
repoPath, obsDistroVersion, pkg.RepoURL)
checkCmd := exec.CommandContext(ctx, "zypper", "repos", repoName)
if checkCmd.Run() == nil {

View File

@@ -19,11 +19,12 @@ type DistroInfo struct {
// OSInfo contains complete OS information
type OSInfo struct {
Distribution DistroInfo
Version string
VersionID string
PrettyName string
Architecture string
Distribution DistroInfo
Version string
VersionID string
VersionCodename string
PrettyName string
Architecture string
}
// GetOSInfo detects the current OS and returns information about it
@@ -72,6 +73,8 @@ func GetOSInfo() (*OSInfo, error) {
info.VersionID = value
case "VERSION":
info.Version = value
case "VERSION_CODENAME":
info.VersionCodename = value
case "PRETTY_NAME":
info.PrettyName = value
}
@@ -100,6 +103,10 @@ func IsUnsupportedDistro(distroID, versionID string) bool {
}
if distroID == "debian" {
// unstable/sid support
if versionID == "sid" {
return false
}
if versionID == "" {
// debian testing/sid have no version ID
return false

View File

@@ -255,10 +255,15 @@ output_path = '%s'
if !opts.ShouldSkipTemplate("pywalfox") {
appendConfig(opts, cfgFile, "pywalfox", "pywalfox.toml")
}
if !opts.ShouldSkipTemplate("zenbrowser") {
appendConfig(opts, cfgFile, "zen", "zenbrowser.toml")
}
if !opts.ShouldSkipTemplate("vesktop") {
appendConfig(opts, cfgFile, "vesktop", "vesktop.toml")
}
if !opts.ShouldSkipTemplate("equibop") {
appendConfig(opts, cfgFile, "equibop", "equibop.toml")
}
if !opts.ShouldSkipTemplate("ghostty") {
appendTerminalConfig(opts, cfgFile, tmpDir, "ghostty", "ghostty.toml")
}
@@ -274,6 +279,9 @@ output_path = '%s'
if !opts.ShouldSkipTemplate("wezterm") {
appendTerminalConfig(opts, cfgFile, tmpDir, "wezterm", "wezterm.toml")
}
if !opts.ShouldSkipTemplate("nvim") {
appendTerminalConfig(opts, cfgFile, tmpDir, "nvim", "neovim.toml")
}
if !opts.ShouldSkipTemplate("dgop") {
appendConfig(opts, cfgFile, "dgop", "dgop.toml")

View File

@@ -238,7 +238,7 @@ func (i *ZwlrOutputManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
l := 0
objectID := client.Uint32(data[l : l+4])
proxy := i.Context().GetProxy(objectID)
if proxy == nil {
if proxy == nil || proxy.IsZombie() {
head := &ZwlrOutputHeadV1{}
head.SetContext(i.Context())
head.SetID(objectID)
@@ -723,7 +723,7 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
l := 0
objectID := client.Uint32(data[l : l+4])
proxy := i.Context().GetProxy(objectID)
if proxy == nil {
if proxy == nil || proxy.IsZombie() {
mode := &ZwlrOutputModeV1{}
mode.SetContext(i.Context())
mode.SetID(objectID)
@@ -761,8 +761,8 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
l := 0
objectID := client.Uint32(data[l : l+4])
proxy := i.Context().GetProxy(objectID)
if proxy == nil {
// Mode not yet registered, create it
if proxy == nil || proxy.IsZombie() {
// Mode not yet registered or zombie, create fresh
mode := &ZwlrOutputModeV1{}
mode.SetContext(i.Context())
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 {
cfg.DisableHistory = v
}
if v, ok := req.Params["disablePersist"].(bool); ok {
cfg.DisablePersist = v
}
if err := m.SetConfig(cfg); err != nil {
models.RespondError(conn, req.ID, err.Error())

View File

@@ -11,14 +11,16 @@ import (
"io"
"os"
"path/filepath"
"slices"
"strings"
"syscall"
"time"
"hash/fnv"
"github.com/fsnotify/fsnotify"
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/tiff"
"hash/fnv"
bolt "go.etcd.io/bbolt"
@@ -28,6 +30,11 @@ import (
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
// These mime types wont be stored in history
var sensitiveMimeTypes = []string{
"x-kde-passwordManagerHint",
}
func NewManager(wlCtx wlcontext.WaylandContext, config Config) (*Manager, error) {
if config.Disabled {
return nil, fmt.Errorf("clipboard disabled in config")
@@ -253,6 +260,10 @@ func (m *Manager) setupDataDeviceSync() {
return
}
if m.hasSensitiveMimeType(mimes) {
return
}
preferredMime := m.selectMimeType(mimes)
if preferredMime == "" {
return
@@ -319,10 +330,6 @@ func (m *Manager) readAndStore(r *os.File, mimeType string) {
m.storeClipboardEntry(data, mimeType)
}
if !cfg.DisablePersist {
m.persistClipboard([]string{mimeType}, map[string][]byte{mimeType: data})
}
m.updateState()
m.notifySubscribers()
}
@@ -348,105 +355,6 @@ func (m *Manager) storeClipboardEntry(data []byte, mimeType string) {
}
}
func (m *Manager) persistClipboard(mimeTypes []string, data map[string][]byte) {
m.persistMutex.Lock()
m.persistMimeTypes = mimeTypes
m.persistData = data
m.persistMutex.Unlock()
m.post(func() {
m.takePersistOwnership()
})
}
func (m *Manager) takePersistOwnership() {
if m.dataControlMgr == nil || m.dataDevice == nil {
return
}
if m.getConfig().DisablePersist {
return
}
m.persistMutex.RLock()
mimeTypes := m.persistMimeTypes
m.persistMutex.RUnlock()
if len(mimeTypes) == 0 {
return
}
dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
source, err := dataMgr.CreateDataSource()
if err != nil {
log.Errorf("Failed to create persist source: %v", err)
return
}
for _, mime := range mimeTypes {
if err := source.Offer(mime); err != nil {
log.Errorf("Failed to offer mime type %s: %v", mime, err)
}
}
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
fd := e.Fd
defer syscall.Close(fd)
m.persistMutex.RLock()
d := m.persistData[e.MimeType]
m.persistMutex.RUnlock()
if len(d) == 0 {
return
}
file := os.NewFile(uintptr(fd), "clipboard-pipe")
defer file.Close()
file.Write(d)
})
source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) {
m.ownerLock.Lock()
m.isOwner = false
m.ownerLock.Unlock()
})
if m.currentSource != nil {
oldSource := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
oldSource.Destroy()
}
m.currentSource = source
device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1)
if err := device.SetSelection(source); err != nil {
log.Errorf("Failed to set persist selection: %v", err)
return
}
m.ownerLock.Lock()
m.isOwner = true
m.ownerLock.Unlock()
}
func (m *Manager) releaseOwnership() {
m.ownerLock.Lock()
m.isOwner = false
m.ownerLock.Unlock()
m.persistMutex.Lock()
m.persistData = nil
m.persistMimeTypes = nil
m.persistMutex.Unlock()
if m.currentSource != nil {
source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
source.Destroy()
m.currentSource = nil
}
}
func (m *Manager) storeEntry(entry Entry) error {
if m.db == nil {
return fmt.Errorf("database not available")
@@ -495,6 +403,9 @@ func (m *Manager) deduplicateInTx(b *bolt.Bucket, hash uint64) error {
}
func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
if m.config.MaxHistory < 0 {
return nil
}
c := b.Cursor()
var count int
for k, _ := c.Last(); k != nil; k, _ = c.Prev() {
@@ -592,6 +503,12 @@ func extractHash(data []byte) uint64 {
return binary.BigEndian.Uint64(data[len(data)-8:])
}
func (m *Manager) hasSensitiveMimeType(mimes []string) bool {
return slices.ContainsFunc(mimes, func(mime string) bool {
return slices.Contains(sensitiveMimeTypes, mime)
})
}
func (m *Manager) selectMimeType(mimes []string) string {
preferredTypes := []string{
"text/plain;charset=utf-8",
@@ -1309,13 +1226,7 @@ func (m *Manager) applyConfigChange(newCfg Config) {
}
}
if newCfg.DisablePersist && !oldCfg.DisablePersist {
log.Info("Clipboard persist disabled, releasing ownership")
m.releaseOwnership()
}
log.Infof("Clipboard config reloaded: disableHistory=%v disablePersist=%v",
newCfg.DisableHistory, newCfg.DisablePersist)
log.Infof("Clipboard config reloaded: disableHistory=%v", newCfg.DisableHistory)
m.updateState()
m.notifySubscribers()

View File

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

View File

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

View File

@@ -2,6 +2,8 @@ package cups
import (
"errors"
"net"
"net/url"
"strings"
"time"
@@ -156,9 +158,42 @@ func (m *Manager) PurgeJobs(printerName string) error {
return err
}
func resolveIPFromURI(uri string) string {
parsed, err := url.Parse(uri)
if err != nil {
return ""
}
host := parsed.Hostname()
if host == "" {
return ""
}
if ip := net.ParseIP(host); ip != nil {
return ip.String()
}
addrs, err := net.LookupIP(host)
if err != nil || len(addrs) == 0 {
return ""
}
for _, addr := range addrs {
if v4 := addr.To4(); v4 != nil {
return v4.String()
}
}
return addrs[0].String()
}
func (m *Manager) GetDevices() ([]Device, error) {
if m.pkHelper != nil {
return m.pkHelper.DevicesGet(10, 0, nil, nil)
devices, err := m.pkHelper.DevicesGet(10, 0, nil, nil)
if err != nil {
return nil, err
}
for i := range devices {
if devices[i].Class == "network" {
devices[i].IP = resolveIPFromURI(devices[i].URI)
}
}
return devices, nil
}
deviceAttrs, err := m.client.GetDevices()
@@ -176,6 +211,9 @@ func (m *Manager) GetDevices() ([]Device, error) {
ID: getStringAttr(attrs, "device-id"),
Location: getStringAttr(attrs, "device-location"),
}
if device.Class == "network" {
device.IP = resolveIPFromURI(uri)
}
devices = append(devices, device)
}

View File

@@ -42,6 +42,7 @@ type Device struct {
MakeModel string `json:"makeModel"`
ID string `json:"id"`
Location string `json:"location"`
IP string `json:"ip,omitempty"`
}
type PPD struct {

View File

@@ -33,7 +33,7 @@ func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
return nil, fmt.Errorf("failed to get connections: %w", err)
}
var profiles []VPNProfile
profiles := []VPNProfile{}
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
@@ -101,7 +101,7 @@ func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
return nil, fmt.Errorf("failed to get active connections: %w", err)
}
var active []VPNActive
active := []VPNActive{}
for _, activeConn := range activeConns {
connType, err := activeConn.GetPropertyType()
if err != nil {

View File

@@ -18,6 +18,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
serverPlugins "github.com/AvengeMedia/DankMaterialShell/core/internal/server/plugins"
serverThemes "github.com/AvengeMedia/DankMaterialShell/core/internal/server/themes"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
)
@@ -37,6 +38,11 @@ func RouteRequest(conn net.Conn, req models.Request) {
return
}
if strings.HasPrefix(req.Method, "themes.") {
serverThemes.HandleRequest(conn, req)
return
}
if strings.HasPrefix(req.Method, "loginctl.") {
if loginctlManager == nil {
models.RespondError(conn, req.ID, "loginctl manager not initialized")
@@ -204,9 +210,6 @@ func handleClipboardSetConfig(conn net.Conn, req models.Request) {
if v, ok := req.Params["disableHistory"].(bool); ok {
cfg.DisableHistory = v
}
if v, ok := req.Params["disablePersist"].(bool); ok {
cfg.DisablePersist = v
}
if err := clipboard.SaveConfig(cfg); err != nil {
models.RespondError(conn, req.ID, err.Error())

View File

@@ -0,0 +1,27 @@
package themes
import (
"fmt"
"net"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
)
func HandleRequest(conn net.Conn, req models.Request) {
switch req.Method {
case "themes.list":
HandleList(conn, req)
case "themes.listInstalled":
HandleListInstalled(conn, req)
case "themes.install":
HandleInstall(conn, req)
case "themes.uninstall":
HandleUninstall(conn, req)
case "themes.update":
HandleUpdate(conn, req)
case "themes.search":
HandleSearch(conn, req)
default:
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
}
}

View File

@@ -0,0 +1,52 @@
package themes
import (
"fmt"
"net"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
)
func HandleInstall(conn net.Conn, req models.Request) {
idOrName, ok := req.Params["name"].(string)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
return
}
registry, err := themes.NewRegistry()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
return
}
themeList, err := registry.List()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
return
}
theme := themes.FindByIDOrName(idOrName, themeList)
if theme == nil {
models.RespondError(conn, req.ID, fmt.Sprintf("theme not found: %s", idOrName))
return
}
manager, err := themes.NewManager()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
return
}
registryThemeDir := registry.GetThemeDir(theme.SourceDir)
if err := manager.Install(*theme, registryThemeDir); err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to install theme: %v", err))
return
}
models.Respond(conn, req.ID, models.SuccessResult{
Success: true,
Message: fmt.Sprintf("theme installed: %s", theme.Name),
})
}

View File

@@ -0,0 +1,54 @@
package themes
import (
"fmt"
"net"
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
)
func HandleList(conn net.Conn, req models.Request) {
registry, err := themes.NewRegistry()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
return
}
themeList, err := registry.List()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
return
}
manager, err := themes.NewManager()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
return
}
result := make([]ThemeInfo, len(themeList))
for i, t := range themeList {
installed, _ := manager.IsInstalled(t)
info := ThemeInfo{
ID: t.ID,
Name: t.Name,
Version: t.Version,
Author: t.Author,
Description: t.Description,
PreviewPath: t.PreviewPath,
SourceDir: t.SourceDir,
Installed: installed,
FirstParty: isFirstParty(t.Author),
}
addVariantsInfo(&info, t.Variants)
result[i] = info
}
models.Respond(conn, req.ID, result)
}
func isFirstParty(author string) bool {
return strings.EqualFold(author, "Avenge Media") || strings.EqualFold(author, "AvengeMedia")
}

View File

@@ -0,0 +1,157 @@
package themes
import (
"fmt"
"net"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
)
func addVariantsInfo(info *ThemeInfo, variants *themes.ThemeVariants) {
if variants == nil {
return
}
if variants.Type == "multi" {
if len(variants.Flavors) == 0 && len(variants.Accents) == 0 {
return
}
info.HasVariants = true
info.Variants = &VariantsInfo{
Type: "multi",
Flavors: make([]FlavorInfo, len(variants.Flavors)),
Accents: make([]AccentInfo, len(variants.Accents)),
}
if variants.Defaults != nil {
info.Variants.Defaults = &MultiDefaults{
Dark: variants.Defaults.Dark,
Light: variants.Defaults.Light,
}
}
for i, f := range variants.Flavors {
mode := ""
switch {
case f.Dark.Primary != "" && f.Light.Primary != "":
mode = "both"
case f.Dark.Primary != "":
mode = "dark"
case f.Light.Primary != "":
mode = "light"
default:
if f.Dark.Surface != "" {
mode = "dark"
} else if f.Light.Surface != "" {
mode = "light"
}
}
info.Variants.Flavors[i] = FlavorInfo{ID: f.ID, Name: f.Name, Mode: mode}
}
for i, a := range variants.Accents {
color := ""
if colors, ok := a.FlavorColors["mocha"]; ok && colors.Primary != "" {
color = colors.Primary
} else if colors, ok := a.FlavorColors["latte"]; ok && colors.Primary != "" {
color = colors.Primary
} else {
for _, c := range a.FlavorColors {
if c.Primary != "" {
color = c.Primary
break
}
}
}
info.Variants.Accents[i] = AccentInfo{ID: a.ID, Name: a.Name, Color: color}
}
return
}
if len(variants.Options) == 0 {
return
}
info.HasVariants = true
info.Variants = &VariantsInfo{
Default: variants.Default,
Options: make([]VariantInfo, len(variants.Options)),
}
for i, v := range variants.Options {
info.Variants.Options[i] = VariantInfo{ID: v.ID, Name: v.Name}
}
}
func HandleListInstalled(conn net.Conn, req models.Request) {
manager, err := themes.NewManager()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
return
}
installedIDs, err := manager.ListInstalled()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list installed themes: %v", err))
return
}
registry, err := themes.NewRegistry()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
return
}
allThemes, err := registry.List()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
return
}
themeMap := make(map[string]themes.Theme)
for _, t := range allThemes {
themeMap[t.ID] = t
}
result := make([]ThemeInfo, 0, len(installedIDs))
for _, id := range installedIDs {
if theme, ok := themeMap[id]; ok {
hasUpdate := false
if hasUpdates, err := manager.HasUpdates(id, theme); err == nil {
hasUpdate = hasUpdates
}
info := ThemeInfo{
ID: theme.ID,
Name: theme.Name,
Version: theme.Version,
Author: theme.Author,
Description: theme.Description,
SourceDir: id,
FirstParty: isFirstParty(theme.Author),
HasUpdate: hasUpdate,
}
addVariantsInfo(&info, theme.Variants)
result = append(result, info)
} else {
installed, err := manager.GetInstalledTheme(id)
if err != nil {
result = append(result, ThemeInfo{
ID: id,
Name: id,
SourceDir: id,
})
continue
}
info := ThemeInfo{
ID: installed.ID,
Name: installed.Name,
Version: installed.Version,
Author: installed.Author,
Description: installed.Description,
SourceDir: id,
FirstParty: isFirstParty(installed.Author),
}
addVariantsInfo(&info, installed.Variants)
result = append(result, info)
}
}
models.Respond(conn, req.ID, result)
}

View File

@@ -0,0 +1,53 @@
package themes
import (
"fmt"
"net"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
)
func HandleSearch(conn net.Conn, req models.Request) {
query, ok := req.Params["query"].(string)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'query' parameter")
return
}
registry, err := themes.NewRegistry()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
return
}
themeList, err := registry.List()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
return
}
searchResults := themes.FuzzySearch(query, themeList)
manager, err := themes.NewManager()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
return
}
result := make([]ThemeInfo, len(searchResults))
for i, t := range searchResults {
installed, _ := manager.IsInstalled(t)
result[i] = ThemeInfo{
ID: t.ID,
Name: t.Name,
Version: t.Version,
Author: t.Author,
Description: t.Description,
Installed: installed,
FirstParty: isFirstParty(t.Author),
}
}
models.Respond(conn, req.ID, result)
}

View File

@@ -0,0 +1,47 @@
package themes
type VariantInfo struct {
ID string `json:"id"`
Name string `json:"name"`
}
type FlavorInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Mode string `json:"mode,omitempty"`
}
type AccentInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Color string `json:"color,omitempty"`
}
type MultiDefaults struct {
Dark map[string]string `json:"dark,omitempty"`
Light map[string]string `json:"light,omitempty"`
}
type VariantsInfo struct {
Type string `json:"type,omitempty"`
Default string `json:"default,omitempty"`
Defaults *MultiDefaults `json:"defaults,omitempty"`
Options []VariantInfo `json:"options,omitempty"`
Flavors []FlavorInfo `json:"flavors,omitempty"`
Accents []AccentInfo `json:"accents,omitempty"`
}
type ThemeInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Author string `json:"author,omitempty"`
Description string `json:"description,omitempty"`
PreviewPath string `json:"previewPath,omitempty"`
SourceDir string `json:"sourceDir,omitempty"`
Installed bool `json:"installed,omitempty"`
FirstParty bool `json:"firstParty,omitempty"`
HasUpdate bool `json:"hasUpdate,omitempty"`
HasVariants bool `json:"hasVariants,omitempty"`
Variants *VariantsInfo `json:"variants,omitempty"`
}

View File

@@ -0,0 +1,63 @@
package themes
import (
"fmt"
"net"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
)
func HandleUninstall(conn net.Conn, req models.Request) {
idOrName, ok := req.Params["name"].(string)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
return
}
manager, err := themes.NewManager()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
return
}
registry, err := themes.NewRegistry()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
return
}
themeList, _ := registry.List()
theme := themes.FindByIDOrName(idOrName, themeList)
if theme != nil {
installed, err := manager.IsInstalled(*theme)
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if theme is installed: %v", err))
return
}
if !installed {
models.RespondError(conn, req.ID, fmt.Sprintf("theme not installed: %s", idOrName))
return
}
if err := manager.Uninstall(*theme); err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall theme: %v", err))
return
}
models.Respond(conn, req.ID, models.SuccessResult{
Success: true,
Message: fmt.Sprintf("theme uninstalled: %s", theme.Name),
})
return
}
if err := manager.UninstallByID(idOrName); err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("theme not found: %s", idOrName))
return
}
models.Respond(conn, req.ID, models.SuccessResult{
Success: true,
Message: fmt.Sprintf("theme uninstalled: %s", idOrName),
})
}

View File

@@ -0,0 +1,57 @@
package themes
import (
"fmt"
"net"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
)
func HandleUpdate(conn net.Conn, req models.Request) {
idOrName, ok := req.Params["name"].(string)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
return
}
manager, err := themes.NewManager()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
return
}
registry, err := themes.NewRegistry()
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
return
}
themeList, _ := registry.List()
theme := themes.FindByIDOrName(idOrName, themeList)
if theme == nil {
models.RespondError(conn, req.ID, fmt.Sprintf("theme not found in registry: %s", idOrName))
return
}
installed, err := manager.IsInstalled(*theme)
if err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if theme is installed: %v", err))
return
}
if !installed {
models.RespondError(conn, req.ID, fmt.Sprintf("theme not installed: %s", idOrName))
return
}
if err := manager.Update(*theme); err != nil {
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update theme: %v", err))
return
}
models.Respond(conn, req.ID, models.SuccessResult{
Success: true,
Message: fmt.Sprintf("theme updated: %s", theme.Name),
})
}

View File

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

View File

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

View File

@@ -0,0 +1,258 @@
package themes
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/spf13/afero"
)
type Manager struct {
fs afero.Fs
themesDir string
}
func NewManager() (*Manager, error) {
return NewManagerWithFs(afero.NewOsFs())
}
func NewManagerWithFs(fs afero.Fs) (*Manager, error) {
themesDir := getThemesDir()
return &Manager{
fs: fs,
themesDir: themesDir,
}, nil
}
func getThemesDir() string {
configDir, err := os.UserConfigDir()
if err != nil {
log.Error("failed to get user config dir", "err", err)
return ""
}
return filepath.Join(configDir, "DankMaterialShell", "themes")
}
func (m *Manager) IsInstalled(theme Theme) (bool, error) {
path := m.getInstalledPath(theme.ID)
exists, err := afero.Exists(m.fs, path)
if err != nil {
return false, err
}
return exists, nil
}
func (m *Manager) getInstalledDir(themeID string) string {
return filepath.Join(m.themesDir, themeID)
}
func (m *Manager) getInstalledPath(themeID string) string {
return filepath.Join(m.getInstalledDir(themeID), "theme.json")
}
func (m *Manager) Install(theme Theme, registryThemeDir string) error {
themeDir := m.getInstalledDir(theme.ID)
exists, err := afero.DirExists(m.fs, themeDir)
if err != nil {
return fmt.Errorf("failed to check if theme exists: %w", err)
}
if exists {
return fmt.Errorf("theme already installed: %s", theme.Name)
}
if err := m.fs.MkdirAll(themeDir, 0755); err != nil {
return fmt.Errorf("failed to create theme directory: %w", err)
}
data, err := json.MarshalIndent(theme, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal theme: %w", err)
}
themePath := filepath.Join(themeDir, "theme.json")
if err := afero.WriteFile(m.fs, themePath, data, 0644); err != nil {
return fmt.Errorf("failed to write theme file: %w", err)
}
m.copyPreviewFiles(registryThemeDir, themeDir, theme)
return nil
}
func (m *Manager) copyPreviewFiles(srcDir, dstDir string, theme Theme) {
previews := []string{"preview-dark.svg", "preview-light.svg"}
if theme.Variants != nil {
for _, v := range theme.Variants.Options {
previews = append(previews,
fmt.Sprintf("preview-%s.svg", v.ID),
fmt.Sprintf("preview-%s-dark.svg", v.ID),
fmt.Sprintf("preview-%s-light.svg", v.ID),
)
}
}
for _, preview := range previews {
srcPath := filepath.Join(srcDir, preview)
if exists, _ := afero.Exists(m.fs, srcPath); !exists {
continue
}
data, err := afero.ReadFile(m.fs, srcPath)
if err != nil {
continue
}
dstPath := filepath.Join(dstDir, preview)
_ = afero.WriteFile(m.fs, dstPath, data, 0644)
}
}
func (m *Manager) InstallFromRegistry(registry *Registry, themeID string) error {
theme, err := registry.Get(themeID)
if err != nil {
return err
}
registryThemeDir := registry.GetThemeDir(theme.SourceDir)
return m.Install(*theme, registryThemeDir)
}
func (m *Manager) Update(theme Theme) error {
themePath := m.getInstalledPath(theme.ID)
exists, err := afero.Exists(m.fs, themePath)
if err != nil {
return fmt.Errorf("failed to check if theme exists: %w", err)
}
if !exists {
return fmt.Errorf("theme not installed: %s", theme.Name)
}
data, err := json.MarshalIndent(theme, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal theme: %w", err)
}
if err := afero.WriteFile(m.fs, themePath, data, 0644); err != nil {
return fmt.Errorf("failed to write theme file: %w", err)
}
return nil
}
func (m *Manager) Uninstall(theme Theme) error {
return m.UninstallByID(theme.ID)
}
func (m *Manager) UninstallByID(themeID string) error {
themeDir := m.getInstalledDir(themeID)
exists, err := afero.DirExists(m.fs, themeDir)
if err != nil {
return fmt.Errorf("failed to check if theme exists: %w", err)
}
if !exists {
return fmt.Errorf("theme not installed: %s", themeID)
}
if err := m.fs.RemoveAll(themeDir); err != nil {
return fmt.Errorf("failed to remove theme: %w", err)
}
return nil
}
func (m *Manager) ListInstalled() ([]string, error) {
exists, err := afero.DirExists(m.fs, m.themesDir)
if err != nil {
return nil, err
}
if !exists {
return []string{}, nil
}
entries, err := afero.ReadDir(m.fs, m.themesDir)
if err != nil {
return nil, fmt.Errorf("failed to read themes directory: %w", err)
}
var installed []string
for _, entry := range entries {
if !entry.IsDir() {
continue
}
themeID := entry.Name()
themePath := filepath.Join(m.themesDir, themeID, "theme.json")
if exists, _ := afero.Exists(m.fs, themePath); exists {
installed = append(installed, themeID)
}
}
return installed, nil
}
func (m *Manager) GetInstalledTheme(themeID string) (*Theme, error) {
themePath := m.getInstalledPath(themeID)
data, err := afero.ReadFile(m.fs, themePath)
if err != nil {
return nil, fmt.Errorf("failed to read theme file: %w", err)
}
var theme Theme
if err := json.Unmarshal(data, &theme); err != nil {
return nil, fmt.Errorf("failed to parse theme file: %w", err)
}
return &theme, nil
}
func (m *Manager) HasUpdates(themeID string, registryTheme Theme) (bool, error) {
installed, err := m.GetInstalledTheme(themeID)
if err != nil {
return false, err
}
return compareVersions(installed.Version, registryTheme.Version) < 0, nil
}
func compareVersions(installed, registry string) int {
installedParts := strings.Split(installed, ".")
registryParts := strings.Split(registry, ".")
maxLen := len(installedParts)
if len(registryParts) > maxLen {
maxLen = len(registryParts)
}
for i := 0; i < maxLen; i++ {
var installedNum, registryNum int
if i < len(installedParts) {
fmt.Sscanf(installedParts[i], "%d", &installedNum)
}
if i < len(registryParts) {
fmt.Sscanf(registryParts[i], "%d", &registryNum)
}
if installedNum < registryNum {
return -1
}
if installedNum > registryNum {
return 1
}
}
return 0
}
func (m *Manager) GetThemesDir() string {
return m.themesDir
}

View File

@@ -0,0 +1,309 @@
package themes
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/go-git/go-git/v6"
"github.com/spf13/afero"
)
const registryRepo = "https://github.com/AvengeMedia/dms-plugin-registry.git"
type ColorScheme struct {
Primary string `json:"primary,omitempty"`
PrimaryText string `json:"primaryText,omitempty"`
PrimaryContainer string `json:"primaryContainer,omitempty"`
Secondary string `json:"secondary,omitempty"`
Surface string `json:"surface,omitempty"`
SurfaceText string `json:"surfaceText,omitempty"`
SurfaceVariant string `json:"surfaceVariant,omitempty"`
SurfaceVariantText string `json:"surfaceVariantText,omitempty"`
SurfaceTint string `json:"surfaceTint,omitempty"`
Background string `json:"background,omitempty"`
BackgroundText string `json:"backgroundText,omitempty"`
Outline string `json:"outline,omitempty"`
SurfaceContainer string `json:"surfaceContainer,omitempty"`
SurfaceContainerHigh string `json:"surfaceContainerHigh,omitempty"`
SurfaceContainerHighest string `json:"surfaceContainerHighest,omitempty"`
Error string `json:"error,omitempty"`
Warning string `json:"warning,omitempty"`
Info string `json:"info,omitempty"`
}
type ThemeVariant struct {
ID string `json:"id"`
Name string `json:"name"`
Dark ColorScheme `json:"dark,omitempty"`
Light ColorScheme `json:"light,omitempty"`
}
type ThemeFlavor struct {
ID string `json:"id"`
Name string `json:"name"`
Dark ColorScheme `json:"dark,omitempty"`
Light ColorScheme `json:"light,omitempty"`
}
type ThemeAccent struct {
ID string `json:"id"`
Name string `json:"name"`
FlavorColors map[string]ColorScheme `json:"-"`
}
func (a *ThemeAccent) UnmarshalJSON(data []byte) error {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
a.FlavorColors = make(map[string]ColorScheme)
var mErr error
for key, value := range raw {
switch key {
case "id":
mErr = errors.Join(mErr, json.Unmarshal(value, &a.ID))
case "name":
mErr = errors.Join(mErr, json.Unmarshal(value, &a.Name))
default:
var colors ColorScheme
if err := json.Unmarshal(value, &colors); err == nil {
a.FlavorColors[key] = colors
} else {
mErr = errors.Join(mErr, fmt.Errorf("failed to unmarshal flavor colors for key %s: %w", key, err))
}
}
}
return mErr
}
func (a ThemeAccent) MarshalJSON() ([]byte, error) {
m := map[string]any{
"id": a.ID,
"name": a.Name,
}
for k, v := range a.FlavorColors {
m[k] = v
}
return json.Marshal(m)
}
type MultiVariantDefaults struct {
Dark map[string]string `json:"dark,omitempty"`
Light map[string]string `json:"light,omitempty"`
}
type ThemeVariants struct {
Type string `json:"type,omitempty"`
Default string `json:"default,omitempty"`
Defaults *MultiVariantDefaults `json:"defaults,omitempty"`
Options []ThemeVariant `json:"options,omitempty"`
Flavors []ThemeFlavor `json:"flavors,omitempty"`
Accents []ThemeAccent `json:"accents,omitempty"`
}
type Theme struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Author string `json:"author"`
Description string `json:"description"`
Dark ColorScheme `json:"dark"`
Light ColorScheme `json:"light"`
Variants *ThemeVariants `json:"variants,omitempty"`
PreviewPath string `json:"-"`
SourceDir string `json:"sourceDir,omitempty"`
}
type GitClient interface {
PlainClone(path string, url string) error
Pull(path string) error
}
type realGitClient struct{}
func (g *realGitClient) PlainClone(path string, url string) error {
_, err := git.PlainClone(path, &git.CloneOptions{
URL: url,
Progress: os.Stdout,
})
return err
}
func (g *realGitClient) Pull(path string) error {
repo, err := git.PlainOpen(path)
if err != nil {
return err
}
worktree, err := repo.Worktree()
if err != nil {
return err
}
err = worktree.Pull(&git.PullOptions{})
if err != nil && err.Error() != "already up-to-date" {
return err
}
return nil
}
type Registry struct {
fs afero.Fs
cacheDir string
themes []Theme
git GitClient
}
func NewRegistry() (*Registry, error) {
return NewRegistryWithFs(afero.NewOsFs())
}
func NewRegistryWithFs(fs afero.Fs) (*Registry, error) {
cacheDir := getCacheDir()
return &Registry{
fs: fs,
cacheDir: cacheDir,
git: &realGitClient{},
}, nil
}
func getCacheDir() string {
return filepath.Join(os.TempDir(), "dankdots-plugin-registry")
}
func (r *Registry) Update() error {
exists, err := afero.DirExists(r.fs, r.cacheDir)
if err != nil {
return fmt.Errorf("failed to check cache directory: %w", err)
}
if !exists {
if err := r.fs.MkdirAll(filepath.Dir(r.cacheDir), 0755); err != nil {
return fmt.Errorf("failed to create cache directory: %w", err)
}
if err := r.git.PlainClone(r.cacheDir, registryRepo); err != nil {
return fmt.Errorf("failed to clone registry: %w", err)
}
} else {
if err := r.git.Pull(r.cacheDir); err != nil {
if err := r.fs.RemoveAll(r.cacheDir); err != nil {
return fmt.Errorf("failed to remove corrupted registry: %w", err)
}
if err := r.fs.MkdirAll(filepath.Dir(r.cacheDir), 0755); err != nil {
return fmt.Errorf("failed to create cache directory: %w", err)
}
if err := r.git.PlainClone(r.cacheDir, registryRepo); err != nil {
return fmt.Errorf("failed to re-clone registry: %w", err)
}
}
}
return r.loadThemes()
}
func (r *Registry) loadThemes() error {
themesDir := filepath.Join(r.cacheDir, "themes")
entries, err := afero.ReadDir(r.fs, themesDir)
if err != nil {
return fmt.Errorf("failed to read themes directory: %w", err)
}
r.themes = []Theme{}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
themeDir := filepath.Join(themesDir, entry.Name())
themeFile := filepath.Join(themeDir, "theme.json")
data, err := afero.ReadFile(r.fs, themeFile)
if err != nil {
continue
}
var theme Theme
if err := json.Unmarshal(data, &theme); err != nil {
continue
}
if theme.ID == "" {
theme.ID = entry.Name()
}
theme.SourceDir = entry.Name()
previewPath := filepath.Join(themeDir, "preview.svg")
if exists, _ := afero.Exists(r.fs, previewPath); exists {
theme.PreviewPath = previewPath
}
r.themes = append(r.themes, theme)
}
return nil
}
func (r *Registry) List() ([]Theme, error) {
if len(r.themes) == 0 {
if err := r.Update(); err != nil {
return nil, err
}
}
return SortByFirstParty(r.themes), nil
}
func (r *Registry) Search(query string) ([]Theme, error) {
allThemes, err := r.List()
if err != nil {
return nil, err
}
if query == "" {
return allThemes, nil
}
return SortByFirstParty(FuzzySearch(query, allThemes)), nil
}
func (r *Registry) Get(idOrName string) (*Theme, error) {
themes, err := r.List()
if err != nil {
return nil, err
}
for _, t := range themes {
if t.ID == idOrName {
return &t, nil
}
}
for _, t := range themes {
if t.Name == idOrName {
return &t, nil
}
}
return nil, fmt.Errorf("theme not found: %s", idOrName)
}
func (r *Registry) GetThemeSourcePath(themeID string) string {
return filepath.Join(r.cacheDir, "themes", themeID, "theme.json")
}
func (r *Registry) GetThemeDir(themeID string) string {
return filepath.Join(r.cacheDir, "themes", themeID)
}
func SortByFirstParty(themes []Theme) []Theme {
return themes
}

View File

@@ -0,0 +1,40 @@
package themes
import (
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
)
func FuzzySearch(query string, themes []Theme) []Theme {
if query == "" {
return themes
}
queryLower := strings.ToLower(query)
return utils.Filter(themes, func(t Theme) bool {
return fuzzyMatch(queryLower, strings.ToLower(t.Name)) ||
fuzzyMatch(queryLower, strings.ToLower(t.Description)) ||
fuzzyMatch(queryLower, strings.ToLower(t.Author))
})
}
func fuzzyMatch(query, text string) bool {
queryIdx := 0
for _, char := range text {
if queryIdx < len(query) && char == rune(query[queryIdx]) {
queryIdx++
}
}
return queryIdx == len(query)
}
func FindByIDOrName(idOrName string, themes []Theme) *Theme {
if t, found := utils.Find(themes, func(t Theme) bool { return t.ID == idOrName }); found {
return &t
}
if t, found := utils.Find(themes, func(t Theme) bool { return t.Name == idOrName }); found {
return &t
}
return nil
}

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@@ -6,13 +6,13 @@
...
}:
let
cfg = config.programs.dankMaterialShell;
cfg = config.programs.dank-material-shell;
in
{
packages = [
dmsPkgs.dms-shell
]
++ lib.optional cfg.enableSystemMonitoring dmsPkgs.dgop
++ lib.optional cfg.enableSystemMonitoring cfg.dgop.package
++ lib.optionals cfg.enableVPN [
pkgs.glib
pkgs.networkmanager

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,14 @@
{
lib,
dmsPkgs,
pkgs,
...
}:
let
inherit (lib) types;
path = [
"programs"
"dankMaterialShell"
"dank-material-shell"
];
builtInRemovedMsg = "This is now built-in in DMS and doesn't need additional dependencies.";
@@ -20,46 +21,58 @@ in
(lib.mkRemovedOptionModule (
path ++ [ "enableSystemSound" ]
) "qtmultimedia is now included on dms-shell package.")
./dms-rename.nix
];
options.programs.dankMaterialShell = {
options.programs.dank-material-shell = {
enable = lib.mkEnableOption "DankMaterialShell";
systemd = {
enable = lib.mkEnableOption "DankMaterialShell systemd startup";
restartIfChanged = lib.mkOption {
type = types.bool;
default = true;
description = "Auto-restart dms.service when dankMaterialShell changes";
description = "Auto-restart dms.service when dank-material-shell changes";
};
};
dgop = {
package = lib.mkPackageOption pkgs "dgop" {};
};
enableSystemMonitoring = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to use system monitoring widgets";
};
enableVPN = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to use the VPN widget";
};
enableDynamicTheming = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to have dynamic theming support";
};
enableAudioWavelength = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to have audio wavelength support";
};
enableCalendarEvents = lib.mkOption {
type = types.bool;
default = true;
description = "Add calendar events support via khal";
};
quickshell = {
package = lib.mkPackageOption dmsPkgs "quickshell" {
extraDescription = "The quickshell package to use (defaults to be built from source, in the commit 26531f due to unreleased features used by DMS).";
extraDescription = "The quickshell package to use (defaults to be built from source, due to unreleased features used by DMS).";
};
};

View File

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

View File

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

View File

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

View File

@@ -191,19 +191,8 @@ fi
cd "$WORK_PACKAGE_DIR"
get_latest_tag() {
local repo="$1"
if command -v curl &>/dev/null; then
LATEST_TAG=$(curl -s "https://api.github.com/repos/$repo/releases/latest" 2>/dev/null | grep '"tag_name":' | sed 's/.*"tag_name": "\(.*\)".*/\1/' | head -1)
if [ -n "$LATEST_TAG" ]; then
echo "$LATEST_TAG" | sed 's/^v//'
return
fi
fi
TEMP_REPO=$(mktemp -d "$TEMP_BASE/ppa_tag_XXXXXX")
if git clone --depth=1 --quiet "https://github.com/$repo.git" "$TEMP_REPO" 2>/dev/null; then
LATEST_TAG=$(cd "$TEMP_REPO" && git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "")
rm -rf "$TEMP_REPO"
echo "$LATEST_TAG"
fi
# Get the latest tag, sorted by version
git ls-remote --tags --refs --sort='-v:refname' "https://github.com/$repo.git" | head -n1 | awk -F/ '{print $NF}' | sed 's/^v//'
}
IS_GIT_PACKAGE=false
@@ -334,6 +323,17 @@ EOF
fi
fi
if [ ! -f "dms-distropkg-arm64.gz" ]; then
info "Downloading dms binary for arm64..."
# Try to download arm64 binary, but don't fail if it doesn't exist (yet)
if wget -O dms-distropkg-arm64.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-distropkg-arm64.gz"; then
success "arm64 binary downloaded"
else
warn "Failed to download dms-distropkg-arm64.gz (skipping)"
rm -f dms-distropkg-arm64.gz
fi
fi
if [ ! -f "dms-source.tar.gz" ]; then
info "Downloading dms source for QML files..."
if wget -O dms-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then

View File

@@ -341,6 +341,10 @@ if [ "$KEEP_BUILDS" = "false" ]; then
rm -f "$PACKAGE_DIR/dms-distropkg-amd64.gz"
REMOVED=$((REMOVED + 1))
fi
if [ -f "$PACKAGE_DIR/dms-distropkg-arm64.gz" ]; then
rm -f "$PACKAGE_DIR/dms-distropkg-arm64.gz"
REMOVED=$((REMOVED + 1))
fi
if [ -f "$PACKAGE_DIR/dms-source.tar.gz" ]; then
rm -f "$PACKAGE_DIR/dms-source.tar.gz"
REMOVED=$((REMOVED + 1))

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

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

View File

@@ -32,15 +32,17 @@ override_dh_auto_build:
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' dms-git-repo/core/go.mod
# Build dms-cli from source
# Detect architecture
$(eval DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH))
@if [ -f dms-git-repo/.dms-version ]; then \
. dms-git-repo/.dms-version; \
echo "Building with VERSION=$$VERSION COMMIT=$$COMMIT"; \
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=amd64 VERSION="$$VERSION" COMMIT="$$COMMIT"; \
echo "Building with VERSION=$$VERSION COMMIT=$$COMMIT ARCH=$(DEB_HOST_ARCH)"; \
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=$(DEB_HOST_ARCH) VERSION="$$VERSION" COMMIT="$$COMMIT"; \
else \
echo "Warning: .dms-version not found, building without version info"; \
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=amd64; \
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=$(DEB_HOST_ARCH); \
fi
cp dms-git-repo/core/bin/dms-linux-amd64 dms
cp dms-git-repo/core/bin/dms-linux-$(DEB_HOST_ARCH) dms
chmod +x dms
override_dh_auto_install:

View File

@@ -9,15 +9,15 @@ Vcs-Browser: https://github.com/AvengeMedia/DankMaterialShell
Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
Package: dms-greeter
Architecture: all
Architecture: any
Depends: ${misc:Depends},
greetd,
quickshell-git | quickshell
greetd,
quickshell-git | quickshell
Recommends: niri | hyprland | sway
Description: DankMaterialShell greeter for greetd
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
inspired greeter interface built with Quickshell for Wayland compositors.
.
Supports multiple compositors including Niri, Hyprland, and Sway with automatic
compositor detection and configuration. Features session selection, user
authentication, and dynamic theming.
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
inspired greeter interface built with Quickshell for Wayland compositors.
.
Supports multiple compositors including Niri, Hyprland, and Sway with automatic
compositor detection and configuration. Features session selection, user
authentication, and dynamic theming.

View File

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

View File

@@ -19,11 +19,14 @@ override_dh_installsystemd:
override_dh_auto_build:
# All files are included in source package (downloaded by build-source.sh)
# Launchpad build environment has no internet access
test -f dms-distropkg-amd64.gz || (echo "ERROR: dms-distropkg-amd64.gz not found!" && exit 1)
$(eval DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH))
@echo "Building for architecture: $(DEB_HOST_ARCH)"
test -f dms-distropkg-$(DEB_HOST_ARCH).gz || (echo "ERROR: dms-distropkg-$(DEB_HOST_ARCH).gz not found!" && exit 1)
test -f dms-source.tar.gz || (echo "ERROR: dms-source.tar.gz not found!" && exit 1)
# Extract pre-built binary
gunzip -c dms-distropkg-amd64.gz > dms
gunzip -c dms-distropkg-$(DEB_HOST_ARCH).gz > dms
chmod +x dms
# Extract source tarball for QML files

View File

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

View File

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

View File

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

37
flake.lock generated
View File

@@ -1,32 +1,12 @@
{
"nodes": {
"dgop": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762835999,
"narHash": "sha256-UykYGrGFOFTmDpKTLNxj1wvd1gbDG4TkqLNSbV0TYwk=",
"owner": "AvengeMedia",
"repo": "dgop",
"rev": "799301991cd5dcea9b64245f9d500dcc76615653",
"type": "github"
},
"original": {
"owner": "AvengeMedia",
"repo": "dgop",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1764950072,
"narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=",
"lastModified": 1766651565,
"narHash": "sha256-QEhk0eXgyIqTpJ/ehZKg9IKS7EtlWxF3N7DXy42zPfU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "f61125a668a320878494449750330ca58b78c557",
"rev": "3e2499d5539c16d0d173ba53552a4ff8547f4539",
"type": "github"
},
"original": {
@@ -43,23 +23,22 @@
]
},
"locked": {
"lastModified": 1764663772,
"narHash": "sha256-sHqLmm0wAt3PC4vczJeBozI1/f4rv9yp3IjkClHDXDs=",
"lastModified": 1766725085,
"narHash": "sha256-O2aMFdDUYJazFrlwL7aSIHbUSEm3ADVZjmf41uBJfHs=",
"ref": "refs/heads/master",
"rev": "26531fc46ef17e9365b03770edd3fb9206fcb460",
"revCount": 713,
"rev": "41828c4180fb921df7992a5405f5ff05d2ac2fff",
"revCount": 715,
"type": "git",
"url": "https://git.outfoxxed.me/quickshell/quickshell"
},
"original": {
"rev": "26531fc46ef17e9365b03770edd3fb9206fcb460",
"rev": "41828c4180fb921df7992a5405f5ff05d2ac2fff",
"type": "git",
"url": "https://git.outfoxxed.me/quickshell/quickshell"
}
},
"root": {
"inputs": {
"dgop": "dgop",
"nixpkgs": "nixpkgs",
"quickshell": "quickshell"
}

View File

@@ -3,12 +3,8 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
dgop = {
url = "github:AvengeMedia/dgop";
inputs.nixpkgs.follows = "nixpkgs";
};
quickshell = {
url = "git+https://git.outfoxxed.me/quickshell/quickshell?rev=26531fc46ef17e9365b03770edd3fb9206fcb460";
url = "git+https://git.outfoxxed.me/quickshell/quickshell?rev=41828c4180fb921df7992a5405f5ff05d2ac2fff";
inputs.nixpkgs.follows = "nixpkgs";
};
};
@@ -17,7 +13,6 @@
{
self,
nixpkgs,
dgop,
quickshell,
...
}:
@@ -29,15 +24,14 @@
);
buildDmsPkgs = pkgs: {
dms-shell = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
inherit (dgop.packages.${pkgs.stdenv.hostPlatform.system}) dgop;
quickshell = quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default;
};
mkModuleWithDmsPkgs =
path:
modulePath:
args@{ pkgs, ... }:
{
imports = [
(import path (args // { dmsPkgs = buildDmsPkgs pkgs; }))
(import modulePath (args // { dmsPkgs = buildDmsPkgs pkgs; }))
];
};
mkQmlImportPath =
@@ -145,18 +139,30 @@
}
);
quickshell = quickshell.packages.${system}.default;
default = self.packages.${system}.dms-shell;
}
);
homeModules.dankMaterialShell.default = mkModuleWithDmsPkgs ./distro/nix/home.nix;
homeModules.dank-material-shell = mkModuleWithDmsPkgs ./distro/nix/home.nix;
homeModules.dankMaterialShell.niri = import ./distro/nix/niri.nix;
homeModules.default = self.homeModules.dank-material-shell;
nixosModules.dankMaterialShell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix;
homeModules.niri = import ./distro/nix/niri.nix;
homeModules.dankMaterialShell.default = builtins.warn "dank-material-shell: flake output `homeModules.dankMaterialShell.default` has been renamed to `homeModules.dank-material-shell`" self.homeModules.dank-material-shell;
homeModules.dankMaterialShell.niri = builtins.warn "dank-material-shell: flake output `homeModules.dankMaterialShell.niri` has been renamed to `homeModules.niri`" self.homeModules.niri;
nixosModules.dank-material-shell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix;
nixosModules.default = self.nixosModules.dank-material-shell;
nixosModules.greeter = mkModuleWithDmsPkgs ./distro/nix/greeter.nix;
nixosModules.dankMaterialShell = builtins.warn "dank-material-shell: flake output `nixosModules.dankMaterialShell` has been renamed to `nixosModules.dank-material-shell`" self.nixosModules.dank-material-shell;
devShells = forEachSystem (
system: pkgs:
let

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
pragma Singleton
pragma ComponentBehavior: Bound
pragma ComponentBehavior
import QtCore
import QtQuick
@@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store
Singleton {
id: root
readonly property int settingsConfigVersion: 3
readonly property int settingsConfigVersion: 4
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
@@ -63,7 +63,9 @@ Singleton {
property alias dankBarRightWidgetsModel: rightWidgetsModel
property string currentThemeName: "blue"
property string currentThemeCategory: "generic"
property string customThemeFile: ""
property var registryThemeVariants: ({})
property string matugenScheme: "scheme-tonal-spot"
property bool runUserMatugenTemplates: true
property string matugenTargetMonitor: ""
@@ -72,6 +74,8 @@ Singleton {
property string widgetBackgroundColor: "sch"
property string widgetColorMode: "default"
property real cornerRadius: 12
property int niriLayoutGapsOverride: -1
property int niriLayoutRadiusOverride: -1
property bool use24HourClock: true
property bool showSeconds: false
@@ -105,9 +109,12 @@ Singleton {
property bool controlCenterShowNetworkIcon: true
property bool controlCenterShowBluetoothIcon: true
property bool controlCenterShowAudioIcon: true
property bool controlCenterShowAudioPercent: true
property bool controlCenterShowVpnIcon: true
property bool controlCenterShowBrightnessIcon: false
property bool controlCenterShowBrightnessPercent: false
property bool controlCenterShowMicIcon: false
property bool controlCenterShowMicPercent: true
property bool controlCenterShowBatteryIcon: false
property bool controlCenterShowPrinterIcon: false
property bool showPrivacyButton: true
@@ -165,11 +172,13 @@ Singleton {
property int maxWorkspaceIcons: 3
property bool workspacesPerMonitor: true
property bool showOccupiedWorkspacesOnly: false
property bool reverseScrolling: false
property bool dwlShowAllTags: false
property var workspaceNameIcons: ({})
property bool waveProgressEnabled: true
property bool scrollTitleEnabled: true
property bool audioVisualizerEnabled: true
property bool audioScrollEnabled: true
property bool clockCompactMode: false
property bool focusedWindowCompactMode: false
property bool runningAppsCompactMode: true
@@ -255,6 +264,7 @@ Singleton {
property int batterySuspendTimeout: 0
property int batterySuspendBehavior: SettingsData.SuspendBehavior.Suspend
property string batteryProfileName: ""
property int batteryChargeLimit: 100
property bool lockBeforeSuspend: false
property bool loginctlLockIntegration: true
property bool fadeToLockEnabled: false
@@ -278,10 +288,13 @@ Singleton {
property bool matugenTemplateQt6ct: true
property bool matugenTemplateFirefox: true
property bool matugenTemplatePywalfox: true
property bool matugenTemplateZenBrowser: true
property bool matugenTemplateVesktop: true
property bool matugenTemplateEquibop: true
property bool matugenTemplateGhostty: true
property bool matugenTemplateKitty: true
property bool matugenTemplateFoot: true
property bool matugenTemplateNeovim: true
property bool matugenTemplateAlacritty: true
property bool matugenTemplateWezterm: true
property bool matugenTemplateDgop: true
@@ -302,6 +315,7 @@ Singleton {
property string dockBorderColor: "surfaceText"
property real dockBorderOpacity: 1.0
property int dockBorderThickness: 1
property bool dockIsolateDisplays: false
property bool notificationOverlayEnabled: false
property int overviewRows: 2
@@ -360,50 +374,279 @@ Singleton {
property string displayNameMode: "system"
property var screenPreferences: ({})
property var showOnLastDisplay: ({})
property var niriOutputSettings: ({})
property var hyprlandOutputSettings: ({})
property var barConfigs: [
{
id: "default",
name: "Main Bar",
enabled: true,
position: 0,
screenPreferences: ["all"],
showOnLastDisplay: true,
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
centerWidgets: ["music", "clock", "weather"],
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
spacing: 4,
innerPadding: 4,
bottomGap: 0,
transparency: 1.0,
widgetTransparency: 1.0,
squareCorners: false,
noBackground: false,
gothCornersEnabled: false,
gothCornerRadiusOverride: false,
gothCornerRadiusValue: 12,
borderEnabled: false,
borderColor: "surfaceText",
borderOpacity: 1.0,
borderThickness: 1,
widgetOutlineEnabled: false,
widgetOutlineColor: "primary",
widgetOutlineOpacity: 1.0,
widgetOutlineThickness: 1,
fontScale: 1.0,
autoHide: false,
autoHideDelay: 250,
openOnOverview: false,
visible: true,
popupGapsAuto: true,
popupGapsManual: 4,
maximizeDetection: true,
scrollEnabled: true,
scrollXBehavior: "column",
scrollYBehavior: "workspace"
"id": "default",
"name": "Main Bar",
"enabled": true,
"position": 0,
"screenPreferences": ["all"],
"showOnLastDisplay": true,
"leftWidgets": ["launcherButton", "workspaceSwitcher", "focusedWindow"],
"centerWidgets": ["music", "clock", "weather"],
"rightWidgets": ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
"spacing": 4,
"innerPadding": 4,
"bottomGap": 0,
"transparency": 1.0,
"widgetTransparency": 1.0,
"squareCorners": false,
"noBackground": false,
"gothCornersEnabled": false,
"gothCornerRadiusOverride": false,
"gothCornerRadiusValue": 12,
"borderEnabled": false,
"borderColor": "surfaceText",
"borderOpacity": 1.0,
"borderThickness": 1,
"widgetOutlineEnabled": false,
"widgetOutlineColor": "primary",
"widgetOutlineOpacity": 1.0,
"widgetOutlineThickness": 1,
"fontScale": 1.0,
"autoHide": false,
"autoHideDelay": 250,
"showOnWindowsOpen": false,
"openOnOverview": false,
"visible": true,
"popupGapsAuto": true,
"popupGapsManual": 4,
"maximizeDetection": true,
"scrollEnabled": true,
"scrollXBehavior": "column",
"scrollYBehavior": "workspace"
}
]
property bool desktopClockEnabled: false
property string desktopClockStyle: "analog"
property real desktopClockTransparency: 0.8
property string desktopClockColorMode: "primary"
property color desktopClockCustomColor: "#ffffff"
property bool desktopClockShowDate: true
property bool desktopClockShowAnalogNumbers: false
property bool desktopClockShowAnalogSeconds: true
property real desktopClockX: -1
property real desktopClockY: -1
property real desktopClockWidth: 280
property real desktopClockHeight: 180
property var desktopClockDisplayPreferences: ["all"]
property bool systemMonitorEnabled: false
property bool systemMonitorShowHeader: true
property real systemMonitorTransparency: 0.8
property string systemMonitorColorMode: "primary"
property color systemMonitorCustomColor: "#ffffff"
property bool systemMonitorShowCpu: true
property bool systemMonitorShowCpuGraph: true
property bool systemMonitorShowCpuTemp: true
property bool systemMonitorShowGpuTemp: false
property string systemMonitorGpuPciId: ""
property bool systemMonitorShowMemory: true
property bool systemMonitorShowMemoryGraph: true
property bool systemMonitorShowNetwork: true
property bool systemMonitorShowNetworkGraph: true
property bool systemMonitorShowDisk: true
property bool systemMonitorShowTopProcesses: false
property int systemMonitorTopProcessCount: 3
property string systemMonitorTopProcessSortBy: "cpu"
property string systemMonitorLayoutMode: "auto"
property int systemMonitorGraphInterval: 60
property real systemMonitorX: -1
property real systemMonitorY: -1
property real systemMonitorWidth: 320
property real systemMonitorHeight: 480
property var systemMonitorDisplayPreferences: ["all"]
property var systemMonitorVariants: []
property var desktopWidgetPositions: ({})
property var desktopWidgetGridSettings: ({})
property var desktopWidgetInstances: []
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"]
};
}
function createDesktopWidgetInstance(widgetType, name, config) {
const id = "dw_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
const instance = {
id: id,
widgetType: widgetType,
name: name || widgetType,
enabled: true,
config: config || {},
positions: {}
};
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
instances.push(instance);
desktopWidgetInstances = instances;
saveSettings();
return instance;
}
function updateDesktopWidgetInstance(instanceId, updates) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
Object.assign(instances[idx], updates);
desktopWidgetInstances = instances;
saveSettings();
}
function updateDesktopWidgetInstanceConfig(instanceId, configUpdates) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
instances[idx].config = Object.assign({}, instances[idx].config || {}, configUpdates);
desktopWidgetInstances = instances;
saveSettings();
}
function updateDesktopWidgetInstancePosition(instanceId, screenKey, positionUpdates) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
if (!instances[idx].positions)
instances[idx].positions = {};
instances[idx].positions[screenKey] = Object.assign({}, instances[idx].positions[screenKey] || {}, positionUpdates);
desktopWidgetInstances = instances;
saveSettings();
}
function removeDesktopWidgetInstance(instanceId) {
const instances = (desktopWidgetInstances || []).filter(inst => inst.id !== instanceId);
desktopWidgetInstances = instances;
saveSettings();
}
function duplicateDesktopWidgetInstance(instanceId) {
const source = getDesktopWidgetInstance(instanceId);
if (!source)
return null;
const newId = "dw_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
const instance = {
id: newId,
widgetType: source.widgetType,
name: source.name + " (Copy)",
enabled: source.enabled,
config: JSON.parse(JSON.stringify(source.config || {})),
positions: {}
};
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
instances.push(instance);
desktopWidgetInstances = instances;
saveSettings();
return instance;
}
function getDesktopWidgetInstance(instanceId) {
return (desktopWidgetInstances || []).find(inst => inst.id === instanceId) || null;
}
function getDesktopWidgetInstancesOfType(widgetType) {
return (desktopWidgetInstances || []).filter(inst => inst.widgetType === widgetType);
}
function getEnabledDesktopWidgetInstances() {
return (desktopWidgetInstances || []).filter(inst => inst.enabled);
}
signal forceDankBarLayoutRefresh
signal forceDockLayoutRefresh
signal widgetDataChanged
@@ -421,10 +664,12 @@ Singleton {
function applyStoredTheme() {
if (typeof Theme !== "undefined") {
Theme.currentThemeCategory = currentThemeCategory;
Theme.switchTheme(currentThemeName, false, false);
} else {
Qt.callLater(function () {
if (typeof Theme !== "undefined") {
Theme.currentThemeCategory = currentThemeCategory;
Theme.switchTheme(currentThemeName, false, false);
}
});
@@ -458,25 +703,25 @@ Singleton {
const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
settings_file="$config_dir/settings.ini"
if [ -f "$settings_file" ]; then
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
settings_file="$config_dir/settings.ini"
if [ -f "$settings_file" ]; then
if grep -q "^gtk-icon-theme-name=" "$settings_file"; then
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
else
if grep -q "\\[Settings\\]" "$settings_file"; then
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
else
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
fi
if grep -q "\\[Settings\\]" "$settings_file"; then
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
else
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
fi
else
fi
else
echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
fi
done
fi
done
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
pkill -HUP -f 'gtk' 2>/dev/null || true`;
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
pkill -HUP -f 'gtk' 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", configScript]);
}
@@ -489,36 +734,36 @@ pkill -HUP -f 'gtk' 2>/dev/null || true`;
const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
update_qt_icon_theme() {
local config_file="$1"
local theme_name="$2"
if [ -f "$config_file" ]; then
if grep -q "^\\[Appearance\\]" "$config_file"; then
if grep -q "^icon_theme=" "$config_file"; then
update_qt_icon_theme() {
local config_file="$1"
local theme_name="$2"
if [ -f "$config_file" ]; then
if grep -q "^\\[Appearance\\]" "$config_file"; then
if grep -q "^icon_theme=" "$config_file"; then
sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
else
else
sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
fi
else
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
fi
else
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
fi
}
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
fi
else
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
fi
else
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
fi
}
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", script]);
}
readonly property var _hooks: ({
applyStoredTheme: applyStoredTheme,
regenSystemThemes: regenSystemThemes,
updateNiriLayout: updateNiriLayout,
applyStoredIconTheme: applyStoredIconTheme,
updateBarConfigs: updateBarConfigs
"applyStoredTheme": applyStoredTheme,
"regenSystemThemes": regenSystemThemes,
"updateNiriLayout": updateNiriLayout,
"applyStoredIconTheme": applyStoredIconTheme,
"updateBarConfigs": updateBarConfigs
})
function set(key, value) {
@@ -543,7 +788,6 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
Store.parse(root, obj);
applyStoredTheme();
applyStoredIconTheme();
Processes.detectIcons();
Processes.detectQtTools();
} catch (e) {
console.warn("SettingsData: Failed to load settings:", e.message);
@@ -590,7 +834,42 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
}
function detectAvailableIconThemes() {
Processes.detectIcons();
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") || "";
const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation));
const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
const dataDirs = xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat([localData]) : ["/usr/share", "/usr/local/share", localData];
const iconPaths = dataDirs.map(d => d + "/icons").concat([homeDir + "/.icons"]);
const pathsArg = iconPaths.join(" ");
const script = `
echo "SYSDEFAULT:$(gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g" || echo '')"
for dir in ${pathsArg}; do
[ -d "$dir" ] || continue
for theme in "$dir"/*/; do
[ -d "$theme" ] || continue
basename "$theme"
done
done | grep -v '^icons$' | grep -v '^default$' | grep -v '^hicolor$' | grep -v '^locolor$' | sort -u
`;
Proc.runCommand("detectIconThemes", ["sh", "-c", script], (output, exitCode) => {
const themes = ["System Default"];
if (output && output.trim()) {
const lines = output.trim().split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith("SYSDEFAULT:")) {
systemDefaultIconTheme = line.substring(11).trim();
continue;
}
if (line)
themes.push(line);
}
}
availableIconThemes = themes;
});
}
function getEffectiveTimeFormat() {
@@ -654,15 +933,14 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return barHeight + spacing + bottomGap - gothOffset + Theme.popupDistance;
}
function getPopupTriggerPosition(globalPos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) {
const screenX = screen ? screen.x : 0;
const screenY = screen ? screen.y : 0;
const relativeX = globalPos.x - screenX;
const relativeY = globalPos.y - screenY;
function getPopupTriggerPosition(pos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) {
const relativeX = pos.x;
const relativeY = pos.y;
const defaultBar = barConfigs[0] || getBarConfig("default");
const spacing = barSpacing !== undefined ? barSpacing : (defaultBar?.spacing ?? 4);
const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top);
const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0);
const rawBottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0);
const bottomGap = Math.max(0, rawBottomGap);
const useAutoGaps = (barConfig && barConfig.popupGapsAuto !== undefined) ? barConfig.popupGapsAuto : (defaultBar?.popupGapsAuto ?? true);
const manualGapValue = (barConfig && barConfig.popupGapsManual !== undefined) ? barConfig.popupGapsManual : (defaultBar?.popupGapsManual ?? 4);
@@ -723,7 +1001,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
let leftBar = 0;
let rightBar = 0;
for (let i = 0; i < enabledBars.length; i++) {
for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barConfig.id)
continue;
@@ -793,7 +1071,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
if (barConfig) {
const enabledBars = getEnabledBarConfigs();
for (let i = 0; i < enabledBars.length; i++) {
for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barConfig.id)
continue;
@@ -913,7 +1191,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
updateBarConfigs();
if (positionChanged) {
NotificationService.clearAllPopups();
NotificationService.dismissAllPopups();
}
}
@@ -925,7 +1203,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const conflicts = [];
const enabledBars = getEnabledBarConfigs();
for (let i = 0; i < enabledBars.length; i++) {
for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barId)
continue;
@@ -938,9 +1216,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const hasAll = barScreens.includes("all") || otherScreens.includes("all");
if (hasAll) {
conflicts.push({
barId: other.id,
barName: other.name,
reason: "Same position on all screens"
"barId": other.id,
"barName": other.name,
"reason": "Same position on all screens"
});
continue;
}
@@ -948,9 +1226,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const overlapping = barScreens.some(screen => otherScreens.includes(screen));
if (overlapping) {
conflicts.push({
barId: other.id,
barName: other.name,
reason: "Same position on overlapping screens"
"barId": other.id,
"barName": other.name,
"reason": "Same position on overlapping screens"
});
}
}
@@ -972,7 +1250,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
function getScreensSortedByPosition() {
const screens = [];
for (let i = 0; i < Quickshell.screens.length; i++) {
for (var i = 0; i < Quickshell.screens.length; i++) {
screens.push(Quickshell.screens[i]);
}
screens.sort((a, b) => {
@@ -989,7 +1267,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const sorted = getScreensSortedByPosition();
let modelCount = 0;
let screenIndex = -1;
for (let i = 0; i < sorted.length; i++) {
for (var i = 0; i < sorted.length; i++) {
if (sorted[i].model === screen.model) {
if (sorted[i].name === screen.name) {
screenIndex = modelCount;
@@ -1058,7 +1336,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
}
function sendTestNotifications() {
NotificationService.clearAllPopups();
NotificationService.dismissAllPopups();
sendTestNotification(0);
testNotifTimer1.start();
testNotifTimer2.start();
@@ -1187,7 +1465,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
spacing: spacing
"spacing": spacing
});
}
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
@@ -1216,7 +1494,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return;
}
updateBarConfig(defaultBar.id, {
position: position
"position": position
});
}
@@ -1224,7 +1502,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
leftWidgets: order
"leftWidgets": order
});
updateListModel(leftWidgetsModel, order);
}
@@ -1234,7 +1512,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
centerWidgets: order
"centerWidgets": order
});
updateListModel(centerWidgetsModel, order);
}
@@ -1244,7 +1522,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
rightWidgets: order
"rightWidgets": order
});
updateListModel(rightWidgetsModel, order);
}
@@ -1257,9 +1535,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
leftWidgets: defaultLeft,
centerWidgets: defaultCenter,
rightWidgets: defaultRight
"leftWidgets": defaultLeft,
"centerWidgets": defaultCenter,
"rightWidgets": defaultRight
});
}
updateListModel(leftWidgetsModel, defaultLeft);
@@ -1303,11 +1581,46 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return workspaceNameIcons[workspaceName] || null;
}
function getRegistryThemeVariant(themeId, defaultVariant) {
var stored = registryThemeVariants[themeId];
if (typeof stored === "string")
return stored || defaultVariant || "";
return defaultVariant || "";
}
function setRegistryThemeVariant(themeId, variantId) {
var variants = JSON.parse(JSON.stringify(registryThemeVariants));
variants[themeId] = variantId;
registryThemeVariants = variants;
saveSettings();
if (typeof Theme !== "undefined")
Theme.reloadCustomThemeVariant();
}
function getRegistryThemeMultiVariant(themeId, defaults) {
var stored = registryThemeVariants[themeId];
if (stored && typeof stored === "object")
return stored;
return defaults || {};
}
function setRegistryThemeMultiVariant(themeId, flavor, accent) {
var variants = JSON.parse(JSON.stringify(registryThemeVariants));
variants[themeId] = {
flavor: flavor,
accent: accent
};
registryThemeVariants = variants;
saveSettings();
if (typeof Theme !== "undefined")
Theme.reloadCustomThemeVariant();
}
function toggleDankBarVisible() {
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
visible: !defaultBar.visible
"visible": !defaultBar.visible
});
}
}
@@ -1345,6 +1658,87 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function getNiriOutputSetting(outputId, key, defaultValue) {
if (!niriOutputSettings[outputId])
return defaultValue;
return niriOutputSettings[outputId][key] !== undefined ? niriOutputSettings[outputId][key] : defaultValue;
}
function setNiriOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
niriOutputSettings = updated;
saveSettings();
}
function getNiriOutputSettings(outputId) {
const settings = niriOutputSettings[outputId];
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function setNiriOutputSettings(outputId, settings) {
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
updated[outputId] = settings;
niriOutputSettings = updated;
saveSettings();
}
function removeNiriOutputSettings(outputId) {
if (!niriOutputSettings[outputId])
return;
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
delete updated[outputId];
niriOutputSettings = updated;
saveSettings();
}
function getHyprlandOutputSetting(outputId, key, defaultValue) {
if (!hyprlandOutputSettings[outputId])
return defaultValue;
return hyprlandOutputSettings[outputId][key] !== undefined ? hyprlandOutputSettings[outputId][key] : defaultValue;
}
function setHyprlandOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
hyprlandOutputSettings = updated;
saveSettings();
}
function removeHyprlandOutputSetting(outputId, key) {
if (!hyprlandOutputSettings[outputId] || !(key in hyprlandOutputSettings[outputId]))
return;
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
delete updated[outputId][key];
hyprlandOutputSettings = updated;
saveSettings();
}
function getHyprlandOutputSettings(outputId) {
const settings = hyprlandOutputSettings[outputId];
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function setHyprlandOutputSettings(outputId, settings) {
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
updated[outputId] = settings;
hyprlandOutputSettings = updated;
saveSettings();
}
function removeHyprlandOutputSettings(outputId) {
if (!hyprlandOutputSettings[outputId])
return;
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
delete updated[outputId];
hyprlandOutputSettings = updated;
saveSettings();
}
ListModel {
id: leftWidgetsModel
}
@@ -1396,6 +1790,8 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const txt = settingsFile.text();
const obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
Store.parse(root, obj);
applyStoredTheme();
applyStoredIconTheme();
} catch (e) {
console.warn("SettingsData: Failed to reload settings:", e.message);
}

View File

@@ -1,545 +1,430 @@
// Stock theme definitions for DankMaterialShell
// Separated from Theme.qml to keep that file clean
const CatppuccinMocha = {
surface: "#181825",
surfaceText: "#cdd6f4",
surfaceVariant: "#1e1e2e",
surfaceVariantText: "#a6adc8",
background: "#181825",
backgroundText: "#cdd6f4",
outline: "#6c7086",
surfaceContainer: "#1e1e2e",
surfaceContainerHigh: "#313244",
surfaceContainerHighest: "#45475a"
}
const CatppuccinLatte = {
surface: "#e6e9ef",
surfaceText: "#4c4f69",
surfaceVariant: "#e6e9ef",
surfaceVariantText: "#6c6f85",
background: "#eff1f5",
backgroundText: "#4c4f69",
outline: "#9ca0b0",
surfaceContainer: "#dce0e8",
surfaceContainerHigh: "#ccd0da",
surfaceContainerHighest: "#bcc0cc"
}
const CatppuccinVariants = {
"cat-rosewater": {
name: "Rosewater",
dark: { primary: "#f5e0dc", secondary: "#f2cdcd", primaryText: "#1e1e2e", primaryContainer: "#7d5d56", surfaceTint: "#f5e0dc" },
light: { primary: "#dc8a78", secondary: "#dd7878", primaryText: "#ffffff", primaryContainer: "#f6e7e3", surfaceTint: "#dc8a78" }
},
"cat-flamingo": {
name: "Flamingo",
dark: { primary: "#f2cdcd", secondary: "#f5e0dc", primaryText: "#1e1e2e", primaryContainer: "#7a555a", surfaceTint: "#f2cdcd" },
light: { primary: "#dd7878", secondary: "#dc8a78", primaryText: "#ffffff", primaryContainer: "#f6e5e5", surfaceTint: "#dd7878" }
},
"cat-pink": {
name: "Pink",
dark: { primary: "#f5c2e7", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#7a3f69", surfaceTint: "#f5c2e7" },
light: { primary: "#ea76cb", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#f7d7ee", surfaceTint: "#ea76cb" }
},
"cat-mauve": {
name: "Mauve",
dark: { primary: "#cba6f7", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#55307f", surfaceTint: "#cba6f7" },
light: { primary: "#8839ef", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#eadcff", surfaceTint: "#8839ef" }
},
"cat-red": {
name: "Red",
dark: { primary: "#f38ba8", secondary: "#eba0ac", primaryText: "#1e1e2e", primaryContainer: "#6f2438", surfaceTint: "#f38ba8" },
light: { primary: "#d20f39", secondary: "#e64553", primaryText: "#ffffff", primaryContainer: "#f6d0d6", surfaceTint: "#d20f39" }
},
"cat-maroon": {
name: "Maroon",
dark: { primary: "#eba0ac", secondary: "#f38ba8", primaryText: "#1e1e2e", primaryContainer: "#6d3641", surfaceTint: "#eba0ac" },
light: { primary: "#e64553", secondary: "#d20f39", primaryText: "#ffffff", primaryContainer: "#f7d8dc", surfaceTint: "#e64553" }
},
"cat-peach": {
name: "Peach",
dark: { primary: "#fab387", secondary: "#f9e2af", primaryText: "#1e1e2e", primaryContainer: "#734226", surfaceTint: "#fab387" },
light: { primary: "#fe640b", secondary: "#df8e1d", primaryText: "#ffffff", primaryContainer: "#ffe4d5", surfaceTint: "#fe640b" }
},
"cat-yellow": {
name: "Yellow",
dark: { primary: "#f9e2af", secondary: "#a6e3a1", primaryText: "#1e1e2e", primaryContainer: "#6e5a2f", surfaceTint: "#f9e2af" },
light: { primary: "#df8e1d", secondary: "#40a02b", primaryText: "#ffffff", primaryContainer: "#fff6d6", surfaceTint: "#df8e1d" }
},
"cat-green": {
name: "Green",
dark: { primary: "#a6e3a1", secondary: "#94e2d5", primaryText: "#1e1e2e", primaryContainer: "#2f5f36", surfaceTint: "#a6e3a1" },
light: { primary: "#40a02b", secondary: "#179299", primaryText: "#ffffff", primaryContainer: "#dff4e0", surfaceTint: "#40a02b" }
},
"cat-teal": {
name: "Teal",
dark: { primary: "#94e2d5", secondary: "#89dceb", primaryText: "#1e1e2e", primaryContainer: "#2e5e59", surfaceTint: "#94e2d5" },
light: { primary: "#179299", secondary: "#04a5e5", primaryText: "#ffffff", primaryContainer: "#daf3f1", surfaceTint: "#179299" }
},
"cat-sky": {
name: "Sky",
dark: { primary: "#89dceb", secondary: "#74c7ec", primaryText: "#1e1e2e", primaryContainer: "#24586a", surfaceTint: "#89dceb" },
light: { primary: "#04a5e5", secondary: "#209fb5", primaryText: "#ffffff", primaryContainer: "#dbf1fb", surfaceTint: "#04a5e5" }
},
"cat-sapphire": {
name: "Sapphire",
dark: { primary: "#74c7ec", secondary: "#89b4fa", primaryText: "#1e1e2e", primaryContainer: "#1f4d6f", surfaceTint: "#74c7ec" },
light: { primary: "#209fb5", secondary: "#1e66f5", primaryText: "#ffffff", primaryContainer: "#def3f8", surfaceTint: "#209fb5" }
},
"cat-blue": {
name: "Blue",
dark: { primary: "#89b4fa", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#243f75", surfaceTint: "#89b4fa" },
light: { primary: "#1e66f5", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#e0e9ff", surfaceTint: "#1e66f5" }
},
"cat-lavender": {
name: "Lavender",
dark: { primary: "#b4befe", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#3f4481", surfaceTint: "#b4befe" },
light: { primary: "#7287fd", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#e5e8ff", surfaceTint: "#7287fd" }
}
}
function getCatppuccinTheme(variant, isLight = false) {
const variantData = CatppuccinVariants[variant]
if (!variantData) return null
const baseColors = isLight ? CatppuccinLatte : CatppuccinMocha
const accentColors = isLight ? variantData.light : variantData.dark
return Object.assign({
name: `${variantData.name}${isLight ? ' Light' : ''}`
}, baseColors, accentColors)
}
const StockThemes = {
DARK: {
blue: {
name: "Blue",
primary: "#42a5f5",
primaryText: "#000000",
primaryContainer: "#0d47a1",
secondary: "#8ab4f8",
surface: "#101418",
surfaceText: "#e0e2e8",
surfaceVariant: "#42474e",
surfaceVariantText: "#c2c7cf",
surfaceTint: "#8ab4f8",
background: "#101418",
backgroundText: "#e0e2e8",
outline: "#8c9199",
surfaceContainer: "#1d2024",
surfaceContainerHigh: "#272a2f",
surfaceContainerHighest: "#32353a"
},
purple: {
name: "Purple",
primary: "#D0BCFF",
primaryText: "#381E72",
primaryContainer: "#4F378B",
secondary: "#CCC2DC",
surface: "#141218",
surfaceText: "#e6e0e9",
surfaceVariant: "#49454e",
surfaceVariantText: "#cac4cf",
surfaceTint: "#D0BCFF",
background: "#141218",
backgroundText: "#e6e0e9",
outline: "#948f99",
surfaceContainer: "#211f24",
surfaceContainerHigh: "#2b292f",
surfaceContainerHighest: "#36343a"
},
green: {
name: "Green",
primary: "#4caf50",
primaryText: "#000000",
primaryContainer: "#1b5e20",
secondary: "#81c995",
surface: "#10140f",
surfaceText: "#e0e4db",
surfaceVariant: "#424940",
surfaceVariantText: "#c2c9bd",
surfaceTint: "#81c995",
background: "#10140f",
backgroundText: "#e0e4db",
outline: "#8c9388",
surfaceContainer: "#1d211b",
surfaceContainerHigh: "#272b25",
surfaceContainerHighest: "#323630"
},
orange: {
name: "Orange",
primary: "#ff6d00",
primaryText: "#000000",
primaryContainer: "#3e2723",
secondary: "#ffb74d",
surface: "#1a120e",
surfaceText: "#f0dfd8",
surfaceVariant: "#52443d",
surfaceVariantText: "#d7c2b9",
surfaceTint: "#ffb74d",
background: "#1a120e",
backgroundText: "#f0dfd8",
outline: "#a08d85",
surfaceContainer: "#271e1a",
surfaceContainerHigh: "#322824",
surfaceContainerHighest: "#3d332e"
},
red: {
name: "Red",
primary: "#f44336",
primaryText: "#000000",
primaryContainer: "#4a0e0e",
secondary: "#f28b82",
surface: "#1a1110",
surfaceText: "#f1dedc",
surfaceVariant: "#534341",
surfaceVariantText: "#d8c2be",
surfaceTint: "#f28b82",
background: "#1a1110",
backgroundText: "#f1dedc",
outline: "#a08c89",
surfaceContainer: "#271d1c",
surfaceContainerHigh: "#322826",
surfaceContainerHighest: "#3d3231"
},
cyan: {
name: "Cyan",
primary: "#00bcd4",
primaryText: "#000000",
primaryContainer: "#004d5c",
secondary: "#4dd0e1",
surface: "#0e1416",
surfaceText: "#dee3e5",
surfaceVariant: "#3f484a",
surfaceVariantText: "#bfc8ca",
surfaceTint: "#4dd0e1",
background: "#0e1416",
backgroundText: "#dee3e5",
outline: "#899295",
surfaceContainer: "#1b2122",
surfaceContainerHigh: "#252b2c",
surfaceContainerHighest: "#303637"
},
pink: {
name: "Pink",
primary: "#e91e63",
primaryText: "#000000",
primaryContainer: "#4a0e2f",
secondary: "#f8bbd9",
surface: "#191112",
surfaceText: "#f0dee0",
surfaceVariant: "#524345",
surfaceVariantText: "#d6c2c3",
surfaceTint: "#f8bbd9",
background: "#191112",
backgroundText: "#f0dee0",
outline: "#9f8c8e",
surfaceContainer: "#261d1e",
surfaceContainerHigh: "#312829",
surfaceContainerHighest: "#3c3233"
},
amber: {
name: "Amber",
primary: "#ffc107",
primaryText: "#000000",
primaryContainer: "#4a3c00",
secondary: "#ffd54f",
surface: "#17130b",
surfaceText: "#ebe1d4",
surfaceVariant: "#4d4639",
surfaceVariantText: "#d0c5b4",
surfaceTint: "#ffd54f",
background: "#17130b",
backgroundText: "#ebe1d4",
outline: "#998f80",
surfaceContainer: "#231f17",
surfaceContainerHigh: "#2e2921",
surfaceContainerHighest: "#39342b"
},
coral: {
name: "Coral",
primary: "#ffb4ab",
primaryText: "#000000",
primaryContainer: "#8c1d18",
secondary: "#f9dedc",
surface: "#1a1110",
surfaceText: "#f1dedc",
surfaceVariant: "#534341",
surfaceVariantText: "#d8c2bf",
surfaceTint: "#ffb4ab",
background: "#1a1110",
backgroundText: "#f1dedc",
outline: "#a08c8a",
surfaceContainer: "#271d1c",
surfaceContainerHigh: "#322826",
surfaceContainerHighest: "#3d3231"
},
monochrome: {
name: "Monochrome",
primary: "#ffffff",
primaryText: "#2b303c",
primaryContainer: "#424753",
secondary: "#c4c6d0",
surface: "#2a2a2a",
surfaceText: "#e4e2e3",
surfaceVariant: "#474648",
surfaceVariantText: "#c8c6c7",
surfaceTint: "#c2c6d6",
background: "#131315",
backgroundText: "#e4e2e3",
outline: "#929092",
surfaceContainer: "#353535",
surfaceContainerHigh: "#424242",
surfaceContainerHighest: "#505050",
error: "#ffb4ab",
warning: "#3f4759",
info: "#595e6c",
matugen_type: "scheme-monochrome"
}
DARK: {
blue: {
name: "Blue",
primary: "#42a5f5",
primaryText: "#000000",
primaryContainer: "#0d47a1",
secondary: "#8ab4f8",
surface: "#101418",
surfaceText: "#e0e2e8",
surfaceVariant: "#42474e",
surfaceVariantText: "#c2c7cf",
surfaceTint: "#8ab4f8",
background: "#101418",
backgroundText: "#e0e2e8",
outline: "#8c9199",
surfaceContainer: "#1d2024",
surfaceContainerHigh: "#272a2f",
surfaceContainerHighest: "#32353a",
},
LIGHT: {
blue: {
name: "Blue Light",
primary: "#1976d2",
primaryText: "#ffffff",
primaryContainer: "#e3f2fd",
secondary: "#42a5f5",
surface: "#f7f9ff",
surfaceText: "#181c20",
surfaceVariant: "#dee3eb",
surfaceVariantText: "#42474e",
surfaceTint: "#1976d2",
background: "#f7f9ff",
backgroundText: "#181c20",
outline: "#72777f",
surfaceContainer: "#eceef4",
surfaceContainerHigh: "#e6e8ee",
surfaceContainerHighest: "#e0e2e8"
},
purple: {
name: "Purple Light",
primary: "#6750A4",
primaryText: "#ffffff",
primaryContainer: "#EADDFF",
secondary: "#625B71",
surface: "#fef7ff",
surfaceText: "#1d1b20",
surfaceVariant: "#e7e0eb",
surfaceVariantText: "#49454e",
surfaceTint: "#6750A4",
background: "#fef7ff",
backgroundText: "#1d1b20",
outline: "#7a757f",
surfaceContainer: "#f2ecf4",
surfaceContainerHigh: "#ece6ee",
surfaceContainerHighest: "#e6e0e9"
},
green: {
name: "Green Light",
primary: "#2e7d32",
primaryText: "#ffffff",
primaryContainer: "#e8f5e8",
secondary: "#4caf50",
surface: "#f7fbf1",
surfaceText: "#191d17",
surfaceVariant: "#dee5d8",
surfaceVariantText: "#424940",
surfaceTint: "#2e7d32",
background: "#f7fbf1",
backgroundText: "#191d17",
outline: "#72796f",
surfaceContainer: "#ecefe6",
surfaceContainerHigh: "#e6e9e0",
surfaceContainerHighest: "#e0e4db"
},
orange: {
name: "Orange Light",
primary: "#e65100",
primaryText: "#ffffff",
primaryContainer: "#ffecb3",
secondary: "#ff9800",
surface: "#fff8f6",
surfaceText: "#221a16",
surfaceVariant: "#f4ded5",
surfaceVariantText: "#52443d",
surfaceTint: "#e65100",
background: "#fff8f6",
backgroundText: "#221a16",
outline: "#85736c",
surfaceContainer: "#fceae3",
surfaceContainerHigh: "#f6e5de",
surfaceContainerHighest: "#f0dfd8"
},
red: {
name: "Red Light",
primary: "#d32f2f",
primaryText: "#ffffff",
primaryContainer: "#ffebee",
secondary: "#f44336",
surface: "#fff8f7",
surfaceText: "#231918",
surfaceVariant: "#f5ddda",
surfaceVariantText: "#534341",
surfaceTint: "#d32f2f",
background: "#fff8f7",
backgroundText: "#231918",
outline: "#857370",
surfaceContainer: "#fceae7",
surfaceContainerHigh: "#f7e4e1",
surfaceContainerHighest: "#f1dedc"
},
cyan: {
name: "Cyan Light",
primary: "#0097a7",
primaryText: "#ffffff",
primaryContainer: "#e0f2f1",
secondary: "#00bcd4",
surface: "#f5fafc",
surfaceText: "#171d1e",
surfaceVariant: "#dbe4e6",
surfaceVariantText: "#3f484a",
surfaceTint: "#0097a7",
background: "#f5fafc",
backgroundText: "#171d1e",
outline: "#6f797b",
surfaceContainer: "#e9eff0",
surfaceContainerHigh: "#e3e9eb",
surfaceContainerHighest: "#dee3e5"
},
pink: {
name: "Pink Light",
primary: "#c2185b",
primaryText: "#ffffff",
primaryContainer: "#fce4ec",
secondary: "#e91e63",
surface: "#fff8f7",
surfaceText: "#22191a",
surfaceVariant: "#f3dddf",
surfaceVariantText: "#524345",
surfaceTint: "#c2185b",
background: "#fff8f7",
backgroundText: "#22191a",
outline: "#847375",
surfaceContainer: "#fbeaeb",
surfaceContainerHigh: "#f5e4e5",
surfaceContainerHighest: "#f0dee0"
},
amber: {
name: "Amber Light",
primary: "#ff8f00",
primaryText: "#000000",
primaryContainer: "#fff8e1",
secondary: "#ffc107",
surface: "#fff8f2",
surfaceText: "#1f1b13",
surfaceVariant: "#ede1cf",
surfaceVariantText: "#4d4639",
surfaceTint: "#ff8f00",
background: "#fff8f2",
backgroundText: "#1f1b13",
outline: "#7f7667",
surfaceContainer: "#f6ecdf",
surfaceContainerHigh: "#f1e7d9",
surfaceContainerHighest: "#ebe1d4"
},
coral: {
name: "Coral Light",
primary: "#8c1d18",
primaryText: "#ffffff",
primaryContainer: "#ffdad6",
secondary: "#ff5449",
surface: "#fff8f7",
surfaceText: "#231918",
surfaceVariant: "#f5ddda",
surfaceVariantText: "#534341",
surfaceTint: "#8c1d18",
background: "#fff8f7",
backgroundText: "#231918",
outline: "#857371",
surfaceContainer: "#fceae7",
surfaceContainerHigh: "#f6e4e2",
surfaceContainerHighest: "#f1dedc"
},
monochrome: {
name: "Monochrome Light",
primary: "#2b303c",
primaryText: "#ffffff",
primaryContainer: "#d6d7dc",
secondary: "#4a4d56",
surface: "#f5f5f6",
surfaceText: "#2a2a2a",
surfaceVariant: "#e0e0e2",
surfaceVariantText: "#424242",
surfaceTint: "#5a5f6e",
background: "#ffffff",
backgroundText: "#1a1a1a",
outline: "#757577",
surfaceContainer: "#e8e8ea",
surfaceContainerHigh: "#dcdcde",
surfaceContainerHighest: "#d0d0d2",
error: "#ba1a1a",
warning: "#f9e79f",
info: "#5d6475",
matugen_type: "scheme-monochrome"
}
}
}
purple: {
name: "Purple",
primary: "#D0BCFF",
primaryText: "#381E72",
primaryContainer: "#4F378B",
secondary: "#CCC2DC",
surface: "#141218",
surfaceText: "#e6e0e9",
surfaceVariant: "#49454e",
surfaceVariantText: "#cac4cf",
surfaceTint: "#D0BCFF",
background: "#141218",
backgroundText: "#e6e0e9",
outline: "#948f99",
surfaceContainer: "#211f24",
surfaceContainerHigh: "#2b292f",
surfaceContainerHighest: "#36343a",
},
green: {
name: "Green",
primary: "#4caf50",
primaryText: "#000000",
primaryContainer: "#1b5e20",
secondary: "#81c995",
surface: "#10140f",
surfaceText: "#e0e4db",
surfaceVariant: "#424940",
surfaceVariantText: "#c2c9bd",
surfaceTint: "#81c995",
background: "#10140f",
backgroundText: "#e0e4db",
outline: "#8c9388",
surfaceContainer: "#1d211b",
surfaceContainerHigh: "#272b25",
surfaceContainerHighest: "#323630",
},
orange: {
name: "Orange",
primary: "#ff6d00",
primaryText: "#000000",
primaryContainer: "#3e2723",
secondary: "#ffb74d",
surface: "#1a120e",
surfaceText: "#f0dfd8",
surfaceVariant: "#52443d",
surfaceVariantText: "#d7c2b9",
surfaceTint: "#ffb74d",
background: "#1a120e",
backgroundText: "#f0dfd8",
outline: "#a08d85",
surfaceContainer: "#271e1a",
surfaceContainerHigh: "#322824",
surfaceContainerHighest: "#3d332e",
},
red: {
name: "Red",
primary: "#f44336",
primaryText: "#000000",
primaryContainer: "#4a0e0e",
secondary: "#f28b82",
surface: "#1a1110",
surfaceText: "#f1dedc",
surfaceVariant: "#534341",
surfaceVariantText: "#d8c2be",
surfaceTint: "#f28b82",
background: "#1a1110",
backgroundText: "#f1dedc",
outline: "#a08c89",
surfaceContainer: "#271d1c",
surfaceContainerHigh: "#322826",
surfaceContainerHighest: "#3d3231",
},
cyan: {
name: "Cyan",
primary: "#00bcd4",
primaryText: "#000000",
primaryContainer: "#004d5c",
secondary: "#4dd0e1",
surface: "#0e1416",
surfaceText: "#dee3e5",
surfaceVariant: "#3f484a",
surfaceVariantText: "#bfc8ca",
surfaceTint: "#4dd0e1",
background: "#0e1416",
backgroundText: "#dee3e5",
outline: "#899295",
surfaceContainer: "#1b2122",
surfaceContainerHigh: "#252b2c",
surfaceContainerHighest: "#303637",
},
pink: {
name: "Pink",
primary: "#e91e63",
primaryText: "#000000",
primaryContainer: "#4a0e2f",
secondary: "#f8bbd9",
surface: "#191112",
surfaceText: "#f0dee0",
surfaceVariant: "#524345",
surfaceVariantText: "#d6c2c3",
surfaceTint: "#f8bbd9",
background: "#191112",
backgroundText: "#f0dee0",
outline: "#9f8c8e",
surfaceContainer: "#261d1e",
surfaceContainerHigh: "#312829",
surfaceContainerHighest: "#3c3233",
},
amber: {
name: "Amber",
primary: "#ffc107",
primaryText: "#000000",
primaryContainer: "#4a3c00",
secondary: "#ffd54f",
surface: "#17130b",
surfaceText: "#ebe1d4",
surfaceVariant: "#4d4639",
surfaceVariantText: "#d0c5b4",
surfaceTint: "#ffd54f",
background: "#17130b",
backgroundText: "#ebe1d4",
outline: "#998f80",
surfaceContainer: "#231f17",
surfaceContainerHigh: "#2e2921",
surfaceContainerHighest: "#39342b",
},
coral: {
name: "Coral",
primary: "#ffb4ab",
primaryText: "#000000",
primaryContainer: "#8c1d18",
secondary: "#f9dedc",
surface: "#1a1110",
surfaceText: "#f1dedc",
surfaceVariant: "#534341",
surfaceVariantText: "#d8c2bf",
surfaceTint: "#ffb4ab",
background: "#1a1110",
backgroundText: "#f1dedc",
outline: "#a08c8a",
surfaceContainer: "#271d1c",
surfaceContainerHigh: "#322826",
surfaceContainerHighest: "#3d3231",
},
monochrome: {
name: "Monochrome",
primary: "#ffffff",
primaryText: "#2b303c",
primaryContainer: "#424753",
secondary: "#c4c6d0",
surface: "#2a2a2a",
surfaceText: "#e4e2e3",
surfaceVariant: "#474648",
surfaceVariantText: "#c8c6c7",
surfaceTint: "#c2c6d6",
background: "#131315",
backgroundText: "#e4e2e3",
outline: "#929092",
surfaceContainer: "#353535",
surfaceContainerHigh: "#424242",
surfaceContainerHighest: "#505050",
error: "#ffb4ab",
warning: "#3f4759",
info: "#595e6c",
matugen_type: "scheme-monochrome",
},
},
LIGHT: {
blue: {
name: "Blue Light",
primary: "#1976d2",
primaryText: "#ffffff",
primaryContainer: "#e3f2fd",
secondary: "#42a5f5",
surface: "#f7f9ff",
surfaceText: "#181c20",
surfaceVariant: "#dee3eb",
surfaceVariantText: "#42474e",
surfaceTint: "#1976d2",
background: "#f7f9ff",
backgroundText: "#181c20",
outline: "#72777f",
surfaceContainer: "#eceef4",
surfaceContainerHigh: "#e6e8ee",
surfaceContainerHighest: "#e0e2e8",
},
purple: {
name: "Purple Light",
primary: "#6750A4",
primaryText: "#ffffff",
primaryContainer: "#EADDFF",
secondary: "#625B71",
surface: "#fef7ff",
surfaceText: "#1d1b20",
surfaceVariant: "#e7e0eb",
surfaceVariantText: "#49454e",
surfaceTint: "#6750A4",
background: "#fef7ff",
backgroundText: "#1d1b20",
outline: "#7a757f",
surfaceContainer: "#f2ecf4",
surfaceContainerHigh: "#ece6ee",
surfaceContainerHighest: "#e6e0e9",
},
green: {
name: "Green Light",
primary: "#2e7d32",
primaryText: "#ffffff",
primaryContainer: "#e8f5e8",
secondary: "#4caf50",
surface: "#f7fbf1",
surfaceText: "#191d17",
surfaceVariant: "#dee5d8",
surfaceVariantText: "#424940",
surfaceTint: "#2e7d32",
background: "#f7fbf1",
backgroundText: "#191d17",
outline: "#72796f",
surfaceContainer: "#ecefe6",
surfaceContainerHigh: "#e6e9e0",
surfaceContainerHighest: "#e0e4db",
},
orange: {
name: "Orange Light",
primary: "#e65100",
primaryText: "#ffffff",
primaryContainer: "#ffecb3",
secondary: "#ff9800",
surface: "#fff8f6",
surfaceText: "#221a16",
surfaceVariant: "#f4ded5",
surfaceVariantText: "#52443d",
surfaceTint: "#e65100",
background: "#fff8f6",
backgroundText: "#221a16",
outline: "#85736c",
surfaceContainer: "#fceae3",
surfaceContainerHigh: "#f6e5de",
surfaceContainerHighest: "#f0dfd8",
},
red: {
name: "Red Light",
primary: "#d32f2f",
primaryText: "#ffffff",
primaryContainer: "#ffebee",
secondary: "#f44336",
surface: "#fff8f7",
surfaceText: "#231918",
surfaceVariant: "#f5ddda",
surfaceVariantText: "#534341",
surfaceTint: "#d32f2f",
background: "#fff8f7",
backgroundText: "#231918",
outline: "#857370",
surfaceContainer: "#fceae7",
surfaceContainerHigh: "#f7e4e1",
surfaceContainerHighest: "#f1dedc",
},
cyan: {
name: "Cyan Light",
primary: "#0097a7",
primaryText: "#ffffff",
primaryContainer: "#e0f2f1",
secondary: "#00bcd4",
surface: "#f5fafc",
surfaceText: "#171d1e",
surfaceVariant: "#dbe4e6",
surfaceVariantText: "#3f484a",
surfaceTint: "#0097a7",
background: "#f5fafc",
backgroundText: "#171d1e",
outline: "#6f797b",
surfaceContainer: "#e9eff0",
surfaceContainerHigh: "#e3e9eb",
surfaceContainerHighest: "#dee3e5",
},
pink: {
name: "Pink Light",
primary: "#c2185b",
primaryText: "#ffffff",
primaryContainer: "#fce4ec",
secondary: "#e91e63",
surface: "#fff8f7",
surfaceText: "#22191a",
surfaceVariant: "#f3dddf",
surfaceVariantText: "#524345",
surfaceTint: "#c2185b",
background: "#fff8f7",
backgroundText: "#22191a",
outline: "#847375",
surfaceContainer: "#fbeaeb",
surfaceContainerHigh: "#f5e4e5",
surfaceContainerHighest: "#f0dee0",
},
amber: {
name: "Amber Light",
primary: "#ff8f00",
primaryText: "#000000",
primaryContainer: "#fff8e1",
secondary: "#ffc107",
surface: "#fff8f2",
surfaceText: "#1f1b13",
surfaceVariant: "#ede1cf",
surfaceVariantText: "#4d4639",
surfaceTint: "#ff8f00",
background: "#fff8f2",
backgroundText: "#1f1b13",
outline: "#7f7667",
surfaceContainer: "#f6ecdf",
surfaceContainerHigh: "#f1e7d9",
surfaceContainerHighest: "#ebe1d4",
},
coral: {
name: "Coral Light",
primary: "#8c1d18",
primaryText: "#ffffff",
primaryContainer: "#ffdad6",
secondary: "#ff5449",
surface: "#fff8f7",
surfaceText: "#231918",
surfaceVariant: "#f5ddda",
surfaceVariantText: "#534341",
surfaceTint: "#8c1d18",
background: "#fff8f7",
backgroundText: "#231918",
outline: "#857371",
surfaceContainer: "#fceae7",
surfaceContainerHigh: "#f6e4e2",
surfaceContainerHighest: "#f1dedc",
},
monochrome: {
name: "Monochrome Light",
primary: "#2b303c",
primaryText: "#ffffff",
primaryContainer: "#d6d7dc",
secondary: "#4a4d56",
surface: "#f5f5f6",
surfaceText: "#2a2a2a",
surfaceVariant: "#e0e0e2",
surfaceVariantText: "#424242",
surfaceTint: "#5a5f6e",
background: "#ffffff",
backgroundText: "#1a1a1a",
outline: "#757577",
surfaceContainer: "#e8e8ea",
surfaceContainerHigh: "#dcdcde",
surfaceContainerHighest: "#d0d0d2",
error: "#ba1a1a",
warning: "#f9e79f",
info: "#5d6475",
matugen_type: "scheme-monochrome",
},
},
};
const ThemeCategories = {
GENERIC: {
name: "Generic",
variants: ["blue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral", "monochrome"]
},
CATPPUCCIN: {
name: "Catppuccin",
variants: Object.keys(CatppuccinVariants)
}
}
GENERIC: {
name: "Generic",
variants: [
"blue",
"purple",
"green",
"orange",
"red",
"cyan",
"pink",
"amber",
"coral",
"monochrome",
],
},
};
const ThemeNames = {
BLUE: "blue",
PURPLE: "purple",
GREEN: "green",
ORANGE: "orange",
RED: "red",
CYAN: "cyan",
PINK: "pink",
AMBER: "amber",
CORAL: "coral",
MONOCHROME: "monochrome",
DYNAMIC: "dynamic"
}
BLUE: "blue",
PURPLE: "purple",
GREEN: "green",
ORANGE: "orange",
RED: "red",
CYAN: "cyan",
PINK: "pink",
AMBER: "amber",
CORAL: "coral",
MONOCHROME: "monochrome",
DYNAMIC: "dynamic",
};
function isStockTheme(themeName) {
return Object.keys(StockThemes.DARK).includes(themeName)
}
function isCatppuccinVariant(themeName) {
return Object.keys(CatppuccinVariants).includes(themeName)
return Object.keys(StockThemes.DARK).includes(themeName);
}
function getAvailableThemes(isLight = false) {
return isLight ? StockThemes.LIGHT : StockThemes.DARK
return isLight ? StockThemes.LIGHT : StockThemes.DARK;
}
function getThemeByName(themeName, isLight = false) {
if (isCatppuccinVariant(themeName)) {
return getCatppuccinTheme(themeName, isLight)
}
const themes = getAvailableThemes(isLight)
return themes[themeName] || themes.blue
const themes = getAvailableThemes(isLight);
return themes[themeName] || themes.blue;
}
function getAllThemeNames() {
return Object.keys(StockThemes.DARK)
}
function getCatppuccinVariantNames() {
return Object.keys(CatppuccinVariants)
return Object.keys(StockThemes.DARK);
}
function getThemeCategories() {
return ThemeCategories
return ThemeCategories;
}

View File

@@ -92,6 +92,9 @@ Singleton {
property var matugenColors: ({})
property var _pendingGenerateParams: null
property var customThemeData: null
property var customThemeRawData: null
readonly property var currentThemeVariants: customThemeRawData?.variants || null
readonly property string currentThemeId: customThemeRawData?.id || ""
Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", stateDir]);
@@ -257,7 +260,7 @@ Singleton {
property color outlineVariant: currentThemeData.outlineVariant || Qt.rgba(outline.r, outline.g, outline.b, 0.6)
property color surfaceContainer: currentThemeData.surfaceContainer
property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh
property color surfaceContainerHighest: currentThemeData.surfaceContainerHighest
property color surfaceContainerHighest: currentThemeData.surfaceContainerHighest || surfaceContainerHigh
property color onSurface: surfaceText
property color onSurfaceVariant: surfaceVariantText
@@ -474,24 +477,28 @@ Singleton {
if (themeName === dynamic) {
currentTheme = dynamic;
currentThemeCategory = dynamic;
if (currentThemeCategory !== "registry")
currentThemeCategory = dynamic;
} else if (themeName === custom) {
currentTheme = custom;
currentThemeCategory = custom;
if (currentThemeCategory !== "registry")
currentThemeCategory = custom;
if (typeof SettingsData !== "undefined" && SettingsData.customThemeFile) {
loadCustomThemeFromFile(SettingsData.customThemeFile);
}
} else if (themeName === "" && currentThemeCategory === "registry") {
// Registry category selected but no theme chosen yet
} else {
currentTheme = themeName;
if (StockThemes.isCatppuccinVariant(themeName)) {
currentThemeCategory = "catppuccin";
} else {
if (currentThemeCategory !== "registry") {
currentThemeCategory = "generic";
}
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode) {
SettingsData.set("currentThemeCategory", currentThemeCategory);
SettingsData.set("currentThemeName", currentTheme);
}
if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme();
@@ -553,62 +560,103 @@ Singleton {
themeCategoryTransitionTimer.restart();
}
function getCatppuccinColor(variantName) {
const catColors = {
"cat-rosewater": "#f5e0dc",
"cat-flamingo": "#f2cdcd",
"cat-pink": "#f5c2e7",
"cat-mauve": "#cba6f7",
"cat-red": "#f38ba8",
"cat-maroon": "#eba0ac",
"cat-peach": "#fab387",
"cat-yellow": "#f9e2af",
"cat-green": "#a6e3a1",
"cat-teal": "#94e2d5",
"cat-sky": "#89dceb",
"cat-sapphire": "#74c7ec",
"cat-blue": "#89b4fa",
"cat-lavender": "#b4befe"
};
return catColors[variantName] || "#cba6f7";
}
function getCatppuccinVariantName(variantName) {
const catNames = {
"cat-rosewater": "Rosewater",
"cat-flamingo": "Flamingo",
"cat-pink": "Pink",
"cat-mauve": "Mauve",
"cat-red": "Red",
"cat-maroon": "Maroon",
"cat-peach": "Peach",
"cat-yellow": "Yellow",
"cat-green": "Green",
"cat-teal": "Teal",
"cat-sky": "Sky",
"cat-sapphire": "Sapphire",
"cat-blue": "Blue",
"cat-lavender": "Lavender"
};
return catNames[variantName] || "Unknown";
}
function loadCustomTheme(themeData) {
customThemeRawData = themeData;
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
var baseColors = {};
if (themeData.dark || themeData.light) {
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light;
customThemeData = selectedTheme;
baseColors = themeData[colorMode] || themeData.dark || themeData.light || {};
} else {
customThemeData = themeData;
baseColors = themeData;
}
if (themeData.variants) {
const themeId = themeData.id || "";
if (themeData.variants.type === "multi" && themeData.variants.flavors && themeData.variants.accents) {
const defaults = themeData.variants.defaults || {};
const modeDefaults = defaults[colorMode] || defaults.dark || {};
const stored = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, modeDefaults) : modeDefaults;
var flavorId = stored.flavor || modeDefaults.flavor || "";
const accentId = stored.accent || modeDefaults.accent || "";
var flavor = findVariant(themeData.variants.flavors, flavorId);
if (flavor) {
const hasCurrentModeColors = flavor[colorMode] && (flavor[colorMode].primary || flavor[colorMode].surface);
if (!hasCurrentModeColors) {
flavorId = modeDefaults.flavor || "";
flavor = findVariant(themeData.variants.flavors, flavorId);
}
}
const accent = findAccent(themeData.variants.accents, accentId);
if (flavor) {
const flavorColors = flavor[colorMode] || flavor.dark || flavor.light || {};
baseColors = mergeColors(baseColors, flavorColors);
}
if (accent && flavor) {
const accentColors = accent[flavor.id] || {};
baseColors = mergeColors(baseColors, accentColors);
}
customThemeData = baseColors;
generateSystemThemesFromCurrentTheme();
return;
}
if (themeData.variants.options && themeData.variants.options.length > 0) {
const selectedVariantId = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default;
const variant = findVariant(themeData.variants.options, selectedVariantId);
if (variant) {
const variantColors = variant[colorMode] || variant.dark || variant.light || {};
customThemeData = mergeColors(baseColors, variantColors);
generateSystemThemesFromCurrentTheme();
return;
}
}
}
customThemeData = baseColors;
generateSystemThemesFromCurrentTheme();
}
function findVariant(options, variantId) {
if (!variantId || !options)
return null;
for (var i = 0; i < options.length; i++) {
if (options[i].id === variantId)
return options[i];
}
return options[0] || null;
}
function findAccent(accents, accentId) {
if (!accentId || !accents)
return null;
for (var i = 0; i < accents.length; i++) {
if (accents[i].id === accentId)
return accents[i];
}
return accents[0] || null;
}
function mergeColors(base, overlay) {
var result = JSON.parse(JSON.stringify(base));
for (var key in overlay) {
if (overlay[key])
result[key] = overlay[key];
}
return result;
}
function loadCustomThemeFromFile(filePath) {
customThemeFileView.path = filePath;
}
function reloadCustomThemeVariant() {
if (currentTheme !== "custom" || !customThemeRawData)
return;
loadCustomTheme(customThemeRawData);
}
property alias availableThemeNames: root._availableThemeNames
readonly property var _availableThemeNames: StockThemes.getAllThemeNames()
property string currentThemeName: currentTheme
@@ -639,10 +687,9 @@ Singleton {
}
}
property var widgetBaseHoverColor: {
const baseColor = widgetBaseBackgroundColor;
const factor = 1.2;
return isLightMode ? Qt.darker(baseColor, factor) : Qt.lighter(baseColor, factor);
property color widgetBaseHoverColor: {
const blended = blend(widgetBaseBackgroundColor, primary, 0.1);
return withAlpha(blended, Math.max(0.3, blended.a));
}
property color widgetIconColor: {
@@ -829,7 +876,7 @@ Singleton {
if (typeof SettingsData !== "undefined") {
const skipTemplates = [];
if (!SettingsData.runDmsMatugenTemplates) {
skipTemplates.push("gtk", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "vesktop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
skipTemplates.push("gtk", "neovim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
} else {
if (!SettingsData.matugenTemplateGtk)
skipTemplates.push("gtk");
@@ -843,14 +890,20 @@ Singleton {
skipTemplates.push("firefox");
if (!SettingsData.matugenTemplatePywalfox)
skipTemplates.push("pywalfox");
if (!SettingsData.matugenTemplateZenBrowser)
skipTemplates.push("zenbrowser");
if (!SettingsData.matugenTemplateVesktop)
skipTemplates.push("vesktop");
if (!SettingsData.matugenTemplateEquibop)
skipTemplates.push("equibop");
if (!SettingsData.matugenTemplateGhostty)
skipTemplates.push("ghostty");
if (!SettingsData.matugenTemplateKitty)
skipTemplates.push("kitty");
if (!SettingsData.matugenTemplateFoot)
skipTemplates.push("foot");
if (!SettingsData.matugenTemplateNeovim)
skipTemplates.push("nvim");
if (!SettingsData.matugenTemplateAlacritty)
skipTemplates.push("alacritty");
if (!SettingsData.matugenTemplateWezterm)
@@ -899,8 +952,48 @@ Singleton {
let darkTheme, lightTheme;
if (currentTheme === "custom") {
darkTheme = customThemeData;
lightTheme = customThemeData;
if (customThemeRawData && (customThemeRawData.dark || customThemeRawData.light)) {
darkTheme = customThemeRawData.dark || customThemeRawData.light;
lightTheme = customThemeRawData.light || customThemeRawData.dark;
if (customThemeRawData.variants) {
const themeId = customThemeRawData.id || "";
if (customThemeRawData.variants.type === "multi" && customThemeRawData.variants.flavors && customThemeRawData.variants.accents) {
const defaults = customThemeRawData.variants.defaults || {};
const darkDefaults = defaults.dark || {};
const lightDefaults = defaults.light || defaults.dark || {};
const storedDark = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults) : darkDefaults;
const storedLight = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults) : lightDefaults;
const darkFlavorId = storedDark.flavor || darkDefaults.flavor || "";
const lightFlavorId = storedLight.flavor || lightDefaults.flavor || "";
const accentId = storedDark.accent || darkDefaults.accent || "";
const darkFlavor = findVariant(customThemeRawData.variants.flavors, darkFlavorId);
const lightFlavor = findVariant(customThemeRawData.variants.flavors, lightFlavorId);
const accent = findAccent(customThemeRawData.variants.accents, accentId);
if (darkFlavor) {
darkTheme = mergeColors(darkTheme, darkFlavor.dark || {});
if (accent)
darkTheme = mergeColors(darkTheme, accent[darkFlavor.id] || {});
}
if (lightFlavor) {
lightTheme = mergeColors(lightTheme, lightFlavor.light || {});
if (accent)
lightTheme = mergeColors(lightTheme, accent[lightFlavor.id] || {});
}
} else if (customThemeRawData.variants.options) {
const selectedVariantId = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default;
const variant = findVariant(customThemeRawData.variants.options, selectedVariantId);
if (variant) {
darkTheme = mergeColors(darkTheme, variant.dark || {});
lightTheme = mergeColors(lightTheme, variant.light || {});
}
}
}
} else {
darkTheme = customThemeData;
lightTheme = customThemeData;
}
} else {
darkTheme = StockThemes.getThemeByName(currentTheme, false);
lightTheme = StockThemes.getThemeByName(currentTheme, true);
@@ -918,6 +1011,7 @@ Singleton {
function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
const colors = {};
const isLight = SessionData !== "undefined" && SessionData.isLightMode;
function addColor(matugenKey, darkVal, lightVal) {
if (!darkVal && !lightVal)
@@ -930,7 +1024,7 @@ Singleton {
"color": String(lightVal || darkVal)
},
"default": {
"color": String(darkVal || lightVal)
"color": String((isLight && lightVal) ? lightVal : darkVal)
}
};
}

View File

@@ -21,9 +21,12 @@ Singleton {
showNetworkIcon: true,
showBluetoothIcon: true,
showAudioIcon: true,
showAudioPercent: true,
showVpnIcon: true,
showBrightnessIcon: false,
showBrightnessPercent: false,
showMicIcon: false,
showMicPercent: true,
showBatteryIcon: false,
showPrinterIcon: false
};
@@ -65,12 +68,18 @@ Singleton {
item.showBluetoothIcon = order[i].showBluetoothIcon;
if (isObj && order[i].showAudioIcon !== undefined)
item.showAudioIcon = order[i].showAudioIcon;
if (isObj && order[i].showAudioPercent !== undefined)
item.showAudioPercent = order[i].showAudioPercent;
if (isObj && order[i].showVpnIcon !== undefined)
item.showVpnIcon = order[i].showVpnIcon;
if (isObj && order[i].showBrightnessIcon !== undefined)
item.showBrightnessIcon = order[i].showBrightnessIcon;
if (isObj && order[i].showBrightnessPercent !== undefined)
item.showBrightnessPercent = order[i].showBrightnessPercent;
if (isObj && order[i].showMicIcon !== undefined)
item.showMicIcon = order[i].showMicIcon;
if (isObj && order[i].showMicPercent !== undefined)
item.showMicPercent = order[i].showMicPercent;
if (isObj && order[i].showBatteryIcon !== undefined)
item.showBatteryIcon = order[i].showBatteryIcon;
if (isObj && order[i].showPrinterIcon !== undefined)

View File

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

View File

@@ -7,7 +7,9 @@ function percentToUnit(v) {
var SPEC = {
currentThemeName: { def: "blue", onChange: "applyStoredTheme" },
currentThemeCategory: { def: "generic" },
customThemeFile: { def: "" },
registryThemeVariants: { def: {} },
matugenScheme: { def: "scheme-tonal-spot", onChange: "regenSystemThemes" },
runUserMatugenTemplates: { def: true, onChange: "regenSystemThemes" },
matugenTargetMonitor: { def: "", onChange: "regenSystemThemes" },
@@ -18,6 +20,8 @@ var SPEC = {
widgetBackgroundColor: { def: "sch" },
widgetColorMode: { def: "default" },
cornerRadius: { def: 12, onChange: "updateNiriLayout" },
niriLayoutGapsOverride: { def: -1, onChange: "updateNiriLayout" },
niriLayoutRadiusOverride: { def: -1, onChange: "updateNiriLayout" },
use24HourClock: { def: true },
showSeconds: { def: false },
@@ -51,9 +55,12 @@ var SPEC = {
controlCenterShowNetworkIcon: { def: true },
controlCenterShowBluetoothIcon: { def: true },
controlCenterShowAudioIcon: { def: true },
controlCenterShowAudioPercent: { def: false },
controlCenterShowVpnIcon: { def: true },
controlCenterShowBrightnessIcon: { def: false },
controlCenterShowBrightnessPercent: { def: false },
controlCenterShowMicIcon: { def: false },
controlCenterShowMicPercent: { def: false },
controlCenterShowBatteryIcon: { def: false },
controlCenterShowPrinterIcon: { def: false },
@@ -80,11 +87,13 @@ var SPEC = {
maxWorkspaceIcons: { def: 3 },
workspacesPerMonitor: { def: true },
showOccupiedWorkspacesOnly: { def: false },
reverseScrolling: { def: false },
dwlShowAllTags: { def: false },
workspaceNameIcons: { def: {} },
waveProgressEnabled: { def: true },
scrollTitleEnabled: { def: true },
audioVisualizerEnabled: { def: true },
audioScrollEnabled: { def: true },
clockCompactMode: { def: false },
focusedWindowCompactMode: { def: false },
runningAppsCompactMode: { def: true },
@@ -154,6 +163,7 @@ var SPEC = {
batterySuspendTimeout: { def: 0 },
batterySuspendBehavior: { def: 0 },
batteryProfileName: { def: "" },
batteryChargeLimit: { def: 100 },
lockBeforeSuspend: { def: false },
loginctlLockIntegration: { def: true },
fadeToLockEnabled: { def: false },
@@ -177,11 +187,14 @@ var SPEC = {
matugenTemplateQt6ct: { def: true },
matugenTemplateFirefox: { def: true },
matugenTemplatePywalfox: { def: true },
matugenTemplateZenBrowser: { def: true },
matugenTemplateVesktop: { def: true },
matugenTemplateEquibop: { def: true },
matugenTemplateGhostty: { def: true },
matugenTemplateKitty: { def: true },
matugenTemplateFoot: { def: true },
matugenTemplateAlacritty: { def: true },
matugenTemplateNeovim: { def: true },
matugenTemplateWezterm: { def: true },
matugenTemplateDgop: { def: true },
matugenTemplateKcolorscheme: { def: true },
@@ -201,6 +214,7 @@ var SPEC = {
dockBorderColor: { def: "surfaceText" },
dockBorderOpacity: { def: 1.0, coerce: percentToUnit },
dockBorderThickness: { def: 1 },
dockIsolateDisplays: { def: false },
notificationOverlayEnabled: { def: false },
overviewRows: { def: 2, persist: false },
@@ -258,6 +272,8 @@ var SPEC = {
displayNameMode: { def: "system" },
screenPreferences: { def: {} },
showOnLastDisplay: { def: {} },
niriOutputSettings: { def: {} },
hyprlandOutputSettings: { def: {} },
barConfigs: { def: [{
id: "default",
@@ -290,6 +306,7 @@ var SPEC = {
fontScale: 1.0,
autoHide: false,
autoHideDelay: 250,
showOnWindowsOpen: false,
openOnOverview: false,
visible: true,
popupGapsAuto: true,
@@ -298,7 +315,52 @@ var SPEC = {
scrollEnabled: true,
scrollXBehavior: "column",
scrollYBehavior: "workspace"
}], onChange: "updateBarConfigs" }
}], onChange: "updateBarConfigs" },
desktopClockEnabled: { def: false },
desktopClockStyle: { def: "analog" },
desktopClockTransparency: { def: 0.8, coerce: percentToUnit },
desktopClockColorMode: { def: "primary" },
desktopClockCustomColor: { def: "#ffffff" },
desktopClockShowDate: { def: true },
desktopClockShowAnalogNumbers: { def: false },
desktopClockShowAnalogSeconds: { def: true },
desktopClockX: { def: -1 },
desktopClockY: { def: -1 },
desktopClockWidth: { def: 280 },
desktopClockHeight: { def: 180 },
desktopClockDisplayPreferences: { def: ["all"] },
systemMonitorEnabled: { def: false },
systemMonitorShowHeader: { def: true },
systemMonitorTransparency: { def: 0.8, coerce: percentToUnit },
systemMonitorColorMode: { def: "primary" },
systemMonitorCustomColor: { def: "#ffffff" },
systemMonitorShowCpu: { def: true },
systemMonitorShowCpuGraph: { def: true },
systemMonitorShowCpuTemp: { def: true },
systemMonitorShowGpuTemp: { def: false },
systemMonitorGpuPciId: { def: "" },
systemMonitorShowMemory: { def: true },
systemMonitorShowMemoryGraph: { def: true },
systemMonitorShowNetwork: { def: true },
systemMonitorShowNetworkGraph: { def: true },
systemMonitorShowDisk: { def: true },
systemMonitorShowTopProcesses: { def: false },
systemMonitorTopProcessCount: { def: 3 },
systemMonitorTopProcessSortBy: { def: "cpu" },
systemMonitorGraphInterval: { def: 60 },
systemMonitorLayoutMode: { def: "auto" },
systemMonitorX: { def: -1 },
systemMonitorY: { def: -1 },
systemMonitorWidth: { def: 320 },
systemMonitorHeight: { def: 480 },
systemMonitorDisplayPreferences: { def: ["all"] },
systemMonitorVariants: { def: [] },
desktopWidgetPositions: { def: {} },
desktopWidgetGridSettings: { def: {} },
desktopWidgetInstances: { def: [] }
};
function getValidKeys() {

View File

@@ -119,6 +119,101 @@ function migrateToVersion(obj, targetVersion) {
settings.configVersion = 3;
}
if (currentVersion < 4) {
console.info("Migrating settings from version", currentVersion, "to version 4");
console.info("Migrating desktop widgets to unified desktopWidgetInstances");
var instances = [];
if (settings.desktopClockEnabled) {
var clockPositions = {};
if (settings.desktopClockX !== undefined && settings.desktopClockX >= 0) {
clockPositions["default"] = {
x: settings.desktopClockX,
y: settings.desktopClockY,
width: settings.desktopClockWidth || 280,
height: settings.desktopClockHeight || 180
};
}
instances.push({
id: "dw_clock_primary",
widgetType: "desktopClock",
name: "Desktop Clock",
enabled: true,
config: {
style: settings.desktopClockStyle || "analog",
transparency: settings.desktopClockTransparency !== undefined ? settings.desktopClockTransparency : 0.8,
colorMode: settings.desktopClockColorMode || "primary",
customColor: settings.desktopClockCustomColor || "#ffffff",
showDate: settings.desktopClockShowDate !== false,
showAnalogNumbers: settings.desktopClockShowAnalogNumbers || false,
showAnalogSeconds: settings.desktopClockShowAnalogSeconds !== false,
displayPreferences: settings.desktopClockDisplayPreferences || ["all"]
},
positions: clockPositions
});
}
if (settings.systemMonitorEnabled) {
var sysmonPositions = {};
if (settings.systemMonitorX !== undefined && settings.systemMonitorX >= 0) {
sysmonPositions["default"] = {
x: settings.systemMonitorX,
y: settings.systemMonitorY,
width: settings.systemMonitorWidth || 320,
height: settings.systemMonitorHeight || 480
};
}
instances.push({
id: "dw_sysmon_primary",
widgetType: "systemMonitor",
name: "System Monitor",
enabled: true,
config: {
showHeader: settings.systemMonitorShowHeader !== false,
transparency: settings.systemMonitorTransparency !== undefined ? settings.systemMonitorTransparency : 0.8,
colorMode: settings.systemMonitorColorMode || "primary",
customColor: settings.systemMonitorCustomColor || "#ffffff",
showCpu: settings.systemMonitorShowCpu !== false,
showCpuGraph: settings.systemMonitorShowCpuGraph !== false,
showCpuTemp: settings.systemMonitorShowCpuTemp !== false,
showGpuTemp: settings.systemMonitorShowGpuTemp || false,
gpuPciId: settings.systemMonitorGpuPciId || "",
showMemory: settings.systemMonitorShowMemory !== false,
showMemoryGraph: settings.systemMonitorShowMemoryGraph !== false,
showNetwork: settings.systemMonitorShowNetwork !== false,
showNetworkGraph: settings.systemMonitorShowNetworkGraph !== false,
showDisk: settings.systemMonitorShowDisk !== false,
showTopProcesses: settings.systemMonitorShowTopProcesses || false,
topProcessCount: settings.systemMonitorTopProcessCount || 3,
topProcessSortBy: settings.systemMonitorTopProcessSortBy || "cpu",
layoutMode: settings.systemMonitorLayoutMode || "auto",
graphInterval: settings.systemMonitorGraphInterval || 60,
displayPreferences: settings.systemMonitorDisplayPreferences || ["all"]
},
positions: sysmonPositions
});
}
var variants = settings.systemMonitorVariants || [];
for (var i = 0; i < variants.length; i++) {
var v = variants[i];
instances.push({
id: v.id,
widgetType: "systemMonitor",
name: v.name || ("System Monitor " + (i + 2)),
enabled: true,
config: v.config || {},
positions: v.positions || {}
});
}
settings.desktopWidgetInstances = instances;
settings.configVersion = 4;
}
return settings;
}

View File

@@ -58,6 +58,8 @@ Item {
WallpaperBackground {}
DesktopWidgetLayer {}
Lock {
id: lock
}
@@ -508,6 +510,22 @@ Item {
Connections {
target: DMSService
function onOpenUrlRequested(url) {
if (url.startsWith("dms://theme/install/")) {
var themeId = url.replace("dms://theme/install/", "").split(/[?#]/)[0];
if (themeId) {
PopoutService.pendingThemeInstall = themeId;
PopoutService.openSettingsWithTab("theme");
}
return;
}
if (url.startsWith("dms://plugin/install/")) {
var pluginId = url.replace("dms://plugin/install/", "").split(/[?#]/)[0];
if (pluginId) {
PopoutService.pendingPluginInstall = pluginId;
PopoutService.openSettingsWithTab("plugins");
}
return;
}
browserPickerModal.url = url;
browserPickerModal.open();
}

View File

@@ -893,4 +893,58 @@ Item {
target: "clipboard"
}
IpcHandler {
function toggleOverlay(instanceId: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const currentValue = instance.config?.showOnOverlay ?? false;
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
showOnOverlay: !currentValue
});
return !currentValue ? `DESKTOP_WIDGET_OVERLAY_ENABLED: ${instanceId}` : `DESKTOP_WIDGET_OVERLAY_DISABLED: ${instanceId}`;
}
function setOverlay(instanceId: string, enabled: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const enabledBool = enabled === "true" || enabled === "1";
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
showOnOverlay: enabledBool
});
return enabledBool ? `DESKTOP_WIDGET_OVERLAY_ENABLED: ${instanceId}` : `DESKTOP_WIDGET_OVERLAY_DISABLED: ${instanceId}`;
}
function list(): string {
const instances = SettingsData.desktopWidgetInstances || [];
if (instances.length === 0)
return "No desktop widgets configured";
return instances.map(i => `${i.id} [${i.widgetType}] ${i.name || i.widgetType}`).join("\n");
}
function status(instanceId: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const overlay = instance.config?.showOnOverlay ?? false;
const overview = instance.config?.showOnOverview ?? false;
return `overlay: ${overlay}, overview: ${overview}`;
}
target: "desktopWidget"
}
}

View File

@@ -46,17 +46,16 @@ Item {
property var customKeyboardFocus: null
property bool useOverlayLayer: false
readonly property alias contentWindow: contentWindow
readonly property alias backgroundWindow: backgroundWindow
readonly property alias clickCatcher: clickCatcher
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
readonly property bool useSingleWindow: root.useHyprlandFocusGrab
readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground
readonly property bool useSingleWindow: useHyprlandFocusGrab || useBackground
signal opened
signal dialogClosed
signal backgroundClicked
property bool animationsEnabled: true
readonly property bool useBackgroundWindow: !useSingleWindow
function open() {
ModalManager.openModal(root);
@@ -64,25 +63,21 @@ Item {
const focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen) {
contentWindow.screen = focusedScreen;
if (useBackgroundWindow)
backgroundWindow.screen = focusedScreen;
if (!useSingleWindow)
clickCatcher.screen = focusedScreen;
}
shouldBeVisible = true;
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = true;
Qt.callLater(() => {
contentWindow.visible = true;
shouldHaveFocus = false;
Qt.callLater(() => {
shouldHaveFocus = Qt.binding(() => shouldBeVisible);
});
});
if (!useSingleWindow)
clickCatcher.visible = true;
contentWindow.visible = true;
shouldHaveFocus = false;
Qt.callLater(() => shouldHaveFocus = Qt.binding(() => shouldBeVisible));
}
function close() {
shouldBeVisible = false;
shouldHaveFocus = false;
ModalManager.closeModal(root);
closeTimer.restart();
}
@@ -90,10 +85,11 @@ Item {
animationsEnabled = false;
shouldBeVisible = false;
shouldHaveFocus = false;
ModalManager.closeModal(root);
closeTimer.stop();
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = false;
if (!useSingleWindow)
clickCatcher.visible = false;
dialogClosed();
Qt.callLater(() => animationsEnabled = true);
}
@@ -105,9 +101,8 @@ Item {
Connections {
target: ModalManager
function onCloseAllModalsExcept(excludedModal) {
if (excludedModal !== root && !allowStacking && shouldBeVisible) {
if (excludedModal !== root && !allowStacking && shouldBeVisible)
close();
}
}
}
@@ -129,8 +124,8 @@ Item {
const newScreen = CompositorService.getFocusedScreen();
if (newScreen) {
contentWindow.screen = newScreen;
if (useBackgroundWindow)
backgroundWindow.screen = newScreen;
if (!useSingleWindow)
clickCatcher.screen = newScreen;
}
}
}
@@ -139,12 +134,12 @@ Item {
id: closeTimer
interval: animationDuration + 120
onTriggered: {
if (!shouldBeVisible) {
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = false;
dialogClosed();
}
if (shouldBeVisible)
return;
contentWindow.visible = false;
if (!useSingleWindow)
clickCatcher.visible = false;
dialogClosed();
}
}
@@ -179,11 +174,11 @@ Item {
})(), dpr)
PanelWindow {
id: backgroundWindow
id: clickCatcher
visible: false
color: "transparent"
WlrLayershell.namespace: root.layerNamespace + ":background"
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
@@ -199,41 +194,16 @@ Item {
item: Rectangle {
x: root.alignedX
y: root.alignedY
width: root.shouldBeVisible ? root.alignedWidth : 0
height: root.shouldBeVisible ? root.alignedHeight : 0
width: root.alignedWidth
height: root.alignedHeight
}
intersection: Intersection.Xor
}
MouseArea {
anchors.fill: parent
enabled: root.useBackgroundWindow && root.closeOnBackgroundClick && root.shouldBeVisible
onClicked: mouse => {
const clickX = mouse.x;
const clickY = mouse.y;
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
if (!outsideContent)
return;
root.backgroundClicked();
}
}
Rectangle {
id: background
anchors.fill: parent
color: "black"
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: root.useBackgroundWindow && root.showBackground && SettingsData.modalDarkenBackground
Behavior on opacity {
enabled: root.animationsEnabled
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
onClicked: root.backgroundClicked()
}
}
@@ -273,8 +243,8 @@ Item {
anchors {
left: true
top: true
right: root.useSingleWindow ? true : undefined
bottom: root.useSingleWindow ? true : undefined
right: root.useSingleWindow
bottom: root.useSingleWindow
}
WlrLayershell.margins {
@@ -284,8 +254,8 @@ Item {
bottom: 0
}
implicitWidth: root.useSingleWindow ? undefined : root.alignedWidth + (shadowBuffer * 2)
implicitHeight: root.useSingleWindow ? undefined : root.alignedHeight + (shadowBuffer * 2)
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
onVisibleChanged: {
if (visible) {
@@ -309,8 +279,8 @@ Item {
anchors.fill: parent
z: -1
color: "black"
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: root.useSingleWindow && root.showBackground && SettingsData.modalDarkenBackground
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: root.useBackground
Behavior on opacity {
enabled: root.animationsEnabled

View File

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

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