1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 05:52:50 -05:00

Compare commits

..

3 Commits

Author SHA1 Message Date
bbedward
9673078a75 fix gui 2025-12-15 16:30:35 -05:00
bbedward
9e8c93bfd7 displays: fix hyprland config saving 2025-12-15 16:04:31 -05:00
bbedward
43d6f4b1d3 displays: add configurator (Beta)
- Position, resolution, refresh, orientation, VRR
- niri, Hyprland, MangoWC
- Rely on wlr-output for reading data, compositors to write output
  configurations
- Re-organize display setting group
2025-12-15 15:55:31 -05:00
303 changed files with 9804 additions and 40423 deletions

View File

@@ -398,3 +398,297 @@ 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,17 +3,8 @@ 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., 1.0.3, leave empty for latest release)'
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
required: false
default: ''
release:
@@ -22,27 +13,8 @@ on:
default: '1'
jobs:
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
@@ -67,7 +39,7 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release=$RELEASE" >> $GITHUB_OUTPUT
echo "✅ Building ${{ matrix.package }} version: $VERSION-$RELEASE"
echo "✅ Building DMS hotfix version: $VERSION-$RELEASE"
- name: Setup build environment
run: |
@@ -98,31 +70,157 @@ jobs:
VERSION="${{ steps.version.outputs.version }}"
RELEASE="${{ steps.version.outputs.release }}"
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
PACKAGE="${{ matrix.package }}"
# Copy spec file from repository
cp distro/fedora/${PACKAGE}.spec ~/rpmbuild/SPECS/${PACKAGE}.spec
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
# Spec for DMS stable releases - Generated by GitHub Actions
# 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
%global debug_package %{nil}
%global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
echo "✅ Spec file generated for ${PACKAGE} v${VERSION}-${RELEASE}"
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 ""
echo "=== Spec file preview ==="
head -40 ~/rpmbuild/SPECS/${PACKAGE}.spec
head -40 ~/rpmbuild/SPECS/dms.spec
- name: Build SRPM
id: build
run: |
cd ~/rpmbuild/SPECS
PACKAGE="${{ matrix.package }}"
echo "🔨 Building SRPM for ${PACKAGE}..."
rpmbuild -bs ${PACKAGE}.spec
echo "🔨 Building SRPM..."
rpmbuild -bs dms.spec
SRPM=$(ls ~/rpmbuild/SRPMS/${PACKAGE}-*.src.rpm | tail -n 1)
SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
SRPM_NAME=$(basename "$SRPM")
echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
@@ -136,7 +234,7 @@ jobs:
- name: Upload SRPM artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.package }}-stable-srpm-${{ steps.version.outputs.version }}
name: dms-stable-srpm-${{ steps.version.outputs.version }}
path: ${{ steps.build.outputs.srpm_path }}
retention-days: 90
@@ -157,40 +255,23 @@ 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 ${PACKAGE} SRPM to ${COPR_PROJECT}..."
echo "🚀 Uploading SRPM to avengemedia/dms..."
echo " SRPM: $(basename $SRPM)"
echo " Version: $VERSION"
BUILD_OUTPUT=$(copr-cli build "$COPR_PROJECT" "$SRPM" --nowait 2>&1)
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 successfully!"
echo "🔗 https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/build/$BUILD_ID/"
echo "🔗 https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
else
echo "⚠️ Could not extract build ID, but upload may have succeeded"
fi
@@ -198,13 +279,10 @@ jobs:
- name: Build summary
if: always()
run: |
PACKAGE="${{ matrix.package }}"
COPR_PROJECT="${{ steps.copr_project.outputs.copr_project }}"
echo "### 🎉 ${PACKAGE} Stable Build Summary" >> $GITHUB_STEP_SUMMARY
echo "### 🎉 DMS 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/${COPR_PROJECT}/" >> $GITHUB_STEP_SUMMARY
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Stable release has been built and uploaded to Copr!" >> $GITHUB_STEP_SUMMARY

View File

@@ -7,14 +7,13 @@ 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
@@ -98,7 +97,7 @@ jobs:
# Rebuild requested - always proceed
echo "packages=$PKG" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "🔄 Manual rebuild requested: $PKG (db$REBUILD)"
echo "🔄 Manual rebuild requested: $PKG (ppa$REBUILD)"
elif [[ "$PKG" == "all" ]]; then
# Check each package and build list of those needing updates
@@ -162,51 +161,16 @@ 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
# 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
# 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
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
else
@@ -222,7 +186,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 "1.0.2")
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
@@ -243,14 +207,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 "1.0.2")
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.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}db1) nightly; urgency=medium"
echo "dms-git ($NEW_VERSION) nightly; urgency=medium"
echo ""
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
echo ""
@@ -262,15 +226,10 @@ jobs:
run: |
VERSION="${{ steps.packages.outputs.version }}"
VERSION_NO_V="${VERSION#v}"
echo "==> Updating packaging files to version: $VERSION_NO_V"
echo "Updating packaging 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)
@@ -297,13 +256,13 @@ jobs:
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
CHANGELOG_DATE=$(date -R)
{
echo "dms (${VERSION_NO_V}db1) stable; urgency=medium"
echo "dms ($VERSION_NO_V) 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}db1"
echo "✓ Updated Debian changelog to $VERSION_NO_V"
fi
- name: Install Go
@@ -330,7 +289,6 @@ 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 }}"
@@ -342,7 +300,6 @@ 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)
@@ -352,7 +309,7 @@ jobs:
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Uploading $PKG to OBS..."
if [[ -n "$REBUILD_RELEASE" ]]; then
echo "🔄 Using rebuild release number: db$REBUILD_RELEASE"
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -393,7 +350,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
echo "**Rebuild Number:** db${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
echo "**Rebuild Number:** ppa${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi

View File

