mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-25 05:52:50 -05:00
Compare commits
146 Commits
displaycon
...
45ba64ab02
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45ba64ab02 | ||
|
|
9501d66af6 | ||
|
|
2127fc339a | ||
|
|
7962fee0bd | ||
|
|
d5c7b5c0cc | ||
|
|
5f77d69dd8 | ||
|
|
60034be06a | ||
|
|
518a5d38aa | ||
|
|
2eeaf8ff62 | ||
|
|
cffee0fae6 | ||
|
|
f08e2ef5b8 | ||
|
|
2b0070c31a | ||
|
|
ae82716afa | ||
|
|
c281bf3b53 | ||
|
|
45b8b2a895 | ||
|
|
7b9ba840fb | ||
|
|
84fb567ff5 | ||
|
|
bbd57e0673 | ||
|
|
b1632a0a03 | ||
|
|
7aff1182c8 | ||
|
|
fbe362cd20 | ||
|
|
e7f94c94cc | ||
|
|
7523190b16 | ||
|
|
da37e16b6e | ||
|
|
5c420ab50b | ||
|
|
4493b7c231 | ||
|
|
40a96c6eaf | ||
|
|
23a93082c6 | ||
|
|
f7650b5e1f | ||
|
|
3ebdd5631c | ||
|
|
6c4caf121a | ||
|
|
89788e9ca7 | ||
|
|
0787c63fed | ||
|
|
9fc0d5efff | ||
|
|
6611dfbe05 | ||
|
|
8a71ead51d | ||
|
|
d9d6ab5776 | ||
|
|
d6fe7bea27 | ||
|
|
1194f3ffb8 | ||
|
|
5ac81e6dd6 | ||
|
|
987856a1de | ||
|
|
ef52ce0990 | ||
|
|
06b14a5869 | ||
|
|
fd839059c0 | ||
|
|
ec6db7962a | ||
|
|
adf92cbc46 | ||
|
|
6b6f51cd1f | ||
|
|
df6c60213f | ||
|
|
6303304a10 | ||
|
|
8e76789119 | ||
|
|
10e81cfdd3 | ||
|
|
03fd3a4f16 | ||
|
|
8fdc748ed2 | ||
|
|
6c56d23b93 | ||
|
|
45d34dcb5b | ||
|
|
d7ac0d50fa | ||
|
|
1d4d145187 | ||
|
|
a5b9ff98c0 | ||
|
|
6feaecd92e | ||
|
|
b066a25308 | ||
|
|
777a552b57 | ||
|
|
7dbe608c28 | ||
|
|
61630e447b | ||
|
|
91385e7c83 | ||
|
|
04648fcca7 | ||
|
|
080fc7e44e | ||
|
|
0b60da3d6d | ||
|
|
a4492b90e7 | ||
|
|
c9331b7338 | ||
|
|
4982ea53dd | ||
|
|
c703cb6504 | ||
|
|
a7494971fd | ||
|
|
c548255bfc | ||
|
|
9656c7afd7 | ||
|
|
414b8c8272 | ||
|
|
b4f83d09d4 | ||
|
|
67ee74ac20 | ||
|
|
93539d2b6b | ||
|
|
524d967745 | ||
|
|
0effbebbb6 | ||
|
|
dca07a70f8 | ||
|
|
02936c97fd | ||
|
|
8f7e732827 | ||
|
|
5ffe563b7d | ||
|
|
6ef08c3d54 | ||
|
|
908b4b58cd | ||
|
|
f2611e0de0 | ||
|
|
ea75a9d351 | ||
|
|
3a744d7d68 | ||
|
|
195d312ae2 | ||
|
|
76006a7377 | ||
|
|
11536da512 | ||
|
|
2a91bc41f7 | ||
|
|
baf23157fc | ||
|
|
83b81be825 | ||
|
|
4aefa0f1f7 | ||
|
|
e53a7cee97 | ||
|
|
8437e1aa7b | ||
|
|
632f40cc0a | ||
|
|
7d81445341 | ||
|
|
78a5f401d7 | ||
|
|
8745f98c95 | ||
|
|
f0f5bcc630 | ||
|
|
8a3c513605 | ||
|
|
145d2636dd | ||
|
|
f2b9dc8988 | ||
|
|
2e4d56728b | ||
|
|
18231ed324 | ||
|
|
d0b61d8ed1 | ||
|
|
d385a44949 | ||
|
|
d97392d46e | ||
|
|
6abb2c73fd | ||
|
|
7e141c6b36 | ||
|
|
53553c1f62 | ||
|
|
523ccc6bf8 | ||
|
|
811e89fcfa | ||
|
|
5d5be4d9d7 | ||
|
|
88457ab139 | ||
|
|
0034926df7 | ||
|
|
d082d41ab9 | ||
|
|
b7911475b6 | ||
|
|
672754b0b5 | ||
|
|
0d1553123b | ||
|
|
ba6c51c102 | ||
|
|
d64206a9ff | ||
|
|
d9a1089039 | ||
|
|
55fe463405 | ||
|
|
e84210e962 | ||
|
|
ff506548d3 | ||
|
|
f6b09751e9 | ||
|
|
3d863979c4 | ||
|
|
2947ff4131 | ||
|
|
b8fca10896 | ||
|
|
33e45794d2 | ||
|
|
42cc88ca65 | ||
|
|
0b7f2416ca | ||
|
|
5d5c745ee5 | ||
|
|
e0429e4c60 | ||
|
|
0bece5287e | ||
|
|
60b5e47836 | ||
|
|
aa75b44790 | ||
|
|
769f58caa9 | ||
|
|
e7facf740d | ||
|
|
04921eef62 | ||
|
|
8863c42879 | ||
|
|
2745116ac5 |
294
.github/workflows/release.yml
vendored
294
.github/workflows/release.yml
vendored
@@ -398,297 +398,3 @@ jobs:
|
||||
prerelease: ${{ contains(env.TAG, '-') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# trigger-obs-update:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: release
|
||||
# env:
|
||||
# TAG: ${{ inputs.tag }}
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# ref: ${{ inputs.tag }}
|
||||
|
||||
# - name: Install OSC
|
||||
# run: |
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y osc
|
||||
|
||||
# mkdir -p ~/.config/osc
|
||||
# cat > ~/.config/osc/oscrc << EOF
|
||||
# [general]
|
||||
# apiurl = https://api.opensuse.org
|
||||
|
||||
# [https://api.opensuse.org]
|
||||
# user = ${{ secrets.OBS_USERNAME }}
|
||||
# pass = ${{ secrets.OBS_PASSWORD }}
|
||||
# EOF
|
||||
# chmod 600 ~/.config/osc/oscrc
|
||||
|
||||
# - name: Update OBS packages
|
||||
# run: |
|
||||
# cd distro
|
||||
# bash scripts/obs-upload.sh dms "Update to ${TAG}"
|
||||
|
||||
# trigger-ppa-update:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: release
|
||||
# env:
|
||||
# TAG: ${{ inputs.tag }}
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# ref: ${{ inputs.tag }}
|
||||
|
||||
# - name: Install build dependencies
|
||||
# run: |
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y \
|
||||
# debhelper \
|
||||
# devscripts \
|
||||
# dput \
|
||||
# lftp \
|
||||
# build-essential \
|
||||
# fakeroot \
|
||||
# dpkg-dev
|
||||
|
||||
# - name: Configure GPG
|
||||
# env:
|
||||
# GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
# run: |
|
||||
# echo "$GPG_KEY" | gpg --import
|
||||
# GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | awk '{print $2}' | cut -d'/' -f2)
|
||||
# echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV
|
||||
|
||||
# - name: Upload to PPA
|
||||
# run: |
|
||||
# cd distro/ubuntu/ppa
|
||||
# bash create-and-upload.sh ../dms dms questing
|
||||
|
||||
# copr-build:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: release
|
||||
# env:
|
||||
# TAG: ${{ inputs.tag }}
|
||||
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# ref: ${{ inputs.tag }}
|
||||
|
||||
# - name: Determine version
|
||||
# id: version
|
||||
# run: |
|
||||
# VERSION="${TAG#v}"
|
||||
# echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
# echo "Building DMS stable version: $VERSION"
|
||||
|
||||
# - name: Setup build environment
|
||||
# run: |
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y rpm wget curl jq gzip
|
||||
# mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
|
||||
# - name: Download release assets
|
||||
# run: |
|
||||
# VERSION="${{ steps.version.outputs.version }}"
|
||||
# cd ~/rpmbuild/SOURCES
|
||||
|
||||
# wget "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
|
||||
# echo "Failed to download dms-qml.tar.gz for v${VERSION}"
|
||||
# exit 1
|
||||
# }
|
||||
|
||||
# - name: Generate stable spec file
|
||||
# run: |
|
||||
# VERSION="${{ steps.version.outputs.version }}"
|
||||
# CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
||||
|
||||
# cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
||||
# # Spec for DMS stable releases - Generated by GitHub Actions
|
||||
|
||||
# %global debug_package %{nil}
|
||||
# %global version VERSION_PLACEHOLDER
|
||||
# %global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
# Name: dms
|
||||
# Version: %{version}
|
||||
# Release: 1%{?dist}
|
||||
# Summary: %{pkg_summary}
|
||||
|
||||
# License: MIT
|
||||
# URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
# Source0: dms-qml.tar.gz
|
||||
|
||||
# BuildRequires: gzip
|
||||
# BuildRequires: wget
|
||||
# BuildRequires: systemd-rpm-macros
|
||||
|
||||
# Requires: (quickshell or quickshell-git)
|
||||
# Requires: accountsservice
|
||||
# Requires: dms-cli = %{version}-%{release}
|
||||
# Requires: dgop
|
||||
|
||||
# Recommends: cava
|
||||
# Recommends: cliphist
|
||||
# Recommends: danksearch
|
||||
# Recommends: matugen
|
||||
# Recommends: wl-clipboard
|
||||
# Recommends: NetworkManager
|
||||
# Recommends: qt6-qtmultimedia
|
||||
# Suggests: qt6ct
|
||||
|
||||
# %description
|
||||
# DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
# and optimized for the niri and hyprland compositors. Features notifications,
|
||||
# app launcher, wallpaper customization, and fully customizable with plugins.
|
||||
|
||||
# Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
# process monitoring, notification center, clipboard history, dock, control center,
|
||||
# lock screen, and comprehensive plugin system.
|
||||
|
||||
# %package -n dms-cli
|
||||
# Summary: DankMaterialShell CLI tool
|
||||
# License: MIT
|
||||
# URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
# %description -n dms-cli
|
||||
# Command-line interface for DankMaterialShell configuration and management.
|
||||
# Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||
|
||||
# %prep
|
||||
# %setup -q -c -n dms-qml
|
||||
|
||||
# # Download architecture-specific binaries during build
|
||||
# case "%{_arch}" in
|
||||
# x86_64)
|
||||
# ARCH_SUFFIX="amd64"
|
||||
# ;;
|
||||
# aarch64)
|
||||
# ARCH_SUFFIX="arm64"
|
||||
# ;;
|
||||
# *)
|
||||
# echo "Unsupported architecture: %{_arch}"
|
||||
# exit 1
|
||||
# ;;
|
||||
# esac
|
||||
|
||||
# wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
|
||||
# echo "Failed to download dms-cli for architecture %{_arch}"
|
||||
# exit 1
|
||||
# }
|
||||
# gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
||||
# chmod +x %{_builddir}/dms-cli
|
||||
|
||||
# %build
|
||||
|
||||
# %install
|
||||
# install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
||||
|
||||
# install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||
# install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||
# install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
||||
# %{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
||||
# %{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
# %{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
|
||||
# install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
|
||||
# install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
|
||||
# install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
# install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||
# cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
|
||||
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
||||
# rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
||||
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
||||
|
||||
# echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
||||
|
||||
# %posttrans
|
||||
# if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
||||
# rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
||||
# rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||
# rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||
# fi
|
||||
# # Signal running DMS instances to reload
|
||||
# pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||
|
||||
# %files
|
||||
# %license LICENSE
|
||||
# %doc README.md CONTRIBUTING.md
|
||||
# %{_datadir}/quickshell/dms/
|
||||
# %{_userunitdir}/dms.service
|
||||
# %{_datadir}/applications/dms-open.desktop
|
||||
# %{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
# %files -n dms-cli
|
||||
# %{_bindir}/dms
|
||||
# %{_datadir}/bash-completion/completions/dms
|
||||
# %{_datadir}/zsh/site-functions/_dms
|
||||
# %{_datadir}/fish/vendor_completions.d/dms.fish
|
||||
|
||||
# %changelog
|
||||
# * CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
||||
# - Stable release VERSION_PLACEHOLDER
|
||||
# - Built from GitHub release
|
||||
# SPECEOF
|
||||
|
||||
# sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
# sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
|
||||
# - name: Build SRPM
|
||||
# id: build
|
||||
# run: |
|
||||
# cd ~/rpmbuild/SPECS
|
||||
# rpmbuild -bs dms.spec
|
||||
|
||||
# SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
|
||||
# SRPM_NAME=$(basename "$SRPM")
|
||||
|
||||
# echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
|
||||
# echo "srpm_name=$SRPM_NAME" >> $GITHUB_OUTPUT
|
||||
# echo "SRPM built: $SRPM_NAME"
|
||||
|
||||
# - name: Upload SRPM artifact
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: dms-stable-srpm-${{ steps.version.outputs.version }}
|
||||
# path: ${{ steps.build.outputs.srpm_path }}
|
||||
# retention-days: 90
|
||||
|
||||
# - name: Install Copr CLI
|
||||
# run: |
|
||||
# sudo apt-get install -y python3-pip
|
||||
# pip3 install copr-cli
|
||||
|
||||
# mkdir -p ~/.config
|
||||
# cat > ~/.config/copr << EOF
|
||||
# [copr-cli]
|
||||
# login = ${{ secrets.COPR_LOGIN }}
|
||||
# username = avengemedia
|
||||
# token = ${{ secrets.COPR_TOKEN }}
|
||||
# copr_url = https://copr.fedorainfracloud.org
|
||||
# EOF
|
||||
# chmod 600 ~/.config/copr
|
||||
|
||||
# - name: Upload to Copr
|
||||
# run: |
|
||||
# SRPM="${{ steps.build.outputs.srpm_path }}"
|
||||
# VERSION="${{ steps.version.outputs.version }}"
|
||||
|
||||
# echo "Uploading SRPM to avengemedia/dms..."
|
||||
# BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
|
||||
# echo "$BUILD_OUTPUT"
|
||||
|
||||
# BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
|
||||
|
||||
# if [ "$BUILD_ID" != "unknown" ]; then
|
||||
# echo "Build submitted: https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
|
||||
# fi
|
||||
|
||||
218
.github/workflows/run-copr.yml
vendored
218
.github/workflows/run-copr.yml
vendored
@@ -3,8 +3,17 @@ name: DMS Copr Stable Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: 'Package to build (dms, dms-greeter, or both)'
|
||||
required: false
|
||||
default: 'dms'
|
||||
type: choice
|
||||
options:
|
||||
- dms
|
||||
- dms-greeter
|
||||
- both
|
||||
version:
|
||||
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
|
||||
description: 'Versioning (e.g., 1.0.3, leave empty for latest release)'
|
||||
required: false
|
||||
default: ''
|
||||
release:
|
||||
@@ -13,8 +22,27 @@ on:
|
||||
default: '1'
|
||||
|
||||
jobs:
|
||||
build-and-upload:
|
||||
determine-packages:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
packages: ${{ steps.set-packages.outputs.packages }}
|
||||
steps:
|
||||
- name: Set package list
|
||||
id: set-packages
|
||||
run: |
|
||||
PACKAGE_INPUT="${{ github.event.inputs.package || 'dms' }}"
|
||||
if [ "$PACKAGE_INPUT" = "both" ]; then
|
||||
echo 'packages=["dms","dms-greeter"]' >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "packages=[\"$PACKAGE_INPUT\"]" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
build-and-upload:
|
||||
needs: determine-packages
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
package: ${{ fromJSON(needs.determine-packages.outputs.packages) }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -39,7 +67,7 @@ jobs:
|
||||
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "release=$RELEASE" >> $GITHUB_OUTPUT
|
||||
echo "✅ Building DMS hotfix version: $VERSION-$RELEASE"
|
||||
echo "✅ Building ${{ matrix.package }} version: $VERSION-$RELEASE"
|
||||
|
||||
- name: Setup build environment
|
||||
run: |
|
||||
@@ -70,157 +98,31 @@ jobs:
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
RELEASE="${{ steps.version.outputs.release }}"
|
||||
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
||||
PACKAGE="${{ matrix.package }}"
|
||||
|
||||
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
||||
# Spec for DMS stable releases - Generated by GitHub Actions
|
||||
# Copy spec file from repository
|
||||
cp distro/fedora/${PACKAGE}.spec ~/rpmbuild/SPECS/${PACKAGE}.spec
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global version VERSION_PLACEHOLDER
|
||||
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
# Replace placeholders with actual values
|
||||
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/${PACKAGE}.spec
|
||||
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/${PACKAGE}.spec
|
||||
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/${PACKAGE}.spec
|
||||
|
||||
Name: dms
|
||||
Version: %{version}
|
||||
Release: RELEASE_PLACEHOLDER%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
Source0: dms-qml.tar.gz
|
||||
|
||||
BuildRequires: gzip
|
||||
BuildRequires: wget
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
Requires: (quickshell or quickshell-git)
|
||||
Requires: accountsservice
|
||||
Requires: dms-cli = %{version}-%{release}
|
||||
Requires: dgop
|
||||
|
||||
Recommends: cava
|
||||
Recommends: cliphist
|
||||
Recommends: danksearch
|
||||
Recommends: hyprpicker
|
||||
Recommends: matugen
|
||||
Recommends: wl-clipboard
|
||||
Recommends: NetworkManager
|
||||
Recommends: qt6-qtmultimedia
|
||||
Suggests: qt6ct
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for the niri and hyprland compositors. Features notifications,
|
||||
app launcher, wallpaper customization, and fully customizable with plugins.
|
||||
|
||||
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
process monitoring, notification center, clipboard history, dock, control center,
|
||||
lock screen, and comprehensive plugin system.
|
||||
|
||||
%package -n dms-cli
|
||||
Summary: DankMaterialShell CLI tool
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
%description -n dms-cli
|
||||
Command-line interface for DankMaterialShell configuration and management.
|
||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||
|
||||
%prep
|
||||
%setup -q -c -n dms-qml
|
||||
|
||||
# Download architecture-specific binaries during build
|
||||
# This ensures the correct architecture is used for each build target
|
||||
case "%{_arch}" in
|
||||
x86_64)
|
||||
ARCH_SUFFIX="amd64"
|
||||
;;
|
||||
aarch64)
|
||||
ARCH_SUFFIX="arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: %{_arch}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Download dms-cli for target architecture
|
||||
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
|
||||
echo "Failed to download dms-cli for architecture %{_arch}"
|
||||
exit 1
|
||||
}
|
||||
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
||||
chmod +x %{_builddir}/dms-cli
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
||||
|
||||
# Shell completions
|
||||
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
||||
%{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
||||
%{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
%{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
|
||||
install -Dm644 %{_builddir}/dms-qml/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
|
||||
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
||||
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
||||
|
||||
%posttrans
|
||||
# Clean up old installation path from previous versions (only if empty)
|
||||
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
||||
# Remove directories only if empty (preserves any user-added files)
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||
fi
|
||||
# Signal running DMS instances to reload (harmless if none running)
|
||||
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc README.md CONTRIBUTING.md
|
||||
%{_datadir}/quickshell/dms/
|
||||
%{_userunitdir}/dms.service
|
||||
|
||||
%files -n dms-cli
|
||||
%{_bindir}/dms
|
||||
%{_datadir}/bash-completion/completions/dms
|
||||
%{_datadir}/zsh/site-functions/_dms
|
||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||
|
||||
%changelog
|
||||
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
|
||||
- Stable release VERSION_PLACEHOLDER
|
||||
- Built from GitHub release
|
||||
SPECEOF
|
||||
|
||||
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
|
||||
echo "✅ Spec file generated for v${VERSION}-${RELEASE}"
|
||||
echo "✅ Spec file generated for ${PACKAGE} v${VERSION}-${RELEASE}"
|
||||
echo ""
|
||||
echo "=== Spec file preview ==="
|
||||
head -40 ~/rpmbuild/SPECS/dms.spec
|
||||
head -40 ~/rpmbuild/SPECS/${PACKAGE}.spec
|
||||
|
||||
- name: Build SRPM
|
||||
id: build
|
||||
run: |
|
||||
cd ~/rpmbuild/SPECS
|
||||
PACKAGE="${{ matrix.package }}"
|
||||
|
||||
echo "🔨 Building SRPM..."
|
||||
rpmbuild -bs dms.spec
|
||||
echo "🔨 Building SRPM for ${PACKAGE}..."
|
||||
rpmbuild -bs ${PACKAGE}.spec
|
||||
|
||||
SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
|
||||
SRPM=$(ls ~/rpmbuild/SRPMS/${PACKAGE}-*.src.rpm | tail -n 1)
|
||||
SRPM_NAME=$(basename "$SRPM")
|
||||
|
||||
echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
|
||||
@@ -234,7 +136,7 @@ jobs:
|
||||
- name: Upload SRPM artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dms-stable-srpm-${{ steps.version.outputs.version }}
|
||||
name: ${{ matrix.package }}-stable-srpm-${{ steps.version.outputs.version }}
|
||||
path: ${{ steps.build.outputs.srpm_path }}
|
||||
retention-days: 90
|
||||
|
||||
@@ -255,23 +157,40 @@ jobs:
|
||||
|
||||
echo "✅ Copr CLI configured"
|
||||
|
||||
- name: Determine Copr project
|
||||
id: copr_project
|
||||
run: |
|
||||
PACKAGE="${{ matrix.package }}"
|
||||
if [ "$PACKAGE" = "dms" ]; then
|
||||
COPR_PROJECT="avengemedia/dms"
|
||||
elif [ "$PACKAGE" = "dms-greeter" ]; then
|
||||
COPR_PROJECT="avengemedia/danklinux"
|
||||
else
|
||||
echo "❌ Unknown package: $PACKAGE"
|
||||
exit 1
|
||||
fi
|
||||
echo "copr_project=$COPR_PROJECT" >> $GITHUB_OUTPUT
|
||||
echo "✅ Copr project: $COPR_PROJECT"
|
||||
|
||||
- name: Upload to Copr
|
||||
run: |
|
||||
SRPM="${{ steps.build.outputs.srpm_path }}"
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
COPR_PROJECT="${{ steps.copr_project.outputs.copr_project }}"
|
||||
PACKAGE="${{ matrix.package }}"
|
||||
|
||||
echo "🚀 Uploading SRPM to avengemedia/dms..."
|
||||
echo "🚀 Uploading ${PACKAGE} SRPM to ${COPR_PROJECT}..."
|
||||
echo " SRPM: $(basename $SRPM)"
|
||||
echo " Version: $VERSION"
|
||||
|
||||
BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
|
||||
BUILD_OUTPUT=$(copr-cli build "$COPR_PROJECT" "$SRPM" --nowait 2>&1)
|
||||
echo "$BUILD_OUTPUT"
|
||||
|
||||
BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
|
||||
|
||||
if [ "$BUILD_ID" != "unknown" ]; then
|
||||
echo "✅ Build submitted successfully!"
|
||||
echo "🔗 https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
|
||||
echo "🔗 https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/build/$BUILD_ID/"
|
||||
else
|
||||
echo "⚠️ Could not extract build ID, but upload may have succeeded"
|
||||
fi
|
||||
@@ -279,10 +198,13 @@ jobs:
|
||||
- name: Build summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
PACKAGE="${{ matrix.package }}"
|
||||
COPR_PROJECT="${{ steps.copr_project.outputs.copr_project }}"
|
||||
echo "### 🎉 ${PACKAGE} Stable Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Package:** ${PACKAGE}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version:** ${{ steps.version.outputs.version }}-${{ steps.version.outputs.release }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Stable release has been built and uploaded to Copr!" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
71
.github/workflows/run-obs.yml
vendored
71
.github/workflows/run-obs.yml
vendored
@@ -7,13 +7,14 @@ on:
|
||||
description: "Package to update (dms, dms-git, or all)"
|
||||
required: false
|
||||
default: "all"
|
||||
tag_version:
|
||||
description: "Specific tag version for dms stable (e.g., v1.0.2). Leave empty to auto-detect latest release."
|
||||
required: false
|
||||
default: ""
|
||||
rebuild_release:
|
||||
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
|
||||
required: false
|
||||
default: ""
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
schedule:
|
||||
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
|
||||
|
||||
@@ -97,7 +98,7 @@ jobs:
|
||||
# Rebuild requested - always proceed
|
||||
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "🔄 Manual rebuild requested: $PKG (ppa$REBUILD)"
|
||||
echo "🔄 Manual rebuild requested: $PKG (db$REBUILD)"
|
||||
|
||||
elif [[ "$PKG" == "all" ]]; then
|
||||
# Check each package and build list of those needing updates
|
||||
@@ -161,16 +162,51 @@ jobs:
|
||||
id: packages
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
# Tag push event - use the pushed tag
|
||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by tag: $VERSION"
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
# Scheduled run - dms-git only
|
||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by schedule: updating git package"
|
||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||
# Use filtered packages from check-updates when package="all" and no rebuild requested
|
||||
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||
# Manual workflow dispatch
|
||||
|
||||
# Determine version for dms stable
|
||||
if [[ "${{ github.event.inputs.package }}" == "dms" ]]; then
|
||||
# For explicit dms selection, require tag_version
|
||||
if [[ -n "${{ github.event.inputs.tag_version }}" ]]; then
|
||||
VERSION="${{ github.event.inputs.tag_version }}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Using specified tag: $VERSION"
|
||||
else
|
||||
echo "ERROR: tag_version is required when package=dms"
|
||||
echo "Please specify a tag version (e.g., v1.0.2) or use package=all for auto-detection"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "${{ github.event.inputs.package }}" == "all" ]]; then
|
||||
# For "all", auto-detect if tag_version not specified
|
||||
if [[ -n "${{ github.event.inputs.tag_version }}" ]]; then
|
||||
VERSION="${{ github.event.inputs.tag_version }}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Using specified tag: $VERSION"
|
||||
else
|
||||
# Auto-detect latest release for "all"
|
||||
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "")
|
||||
if [[ -n "$LATEST_TAG" ]]; then
|
||||
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
echo "Auto-detected latest release: $LATEST_TAG"
|
||||
else
|
||||
echo "ERROR: Could not auto-detect latest release"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Use filtered packages from check-updates when package="all" and no rebuild/tag specified
|
||||
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]] && [[ -z "${{ github.event.inputs.tag_version }}" ]]; then
|
||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
|
||||
else
|
||||
@@ -186,7 +222,7 @@ jobs:
|
||||
run: |
|
||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "1.0.2")
|
||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
|
||||
|
||||
@@ -207,14 +243,14 @@ jobs:
|
||||
run: |
|
||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "1.0.2")
|
||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
|
||||
|
||||
# Single changelog entry (git snapshots don't need history)
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
{
|
||||
echo "dms-git ($NEW_VERSION) nightly; urgency=medium"
|
||||
echo "dms-git (${NEW_VERSION}db1) nightly; urgency=medium"
|
||||
echo ""
|
||||
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||
echo ""
|
||||
@@ -226,10 +262,15 @@ jobs:
|
||||
run: |
|
||||
VERSION="${{ steps.packages.outputs.version }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
echo "Updating packaging to version $VERSION_NO_V"
|
||||
echo "==> Updating packaging files to version: $VERSION_NO_V"
|
||||
|
||||
# Update spec file
|
||||
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
||||
|
||||
# Verify the update
|
||||
UPDATED_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1)
|
||||
echo "✓ Spec file now shows Version: $UPDATED_VERSION"
|
||||
|
||||
# Single changelog entry (full history on OBS website)
|
||||
DATE_STR=$(date "+%a %b %d %Y")
|
||||
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
|
||||
@@ -256,13 +297,13 @@ jobs:
|
||||
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
{
|
||||
echo "dms ($VERSION_NO_V) stable; urgency=medium"
|
||||
echo "dms (${VERSION_NO_V}db1) stable; urgency=medium"
|
||||
echo ""
|
||||
echo " * Update to $VERSION stable release"
|
||||
echo ""
|
||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||
} > "distro/debian/dms/debian/changelog"
|
||||
echo "✓ Updated Debian changelog to $VERSION_NO_V"
|
||||
echo "✓ Updated Debian changelog to ${VERSION_NO_V}db1"
|
||||
fi
|
||||
|
||||
- name: Install Go
|
||||
@@ -289,6 +330,7 @@ jobs:
|
||||
- name: Upload to OBS
|
||||
env:
|
||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
||||
TAG_VERSION: ${{ steps.packages.outputs.version }}
|
||||
run: |
|
||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||
|
||||
@@ -300,6 +342,7 @@ jobs:
|
||||
MESSAGE="Automated update from GitHub Actions"
|
||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
|
||||
echo "==> Version being uploaded: ${{ steps.packages.outputs.version }}"
|
||||
fi
|
||||
|
||||
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
|
||||
@@ -309,7 +352,7 @@ jobs:
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Uploading $PKG to OBS..."
|
||||
if [[ -n "$REBUILD_RELEASE" ]]; then
|
||||
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
||||
echo "🔄 Using rebuild release number: db$REBUILD_RELEASE"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
@@ -350,7 +393,7 @@ jobs:
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||
echo "**Rebuild Number:** ppa${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Rebuild Number:** db${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
|
||||
3
.github/workflows/run-ppa.yml
vendored
3
.github/workflows/run-ppa.yml
vendored
@@ -51,7 +51,8 @@ jobs:
|
||||
check_stable_package() {
|
||||
local PKG="$1"
|
||||
local PPA_NAME="$2"
|
||||
local LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "v\?\([^"]*\)".*/\1/' || echo "")
|
||||
# Use git ls-remote to find the latest tag, sorted by version (descending)
|
||||
local LATEST_TAG=$(git ls-remote --tags --refs --sort='-v:refname' https://github.com/AvengeMedia/DankMaterialShell.git | head -n1 | awk -F/ '{print $NF}' | sed 's/^v//')
|
||||
local PPA_VERSION=$(curl -s "https://api.launchpad.net/1.0/~avengemedia/+archive/ubuntu/$PPA_NAME?ws.op=getPublishedSources&source_name=$PKG&status=Published" | grep -oP '"source_package_version":\s*"\K[^"]+' | head -1 || echo "")
|
||||
local PPA_BASE_VERSION=$(echo "$PPA_VERSION" | sed 's/ppa[0-9]*$//')
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -96,11 +96,12 @@ go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
.env*
|
||||
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
# .vscode/
|
||||
vim/
|
||||
|
||||
bin/
|
||||
|
||||
|
||||
11
CHANGELOG.MD
11
CHANGELOG.MD
@@ -1,6 +1,17 @@
|
||||
This file is more of a quick reference so I know what to account for before next releases.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
- Added clipboard and clipboard history integration
|
||||
- Added swipe to dismiss notification popups and from center
|
||||
- Added paste from clipboard history view - requires wtype
|
||||
- Optimize surface damage of OSD & Toast
|
||||
- Add monitor configurator (niri, Hyprland, MangoWC)
|
||||
- **BREAKING** ghostty theme changed to ~/.config/ghostty/themes/danktheme
|
||||
- requires intervention and doc update
|
||||
- Added desktop widget plugins
|
||||
- dev guidance available
|
||||
- builtin clock & dgop widgets
|
||||
- new IPC targets
|
||||
- Initial RTL support/i18n
|
||||
- Theme registry
|
||||
|
||||
@@ -163,7 +163,7 @@ quickshell -p quickshell/
|
||||
inputs.dms.url = "github:AvengeMedia/DankMaterialShell";
|
||||
|
||||
# Use in home-manager or NixOS configuration
|
||||
imports = [ inputs.dms.homeModules.dankMaterialShell.default ];
|
||||
imports = [ inputs.dms.homeModules.dank-material-shell ];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=DMS Application Picker
|
||||
Name=DMS
|
||||
Comment=Select an application to open links and files
|
||||
Exec=dms open %u
|
||||
Icon=danklogo
|
||||
Terminal=false
|
||||
NoDisplay=true
|
||||
MimeType=x-scheme-handler/http;x-scheme-handler/https;text/html;application/xhtml+xml;
|
||||
MimeType=x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/dms;text/html;application/xhtml+xml;
|
||||
Categories=Utility;
|
||||
|
||||
@@ -9,7 +9,7 @@ Type=dbus
|
||||
BusName=org.freedesktop.Notifications
|
||||
ExecStart=/usr/bin/dms run --session
|
||||
ExecReload=/usr/bin/pkill -USR1 -x dms
|
||||
Restart=always
|
||||
Restart=on-failure
|
||||
RestartSec=1.23
|
||||
TimeoutStopSec=10
|
||||
|
||||
|
||||
@@ -14,34 +14,63 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
|
||||
|
||||
## System Integration
|
||||
|
||||
**Wayland Protocols**
|
||||
- `wlr-gamma-control-unstable-v1` - Night mode and gamma control
|
||||
- `wlr-screencopy-unstable-v1` - Screen capture for color picker
|
||||
- `wlr-layer-shell-unstable-v1` - Overlay surfaces for color picker
|
||||
- `wp-viewporter` - Fractional scaling support
|
||||
- `dwl-ipc-unstable-v2` - dwl/MangoWC workspace integration
|
||||
- `ext-workspace-v1` - Workspace protocol support
|
||||
- `wlr-output-management-unstable-v1` - Display configuration
|
||||
### Wayland Protocols (Client)
|
||||
|
||||
**DBus Interfaces**
|
||||
- NetworkManager/iwd - Network management
|
||||
- logind - Session control and inhibit locks
|
||||
- accountsservice - User account information
|
||||
- CUPS - Printer management
|
||||
- Custom IPC via unix socket (JSON API)
|
||||
All Wayland protocols are consumed as a client - connecting to the compositor.
|
||||
|
||||
**Hardware Control**
|
||||
- DDC/CI protocol - External monitor brightness control (like `ddcutil`)
|
||||
- Backlight control - Internal display brightness via `login1` or sysfs
|
||||
- LED control - Keyboard/device LED management
|
||||
- evdev input monitoring - Keyboard state tracking (caps lock, etc.)
|
||||
| Protocol | Purpose |
|
||||
| ----------------------------------------- | ----------------------------------------------------------- |
|
||||
| `wlr-gamma-control-unstable-v1` | Night mode color temperature control |
|
||||
| `wlr-screencopy-unstable-v1` | Screen capture for color picker/screenshot |
|
||||
| `wlr-layer-shell-unstable-v1` | Overlay surfaces for color picker UI/screenshot |
|
||||
| `wlr-output-management-unstable-v1` | Display configuration |
|
||||
| `wlr-output-power-management-unstable-v1` | DPMS on/off CLI |
|
||||
| `wp-viewporter` | Fractional scaling support (color picker/screenshot UIs) |
|
||||
| `keyboard-shortcuts-inhibit-unstable-v1` | Inhibit compositor shortcuts during color picker/screenshot |
|
||||
| `ext-data-control-v1` | Clipboard history and persistence |
|
||||
| `ext-workspace-v1` | Workspace integration |
|
||||
| `dwl-ipc-unstable-v2` | dwl/MangoWC IPC for tags, outputs, etc. |
|
||||
|
||||
### DBus Interfaces
|
||||
|
||||
**Client (consuming external services):**
|
||||
|
||||
| Interface | Purpose |
|
||||
| -------------------------------- | --------------------------------------------- |
|
||||
| `org.bluez` | Bluetooth management with pairing agent |
|
||||
| `org.freedesktop.NetworkManager` | Network management |
|
||||
| `net.connman.iwd` | iwd Wi-Fi backend |
|
||||
| `org.freedesktop.network1` | systemd-networkd integration |
|
||||
| `org.freedesktop.login1` | Session control, sleep inhibitors, brightness |
|
||||
| `org.freedesktop.Accounts` | User account information |
|
||||
| `org.freedesktop.portal.Desktop` | Desktop appearance settings (color scheme) |
|
||||
| CUPS via IPP + D-Bus | Printer management with job notifications |
|
||||
|
||||
**Server (implementing interfaces):**
|
||||
|
||||
| Interface | Purpose |
|
||||
| ----------------------------- | -------------------------------------- |
|
||||
| `org.freedesktop.ScreenSaver` | Screensaver inhibit for video playback |
|
||||
|
||||
Custom IPC via unix socket (JSON API) for shell communication.
|
||||
|
||||
### Hardware Control
|
||||
|
||||
| Subsystem | Method | Purpose |
|
||||
| --------- | ------------------- | ---------------------------------- |
|
||||
| DDC/CI | I2C direct | External monitor brightness |
|
||||
| Backlight | logind or sysfs | Internal display brightness |
|
||||
| evdev | `/dev/input/event*` | Keyboard state (caps lock LED) |
|
||||
| udev | netlink monitor | Backlight device updates (for OSD) |
|
||||
|
||||
### Plugin System
|
||||
|
||||
**Plugin System**
|
||||
- Plugin registry integration
|
||||
- Plugin lifecycle management
|
||||
- Settings persistence
|
||||
|
||||
## CLI Commands
|
||||
|
||||
- `dms run [-d]` - Start shell (optionally as daemon)
|
||||
- `dms restart` / `dms kill` - Manage running processes
|
||||
- `dms ipc <command>` - Send IPC commands (toggle launcher, notifications, etc.)
|
||||
@@ -70,6 +99,7 @@ The on-screen preview displays the selected format. JSON output includes hex, RG
|
||||
Requires Go 1.24+
|
||||
|
||||
**Development build:**
|
||||
|
||||
```bash
|
||||
make # Build dms CLI
|
||||
make dankinstall # Build installer
|
||||
@@ -77,6 +107,7 @@ make test # Run tests
|
||||
```
|
||||
|
||||
**Distribution build:**
|
||||
|
||||
```bash
|
||||
make dist # Build without update/greeter features
|
||||
```
|
||||
@@ -84,6 +115,7 @@ make dist # Build without update/greeter features
|
||||
Produces `bin/dms-linux-amd64` and `bin/dms-linux-arm64`
|
||||
|
||||
**Installation:**
|
||||
|
||||
```bash
|
||||
sudo make install # Install to /usr/local/bin/dms
|
||||
```
|
||||
@@ -91,6 +123,7 @@ sudo make install # Install to /usr/local/bin/dms
|
||||
## Development
|
||||
|
||||
**Setup pre-commit hooks:**
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
@@ -98,6 +131,7 @@ git config core.hooksPath .githooks
|
||||
This runs gofmt, golangci-lint, tests, and builds before each commit when `core/` files are staged.
|
||||
|
||||
**Regenerating Wayland Protocol Bindings:**
|
||||
|
||||
```bash
|
||||
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest
|
||||
go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
|
||||
@@ -105,6 +139,7 @@ go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
|
||||
```
|
||||
|
||||
**Module Structure:**
|
||||
|
||||
- `cmd/` - Binary entrypoints (dms, dankinstall)
|
||||
- `internal/distros/` - Distribution-specific installation logic
|
||||
- `internal/proto/` - Wayland protocol bindings
|
||||
|
||||
@@ -179,7 +179,7 @@ func runBrightnessList(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("%-*s %-12s %-*s %s\n", idPad, "Device", "Class", namePad, "Name", "Brightness")
|
||||
|
||||
sepLen := idPad + 2 + 12 + 2 + namePad + 2 + 15
|
||||
for i := 0; i < sepLen; i++ {
|
||||
for range sepLen {
|
||||
fmt.Print("─")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
@@ -144,8 +144,6 @@ var (
|
||||
clipConfigEnabled bool
|
||||
clipConfigDisableHistory bool
|
||||
clipConfigEnableHistory bool
|
||||
clipConfigDisablePersist bool
|
||||
clipConfigEnablePersist bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -173,8 +171,6 @@ func init() {
|
||||
clipConfigSetCmd.Flags().BoolVar(&clipConfigEnabled, "enable", false, "Enable clipboard manager")
|
||||
clipConfigSetCmd.Flags().BoolVar(&clipConfigDisableHistory, "disable-history", false, "Disable clipboard history persistence")
|
||||
clipConfigSetCmd.Flags().BoolVar(&clipConfigEnableHistory, "enable-history", false, "Enable clipboard history persistence")
|
||||
clipConfigSetCmd.Flags().BoolVar(&clipConfigDisablePersist, "disable-persist", false, "Disable clipboard ownership persistence")
|
||||
clipConfigSetCmd.Flags().BoolVar(&clipConfigEnablePersist, "enable-persist", false, "Enable clipboard ownership persistence")
|
||||
|
||||
clipWatchCmd.Flags().BoolVarP(&clipWatchStore, "store", "s", false, "Store clipboard changes to history (no server required)")
|
||||
|
||||
@@ -597,12 +593,6 @@ func runClipConfigSet(cmd *cobra.Command, args []string) {
|
||||
if clipConfigEnableHistory {
|
||||
params["disableHistory"] = false
|
||||
}
|
||||
if clipConfigDisablePersist {
|
||||
params["disablePersist"] = true
|
||||
}
|
||||
if clipConfigEnablePersist {
|
||||
params["disablePersist"] = false
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
fmt.Println("No config options specified")
|
||||
|
||||
@@ -171,7 +171,6 @@ var pluginsUpdateCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func runVersion(cmd *cobra.Command, args []string) {
|
||||
printASCII()
|
||||
fmt.Printf("%s\n", formatVersion(Version))
|
||||
}
|
||||
|
||||
@@ -220,7 +219,7 @@ func getBaseVersion() string {
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return "0.6.2"
|
||||
return "1.0.2"
|
||||
}
|
||||
|
||||
func startDebugServer() error {
|
||||
|
||||
@@ -22,6 +22,7 @@ func init() {
|
||||
dank16Cmd.Flags().Bool("json", false, "Output in JSON format")
|
||||
dank16Cmd.Flags().Bool("kitty", false, "Output in Kitty terminal format")
|
||||
dank16Cmd.Flags().Bool("foot", false, "Output in Foot terminal format")
|
||||
dank16Cmd.Flags().Bool("neovim", false, "Output in Neovim plugin format")
|
||||
dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format")
|
||||
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
|
||||
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
|
||||
@@ -40,6 +41,7 @@ func runDank16(cmd *cobra.Command, args []string) {
|
||||
isJson, _ := cmd.Flags().GetBool("json")
|
||||
isKitty, _ := cmd.Flags().GetBool("kitty")
|
||||
isFoot, _ := cmd.Flags().GetBool("foot")
|
||||
isNeovim, _ := cmd.Flags().GetBool("neovim")
|
||||
isAlacritty, _ := cmd.Flags().GetBool("alacritty")
|
||||
isGhostty, _ := cmd.Flags().GetBool("ghostty")
|
||||
isWezterm, _ := cmd.Flags().GetBool("wezterm")
|
||||
@@ -116,6 +118,8 @@ func runDank16(cmd *cobra.Command, args []string) {
|
||||
fmt.Print(dank16.GenerateGhosttyTheme(colors))
|
||||
} else if isWezterm {
|
||||
fmt.Print(dank16.GenerateWeztermTheme(colors))
|
||||
} else if isNeovim {
|
||||
fmt.Print(dank16.GenerateNeovimTheme(colors))
|
||||
} else {
|
||||
fmt.Print(dank16.GenerateGhosttyTheme(colors))
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ func updateDMSBinary() error {
|
||||
}
|
||||
|
||||
version := ""
|
||||
for _, line := range strings.Split(string(output), "\n") {
|
||||
for line := range strings.SplitSeq(string(output), "\n") {
|
||||
if strings.Contains(line, "\"tag_name\"") {
|
||||
parts := strings.Split(line, "\"")
|
||||
if len(parts) >= 4 {
|
||||
@@ -443,7 +443,7 @@ func updateDMSBinary() error {
|
||||
|
||||
decompressedPath := filepath.Join(tempDir, "dms")
|
||||
|
||||
if err := os.Chmod(decompressedPath, 0755); err != nil {
|
||||
if err := os.Chmod(decompressedPath, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to make binary executable: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -211,8 +211,8 @@ func checkGroupExists(groupName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
lines := strings.SplitSeq(string(data), "\n")
|
||||
for line := range lines {
|
||||
if strings.HasPrefix(line, groupName+":") {
|
||||
return true
|
||||
}
|
||||
@@ -521,7 +521,7 @@ func enableGreeter() error {
|
||||
newConfig := strings.Join(finalLines, "\n")
|
||||
|
||||
tmpFile := "/tmp/greetd-config.toml"
|
||||
if err := os.WriteFile(tmpFile, []byte(newConfig), 0644); err != nil {
|
||||
if err := os.WriteFile(tmpFile, []byte(newConfig), 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write temp config: %w", err)
|
||||
}
|
||||
|
||||
@@ -592,8 +592,8 @@ func checkGreeterStatus() error {
|
||||
if data, err := os.ReadFile(configPath); err == nil {
|
||||
configContent := string(data)
|
||||
if strings.Contains(configContent, "dms-greeter") {
|
||||
lines := strings.Split(configContent, "\n")
|
||||
for _, line := range lines {
|
||||
lines := strings.SplitSeq(configContent, "\n")
|
||||
for line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "command =") || strings.HasPrefix(trimmed, "command=") {
|
||||
parts := strings.SplitN(trimmed, "=", 2)
|
||||
|
||||
@@ -131,6 +131,12 @@ func runOpen(target string) {
|
||||
detectedRequestType = "url"
|
||||
}
|
||||
log.Infof("Detected HTTP(S) URL")
|
||||
} else if strings.HasPrefix(target, "dms://") {
|
||||
// Handle DMS internal URLs (theme/plugin install, etc.)
|
||||
if detectedRequestType == "" {
|
||||
detectedRequestType = "url"
|
||||
}
|
||||
log.Infof("Detected DMS internal URL")
|
||||
} else if _, err := os.Stat(target); err == nil {
|
||||
// Handle local file paths directly (not file:// URIs)
|
||||
// Convert to absolute path
|
||||
@@ -177,7 +183,7 @@ func runOpen(target string) {
|
||||
}
|
||||
|
||||
method := "apppicker.open"
|
||||
if detectedMimeType == "" && len(detectedCategories) == 0 && (strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://")) {
|
||||
if detectedMimeType == "" && len(detectedCategories) == 0 && (strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") || strings.HasPrefix(target, "dms://")) {
|
||||
method = "browser.open"
|
||||
params["url"] = target
|
||||
}
|
||||
|
||||
@@ -87,20 +87,14 @@ func newDPMSClient() (*dpmsClient, error) {
|
||||
switch e.Interface {
|
||||
case wlr_output_power.ZwlrOutputPowerManagerV1InterfaceName:
|
||||
powerMgr := wlr_output_power.NewZwlrOutputPowerManagerV1(c.ctx)
|
||||
version := e.Version
|
||||
if version > 1 {
|
||||
version = 1
|
||||
}
|
||||
version := min(e.Version, 1)
|
||||
if err := registry.Bind(e.Name, e.Interface, version, powerMgr); err == nil {
|
||||
c.powerMgr = powerMgr
|
||||
}
|
||||
|
||||
case "wl_output":
|
||||
output := wlclient.NewOutput(c.ctx)
|
||||
version := e.Version
|
||||
if version > 4 {
|
||||
version = 4
|
||||
}
|
||||
version := min(e.Version, 4)
|
||||
if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
|
||||
outputID := fmt.Sprintf("output-%d", output.ID())
|
||||
state := &outputState{
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -18,6 +19,25 @@ import (
|
||||
|
||||
type ipcTargets map[string]map[string][]string
|
||||
|
||||
// getProcessExitCode returns the exit code from a ProcessState.
|
||||
// For normal exits, returns the exit code directly.
|
||||
// For signal termination, returns 128 + signal number (Unix convention).
|
||||
func getProcessExitCode(state *os.ProcessState) int {
|
||||
if state == nil {
|
||||
return 1
|
||||
}
|
||||
if code := state.ExitCode(); code != -1 {
|
||||
return code
|
||||
}
|
||||
// Process was killed by signal - extract signal number
|
||||
if status, ok := state.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Signaled() {
|
||||
return 128 + int(status.Signal())
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
var isSessionManaged bool
|
||||
|
||||
func execDetachedRestart(targetPID int) {
|
||||
@@ -180,6 +200,16 @@ func runShellInteractive(session bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("QT_QPA_PLATFORMTHEME") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME=gtk3")
|
||||
}
|
||||
if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME_QT6=gtk3")
|
||||
}
|
||||
if os.Getenv("QT_QPA_PLATFORM") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland")
|
||||
}
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
@@ -214,14 +244,28 @@ func runShellInteractive(session bool) {
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
// Handle SIGUSR1 restart for non-session managed processes
|
||||
if sig == syscall.SIGUSR1 && !isSessionManaged {
|
||||
if sig == syscall.SIGUSR1 {
|
||||
if isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, exiting for systemd restart...")
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Infof("Received SIGUSR1, spawning detached restart process...")
|
||||
execDetachedRestart(os.Getpid())
|
||||
// Exit immediately to avoid race conditions with detached restart
|
||||
return
|
||||
}
|
||||
|
||||
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
|
||||
select {
|
||||
case <-errChan:
|
||||
cancel()
|
||||
os.Remove(socketPath)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
|
||||
log.Infof("\nReceived signal %v, shutting down...", sig)
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
@@ -235,7 +279,7 @@ func runShellInteractive(session bool) {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,13 +372,7 @@ func killShell() {
|
||||
|
||||
func runShellDaemon(session bool) {
|
||||
isSessionManaged = session
|
||||
isDaemonChild := false
|
||||
for _, arg := range os.Args {
|
||||
if arg == "--daemon-child" {
|
||||
isDaemonChild = true
|
||||
break
|
||||
}
|
||||
}
|
||||
isDaemonChild := slices.Contains(os.Args, "--daemon-child")
|
||||
|
||||
if !isDaemonChild {
|
||||
fmt.Fprintf(os.Stderr, "dms %s\n", Version)
|
||||
@@ -400,6 +438,16 @@ func runShellDaemon(session bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("QT_QPA_PLATFORMTHEME") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME=gtk3")
|
||||
}
|
||||
if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME_QT6=gtk3")
|
||||
}
|
||||
if os.Getenv("QT_QPA_PLATFORM") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland")
|
||||
}
|
||||
|
||||
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening /dev/null: %v", err)
|
||||
@@ -440,15 +488,28 @@ func runShellDaemon(session bool) {
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
// Handle SIGUSR1 restart for non-session managed processes
|
||||
if sig == syscall.SIGUSR1 && !isSessionManaged {
|
||||
if sig == syscall.SIGUSR1 {
|
||||
if isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, exiting for systemd restart...")
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Infof("Received SIGUSR1, spawning detached restart process...")
|
||||
execDetachedRestart(os.Getpid())
|
||||
// Exit immediately to avoid race conditions with detached restart
|
||||
return
|
||||
}
|
||||
|
||||
// All other signals: clean shutdown
|
||||
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
|
||||
select {
|
||||
case <-errChan:
|
||||
cancel()
|
||||
os.Remove(socketPath)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
@@ -460,7 +521,7 @@ func runShellDaemon(session bool) {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -468,9 +529,9 @@ func runShellDaemon(session bool) {
|
||||
func parseTargetsFromIPCShowOutput(output string) ipcTargets {
|
||||
targets := make(ipcTargets)
|
||||
var currentTarget string
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.HasPrefix(line, "target ") {
|
||||
currentTarget = strings.TrimSpace(strings.TrimPrefix(line, "target "))
|
||||
for line := range strings.SplitSeq(output, "\n") {
|
||||
if after, ok := strings.CutPrefix(line, "target "); ok {
|
||||
currentTarget = strings.TrimSpace(after)
|
||||
targets[currentTarget] = make(map[string][]string)
|
||||
}
|
||||
if strings.HasPrefix(line, " function") && currentTarget != "" {
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -36,13 +37,7 @@ func checkSystemdServiceEnabled(serviceName string) (string, bool, error) {
|
||||
|
||||
if err != nil {
|
||||
knownStates := []string{"disabled", "masked", "masked-runtime", "not-found", "enabled", "enabled-runtime", "static", "indirect", "alias"}
|
||||
isKnownState := false
|
||||
for _, known := range knownStates {
|
||||
if stateStr == known {
|
||||
isKnownState = true
|
||||
break
|
||||
}
|
||||
}
|
||||
isKnownState := slices.Contains(knownStates, stateStr)
|
||||
|
||||
if !isKnownState {
|
||||
return stateStr, false, fmt.Errorf("systemctl is-enabled failed: %w (output: %s)", err, stateStr)
|
||||
|
||||
@@ -221,10 +221,7 @@ func (p *Picker) handleGlobal(e client.RegistryGlobalEvent) {
|
||||
|
||||
case client.OutputInterfaceName:
|
||||
output := client.NewOutput(p.ctx)
|
||||
version := e.Version
|
||||
if version > 4 {
|
||||
version = 4
|
||||
}
|
||||
version := min(e.Version, 4)
|
||||
if err := p.registry.Bind(e.Name, e.Interface, version, output); err == nil {
|
||||
p.outputsMu.Lock()
|
||||
p.outputs[e.Name] = &Output{
|
||||
@@ -239,20 +236,14 @@ func (p *Picker) handleGlobal(e client.RegistryGlobalEvent) {
|
||||
|
||||
case wlr_layer_shell.ZwlrLayerShellV1InterfaceName:
|
||||
layerShell := wlr_layer_shell.NewZwlrLayerShellV1(p.ctx)
|
||||
version := e.Version
|
||||
if version > 4 {
|
||||
version = 4
|
||||
}
|
||||
version := min(e.Version, 4)
|
||||
if err := p.registry.Bind(e.Name, e.Interface, version, layerShell); err == nil {
|
||||
p.layerShell = layerShell
|
||||
}
|
||||
|
||||
case wlr_screencopy.ZwlrScreencopyManagerV1InterfaceName:
|
||||
screencopy := wlr_screencopy.NewZwlrScreencopyManagerV1(p.ctx)
|
||||
version := e.Version
|
||||
if version > 3 {
|
||||
version = 3
|
||||
}
|
||||
version := min(e.Version, 3)
|
||||
if err := p.registry.Bind(e.Name, e.Interface, version, screencopy); err == nil {
|
||||
p.screencopy = screencopy
|
||||
}
|
||||
|
||||
@@ -1157,7 +1157,7 @@ func drawGlyph(data []byte, stride, width, height, x, y int, r rune, col Color,
|
||||
rOff, bOff = 2, 0
|
||||
}
|
||||
|
||||
for row := 0; row < fontH; row++ {
|
||||
for row := range fontH {
|
||||
yy := y + row
|
||||
if yy < 0 || yy >= height {
|
||||
continue
|
||||
@@ -1165,7 +1165,7 @@ func drawGlyph(data []byte, stride, width, height, x, y int, r rune, col Color,
|
||||
rowPattern := g[row]
|
||||
dstRowOff := yy * stride
|
||||
|
||||
for colIdx := 0; colIdx < fontW; colIdx++ {
|
||||
for colIdx := range fontW {
|
||||
if (rowPattern & (1 << (fontW - 1 - colIdx))) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ func TestSurfaceState_ConcurrentPointerMotion(t *testing.T) {
|
||||
const goroutines = 50
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
for i := range goroutines {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for j := range iterations {
|
||||
s.OnPointerMotion(float64(id*10+j), float64(id*10+j))
|
||||
}
|
||||
}(i)
|
||||
@@ -34,21 +34,21 @@ func TestSurfaceState_ConcurrentScaleAccess(t *testing.T) {
|
||||
const goroutines = 30
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
for i := range goroutines / 2 {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
s.SetScale(int32(id%3 + 1))
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
for range goroutines / 2 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
scale := s.Scale()
|
||||
assert.GreaterOrEqual(t, scale, int32(1))
|
||||
}
|
||||
@@ -65,21 +65,21 @@ func TestSurfaceState_ConcurrentLogicalSize(t *testing.T) {
|
||||
const goroutines = 20
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
for i := range goroutines / 2 {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for j := range iterations {
|
||||
_ = s.OnLayerConfigure(1920+id, 1080+j)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
for range goroutines / 2 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
w, h := s.LogicalSize()
|
||||
_ = w
|
||||
_ = h
|
||||
@@ -97,31 +97,31 @@ func TestSurfaceState_ConcurrentIsDone(t *testing.T) {
|
||||
const goroutines = 30
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/3; i++ {
|
||||
for range goroutines / 3 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
s.OnPointerButton(0x110, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/3; i++ {
|
||||
for range goroutines / 3 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
s.OnKey(1, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/3; i++ {
|
||||
for range goroutines / 3 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
picked, cancelled := s.IsDone()
|
||||
_ = picked
|
||||
_ = cancelled
|
||||
@@ -139,11 +139,11 @@ func TestSurfaceState_ConcurrentIsReady(t *testing.T) {
|
||||
const goroutines = 20
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
for range goroutines {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
_ = s.IsReady()
|
||||
}
|
||||
}()
|
||||
@@ -159,11 +159,11 @@ func TestSurfaceState_ConcurrentSwapBuffers(t *testing.T) {
|
||||
const goroutines = 20
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
for range goroutines {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
s.SwapBuffers()
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -213,6 +213,11 @@ func (cd *ConfigDeployer) deployNiriDmsConfigs(dmsDir, terminalCommand string) e
|
||||
|
||||
for _, cfg := range configs {
|
||||
path := filepath.Join(dmsDir, cfg.name)
|
||||
// Skip if file already exists to preserve user modifications
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
cd.log(fmt.Sprintf("Skipping %s (already exists)", cfg.name))
|
||||
continue
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(cfg.content), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write %s: %w", cfg.name, err)
|
||||
}
|
||||
@@ -265,7 +270,13 @@ func (cd *ConfigDeployer) deployGhosttyConfig() ([]DeploymentResult, error) {
|
||||
|
||||
colorResult := DeploymentResult{
|
||||
ConfigType: "Ghostty Colors",
|
||||
Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config-dankcolors"),
|
||||
Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "themes", "dankcolors"),
|
||||
}
|
||||
|
||||
themesDir := filepath.Dir(colorResult.Path)
|
||||
if err := os.MkdirAll(themesDir, 0755); err != nil {
|
||||
mainResult.Error = fmt.Errorf("failed to create themes directory: %w", err)
|
||||
return []DeploymentResult{mainResult}, mainResult.Error
|
||||
}
|
||||
|
||||
if err := os.WriteFile(colorResult.Path, []byte(GhosttyColorConfig), 0644); err != nil {
|
||||
|
||||
@@ -462,13 +462,13 @@ func TestHyprlandConfigStructure(t *testing.T) {
|
||||
assert.Contains(t, HyprlandConfig, "# KEYBINDINGS")
|
||||
assert.Contains(t, HyprlandConfig, "bind = $mod, T, exec, {{TERMINAL_COMMAND}}")
|
||||
assert.Contains(t, HyprlandConfig, "bind = $mod, space, exec, dms ipc call spotlight toggle")
|
||||
assert.Contains(t, HyprlandConfig, "windowrulev2 = noborder, class:^(com\\.mitchellh\\.ghostty)$")
|
||||
assert.Contains(t, HyprlandConfig, "windowrule = border_size 0, match:class ^(com\\.mitchellh\\.ghostty)$")
|
||||
}
|
||||
|
||||
func TestGhosttyConfigStructure(t *testing.T) {
|
||||
assert.Contains(t, GhosttyConfig, "window-decoration = false")
|
||||
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
|
||||
assert.Contains(t, GhosttyConfig, "config-file = ./config-dankcolors")
|
||||
assert.Contains(t, GhosttyConfig, "theme = dankcolors")
|
||||
}
|
||||
|
||||
func TestGhosttyColorConfigStructure(t *testing.T) {
|
||||
|
||||
@@ -21,7 +21,7 @@ func LocateDMSConfig() (string, error) {
|
||||
dataDirs = "/usr/local/share:/usr/share"
|
||||
}
|
||||
|
||||
for _, dir := range strings.Split(dataDirs, ":") {
|
||||
for dir := range strings.SplitSeq(dataDirs, ":") {
|
||||
if dir != "" {
|
||||
primaryPaths = append(primaryPaths, filepath.Join(dir, "quickshell", "dms"))
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func LocateDMSConfig() (string, error) {
|
||||
configDirs = "/etc/xdg"
|
||||
}
|
||||
|
||||
for _, dir := range strings.Split(configDirs, ":") {
|
||||
for dir := range strings.SplitSeq(configDirs, ":") {
|
||||
if dir != "" {
|
||||
primaryPaths = append(primaryPaths, filepath.Join(dir, "quickshell", "dms"))
|
||||
}
|
||||
|
||||
@@ -48,4 +48,4 @@ keybind = shift+enter=text:\n
|
||||
gtk-single-instance = true
|
||||
|
||||
# Dank color generation
|
||||
config-file = ./config-dankcolors
|
||||
theme = dankcolors
|
||||
|
||||
@@ -90,36 +90,36 @@ misc {
|
||||
# ==================
|
||||
# WINDOW RULES
|
||||
# ==================
|
||||
windowrulev2 = tile, class:^(org\.wezfurlong\.wezterm)$
|
||||
windowrule = tile on, match:class ^(org\.wezfurlong\.wezterm)$
|
||||
|
||||
windowrulev2 = rounding 12, class:^(org\.gnome\.)
|
||||
windowrulev2 = noborder, class:^(org\.gnome\.)
|
||||
windowrule = rounding 12, match:class ^(org\.gnome\.)
|
||||
windowrule = border_size 0, match:class ^(org\.gnome\.)
|
||||
|
||||
windowrulev2 = tile, class:^(gnome-control-center)$
|
||||
windowrulev2 = tile, class:^(pavucontrol)$
|
||||
windowrulev2 = tile, class:^(nm-connection-editor)$
|
||||
windowrule = tile on, match:class ^(gnome-control-center)$
|
||||
windowrule = tile on, match:class ^(pavucontrol)$
|
||||
windowrule = tile on, match:class ^(nm-connection-editor)$
|
||||
|
||||
windowrulev2 = float, class:^(gnome-calculator)$
|
||||
windowrulev2 = float, class:^(galculator)$
|
||||
windowrulev2 = float, class:^(blueman-manager)$
|
||||
windowrulev2 = float, class:^(org\.gnome\.Nautilus)$
|
||||
windowrulev2 = float, class:^(steam)$
|
||||
windowrulev2 = float, class:^(xdg-desktop-portal)$
|
||||
windowrule = float on, match:class ^(gnome-calculator)$
|
||||
windowrule = float on, match:class ^(galculator)$
|
||||
windowrule = float on, match:class ^(blueman-manager)$
|
||||
windowrule = float on, match:class ^(org\.gnome\.Nautilus)$
|
||||
windowrule = float on, match:class ^(steam)$
|
||||
windowrule = float on, match:class ^(xdg-desktop-portal)$
|
||||
|
||||
windowrulev2 = noborder, class:^(org\.wezfurlong\.wezterm)$
|
||||
windowrulev2 = noborder, class:^(Alacritty)$
|
||||
windowrulev2 = noborder, class:^(zen)$
|
||||
windowrulev2 = noborder, class:^(com\.mitchellh\.ghostty)$
|
||||
windowrulev2 = noborder, class:^(kitty)$
|
||||
windowrule = border_size 0, match:class ^(org\.wezfurlong\.wezterm)$
|
||||
windowrule = border_size 0, match:class ^(Alacritty)$
|
||||
windowrule = border_size 0, match:class ^(zen)$
|
||||
windowrule = border_size 0, match:class ^(com\.mitchellh\.ghostty)$
|
||||
windowrule = border_size 0, match:class ^(kitty)$
|
||||
|
||||
windowrulev2 = float, class:^(firefox)$, title:^(Picture-in-Picture)$
|
||||
windowrulev2 = float, class:^(zoom)$
|
||||
windowrule = float on, match:class ^(firefox)$, match:title ^(Picture-in-Picture)$
|
||||
windowrule = float on, match:class ^(zoom)$
|
||||
|
||||
# DMS windows floating by default
|
||||
windowrulev2 = float, class:^(org.quickshell)$
|
||||
windowrulev2 = opacity 0.9 0.9, floating:0, focus:0
|
||||
windowrule = float on, match:class ^(org.quickshell)$
|
||||
windowrule = opacity 0.9 0.9, match:float false, match:focus false
|
||||
|
||||
layerrule = noanim, ^(quickshell)$
|
||||
layerrule = no_anim on, match:namespace ^(quickshell)$
|
||||
|
||||
# ==================
|
||||
# KEYBINDINGS
|
||||
|
||||
@@ -345,7 +345,7 @@ func EnsureContrastDPSLstar(hexColor, hexBg string, minLc float64, isLightMode b
|
||||
}
|
||||
|
||||
step := 0.5
|
||||
for i := 0; i < 120; i++ {
|
||||
for range 120 {
|
||||
Lf = math.Max(0, math.Min(100, Lf+dir*step))
|
||||
cand := labToHex(Lf, af, bf)
|
||||
if DeltaPhiStarContrast(cand, hexBg, isLightMode) >= minLc {
|
||||
|
||||
@@ -658,7 +658,7 @@ func TestContrastAlgorithmComparison(t *testing.T) {
|
||||
}
|
||||
|
||||
differentCount := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
if wcagColors[i].Hex != dpsColors[i].Hex {
|
||||
differentCount++
|
||||
}
|
||||
|
||||
@@ -112,3 +112,24 @@ func GenerateWeztermTheme(p Palette) string {
|
||||
p.Color12.Hex, p.Color13.Hex, p.Color14.Hex, p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateNeovimTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_0 = \"%s\"\n", p.Color0.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_1 = \"%s\"\n", p.Color1.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_2 = \"%s\"\n", p.Color2.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_3 = \"%s\"\n", p.Color3.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_4 = \"%s\"\n", p.Color4.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_5 = \"%s\"\n", p.Color5.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_6 = \"%s\"\n", p.Color6.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_7 = \"%s\"\n", p.Color7.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_8 = \"%s\"\n", p.Color8.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_9 = \"%s\"\n", p.Color9.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_10 = \"%s\"\n", p.Color10.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_11 = \"%s\"\n", p.Color11.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_12 = \"%s\"\n", p.Color12.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_13 = \"%s\"\n", p.Color13.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_14 = \"%s\"\n", p.Color14.Hex)
|
||||
fmt.Fprintf(&result, "vim.g.terminal_color_15 = \"%s\"\n", p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||
@@ -514,12 +515,9 @@ func (a *ArchDistribution) reorderAURPackages(packages []string) []string {
|
||||
dmsShell = append(dmsShell, pkg)
|
||||
} else {
|
||||
isDep := false
|
||||
for _, dep := range dmsDepencies {
|
||||
if pkg == dep {
|
||||
deps = append(deps, pkg)
|
||||
isDep = true
|
||||
break
|
||||
}
|
||||
if slices.Contains(dmsDepencies, pkg) {
|
||||
deps = append(deps, pkg)
|
||||
isDep = true
|
||||
}
|
||||
if !isDep {
|
||||
others = append(others, pkg)
|
||||
@@ -545,7 +543,7 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
a.log(fmt.Sprintf("Warning: failed to clean existing cache for %s: %v", pkg, err))
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(buildDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(buildDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create build directory: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -550,10 +550,7 @@ func (b *BaseDistribution) WriteEnvironmentConfig(terminal deps.Terminal) error
|
||||
terminalCmd = "ghostty"
|
||||
}
|
||||
|
||||
content := fmt.Sprintf(`QT_QPA_PLATFORM=wayland
|
||||
ELECTRON_OZONE_PLATFORM_HINT=auto
|
||||
QT_QPA_PLATFORMTHEME=gtk3
|
||||
QT_QPA_PLATFORMTHEME_QT6=gtk3
|
||||
content := fmt.Sprintf(`ELECTRON_OZONE_PLATFORM_HINT=auto
|
||||
TERMINAL=%s
|
||||
`, terminalCmd)
|
||||
|
||||
@@ -567,12 +564,6 @@ TERMINAL=%s
|
||||
}
|
||||
|
||||
func (b *BaseDistribution) EnableDMSService(ctx context.Context, wm deps.WindowManager) error {
|
||||
cmd := exec.CommandContext(ctx, "systemctl", "--user", "enable", "--now", "dms")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to enable dms service: %w", err)
|
||||
}
|
||||
b.log("Enabled dms systemd user service")
|
||||
|
||||
switch wm {
|
||||
case deps.WindowManagerNiri:
|
||||
if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "niri.service", "dms").Run(); err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||
@@ -384,6 +385,8 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
||||
debianVersion := "Debian_13"
|
||||
if osInfo.VersionID == "testing" {
|
||||
debianVersion = "Debian_Testing"
|
||||
} else if osInfo.VersionCodename == "sid" || osInfo.VersionID == "sid" || strings.Contains(strings.ToLower(osInfo.PrettyName), "sid") || strings.Contains(strings.ToLower(osInfo.PrettyName), "unstable") {
|
||||
debianVersion = "Debian_Unstable"
|
||||
}
|
||||
|
||||
for _, pkg := range obsPkgs {
|
||||
@@ -427,7 +430,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
||||
}
|
||||
|
||||
// Add repository
|
||||
repoLine := fmt.Sprintf("deb [signed-by=%s] %s/ /", keyringPath, baseURL)
|
||||
repoLine := fmt.Sprintf("deb [signed-by=%s, arch=%s] %s/ /", keyringPath, runtime.GOARCH, baseURL)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
|
||||
@@ -18,8 +18,8 @@ type ManualPackageInstaller struct {
|
||||
|
||||
// parseLatestTagFromGitOutput parses git ls-remote output and returns the latest tag
|
||||
func (m *ManualPackageInstaller) parseLatestTagFromGitOutput(output string) string {
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
lines := strings.SplitSeq(output, "\n")
|
||||
for line := range lines {
|
||||
if strings.Contains(line, "refs/tags/") && !strings.Contains(line, "^{}") {
|
||||
parts := strings.Split(line, "refs/tags/")
|
||||
if len(parts) > 1 {
|
||||
@@ -103,12 +103,12 @@ func (m *ManualPackageInstaller) installDgop(ctx context.Context, sudoPassword s
|
||||
}
|
||||
|
||||
cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
|
||||
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
tmpDir := filepath.Join(cacheDir, "dgop-build")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
@@ -160,10 +160,10 @@ func (m *ManualPackageInstaller) installNiri(ctx context.Context, sudoPassword s
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
buildDir := filepath.Join(homeDir, ".cache", "dankinstall", "niri-build")
|
||||
tmpDir := filepath.Join(homeDir, ".cache", "dankinstall", "tmp")
|
||||
if err := os.MkdirAll(buildDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(buildDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create build directory: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
@@ -237,12 +237,12 @@ func (m *ManualPackageInstaller) installQuickshell(ctx context.Context, variant
|
||||
}
|
||||
|
||||
cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
|
||||
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
tmpDir := filepath.Join(cacheDir, "quickshell-build")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
@@ -273,7 +273,7 @@ func (m *ManualPackageInstaller) installQuickshell(ctx context.Context, variant
|
||||
}
|
||||
|
||||
buildDir := tmpDir + "/build"
|
||||
if err := os.MkdirAll(buildDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(buildDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create build directory: %w", err)
|
||||
}
|
||||
|
||||
@@ -343,12 +343,12 @@ func (m *ManualPackageInstaller) installHyprland(ctx context.Context, sudoPasswo
|
||||
}
|
||||
|
||||
cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
|
||||
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
tmpDir := filepath.Join(cacheDir, "hyprland-build")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
@@ -406,12 +406,12 @@ func (m *ManualPackageInstaller) installGhostty(ctx context.Context, sudoPasswor
|
||||
}
|
||||
|
||||
cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
|
||||
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
tmpDir := filepath.Join(cacheDir, "ghostty-build")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
@@ -528,7 +528,7 @@ func (m *ManualPackageInstaller) installDankMaterialShell(ctx context.Context, v
|
||||
}
|
||||
|
||||
configDir := filepath.Dir(dmsPath)
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(configDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create quickshell config directory: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ func init() {
|
||||
Register("opensuse-tumbleweed", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewOpenSUSEDistribution(config, logChan)
|
||||
})
|
||||
Register("opensuse-leap", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewOpenSUSEDistribution(config, logChan)
|
||||
})
|
||||
Register("opensuse-slowroll", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewOpenSUSEDistribution(config, logChan)
|
||||
})
|
||||
}
|
||||
|
||||
type OpenSUSEDistribution struct {
|
||||
@@ -434,6 +440,19 @@ func (o *OpenSUSEDistribution) extractPackageNames(packages []PackageMapping) []
|
||||
func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
enabledRepos := make(map[string]bool)
|
||||
|
||||
osInfo, err := GetOSInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get OS info: %w", err)
|
||||
}
|
||||
|
||||
obsDistroVersion := "openSUSE_Tumbleweed"
|
||||
switch osInfo.Distribution.ID {
|
||||
case "opensuse-leap":
|
||||
obsDistroVersion = fmt.Sprintf("openSUSE_Leap_%s", osInfo.VersionID)
|
||||
case "opensuse-slowroll":
|
||||
obsDistroVersion = "openSUSE_Slowroll"
|
||||
}
|
||||
|
||||
for _, pkg := range obsPkgs {
|
||||
if pkg.RepoURL != "" && !enabledRepos[pkg.RepoURL] {
|
||||
o.log(fmt.Sprintf("Enabling OBS repository: %s", pkg.RepoURL))
|
||||
@@ -441,8 +460,8 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
|
||||
// RepoURL format: "home:AvengeMedia:danklinux"
|
||||
repoPath := strings.ReplaceAll(pkg.RepoURL, ":", ":/")
|
||||
repoName := strings.ReplaceAll(pkg.RepoURL, ":", "-")
|
||||
repoURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/openSUSE_Tumbleweed/%s.repo",
|
||||
repoPath, pkg.RepoURL)
|
||||
repoURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/%s/%s.repo",
|
||||
repoPath, obsDistroVersion, pkg.RepoURL)
|
||||
|
||||
checkCmd := exec.CommandContext(ctx, "zypper", "repos", repoName)
|
||||
if checkCmd.Run() == nil {
|
||||
|
||||
@@ -19,11 +19,12 @@ type DistroInfo struct {
|
||||
|
||||
// OSInfo contains complete OS information
|
||||
type OSInfo struct {
|
||||
Distribution DistroInfo
|
||||
Version string
|
||||
VersionID string
|
||||
PrettyName string
|
||||
Architecture string
|
||||
Distribution DistroInfo
|
||||
Version string
|
||||
VersionID string
|
||||
VersionCodename string
|
||||
PrettyName string
|
||||
Architecture string
|
||||
}
|
||||
|
||||
// GetOSInfo detects the current OS and returns information about it
|
||||
@@ -72,6 +73,8 @@ func GetOSInfo() (*OSInfo, error) {
|
||||
info.VersionID = value
|
||||
case "VERSION":
|
||||
info.Version = value
|
||||
case "VERSION_CODENAME":
|
||||
info.VersionCodename = value
|
||||
case "PRETTY_NAME":
|
||||
info.PrettyName = value
|
||||
}
|
||||
@@ -100,6 +103,10 @@ func IsUnsupportedDistro(distroID, versionID string) bool {
|
||||
}
|
||||
|
||||
if distroID == "debian" {
|
||||
// unstable/sid support
|
||||
if versionID == "sid" {
|
||||
return false
|
||||
}
|
||||
if versionID == "" {
|
||||
// debian testing/sid have no version ID
|
||||
return false
|
||||
|
||||
@@ -23,7 +23,7 @@ func DefaultDiscoveryConfig() *DiscoveryConfig {
|
||||
|
||||
configDirs := os.Getenv("XDG_CONFIG_DIRS")
|
||||
if configDirs != "" {
|
||||
for _, dir := range strings.Split(configDirs, ":") {
|
||||
for dir := range strings.SplitSeq(configDirs, ":") {
|
||||
if dir != "" {
|
||||
searchPaths = append(searchPaths, filepath.Join(dir, "DankMaterialShell", "cheatsheets"))
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestNewJSONFileProvider(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test.json")
|
||||
|
||||
if err := os.WriteFile(testFile, []byte("{}"), 0644); err != nil {
|
||||
if err := os.WriteFile(testFile, []byte("{}"), 0o644); err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestJSONFileProviderGetCheatSheet(t *testing.T) {
|
||||
}
|
||||
}`
|
||||
|
||||
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
|
||||
if err := os.WriteFile(testFile, []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("Failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ func TestJSONFileProviderGetCheatSheetNoProvider(t *testing.T) {
|
||||
"binds": {}
|
||||
}`
|
||||
|
||||
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
|
||||
if err := os.WriteFile(testFile, []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("Failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func TestJSONFileProviderFlatArrayBackwardsCompat(t *testing.T) {
|
||||
]
|
||||
}`
|
||||
|
||||
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
|
||||
if err := os.WriteFile(testFile, []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("Failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ func TestJSONFileProviderInvalidJSON(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "invalid.json")
|
||||
|
||||
if err := os.WriteFile(testFile, []byte("not valid json"), 0644); err != nil {
|
||||
if err := os.WriteFile(testFile, []byte("not valid json"), 0o644); err != nil {
|
||||
t.Fatalf("Failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -234,53 +234,61 @@ output_path = '%s'
|
||||
if !opts.ShouldSkipTemplate("gtk") {
|
||||
switch opts.Mode {
|
||||
case "light":
|
||||
appendConfig(opts, cfgFile, "skip", "gtk3-light.toml")
|
||||
appendConfig(opts, cfgFile, nil, "gtk3-light.toml")
|
||||
default:
|
||||
appendConfig(opts, cfgFile, "skip", "gtk3-dark.toml")
|
||||
appendConfig(opts, cfgFile, nil, "gtk3-dark.toml")
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("niri") {
|
||||
appendConfig(opts, cfgFile, "niri", "niri.toml")
|
||||
appendConfig(opts, cfgFile, []string{"niri"}, "niri.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("qt5ct") {
|
||||
appendConfig(opts, cfgFile, "qt5ct", "qt5ct.toml")
|
||||
appendConfig(opts, cfgFile, []string{"qt5ct"}, "qt5ct.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("qt6ct") {
|
||||
appendConfig(opts, cfgFile, "qt6ct", "qt6ct.toml")
|
||||
appendConfig(opts, cfgFile, []string{"qt6ct"}, "qt6ct.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("firefox") {
|
||||
appendConfig(opts, cfgFile, "firefox", "firefox.toml")
|
||||
appendConfig(opts, cfgFile, []string{"firefox"}, "firefox.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("pywalfox") {
|
||||
appendConfig(opts, cfgFile, "pywalfox", "pywalfox.toml")
|
||||
appendConfig(opts, cfgFile, []string{"pywalfox"}, "pywalfox.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("zenbrowser") {
|
||||
appendConfig(opts, cfgFile, []string{"zen", "zen-browser"}, "zenbrowser.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("vesktop") {
|
||||
appendConfig(opts, cfgFile, "vesktop", "vesktop.toml")
|
||||
appendConfig(opts, cfgFile, []string{"vesktop"}, "vesktop.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("equibop") {
|
||||
appendConfig(opts, cfgFile, []string{"equibop"}, "equibop.toml")
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("ghostty") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, "ghostty", "ghostty.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"ghostty"}, "ghostty.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("kitty") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, "kitty", "kitty.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"kitty"}, "kitty.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("foot") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, "foot", "foot.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"foot"}, "foot.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("alacritty") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, "alacritty", "alacritty.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"alacritty"}, "alacritty.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("wezterm") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, "wezterm", "wezterm.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"wezterm"}, "wezterm.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("nvim") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"nvim"}, "neovim.toml")
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("dgop") {
|
||||
appendConfig(opts, cfgFile, "dgop", "dgop.toml")
|
||||
appendConfig(opts, cfgFile, []string{"dgop"}, "dgop.toml")
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("kcolorscheme") {
|
||||
appendConfig(opts, cfgFile, "skip", "kcolorscheme.toml")
|
||||
appendConfig(opts, cfgFile, nil, "kcolorscheme.toml")
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("vscode") {
|
||||
@@ -318,12 +326,12 @@ output_path = '%s'
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendConfig(opts *Options, cfgFile *os.File, checkCmd, fileName string) {
|
||||
func appendConfig(opts *Options, cfgFile *os.File, checkCmd []string, fileName string) {
|
||||
configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
return
|
||||
}
|
||||
if checkCmd != "skip" && !utils.CommandExists(checkCmd) {
|
||||
if len(checkCmd) > 0 && !utils.AnyCommandExists(checkCmd...) {
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(configPath)
|
||||
@@ -334,12 +342,12 @@ func appendConfig(opts *Options, cfgFile *os.File, checkCmd, fileName string) {
|
||||
cfgFile.WriteString("\n")
|
||||
}
|
||||
|
||||
func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir, checkCmd, fileName string) {
|
||||
func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir string, checkCmd []string, fileName string) {
|
||||
configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
return
|
||||
}
|
||||
if checkCmd != "skip" && !utils.CommandExists(checkCmd) {
|
||||
if len(checkCmd) > 0 && !utils.AnyCommandExists(checkCmd...) {
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(configPath)
|
||||
|
||||
@@ -238,7 +238,7 @@ func (i *ZwlrOutputManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
l := 0
|
||||
objectID := client.Uint32(data[l : l+4])
|
||||
proxy := i.Context().GetProxy(objectID)
|
||||
if proxy == nil {
|
||||
if proxy == nil || proxy.IsZombie() {
|
||||
head := &ZwlrOutputHeadV1{}
|
||||
head.SetContext(i.Context())
|
||||
head.SetID(objectID)
|
||||
@@ -723,7 +723,7 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
l := 0
|
||||
objectID := client.Uint32(data[l : l+4])
|
||||
proxy := i.Context().GetProxy(objectID)
|
||||
if proxy == nil {
|
||||
if proxy == nil || proxy.IsZombie() {
|
||||
mode := &ZwlrOutputModeV1{}
|
||||
mode.SetContext(i.Context())
|
||||
mode.SetID(objectID)
|
||||
@@ -761,8 +761,8 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
l := 0
|
||||
objectID := client.Uint32(data[l : l+4])
|
||||
proxy := i.Context().GetProxy(objectID)
|
||||
if proxy == nil {
|
||||
// Mode not yet registered, create it
|
||||
if proxy == nil || proxy.IsZombie() {
|
||||
// Mode not yet registered or zombie, create fresh
|
||||
mode := &ZwlrOutputModeV1{}
|
||||
mode.SetContext(i.Context())
|
||||
mode.SetID(objectID)
|
||||
|
||||
@@ -141,7 +141,7 @@ func (r *RegionSelector) setupKeyboardHandlers() {
|
||||
for _, os := range r.surfaces {
|
||||
r.redrawSurface(os)
|
||||
}
|
||||
case 28, 57:
|
||||
case 28, 57, 96:
|
||||
if r.selection.hasSelection {
|
||||
r.finishSelection()
|
||||
}
|
||||
|
||||
@@ -208,9 +208,6 @@ func handleSetConfig(conn net.Conn, req models.Request, m *Manager) {
|
||||
if v, ok := req.Params["disableHistory"].(bool); ok {
|
||||
cfg.DisableHistory = v
|
||||
}
|
||||
if v, ok := req.Params["disablePersist"].(bool); ok {
|
||||
cfg.DisablePersist = v
|
||||
}
|
||||
|
||||
if err := m.SetConfig(cfg); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
|
||||
@@ -11,14 +11,16 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hash/fnv"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/tiff"
|
||||
"hash/fnv"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
|
||||
@@ -28,6 +30,11 @@ import (
|
||||
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
)
|
||||
|
||||
// These mime types wont be stored in history
|
||||
var sensitiveMimeTypes = []string{
|
||||
"x-kde-passwordManagerHint",
|
||||
}
|
||||
|
||||
func NewManager(wlCtx wlcontext.WaylandContext, config Config) (*Manager, error) {
|
||||
if config.Disabled {
|
||||
return nil, fmt.Errorf("clipboard disabled in config")
|
||||
@@ -253,6 +260,10 @@ func (m *Manager) setupDataDeviceSync() {
|
||||
return
|
||||
}
|
||||
|
||||
if m.hasSensitiveMimeType(mimes) {
|
||||
return
|
||||
}
|
||||
|
||||
preferredMime := m.selectMimeType(mimes)
|
||||
if preferredMime == "" {
|
||||
return
|
||||
@@ -319,10 +330,6 @@ func (m *Manager) readAndStore(r *os.File, mimeType string) {
|
||||
m.storeClipboardEntry(data, mimeType)
|
||||
}
|
||||
|
||||
if !cfg.DisablePersist {
|
||||
m.persistClipboard([]string{mimeType}, map[string][]byte{mimeType: data})
|
||||
}
|
||||
|
||||
m.updateState()
|
||||
m.notifySubscribers()
|
||||
}
|
||||
@@ -348,105 +355,6 @@ func (m *Manager) storeClipboardEntry(data []byte, mimeType string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) persistClipboard(mimeTypes []string, data map[string][]byte) {
|
||||
m.persistMutex.Lock()
|
||||
m.persistMimeTypes = mimeTypes
|
||||
m.persistData = data
|
||||
m.persistMutex.Unlock()
|
||||
|
||||
m.post(func() {
|
||||
m.takePersistOwnership()
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Manager) takePersistOwnership() {
|
||||
if m.dataControlMgr == nil || m.dataDevice == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if m.getConfig().DisablePersist {
|
||||
return
|
||||
}
|
||||
|
||||
m.persistMutex.RLock()
|
||||
mimeTypes := m.persistMimeTypes
|
||||
m.persistMutex.RUnlock()
|
||||
|
||||
if len(mimeTypes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
|
||||
|
||||
source, err := dataMgr.CreateDataSource()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create persist source: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, mime := range mimeTypes {
|
||||
if err := source.Offer(mime); err != nil {
|
||||
log.Errorf("Failed to offer mime type %s: %v", mime, err)
|
||||
}
|
||||
}
|
||||
|
||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||
fd := e.Fd
|
||||
defer syscall.Close(fd)
|
||||
|
||||
m.persistMutex.RLock()
|
||||
d := m.persistData[e.MimeType]
|
||||
m.persistMutex.RUnlock()
|
||||
|
||||
if len(d) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "clipboard-pipe")
|
||||
defer file.Close()
|
||||
file.Write(d)
|
||||
})
|
||||
|
||||
source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) {
|
||||
m.ownerLock.Lock()
|
||||
m.isOwner = false
|
||||
m.ownerLock.Unlock()
|
||||
})
|
||||
|
||||
if m.currentSource != nil {
|
||||
oldSource := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||
oldSource.Destroy()
|
||||
}
|
||||
m.currentSource = source
|
||||
|
||||
device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1)
|
||||
if err := device.SetSelection(source); err != nil {
|
||||
log.Errorf("Failed to set persist selection: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
m.ownerLock.Lock()
|
||||
m.isOwner = true
|
||||
m.ownerLock.Unlock()
|
||||
}
|
||||
|
||||
func (m *Manager) releaseOwnership() {
|
||||
m.ownerLock.Lock()
|
||||
m.isOwner = false
|
||||
m.ownerLock.Unlock()
|
||||
|
||||
m.persistMutex.Lock()
|
||||
m.persistData = nil
|
||||
m.persistMimeTypes = nil
|
||||
m.persistMutex.Unlock()
|
||||
|
||||
if m.currentSource != nil {
|
||||
source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||
source.Destroy()
|
||||
m.currentSource = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) storeEntry(entry Entry) error {
|
||||
if m.db == nil {
|
||||
return fmt.Errorf("database not available")
|
||||
@@ -495,6 +403,9 @@ func (m *Manager) deduplicateInTx(b *bolt.Bucket, hash uint64) error {
|
||||
}
|
||||
|
||||
func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
|
||||
if m.config.MaxHistory < 0 {
|
||||
return nil
|
||||
}
|
||||
c := b.Cursor()
|
||||
var count int
|
||||
for k, _ := c.Last(); k != nil; k, _ = c.Prev() {
|
||||
@@ -592,6 +503,12 @@ func extractHash(data []byte) uint64 {
|
||||
return binary.BigEndian.Uint64(data[len(data)-8:])
|
||||
}
|
||||
|
||||
func (m *Manager) hasSensitiveMimeType(mimes []string) bool {
|
||||
return slices.ContainsFunc(mimes, func(mime string) bool {
|
||||
return slices.Contains(sensitiveMimeTypes, mime)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Manager) selectMimeType(mimes []string) string {
|
||||
preferredTypes := []string{
|
||||
"text/plain;charset=utf-8",
|
||||
@@ -1309,13 +1226,7 @@ func (m *Manager) applyConfigChange(newCfg Config) {
|
||||
}
|
||||
}
|
||||
|
||||
if newCfg.DisablePersist && !oldCfg.DisablePersist {
|
||||
log.Info("Clipboard persist disabled, releasing ownership")
|
||||
m.releaseOwnership()
|
||||
}
|
||||
|
||||
log.Infof("Clipboard config reloaded: disableHistory=%v disablePersist=%v",
|
||||
newCfg.DisableHistory, newCfg.DisablePersist)
|
||||
log.Infof("Clipboard config reloaded: disableHistory=%v", newCfg.DisableHistory)
|
||||
|
||||
m.updateState()
|
||||
m.notifySubscribers()
|
||||
|
||||
@@ -458,7 +458,6 @@ func TestDefaultConfig(t *testing.T) {
|
||||
assert.False(t, cfg.ClearAtStartup)
|
||||
assert.False(t, cfg.Disabled)
|
||||
assert.False(t, cfg.DisableHistory)
|
||||
assert.True(t, cfg.DisablePersist)
|
||||
}
|
||||
|
||||
func TestManager_PostDelegatesToWlContext(t *testing.T) {
|
||||
|
||||
@@ -21,7 +21,6 @@ type Config struct {
|
||||
|
||||
Disabled bool `json:"disabled"`
|
||||
DisableHistory bool `json:"disableHistory"`
|
||||
DisablePersist bool `json:"disablePersist"`
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
@@ -30,7 +29,6 @@ func DefaultConfig() Config {
|
||||
MaxEntrySize: 5 * 1024 * 1024,
|
||||
AutoClearDays: 0,
|
||||
ClearAtStartup: false,
|
||||
DisablePersist: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package cups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -156,9 +158,42 @@ func (m *Manager) PurgeJobs(printerName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func resolveIPFromURI(uri string) string {
|
||||
parsed, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
host := parsed.Hostname()
|
||||
if host == "" {
|
||||
return ""
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
return ip.String()
|
||||
}
|
||||
addrs, err := net.LookupIP(host)
|
||||
if err != nil || len(addrs) == 0 {
|
||||
return ""
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if v4 := addr.To4(); v4 != nil {
|
||||
return v4.String()
|
||||
}
|
||||
}
|
||||
return addrs[0].String()
|
||||
}
|
||||
|
||||
func (m *Manager) GetDevices() ([]Device, error) {
|
||||
if m.pkHelper != nil {
|
||||
return m.pkHelper.DevicesGet(10, 0, nil, nil)
|
||||
devices, err := m.pkHelper.DevicesGet(10, 0, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range devices {
|
||||
if devices[i].Class == "network" {
|
||||
devices[i].IP = resolveIPFromURI(devices[i].URI)
|
||||
}
|
||||
}
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
deviceAttrs, err := m.client.GetDevices()
|
||||
@@ -176,6 +211,9 @@ func (m *Manager) GetDevices() ([]Device, error) {
|
||||
ID: getStringAttr(attrs, "device-id"),
|
||||
Location: getStringAttr(attrs, "device-location"),
|
||||
}
|
||||
if device.Class == "network" {
|
||||
device.IP = resolveIPFromURI(uri)
|
||||
}
|
||||
devices = append(devices, device)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ type Device struct {
|
||||
MakeModel string `json:"makeModel"`
|
||||
ID string `json:"id"`
|
||||
Location string `json:"location"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
}
|
||||
|
||||
type PPD struct {
|
||||
|
||||
@@ -33,7 +33,7 @@ func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
|
||||
return nil, fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
var profiles []VPNProfile
|
||||
profiles := []VPNProfile{}
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
@@ -101,7 +101,7 @@ func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
|
||||
return nil, fmt.Errorf("failed to get active connections: %w", err)
|
||||
}
|
||||
|
||||
var active []VPNActive
|
||||
active := []VPNActive{}
|
||||
for _, activeConn := range activeConns {
|
||||
connType, err := activeConn.GetPropertyType()
|
||||
if err != nil {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
|
||||
serverPlugins "github.com/AvengeMedia/DankMaterialShell/core/internal/server/plugins"
|
||||
serverThemes "github.com/AvengeMedia/DankMaterialShell/core/internal/server/themes"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
|
||||
)
|
||||
@@ -37,6 +38,11 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(req.Method, "themes.") {
|
||||
serverThemes.HandleRequest(conn, req)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(req.Method, "loginctl.") {
|
||||
if loginctlManager == nil {
|
||||
models.RespondError(conn, req.ID, "loginctl manager not initialized")
|
||||
@@ -204,9 +210,6 @@ func handleClipboardSetConfig(conn net.Conn, req models.Request) {
|
||||
if v, ok := req.Params["disableHistory"].(bool); ok {
|
||||
cfg.DisableHistory = v
|
||||
}
|
||||
if v, ok := req.Params["disablePersist"].(bool); ok {
|
||||
cfg.DisablePersist = v
|
||||
}
|
||||
|
||||
if err := clipboard.SaveConfig(cfg); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
|
||||
27
core/internal/server/themes/handlers.go
Normal file
27
core/internal/server/themes/handlers.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
)
|
||||
|
||||
func HandleRequest(conn net.Conn, req models.Request) {
|
||||
switch req.Method {
|
||||
case "themes.list":
|
||||
HandleList(conn, req)
|
||||
case "themes.listInstalled":
|
||||
HandleListInstalled(conn, req)
|
||||
case "themes.install":
|
||||
HandleInstall(conn, req)
|
||||
case "themes.uninstall":
|
||||
HandleUninstall(conn, req)
|
||||
case "themes.update":
|
||||
HandleUpdate(conn, req)
|
||||
case "themes.search":
|
||||
HandleSearch(conn, req)
|
||||
default:
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
||||
}
|
||||
}
|
||||
52
core/internal/server/themes/install.go
Normal file
52
core/internal/server/themes/install.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
|
||||
)
|
||||
|
||||
func HandleInstall(conn net.Conn, req models.Request) {
|
||||
idOrName, ok := req.Params["name"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
registry, err := themes.NewRegistry()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
themeList, err := registry.List()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
theme := themes.FindByIDOrName(idOrName, themeList)
|
||||
if theme == nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("theme not found: %s", idOrName))
|
||||
return
|
||||
}
|
||||
|
||||
manager, err := themes.NewManager()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
registryThemeDir := registry.GetThemeDir(theme.SourceDir)
|
||||
if err := manager.Install(*theme, registryThemeDir); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to install theme: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, models.SuccessResult{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("theme installed: %s", theme.Name),
|
||||
})
|
||||
}
|
||||
54
core/internal/server/themes/list.go
Normal file
54
core/internal/server/themes/list.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
|
||||
)
|
||||
|
||||
func HandleList(conn net.Conn, req models.Request) {
|
||||
registry, err := themes.NewRegistry()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
themeList, err := registry.List()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
manager, err := themes.NewManager()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]ThemeInfo, len(themeList))
|
||||
for i, t := range themeList {
|
||||
installed, _ := manager.IsInstalled(t)
|
||||
info := ThemeInfo{
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Version: t.Version,
|
||||
Author: t.Author,
|
||||
Description: t.Description,
|
||||
PreviewPath: t.PreviewPath,
|
||||
SourceDir: t.SourceDir,
|
||||
Installed: installed,
|
||||
FirstParty: isFirstParty(t.Author),
|
||||
}
|
||||
addVariantsInfo(&info, t.Variants)
|
||||
result[i] = info
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, result)
|
||||
}
|
||||
|
||||
func isFirstParty(author string) bool {
|
||||
return strings.EqualFold(author, "Avenge Media") || strings.EqualFold(author, "AvengeMedia")
|
||||
}
|
||||
157
core/internal/server/themes/list_installed.go
Normal file
157
core/internal/server/themes/list_installed.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
|
||||
)
|
||||
|
||||
func addVariantsInfo(info *ThemeInfo, variants *themes.ThemeVariants) {
|
||||
if variants == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if variants.Type == "multi" {
|
||||
if len(variants.Flavors) == 0 && len(variants.Accents) == 0 {
|
||||
return
|
||||
}
|
||||
info.HasVariants = true
|
||||
info.Variants = &VariantsInfo{
|
||||
Type: "multi",
|
||||
Flavors: make([]FlavorInfo, len(variants.Flavors)),
|
||||
Accents: make([]AccentInfo, len(variants.Accents)),
|
||||
}
|
||||
if variants.Defaults != nil {
|
||||
info.Variants.Defaults = &MultiDefaults{
|
||||
Dark: variants.Defaults.Dark,
|
||||
Light: variants.Defaults.Light,
|
||||
}
|
||||
}
|
||||
for i, f := range variants.Flavors {
|
||||
mode := ""
|
||||
switch {
|
||||
case f.Dark.Primary != "" && f.Light.Primary != "":
|
||||
mode = "both"
|
||||
case f.Dark.Primary != "":
|
||||
mode = "dark"
|
||||
case f.Light.Primary != "":
|
||||
mode = "light"
|
||||
default:
|
||||
if f.Dark.Surface != "" {
|
||||
mode = "dark"
|
||||
} else if f.Light.Surface != "" {
|
||||
mode = "light"
|
||||
}
|
||||
}
|
||||
info.Variants.Flavors[i] = FlavorInfo{ID: f.ID, Name: f.Name, Mode: mode}
|
||||
}
|
||||
for i, a := range variants.Accents {
|
||||
color := ""
|
||||
if colors, ok := a.FlavorColors["mocha"]; ok && colors.Primary != "" {
|
||||
color = colors.Primary
|
||||
} else if colors, ok := a.FlavorColors["latte"]; ok && colors.Primary != "" {
|
||||
color = colors.Primary
|
||||
} else {
|
||||
for _, c := range a.FlavorColors {
|
||||
if c.Primary != "" {
|
||||
color = c.Primary
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
info.Variants.Accents[i] = AccentInfo{ID: a.ID, Name: a.Name, Color: color}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(variants.Options) == 0 {
|
||||
return
|
||||
}
|
||||
info.HasVariants = true
|
||||
info.Variants = &VariantsInfo{
|
||||
Default: variants.Default,
|
||||
Options: make([]VariantInfo, len(variants.Options)),
|
||||
}
|
||||
for i, v := range variants.Options {
|
||||
info.Variants.Options[i] = VariantInfo{ID: v.ID, Name: v.Name}
|
||||
}
|
||||
}
|
||||
|
||||
func HandleListInstalled(conn net.Conn, req models.Request) {
|
||||
manager, err := themes.NewManager()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
installedIDs, err := manager.ListInstalled()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list installed themes: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
registry, err := themes.NewRegistry()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
allThemes, err := registry.List()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
themeMap := make(map[string]themes.Theme)
|
||||
for _, t := range allThemes {
|
||||
themeMap[t.ID] = t
|
||||
}
|
||||
|
||||
result := make([]ThemeInfo, 0, len(installedIDs))
|
||||
for _, id := range installedIDs {
|
||||
if theme, ok := themeMap[id]; ok {
|
||||
hasUpdate := false
|
||||
if hasUpdates, err := manager.HasUpdates(id, theme); err == nil {
|
||||
hasUpdate = hasUpdates
|
||||
}
|
||||
|
||||
info := ThemeInfo{
|
||||
ID: theme.ID,
|
||||
Name: theme.Name,
|
||||
Version: theme.Version,
|
||||
Author: theme.Author,
|
||||
Description: theme.Description,
|
||||
SourceDir: id,
|
||||
FirstParty: isFirstParty(theme.Author),
|
||||
HasUpdate: hasUpdate,
|
||||
}
|
||||
addVariantsInfo(&info, theme.Variants)
|
||||
result = append(result, info)
|
||||
} else {
|
||||
installed, err := manager.GetInstalledTheme(id)
|
||||
if err != nil {
|
||||
result = append(result, ThemeInfo{
|
||||
ID: id,
|
||||
Name: id,
|
||||
SourceDir: id,
|
||||
})
|
||||
continue
|
||||
}
|
||||
info := ThemeInfo{
|
||||
ID: installed.ID,
|
||||
Name: installed.Name,
|
||||
Version: installed.Version,
|
||||
Author: installed.Author,
|
||||
Description: installed.Description,
|
||||
SourceDir: id,
|
||||
FirstParty: isFirstParty(installed.Author),
|
||||
}
|
||||
addVariantsInfo(&info, installed.Variants)
|
||||
result = append(result, info)
|
||||
}
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, result)
|
||||
}
|
||||
53
core/internal/server/themes/search.go
Normal file
53
core/internal/server/themes/search.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
|
||||
)
|
||||
|
||||
func HandleSearch(conn net.Conn, req models.Request) {
|
||||
query, ok := req.Params["query"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'query' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
registry, err := themes.NewRegistry()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
themeList, err := registry.List()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
searchResults := themes.FuzzySearch(query, themeList)
|
||||
|
||||
manager, err := themes.NewManager()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]ThemeInfo, len(searchResults))
|
||||
for i, t := range searchResults {
|
||||
installed, _ := manager.IsInstalled(t)
|
||||
result[i] = ThemeInfo{
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Version: t.Version,
|
||||
Author: t.Author,
|
||||
Description: t.Description,
|
||||
Installed: installed,
|
||||
FirstParty: isFirstParty(t.Author),
|
||||
}
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, result)
|
||||
}
|
||||
47
core/internal/server/themes/types.go
Normal file
47
core/internal/server/themes/types.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package themes
|
||||
|
||||
type VariantInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type FlavorInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
type AccentInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color,omitempty"`
|
||||
}
|
||||
|
||||
type MultiDefaults struct {
|
||||
Dark map[string]string `json:"dark,omitempty"`
|
||||
Light map[string]string `json:"light,omitempty"`
|
||||
}
|
||||
|
||||
type VariantsInfo struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
Defaults *MultiDefaults `json:"defaults,omitempty"`
|
||||
Options []VariantInfo `json:"options,omitempty"`
|
||||
Flavors []FlavorInfo `json:"flavors,omitempty"`
|
||||
Accents []AccentInfo `json:"accents,omitempty"`
|
||||
}
|
||||
|
||||
type ThemeInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
PreviewPath string `json:"previewPath,omitempty"`
|
||||
SourceDir string `json:"sourceDir,omitempty"`
|
||||
Installed bool `json:"installed,omitempty"`
|
||||
FirstParty bool `json:"firstParty,omitempty"`
|
||||
HasUpdate bool `json:"hasUpdate,omitempty"`
|
||||
HasVariants bool `json:"hasVariants,omitempty"`
|
||||
Variants *VariantsInfo `json:"variants,omitempty"`
|
||||
}
|
||||
63
core/internal/server/themes/uninstall.go
Normal file
63
core/internal/server/themes/uninstall.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
|
||||
)
|
||||
|
||||
func HandleUninstall(conn net.Conn, req models.Request) {
|
||||
idOrName, ok := req.Params["name"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
manager, err := themes.NewManager()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
registry, err := themes.NewRegistry()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
themeList, _ := registry.List()
|
||||
theme := themes.FindByIDOrName(idOrName, themeList)
|
||||
|
||||
if theme != nil {
|
||||
installed, err := manager.IsInstalled(*theme)
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if theme is installed: %v", err))
|
||||
return
|
||||
}
|
||||
if !installed {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("theme not installed: %s", idOrName))
|
||||
return
|
||||
}
|
||||
if err := manager.Uninstall(*theme); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall theme: %v", err))
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, models.SuccessResult{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("theme uninstalled: %s", theme.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.UninstallByID(idOrName); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("theme not found: %s", idOrName))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, models.SuccessResult{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("theme uninstalled: %s", idOrName),
|
||||
})
|
||||
}
|
||||
57
core/internal/server/themes/update.go
Normal file
57
core/internal/server/themes/update.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
|
||||
)
|
||||
|
||||
func HandleUpdate(conn net.Conn, req models.Request) {
|
||||
idOrName, ok := req.Params["name"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
manager, err := themes.NewManager()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
registry, err := themes.NewRegistry()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
themeList, _ := registry.List()
|
||||
theme := themes.FindByIDOrName(idOrName, themeList)
|
||||
|
||||
if theme == nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("theme not found in registry: %s", idOrName))
|
||||
return
|
||||
}
|
||||
|
||||
installed, err := manager.IsInstalled(*theme)
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if theme is installed: %v", err))
|
||||
return
|
||||
}
|
||||
if !installed {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("theme not installed: %s", idOrName))
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.Update(*theme); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update theme: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, models.SuccessResult{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("theme updated: %s", theme.Name),
|
||||
})
|
||||
}
|
||||
@@ -145,6 +145,7 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
|
||||
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
|
||||
log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name)
|
||||
head.name = e.Name
|
||||
head.ready = true
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
@@ -240,6 +241,7 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
|
||||
handle.SetAdaptiveSyncHandler(func(e wlr_output_management.ZwlrOutputHeadV1AdaptiveSyncEvent) {
|
||||
log.Debugf("WlrOutput: Head %d adaptive sync: %d", headID, e.State)
|
||||
head.adaptiveSync = e.State
|
||||
head.adaptiveSyncSupported = true
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
@@ -251,11 +253,11 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
|
||||
|
||||
m.heads.Delete(headID)
|
||||
|
||||
m.post(func() {
|
||||
m.wlMutex.Lock()
|
||||
handle.Release()
|
||||
m.wlMutex.Unlock()
|
||||
m.wlMutex.Lock()
|
||||
handle.Release()
|
||||
m.wlMutex.Unlock()
|
||||
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
@@ -310,11 +312,11 @@ func (m *Manager) handleMode(headID uint32, e wlr_output_management.ZwlrOutputHe
|
||||
|
||||
m.modes.Delete(modeID)
|
||||
|
||||
m.post(func() {
|
||||
m.wlMutex.Lock()
|
||||
handle.Release()
|
||||
m.wlMutex.Unlock()
|
||||
m.wlMutex.Lock()
|
||||
handle.Release()
|
||||
m.wlMutex.Unlock()
|
||||
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
@@ -328,6 +330,10 @@ func (m *Manager) updateState() {
|
||||
return true
|
||||
}
|
||||
|
||||
if !head.ready {
|
||||
return true
|
||||
}
|
||||
|
||||
modes := make([]OutputMode, 0)
|
||||
var currentMode *OutputMode
|
||||
|
||||
@@ -355,22 +361,23 @@ func (m *Manager) updateState() {
|
||||
}
|
||||
|
||||
output := Output{
|
||||
Name: head.name,
|
||||
Description: head.description,
|
||||
Make: head.make,
|
||||
Model: head.model,
|
||||
SerialNumber: head.serialNumber,
|
||||
PhysicalWidth: head.physicalWidth,
|
||||
PhysicalHeight: head.physicalHeight,
|
||||
Enabled: head.enabled,
|
||||
X: head.x,
|
||||
Y: head.y,
|
||||
Transform: head.transform,
|
||||
Scale: head.scale,
|
||||
CurrentMode: currentMode,
|
||||
Modes: modes,
|
||||
AdaptiveSync: head.adaptiveSync,
|
||||
ID: head.id,
|
||||
Name: head.name,
|
||||
Description: head.description,
|
||||
Make: head.make,
|
||||
Model: head.model,
|
||||
SerialNumber: head.serialNumber,
|
||||
PhysicalWidth: head.physicalWidth,
|
||||
PhysicalHeight: head.physicalHeight,
|
||||
Enabled: head.enabled,
|
||||
X: head.x,
|
||||
Y: head.y,
|
||||
Transform: head.transform,
|
||||
Scale: head.scale,
|
||||
CurrentMode: currentMode,
|
||||
Modes: modes,
|
||||
AdaptiveSync: head.adaptiveSync,
|
||||
AdaptiveSyncSupported: head.adaptiveSyncSupported,
|
||||
ID: head.id,
|
||||
}
|
||||
outputs = append(outputs, output)
|
||||
return true
|
||||
|
||||
@@ -17,22 +17,23 @@ type OutputMode struct {
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Make string `json:"make"`
|
||||
Model string `json:"model"`
|
||||
SerialNumber string `json:"serialNumber"`
|
||||
PhysicalWidth int32 `json:"physicalWidth"`
|
||||
PhysicalHeight int32 `json:"physicalHeight"`
|
||||
Enabled bool `json:"enabled"`
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
Transform int32 `json:"transform"`
|
||||
Scale float64 `json:"scale"`
|
||||
CurrentMode *OutputMode `json:"currentMode"`
|
||||
Modes []OutputMode `json:"modes"`
|
||||
AdaptiveSync uint32 `json:"adaptiveSync"`
|
||||
ID uint32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Make string `json:"make"`
|
||||
Model string `json:"model"`
|
||||
SerialNumber string `json:"serialNumber"`
|
||||
PhysicalWidth int32 `json:"physicalWidth"`
|
||||
PhysicalHeight int32 `json:"physicalHeight"`
|
||||
Enabled bool `json:"enabled"`
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
Transform int32 `json:"transform"`
|
||||
Scale float64 `json:"scale"`
|
||||
CurrentMode *OutputMode `json:"currentMode"`
|
||||
Modes []OutputMode `json:"modes"`
|
||||
AdaptiveSync uint32 `json:"adaptiveSync"`
|
||||
AdaptiveSyncSupported bool `json:"adaptiveSyncSupported"`
|
||||
ID uint32 `json:"id"`
|
||||
}
|
||||
|
||||
type State struct {
|
||||
@@ -72,24 +73,26 @@ type Manager struct {
|
||||
}
|
||||
|
||||
type headState struct {
|
||||
id uint32
|
||||
handle *wlr_output_management.ZwlrOutputHeadV1
|
||||
name string
|
||||
description string
|
||||
make string
|
||||
model string
|
||||
serialNumber string
|
||||
physicalWidth int32
|
||||
physicalHeight int32
|
||||
enabled bool
|
||||
x int32
|
||||
y int32
|
||||
transform int32
|
||||
scale float64
|
||||
currentModeID uint32
|
||||
modeIDs []uint32
|
||||
adaptiveSync uint32
|
||||
finished bool
|
||||
id uint32
|
||||
handle *wlr_output_management.ZwlrOutputHeadV1
|
||||
name string
|
||||
description string
|
||||
make string
|
||||
model string
|
||||
serialNumber string
|
||||
physicalWidth int32
|
||||
physicalHeight int32
|
||||
enabled bool
|
||||
x int32
|
||||
y int32
|
||||
transform int32
|
||||
scale float64
|
||||
currentModeID uint32
|
||||
modeIDs []uint32
|
||||
adaptiveSync uint32
|
||||
adaptiveSyncSupported bool
|
||||
finished bool
|
||||
ready bool
|
||||
}
|
||||
|
||||
type modeState struct {
|
||||
@@ -168,7 +171,7 @@ func stateChanged(old, new *State) bool {
|
||||
if oldOut.Transform != newOut.Transform || oldOut.Scale != newOut.Scale {
|
||||
return true
|
||||
}
|
||||
if oldOut.AdaptiveSync != newOut.AdaptiveSync {
|
||||
if oldOut.AdaptiveSync != newOut.AdaptiveSync || oldOut.AdaptiveSyncSupported != newOut.AdaptiveSyncSupported {
|
||||
return true
|
||||
}
|
||||
if (oldOut.CurrentMode == nil) != (newOut.CurrentMode == nil) {
|
||||
|
||||
258
core/internal/themes/manager.go
Normal file
258
core/internal/themes/manager.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
fs afero.Fs
|
||||
themesDir string
|
||||
}
|
||||
|
||||
func NewManager() (*Manager, error) {
|
||||
return NewManagerWithFs(afero.NewOsFs())
|
||||
}
|
||||
|
||||
func NewManagerWithFs(fs afero.Fs) (*Manager, error) {
|
||||
themesDir := getThemesDir()
|
||||
return &Manager{
|
||||
fs: fs,
|
||||
themesDir: themesDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getThemesDir() string {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
log.Error("failed to get user config dir", "err", err)
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(configDir, "DankMaterialShell", "themes")
|
||||
}
|
||||
|
||||
func (m *Manager) IsInstalled(theme Theme) (bool, error) {
|
||||
path := m.getInstalledPath(theme.ID)
|
||||
exists, err := afero.Exists(m.fs, path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getInstalledDir(themeID string) string {
|
||||
return filepath.Join(m.themesDir, themeID)
|
||||
}
|
||||
|
||||
func (m *Manager) getInstalledPath(themeID string) string {
|
||||
return filepath.Join(m.getInstalledDir(themeID), "theme.json")
|
||||
}
|
||||
|
||||
func (m *Manager) Install(theme Theme, registryThemeDir string) error {
|
||||
themeDir := m.getInstalledDir(theme.ID)
|
||||
|
||||
exists, err := afero.DirExists(m.fs, themeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if theme exists: %w", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
return fmt.Errorf("theme already installed: %s", theme.Name)
|
||||
}
|
||||
|
||||
if err := m.fs.MkdirAll(themeDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create theme directory: %w", err)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(theme, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal theme: %w", err)
|
||||
}
|
||||
|
||||
themePath := filepath.Join(themeDir, "theme.json")
|
||||
if err := afero.WriteFile(m.fs, themePath, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write theme file: %w", err)
|
||||
}
|
||||
|
||||
m.copyPreviewFiles(registryThemeDir, themeDir, theme)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) copyPreviewFiles(srcDir, dstDir string, theme Theme) {
|
||||
previews := []string{"preview-dark.svg", "preview-light.svg"}
|
||||
|
||||
if theme.Variants != nil {
|
||||
for _, v := range theme.Variants.Options {
|
||||
previews = append(previews,
|
||||
fmt.Sprintf("preview-%s.svg", v.ID),
|
||||
fmt.Sprintf("preview-%s-dark.svg", v.ID),
|
||||
fmt.Sprintf("preview-%s-light.svg", v.ID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for _, preview := range previews {
|
||||
srcPath := filepath.Join(srcDir, preview)
|
||||
if exists, _ := afero.Exists(m.fs, srcPath); !exists {
|
||||
continue
|
||||
}
|
||||
data, err := afero.ReadFile(m.fs, srcPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
dstPath := filepath.Join(dstDir, preview)
|
||||
_ = afero.WriteFile(m.fs, dstPath, data, 0644)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) InstallFromRegistry(registry *Registry, themeID string) error {
|
||||
theme, err := registry.Get(themeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registryThemeDir := registry.GetThemeDir(theme.SourceDir)
|
||||
return m.Install(*theme, registryThemeDir)
|
||||
}
|
||||
|
||||
func (m *Manager) Update(theme Theme) error {
|
||||
themePath := m.getInstalledPath(theme.ID)
|
||||
|
||||
exists, err := afero.Exists(m.fs, themePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if theme exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("theme not installed: %s", theme.Name)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(theme, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal theme: %w", err)
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(m.fs, themePath, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write theme file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Uninstall(theme Theme) error {
|
||||
return m.UninstallByID(theme.ID)
|
||||
}
|
||||
|
||||
func (m *Manager) UninstallByID(themeID string) error {
|
||||
themeDir := m.getInstalledDir(themeID)
|
||||
|
||||
exists, err := afero.DirExists(m.fs, themeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if theme exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("theme not installed: %s", themeID)
|
||||
}
|
||||
|
||||
if err := m.fs.RemoveAll(themeDir); err != nil {
|
||||
return fmt.Errorf("failed to remove theme: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) ListInstalled() ([]string, error) {
|
||||
exists, err := afero.DirExists(m.fs, m.themesDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
entries, err := afero.ReadDir(m.fs, m.themesDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read themes directory: %w", err)
|
||||
}
|
||||
|
||||
var installed []string
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
themeID := entry.Name()
|
||||
themePath := filepath.Join(m.themesDir, themeID, "theme.json")
|
||||
if exists, _ := afero.Exists(m.fs, themePath); exists {
|
||||
installed = append(installed, themeID)
|
||||
}
|
||||
}
|
||||
|
||||
return installed, nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetInstalledTheme(themeID string) (*Theme, error) {
|
||||
themePath := m.getInstalledPath(themeID)
|
||||
|
||||
data, err := afero.ReadFile(m.fs, themePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read theme file: %w", err)
|
||||
}
|
||||
|
||||
var theme Theme
|
||||
if err := json.Unmarshal(data, &theme); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse theme file: %w", err)
|
||||
}
|
||||
|
||||
return &theme, nil
|
||||
}
|
||||
|
||||
func (m *Manager) HasUpdates(themeID string, registryTheme Theme) (bool, error) {
|
||||
installed, err := m.GetInstalledTheme(themeID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return compareVersions(installed.Version, registryTheme.Version) < 0, nil
|
||||
}
|
||||
|
||||
func compareVersions(installed, registry string) int {
|
||||
installedParts := strings.Split(installed, ".")
|
||||
registryParts := strings.Split(registry, ".")
|
||||
|
||||
maxLen := len(installedParts)
|
||||
if len(registryParts) > maxLen {
|
||||
maxLen = len(registryParts)
|
||||
}
|
||||
|
||||
for i := 0; i < maxLen; i++ {
|
||||
var installedNum, registryNum int
|
||||
if i < len(installedParts) {
|
||||
fmt.Sscanf(installedParts[i], "%d", &installedNum)
|
||||
}
|
||||
if i < len(registryParts) {
|
||||
fmt.Sscanf(registryParts[i], "%d", ®istryNum)
|
||||
}
|
||||
|
||||
if installedNum < registryNum {
|
||||
return -1
|
||||
}
|
||||
if installedNum > registryNum {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Manager) GetThemesDir() string {
|
||||
return m.themesDir
|
||||
}
|
||||
309
core/internal/themes/registry.go
Normal file
309
core/internal/themes/registry.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-git/go-git/v6"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
const registryRepo = "https://github.com/AvengeMedia/dms-plugin-registry.git"
|
||||
|
||||
type ColorScheme struct {
|
||||
Primary string `json:"primary,omitempty"`
|
||||
PrimaryText string `json:"primaryText,omitempty"`
|
||||
PrimaryContainer string `json:"primaryContainer,omitempty"`
|
||||
Secondary string `json:"secondary,omitempty"`
|
||||
Surface string `json:"surface,omitempty"`
|
||||
SurfaceText string `json:"surfaceText,omitempty"`
|
||||
SurfaceVariant string `json:"surfaceVariant,omitempty"`
|
||||
SurfaceVariantText string `json:"surfaceVariantText,omitempty"`
|
||||
SurfaceTint string `json:"surfaceTint,omitempty"`
|
||||
Background string `json:"background,omitempty"`
|
||||
BackgroundText string `json:"backgroundText,omitempty"`
|
||||
Outline string `json:"outline,omitempty"`
|
||||
SurfaceContainer string `json:"surfaceContainer,omitempty"`
|
||||
SurfaceContainerHigh string `json:"surfaceContainerHigh,omitempty"`
|
||||
SurfaceContainerHighest string `json:"surfaceContainerHighest,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Warning string `json:"warning,omitempty"`
|
||||
Info string `json:"info,omitempty"`
|
||||
}
|
||||
|
||||
type ThemeVariant struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Dark ColorScheme `json:"dark,omitempty"`
|
||||
Light ColorScheme `json:"light,omitempty"`
|
||||
}
|
||||
|
||||
type ThemeFlavor struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Dark ColorScheme `json:"dark,omitempty"`
|
||||
Light ColorScheme `json:"light,omitempty"`
|
||||
}
|
||||
|
||||
type ThemeAccent struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
FlavorColors map[string]ColorScheme `json:"-"`
|
||||
}
|
||||
|
||||
func (a *ThemeAccent) UnmarshalJSON(data []byte) error {
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
a.FlavorColors = make(map[string]ColorScheme)
|
||||
var mErr error
|
||||
for key, value := range raw {
|
||||
switch key {
|
||||
case "id":
|
||||
mErr = errors.Join(mErr, json.Unmarshal(value, &a.ID))
|
||||
case "name":
|
||||
mErr = errors.Join(mErr, json.Unmarshal(value, &a.Name))
|
||||
default:
|
||||
var colors ColorScheme
|
||||
if err := json.Unmarshal(value, &colors); err == nil {
|
||||
a.FlavorColors[key] = colors
|
||||
} else {
|
||||
mErr = errors.Join(mErr, fmt.Errorf("failed to unmarshal flavor colors for key %s: %w", key, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mErr
|
||||
}
|
||||
|
||||
func (a ThemeAccent) MarshalJSON() ([]byte, error) {
|
||||
m := map[string]any{
|
||||
"id": a.ID,
|
||||
"name": a.Name,
|
||||
}
|
||||
for k, v := range a.FlavorColors {
|
||||
m[k] = v
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
type MultiVariantDefaults struct {
|
||||
Dark map[string]string `json:"dark,omitempty"`
|
||||
Light map[string]string `json:"light,omitempty"`
|
||||
}
|
||||
|
||||
type ThemeVariants struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
Defaults *MultiVariantDefaults `json:"defaults,omitempty"`
|
||||
Options []ThemeVariant `json:"options,omitempty"`
|
||||
Flavors []ThemeFlavor `json:"flavors,omitempty"`
|
||||
Accents []ThemeAccent `json:"accents,omitempty"`
|
||||
}
|
||||
|
||||
type Theme struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Author string `json:"author"`
|
||||
Description string `json:"description"`
|
||||
Dark ColorScheme `json:"dark"`
|
||||
Light ColorScheme `json:"light"`
|
||||
Variants *ThemeVariants `json:"variants,omitempty"`
|
||||
PreviewPath string `json:"-"`
|
||||
SourceDir string `json:"sourceDir,omitempty"`
|
||||
}
|
||||
|
||||
type GitClient interface {
|
||||
PlainClone(path string, url string) error
|
||||
Pull(path string) error
|
||||
}
|
||||
|
||||
type realGitClient struct{}
|
||||
|
||||
func (g *realGitClient) PlainClone(path string, url string) error {
|
||||
_, err := git.PlainClone(path, &git.CloneOptions{
|
||||
URL: url,
|
||||
Progress: os.Stdout,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *realGitClient) Pull(path string) error {
|
||||
repo, err := git.PlainOpen(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = worktree.Pull(&git.PullOptions{})
|
||||
if err != nil && err.Error() != "already up-to-date" {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
fs afero.Fs
|
||||
cacheDir string
|
||||
themes []Theme
|
||||
git GitClient
|
||||
}
|
||||
|
||||
func NewRegistry() (*Registry, error) {
|
||||
return NewRegistryWithFs(afero.NewOsFs())
|
||||
}
|
||||
|
||||
func NewRegistryWithFs(fs afero.Fs) (*Registry, error) {
|
||||
cacheDir := getCacheDir()
|
||||
return &Registry{
|
||||
fs: fs,
|
||||
cacheDir: cacheDir,
|
||||
git: &realGitClient{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getCacheDir() string {
|
||||
return filepath.Join(os.TempDir(), "dankdots-plugin-registry")
|
||||
}
|
||||
|
||||
func (r *Registry) Update() error {
|
||||
exists, err := afero.DirExists(r.fs, r.cacheDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check cache directory: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if err := r.fs.MkdirAll(filepath.Dir(r.cacheDir), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
if err := r.git.PlainClone(r.cacheDir, registryRepo); err != nil {
|
||||
return fmt.Errorf("failed to clone registry: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := r.git.Pull(r.cacheDir); err != nil {
|
||||
if err := r.fs.RemoveAll(r.cacheDir); err != nil {
|
||||
return fmt.Errorf("failed to remove corrupted registry: %w", err)
|
||||
}
|
||||
|
||||
if err := r.fs.MkdirAll(filepath.Dir(r.cacheDir), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
if err := r.git.PlainClone(r.cacheDir, registryRepo); err != nil {
|
||||
return fmt.Errorf("failed to re-clone registry: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r.loadThemes()
|
||||
}
|
||||
|
||||
func (r *Registry) loadThemes() error {
|
||||
themesDir := filepath.Join(r.cacheDir, "themes")
|
||||
|
||||
entries, err := afero.ReadDir(r.fs, themesDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read themes directory: %w", err)
|
||||
}
|
||||
|
||||
r.themes = []Theme{}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
themeDir := filepath.Join(themesDir, entry.Name())
|
||||
themeFile := filepath.Join(themeDir, "theme.json")
|
||||
|
||||
data, err := afero.ReadFile(r.fs, themeFile)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var theme Theme
|
||||
if err := json.Unmarshal(data, &theme); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if theme.ID == "" {
|
||||
theme.ID = entry.Name()
|
||||
}
|
||||
theme.SourceDir = entry.Name()
|
||||
|
||||
previewPath := filepath.Join(themeDir, "preview.svg")
|
||||
if exists, _ := afero.Exists(r.fs, previewPath); exists {
|
||||
theme.PreviewPath = previewPath
|
||||
}
|
||||
|
||||
r.themes = append(r.themes, theme)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Registry) List() ([]Theme, error) {
|
||||
if len(r.themes) == 0 {
|
||||
if err := r.Update(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return SortByFirstParty(r.themes), nil
|
||||
}
|
||||
|
||||
func (r *Registry) Search(query string) ([]Theme, error) {
|
||||
allThemes, err := r.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if query == "" {
|
||||
return allThemes, nil
|
||||
}
|
||||
|
||||
return SortByFirstParty(FuzzySearch(query, allThemes)), nil
|
||||
}
|
||||
|
||||
func (r *Registry) Get(idOrName string) (*Theme, error) {
|
||||
themes, err := r.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, t := range themes {
|
||||
if t.ID == idOrName {
|
||||
return &t, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range themes {
|
||||
if t.Name == idOrName {
|
||||
return &t, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("theme not found: %s", idOrName)
|
||||
}
|
||||
|
||||
func (r *Registry) GetThemeSourcePath(themeID string) string {
|
||||
return filepath.Join(r.cacheDir, "themes", themeID, "theme.json")
|
||||
}
|
||||
|
||||
func (r *Registry) GetThemeDir(themeID string) string {
|
||||
return filepath.Join(r.cacheDir, "themes", themeID)
|
||||
}
|
||||
|
||||
func SortByFirstParty(themes []Theme) []Theme {
|
||||
return themes
|
||||
}
|
||||
40
core/internal/themes/search.go
Normal file
40
core/internal/themes/search.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package themes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
func FuzzySearch(query string, themes []Theme) []Theme {
|
||||
if query == "" {
|
||||
return themes
|
||||
}
|
||||
|
||||
queryLower := strings.ToLower(query)
|
||||
return utils.Filter(themes, func(t Theme) bool {
|
||||
return fuzzyMatch(queryLower, strings.ToLower(t.Name)) ||
|
||||
fuzzyMatch(queryLower, strings.ToLower(t.Description)) ||
|
||||
fuzzyMatch(queryLower, strings.ToLower(t.Author))
|
||||
})
|
||||
}
|
||||
|
||||
func fuzzyMatch(query, text string) bool {
|
||||
queryIdx := 0
|
||||
for _, char := range text {
|
||||
if queryIdx < len(query) && char == rune(query[queryIdx]) {
|
||||
queryIdx++
|
||||
}
|
||||
}
|
||||
return queryIdx == len(query)
|
||||
}
|
||||
|
||||
func FindByIDOrName(idOrName string, themes []Theme) *Theme {
|
||||
if t, found := utils.Find(themes, func(t Theme) bool { return t.ID == idOrName }); found {
|
||||
return &t
|
||||
}
|
||||
if t, found := utils.Find(themes, func(t Theme) bool { return t.Name == idOrName }); found {
|
||||
return &t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -6,3 +6,12 @@ func CommandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func AnyCommandExists(cmds ...string) bool {
|
||||
for _, cmd := range cmds {
|
||||
if CommandExists(cmd) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dms-git (1.0.2+git2528.d336866f) nightly; urgency=medium
|
||||
dms-git (1.0.2+git2528.d336866fdb1) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2528: d336866f)
|
||||
|
||||
@@ -16,23 +16,6 @@ dms-git (1.0.2+git2518.a783d650) nightly; urgency=medium
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 15:11:40 +0000
|
||||
|
||||
dms-git (1.0.2+git2510.0f89886c) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2510: 0f89886c)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:46:43 +0000
|
||||
|
||||
dms-git (1.0.2+git2507.b2ac9c6c) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2507: b2ac9c6c)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:18:05 +0000
|
||||
|
||||
dms-git (1.0.2+git2505.82f881af) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2505: 82f881af)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 05:55:03 +0000
|
||||
|
||||
dms-git (1.0.0+git2419.993f14a3) nightly; urgency=medium
|
||||
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.2.tar.gz</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.3.tar.gz</param>
|
||||
<param name="filename">dms-source.tar.gz</param>
|
||||
</service>
|
||||
<!-- Download amd64 binary -->
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-amd64.gz</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-amd64.gz</param>
|
||||
</service>
|
||||
<!-- Download arm64 binary -->
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-arm64.gz</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-arm64.gz</param>
|
||||
</service>
|
||||
</services>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
dms (1.0.2ppa6) unstable; urgency=medium
|
||||
dms (1.0.3db1) unstable; urgency=medium
|
||||
|
||||
* Rebuild to fix repository metadata issues
|
||||
* Update to v1.0.3 stable release
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Mon, 16 Dec 2025 10:00:00 +0000
|
||||
|
||||
dms (1.0.2db1) unstable; urgency=medium
|
||||
|
||||
* Update to v1.0.2 stable release
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:47:39 +0000
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
|
||||
Package: dms
|
||||
Architecture: amd64
|
||||
Depends: ${misc:Depends},
|
||||
quickshell-git | quickshell,
|
||||
quickshell | quickshell-git,
|
||||
accountsservice,
|
||||
cava,
|
||||
cliphist,
|
||||
|
||||
135
distro/fedora/dms-git.spec
Normal file
135
distro/fedora/dms-git.spec
Normal file
@@ -0,0 +1,135 @@
|
||||
# Spec for DMS - uses rpkg macros for git builds
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global version {{{ git_repo_version }}}
|
||||
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
Name: dms
|
||||
Epoch: 2
|
||||
Version: %{version}
|
||||
Release: 1%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
VCS: {{{ git_repo_vcs }}}
|
||||
Source0: {{{ git_repo_pack }}}
|
||||
|
||||
BuildRequires: git-core
|
||||
BuildRequires: gzip
|
||||
BuildRequires: golang >= 1.24
|
||||
BuildRequires: make
|
||||
BuildRequires: wget
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
# Core requirements
|
||||
Requires: (quickshell-git or quickshell)
|
||||
Requires: accountsservice
|
||||
Requires: dms-cli = %{epoch}:%{version}-%{release}
|
||||
Requires: dgop
|
||||
|
||||
# Core utilities (Highly recommended for DMS functionality)
|
||||
Recommends: cava
|
||||
Recommends: danksearch
|
||||
Recommends: matugen
|
||||
Recommends: quickshell-git
|
||||
Recommends: wl-clipboard
|
||||
|
||||
# Recommended system packages
|
||||
Recommends: NetworkManager
|
||||
Recommends: qt6-qtmultimedia
|
||||
Suggests: qt6ct
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for the niri, hyprland, sway, and dwl (MangoWC) compositors. Features notifications,
|
||||
app launcher, wallpaper customization, and fully customizable with plugins.
|
||||
|
||||
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
process monitoring, notification center, clipboard history, dock, control center,
|
||||
lock screen, and comprehensive plugin system.
|
||||
|
||||
%package -n dms-cli
|
||||
Summary: DankMaterialShell CLI tool
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
%description -n dms-cli
|
||||
Command-line interface for DankMaterialShell configuration and management.
|
||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||
|
||||
%prep
|
||||
{{{ git_repo_setup_macro }}}
|
||||
|
||||
%build
|
||||
# Build DMS CLI from source (core/subdirectory)
|
||||
VERSION="%{version}"
|
||||
COMMIT=$(echo "%{version}" | grep -oP '[a-f0-9]{7,}' | head -n1 || echo "unknown")
|
||||
|
||||
cd core
|
||||
make dist VERSION="$VERSION" COMMIT="$COMMIT"
|
||||
|
||||
%install
|
||||
# Install dms-cli binary (built from source)
|
||||
case "%{_arch}" in
|
||||
x86_64)
|
||||
DMS_BINARY="dms-linux-amd64"
|
||||
;;
|
||||
aarch64)
|
||||
DMS_BINARY="dms-linux-arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: %{_arch}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
install -Dm755 core/bin/${DMS_BINARY} %{buildroot}%{_bindir}/dms
|
||||
|
||||
# Shell completions
|
||||
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
||||
core/bin/${DMS_BINARY} completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
||||
core/bin/${DMS_BINARY} completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
core/bin/${DMS_BINARY} completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
|
||||
# Install systemd user service
|
||||
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
|
||||
install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
|
||||
install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
# Install shell files to shared data location
|
||||
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
|
||||
# Remove build files
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
||||
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
||||
|
||||
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
||||
|
||||
%posttrans
|
||||
# Signal running DMS instances to reload
|
||||
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc CONTRIBUTING.md
|
||||
%doc quickshell/README.md
|
||||
%{_datadir}/quickshell/dms/
|
||||
%{_userunitdir}/dms.service
|
||||
%{_datadir}/applications/dms-open.desktop
|
||||
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
%files -n dms-cli
|
||||
%{_bindir}/dms
|
||||
%{_datadir}/bash-completion/completions/dms
|
||||
%{_datadir}/zsh/site-functions/_dms
|
||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||
|
||||
%changelog
|
||||
{{{ git_repo_changelog }}}
|
||||
@@ -1,22 +1,22 @@
|
||||
# Spec for DMS Greeter - Git builds using rpkg macros
|
||||
# Spec for DMS Greeter - Stable releases
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global version {{{ git_repo_version }}}
|
||||
%global version VERSION_PLACEHOLDER
|
||||
%global pkg_summary DankMaterialShell greeter for greetd
|
||||
|
||||
Name: dms-greeter
|
||||
Version: %{version}
|
||||
Release: 0.git%{?dist}
|
||||
Release: RELEASE_PLACEHOLDER%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
VCS: {{{ git_repo_vcs }}}
|
||||
Source0: {{{ git_repo_pack }}}
|
||||
|
||||
BuildRequires: git-core
|
||||
# For the _tmpfilesdir macro.
|
||||
BuildRequires: systemd-rpm-macros
|
||||
Source0: dms-qml.tar.gz
|
||||
|
||||
BuildRequires: gzip
|
||||
BuildRequires: wget
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
Requires: greetd
|
||||
Requires: (quickshell-git or quickshell)
|
||||
@@ -24,14 +24,11 @@ Requires(post): /usr/sbin/useradd
|
||||
Requires(post): /usr/sbin/groupadd
|
||||
|
||||
Recommends: policycoreutils-python-utils
|
||||
Recommends: setfacl
|
||||
Recommends: acl
|
||||
Suggests: niri
|
||||
Suggests: hyprland
|
||||
Suggests: sway
|
||||
|
||||
# Provides: greetd-dms-greeter = %{version}-%{release}
|
||||
# Conflicts: greetd-dms-greeter
|
||||
|
||||
%description
|
||||
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
||||
inspired greeter interface built with Quickshell for Wayland compositors.
|
||||
@@ -41,31 +38,26 @@ compositor detection and configuration. Features session selection, user
|
||||
authentication, and dynamic theming.
|
||||
|
||||
%prep
|
||||
{{{ git_repo_setup_macro }}}
|
||||
%setup -q -c -n dms-qml
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
# Install greeter files to shared data location (from quickshell/ subdirectory)
|
||||
# Install greeter files to shared data location
|
||||
install -dm755 %{buildroot}%{_datadir}/quickshell/dms-greeter
|
||||
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms-greeter/
|
||||
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms-greeter/
|
||||
|
||||
# Install launcher script
|
||||
install -Dm755 quickshell/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
|
||||
install -Dm755 %{_builddir}/dms-qml/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
|
||||
|
||||
# Install documentation
|
||||
install -Dm644 quickshell/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
|
||||
install -Dm644 %{_builddir}/dms-qml/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
|
||||
|
||||
# Create cache directory for greeter data
|
||||
install -Dpm0644 quickshell/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
|
||||
install -Dpm0644 %{_builddir}/dms-qml/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
|
||||
|
||||
# Install LICENSE file
|
||||
install -Dm644 LICENSE %{buildroot}%{_docdir}/dms-greeter/LICENSE
|
||||
install -Dm644 %{_builddir}/dms-qml/LICENSE %{buildroot}%{_docdir}/dms-greeter/LICENSE
|
||||
|
||||
# Create greeter home directory
|
||||
install -dm755 %{buildroot}%{_sharedstatedir}/greeter
|
||||
|
||||
# Note: We do NOT install a PAM config here to avoid conflicting with greetd package
|
||||
# Instead, we verify/fix it in %post if needed
|
||||
|
||||
# Note: We do NOT install a PAM config here to avoid conflicting w/greetd packages
|
||||
# Remove build and development files
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.git*
|
||||
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/.gitignore
|
||||
@@ -73,9 +65,8 @@ rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.github
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/distro
|
||||
|
||||
%posttrans
|
||||
# Clean up old installation path from previous versions (only if empty)
|
||||
if [ -d "%{_sysconfdir}/xdg/quickshell/dms-greeter" ]; then
|
||||
# Remove directories only if empty (preserves any user-added files)
|
||||
# Remove directories & preserves any user-added files
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell/dms-greeter" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||
@@ -89,7 +80,7 @@ fi
|
||||
%{_tmpfilesdir}/%{name}.conf
|
||||
|
||||
%pre
|
||||
# Create greeter user/group if they don't exist (greetd expects this)
|
||||
# Create greeter user/group if they don't exist
|
||||
getent group greeter >/dev/null || groupadd -r greeter
|
||||
getent passwd greeter >/dev/null || \
|
||||
useradd -r -g greeter -d %{_sharedstatedir}/greeter -s /bin/bash \
|
||||
@@ -127,7 +118,6 @@ chown -R greeter:greeter %{_sharedstatedir}/greeter 2>/dev/null || true
|
||||
# Verify PAM configuration - only fix if insufficient
|
||||
PAM_CONFIG="/etc/pam.d/greetd"
|
||||
if [ ! -f "$PAM_CONFIG" ]; then
|
||||
# PAM config doesn't exist - create it
|
||||
cat > "$PAM_CONFIG" << 'PAM_EOF'
|
||||
#%PAM-1.0
|
||||
auth substack system-auth
|
||||
@@ -149,7 +139,6 @@ PAM_EOF
|
||||
# Only show message on initial install
|
||||
[ "$1" -eq 1 ] && echo "Created PAM configuration for greetd"
|
||||
elif ! grep -q "pam_systemd\|system-auth" "$PAM_CONFIG"; then
|
||||
# PAM config exists but looks insufficient - back it up and replace
|
||||
cp "$PAM_CONFIG" "$PAM_CONFIG.backup-dms-greeter"
|
||||
cat > "$PAM_CONFIG" << 'PAM_EOF'
|
||||
#%PAM-1.0
|
||||
@@ -198,9 +187,8 @@ command = "/usr/bin/dms-greeter --command COMPOSITOR_PLACEHOLDER"
|
||||
GREETD_EOF
|
||||
sed -i "s|COMPOSITOR_PLACEHOLDER|$COMPOSITOR|" "$GREETD_CONFIG"
|
||||
CONFIG_STATUS="Created new config with $COMPOSITOR ✓"
|
||||
# If config exists and doesn't have dms-greeter, update it
|
||||
|
||||
elif ! grep -q "dms-greeter" "$GREETD_CONFIG"; then
|
||||
# Backup existing config
|
||||
BACKUP_FILE="${GREETD_CONFIG}.backup-$(date +%%Y%%m%%d-%%H%%M%%S)"
|
||||
cp "$GREETD_CONFIG" "$BACKUP_FILE" 2>/dev/null || true
|
||||
|
||||
@@ -267,4 +255,6 @@ if [ "$1" -eq 0 ] && [ -x /usr/sbin/semanage ]; then
|
||||
fi
|
||||
|
||||
%changelog
|
||||
{{{ git_repo_changelog }}}
|
||||
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
|
||||
- Stable release VERSION_PLACEHOLDER
|
||||
- Built from GitHub release
|
||||
|
||||
@@ -1,49 +1,40 @@
|
||||
# Spec for DMS - uses rpkg macros for git builds
|
||||
# Feodra spec for DMS stable releases
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global version {{{ git_repo_version }}}
|
||||
%global version VERSION_PLACEHOLDER
|
||||
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
Name: dms
|
||||
Epoch: 2
|
||||
Version: %{version}
|
||||
Release: 1%{?dist}
|
||||
Release: RELEASE_PLACEHOLDER%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
VCS: {{{ git_repo_vcs }}}
|
||||
Source0: {{{ git_repo_pack }}}
|
||||
|
||||
BuildRequires: git-core
|
||||
Source0: dms-qml.tar.gz
|
||||
|
||||
BuildRequires: gzip
|
||||
BuildRequires: golang >= 1.24
|
||||
BuildRequires: make
|
||||
BuildRequires: wget
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
# Core requirements
|
||||
Requires: (quickshell-git or quickshell)
|
||||
Requires: (quickshell or quickshell-git)
|
||||
Requires: accountsservice
|
||||
Requires: dms-cli = %{epoch}:%{version}-%{release}
|
||||
Requires: dms-cli = %{version}-%{release}
|
||||
Requires: dgop
|
||||
|
||||
# Core utilities (Highly recommended for DMS functionality)
|
||||
Recommends: cava
|
||||
Recommends: cliphist
|
||||
Recommends: danksearch
|
||||
Recommends: matugen
|
||||
Recommends: quickshell-git
|
||||
Recommends: wl-clipboard
|
||||
|
||||
# Recommended system packages
|
||||
Recommends: NetworkManager
|
||||
Recommends: qt6-qtmultimedia
|
||||
Suggests: qt6ct
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for the niri, hyprland, sway, and dwl (MangoWC) compositors. Features notifications,
|
||||
and optimized for the niri and hyprland compositors. Features notifications,
|
||||
app launcher, wallpaper customization, and fully customizable with plugins.
|
||||
|
||||
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
@@ -60,24 +51,14 @@ Command-line interface for DankMaterialShell configuration and management.
|
||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||
|
||||
%prep
|
||||
{{{ git_repo_setup_macro }}}
|
||||
%setup -q -c -n dms-qml
|
||||
|
||||
%build
|
||||
# Build DMS CLI from source (core/subdirectory)
|
||||
VERSION="%{version}"
|
||||
COMMIT=$(echo "%{version}" | grep -oP '[a-f0-9]{7,}' | head -n1 || echo "unknown")
|
||||
|
||||
cd core
|
||||
make dist VERSION="$VERSION" COMMIT="$COMMIT"
|
||||
|
||||
%install
|
||||
# Install dms-cli binary (built from source)
|
||||
case "%{_arch}" in
|
||||
x86_64)
|
||||
DMS_BINARY="dms-linux-amd64"
|
||||
ARCH_SUFFIX="amd64"
|
||||
;;
|
||||
aarch64)
|
||||
DMS_BINARY="dms-linux-arm64"
|
||||
ARCH_SUFFIX="arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: %{_arch}"
|
||||
@@ -85,27 +66,35 @@ case "%{_arch}" in
|
||||
;;
|
||||
esac
|
||||
|
||||
install -Dm755 core/bin/${DMS_BINARY} %{buildroot}%{_bindir}/dms
|
||||
# Download dms-cli for target architecture
|
||||
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
|
||||
echo "Failed to download dms-cli for architecture %{_arch}"
|
||||
exit 1
|
||||
}
|
||||
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
||||
chmod +x %{_builddir}/dms-cli
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
||||
|
||||
# Shell completions
|
||||
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
||||
core/bin/${DMS_BINARY} completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
||||
core/bin/${DMS_BINARY} completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
core/bin/${DMS_BINARY} completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
%{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
||||
%{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
%{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
|
||||
# Install systemd user service
|
||||
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
install -Dm644 %{_builddir}/dms-qml/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
|
||||
install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
|
||||
install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
install -Dm644 %{_builddir}/dms-qml/assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
|
||||
install -Dm644 %{_builddir}/dms-qml/assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
# Install shell files to shared data location
|
||||
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
|
||||
# Remove build files
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
||||
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||
@@ -119,8 +108,7 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc CONTRIBUTING.md
|
||||
%doc quickshell/README.md
|
||||
%doc README.md CONTRIBUTING.md
|
||||
%{_datadir}/quickshell/dms/
|
||||
%{_userunitdir}/dms.service
|
||||
%{_datadir}/applications/dms-open.desktop
|
||||
@@ -133,4 +121,6 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||
|
||||
%changelog
|
||||
{{{ git_repo_changelog }}}
|
||||
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
|
||||
- Stable release VERSION_PLACEHOLDER
|
||||
- Built from GitHub release
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.programs.dankMaterialShell;
|
||||
cfg = config.programs.dank-material-shell;
|
||||
in
|
||||
{
|
||||
packages = [
|
||||
dmsPkgs.dms-shell
|
||||
]
|
||||
++ lib.optional cfg.enableSystemMonitoring dmsPkgs.dgop
|
||||
++ lib.optional cfg.enableSystemMonitoring cfg.dgop.package
|
||||
++ lib.optionals cfg.enableVPN [
|
||||
pkgs.glib
|
||||
pkgs.networkmanager
|
||||
|
||||
15
distro/nix/dms-rename.nix
Normal file
15
distro/nix/dms-rename.nix
Normal file
@@ -0,0 +1,15 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
(lib.mkRenamedOptionModule
|
||||
[
|
||||
"programs"
|
||||
"dankMaterialShell"
|
||||
]
|
||||
[
|
||||
"programs"
|
||||
"dank-material-shell"
|
||||
]
|
||||
)
|
||||
];
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
}:
|
||||
let
|
||||
inherit (lib) types;
|
||||
cfg = config.programs.dankMaterialShell.greeter;
|
||||
cfg = config.programs.dank-material-shell.greeter;
|
||||
|
||||
inherit (config.services.greetd.settings.default_session) user;
|
||||
|
||||
@@ -44,19 +44,20 @@ in
|
||||
{
|
||||
imports =
|
||||
let
|
||||
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
|
||||
msg = "The option 'programs.dank-material-shell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dank-material-shell.greeter.compositor.customConfig' instead.";
|
||||
in
|
||||
[
|
||||
(lib.mkRemovedOptionModule [
|
||||
"programs"
|
||||
"dankMaterialShell"
|
||||
"dank-material-shell"
|
||||
"greeter"
|
||||
"compositor"
|
||||
"extraConfig"
|
||||
] msg)
|
||||
./dms-rename.nix
|
||||
];
|
||||
|
||||
options.programs.dankMaterialShell.greeter = {
|
||||
options.programs.dank-material-shell.greeter = {
|
||||
enable = lib.mkEnableOption "DankMaterialShell greeter";
|
||||
compositor.name = lib.mkOption {
|
||||
type = types.enum [
|
||||
@@ -177,7 +178,7 @@ in
|
||||
mv dms-colors.json colors.json || :
|
||||
chown ${user}: * || :
|
||||
'';
|
||||
programs.dankMaterialShell.greeter.configFiles = lib.mkIf (cfg.configHome != null) [
|
||||
programs.dank-material-shell.greeter.configFiles = lib.mkIf (cfg.configHome != null) [
|
||||
"${cfg.configHome}/.config/DankMaterialShell/settings.json"
|
||||
"${cfg.configHome}/.local/state/DankMaterialShell/session.json"
|
||||
"${cfg.configHome}/.cache/DankMaterialShell/dms-colors.json"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
...
|
||||
}@args:
|
||||
let
|
||||
cfg = config.programs.dankMaterialShell;
|
||||
cfg = config.programs.dank-material-shell;
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
common = import ./common.nix {
|
||||
inherit
|
||||
@@ -22,16 +22,16 @@ in
|
||||
(import ./options.nix args)
|
||||
(lib.mkRemovedOptionModule [
|
||||
"programs"
|
||||
"dankMaterialShell"
|
||||
"dank-material-shell"
|
||||
"enableNightMode"
|
||||
] "Night mode is now always available.")
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "programs" "dankMaterialShell" "enableSystemd" ]
|
||||
[ "programs" "dankMaterialShell" "systemd" "enable" ]
|
||||
[ "programs" "dank-material-shell" "enableSystemd" ]
|
||||
[ "programs" "dank-material-shell" "systemd" "enable" ]
|
||||
)
|
||||
];
|
||||
|
||||
options.programs.dankMaterialShell = with lib.types; {
|
||||
options.programs.dank-material-shell = with lib.types; {
|
||||
default = {
|
||||
settings = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.programs.dankMaterialShell;
|
||||
cfg = config.programs.dank-material-shell;
|
||||
in
|
||||
{
|
||||
options.programs.dankMaterialShell = {
|
||||
imports = [
|
||||
./dms-rename.nix
|
||||
];
|
||||
|
||||
options.programs.dank-material-shell = {
|
||||
niri = {
|
||||
enableKeybinds = lib.mkEnableOption "DankMaterialShell niri keybinds";
|
||||
enableSpawn = lib.mkEnableOption "DankMaterialShell niri spawn-at-startup";
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
...
|
||||
}@args:
|
||||
let
|
||||
cfg = config.programs.dankMaterialShell;
|
||||
cfg = config.programs.dank-material-shell;
|
||||
common = import ./common.nix {
|
||||
inherit
|
||||
config
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
lib,
|
||||
dmsPkgs,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) types;
|
||||
path = [
|
||||
"programs"
|
||||
"dankMaterialShell"
|
||||
"dank-material-shell"
|
||||
];
|
||||
|
||||
builtInRemovedMsg = "This is now built-in in DMS and doesn't need additional dependencies.";
|
||||
@@ -20,46 +21,58 @@ in
|
||||
(lib.mkRemovedOptionModule (
|
||||
path ++ [ "enableSystemSound" ]
|
||||
) "qtmultimedia is now included on dms-shell package.")
|
||||
./dms-rename.nix
|
||||
];
|
||||
|
||||
options.programs.dankMaterialShell = {
|
||||
options.programs.dank-material-shell = {
|
||||
enable = lib.mkEnableOption "DankMaterialShell";
|
||||
|
||||
systemd = {
|
||||
enable = lib.mkEnableOption "DankMaterialShell systemd startup";
|
||||
restartIfChanged = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Auto-restart dms.service when dankMaterialShell changes";
|
||||
description = "Auto-restart dms.service when dank-material-shell changes";
|
||||
};
|
||||
};
|
||||
|
||||
dgop = {
|
||||
package = lib.mkPackageOption pkgs "dgop" {};
|
||||
};
|
||||
|
||||
enableSystemMonitoring = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Add needed dependencies to use system monitoring widgets";
|
||||
};
|
||||
|
||||
enableVPN = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Add needed dependencies to use the VPN widget";
|
||||
};
|
||||
|
||||
enableDynamicTheming = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Add needed dependencies to have dynamic theming support";
|
||||
};
|
||||
|
||||
enableAudioWavelength = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Add needed dependencies to have audio wavelength support";
|
||||
};
|
||||
|
||||
enableCalendarEvents = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Add calendar events support via khal";
|
||||
};
|
||||
|
||||
quickshell = {
|
||||
package = lib.mkPackageOption dmsPkgs "quickshell" {
|
||||
extraDescription = "The quickshell package to use (defaults to be built from source, in the commit 26531f due to unreleased features used by DMS).";
|
||||
extraDescription = "The quickshell package to use (defaults to be built from source, due to unreleased features used by DMS).";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
%global debug_package %{nil}
|
||||
|
||||
Name: dms
|
||||
Version: 1.0.2
|
||||
Release: 7%{?dist}
|
||||
Version: 1.0.3
|
||||
Release: 1%{?dist}
|
||||
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
License: MIT
|
||||
@@ -17,7 +17,7 @@ BuildRequires: gzip
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
# Core requirements
|
||||
Requires: (quickshell-git or quickshell)
|
||||
Requires: (quickshell or quickshell-git)
|
||||
Requires: accountsservice
|
||||
Requires: dgop
|
||||
|
||||
@@ -105,6 +105,9 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
%changelog
|
||||
* Mon Dec 16 2025 AvengeMedia <maintainer@avengemedia.com> - 1.0.3-1
|
||||
- Update to stable v1.0.3 release
|
||||
|
||||
* Fri Dec 12 2025 AvengeMedia <maintainer@avengemedia.com> - 1.0.2-1
|
||||
- Update to stable v1.0.2 release
|
||||
- Bug fixes and improvements
|
||||
|
||||
@@ -2,226 +2,121 @@
|
||||
set -euo pipefail
|
||||
|
||||
# Build SRPM locally with correct tarball and upload to Copr
|
||||
# Usage: ./create-upload-copr.sh VERSION [RELEASE]
|
||||
# Example: ./create-upload-copr.sh 1.0.0 4
|
||||
# Usage: ./copr-upload.sh [PACKAGE] [VERSION] [RELEASE]
|
||||
# Examples:
|
||||
# ./copr-upload.sh dms 1.0.3 1
|
||||
# ./copr-upload.sh dms-greeter 1.0.3 1
|
||||
|
||||
PACKAGE="${1:-dms}"
|
||||
VERSION="${2:-}"
|
||||
RELEASE="${3:-1}"
|
||||
|
||||
VERSION="${1:-1.0.0}"
|
||||
RELEASE="${2:-1}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
echo "Building DMS v${VERSION}-${RELEASE} SRPM for Copr..."
|
||||
# Determine Copr project based on package
|
||||
if [ "$PACKAGE" = "dms" ]; then
|
||||
COPR_PROJECT="avengemedia/dms"
|
||||
elif [ "$PACKAGE" = "dms-greeter" ]; then
|
||||
COPR_PROJECT="avengemedia/danklinux"
|
||||
else
|
||||
echo "❌ Unknown package: $PACKAGE"
|
||||
echo "Supported packages: dms, dms-greeter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get version from latest release if not provided
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "📦 Determining latest version..."
|
||||
VERSION=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | jq -r '.tag_name' | sed 's/^v//')
|
||||
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
|
||||
echo "❌ Failed to determine version. Please specify manually."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Using latest version: $VERSION"
|
||||
fi
|
||||
|
||||
echo "Building ${PACKAGE} v${VERSION}-${RELEASE} SRPM for Copr..."
|
||||
|
||||
# Setup build directories
|
||||
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
cd ~/rpmbuild/SOURCES
|
||||
|
||||
# Create the corrected QML tarball locally
|
||||
echo "Creating QML tarball with assets..."
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# Copy quickshell contents to temp
|
||||
cp -r quickshell/* "$TEMP_DIR/"
|
||||
|
||||
# Copy root LICENSE and CONTRIBUTING.md
|
||||
cp LICENSE CONTRIBUTING.md "$TEMP_DIR/"
|
||||
|
||||
# Copy root assets directory (this is what was missing!)
|
||||
cp -r assets "$TEMP_DIR/"
|
||||
|
||||
# Create tarball
|
||||
cd "$TEMP_DIR"
|
||||
tar --exclude='.git' \
|
||||
--exclude='.github' \
|
||||
--exclude='*.tar.gz' \
|
||||
-czf ~/rpmbuild/SOURCES/dms-qml.tar.gz .
|
||||
|
||||
cd ~/rpmbuild/SOURCES
|
||||
echo "Created dms-qml.tar.gz with md5sum: $(md5sum dms-qml.tar.gz | awk '{print $1}')"
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# Generate spec file
|
||||
echo "Generating spec file..."
|
||||
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
||||
|
||||
cat >~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
||||
# Spec for DMS stable releases - Built locally
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global version VERSION_PLACEHOLDER
|
||||
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
Name: dms
|
||||
Version: %{version}
|
||||
Release: RELEASE_PLACEHOLDER%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
Source0: dms-qml.tar.gz
|
||||
|
||||
BuildRequires: gzip
|
||||
BuildRequires: wget
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
Requires: (quickshell or quickshell-git)
|
||||
Requires: accountsservice
|
||||
Requires: dms-cli = %{version}-%{release}
|
||||
Requires: dgop
|
||||
|
||||
Recommends: cava
|
||||
Recommends: cliphist
|
||||
Recommends: danksearch
|
||||
Recommends: matugen
|
||||
Recommends: wl-clipboard
|
||||
Recommends: NetworkManager
|
||||
Recommends: qt6-qtmultimedia
|
||||
Suggests: qt6ct
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for the niri and hyprland compositors. Features notifications,
|
||||
app launcher, wallpaper customization, and fully customizable with plugins.
|
||||
|
||||
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
process monitoring, notification center, clipboard history, dock, control center,
|
||||
lock screen, and comprehensive plugin system.
|
||||
|
||||
%package -n dms-cli
|
||||
Summary: DankMaterialShell CLI tool
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
%description -n dms-cli
|
||||
Command-line interface for DankMaterialShell configuration and management.
|
||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||
|
||||
%prep
|
||||
%setup -q -c -n dms-qml
|
||||
|
||||
# Download architecture-specific binaries during build
|
||||
case "%{_arch}" in
|
||||
x86_64)
|
||||
ARCH_SUFFIX="amd64"
|
||||
;;
|
||||
aarch64)
|
||||
ARCH_SUFFIX="arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: %{_arch}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v%{version}/dms-distropkg-${ARCH_SUFFIX}.gz" || {
|
||||
echo "Failed to download dms-cli for architecture %{_arch}"
|
||||
exit 1
|
||||
}
|
||||
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
||||
chmod +x %{_builddir}/dms-cli
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
||||
|
||||
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
||||
%{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
||||
%{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
%{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
|
||||
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
|
||||
install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
|
||||
install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
||||
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
||||
|
||||
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
||||
|
||||
%posttrans
|
||||
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||
# Download source tarball from GitHub releases
|
||||
echo "📦 Downloading source tarball for v${VERSION}..."
|
||||
if [ ! -f ~/rpmbuild/SOURCES/dms-qml.tar.gz ]; then
|
||||
wget -O ~/rpmbuild/SOURCES/dms-qml.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
|
||||
echo "❌ Failed to download dms-qml.tar.gz for v${VERSION}"
|
||||
exit 1
|
||||
}
|
||||
echo "✅ Source tarball downloaded"
|
||||
else
|
||||
echo "✅ Source tarball already exists"
|
||||
fi
|
||||
# Signal running DMS instances to reload
|
||||
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc README.md CONTRIBUTING.md
|
||||
%{_datadir}/quickshell/dms/
|
||||
%{_userunitdir}/dms.service
|
||||
%{_datadir}/applications/dms-open.desktop
|
||||
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
# Copy and prepare spec file
|
||||
echo "📝 Preparing spec file..."
|
||||
SPEC_FILE="$REPO_ROOT/distro/fedora/${PACKAGE}.spec"
|
||||
if [ ! -f "$SPEC_FILE" ]; then
|
||||
echo "❌ Spec file not found: $SPEC_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
%files -n dms-cli
|
||||
%{_bindir}/dms
|
||||
%{_datadir}/bash-completion/completions/dms
|
||||
%{_datadir}/zsh/site-functions/_dms
|
||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||
cp "$SPEC_FILE" ~/rpmbuild/SPECS/"${PACKAGE}".spec
|
||||
|
||||
%changelog
|
||||
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
||||
- Stable release VERSION_PLACEHOLDER
|
||||
- Built locally with corrected tarball
|
||||
SPECEOF
|
||||
# Replace placeholders in spec file
|
||||
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
||||
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
|
||||
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
|
||||
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/"${PACKAGE}".spec
|
||||
|
||||
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
echo "✅ Spec file prepared for ${PACKAGE} v${VERSION}-${RELEASE}"
|
||||
|
||||
# Build SRPM
|
||||
echo "Building SRPM..."
|
||||
echo "🔨 Building SRPM..."
|
||||
cd ~/rpmbuild/SPECS
|
||||
rpmbuild -bs dms.spec
|
||||
rpmbuild -bs "${PACKAGE}".spec
|
||||
|
||||
SRPM=$(ls ~/rpmbuild/SRPMS/dms-"${VERSION}"-*.src.rpm | tail -n 1)
|
||||
SRPM=$(ls ~/rpmbuild/SRPMS/"${PACKAGE}"-"${VERSION}"-*.src.rpm | tail -n 1)
|
||||
if [ ! -f "$SRPM" ]; then
|
||||
echo "Error: SRPM not found!"
|
||||
echo "❌ Error: SRPM not found!"
|
||||
echo "Expected pattern: ${PACKAGE}-${VERSION}-*.src.rpm"
|
||||
ls -la ~/rpmbuild/SRPMS/ || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SRPM built successfully: $SRPM"
|
||||
echo "✅ SRPM built successfully: $SRPM"
|
||||
|
||||
# Check if copr-cli is installed
|
||||
if ! command -v copr-cli &>/dev/null; then
|
||||
echo ""
|
||||
echo "copr-cli is not installed. Install it with:"
|
||||
echo "⚠️ copr-cli is not installed. Install it with:"
|
||||
echo " pip install copr-cli"
|
||||
echo ""
|
||||
echo "Then configure it with your Copr API token in ~/.config/copr"
|
||||
echo ""
|
||||
echo "SRPM is ready at: $SRPM"
|
||||
echo "Upload manually with: copr-cli build avengemedia/dms $SRPM"
|
||||
echo "Upload manually with: copr-cli build $COPR_PROJECT $SRPM"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Upload to Copr
|
||||
echo ""
|
||||
echo "Uploading to Copr..."
|
||||
if copr-cli build avengemedia/dms "$SRPM" --nowait; then
|
||||
echo "🚀 Uploading to Copr..."
|
||||
if copr-cli build "$COPR_PROJECT" "$SRPM" --nowait; then
|
||||
echo ""
|
||||
echo "Build submitted successfully! Check status at:"
|
||||
echo "https://copr.fedorainfracloud.org/coprs/avengemedia/dms/builds/"
|
||||
echo "✅ Build submitted successfully!"
|
||||
echo "📊 Check status at:"
|
||||
echo " https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/builds/"
|
||||
echo ""
|
||||
echo "📦 SRPM location: $SRPM"
|
||||
else
|
||||
echo ""
|
||||
echo "Copr upload failed. You can manually upload the SRPM:"
|
||||
echo " copr-cli build avengemedia/dms $SRPM"
|
||||
echo "❌ Copr upload failed. You can manually upload the SRPM:"
|
||||
echo " copr-cli build $COPR_PROJECT $SRPM"
|
||||
echo ""
|
||||
echo "Or upload via web interface:"
|
||||
echo " https://copr.fedorainfracloud.org/coprs/avengemedia/dms/builds/"
|
||||
echo " https://copr.fedorainfracloud.org/coprs/${COPR_PROJECT}/builds/"
|
||||
echo ""
|
||||
echo "SRPM location: $SRPM"
|
||||
exit 1
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
# ./distro/scripts/obs-upload.sh dms "Update to v1.0.2"
|
||||
# ./distro/scripts/obs-upload.sh debian dms
|
||||
# ./distro/scripts/obs-upload.sh opensuse dms-git
|
||||
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with ppa2 suffix
|
||||
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with ppa2 suffix (flag syntax)
|
||||
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with db2 suffix
|
||||
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with db2 suffix (flag syntax)
|
||||
|
||||
set -e
|
||||
|
||||
@@ -126,8 +126,8 @@ check_obs_version_exists() {
|
||||
OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs)
|
||||
# Commit hash check for -git packages
|
||||
if [[ "$CHECK_MODE" == "commit" ]] && [[ "$PACKAGE" == *"-git" ]]; then
|
||||
OBS_COMMIT=$(echo "$OBS_VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||
NEW_COMMIT=$(echo "$VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||
OBS_COMMIT=$(echo "$OBS_VERSION" | grep -oP '\.([a-f0-9]{8})(db[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||
NEW_COMMIT=$(echo "$VERSION" | grep -oP '\.([a-f0-9]{8})(db[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||
|
||||
if [[ -n "$OBS_COMMIT" && -n "$NEW_COMMIT" && "$OBS_COMMIT" == "$NEW_COMMIT" ]]; then
|
||||
echo "⚠️ Commit $NEW_COMMIT already exists in OBS (current version: $OBS_VERSION)"
|
||||
@@ -279,7 +279,8 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
||||
|
||||
# Apply rebuild suffix if specified (must happen before API check)
|
||||
if [[ -n "$REBUILD_RELEASE" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||
CHANGELOG_VERSION="${CHANGELOG_VERSION}ppa${REBUILD_RELEASE}"
|
||||
BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/db[0-9]*$//')
|
||||
CHANGELOG_VERSION="${BASE_VERSION}db${REBUILD_RELEASE}"
|
||||
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
|
||||
fi
|
||||
|
||||
@@ -307,12 +308,16 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
||||
else
|
||||
# Rebuild number specified - check if this exact version already exists (exact mode)
|
||||
if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION" "exact"; then
|
||||
echo "==> Error: Version $CHANGELOG_VERSION already exists in OBS"
|
||||
echo " This exact version (including ppa${REBUILD_RELEASE}) is already uploaded."
|
||||
echo " To rebuild with a different release number, try incrementing:"
|
||||
echo "==> Version $CHANGELOG_VERSION already exists in OBS"
|
||||
echo " This exact version (including db${REBUILD_RELEASE}) is already uploaded."
|
||||
echo " Skipping upload - nothing to do."
|
||||
echo ""
|
||||
echo " 💡 To rebuild with a different release number, try incrementing:"
|
||||
NEXT_NUM=$((REBUILD_RELEASE + 1))
|
||||
echo " ./distro/scripts/obs-upload.sh $PACKAGE $NEXT_NUM"
|
||||
exit 1
|
||||
echo " REBUILD_RELEASE=$NEXT_NUM"
|
||||
echo ""
|
||||
echo "✓ Exiting gracefully (no changes needed)"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -511,7 +516,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
|
||||
if [[ -n "$URL_PROTOCOL" && -n "$URL_HOST" && -n "$URL_PATH" ]]; then
|
||||
SOURCE_URL="${URL_PROTOCOL}://${URL_HOST}${URL_PATH}"
|
||||
echo " Downloading source from: $SOURCE_URL"
|
||||
echo "==> Downloading source from: $SOURCE_URL"
|
||||
|
||||
if wget -q -O "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null ||
|
||||
curl -L -f -s -o "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null; then
|
||||
@@ -534,9 +539,17 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
fi
|
||||
SOURCE_DIR=$(cd "$SOURCE_DIR" && pwd)
|
||||
cd "$REPO_ROOT"
|
||||
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
|
||||
echo "ERROR: Failed to return to REPO_ROOT. Expected: $REPO_ROOT, Got: $(pwd)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error: Failed to download source from $SOURCE_URL"
|
||||
echo "Tried both wget and curl. Please check the URL and network connectivity."
|
||||
echo "ERROR: Failed to download source from $SOURCE_URL"
|
||||
echo "Attempted both wget and curl"
|
||||
echo "Please check:"
|
||||
echo " 1. URL is accessible: $SOURCE_URL"
|
||||
echo " 2. _service file has correct version"
|
||||
echo " 3. GitHub releases are available"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -553,7 +566,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Found source directory: $SOURCE_DIR"
|
||||
echo "==> Found source directory: $SOURCE_DIR"
|
||||
|
||||
# Vendor Go dependencies for dms-git
|
||||
if [[ "$PACKAGE" == "dms-git" ]] && [[ -d "$SOURCE_DIR/core" ]]; then
|
||||
@@ -712,6 +725,10 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
TARBALL_BASE=$(basename "$SOURCE_DIR")
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||
cd "$REPO_ROOT"
|
||||
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
|
||||
echo "ERROR: Failed to return to REPO_ROOT after tarball creation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGE" == "dms" ]]; then
|
||||
TARBALL_DIR=$(tar -tzf "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null | head -1 | cut -d'/' -f1)
|
||||
@@ -723,6 +740,10 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
rm -f "$WORK_DIR/$COMBINED_TARBALL"
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||
cd "$REPO_ROOT"
|
||||
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
|
||||
echo "ERROR: Failed to return to REPO_ROOT after tarball recreation"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -796,23 +817,29 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$WORK_DIR"
|
||||
echo "==> Ensuring we're in the OSC working directory"
|
||||
cd "$WORK_DIR" || {
|
||||
echo "ERROR: Cannot cd to WORK_DIR: $WORK_DIR"
|
||||
echo "DEBUG: Current directory: $(pwd)"
|
||||
echo "DEBUG: WORK_DIR exists: $(test -d "$WORK_DIR" && echo "yes" || echo "no")"
|
||||
exit 1
|
||||
}
|
||||
echo "DEBUG: Successfully entered WORK_DIR: $(pwd)"
|
||||
|
||||
# Server-side cleanup via API
|
||||
echo "==> Cleaning old tarballs from OBS server (prevents downloading 100+ old versions)"
|
||||
OBS_FILES=$(osc api "/source/$OBS_PROJECT/$PACKAGE" 2>/dev/null || echo "")
|
||||
if [[ -n "$OBS_FILES" ]]; then
|
||||
DELETED_COUNT=0
|
||||
KEEP_PATTERN=""
|
||||
KEEP_CURRENT=""
|
||||
if [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||
BASE_KEEP_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
|
||||
KEEP_PATTERN="${PACKAGE}_${BASE_KEEP_VERSION}"
|
||||
echo " Keeping tarballs matching: ${KEEP_PATTERN}*"
|
||||
KEEP_CURRENT="${PACKAGE}_${CHANGELOG_VERSION}.tar.gz"
|
||||
echo " Keeping only current version: ${KEEP_CURRENT}"
|
||||
fi
|
||||
|
||||
for old_file in $(echo "$OBS_FILES" | grep -oP '(?<=name=")[^"]*\.(tar\.gz|tar\.xz|tar\.bz2)(?=")' || true); do
|
||||
if [[ -n "$KEEP_PATTERN" ]] && [[ "$old_file" == ${KEEP_PATTERN}* ]]; then
|
||||
echo " - Keeping current version: $old_file"
|
||||
if [[ "$old_file" == "$KEEP_CURRENT" ]]; then
|
||||
echo " - Keeping: $old_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -835,14 +862,11 @@ else
|
||||
echo " ⚠️ Could not fetch file list from server, skipping cleanup"
|
||||
fi
|
||||
|
||||
# Fallback update with --server-side-source-service-files flag only syncs metadata (spec, dsc, _service)
|
||||
# Update working copy to latest revision (without expanding service files to avoid revision conflicts)
|
||||
echo "==> Updating working copy"
|
||||
if ! osc up --server-side-source-service-files 2>/dev/null; then
|
||||
echo " Note: Using regular update (--server-side-source-service-files not supported)"
|
||||
if ! osc up; then
|
||||
echo "Error: Failed to update working copy"
|
||||
exit 1
|
||||
fi
|
||||
if ! osc up 2>/dev/null; then
|
||||
echo "Error: Failed to update working copy"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure we're in WORK_DIR and it exists
|
||||
@@ -882,6 +906,15 @@ elif [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
||||
fi
|
||||
echo ""
|
||||
|
||||
if [[ "$(pwd)" != "$WORK_DIR" ]]; then
|
||||
echo "ERROR: Lost directory context. Expected: $WORK_DIR, Got: $(pwd)"
|
||||
cd "$WORK_DIR" || {
|
||||
echo "FATAL: Cannot recover - unable to cd to WORK_DIR"
|
||||
exit 1
|
||||
}
|
||||
echo "WARNING: Recovered directory context"
|
||||
fi
|
||||
|
||||
osc addremove 2>&1 | grep -v "Git SCM package" || true
|
||||
|
||||
SOURCE_TARBALL="${PACKAGE}-source.tar.gz"
|
||||
@@ -908,7 +941,7 @@ if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
|
||||
else
|
||||
echo "==> Committing to OBS"
|
||||
set +e
|
||||
osc commit -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
||||
osc commit --skip-local-service-run -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
||||
COMMIT_EXIT=${PIPESTATUS[0]}
|
||||
set -e
|
||||
if [[ $COMMIT_EXIT -ne 0 ]]; then
|
||||
|
||||
@@ -191,19 +191,8 @@ fi
|
||||
cd "$WORK_PACKAGE_DIR"
|
||||
get_latest_tag() {
|
||||
local repo="$1"
|
||||
if command -v curl &>/dev/null; then
|
||||
LATEST_TAG=$(curl -s "https://api.github.com/repos/$repo/releases/latest" 2>/dev/null | grep '"tag_name":' | sed 's/.*"tag_name": "\(.*\)".*/\1/' | head -1)
|
||||
if [ -n "$LATEST_TAG" ]; then
|
||||
echo "$LATEST_TAG" | sed 's/^v//'
|
||||
return
|
||||
fi
|
||||
fi
|
||||
TEMP_REPO=$(mktemp -d "$TEMP_BASE/ppa_tag_XXXXXX")
|
||||
if git clone --depth=1 --quiet "https://github.com/$repo.git" "$TEMP_REPO" 2>/dev/null; then
|
||||
LATEST_TAG=$(cd "$TEMP_REPO" && git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "")
|
||||
rm -rf "$TEMP_REPO"
|
||||
echo "$LATEST_TAG"
|
||||
fi
|
||||
# Get the latest tag, sorted by version
|
||||
git ls-remote --tags --refs --sort='-v:refname' "https://github.com/$repo.git" | head -n1 | awk -F/ '{print $NF}' | sed 's/^v//'
|
||||
}
|
||||
|
||||
IS_GIT_PACKAGE=false
|
||||
@@ -334,6 +323,17 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "dms-distropkg-arm64.gz" ]; then
|
||||
info "Downloading dms binary for arm64..."
|
||||
# Try to download arm64 binary, but don't fail if it doesn't exist (yet)
|
||||
if wget -O dms-distropkg-arm64.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-distropkg-arm64.gz"; then
|
||||
success "arm64 binary downloaded"
|
||||
else
|
||||
warn "Failed to download dms-distropkg-arm64.gz (skipping)"
|
||||
rm -f dms-distropkg-arm64.gz
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "dms-source.tar.gz" ]; then
|
||||
info "Downloading dms source for QML files..."
|
||||
if wget -O dms-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then
|
||||
|
||||
@@ -341,6 +341,10 @@ if [ "$KEEP_BUILDS" = "false" ]; then
|
||||
rm -f "$PACKAGE_DIR/dms-distropkg-amd64.gz"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
if [ -f "$PACKAGE_DIR/dms-distropkg-arm64.gz" ]; then
|
||||
rm -f "$PACKAGE_DIR/dms-distropkg-arm64.gz"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
if [ -f "$PACKAGE_DIR/dms-source.tar.gz" ]; then
|
||||
rm -f "$PACKAGE_DIR/dms-source.tar.gz"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
danksearch (0.0.7ppa3) questing; urgency=medium
|
||||
|
||||
* Rebuild for packaging fixes (ppa3)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Fri, 21 Nov 2025 14:19:58 -0500
|
||||
@@ -1,24 +0,0 @@
|
||||
Source: danksearch
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13)
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/AvengeMedia/danksearch
|
||||
Vcs-Browser: https://github.com/AvengeMedia/danksearch
|
||||
Vcs-Git: https://github.com/AvengeMedia/danksearch.git
|
||||
|
||||
Package: danksearch
|
||||
Architecture: amd64 arm64
|
||||
Depends: ${misc:Depends}
|
||||
Description: Fast file search utility for DMS
|
||||
DankSearch is a fast file search utility designed for DankMaterialShell.
|
||||
It provides efficient file and content search capabilities with minimal
|
||||
dependencies. This package contains the pre-built binary from the official
|
||||
GitHub release.
|
||||
.
|
||||
Features include:
|
||||
- Fast file searching
|
||||
- Lightweight and efficient
|
||||
- Designed for DMS integration
|
||||
- Minimal resource usage
|
||||
@@ -1,24 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: danksearch
|
||||
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
|
||||
Source: https://github.com/AvengeMedia/danksearch
|
||||
|
||||
Files: *
|
||||
Copyright: 2025 Avenge Media LLC
|
||||
License: GPL-3.0-only
|
||||
|
||||
License: GPL-3.0-only
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 3 as
|
||||
published by the Free Software Foundation.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# Detect architecture for selecting correct binary
|
||||
DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
|
||||
|
||||
# Map Debian arch to binary filename
|
||||
ifeq ($(DEB_HOST_ARCH),amd64)
|
||||
BINARY_FILE := dsearch-amd64
|
||||
else ifeq ($(DEB_HOST_ARCH),arm64)
|
||||
BINARY_FILE := dsearch-arm64
|
||||
else
|
||||
$(error Unsupported architecture: $(DEB_HOST_ARCH))
|
||||
endif
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
# Binary is already included in source package (native format)
|
||||
# Downloaded by build-source.sh before upload
|
||||
# Just verify it exists and is executable
|
||||
test -f $(BINARY_FILE) || (echo "ERROR: $(BINARY_FILE) not found!" && exit 1)
|
||||
chmod +x $(BINARY_FILE)
|
||||
|
||||
override_dh_auto_install:
|
||||
# Install binary as danksearch
|
||||
install -Dm755 $(BINARY_FILE) debian/danksearch/usr/bin/danksearch
|
||||
|
||||
override_dh_auto_clean:
|
||||
# Don't delete binaries - they're part of the source package (native format)
|
||||
dh_auto_clean
|
||||
@@ -1 +0,0 @@
|
||||
3.0 (native)
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
dgop (0.1.11ppa2) questing; urgency=medium
|
||||
|
||||
* Rebuild for Questing (25.10) - Ubuntu 25.10+ only
|
||||
* Stateless CPU/GPU monitoring tool
|
||||
* Support for NVIDIA and AMD GPUs
|
||||
* JSON output for integration
|
||||
* Pre-built binary package for amd64 and arm64
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sun, 16 Nov 2025 22:50:00 -0500
|
||||
@@ -1,27 +0,0 @@
|
||||
Source: dgop
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
wget,
|
||||
gzip
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/AvengeMedia/dgop
|
||||
Vcs-Browser: https://github.com/AvengeMedia/dgop
|
||||
Vcs-Git: https://github.com/AvengeMedia/dgop.git
|
||||
|
||||
Package: dgop
|
||||
Architecture: amd64 arm64
|
||||
Depends: ${misc:Depends}
|
||||
Description: Stateless CPU/GPU monitor for DankMaterialShell
|
||||
DGOP is a stateless system monitoring tool that provides CPU, GPU,
|
||||
memory, and network statistics. Designed for integration with
|
||||
DankMaterialShell but can be used standalone.
|
||||
.
|
||||
Features:
|
||||
- CPU usage monitoring
|
||||
- GPU usage and temperature (NVIDIA, AMD)
|
||||
- Memory and swap statistics
|
||||
- Network traffic monitoring
|
||||
- Zero-state design (no background processes)
|
||||
- JSON output for easy integration
|
||||
@@ -1,27 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: dgop
|
||||
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
|
||||
Source: https://github.com/AvengeMedia/dgop
|
||||
|
||||
Files: *
|
||||
Copyright: 2025 Avenge Media LLC
|
||||
License: MIT
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# Extract version from debian/changelog
|
||||
DEB_VERSION := $(shell dpkg-parsechangelog -S Version)
|
||||
# Get upstream version (strip -1ppa1 suffix)
|
||||
UPSTREAM_VERSION := $(shell echo $(DEB_VERSION) | sed 's/-[^-]*$$//')
|
||||
|
||||
# Detect architecture for downloading correct binary
|
||||
DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
|
||||
|
||||
# Map Debian arch to GitHub release arch names
|
||||
ifeq ($(DEB_HOST_ARCH),amd64)
|
||||
GITHUB_ARCH := amd64
|
||||
else ifeq ($(DEB_HOST_ARCH),arm64)
|
||||
GITHUB_ARCH := arm64
|
||||
else
|
||||
$(error Unsupported architecture: $(DEB_HOST_ARCH))
|
||||
endif
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
# Binary is already included in source package (native format)
|
||||
# Just verify it exists and is executable
|
||||
test -f dgop || (echo "ERROR: dgop binary not found!" && exit 1)
|
||||
chmod +x dgop
|
||||
|
||||
override_dh_auto_install:
|
||||
# Install binary
|
||||
install -Dm755 dgop debian/dgop/usr/bin/dgop
|
||||
|
||||
override_dh_auto_clean:
|
||||
# Don't delete dgop binary - it's part of the source package (native format)
|
||||
rm -f dgop.gz
|
||||
dh_auto_clean
|
||||
@@ -1 +0,0 @@
|
||||
3.0 (native)
|
||||
@@ -10,7 +10,7 @@ Vcs-Browser: https://github.com/AvengeMedia/DankMaterialShell
|
||||
Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
|
||||
|
||||
Package: dms-git
|
||||
Architecture: amd64
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends},
|
||||
quickshell-git | quickshell,
|
||||
accountsservice,
|
||||
|
||||
@@ -32,15 +32,17 @@ override_dh_auto_build:
|
||||
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' dms-git-repo/core/go.mod
|
||||
|
||||
# Build dms-cli from source
|
||||
# Detect architecture
|
||||
$(eval DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH))
|
||||
@if [ -f dms-git-repo/.dms-version ]; then \
|
||||
. dms-git-repo/.dms-version; \
|
||||
echo "Building with VERSION=$$VERSION COMMIT=$$COMMIT"; \
|
||||
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=amd64 VERSION="$$VERSION" COMMIT="$$COMMIT"; \
|
||||
echo "Building with VERSION=$$VERSION COMMIT=$$COMMIT ARCH=$(DEB_HOST_ARCH)"; \
|
||||
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=$(DEB_HOST_ARCH) VERSION="$$VERSION" COMMIT="$$COMMIT"; \
|
||||
else \
|
||||
echo "Warning: .dms-version not found, building without version info"; \
|
||||
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=amd64; \
|
||||
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=$(DEB_HOST_ARCH); \
|
||||
fi
|
||||
cp dms-git-repo/core/bin/dms-linux-amd64 dms
|
||||
cp dms-git-repo/core/bin/dms-linux-$(DEB_HOST_ARCH) dms
|
||||
chmod +x dms
|
||||
|
||||
override_dh_auto_install:
|
||||
|
||||
@@ -9,15 +9,15 @@ Vcs-Browser: https://github.com/AvengeMedia/DankMaterialShell
|
||||
Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
|
||||
|
||||
Package: dms-greeter
|
||||
Architecture: all
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends},
|
||||
greetd,
|
||||
quickshell-git | quickshell
|
||||
greetd,
|
||||
quickshell-git | quickshell
|
||||
Recommends: niri | hyprland | sway
|
||||
Description: DankMaterialShell greeter for greetd
|
||||
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
||||
inspired greeter interface built with Quickshell for Wayland compositors.
|
||||
.
|
||||
Supports multiple compositors including Niri, Hyprland, and Sway with automatic
|
||||
compositor detection and configuration. Features session selection, user
|
||||
authentication, and dynamic theming.
|
||||
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
||||
inspired greeter interface built with Quickshell for Wayland compositors.
|
||||
.
|
||||
Supports multiple compositors including Niri, Hyprland, and Sway with automatic
|
||||
compositor detection and configuration. Features session selection, user
|
||||
authentication, and dynamic theming.
|
||||
|
||||
@@ -9,9 +9,9 @@ Vcs-Browser: https://github.com/AvengeMedia/DankMaterialShell
|
||||
Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
|
||||
|
||||
Package: dms
|
||||
Architecture: amd64
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends},
|
||||
quickshell-git | quickshell,
|
||||
quickshell | quickshell-git,
|
||||
accountsservice,
|
||||
cava,
|
||||
cliphist,
|
||||
|
||||
@@ -19,11 +19,14 @@ override_dh_installsystemd:
|
||||
override_dh_auto_build:
|
||||
# All files are included in source package (downloaded by build-source.sh)
|
||||
# Launchpad build environment has no internet access
|
||||
test -f dms-distropkg-amd64.gz || (echo "ERROR: dms-distropkg-amd64.gz not found!" && exit 1)
|
||||
$(eval DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH))
|
||||
@echo "Building for architecture: $(DEB_HOST_ARCH)"
|
||||
|
||||
test -f dms-distropkg-$(DEB_HOST_ARCH).gz || (echo "ERROR: dms-distropkg-$(DEB_HOST_ARCH).gz not found!" && exit 1)
|
||||
test -f dms-source.tar.gz || (echo "ERROR: dms-source.tar.gz not found!" && exit 1)
|
||||
|
||||
# Extract pre-built binary
|
||||
gunzip -c dms-distropkg-amd64.gz > dms
|
||||
gunzip -c dms-distropkg-$(DEB_HOST_ARCH).gz > dms
|
||||
chmod +x dms
|
||||
|
||||
# Extract source tarball for QML files
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user