@@ -51,8 +51,7 @@ jobs:
check_stable_package() {
local PKG="$1"
local PPA_NAME="$2"
# 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 LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "v\?\([^"]*\)".*/\1/' || echo "")
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,12 +96,11 @@ go.work
go.work.sum
# env file
.env*
.env
# Editor/IDE
# .idea/
# .vscode/
vim/
bin/

View File

@@ -1,17 +1,6 @@
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.dank-material-shell ];
imports = [ inputs.dms.homeModules.dankMaterialShell.default ];
}
```

View File

@@ -1,10 +1,10 @@
[Desktop Entry]
Type=Application
Name=DMS
Name=DMS Application Picker
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;x-scheme-handler/dms;text/html;application/xhtml+xml;
MimeType=x-scheme-handler/http;x-scheme-handler/https;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=on-failure
Restart=always
RestartSec=1.23
TimeoutStopSec=10

View File

@@ -14,63 +14,34 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
## System Integration
### Wayland Protocols (Client)
**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
All Wayland protocols are consumed as a client - connecting to the compositor.
**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)
| 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
**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.)
**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.)
@@ -99,7 +70,6 @@ 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
@@ -107,7 +77,6 @@ make test # Run tests
```
**Distribution build:**
```bash
make dist # Build without update/greeter features
```
@@ -115,7 +84,6 @@ 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
```
@@ -123,7 +91,6 @@ sudo make install # Install to /usr/local/bin/dms
## Development
**Setup pre-commit hooks:**
```bash
git config core.hooksPath .githooks
```
@@ -131,7 +98,6 @@ 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 \
@@ -139,7 +105,6 @@ 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,6 +144,8 @@ var (
clipConfigEnabled bool
clipConfigDisableHistory bool
clipConfigEnableHistory bool
clipConfigDisablePersist bool
clipConfigEnablePersist bool
)
func init() {
@@ -171,6 +173,8 @@ 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)")
@@ -593,6 +597,12 @@ 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,6 +171,7 @@ var pluginsUpdateCmd = &cobra.Command{
}
func runVersion(cmd *cobra.Command, args []string) {
printASCII()
fmt.Printf("%s\n", formatVersion(Version))
}
@@ -219,7 +220,7 @@ func getBaseVersion() string {
}
// Fallback
return "1.0.2"
return "0.6.2"
}
func startDebugServer() error {
@@ -513,6 +514,5 @@ func getCommonCommands() []*cobra.Command {
notifyActionCmd,
matugenCmd,
clipboardCmd,
doctorCmd,
}
}

View File

@@ -22,7 +22,6 @@ 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")
@@ -41,7 +40,6 @@ 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")
@@ -118,8 +116,6 @@ 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

@@ -1,661 +0,0 @@
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"slices"
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
"github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
"github.com/spf13/cobra"
)
var doctorCmd = &cobra.Command{
Use: "doctor",
Short: "Diagnose DMS installation and dependencies",
Long: "Check system health, verify dependencies, and diagnose configuration issues for DMS",
Run: runDoctor,
}
var doctorVerbose bool
func init() {
doctorCmd.Flags().BoolVarP(&doctorVerbose, "verbose", "v", false, "Show detailed output including paths and versions")
}
type category int
const (
catSystem category = iota
catVersions
catInstallation
catCompositor
catQuickshellFeatures
catOptionalFeatures
catConfigFiles
catServices
)
var categoryNames = []string{
"System", "Versions", "Installation", "Compositor",
"Quickshell Features", "Optional Features", "Config Files", "Services",
}
type checkResult struct {
category category
name string
status string
message string
details string
}
func runDoctor(cmd *cobra.Command, args []string) {
printDoctorHeader()
qsFeatures, qsMissingFeatures := checkQuickshellFeatures()
results := slices.Concat(
checkSystemInfo(),
checkVersions(qsMissingFeatures),
checkDMSInstallation(),
checkWindowManagers(),
qsFeatures,
checkOptionalDependencies(),
checkConfigurationFiles(),
checkSystemdServices(),
)
printResults(results)
printSummary(results, qsMissingFeatures)
}
func printDoctorHeader() {
theme := tui.TerminalTheme()
styles := tui.NewStyles(theme)
fmt.Println(getThemedASCII())
fmt.Println(styles.Title.Render("System Health Check"))
fmt.Println(styles.Subtle.Render("──────────────────────────────────────"))
fmt.Println()
}
func checkSystemInfo() []checkResult {
results := []checkResult{}
osInfo, err := distros.GetOSInfo()
if err != nil {
status, message, details := "warn", fmt.Sprintf("Unknown (%v)", err), ""
if strings.Contains(err.Error(), "Unsupported distribution") {
osRelease := readOSRelease()
if osRelease["ID"] == "nixos" {
status = "ok"
message = osRelease["PRETTY_NAME"]
if message == "" {
message = fmt.Sprintf("NixOS %s", osRelease["VERSION_ID"])
}
details = "Supported for runtime (install via NixOS module or Flake)"
} else if osRelease["PRETTY_NAME"] != "" {
message = fmt.Sprintf("%s (not supported by dms setup)", osRelease["PRETTY_NAME"])
details = "DMS may work but automatic installation is not available"
}
}
results = append(results, checkResult{catSystem, "Operating System", status, message, details})
} else {
status := "ok"
message := osInfo.PrettyName
if message == "" {
message = fmt.Sprintf("%s %s", osInfo.Distribution.ID, osInfo.VersionID)
}
if distros.IsUnsupportedDistro(osInfo.Distribution.ID, osInfo.VersionID) {
status = "warn"
message += " (version may not be fully supported)"
}
results = append(results, checkResult{
catSystem, "Operating System", status, message,
fmt.Sprintf("ID: %s, Version: %s, Arch: %s", osInfo.Distribution.ID, osInfo.VersionID, osInfo.Architecture),
})
}
arch := runtime.GOARCH
archStatus := "ok"
if arch != "amd64" && arch != "arm64" {
archStatus = "error"
}
results = append(results, checkResult{catSystem, "Architecture", archStatus, arch, ""})
waylandDisplay := os.Getenv("WAYLAND_DISPLAY")
xdgSessionType := os.Getenv("XDG_SESSION_TYPE")
switch {
case waylandDisplay != "" || xdgSessionType == "wayland":
results = append(results, checkResult{
catSystem, "Display Server", "ok", "Wayland",
fmt.Sprintf("WAYLAND_DISPLAY=%s", waylandDisplay),
})
case xdgSessionType == "x11":
results = append(results, checkResult{catSystem, "Display Server", "error", "X11 (DMS requires Wayland)", ""})
default:
results = append(results, checkResult{
catSystem, "Display Server", "warn", "Unknown (ensure you're running Wayland)",
fmt.Sprintf("XDG_SESSION_TYPE=%s", xdgSessionType),
})
}
return results
}
func readOSRelease() map[string]string {
result := make(map[string]string)
data, err := os.ReadFile("/etc/os-release")
if err != nil {
return result
}
for line := range strings.SplitSeq(string(data), "\n") {
if parts := strings.SplitN(line, "=", 2); len(parts) == 2 {
result[parts[0]] = strings.Trim(parts[1], "\"")
}
}
return result
}
func checkVersions(qsMissingFeatures bool) []checkResult {
results := []checkResult{
{catVersions, "DMS CLI", "info", formatVersion(Version), ""},
}
qsVersion, qsStatus := getQuickshellVersionInfo(qsMissingFeatures)
results = append(results, checkResult{catVersions, "Quickshell", qsStatus, qsVersion, ""})
dmsVersion, dmsPath := getDMSShellVersion()
if dmsVersion != "" {
results = append(results, checkResult{catVersions, "DMS Shell", "ok", dmsVersion, dmsPath})
} else {
results = append(results, checkResult{catVersions, "DMS Shell", "error", "Not installed or not detected", "Run 'dms setup' to install"})
}
return results
}
func getDMSShellVersion() (version, path string) {
if err := findConfig(nil, nil); err == nil && configPath != "" {
versionFile := filepath.Join(configPath, "VERSION")
if data, err := os.ReadFile(versionFile); err == nil {
return strings.TrimSpace(string(data)), configPath
}
return "installed", configPath
}
if dmsPath, err := config.LocateDMSConfig(); err == nil {
versionFile := filepath.Join(dmsPath, "VERSION")
if data, err := os.ReadFile(versionFile); err == nil {
return strings.TrimSpace(string(data)), dmsPath
}
return "installed", dmsPath
}
return "", ""
}
func getQuickshellVersionInfo(missingFeatures bool) (string, string) {
if !utils.CommandExists("qs") {
return "Not installed", "error"
}
output, err := exec.Command("qs", "--version").Output()
if err != nil {
return "Installed (version check failed)", "warn"
}
fullVersion := strings.TrimSpace(string(output))
if matches := regexp.MustCompile(`quickshell (\d+\.\d+\.\d+)`).FindStringSubmatch(fullVersion); len(matches) >= 2 {
if version.CompareVersions(matches[1], "0.2.0") < 0 {
return fmt.Sprintf("%s (needs >= 0.2.0)", fullVersion), "error"
}
if missingFeatures {
return fullVersion, "warn"
}
return fullVersion, "ok"
}
return fullVersion, "warn"
}
func checkDMSInstallation() []checkResult {
results := []checkResult{}
dmsPath := ""
if err := findConfig(nil, nil); err == nil && configPath != "" {
dmsPath = configPath
} else if path, err := config.LocateDMSConfig(); err == nil {
dmsPath = path
}
if dmsPath == "" {
return []checkResult{{catInstallation, "DMS Configuration", "error", "Not found", "shell.qml not found in any config path"}}
}
results = append(results, checkResult{catInstallation, "DMS Configuration", "ok", "Found", dmsPath})
shellQml := filepath.Join(dmsPath, "shell.qml")
if _, err := os.Stat(shellQml); err != nil {
results = append(results, checkResult{catInstallation, "shell.qml", "error", "Missing", shellQml})
} else {
results = append(results, checkResult{catInstallation, "shell.qml", "ok", "Present", shellQml})
}
if doctorVerbose {
installType := "Unknown"
switch {
case strings.Contains(dmsPath, "/nix/store"):
installType = "Nix store"
case strings.Contains(dmsPath, ".local/share") || strings.Contains(dmsPath, "/usr/share"):
installType = "System package"
case strings.Contains(dmsPath, ".config"):
installType = "User config"
}
results = append(results, checkResult{catInstallation, "Install Type", "info", installType, dmsPath})
}
return results
}
func checkWindowManagers() []checkResult {
compositors := []struct {
name, versionCmd, versionArg, versionRe string
commands []string
}{
{"Hyprland", "hyprctl", "version", `v?(\d+\.\d+\.\d+)`, []string{"hyprland", "Hyprland"}},
{"niri", "niri", "--version", `niri (\d+\.\d+)`, []string{"niri"}},
{"Sway", "sway", "--version", `sway version (\d+\.\d+)`, []string{"sway"}},
{"River", "river", "-version", `river (\d+\.\d+)`, []string{"river"}},
{"Wayfire", "wayfire", "--version", `wayfire (\d+\.\d+)`, []string{"wayfire"}},
}
results := []checkResult{}
foundAny := false
for _, c := range compositors {
if slices.ContainsFunc(c.commands, utils.CommandExists) {
foundAny = true
results = append(results, checkResult{
catCompositor, c.name, "ok",
getVersionFromCommand(c.versionCmd, c.versionArg, c.versionRe), "",
})
}
}
if !foundAny {
results = append(results, checkResult{
catCompositor, "Compositor", "error",
"No supported Wayland compositor found",
"Install Hyprland, niri, Sway, River, or Wayfire",
})
}
if wm := detectRunningWM(); wm != "" {
results = append(results, checkResult{catCompositor, "Active", "info", wm, ""})
}
return results
}
func getVersionFromCommand(cmd, arg, regex string) string {
output, err := exec.Command(cmd, arg).Output()
if err != nil {
return "installed"
}
outStr := string(output)
if matches := regexp.MustCompile(regex).FindStringSubmatch(outStr); len(matches) > 1 {
ver := matches[1]
if strings.Contains(outStr, "git") || strings.Contains(outStr, "dirty") {
return ver + " (git)"
}
return ver
}
return strings.TrimSpace(outStr)
}
func detectRunningWM() string {
switch {
case os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "":
return "Hyprland"
case os.Getenv("NIRI_SOCKET") != "":
return "niri"
case os.Getenv("XDG_CURRENT_DESKTOP") != "":
return os.Getenv("XDG_CURRENT_DESKTOP")
}
return ""
}
func checkQuickshellFeatures() ([]checkResult, bool) {
if !utils.CommandExists("qs") {
return nil, false
}
tmpDir := os.TempDir()
testScript := filepath.Join(tmpDir, "qs-feature-test.qml")
defer os.Remove(testScript)
qmlContent := `
import QtQuick
import Quickshell
ShellRoot {
id: root
property bool polkitAvailable: false
property bool idleMonitorAvailable: false
property bool idleInhibitorAvailable: false
property bool shortcutInhibitorAvailable: false
Timer {
interval: 50
running: true
repeat: false
onTriggered: {
try {
var polkitTest = Qt.createQmlObject(
'import Quickshell.Services.Polkit; import QtQuick; Item {}',
root
)
root.polkitAvailable = true
polkitTest.destroy()
} catch (e) {}
try {
var testItem = Qt.createQmlObject(
'import Quickshell.Wayland; import QtQuick; QtObject { ' +
'readonly property bool hasIdleMonitor: typeof IdleMonitor !== "undefined"; ' +
'readonly property bool hasIdleInhibitor: typeof IdleInhibitor !== "undefined"; ' +
'readonly property bool hasShortcutInhibitor: typeof ShortcutInhibitor !== "undefined" ' +
'}',
root
)
root.idleMonitorAvailable = testItem.hasIdleMonitor
root.idleInhibitorAvailable = testItem.hasIdleInhibitor
root.shortcutInhibitorAvailable = testItem.hasShortcutInhibitor
testItem.destroy()
} catch (e) {}
console.warn(root.polkitAvailable ? "FEATURE:Polkit:OK" : "FEATURE:Polkit:UNAVAILABLE")
console.warn(root.idleMonitorAvailable ? "FEATURE:IdleMonitor:OK" : "FEATURE:IdleMonitor:UNAVAILABLE")
console.warn(root.idleInhibitorAvailable ? "FEATURE:IdleInhibitor:OK" : "FEATURE:IdleInhibitor:UNAVAILABLE")
console.warn(root.shortcutInhibitorAvailable ? "FEATURE:ShortcutInhibitor:OK" : "FEATURE:ShortcutInhibitor:UNAVAILABLE")
Quickshell.execDetached(["kill", "-TERM", String(Quickshell.processId)])
}
}
}
`
if err := os.WriteFile(testScript, []byte(qmlContent), 0644); err != nil {
return nil, false
}
cmd := exec.Command("qs", "-p", testScript)
cmd.Env = append(os.Environ(), "NO_COLOR=1")
output, _ := cmd.CombinedOutput()
outputStr := string(output)
features := []struct{ name, desc string }{
{"Polkit", "Authentication prompts"},
{"IdleMonitor", "Idle detection"},
{"IdleInhibitor", "Prevent idle/sleep"},
{"ShortcutInhibitor", "Allow shortcut management (niri)"},
}
results := []checkResult{}
missingFeatures := false
for _, f := range features {
available := strings.Contains(outputStr, fmt.Sprintf("FEATURE:%s:OK", f.name))
status, message := "ok", "Available"
if !available {
status, message = "info", "Not available"
missingFeatures = true
}
results = append(results, checkResult{catQuickshellFeatures, f.name, status, message, f.desc})
}
return results, missingFeatures
}
func checkOptionalDependencies() []checkResult {
results := []checkResult{}
if utils.IsServiceActive("accounts-daemon", false) {
results = append(results, checkResult{catOptionalFeatures, "accountsservice", "ok", "Running", "User accounts"})
} else {
results = append(results, checkResult{catOptionalFeatures, "accountsservice", "warn", "Not running", "User accounts"})
}
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
terminalFound := ""
for _, term := range terminals {
if utils.CommandExists(term) {
terminalFound = term
break
}
}
if terminalFound != "" {
results = append(results, checkResult{catOptionalFeatures, "Terminal", "ok", terminalFound, ""})
} else {
results = append(results, checkResult{catOptionalFeatures, "Terminal", "warn", "None found", "Install ghostty, kitty, or alacritty"})
}
deps := []struct {
name, cmd, altCmd, desc string
important bool
}{
{"matugen", "matugen", "", "Dynamic theming", true},
{"dgop", "dgop", "", "System monitoring", true},
{"cava", "cava", "", "Audio waveform", false},
{"khal", "khal", "", "Calendar events", false},
{"Network", "nmcli", "iwctl", "Network management", false},
{"danksearch", "dsearch", "", "File search", false},
{"loginctl", "loginctl", "", "Session management", false},
{"fprintd", "fprintd-list", "", "Fingerprint auth", false},
}
for _, d := range deps {
found, foundCmd := utils.CommandExists(d.cmd), d.cmd
if !found && d.altCmd != "" {
if utils.CommandExists(d.altCmd) {
found, foundCmd = true, d.altCmd
}
}
if found {
message := "Installed"
switch foundCmd {
case "nmcli":
message = "NetworkManager"
case "iwctl":
message = "iwd"
}
results = append(results, checkResult{catOptionalFeatures, d.name, "ok", message, d.desc})
} else if d.important {
results = append(results, checkResult{catOptionalFeatures, d.name, "warn", "Missing", d.desc})
} else {
results = append(results, checkResult{catOptionalFeatures, d.name, "info", "Not installed", d.desc})
}
}
return results
}
func checkConfigurationFiles() []checkResult {
configFiles := []struct{ name, path string }{
{"Settings", filepath.Join(utils.XDGConfigHome(), "DankMaterialShell", "settings.json")},
{"Session", filepath.Join(utils.XDGStateHome(), "DankMaterialShell", "session.json")},
{"Colors", filepath.Join(utils.XDGCacheHome(), "DankMaterialShell", "dms-colors.json")},
}
results := []checkResult{}
for _, cf := range configFiles {
if _, err := os.Stat(cf.path); err == nil {
results = append(results, checkResult{catConfigFiles, cf.name, "ok", "Present", cf.path})
} else {
results = append(results, checkResult{catConfigFiles, cf.name, "info", "Not yet created", cf.path})
}
}
return results
}
func checkSystemdServices() []checkResult {
if !utils.CommandExists("systemctl") {
return nil
}
results := []checkResult{}
dmsState := getServiceState("dms", true)
if !dmsState.exists {
results = append(results, checkResult{catServices, "dms.service", "info", "Not installed", "Optional user service"})
} else {
status, message := "ok", dmsState.enabled
if dmsState.active != "" {
message = fmt.Sprintf("%s, %s", dmsState.enabled, dmsState.active)
}
if dmsState.enabled == "disabled" {
status, message = "warn", "Disabled"
}
results = append(results, checkResult{catServices, "dms.service", status, message, ""})
}
greetdState := getServiceState("greetd", false)
if greetdState.exists {
status := "ok"
if greetdState.enabled == "disabled" {
status = "info"
}
results = append(results, checkResult{catServices, "greetd", status, greetdState.enabled, ""})
} else if doctorVerbose {
results = append(results, checkResult{catServices, "greetd", "info", "Not installed", "Optional greeter service"})
}
return results
}
type serviceState struct {
exists bool
enabled string
active string
}
func getServiceState(name string, userService bool) serviceState {
args := []string{"is-enabled", name}
if userService {
args = []string{"--user", "is-enabled", name}
}
output, _ := exec.Command("systemctl", args...).Output()
enabled := strings.TrimSpace(string(output))
if enabled == "" || enabled == "not-found" {
return serviceState{}
}
state := serviceState{exists: true, enabled: enabled}
if userService {
output, _ = exec.Command("systemctl", "--user", "is-active", name).Output()
if active := strings.TrimSpace(string(output)); active != "" && active != "unknown" {
state.active = active
}
}
return state
}
func printResults(results []checkResult) {
theme := tui.TerminalTheme()
styles := tui.NewStyles(theme)
currentCategory := category(-1)
for _, r := range results {
if r.category != currentCategory {
if currentCategory != -1 {
fmt.Println()
}
fmt.Printf(" %s\n", styles.Bold.Render(categoryNames[r.category]))
currentCategory = r.category
}
printResultLine(r, styles)
}
}
func printResultLine(r checkResult, styles tui.Styles) {
icon, style := "○", styles.Subtle
switch r.status {
case "ok":
icon, style = "●", styles.Success
case "warn":
icon, style = "●", styles.Warning
case "error":
icon, style = "●", styles.Error
}
name := r.name
if len(name) > 18 {
name = name[:17] + "…"
}
dots := strings.Repeat("·", 19-len(name))
fmt.Printf(" %s %s %s %s\n", style.Render(icon), name, styles.Subtle.Render(dots), r.message)
if doctorVerbose && r.details != "" {
fmt.Printf(" %s\n", styles.Subtle.Render("└─ "+r.details))
}
}
func printSummary(results []checkResult, qsMissingFeatures bool) {
theme := tui.TerminalTheme()
styles := tui.NewStyles(theme)
errors, warnings, ok := 0, 0, 0
for _, r := range results {
switch r.status {
case "error":
errors++
case "warn":
warnings++
case "ok":
ok++
}
}
fmt.Println()
fmt.Printf(" %s\n", styles.Subtle.Render("──────────────────────────────────────"))
if errors == 0 && warnings == 0 {
fmt.Printf(" %s\n", styles.Success.Render("✓ All checks passed!"))
} else {
parts := []string{}
if errors > 0 {
parts = append(parts, styles.Error.Render(fmt.Sprintf("%d error(s)", errors)))
}
if warnings > 0 {
parts = append(parts, styles.Warning.Render(fmt.Sprintf("%d warning(s)", warnings)))
}
parts = append(parts, styles.Success.Render(fmt.Sprintf("%d ok", ok)))
fmt.Printf(" %s\n", strings.Join(parts, ", "))
if qsMissingFeatures {
fmt.Println()
fmt.Printf(" %s\n", styles.Subtle.Render("→ Consider using quickshell-git for full feature support"))
}
}
fmt.Println()
}

View File

@@ -131,12 +131,6 @@ 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
@@ -183,7 +177,7 @@ func runOpen(target string) {
}
method := "apppicker.open"
if detectedMimeType == "" && len(detectedCategories) == 0 && (strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") || strings.HasPrefix(target, "dms://")) {
if detectedMimeType == "" && len(detectedCategories) == 0 && (strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://")) {
method = "browser.open"
params["url"] = target
}

View File

@@ -18,25 +18,6 @@ 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) {
@@ -199,16 +180,6 @@ 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
@@ -243,28 +214,14 @@ func runShellInteractive(session bool) {
for {
select {
case sig := <-sigChan:
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)
}
// Handle SIGUSR1 restart for non-session managed processes
if sig == syscall.SIGUSR1 && !isSessionManaged {
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)
@@ -278,7 +235,7 @@ func runShellInteractive(session bool) {
cmd.Process.Signal(syscall.SIGTERM)
}
os.Remove(socketPath)
os.Exit(getProcessExitCode(cmd.ProcessState))
os.Exit(1)
}
}
}
@@ -443,16 +400,6 @@ 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)
@@ -493,28 +440,15 @@ func runShellDaemon(session bool) {
for {
select {
case sig := <-sigChan:
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)
}
// Handle SIGUSR1 restart for non-session managed processes
if sig == syscall.SIGUSR1 && !isSessionManaged {
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):
}
// All other signals: clean shutdown
cancel()
cmd.Process.Signal(syscall.SIGTERM)
os.Remove(socketPath)
@@ -526,7 +460,7 @@ func runShellDaemon(session bool) {
cmd.Process.Signal(syscall.SIGTERM)
}
os.Remove(socketPath)
os.Exit(getProcessExitCode(cmd.ProcessState))
os.Exit(1)
}
}
}

View File

@@ -213,11 +213,6 @@ 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)
}
@@ -270,13 +265,7 @@ func (cd *ConfigDeployer) deployGhosttyConfig() ([]DeploymentResult, error) {
colorResult := DeploymentResult{
ConfigType: "Ghostty Colors",
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
Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config-dankcolors"),
}
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, "theme = dankcolors")
assert.Contains(t, GhosttyConfig, "config-file = ./config-dankcolors")
}
func TestGhosttyColorConfigStructure(t *testing.T) {

View File

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

View File

@@ -112,24 +112,3 @@ 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,7 +550,10 @@ func (b *BaseDistribution) WriteEnvironmentConfig(terminal deps.Terminal) error
terminalCmd = "ghostty"
}
content := fmt.Sprintf(`ELECTRON_OZONE_PLATFORM_HINT=auto
content := fmt.Sprintf(`QT_QPA_PLATFORM=wayland
ELECTRON_OZONE_PLATFORM_HINT=auto
QT_QPA_PLATFORMTHEME=gtk3
QT_QPA_PLATFORMTHEME_QT6=gtk3
TERMINAL=%s
`, terminalCmd)
@@ -564,6 +567,12 @@ 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,7 +4,6 @@ import (
"context"
"fmt"
"os/exec"
"runtime"
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
@@ -385,8 +384,6 @@ 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 {
@@ -430,7 +427,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
}
// Add repository
repoLine := fmt.Sprintf("deb [signed-by=%s, arch=%s] %s/ /", keyringPath, runtime.GOARCH, baseURL)
repoLine := fmt.Sprintf("deb [signed-by=%s] %s/ /", keyringPath, baseURL)
progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages,

View File

@@ -15,12 +15,6 @@ 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 {
@@ -440,19 +434,6 @@ 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))
@@ -460,8 +441,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/%s/%s.repo",
repoPath, obsDistroVersion, pkg.RepoURL)
repoURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/openSUSE_Tumbleweed/%s.repo",
repoPath, pkg.RepoURL)
checkCmd := exec.CommandContext(ctx, "zypper", "repos", repoName)
if checkCmd.Run() == nil {

View File

@@ -19,12 +19,11 @@ type DistroInfo struct {
// OSInfo contains complete OS information
type OSInfo struct {
Distribution DistroInfo
Version string
VersionID string
VersionCodename string
PrettyName string
Architecture string
Distribution DistroInfo
Version string
VersionID string
PrettyName string
Architecture string
}
// GetOSInfo detects the current OS and returns information about it
@@ -73,8 +72,6 @@ func GetOSInfo() (*OSInfo, error) {
info.VersionID = value
case "VERSION":
info.Version = value
case "VERSION_CODENAME":
info.VersionCodename = value
case "PRETTY_NAME":
info.PrettyName = value
}
@@ -103,10 +100,6 @@ 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

@@ -258,9 +258,7 @@ output_path = '%s'
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")
}
@@ -276,9 +274,6 @@ output_path = '%s'
if !opts.ShouldSkipTemplate("wezterm") {
appendTerminalConfig(opts, cfgFile, tmpDir, "wezterm", "wezterm.toml")
}
if !opts.ShouldSkipTemplate("nvim") {
appendTerminalConfig(opts, cfgFile, tmpDir, "nvim", "neovim.toml")
}
if !opts.ShouldSkipTemplate("dgop") {
appendConfig(opts, cfgFile, "dgop", "dgop.toml")

View File

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

View File

@@ -319,6 +319,10 @@ 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()
}
@@ -344,6 +348,105 @@ 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")
@@ -392,9 +495,6 @@ 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() {
@@ -1209,7 +1309,13 @@ func (m *Manager) applyConfigChange(newCfg Config) {
}
}
log.Infof("Clipboard config reloaded: disableHistory=%v", newCfg.DisableHistory)
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)
m.updateState()
m.notifySubscribers()

View File

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

View File

@@ -2,8 +2,6 @@ package cups
import (
"errors"
"net"
"net/url"
"strings"
"time"
@@ -158,42 +156,9 @@ 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 {
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
return m.pkHelper.DevicesGet(10, 0, nil, nil)
}
deviceAttrs, err := m.client.GetDevices()
@@ -211,9 +176,6 @@ 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,7 +42,6 @@ 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)
}
profiles := []VPNProfile{}
var 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)
}
active := []VPNActive{}
var active []VPNActive
for _, activeConn := range activeConns {
connType, err := activeConn.GetPropertyType()
if err != nil {

View File

@@ -18,7 +18,6 @@ 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"
)
@@ -38,11 +37,6 @@ 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")
@@ -210,6 +204,9 @@ 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

@@ -1,27 +0,0 @@
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

@@ -1,52 +0,0 @@
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

@@ -1,54 +0,0 @@
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

@@ -1,157 +0,0 @@
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

@@ -1,53 +0,0 @@
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

@@ -1,47 +0,0 @@
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

@@ -1,63 +0,0 @@
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

@@ -1,57 +0,0 @@
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

@@ -241,7 +241,6 @@ 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()
})
@@ -361,23 +360,22 @@ 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,
AdaptiveSyncSupported: head.adaptiveSyncSupported,
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,
ID: head.id,
}
outputs = append(outputs, output)
return true

View File

@@ -17,23 +17,22 @@ 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"`
AdaptiveSyncSupported bool `json:"adaptiveSyncSupported"`
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"`
ID uint32 `json:"id"`
}
type State struct {
@@ -73,26 +72,25 @@ 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
adaptiveSyncSupported bool
finished bool
ready bool
id uint32
handle *wlr_output_management.ZwlrOutputHeadV1
name string
description string
make string
model string
serialNumber string
physicalWidth int32
physicalHeight int32
enabled bool
x int32
y int32
transform int32
scale float64
currentModeID uint32
modeIDs []uint32
adaptiveSync uint32
finished bool
ready bool
}
type modeState struct {
@@ -171,7 +169,7 @@ func stateChanged(old, new *State) bool {
if oldOut.Transform != newOut.Transform || oldOut.Scale != newOut.Scale {
return true
}
if oldOut.AdaptiveSync != newOut.AdaptiveSync || oldOut.AdaptiveSyncSupported != newOut.AdaptiveSyncSupported {
if oldOut.AdaptiveSync != newOut.AdaptiveSync {
return true
}
if (oldOut.CurrentMode == nil) != (newOut.CurrentMode == nil) {

View File

@@ -1,258 +0,0 @@
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

@@ -1,309 +0,0 @@
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

@@ -1,40 +0,0 @@
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,24 +1,8 @@
package utils
import (
"os/exec"
"strings"
)
import "os/exec"
func CommandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
func IsServiceActive(name string, userService bool) bool {
if !CommandExists("systemctl") {
return false
}
args := []string{"is-active", name}
if userService {
args = []string{"--user", "is-active", name}
}
output, _ := exec.Command("systemctl", args...).Output()
return strings.TrimSpace(string(output)) == "active"
}

View File

@@ -6,30 +6,6 @@ import (
"strings"
)
func xdgDir(envVar string, defaultPath ...string) string {
if dir := os.Getenv(envVar); dir != "" {
return dir
}
home, _ := os.UserHomeDir()
return filepath.Join(append([]string{home}, defaultPath...)...)
}
func XDGConfigHome() string {
return xdgDir("XDG_CONFIG_HOME", ".config")
}
func XDGStateHome() string {
return xdgDir("XDG_STATE_HOME", ".local", "state")
}
func XDGCacheHome() string {
return xdgDir("XDG_CACHE_HOME", ".cache")
}
func XDGDataHome() string {
return xdgDir("XDG_DATA_HOME", ".local", "share")
}
func ExpandPath(path string) (string, error) {
expanded := os.ExpandEnv(path)
expanded = filepath.Clean(expanded)

View File

@@ -1,4 +1,4 @@
dms-git (1.0.2+git2528.d336866fdb1) nightly; urgency=medium
dms-git (1.0.2+git2528.d336866f) nightly; urgency=medium
* Git snapshot (commit 2528: d336866f)
@@ -16,6 +16,23 @@ 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.3.tar.gz</param>
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.2.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.3/dms-distropkg-amd64.gz</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/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.3/dms-distropkg-arm64.gz</param>
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-arm64.gz</param>
</service>
</services>

View File

@@ -1,12 +1,6 @@
dms (1.0.3db1) unstable; urgency=medium
dms (1.0.2ppa6) unstable; urgency=medium
* 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
* Rebuild to fix repository metadata issues
-- 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 | quickshell-git,
quickshell-git | quickshell,
accountsservice,
cava,
cliphist,

View File

@@ -1,135 +0,0 @@
# 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 - Stable releases
# Spec for DMS Greeter - Git builds using rpkg macros
%global debug_package %{nil}
%global version VERSION_PLACEHOLDER
%global version {{{ git_repo_version }}}
%global pkg_summary DankMaterialShell greeter for greetd
Name: dms-greeter
Version: %{version}
Release: RELEASE_PLACEHOLDER%{?dist}
Release: 0.git%{?dist}
Summary: %{pkg_summary}
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}}
Source0: dms-qml.tar.gz
BuildRequires: gzip
BuildRequires: wget
BuildRequires: systemd-rpm-macros
BuildRequires: git-core
# For the _tmpfilesdir macro.
BuildRequires: systemd-rpm-macros
Requires: greetd
Requires: (quickshell-git or quickshell)
@@ -24,11 +24,14 @@ Requires(post): /usr/sbin/useradd
Requires(post): /usr/sbin/groupadd
Recommends: policycoreutils-python-utils
Recommends: acl
Recommends: setfacl
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.
@@ -38,26 +41,31 @@ compositor detection and configuration. Features session selection, user
authentication, and dynamic theming.
%prep
%setup -q -c -n dms-qml
%build
{{{ git_repo_setup_macro }}}
%install
# Install greeter files to shared data location
# Install greeter files to shared data location (from quickshell/ subdirectory)
install -dm755 %{buildroot}%{_datadir}/quickshell/dms-greeter
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms-greeter/
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms-greeter/
install -Dm755 %{_builddir}/dms-qml/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
# Install launcher script
install -Dm755 quickshell/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
install -Dm644 %{_builddir}/dms-qml/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
# Install documentation
install -Dm644 quickshell/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
install -Dpm0644 %{_builddir}/dms-qml/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
# Create cache directory for greeter data
install -Dpm0644 quickshell/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
install -Dm644 %{_builddir}/dms-qml/LICENSE %{buildroot}%{_docdir}/dms-greeter/LICENSE
# Install LICENSE file
install -Dm644 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 w/greetd packages
# Note: We do NOT install a PAM config here to avoid conflicting with greetd package
# Instead, we verify/fix it in %post if needed
# Remove build and development files
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/.gitignore
@@ -65,8 +73,9 @@ 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 & preserves any user-added files
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms-greeter" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
@@ -80,7 +89,7 @@ fi
%{_tmpfilesdir}/%{name}.conf
%pre
# Create greeter user/group if they don't exist
# Create greeter user/group if they don't exist (greetd expects this)
getent group greeter >/dev/null || groupadd -r greeter
getent passwd greeter >/dev/null || \
useradd -r -g greeter -d %{_sharedstatedir}/greeter -s /bin/bash \
@@ -118,6 +127,7 @@ 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
@@ -139,6 +149,7 @@ 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
@@ -187,8 +198,9 @@ 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
@@ -255,6 +267,4 @@ if [ "$1" -eq 0 ] && [ -x /usr/sbin/semanage ]; then
fi
%changelog
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
- Stable release VERSION_PLACEHOLDER
- Built from GitHub release
{{{ git_repo_changelog }}}

View File

@@ -1,40 +1,49 @@
# Feodra spec for DMS stable releases
# Spec for DMS - uses rpkg macros for git builds
%global debug_package %{nil}
%global version VERSION_PLACEHOLDER
%global version {{{ git_repo_version }}}
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
Name: dms
Epoch: 2
Version: %{version}
Release: RELEASE_PLACEHOLDER%{?dist}
Release: 1%{?dist}
Summary: %{pkg_summary}
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}}
Source0: dms-qml.tar.gz
BuildRequires: git-core
BuildRequires: gzip
BuildRequires: golang >= 1.24
BuildRequires: make
BuildRequires: wget
BuildRequires: systemd-rpm-macros
Requires: (quickshell or quickshell-git)
# Core requirements
Requires: (quickshell-git or quickshell)
Requires: accountsservice
Requires: dms-cli = %{version}-%{release}
Requires: dms-cli = %{epoch}:%{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 and hyprland compositors. Features notifications,
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,
@@ -51,14 +60,24 @@ Command-line interface for DankMaterialShell configuration and management.
Provides native DBus bindings, NetworkManager integration, and system utilities.
%prep
%setup -q -c -n dms-qml
{{{ 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)
ARCH_SUFFIX="amd64"
DMS_BINARY="dms-linux-amd64"
;;
aarch64)
ARCH_SUFFIX="arm64"
DMS_BINARY="dms-linux-arm64"
;;
*)
echo "Unsupported architecture: %{_arch}"
@@ -66,35 +85,27 @@ case "%{_arch}" in
;;
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
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
%{_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 || :
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 -Dm644 %{_builddir}/dms-qml/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
# Install systemd user service
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
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 -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 %{_builddir}/dms-qml/* %{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
@@ -108,7 +119,8 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%doc CONTRIBUTING.md
%doc quickshell/README.md
%{_datadir}/quickshell/dms/
%{_userunitdir}/dms.service
%{_datadir}/applications/dms-open.desktop
@@ -121,6 +133,4 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
%{_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
{{{ git_repo_changelog }}}

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
}:
let
inherit (lib) types;
cfg = config.programs.dank-material-shell.greeter;
cfg = config.programs.dankMaterialShell.greeter;
inherit (config.services.greetd.settings.default_session) user;
@@ -44,20 +44,19 @@ in
{
imports =
let
msg = "The option 'programs.dank-material-shell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dank-material-shell.greeter.compositor.customConfig' instead.";
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
in
[
(lib.mkRemovedOptionModule [
"programs"
"dank-material-shell"
"dankMaterialShell"
"greeter"
"compositor"
"extraConfig"
] msg)
./dms-rename.nix
];
options.programs.dank-material-shell.greeter = {
options.programs.dankMaterialShell.greeter = {
enable = lib.mkEnableOption "DankMaterialShell greeter";
compositor.name = lib.mkOption {
type = types.enum [
@@ -178,7 +177,7 @@ in
mv dms-colors.json colors.json || :
chown ${user}: * || :
'';
programs.dank-material-shell.greeter.configFiles = lib.mkIf (cfg.configHome != null) [
programs.dankMaterialShell.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.dank-material-shell;
cfg = config.programs.dankMaterialShell;
jsonFormat = pkgs.formats.json { };
common = import ./common.nix {
inherit
@@ -22,16 +22,16 @@ in
(import ./options.nix args)
(lib.mkRemovedOptionModule [
"programs"
"dank-material-shell"
"dankMaterialShell"
"enableNightMode"
] "Night mode is now always available.")
(lib.mkRenamedOptionModule
[ "programs" "dank-material-shell" "enableSystemd" ]
[ "programs" "dank-material-shell" "systemd" "enable" ]
[ "programs" "dankMaterialShell" "enableSystemd" ]
[ "programs" "dankMaterialShell" "systemd" "enable" ]
)
];
options.programs.dank-material-shell = with lib.types; {
options.programs.dankMaterialShell = with lib.types; {
default = {
settings = lib.mkOption {
type = jsonFormat.type;

View File

@@ -4,14 +4,10 @@
...
}:
let
cfg = config.programs.dank-material-shell;
cfg = config.programs.dankMaterialShell;
in
{
imports = [
./dms-rename.nix
];
options.programs.dank-material-shell = {
options.programs.dankMaterialShell = {
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.dank-material-shell;
cfg = config.programs.dankMaterialShell;
common = import ./common.nix {
inherit
config

View File

@@ -1,14 +1,13 @@
{
lib,
dmsPkgs,
pkgs,
...
}:
let
inherit (lib) types;
path = [
"programs"
"dank-material-shell"
"dankMaterialShell"
];
builtInRemovedMsg = "This is now built-in in DMS and doesn't need additional dependencies.";
@@ -21,58 +20,46 @@ in
(lib.mkRemovedOptionModule (
path ++ [ "enableSystemSound" ]
) "qtmultimedia is now included on dms-shell package.")
./dms-rename.nix
];
options.programs.dank-material-shell = {
options.programs.dankMaterialShell = {
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 dank-material-shell changes";
description = "Auto-restart dms.service when dankMaterialShell 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, due to unreleased features used by DMS).";
extraDescription = "The quickshell package to use (defaults to be built from source, in the commit 26531f due to unreleased features used by DMS).";
};
};

View File

@@ -3,8 +3,8 @@
%global debug_package %{nil}
Name: dms
Version: 1.0.3
Release: 1%{?dist}
Version: 1.0.2
Release: 7%{?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 or quickshell-git)
Requires: (quickshell-git or quickshell)
Requires: accountsservice
Requires: dgop
@@ -105,9 +105,6 @@ 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,121 +2,226 @@
set -euo pipefail
# Build SRPM locally with correct tarball and upload to Copr
# 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}"
# Usage: ./create-upload-copr.sh VERSION [RELEASE]
# Example: ./create-upload-copr.sh 1.0.0 4
VERSION="${1:-1.0.0}"
RELEASE="${2:-1}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 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..."
echo "Building DMS v${VERSION}-${RELEASE} SRPM for Copr..."
# Setup build directories
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
cd ~/rpmbuild/SOURCES
# 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
# Create the corrected QML tarball locally
echo "Creating QML tarball with assets..."
TEMP_DIR=$(mktemp -d)
cd "$REPO_ROOT"
# 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
# Copy quickshell contents to temp
cp -r quickshell/* "$TEMP_DIR/"
cp "$SPEC_FILE" ~/rpmbuild/SPECS/"${PACKAGE}".spec
# Copy root LICENSE and CONTRIBUTING.md
cp LICENSE CONTRIBUTING.md "$TEMP_DIR/"
# Replace placeholders in spec file
# 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')"
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
echo "✅ Spec file prepared for ${PACKAGE} v${VERSION}-${RELEASE}"
cat >~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
# Spec for DMS stable releases - Built locally
%global debug_package %{nil}
%global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
Name: dms
Version: %{version}
Release: RELEASE_PLACEHOLDER%{?dist}
Summary: %{pkg_summary}
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
Source0: dms-qml.tar.gz
BuildRequires: gzip
BuildRequires: wget
BuildRequires: systemd-rpm-macros
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli = %{version}-%{release}
Requires: dgop
Recommends: cava
Recommends: cliphist
Recommends: danksearch
Recommends: matugen
Recommends: wl-clipboard
Recommends: NetworkManager
Recommends: qt6-qtmultimedia
Suggests: qt6ct
%description
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
and optimized for the niri and hyprland compositors. Features notifications,
app launcher, wallpaper customization, and fully customizable with plugins.
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
process monitoring, notification center, clipboard history, dock, control center,
lock screen, and comprehensive plugin system.
%package -n dms-cli
Summary: DankMaterialShell CLI tool
License: MIT
URL: https://github.com/AvengeMedia/DankMaterialShell
%description -n dms-cli
Command-line interface for DankMaterialShell configuration and management.
Provides native DBus bindings, NetworkManager integration, and system utilities.
%prep
%setup -q -c -n dms-qml
# Download architecture-specific binaries during build
case "%{_arch}" in
x86_64)
ARCH_SUFFIX="amd64"
;;
aarch64)
ARCH_SUFFIX="arm64"
;;
*)
echo "Unsupported architecture: %{_arch}"
exit 1
;;
esac
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v%{version}/dms-distropkg-${ARCH_SUFFIX}.gz" || {
echo "Failed to download dms-cli for architecture %{_arch}"
exit 1
}
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
chmod +x %{_builddir}/dms-cli
%build
%install
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
install -d %{buildroot}%{_datadir}/bash-completion/completions
install -d %{buildroot}%{_datadir}/zsh/site-functions
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
%{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
%{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
%{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
%posttrans
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
# 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 locally with corrected tarball
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
# Build SRPM
echo "🔨 Building SRPM..."
echo "Building SRPM..."
cd ~/rpmbuild/SPECS
rpmbuild -bs "${PACKAGE}".spec
rpmbuild -bs dms.spec
SRPM=$(ls ~/rpmbuild/SRPMS/"${PACKAGE}"-"${VERSION}"-*.src.rpm | tail -n 1)
SRPM=$(ls ~/rpmbuild/SRPMS/dms-"${VERSION}"-*.src.rpm | tail -n 1)
if [ ! -f "$SRPM" ]; then
echo "Error: SRPM not found!"
echo "Expected pattern: ${PACKAGE}-${VERSION}-*.src.rpm"
ls -la ~/rpmbuild/SRPMS/ || true
echo "Error: SRPM not found!"
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 $COPR_PROJECT $SRPM"
echo "Upload manually with: copr-cli build avengemedia/dms $SRPM"
exit 0
fi
# Upload to Copr
echo ""
echo "🚀 Uploading to Copr..."
if copr-cli build "$COPR_PROJECT" "$SRPM" --nowait; then
echo "Uploading to Copr..."
if copr-cli build avengemedia/dms "$SRPM" --nowait; then
echo ""
echo "Build submitted successfully!"
echo "📊 Check status at:"
echo " https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/builds/"
echo ""
echo "📦 SRPM location: $SRPM"
echo "Build submitted successfully! Check status at:"
echo "https://copr.fedorainfracloud.org/coprs/avengemedia/dms/builds/"
else
echo ""
echo "Copr upload failed. You can manually upload the SRPM:"
echo " copr-cli build $COPR_PROJECT $SRPM"
echo "Copr upload failed. You can manually upload the SRPM:"
echo " copr-cli build avengemedia/dms $SRPM"
echo ""
echo "Or upload via web interface:"
echo " https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/builds/"
echo " https://copr.fedorainfracloud.org/coprs/avengemedia/dms/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 db2 suffix
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with db2 suffix (flag syntax)
# ./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)
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})(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 "")
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 "")
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,8 +279,7 @@ 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
BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/db[0-9]*$//')
CHANGELOG_VERSION="${BASE_VERSION}db${REBUILD_RELEASE}"
CHANGELOG_VERSION="${CHANGELOG_VERSION}ppa${REBUILD_RELEASE}"
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
fi
@@ -308,16 +307,12 @@ 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 "==> 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:"
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:"
NEXT_NUM=$((REBUILD_RELEASE + 1))
echo " REBUILD_RELEASE=$NEXT_NUM"
echo ""
echo "✓ Exiting gracefully (no changes needed)"
exit 0
echo " ./distro/scripts/obs-upload.sh $PACKAGE $NEXT_NUM"
exit 1
fi
fi
fi
@@ -516,7 +511,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
@@ -539,17 +534,9 @@ 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 "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"
echo "Error: Failed to download source from $SOURCE_URL"
echo "Tried both wget and curl. Please check the URL and network connectivity."
exit 1
fi
fi
@@ -566,7 +553,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
@@ -725,10 +712,6 @@ 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)
@@ -740,10 +723,6 @@ 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
@@ -817,29 +796,23 @@ EOF
fi
fi
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)"
cd "$WORK_DIR"
# 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_CURRENT=""
KEEP_PATTERN=""
if [[ -n "$CHANGELOG_VERSION" ]]; then
KEEP_CURRENT="${PACKAGE}_${CHANGELOG_VERSION}.tar.gz"
echo " Keeping only current version: ${KEEP_CURRENT}"
BASE_KEEP_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
KEEP_PATTERN="${PACKAGE}_${BASE_KEEP_VERSION}"
echo " Keeping tarballs matching: ${KEEP_PATTERN}*"
fi
for old_file in $(echo "$OBS_FILES" | grep -oP '(?<=name=")[^"]*\.(tar\.gz|tar\.xz|tar\.bz2)(?=")' || true); do
if [[ "$old_file" == "$KEEP_CURRENT" ]]; then
echo " - Keeping: $old_file"
if [[ -n "$KEEP_PATTERN" ]] && [[ "$old_file" == ${KEEP_PATTERN}* ]]; then
echo " - Keeping current version: $old_file"
continue
fi
@@ -862,11 +835,14 @@ else
echo " ⚠️ Could not fetch file list from server, skipping cleanup"
fi
# Update working copy to latest revision (without expanding service files to avoid revision conflicts)
# Fallback update with --server-side-source-service-files flag only syncs metadata (spec, dsc, _service)
echo "==> Updating working copy"
if ! osc up 2>/dev/null; then
echo "Error: Failed to update working copy"
exit 1
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
fi
# Ensure we're in WORK_DIR and it exists
@@ -906,15 +882,6 @@ 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"
@@ -941,7 +908,7 @@ if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
else
echo "==> Committing to OBS"
set +e
osc commit --skip-local-service-run -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
osc commit -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,8 +191,19 @@ fi
cd "$WORK_PACKAGE_DIR"
get_latest_tag() {
local repo="$1"
# 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//'
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
}
IS_GIT_PACKAGE=false
@@ -323,17 +334,6 @@ 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,10 +341,6 @@ 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

@@ -0,0 +1,5 @@
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

@@ -0,0 +1,24 @@
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

@@ -0,0 +1,24 @@
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

@@ -0,0 +1,33 @@
#!/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

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

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,9 @@
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

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,38 @@
#!/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

@@ -0,0 +1 @@
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: any
Architecture: amd64
Depends: ${misc:Depends},
quickshell-git | quickshell,
accountsservice,

View File

@@ -32,17 +32,15 @@ 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 ARCH=$(DEB_HOST_ARCH)"; \
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=$(DEB_HOST_ARCH) VERSION="$$VERSION" COMMIT="$$COMMIT"; \
echo "Building with VERSION=$$VERSION COMMIT=$$COMMIT"; \
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=amd64 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=$(DEB_HOST_ARCH); \
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=amd64; \
fi
cp dms-git-repo/core/bin/dms-linux-$(DEB_HOST_ARCH) dms
cp dms-git-repo/core/bin/dms-linux-amd64 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: any
Architecture: all
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: any
Architecture: amd64
Depends: ${misc:Depends},
quickshell | quickshell-git,
quickshell-git | quickshell,
accountsservice,
cava,
cliphist,

View File

@@ -19,14 +19,11 @@ 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
$(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-distropkg-amd64.gz || (echo "ERROR: dms-distropkg-amd64.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-$(DEB_HOST_ARCH).gz > dms
gunzip -c dms-distropkg-amd64.gz > dms
chmod +x dms
# Extract source tarball for QML files

View File

@@ -1,46 +0,0 @@
{
"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

@@ -1,46 +0,0 @@
{
"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

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

View File

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

View File

@@ -16,9 +16,6 @@ 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,9 +8,6 @@ Singleton {
id: modalManager
signal closeAllModalsExcept(var excludedModal)
signal modalChanged
property var currentModalsByScreen: ({})
function openModal(modal) {
if (!modal.allowStacking) {
@@ -20,17 +17,5 @@ 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
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
@@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store
Singleton {
id: root
readonly property int settingsConfigVersion: 4
readonly property int settingsConfigVersion: 3
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
@@ -63,9 +63,7 @@ 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: ""
@@ -74,8 +72,6 @@ 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
@@ -109,12 +105,9 @@ 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
@@ -172,13 +165,11 @@ 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
@@ -264,7 +255,6 @@ 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
@@ -289,11 +279,9 @@ Singleton {
property bool matugenTemplateFirefox: true
property bool matugenTemplatePywalfox: 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
@@ -314,7 +302,6 @@ 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
@@ -373,279 +360,50 @@ 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,
"showOnWindowsOpen": false,
"openOnOverview": false,
"visible": true,
"popupGapsAuto": true,
"popupGapsManual": 4,
"maximizeDetection": true,
"scrollEnabled": true,
"scrollXBehavior": "column",
"scrollYBehavior": "workspace"
id: "default",
name: "Main Bar",
enabled: true,
position: 0,
screenPreferences: ["all"],
showOnLastDisplay: true,
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
centerWidgets: ["music", "clock", "weather"],
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
spacing: 4,
innerPadding: 4,
bottomGap: 0,
transparency: 1.0,
widgetTransparency: 1.0,
squareCorners: false,
noBackground: false,
gothCornersEnabled: false,
gothCornerRadiusOverride: false,
gothCornerRadiusValue: 12,
borderEnabled: false,
borderColor: "surfaceText",
borderOpacity: 1.0,
borderThickness: 1,
widgetOutlineEnabled: false,
widgetOutlineColor: "primary",
widgetOutlineOpacity: 1.0,
widgetOutlineThickness: 1,
fontScale: 1.0,
autoHide: false,
autoHideDelay: 250,
openOnOverview: false,
visible: true,
popupGapsAuto: true,
popupGapsManual: 4,
maximizeDetection: true,
scrollEnabled: true,
scrollXBehavior: "column",
scrollYBehavior: "workspace"
}
]
property bool desktopClockEnabled: false
property string desktopClockStyle: "analog"
property real desktopClockTransparency: 0.8
property string desktopClockColorMode: "primary"
property color desktopClockCustomColor: "#ffffff"
property bool desktopClockShowDate: true
property bool desktopClockShowAnalogNumbers: false
property bool desktopClockShowAnalogSeconds: true
property real desktopClockX: -1
property real desktopClockY: -1
property real desktopClockWidth: 280
property real desktopClockHeight: 180
property var desktopClockDisplayPreferences: ["all"]
property bool systemMonitorEnabled: false
property bool systemMonitorShowHeader: true
property real systemMonitorTransparency: 0.8
property string systemMonitorColorMode: "primary"
property color systemMonitorCustomColor: "#ffffff"
property bool systemMonitorShowCpu: true
property bool systemMonitorShowCpuGraph: true
property bool systemMonitorShowCpuTemp: true
property bool systemMonitorShowGpuTemp: false
property string systemMonitorGpuPciId: ""
property bool systemMonitorShowMemory: true
property bool systemMonitorShowMemoryGraph: true
property bool systemMonitorShowNetwork: true
property bool systemMonitorShowNetworkGraph: true
property bool systemMonitorShowDisk: true
property bool systemMonitorShowTopProcesses: false
property int systemMonitorTopProcessCount: 3
property string systemMonitorTopProcessSortBy: "cpu"
property string systemMonitorLayoutMode: "auto"
property int systemMonitorGraphInterval: 60
property real systemMonitorX: -1
property real systemMonitorY: -1
property real systemMonitorWidth: 320
property real systemMonitorHeight: 480
property var systemMonitorDisplayPreferences: ["all"]
property var systemMonitorVariants: []
property var desktopWidgetPositions: ({})
property var desktopWidgetGridSettings: ({})
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
@@ -663,12 +421,10 @@ 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);
}
});
@@ -702,25 +458,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"
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
fi
fi
else
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]);
}
@@ -733,36 +489,36 @@ Singleton {
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) {
@@ -787,6 +543,7 @@ Singleton {
Store.parse(root, obj);
applyStoredTheme();
applyStoredIconTheme();
Processes.detectIcons();
Processes.detectQtTools();
} catch (e) {
console.warn("SettingsData: Failed to load settings:", e.message);
@@ -833,42 +590,7 @@ Singleton {
}
function detectAvailableIconThemes() {
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;
});
Processes.detectIcons();
}
function getEffectiveTimeFormat() {
@@ -932,14 +654,15 @@ Singleton {
return barHeight + spacing + bottomGap - gothOffset + Theme.popupDistance;
}
function getPopupTriggerPosition(pos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) {
const relativeX = pos.x;
const relativeY = pos.y;
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;
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 rawBottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0);
const bottomGap = Math.max(0, rawBottomGap);
const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0);
const useAutoGaps = (barConfig && barConfig.popupGapsAuto !== undefined) ? barConfig.popupGapsAuto : (defaultBar?.popupGapsAuto ?? true);
const manualGapValue = (barConfig && barConfig.popupGapsManual !== undefined) ? barConfig.popupGapsManual : (defaultBar?.popupGapsManual ?? 4);
@@ -1000,7 +723,7 @@ Singleton {
let leftBar = 0;
let rightBar = 0;
for (var i = 0; i < enabledBars.length; i++) {
for (let i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barConfig.id)
continue;
@@ -1070,7 +793,7 @@ Singleton {
if (barConfig) {
const enabledBars = getEnabledBarConfigs();
for (var i = 0; i < enabledBars.length; i++) {
for (let i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barConfig.id)
continue;
@@ -1190,7 +913,7 @@ Singleton {
updateBarConfigs();
if (positionChanged) {
NotificationService.dismissAllPopups();
NotificationService.clearAllPopups();
}
}
@@ -1202,7 +925,7 @@ Singleton {
const conflicts = [];
const enabledBars = getEnabledBarConfigs();
for (var i = 0; i < enabledBars.length; i++) {
for (let i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i];
if (other.id === barId)
continue;
@@ -1215,9 +938,9 @@ Singleton {
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;
}
@@ -1225,9 +948,9 @@ Singleton {
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"
});
}
}
@@ -1249,7 +972,7 @@ Singleton {
function getScreensSortedByPosition() {
const screens = [];
for (var i = 0; i < Quickshell.screens.length; i++) {
for (let i = 0; i < Quickshell.screens.length; i++) {
screens.push(Quickshell.screens[i]);
}
screens.sort((a, b) => {
@@ -1266,7 +989,7 @@ Singleton {
const sorted = getScreensSortedByPosition();
let modelCount = 0;
let screenIndex = -1;
for (var i = 0; i < sorted.length; i++) {
for (let i = 0; i < sorted.length; i++) {
if (sorted[i].model === screen.model) {
if (sorted[i].name === screen.name) {
screenIndex = modelCount;
@@ -1335,7 +1058,7 @@ Singleton {
}
function sendTestNotifications() {
NotificationService.dismissAllPopups();
NotificationService.clearAllPopups();
sendTestNotification(0);
testNotifTimer1.start();
testNotifTimer2.start();
@@ -1464,7 +1187,7 @@ Singleton {
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
"spacing": spacing
spacing: spacing
});
}
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
@@ -1493,7 +1216,7 @@ Singleton {
return;
}
updateBarConfig(defaultBar.id, {
"position": position
position: position
});
}
@@ -1501,7 +1224,7 @@ Singleton {
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
"leftWidgets": order
leftWidgets: order
});
updateListModel(leftWidgetsModel, order);
}
@@ -1511,7 +1234,7 @@ Singleton {
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
"centerWidgets": order
centerWidgets: order
});
updateListModel(centerWidgetsModel, order);
}
@@ -1521,7 +1244,7 @@ Singleton {
const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) {
updateBarConfig(defaultBar.id, {
"rightWidgets": order
rightWidgets: order
});
updateListModel(rightWidgetsModel, order);
}
@@ -1534,9 +1257,9 @@ Singleton {
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);
@@ -1580,46 +1303,11 @@ Singleton {
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
});
}
}
@@ -1657,87 +1345,6 @@ Singleton {
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
}
@@ -1789,8 +1396,6 @@ Singleton {
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,430 +1,545 @@
// 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",
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"
}
},
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",
},
},
};
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",
],
},
};
GENERIC: {
name: "Generic",
variants: ["blue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral", "monochrome"]
},
CATPPUCCIN: {
name: "Catppuccin",
variants: Object.keys(CatppuccinVariants)
}
}
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);
return Object.keys(StockThemes.DARK).includes(themeName)
}
function isCatppuccinVariant(themeName) {
return Object.keys(CatppuccinVariants).includes(themeName)
}
function getAvailableThemes(isLight = false) {
return isLight ? StockThemes.LIGHT : StockThemes.DARK;
return isLight ? StockThemes.LIGHT : StockThemes.DARK
}
function getThemeByName(themeName, isLight = false) {
const themes = getAvailableThemes(isLight);
return themes[themeName] || themes.blue;
if (isCatppuccinVariant(themeName)) {
return getCatppuccinTheme(themeName, isLight)
}
const themes = getAvailableThemes(isLight)
return themes[themeName] || themes.blue
}
function getAllThemeNames() {
return Object.keys(StockThemes.DARK);
return Object.keys(StockThemes.DARK)
}
function getCatppuccinVariantNames() {
return Object.keys(CatppuccinVariants)
}
function getThemeCategories() {
return ThemeCategories;
return ThemeCategories
}

View File

@@ -92,9 +92,6 @@ 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]);
@@ -260,7 +257,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 || surfaceContainerHigh
property color surfaceContainerHighest: currentThemeData.surfaceContainerHighest
property color onSurface: surfaceText
property color onSurfaceVariant: surfaceVariantText
@@ -477,28 +474,24 @@ Singleton {
if (themeName === dynamic) {
currentTheme = dynamic;
if (currentThemeCategory !== "registry")
currentThemeCategory = dynamic;
currentThemeCategory = dynamic;
} else if (themeName === custom) {
currentTheme = custom;
if (currentThemeCategory !== "registry")
currentThemeCategory = custom;
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 (currentThemeCategory !== "registry") {
if (StockThemes.isCatppuccinVariant(themeName)) {
currentThemeCategory = "catppuccin";
} else {
currentThemeCategory = "generic";
}
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode) {
SettingsData.set("currentThemeCategory", currentThemeCategory);
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
SettingsData.set("currentThemeName", currentTheme);
}
if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme();
@@ -560,103 +553,62 @@ 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) {
baseColors = themeData[colorMode] || themeData.dark || themeData.light || {};
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light;
customThemeData = selectedTheme;
} else {
baseColors = themeData;
customThemeData = 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
@@ -687,9 +639,10 @@ Singleton {
}
}
property color widgetBaseHoverColor: {
const blended = blend(widgetBaseBackgroundColor, primary, 0.1);
return withAlpha(blended, Math.max(0.3, blended.a));
property var widgetBaseHoverColor: {
const baseColor = widgetBaseBackgroundColor;
const factor = 1.2;
return isLightMode ? Qt.darker(baseColor, factor) : Qt.lighter(baseColor, factor);
}
property color widgetIconColor: {
@@ -876,7 +829,7 @@ Singleton {
if (typeof SettingsData !== "undefined") {
const skipTemplates = [];
if (!SettingsData.runDmsMatugenTemplates) {
skipTemplates.push("gtk", "neovim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
skipTemplates.push("gtk", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "vesktop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
} else {
if (!SettingsData.matugenTemplateGtk)
skipTemplates.push("gtk");
@@ -892,16 +845,12 @@ Singleton {
skipTemplates.push("pywalfox");
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)
@@ -950,48 +899,8 @@ Singleton {
let darkTheme, lightTheme;
if (currentTheme === "custom") {
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;
}
darkTheme = customThemeData;
lightTheme = customThemeData;
} else {
darkTheme = StockThemes.getThemeByName(currentTheme, false);
lightTheme = StockThemes.getThemeByName(currentTheme, true);
@@ -1009,7 +918,6 @@ Singleton {
function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
const colors = {};
const isLight = SessionData !== "undefined" && SessionData.isLightMode;
function addColor(matugenKey, darkVal, lightVal) {
if (!darkVal && !lightVal)
@@ -1022,7 +930,7 @@ Singleton {
"color": String(lightVal || darkVal)
},
"default": {
"color": String((isLight && lightVal) ? lightVal : darkVal)
"color": String(darkVal || lightVal)
}
};
}

View File

@@ -21,12 +21,9 @@ Singleton {
showNetworkIcon: true,
showBluetoothIcon: true,
showAudioIcon: true,
showAudioPercent: true,
showVpnIcon: true,
showBrightnessIcon: false,
showBrightnessPercent: false,
showMicIcon: false,
showMicPercent: true,
showBatteryIcon: false,
showPrinterIcon: false
};
@@ -68,18 +65,12 @@ 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,4 +1,5 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
@@ -10,20 +11,61 @@ 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;
defaultSettingsCheckProcess.running = true
}
property var systemDefaultDetectionProcess: Process {
command: ["sh", "-c", "gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed \"s/'//g\" || echo ''"]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
if (exitCode === 0 && stdout && stdout.length > 0) {
settingsRoot.systemDefaultIconTheme = stdout.trim();
} else {
settingsRoot.systemDefaultIconTheme = "";
}
iconThemeDetectionProcess.running = true;
}
}
property var iconThemeDetectionProcess: Process {
command: ["sh", "-c", "find /usr/share/icons ~/.local/share/icons ~/.icons -maxdepth 1 -type d 2>/dev/null | sed 's|.*/||' | grep -v '^icons$' | sort -u"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (!settingsRoot) return
var detectedThemes = ["System Default"]
if (text && text.trim()) {
var themes = text.trim().split('\n')
for (var i = 0; i < themes.length; i++) {
var theme = themes[i].trim()
if (theme && theme !== "" && theme !== "default" && theme !== "hicolor" && theme !== "locolor") {
detectedThemes.push(theme)
}
}
}
settingsRoot.availableIconThemes = detectedThemes
}
}
}
property var qtToolsDetectionProcess: Process {
@@ -32,8 +74,7 @@ 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++) {
@@ -54,9 +95,8 @@ 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) {
@@ -73,9 +113,8 @@ 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);
}
}
@@ -84,9 +123,8 @@ 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,9 +7,7 @@ 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" },
@@ -20,8 +18,6 @@ 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 },
@@ -55,12 +51,9 @@ 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 },
@@ -87,13 +80,11 @@ 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 },
@@ -163,7 +154,6 @@ var SPEC = {
batterySuspendTimeout: { def: 0 },
batterySuspendBehavior: { def: 0 },
batteryProfileName: { def: "" },
batteryChargeLimit: { def: 100 },
lockBeforeSuspend: { def: false },
loginctlLockIntegration: { def: true },
fadeToLockEnabled: { def: false },
@@ -188,12 +178,10 @@ var SPEC = {
matugenTemplateFirefox: { def: true },
matugenTemplatePywalfox: { 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 },
@@ -213,7 +201,6 @@ 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 },
@@ -271,8 +258,6 @@ var SPEC = {
displayNameMode: { def: "system" },
screenPreferences: { def: {} },
showOnLastDisplay: { def: {} },
niriOutputSettings: { def: {} },
hyprlandOutputSettings: { def: {} },
barConfigs: { def: [{
id: "default",
@@ -305,7 +290,6 @@ var SPEC = {
fontScale: 1.0,
autoHide: false,
autoHideDelay: 250,
showOnWindowsOpen: false,
openOnOverview: false,
visible: true,
popupGapsAuto: true,
@@ -314,52 +298,7 @@ var SPEC = {
scrollEnabled: true,
scrollXBehavior: "column",
scrollYBehavior: "workspace"
}], 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: [] }
}], onChange: "updateBarConfigs" }
};
function getValidKeys() {

View File

@@ -119,101 +119,6 @@ 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,8 +58,6 @@ Item {
WallpaperBackground {}
DesktopWidgetLayer {}
Lock {
id: lock
}
@@ -510,22 +508,6 @@ 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,58 +893,4 @@ 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"
}
}

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