mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-04 11:32:12 -04:00
Compare commits
66 Commits
displaycon
...
dca07a70f8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
.gitignore
vendored
3
.gitignore
vendored
@@ -96,11 +96,12 @@ go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
.env*
|
||||
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
# .vscode/
|
||||
vim/
|
||||
|
||||
bin/
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
This file is more of a quick reference so I know what to account for before next releases.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
- Added clipboard and clipboard history integration
|
||||
- Added swipe to dismiss notification popups and from center
|
||||
- Added paste from clipboard history view - requires wtype
|
||||
- Optimize surface damage of OSD & Toast
|
||||
- Add monitor configurator (niri, Hyprland, MangoWC)
|
||||
- **BREAKING** ghostty theme changed to ~/.config/ghostty/themes/danktheme
|
||||
- requires intervention and doc update
|
||||
- Added desktop widget plugins
|
||||
- dev guidance available
|
||||
- builtin clock & dgop widgets
|
||||
- Initial RTL support/i18n
|
||||
|
||||
@@ -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 ];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,25 @@ import (
|
||||
|
||||
type ipcTargets map[string]map[string][]string
|
||||
|
||||
// getProcessExitCode returns the exit code from a ProcessState.
|
||||
// For normal exits, returns the exit code directly.
|
||||
// For signal termination, returns 128 + signal number (Unix convention).
|
||||
func getProcessExitCode(state *os.ProcessState) int {
|
||||
if state == nil {
|
||||
return 1
|
||||
}
|
||||
if code := state.ExitCode(); code != -1 {
|
||||
return code
|
||||
}
|
||||
// Process was killed by signal - extract signal number
|
||||
if status, ok := state.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Signaled() {
|
||||
return 128 + int(status.Signal())
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
var isSessionManaged bool
|
||||
|
||||
func execDetachedRestart(targetPID int) {
|
||||
@@ -214,14 +233,28 @@ func runShellInteractive(session bool) {
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
// Handle SIGUSR1 restart for non-session managed processes
|
||||
if sig == syscall.SIGUSR1 && !isSessionManaged {
|
||||
if sig == syscall.SIGUSR1 {
|
||||
if isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, exiting for systemd restart...")
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Infof("Received SIGUSR1, spawning detached restart process...")
|
||||
execDetachedRestart(os.Getpid())
|
||||
// Exit immediately to avoid race conditions with detached restart
|
||||
return
|
||||
}
|
||||
|
||||
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
|
||||
select {
|
||||
case <-errChan:
|
||||
cancel()
|
||||
os.Remove(socketPath)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
|
||||
log.Infof("\nReceived signal %v, shutting down...", sig)
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
@@ -235,7 +268,7 @@ func runShellInteractive(session bool) {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,15 +473,28 @@ func runShellDaemon(session bool) {
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
// Handle SIGUSR1 restart for non-session managed processes
|
||||
if sig == syscall.SIGUSR1 && !isSessionManaged {
|
||||
if sig == syscall.SIGUSR1 {
|
||||
if isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, exiting for systemd restart...")
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Infof("Received SIGUSR1, spawning detached restart process...")
|
||||
execDetachedRestart(os.Getpid())
|
||||
// Exit immediately to avoid race conditions with detached restart
|
||||
return
|
||||
}
|
||||
|
||||
// All other signals: clean shutdown
|
||||
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
|
||||
select {
|
||||
case <-errChan:
|
||||
cancel()
|
||||
os.Remove(socketPath)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
@@ -460,7 +506,7 @@ func runShellDaemon(session bool) {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -468,7 +468,7 @@ func TestHyprlandConfigStructure(t *testing.T) {
|
||||
func TestGhosttyConfigStructure(t *testing.T) {
|
||||
assert.Contains(t, GhosttyConfig, "window-decoration = false")
|
||||
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
|
||||
assert.Contains(t, GhosttyConfig, "config-file = ./config-dankcolors")
|
||||
assert.Contains(t, GhosttyConfig, "theme = dankcolors")
|
||||
}
|
||||
|
||||
func TestGhosttyColorConfigStructure(t *testing.T) {
|
||||
|
||||
@@ -48,4 +48,4 @@ keybind = shift+enter=text:\n
|
||||
gtk-single-instance = true
|
||||
|
||||
# Dank color generation
|
||||
config-file = ./config-dankcolors
|
||||
theme = dankcolors
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -274,6 +274,9 @@ output_path = '%s'
|
||||
if !opts.ShouldSkipTemplate("wezterm") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, "wezterm", "wezterm.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("nvim") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, "nvim", "neovim.toml")
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("dgop") {
|
||||
appendConfig(opts, cfgFile, "dgop", "dgop.toml")
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -319,10 +319,6 @@ func (m *Manager) readAndStore(r *os.File, mimeType string) {
|
||||
m.storeClipboardEntry(data, mimeType)
|
||||
}
|
||||
|
||||
if !cfg.DisablePersist {
|
||||
m.persistClipboard([]string{mimeType}, map[string][]byte{mimeType: data})
|
||||
}
|
||||
|
||||
m.updateState()
|
||||
m.notifySubscribers()
|
||||
}
|
||||
@@ -348,105 +344,6 @@ func (m *Manager) storeClipboardEntry(data []byte, mimeType string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) persistClipboard(mimeTypes []string, data map[string][]byte) {
|
||||
m.persistMutex.Lock()
|
||||
m.persistMimeTypes = mimeTypes
|
||||
m.persistData = data
|
||||
m.persistMutex.Unlock()
|
||||
|
||||
m.post(func() {
|
||||
m.takePersistOwnership()
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Manager) takePersistOwnership() {
|
||||
if m.dataControlMgr == nil || m.dataDevice == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if m.getConfig().DisablePersist {
|
||||
return
|
||||
}
|
||||
|
||||
m.persistMutex.RLock()
|
||||
mimeTypes := m.persistMimeTypes
|
||||
m.persistMutex.RUnlock()
|
||||
|
||||
if len(mimeTypes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
|
||||
|
||||
source, err := dataMgr.CreateDataSource()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create persist source: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, mime := range mimeTypes {
|
||||
if err := source.Offer(mime); err != nil {
|
||||
log.Errorf("Failed to offer mime type %s: %v", mime, err)
|
||||
}
|
||||
}
|
||||
|
||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||
fd := e.Fd
|
||||
defer syscall.Close(fd)
|
||||
|
||||
m.persistMutex.RLock()
|
||||
d := m.persistData[e.MimeType]
|
||||
m.persistMutex.RUnlock()
|
||||
|
||||
if len(d) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "clipboard-pipe")
|
||||
defer file.Close()
|
||||
file.Write(d)
|
||||
})
|
||||
|
||||
source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) {
|
||||
m.ownerLock.Lock()
|
||||
m.isOwner = false
|
||||
m.ownerLock.Unlock()
|
||||
})
|
||||
|
||||
if m.currentSource != nil {
|
||||
oldSource := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||
oldSource.Destroy()
|
||||
}
|
||||
m.currentSource = source
|
||||
|
||||
device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1)
|
||||
if err := device.SetSelection(source); err != nil {
|
||||
log.Errorf("Failed to set persist selection: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
m.ownerLock.Lock()
|
||||
m.isOwner = true
|
||||
m.ownerLock.Unlock()
|
||||
}
|
||||
|
||||
func (m *Manager) releaseOwnership() {
|
||||
m.ownerLock.Lock()
|
||||
m.isOwner = false
|
||||
m.ownerLock.Unlock()
|
||||
|
||||
m.persistMutex.Lock()
|
||||
m.persistData = nil
|
||||
m.persistMimeTypes = nil
|
||||
m.persistMutex.Unlock()
|
||||
|
||||
if m.currentSource != nil {
|
||||
source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||
source.Destroy()
|
||||
m.currentSource = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) storeEntry(entry Entry) error {
|
||||
if m.db == nil {
|
||||
return fmt.Errorf("database not available")
|
||||
@@ -495,6 +392,9 @@ func (m *Manager) deduplicateInTx(b *bolt.Bucket, hash uint64) error {
|
||||
}
|
||||
|
||||
func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
|
||||
if m.config.MaxHistory < 0 {
|
||||
return nil
|
||||
}
|
||||
c := b.Cursor()
|
||||
var count int
|
||||
for k, _ := c.Last(); k != nil; k, _ = c.Prev() {
|
||||
@@ -1309,13 +1209,7 @@ func (m *Manager) applyConfigChange(newCfg Config) {
|
||||
}
|
||||
}
|
||||
|
||||
if newCfg.DisablePersist && !oldCfg.DisablePersist {
|
||||
log.Info("Clipboard persist disabled, releasing ownership")
|
||||
m.releaseOwnership()
|
||||
}
|
||||
|
||||
log.Infof("Clipboard config reloaded: disableHistory=%v disablePersist=%v",
|
||||
newCfg.DisableHistory, newCfg.DisablePersist)
|
||||
log.Infof("Clipboard config reloaded: disableHistory=%v", newCfg.DisableHistory)
|
||||
|
||||
m.updateState()
|
||||
m.notifySubscribers()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -204,9 +204,6 @@ func handleClipboardSetConfig(conn net.Conn, req models.Request) {
|
||||
if v, ok := req.Params["disableHistory"].(bool); ok {
|
||||
cfg.DisableHistory = v
|
||||
}
|
||||
if v, ok := req.Params["disablePersist"].(bool); ok {
|
||||
cfg.DisablePersist = v
|
||||
}
|
||||
|
||||
if err := clipboard.SaveConfig(cfg); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
|
||||
@@ -241,6 +241,7 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
|
||||
handle.SetAdaptiveSyncHandler(func(e wlr_output_management.ZwlrOutputHeadV1AdaptiveSyncEvent) {
|
||||
log.Debugf("WlrOutput: Head %d adaptive sync: %d", headID, e.State)
|
||||
head.adaptiveSync = e.State
|
||||
head.adaptiveSyncSupported = true
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
@@ -360,22 +361,23 @@ func (m *Manager) updateState() {
|
||||
}
|
||||
|
||||
output := Output{
|
||||
Name: head.name,
|
||||
Description: head.description,
|
||||
Make: head.make,
|
||||
Model: head.model,
|
||||
SerialNumber: head.serialNumber,
|
||||
PhysicalWidth: head.physicalWidth,
|
||||
PhysicalHeight: head.physicalHeight,
|
||||
Enabled: head.enabled,
|
||||
X: head.x,
|
||||
Y: head.y,
|
||||
Transform: head.transform,
|
||||
Scale: head.scale,
|
||||
CurrentMode: currentMode,
|
||||
Modes: modes,
|
||||
AdaptiveSync: head.adaptiveSync,
|
||||
ID: head.id,
|
||||
Name: head.name,
|
||||
Description: head.description,
|
||||
Make: head.make,
|
||||
Model: head.model,
|
||||
SerialNumber: head.serialNumber,
|
||||
PhysicalWidth: head.physicalWidth,
|
||||
PhysicalHeight: head.physicalHeight,
|
||||
Enabled: head.enabled,
|
||||
X: head.x,
|
||||
Y: head.y,
|
||||
Transform: head.transform,
|
||||
Scale: head.scale,
|
||||
CurrentMode: currentMode,
|
||||
Modes: modes,
|
||||
AdaptiveSync: head.adaptiveSync,
|
||||
AdaptiveSyncSupported: head.adaptiveSyncSupported,
|
||||
ID: head.id,
|
||||
}
|
||||
outputs = append(outputs, output)
|
||||
return true
|
||||
|
||||
@@ -17,22 +17,23 @@ type OutputMode struct {
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Make string `json:"make"`
|
||||
Model string `json:"model"`
|
||||
SerialNumber string `json:"serialNumber"`
|
||||
PhysicalWidth int32 `json:"physicalWidth"`
|
||||
PhysicalHeight int32 `json:"physicalHeight"`
|
||||
Enabled bool `json:"enabled"`
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
Transform int32 `json:"transform"`
|
||||
Scale float64 `json:"scale"`
|
||||
CurrentMode *OutputMode `json:"currentMode"`
|
||||
Modes []OutputMode `json:"modes"`
|
||||
AdaptiveSync uint32 `json:"adaptiveSync"`
|
||||
ID uint32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Make string `json:"make"`
|
||||
Model string `json:"model"`
|
||||
SerialNumber string `json:"serialNumber"`
|
||||
PhysicalWidth int32 `json:"physicalWidth"`
|
||||
PhysicalHeight int32 `json:"physicalHeight"`
|
||||
Enabled bool `json:"enabled"`
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
Transform int32 `json:"transform"`
|
||||
Scale float64 `json:"scale"`
|
||||
CurrentMode *OutputMode `json:"currentMode"`
|
||||
Modes []OutputMode `json:"modes"`
|
||||
AdaptiveSync uint32 `json:"adaptiveSync"`
|
||||
AdaptiveSyncSupported bool `json:"adaptiveSyncSupported"`
|
||||
ID uint32 `json:"id"`
|
||||
}
|
||||
|
||||
type State struct {
|
||||
@@ -72,25 +73,26 @@ type Manager struct {
|
||||
}
|
||||
|
||||
type headState struct {
|
||||
id uint32
|
||||
handle *wlr_output_management.ZwlrOutputHeadV1
|
||||
name string
|
||||
description string
|
||||
make string
|
||||
model string
|
||||
serialNumber string
|
||||
physicalWidth int32
|
||||
physicalHeight int32
|
||||
enabled bool
|
||||
x int32
|
||||
y int32
|
||||
transform int32
|
||||
scale float64
|
||||
currentModeID uint32
|
||||
modeIDs []uint32
|
||||
adaptiveSync uint32
|
||||
finished bool
|
||||
ready bool
|
||||
id uint32
|
||||
handle *wlr_output_management.ZwlrOutputHeadV1
|
||||
name string
|
||||
description string
|
||||
make string
|
||||
model string
|
||||
serialNumber string
|
||||
physicalWidth int32
|
||||
physicalHeight int32
|
||||
enabled bool
|
||||
x int32
|
||||
y int32
|
||||
transform int32
|
||||
scale float64
|
||||
currentModeID uint32
|
||||
modeIDs []uint32
|
||||
adaptiveSync uint32
|
||||
adaptiveSyncSupported bool
|
||||
finished bool
|
||||
ready bool
|
||||
}
|
||||
|
||||
type modeState struct {
|
||||
@@ -169,7 +171,7 @@ func stateChanged(old, new *State) bool {
|
||||
if oldOut.Transform != newOut.Transform || oldOut.Scale != newOut.Scale {
|
||||
return true
|
||||
}
|
||||
if oldOut.AdaptiveSync != newOut.AdaptiveSync {
|
||||
if oldOut.AdaptiveSync != newOut.AdaptiveSync || oldOut.AdaptiveSyncSupported != newOut.AdaptiveSyncSupported {
|
||||
return true
|
||||
}
|
||||
if (oldOut.CurrentMode == nil) != (newOut.CurrentMode == nil) {
|
||||
|
||||
@@ -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,7 +6,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.programs.dankMaterialShell;
|
||||
cfg = config.programs.dank-material-shell;
|
||||
in
|
||||
{
|
||||
packages = [
|
||||
|
||||
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
|
||||
|
||||
@@ -7,7 +7,7 @@ let
|
||||
inherit (lib) types;
|
||||
path = [
|
||||
"programs"
|
||||
"dankMaterialShell"
|
||||
"dank-material-shell"
|
||||
];
|
||||
|
||||
builtInRemovedMsg = "This is now built-in in DMS and doesn't need additional dependencies.";
|
||||
@@ -20,16 +20,17 @@ in
|
||||
(lib.mkRemovedOptionModule (
|
||||
path ++ [ "enableSystemSound" ]
|
||||
) "qtmultimedia is now included on dms-shell package.")
|
||||
./dms-rename.nix
|
||||
];
|
||||
|
||||
options.programs.dankMaterialShell = {
|
||||
options.programs.dank-material-shell = {
|
||||
enable = lib.mkEnableOption "DankMaterialShell";
|
||||
systemd = {
|
||||
enable = lib.mkEnableOption "DankMaterialShell systemd startup";
|
||||
restartIfChanged = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Auto-restart dms.service when dankMaterialShell changes";
|
||||
description = "Auto-restart dms.service when dank-material-shell changes";
|
||||
};
|
||||
};
|
||||
enableSystemMonitoring = lib.mkOption {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
46
docs/theme_gruvbox_material_hard.json
Normal file
46
docs/theme_gruvbox_material_hard.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"dark": {
|
||||
"name": "Gruvbox Material Dark Hard",
|
||||
"surface": "#202020",
|
||||
"surfaceText": "#fbf1c7",
|
||||
"surfaceVariant": "#2a2827",
|
||||
"surfaceVariantText": "#ebdbb2",
|
||||
"background": "#2a2827",
|
||||
"backgroundText": "#ddc7a1",
|
||||
"outline": "#5a524c",
|
||||
"surfaceContainer": "#2a2827",
|
||||
"surfaceContainerHigh": "#504945",
|
||||
"surfaceContainerHighest": "#5a524c",
|
||||
"primary": "#a9b665",
|
||||
"secondary": "#d8a657",
|
||||
"primaryText": "#292828",
|
||||
"primaryContainer": "#798247",
|
||||
"surfaceTint": "#7daea3",
|
||||
"error": "#ea6962",
|
||||
"warning": "#d3869b",
|
||||
"info": "#89b482",
|
||||
"matugen_type": "scheme-tonal-spot"
|
||||
},
|
||||
"light": {
|
||||
"name": "Gruvbox Material Light Hard",
|
||||
"surface": "#f9f5d7",
|
||||
"surfaceText": "#282828",
|
||||
"surfaceVariant": "#fbf1c7",
|
||||
"surfaceVariantText": "#3c3836",
|
||||
"background": "#fbf1c7",
|
||||
"backgroundText": "#654735",
|
||||
"outline": "#a89984",
|
||||
"surfaceContainer": "#fbf1c7",
|
||||
"surfaceContainerHigh": "#e0cfa9",
|
||||
"surfaceContainerHighest": "#a89984",
|
||||
"primary": "#6c782e",
|
||||
"secondary": "#b47109",
|
||||
"primaryText": "#292828",
|
||||
"primaryContainer": "#566026",
|
||||
"surfaceTint": "#45707a",
|
||||
"error": "#c14a4a",
|
||||
"warning": "#945e80",
|
||||
"info": "#4c7a5d",
|
||||
"matugen_type": "scheme-tonal-spot"
|
||||
}
|
||||
}
|
||||
46
docs/theme_gruvbox_material_medium.json
Normal file
46
docs/theme_gruvbox_material_medium.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"dark": {
|
||||
"name": "Gruvbox Material Dark Medium",
|
||||
"surface": "#292828",
|
||||
"surfaceText": "#fbf1c7",
|
||||
"surfaceVariant": "#32302f",
|
||||
"surfaceVariantText": "#ebdbb2",
|
||||
"background": "#32302f",
|
||||
"backgroundText": "#ddc7a1",
|
||||
"outline": "#665c54",
|
||||
"surfaceContainer": "#32302f",
|
||||
"surfaceContainerHigh": "#504945",
|
||||
"surfaceContainerHighest": "#665c54",
|
||||
"primary": "#a9b665",
|
||||
"secondary": "#d8a657",
|
||||
"primaryText": "#292828",
|
||||
"primaryContainer": "#798247",
|
||||
"surfaceTint": "#7daea3",
|
||||
"error": "#ea6962",
|
||||
"warning": "#d3869b",
|
||||
"info": "#89b482",
|
||||
"matugen_type": "scheme-tonal-spot"
|
||||
},
|
||||
"light": {
|
||||
"name": "Gruvbox Material Light Medium",
|
||||
"surface": "#fbf1c7",
|
||||
"surfaceText": "#282828",
|
||||
"surfaceVariant": "#f2e5bc",
|
||||
"surfaceVariantText": "#3c3836",
|
||||
"background": "#f2e5bc",
|
||||
"backgroundText": "#654735",
|
||||
"outline": "#bdae93",
|
||||
"surfaceContainer": "#f2e5bc",
|
||||
"surfaceContainerHigh": "#d5c4a1",
|
||||
"surfaceContainerHighest": "#bdae93",
|
||||
"primary": "#6c782e",
|
||||
"secondary": "#b47109",
|
||||
"primaryText": "#292828",
|
||||
"primaryContainer": "#535d23",
|
||||
"surfaceTint": "#45707a",
|
||||
"error": "#c14a4a",
|
||||
"warning": "#945e80",
|
||||
"info": "#6c782e",
|
||||
"matugen_type": "scheme-tonal-spot"
|
||||
}
|
||||
}
|
||||
46
docs/theme_gruvbox_material_soft.json
Normal file
46
docs/theme_gruvbox_material_soft.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"dark": {
|
||||
"name": "Gruvbox Material Dark Soft",
|
||||
"surface": "#32302f",
|
||||
"surfaceText": "#fbf1c7",
|
||||
"surfaceVariant": "#3c3836",
|
||||
"surfaceVariantText": "#ebdbb2",
|
||||
"background": "#3c3836",
|
||||
"backgroundText": "#ddc7a1",
|
||||
"outline": "#7c6f64",
|
||||
"surfaceContainer": "#3c3836",
|
||||
"surfaceContainerHigh": "#5a524c",
|
||||
"surfaceContainerHighest": "#7c6f64",
|
||||
"primary": "#a9b665",
|
||||
"secondary": "#d8a657",
|
||||
"primaryText": "#292828",
|
||||
"primaryContainer": "#798247",
|
||||
"surfaceTint": "#7daea3",
|
||||
"error": "#ea6962",
|
||||
"warning": "#d3869b",
|
||||
"info": "#89b482",
|
||||
"matugen_type": "scheme-tonal-spot"
|
||||
},
|
||||
"light": {
|
||||
"name": "Gruvbox Material Light Soft",
|
||||
"surface": "#f2e5bc",
|
||||
"surfaceText": "#282828",
|
||||
"surfaceVariant": "#ebdbb2",
|
||||
"surfaceVariantText": "#3c3836",
|
||||
"background": "#ebdbb2",
|
||||
"backgroundText": "#654735",
|
||||
"outline": "#a89984",
|
||||
"surfaceContainer": "#ebdbb2",
|
||||
"surfaceContainerHigh": "#c9b99a",
|
||||
"surfaceContainerHighest": "#a89984",
|
||||
"primary": "#6c782e",
|
||||
"secondary": "#b47109",
|
||||
"primaryText": "#292828",
|
||||
"primaryContainer": "#535d23",
|
||||
"surfaceTint": "#45707a",
|
||||
"error": "#c14a4a",
|
||||
"warning": "#945e80",
|
||||
"info": "#6c782e",
|
||||
"matugen_type": "scheme-tonal-spot"
|
||||
}
|
||||
}
|
||||
16
flake.nix
16
flake.nix
@@ -149,14 +149,24 @@
|
||||
}
|
||||
);
|
||||
|
||||
homeModules.dankMaterialShell.default = mkModuleWithDmsPkgs ./distro/nix/home.nix;
|
||||
homeModules.dank-material-shell = mkModuleWithDmsPkgs ./distro/nix/home.nix;
|
||||
|
||||
homeModules.dankMaterialShell.niri = import ./distro/nix/niri.nix;
|
||||
homeModules.default = self.homeModules.dank-material-shell;
|
||||
|
||||
nixosModules.dankMaterialShell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix;
|
||||
homeModules.niri = import ./distro/nix/niri.nix;
|
||||
|
||||
homeModules.dankMaterialShell.default = builtins.warn "dank-material-shell: flake output `homeModules.dankMaterialShell.default` has been renamed to `homeModules.dank-material-shell`" self.homeModules.dank-material-shell;
|
||||
|
||||
homeModules.dankMaterialShell.niri = builtins.warn "dank-material-shell: flake output `homeModules.dankMaterialShell.niri` has been renamed to `homeModules.niri`" self.homeModules.niri;
|
||||
|
||||
nixosModules.dank-material-shell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix;
|
||||
|
||||
nixosModules.default = self.nixosModules.dank-material-shell;
|
||||
|
||||
nixosModules.greeter = mkModuleWithDmsPkgs ./distro/nix/greeter.nix;
|
||||
|
||||
nixosModules.dankMaterialShell = builtins.warn "dank-material-shell: flake output `nixosModules.dankMaterialShell` has been renamed to `nixosModules.dank-material-shell`" self.nixosModules.dank-material-shell;
|
||||
|
||||
devShells = forEachSystem (
|
||||
system: pkgs:
|
||||
let
|
||||
|
||||
@@ -16,6 +16,9 @@ Singleton {
|
||||
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
|
||||
}
|
||||
|
||||
readonly property var _rtlLanguages: ["ar", "he", "iw", "fa", "ur", "ps", "sd", "dv", "yi", "ku"]
|
||||
readonly property bool isRtl: _rtlLanguages.includes(_lang)
|
||||
|
||||
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
|
||||
|
||||
property string currentLocale: "en"
|
||||
|
||||
@@ -8,6 +8,9 @@ Singleton {
|
||||
id: modalManager
|
||||
|
||||
signal closeAllModalsExcept(var excludedModal)
|
||||
signal modalChanged
|
||||
|
||||
property var currentModalsByScreen: ({})
|
||||
|
||||
function openModal(modal) {
|
||||
if (!modal.allowStacking) {
|
||||
@@ -17,5 +20,17 @@ Singleton {
|
||||
PopoutManager.closeAllPopouts();
|
||||
}
|
||||
TrayMenuManager.closeAllMenus();
|
||||
|
||||
const screenName = modal.effectiveScreen?.name ?? "unknown";
|
||||
currentModalsByScreen[screenName] = modal;
|
||||
modalChanged();
|
||||
}
|
||||
|
||||
function closeModal(modal) {
|
||||
const screenName = modal.effectiveScreen?.name ?? "unknown";
|
||||
if (currentModalsByScreen[screenName] === modal) {
|
||||
delete currentModalsByScreen[screenName];
|
||||
modalChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
pragma ComponentBehavior
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
@@ -282,6 +282,7 @@ Singleton {
|
||||
property bool matugenTemplateGhostty: true
|
||||
property bool matugenTemplateKitty: true
|
||||
property bool matugenTemplateFoot: true
|
||||
property bool matugenTemplateNeovim: true
|
||||
property bool matugenTemplateAlacritty: true
|
||||
property bool matugenTemplateWezterm: true
|
||||
property bool matugenTemplateDgop: true
|
||||
@@ -360,50 +361,190 @@ Singleton {
|
||||
property string displayNameMode: "system"
|
||||
property var screenPreferences: ({})
|
||||
property var showOnLastDisplay: ({})
|
||||
property var niriOutputSettings: ({})
|
||||
property var hyprlandOutputSettings: ({})
|
||||
|
||||
property var barConfigs: [
|
||||
{
|
||||
id: "default",
|
||||
name: "Main Bar",
|
||||
enabled: true,
|
||||
position: 0,
|
||||
screenPreferences: ["all"],
|
||||
showOnLastDisplay: true,
|
||||
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
|
||||
centerWidgets: ["music", "clock", "weather"],
|
||||
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
|
||||
spacing: 4,
|
||||
innerPadding: 4,
|
||||
bottomGap: 0,
|
||||
transparency: 1.0,
|
||||
widgetTransparency: 1.0,
|
||||
squareCorners: false,
|
||||
noBackground: false,
|
||||
gothCornersEnabled: false,
|
||||
gothCornerRadiusOverride: false,
|
||||
gothCornerRadiusValue: 12,
|
||||
borderEnabled: false,
|
||||
borderColor: "surfaceText",
|
||||
borderOpacity: 1.0,
|
||||
borderThickness: 1,
|
||||
widgetOutlineEnabled: false,
|
||||
widgetOutlineColor: "primary",
|
||||
widgetOutlineOpacity: 1.0,
|
||||
widgetOutlineThickness: 1,
|
||||
fontScale: 1.0,
|
||||
autoHide: false,
|
||||
autoHideDelay: 250,
|
||||
openOnOverview: false,
|
||||
visible: true,
|
||||
popupGapsAuto: true,
|
||||
popupGapsManual: 4,
|
||||
maximizeDetection: true,
|
||||
scrollEnabled: true,
|
||||
scrollXBehavior: "column",
|
||||
scrollYBehavior: "workspace"
|
||||
"id": "default",
|
||||
"name": "Main Bar",
|
||||
"enabled": true,
|
||||
"position": 0,
|
||||
"screenPreferences": ["all"],
|
||||
"showOnLastDisplay": true,
|
||||
"leftWidgets": ["launcherButton", "workspaceSwitcher", "focusedWindow"],
|
||||
"centerWidgets": ["music", "clock", "weather"],
|
||||
"rightWidgets": ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
|
||||
"spacing": 4,
|
||||
"innerPadding": 4,
|
||||
"bottomGap": 0,
|
||||
"transparency": 1.0,
|
||||
"widgetTransparency": 1.0,
|
||||
"squareCorners": false,
|
||||
"noBackground": false,
|
||||
"gothCornersEnabled": false,
|
||||
"gothCornerRadiusOverride": false,
|
||||
"gothCornerRadiusValue": 12,
|
||||
"borderEnabled": false,
|
||||
"borderColor": "surfaceText",
|
||||
"borderOpacity": 1.0,
|
||||
"borderThickness": 1,
|
||||
"widgetOutlineEnabled": false,
|
||||
"widgetOutlineColor": "primary",
|
||||
"widgetOutlineOpacity": 1.0,
|
||||
"widgetOutlineThickness": 1,
|
||||
"fontScale": 1.0,
|
||||
"autoHide": false,
|
||||
"autoHideDelay": 250,
|
||||
"openOnOverview": false,
|
||||
"visible": true,
|
||||
"popupGapsAuto": true,
|
||||
"popupGapsManual": 4,
|
||||
"maximizeDetection": true,
|
||||
"scrollEnabled": true,
|
||||
"scrollXBehavior": "column",
|
||||
"scrollYBehavior": "workspace"
|
||||
}
|
||||
]
|
||||
|
||||
property bool desktopClockEnabled: false
|
||||
property string desktopClockStyle: "analog"
|
||||
property real desktopClockTransparency: 0.8
|
||||
property string desktopClockColorMode: "primary"
|
||||
property color desktopClockCustomColor: "#ffffff"
|
||||
property bool desktopClockShowDate: true
|
||||
property bool desktopClockShowAnalogNumbers: false
|
||||
property bool desktopClockShowAnalogSeconds: true
|
||||
property real desktopClockX: -1
|
||||
property real desktopClockY: -1
|
||||
property real desktopClockWidth: 280
|
||||
property real desktopClockHeight: 180
|
||||
property var desktopClockDisplayPreferences: ["all"]
|
||||
|
||||
property bool systemMonitorEnabled: false
|
||||
property bool systemMonitorShowHeader: true
|
||||
property real systemMonitorTransparency: 0.8
|
||||
property string systemMonitorColorMode: "primary"
|
||||
property color systemMonitorCustomColor: "#ffffff"
|
||||
property bool systemMonitorShowCpu: true
|
||||
property bool systemMonitorShowCpuGraph: true
|
||||
property bool systemMonitorShowCpuTemp: true
|
||||
property bool systemMonitorShowGpuTemp: false
|
||||
property string systemMonitorGpuPciId: ""
|
||||
property bool systemMonitorShowMemory: true
|
||||
property bool systemMonitorShowMemoryGraph: true
|
||||
property bool systemMonitorShowNetwork: true
|
||||
property bool systemMonitorShowNetworkGraph: true
|
||||
property bool systemMonitorShowDisk: true
|
||||
property bool systemMonitorShowTopProcesses: false
|
||||
property int systemMonitorTopProcessCount: 3
|
||||
property string systemMonitorTopProcessSortBy: "cpu"
|
||||
property string systemMonitorLayoutMode: "auto"
|
||||
property int systemMonitorGraphInterval: 60
|
||||
property real systemMonitorX: -1
|
||||
property real systemMonitorY: -1
|
||||
property real systemMonitorWidth: 320
|
||||
property real systemMonitorHeight: 480
|
||||
property var systemMonitorDisplayPreferences: ["all"]
|
||||
property var systemMonitorVariants: []
|
||||
property var desktopWidgetPositions: ({})
|
||||
property var desktopWidgetGridSettings: ({})
|
||||
|
||||
function getDesktopWidgetGridSetting(screenKey, property, defaultValue) {
|
||||
const val = desktopWidgetGridSettings?.[screenKey]?.[property];
|
||||
return val !== undefined ? val : defaultValue;
|
||||
}
|
||||
|
||||
function setDesktopWidgetGridSetting(screenKey, property, value) {
|
||||
const allSettings = JSON.parse(JSON.stringify(desktopWidgetGridSettings || {}));
|
||||
if (!allSettings[screenKey])
|
||||
allSettings[screenKey] = {};
|
||||
allSettings[screenKey][property] = value;
|
||||
desktopWidgetGridSettings = allSettings;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getDesktopWidgetPosition(pluginId, screenKey, property, defaultValue) {
|
||||
const pos = desktopWidgetPositions?.[pluginId]?.[screenKey]?.[property];
|
||||
return pos !== undefined ? pos : defaultValue;
|
||||
}
|
||||
|
||||
function updateDesktopWidgetPosition(pluginId, screenKey, updates) {
|
||||
const allPositions = JSON.parse(JSON.stringify(desktopWidgetPositions || {}));
|
||||
if (!allPositions[pluginId])
|
||||
allPositions[pluginId] = {};
|
||||
allPositions[pluginId][screenKey] = Object.assign({}, allPositions[pluginId][screenKey] || {}, updates);
|
||||
desktopWidgetPositions = allPositions;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getSystemMonitorVariants() {
|
||||
return systemMonitorVariants || [];
|
||||
}
|
||||
|
||||
function createSystemMonitorVariant(name, config) {
|
||||
const id = "sysmon_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
||||
const variant = {
|
||||
id: id,
|
||||
name: name,
|
||||
config: config || getDefaultSystemMonitorConfig()
|
||||
};
|
||||
const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
|
||||
variants.push(variant);
|
||||
systemMonitorVariants = variants;
|
||||
saveSettings();
|
||||
return variant;
|
||||
}
|
||||
|
||||
function updateSystemMonitorVariant(variantId, updates) {
|
||||
const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
|
||||
const idx = variants.findIndex(v => v.id === variantId);
|
||||
if (idx === -1)
|
||||
return;
|
||||
Object.assign(variants[idx], updates);
|
||||
systemMonitorVariants = variants;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeSystemMonitorVariant(variantId) {
|
||||
const variants = (systemMonitorVariants || []).filter(v => v.id !== variantId);
|
||||
systemMonitorVariants = variants;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getSystemMonitorVariant(variantId) {
|
||||
return (systemMonitorVariants || []).find(v => v.id === variantId) || null;
|
||||
}
|
||||
|
||||
function getDefaultSystemMonitorConfig() {
|
||||
return {
|
||||
showHeader: true,
|
||||
transparency: 0.8,
|
||||
colorMode: "primary",
|
||||
customColor: "#ffffff",
|
||||
showCpu: true,
|
||||
showCpuGraph: true,
|
||||
showCpuTemp: true,
|
||||
showGpuTemp: false,
|
||||
gpuPciId: "",
|
||||
showMemory: true,
|
||||
showMemoryGraph: true,
|
||||
showNetwork: true,
|
||||
showNetworkGraph: true,
|
||||
showDisk: true,
|
||||
showTopProcesses: false,
|
||||
topProcessCount: 3,
|
||||
topProcessSortBy: "cpu",
|
||||
layoutMode: "auto",
|
||||
graphInterval: 60,
|
||||
x: -1,
|
||||
y: -1,
|
||||
width: 320,
|
||||
height: 480,
|
||||
displayPreferences: ["all"]
|
||||
};
|
||||
}
|
||||
|
||||
signal forceDankBarLayoutRefresh
|
||||
signal forceDockLayoutRefresh
|
||||
signal widgetDataChanged
|
||||
@@ -458,25 +599,25 @@ Singleton {
|
||||
|
||||
const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0
|
||||
|
||||
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
|
||||
settings_file="$config_dir/settings.ini"
|
||||
if [ -f "$settings_file" ]; then
|
||||
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
|
||||
settings_file="$config_dir/settings.ini"
|
||||
if [ -f "$settings_file" ]; then
|
||||
if grep -q "^gtk-icon-theme-name=" "$settings_file"; then
|
||||
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
|
||||
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
|
||||
else
|
||||
if grep -q "\\[Settings\\]" "$settings_file"; then
|
||||
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
|
||||
else
|
||||
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
|
||||
fi
|
||||
if grep -q "\\[Settings\\]" "$settings_file"; then
|
||||
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
|
||||
else
|
||||
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
|
||||
fi
|
||||
else
|
||||
fi
|
||||
else
|
||||
echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
|
||||
pkill -HUP -f 'gtk' 2>/dev/null || true`;
|
||||
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
|
||||
pkill -HUP -f 'gtk' 2>/dev/null || true`;
|
||||
|
||||
Quickshell.execDetached(["sh", "-lc", configScript]);
|
||||
}
|
||||
@@ -489,36 +630,36 @@ pkill -HUP -f 'gtk' 2>/dev/null || true`;
|
||||
const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
|
||||
|
||||
const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
|
||||
update_qt_icon_theme() {
|
||||
local config_file="$1"
|
||||
local theme_name="$2"
|
||||
if [ -f "$config_file" ]; then
|
||||
if grep -q "^\\[Appearance\\]" "$config_file"; then
|
||||
if grep -q "^icon_theme=" "$config_file"; then
|
||||
update_qt_icon_theme() {
|
||||
local config_file="$1"
|
||||
local theme_name="$2"
|
||||
if [ -f "$config_file" ]; then
|
||||
if grep -q "^\\[Appearance\\]" "$config_file"; then
|
||||
if grep -q "^icon_theme=" "$config_file"; then
|
||||
sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
|
||||
else
|
||||
else
|
||||
sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
|
||||
fi
|
||||
else
|
||||
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
|
||||
fi
|
||||
else
|
||||
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
|
||||
fi
|
||||
}
|
||||
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
|
||||
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
|
||||
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
|
||||
fi
|
||||
else
|
||||
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
|
||||
fi
|
||||
else
|
||||
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
|
||||
fi
|
||||
}
|
||||
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
|
||||
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
|
||||
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
|
||||
|
||||
Quickshell.execDetached(["sh", "-lc", script]);
|
||||
}
|
||||
|
||||
readonly property var _hooks: ({
|
||||
applyStoredTheme: applyStoredTheme,
|
||||
regenSystemThemes: regenSystemThemes,
|
||||
updateNiriLayout: updateNiriLayout,
|
||||
applyStoredIconTheme: applyStoredIconTheme,
|
||||
updateBarConfigs: updateBarConfigs
|
||||
"applyStoredTheme": applyStoredTheme,
|
||||
"regenSystemThemes": regenSystemThemes,
|
||||
"updateNiriLayout": updateNiriLayout,
|
||||
"applyStoredIconTheme": applyStoredIconTheme,
|
||||
"updateBarConfigs": updateBarConfigs
|
||||
})
|
||||
|
||||
function set(key, value) {
|
||||
@@ -543,7 +684,6 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
Store.parse(root, obj);
|
||||
applyStoredTheme();
|
||||
applyStoredIconTheme();
|
||||
Processes.detectIcons();
|
||||
Processes.detectQtTools();
|
||||
} catch (e) {
|
||||
console.warn("SettingsData: Failed to load settings:", e.message);
|
||||
@@ -590,7 +730,42 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
}
|
||||
|
||||
function detectAvailableIconThemes() {
|
||||
Processes.detectIcons();
|
||||
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") || "";
|
||||
const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation));
|
||||
const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
|
||||
|
||||
const dataDirs = xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat([localData]) : ["/usr/share", "/usr/local/share", localData];
|
||||
|
||||
const iconPaths = dataDirs.map(d => d + "/icons").concat([homeDir + "/.icons"]);
|
||||
const pathsArg = iconPaths.join(" ");
|
||||
|
||||
const script = `
|
||||
echo "SYSDEFAULT:$(gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g" || echo '')"
|
||||
for dir in ${pathsArg}; do
|
||||
[ -d "$dir" ] || continue
|
||||
for theme in "$dir"/*/; do
|
||||
[ -d "$theme" ] || continue
|
||||
basename "$theme"
|
||||
done
|
||||
done | grep -v '^icons$' | grep -v '^default$' | grep -v '^hicolor$' | grep -v '^locolor$' | sort -u
|
||||
`;
|
||||
|
||||
Proc.runCommand("detectIconThemes", ["sh", "-c", script], (output, exitCode) => {
|
||||
const themes = ["System Default"];
|
||||
if (output && output.trim()) {
|
||||
const lines = output.trim().split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line.startsWith("SYSDEFAULT:")) {
|
||||
systemDefaultIconTheme = line.substring(11).trim();
|
||||
continue;
|
||||
}
|
||||
if (line)
|
||||
themes.push(line);
|
||||
}
|
||||
}
|
||||
availableIconThemes = themes;
|
||||
});
|
||||
}
|
||||
|
||||
function getEffectiveTimeFormat() {
|
||||
@@ -723,7 +898,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
let leftBar = 0;
|
||||
let rightBar = 0;
|
||||
|
||||
for (let i = 0; i < enabledBars.length; i++) {
|
||||
for (var i = 0; i < enabledBars.length; i++) {
|
||||
const other = enabledBars[i];
|
||||
if (other.id === barConfig.id)
|
||||
continue;
|
||||
@@ -793,7 +968,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
|
||||
if (barConfig) {
|
||||
const enabledBars = getEnabledBarConfigs();
|
||||
for (let i = 0; i < enabledBars.length; i++) {
|
||||
for (var i = 0; i < enabledBars.length; i++) {
|
||||
const other = enabledBars[i];
|
||||
if (other.id === barConfig.id)
|
||||
continue;
|
||||
@@ -913,7 +1088,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
updateBarConfigs();
|
||||
|
||||
if (positionChanged) {
|
||||
NotificationService.clearAllPopups();
|
||||
NotificationService.dismissAllPopups();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,7 +1100,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const conflicts = [];
|
||||
const enabledBars = getEnabledBarConfigs();
|
||||
|
||||
for (let i = 0; i < enabledBars.length; i++) {
|
||||
for (var i = 0; i < enabledBars.length; i++) {
|
||||
const other = enabledBars[i];
|
||||
if (other.id === barId)
|
||||
continue;
|
||||
@@ -938,9 +1113,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const hasAll = barScreens.includes("all") || otherScreens.includes("all");
|
||||
if (hasAll) {
|
||||
conflicts.push({
|
||||
barId: other.id,
|
||||
barName: other.name,
|
||||
reason: "Same position on all screens"
|
||||
"barId": other.id,
|
||||
"barName": other.name,
|
||||
"reason": "Same position on all screens"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -948,9 +1123,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const overlapping = barScreens.some(screen => otherScreens.includes(screen));
|
||||
if (overlapping) {
|
||||
conflicts.push({
|
||||
barId: other.id,
|
||||
barName: other.name,
|
||||
reason: "Same position on overlapping screens"
|
||||
"barId": other.id,
|
||||
"barName": other.name,
|
||||
"reason": "Same position on overlapping screens"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -972,7 +1147,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
|
||||
function getScreensSortedByPosition() {
|
||||
const screens = [];
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
screens.push(Quickshell.screens[i]);
|
||||
}
|
||||
screens.sort((a, b) => {
|
||||
@@ -989,7 +1164,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const sorted = getScreensSortedByPosition();
|
||||
let modelCount = 0;
|
||||
let screenIndex = -1;
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
for (var i = 0; i < sorted.length; i++) {
|
||||
if (sorted[i].model === screen.model) {
|
||||
if (sorted[i].name === screen.name) {
|
||||
screenIndex = modelCount;
|
||||
@@ -1058,7 +1233,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
}
|
||||
|
||||
function sendTestNotifications() {
|
||||
NotificationService.clearAllPopups();
|
||||
NotificationService.dismissAllPopups();
|
||||
sendTestNotification(0);
|
||||
testNotifTimer1.start();
|
||||
testNotifTimer2.start();
|
||||
@@ -1187,7 +1362,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
spacing: spacing
|
||||
"spacing": spacing
|
||||
});
|
||||
}
|
||||
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
|
||||
@@ -1216,7 +1391,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
return;
|
||||
}
|
||||
updateBarConfig(defaultBar.id, {
|
||||
position: position
|
||||
"position": position
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1224,7 +1399,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
leftWidgets: order
|
||||
"leftWidgets": order
|
||||
});
|
||||
updateListModel(leftWidgetsModel, order);
|
||||
}
|
||||
@@ -1234,7 +1409,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
centerWidgets: order
|
||||
"centerWidgets": order
|
||||
});
|
||||
updateListModel(centerWidgetsModel, order);
|
||||
}
|
||||
@@ -1244,7 +1419,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
rightWidgets: order
|
||||
"rightWidgets": order
|
||||
});
|
||||
updateListModel(rightWidgetsModel, order);
|
||||
}
|
||||
@@ -1257,9 +1432,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
leftWidgets: defaultLeft,
|
||||
centerWidgets: defaultCenter,
|
||||
rightWidgets: defaultRight
|
||||
"leftWidgets": defaultLeft,
|
||||
"centerWidgets": defaultCenter,
|
||||
"rightWidgets": defaultRight
|
||||
});
|
||||
}
|
||||
updateListModel(leftWidgetsModel, defaultLeft);
|
||||
@@ -1307,7 +1482,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
visible: !defaultBar.visible
|
||||
"visible": !defaultBar.visible
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1345,6 +1520,87 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
|
||||
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
||||
}
|
||||
|
||||
function getNiriOutputSetting(outputId, key, defaultValue) {
|
||||
if (!niriOutputSettings[outputId])
|
||||
return defaultValue;
|
||||
return niriOutputSettings[outputId][key] !== undefined ? niriOutputSettings[outputId][key] : defaultValue;
|
||||
}
|
||||
|
||||
function setNiriOutputSetting(outputId, key, value) {
|
||||
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
|
||||
if (!updated[outputId])
|
||||
updated[outputId] = {};
|
||||
updated[outputId][key] = value;
|
||||
niriOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getNiriOutputSettings(outputId) {
|
||||
const settings = niriOutputSettings[outputId];
|
||||
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
||||
}
|
||||
|
||||
function setNiriOutputSettings(outputId, settings) {
|
||||
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
|
||||
updated[outputId] = settings;
|
||||
niriOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeNiriOutputSettings(outputId) {
|
||||
if (!niriOutputSettings[outputId])
|
||||
return;
|
||||
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
|
||||
delete updated[outputId];
|
||||
niriOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getHyprlandOutputSetting(outputId, key, defaultValue) {
|
||||
if (!hyprlandOutputSettings[outputId])
|
||||
return defaultValue;
|
||||
return hyprlandOutputSettings[outputId][key] !== undefined ? hyprlandOutputSettings[outputId][key] : defaultValue;
|
||||
}
|
||||
|
||||
function setHyprlandOutputSetting(outputId, key, value) {
|
||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
||||
if (!updated[outputId])
|
||||
updated[outputId] = {};
|
||||
updated[outputId][key] = value;
|
||||
hyprlandOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeHyprlandOutputSetting(outputId, key) {
|
||||
if (!hyprlandOutputSettings[outputId] || !(key in hyprlandOutputSettings[outputId]))
|
||||
return;
|
||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
||||
delete updated[outputId][key];
|
||||
hyprlandOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getHyprlandOutputSettings(outputId) {
|
||||
const settings = hyprlandOutputSettings[outputId];
|
||||
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
||||
}
|
||||
|
||||
function setHyprlandOutputSettings(outputId, settings) {
|
||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
||||
updated[outputId] = settings;
|
||||
hyprlandOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeHyprlandOutputSettings(outputId) {
|
||||
if (!hyprlandOutputSettings[outputId])
|
||||
return;
|
||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
||||
delete updated[outputId];
|
||||
hyprlandOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: leftWidgetsModel
|
||||
}
|
||||
|
||||
@@ -829,7 +829,7 @@ Singleton {
|
||||
if (typeof SettingsData !== "undefined") {
|
||||
const skipTemplates = [];
|
||||
if (!SettingsData.runDmsMatugenTemplates) {
|
||||
skipTemplates.push("gtk", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "vesktop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
|
||||
skipTemplates.push("gtk", "neovim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "vesktop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
|
||||
} else {
|
||||
if (!SettingsData.matugenTemplateGtk)
|
||||
skipTemplates.push("gtk");
|
||||
@@ -851,6 +851,8 @@ Singleton {
|
||||
skipTemplates.push("kitty");
|
||||
if (!SettingsData.matugenTemplateFoot)
|
||||
skipTemplates.push("foot");
|
||||
if (!SettingsData.matugenTemplateNeovim)
|
||||
skipTemplates.push("nvim");
|
||||
if (!SettingsData.matugenTemplateAlacritty)
|
||||
skipTemplates.push("alacritty");
|
||||
if (!SettingsData.matugenTemplateWezterm)
|
||||
@@ -918,6 +920,7 @@ Singleton {
|
||||
|
||||
function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
|
||||
const colors = {};
|
||||
const isLight = SessionData !== "undefined" && SessionData.isLightMode;
|
||||
|
||||
function addColor(matugenKey, darkVal, lightVal) {
|
||||
if (!darkVal && !lightVal)
|
||||
@@ -930,7 +933,7 @@ Singleton {
|
||||
"color": String(lightVal || darkVal)
|
||||
},
|
||||
"default": {
|
||||
"color": String(darkVal || lightVal)
|
||||
"color": String((isLight && lightVal) ? lightVal : darkVal)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pragma Singleton
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
@@ -11,61 +10,20 @@ Singleton {
|
||||
|
||||
property var settingsRoot: null
|
||||
|
||||
function detectIcons() {
|
||||
systemDefaultDetectionProcess.running = true
|
||||
}
|
||||
|
||||
function detectQtTools() {
|
||||
qtToolsDetectionProcess.running = true
|
||||
qtToolsDetectionProcess.running = true;
|
||||
}
|
||||
|
||||
function detectFprintd() {
|
||||
fprintdDetectionProcess.running = true
|
||||
fprintdDetectionProcess.running = true;
|
||||
}
|
||||
|
||||
function checkPluginSettings() {
|
||||
pluginSettingsCheckProcess.running = true
|
||||
pluginSettingsCheckProcess.running = true;
|
||||
}
|
||||
|
||||
function checkDefaultSettings() {
|
||||
defaultSettingsCheckProcess.running = true
|
||||
}
|
||||
|
||||
property var systemDefaultDetectionProcess: Process {
|
||||
command: ["sh", "-c", "gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed \"s/'//g\" || echo ''"]
|
||||
running: false
|
||||
onExited: function(exitCode) {
|
||||
if (!settingsRoot) return;
|
||||
if (exitCode === 0 && stdout && stdout.length > 0) {
|
||||
settingsRoot.systemDefaultIconTheme = stdout.trim();
|
||||
} else {
|
||||
settingsRoot.systemDefaultIconTheme = "";
|
||||
}
|
||||
iconThemeDetectionProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
property var iconThemeDetectionProcess: Process {
|
||||
|
||||
command: ["sh", "-c", "find /usr/share/icons ~/.local/share/icons ~/.icons -maxdepth 1 -type d 2>/dev/null | sed 's|.*/||' | grep -v '^icons$' | sort -u"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (!settingsRoot) return
|
||||
var detectedThemes = ["System Default"]
|
||||
if (text && text.trim()) {
|
||||
var themes = text.trim().split('\n')
|
||||
for (var i = 0; i < themes.length; i++) {
|
||||
var theme = themes[i].trim()
|
||||
if (theme && theme !== "" && theme !== "default" && theme !== "hicolor" && theme !== "locolor") {
|
||||
detectedThemes.push(theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
settingsRoot.availableIconThemes = detectedThemes
|
||||
}
|
||||
}
|
||||
defaultSettingsCheckProcess.running = true;
|
||||
}
|
||||
|
||||
property var qtToolsDetectionProcess: Process {
|
||||
@@ -74,7 +32,8 @@ Singleton {
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (!settingsRoot) return;
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
if (text && text.trim()) {
|
||||
var lines = text.trim().split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
@@ -95,8 +54,9 @@ Singleton {
|
||||
property var defaultSettingsCheckProcess: Process {
|
||||
command: ["sh", "-c", "CONFIG_DIR=\"" + (settingsRoot?._configDir || "") + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-settings.json\" ] && [ ! -f \"$CONFIG_DIR/settings.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-settings.json\" \"$CONFIG_DIR/settings.json\" && echo 'copied'; else echo 'not_found'; fi"]
|
||||
running: false
|
||||
onExited: function(exitCode) {
|
||||
if (!settingsRoot) return;
|
||||
onExited: function (exitCode) {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
if (exitCode === 0) {
|
||||
console.info("Copied default-settings.json to settings.json");
|
||||
if (settingsRoot.settingsFile) {
|
||||
@@ -113,8 +73,9 @@ Singleton {
|
||||
property var fprintdDetectionProcess: Process {
|
||||
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"]
|
||||
running: false
|
||||
onExited: function(exitCode) {
|
||||
if (!settingsRoot) return;
|
||||
onExited: function (exitCode) {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
settingsRoot.fprintdAvailable = (exitCode === 0);
|
||||
}
|
||||
}
|
||||
@@ -123,8 +84,9 @@ Singleton {
|
||||
command: ["test", "-f", settingsRoot?.pluginSettingsPath || ""]
|
||||
running: false
|
||||
|
||||
onExited: function(exitCode) {
|
||||
if (!settingsRoot) return;
|
||||
onExited: function (exitCode) {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
settingsRoot.pluginSettingsFileExists = (exitCode === 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ var SPEC = {
|
||||
matugenTemplateKitty: { def: true },
|
||||
matugenTemplateFoot: { def: true },
|
||||
matugenTemplateAlacritty: { def: true },
|
||||
matugenTemplateNeovim: { def: true },
|
||||
matugenTemplateWezterm: { def: true },
|
||||
matugenTemplateDgop: { def: true },
|
||||
matugenTemplateKcolorscheme: { def: true },
|
||||
@@ -258,6 +259,8 @@ var SPEC = {
|
||||
displayNameMode: { def: "system" },
|
||||
screenPreferences: { def: {} },
|
||||
showOnLastDisplay: { def: {} },
|
||||
niriOutputSettings: { def: {} },
|
||||
hyprlandOutputSettings: { def: {} },
|
||||
|
||||
barConfigs: { def: [{
|
||||
id: "default",
|
||||
@@ -298,7 +301,49 @@ var SPEC = {
|
||||
scrollEnabled: true,
|
||||
scrollXBehavior: "column",
|
||||
scrollYBehavior: "workspace"
|
||||
}], onChange: "updateBarConfigs" }
|
||||
}], onChange: "updateBarConfigs" },
|
||||
|
||||
desktopClockEnabled: { def: false },
|
||||
desktopClockStyle: { def: "analog" },
|
||||
desktopClockTransparency: { def: 0.8, coerce: percentToUnit },
|
||||
desktopClockColorMode: { def: "primary" },
|
||||
desktopClockCustomColor: { def: "#ffffff" },
|
||||
desktopClockShowDate: { def: true },
|
||||
desktopClockShowAnalogNumbers: { def: false },
|
||||
desktopClockShowAnalogSeconds: { def: true },
|
||||
desktopClockX: { def: -1 },
|
||||
desktopClockY: { def: -1 },
|
||||
desktopClockWidth: { def: 280 },
|
||||
desktopClockHeight: { def: 180 },
|
||||
desktopClockDisplayPreferences: { def: ["all"] },
|
||||
|
||||
systemMonitorEnabled: { def: false },
|
||||
systemMonitorShowHeader: { def: true },
|
||||
systemMonitorTransparency: { def: 0.8, coerce: percentToUnit },
|
||||
systemMonitorColorMode: { def: "primary" },
|
||||
systemMonitorCustomColor: { def: "#ffffff" },
|
||||
systemMonitorShowCpu: { def: true },
|
||||
systemMonitorShowCpuGraph: { def: true },
|
||||
systemMonitorShowCpuTemp: { def: true },
|
||||
systemMonitorShowGpuTemp: { def: false },
|
||||
systemMonitorGpuPciId: { def: "" },
|
||||
systemMonitorShowMemory: { def: true },
|
||||
systemMonitorShowMemoryGraph: { def: true },
|
||||
systemMonitorShowNetwork: { def: true },
|
||||
systemMonitorShowNetworkGraph: { def: true },
|
||||
systemMonitorShowDisk: { def: true },
|
||||
systemMonitorShowTopProcesses: { def: false },
|
||||
systemMonitorTopProcessCount: { def: 3 },
|
||||
systemMonitorTopProcessSortBy: { def: "cpu" },
|
||||
systemMonitorGraphInterval: { def: 60 },
|
||||
systemMonitorX: { def: -1 },
|
||||
systemMonitorY: { def: -1 },
|
||||
systemMonitorWidth: { def: 320 },
|
||||
systemMonitorHeight: { def: 480 },
|
||||
systemMonitorDisplayPreferences: { def: ["all"] },
|
||||
systemMonitorVariants: { def: [] },
|
||||
desktopWidgetPositions: { def: {} },
|
||||
desktopWidgetGridSettings: { def: {} }
|
||||
};
|
||||
|
||||
function getValidKeys() {
|
||||
|
||||
@@ -58,6 +58,8 @@ Item {
|
||||
|
||||
WallpaperBackground {}
|
||||
|
||||
DesktopWidgetLayer {}
|
||||
|
||||
Lock {
|
||||
id: lock
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ Item {
|
||||
function close() {
|
||||
shouldBeVisible = false;
|
||||
shouldHaveFocus = false;
|
||||
ModalManager.closeModal(root);
|
||||
closeTimer.restart();
|
||||
}
|
||||
|
||||
@@ -90,6 +91,7 @@ Item {
|
||||
animationsEnabled = false;
|
||||
shouldBeVisible = false;
|
||||
shouldHaveFocus = false;
|
||||
ModalManager.closeModal(root);
|
||||
closeTimer.stop();
|
||||
contentWindow.visible = false;
|
||||
if (useBackgroundWindow)
|
||||
|
||||
@@ -128,6 +128,9 @@ DankModal {
|
||||
FocusScope {
|
||||
id: colorContent
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property alias hexInput: hexInput
|
||||
|
||||
anchors.fill: parent
|
||||
@@ -160,12 +163,14 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Select a color from the palette or use custom sliders")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
anchors.left: parent.left
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +365,7 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
GridView {
|
||||
@@ -410,6 +416,7 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -462,6 +469,7 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
@@ -507,6 +515,7 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -566,6 +575,7 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -630,6 +640,7 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
Row {
|
||||
|
||||
@@ -24,26 +24,26 @@ DankModal {
|
||||
repeat: true
|
||||
running: root.shouldBeVisible
|
||||
onTriggered: {
|
||||
root.countdown--
|
||||
root.countdown--;
|
||||
if (root.countdown <= 0) {
|
||||
root.reverted()
|
||||
root.close()
|
||||
root.reverted();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
countdown = 10
|
||||
countdownTimer.start()
|
||||
countdown = 10;
|
||||
countdownTimer.start();
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
countdownTimer.stop()
|
||||
countdownTimer.stop();
|
||||
}
|
||||
|
||||
onBackgroundClicked: {
|
||||
root.reverted()
|
||||
root.close()
|
||||
root.reverted();
|
||||
root.close();
|
||||
}
|
||||
|
||||
content: Component {
|
||||
@@ -55,15 +55,15 @@ DankModal {
|
||||
implicitHeight: mainColumn.implicitHeight
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
root.reverted()
|
||||
root.close()
|
||||
event.accepted = true
|
||||
root.reverted();
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: event => {
|
||||
root.confirmed()
|
||||
root.close()
|
||||
event.accepted = true
|
||||
root.confirmed();
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -149,8 +149,8 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.reverted()
|
||||
root.close()
|
||||
root.reverted();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,8 +178,8 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.confirmed()
|
||||
root.close()
|
||||
root.confirmed();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,8 +203,8 @@ DankModal {
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
root.reverted()
|
||||
root.close()
|
||||
root.reverted();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,14 @@ DankModal {
|
||||
}
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
NotificationService.clearAllNotifications();
|
||||
}
|
||||
|
||||
function dismissAllPopups () {
|
||||
NotificationService.dismissAllPopups();
|
||||
}
|
||||
|
||||
modalWidth: 500
|
||||
modalHeight: 700
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
@@ -99,11 +107,21 @@ DankModal {
|
||||
}
|
||||
|
||||
function toggleDoNotDisturb(): string {
|
||||
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
|
||||
|
||||
return "NOTIFICATION_MODAL_TOGGLE_DND_SUCCESS";
|
||||
}
|
||||
|
||||
function clearAll(): string {
|
||||
notificationModal.clearAll();
|
||||
return "NOTIFICATION_MODAL_CLEAR_ALL_SUCCESS";
|
||||
}
|
||||
|
||||
function dismissAllPopups(): string {
|
||||
notificationModal.dismissAllPopups();
|
||||
return "NOTIFICATION_MODAL_DISMISS_ALL_POPUPS_SUCCESS";
|
||||
}
|
||||
|
||||
target: "notifications"
|
||||
}
|
||||
|
||||
@@ -111,6 +129,9 @@ DankModal {
|
||||
Item {
|
||||
id: notificationKeyHandler
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
|
||||
@@ -108,6 +108,9 @@ FloatingWindow {
|
||||
FocusScope {
|
||||
id: contentFocusScope
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import qs.Modules.Settings
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property int currentIndex: 0
|
||||
property var parentModal: null
|
||||
|
||||
@@ -32,9 +35,8 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,9 +50,8 @@ FocusScope {
|
||||
sourceComponent: TimeWeatherTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +67,8 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +84,8 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +99,8 @@ FocusScope {
|
||||
sourceComponent: WorkspacesTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +116,8 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,9 +131,8 @@ FocusScope {
|
||||
sourceComponent: DisplayConfigTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,9 +146,8 @@ FocusScope {
|
||||
sourceComponent: GammaControlTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,9 +161,8 @@ FocusScope {
|
||||
sourceComponent: DisplayWidgetsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,9 +176,8 @@ FocusScope {
|
||||
sourceComponent: NetworkTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,9 +191,8 @@ FocusScope {
|
||||
sourceComponent: PrinterTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,9 +206,8 @@ FocusScope {
|
||||
sourceComponent: LauncherTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,9 +221,8 @@ FocusScope {
|
||||
sourceComponent: ThemeColorsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,9 +236,8 @@ FocusScope {
|
||||
sourceComponent: LockScreenTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,9 +253,8 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,9 +268,8 @@ FocusScope {
|
||||
sourceComponent: AboutTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,9 +283,8 @@ FocusScope {
|
||||
sourceComponent: TypographyMotionTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,9 +298,8 @@ FocusScope {
|
||||
sourceComponent: SoundsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,9 +313,8 @@ FocusScope {
|
||||
sourceComponent: MediaPlayerTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,9 +328,8 @@ FocusScope {
|
||||
sourceComponent: NotificationsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,9 +343,8 @@ FocusScope {
|
||||
sourceComponent: OSDTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,9 +358,8 @@ FocusScope {
|
||||
sourceComponent: RunningAppsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,9 +373,8 @@ FocusScope {
|
||||
sourceComponent: SystemUpdaterTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,9 +388,8 @@ FocusScope {
|
||||
sourceComponent: PowerSleepTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,9 +405,8 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,9 +420,23 @@ FocusScope {
|
||||
sourceComponent: ClipboardTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: desktopWidgetsLoader
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 27
|
||||
visible: active
|
||||
focus: active
|
||||
|
||||
sourceComponent: DesktopWidgetsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@ FloatingWindow {
|
||||
FocusScope {
|
||||
id: contentFocusScope
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
@@ -223,7 +226,7 @@ FloatingWindow {
|
||||
SettingsSidebar {
|
||||
id: sidebar
|
||||
|
||||
x: 0
|
||||
anchors.left: parent.left
|
||||
width: settingsModal.isCompactMode ? parent.width : 270
|
||||
visible: settingsModal.isCompactMode ? settingsModal.menuVisible : true
|
||||
parentModal: settingsModal
|
||||
@@ -238,8 +241,8 @@ FloatingWindow {
|
||||
}
|
||||
|
||||
Item {
|
||||
x: settingsModal.isCompactMode ? (settingsModal.menuVisible ? parent.width : 0) : sidebar.width
|
||||
width: settingsModal.isCompactMode ? parent.width : parent.width - sidebar.width
|
||||
anchors.left: settingsModal.isCompactMode ? (settingsModal.menuVisible ? sidebar.right : parent.left) : sidebar.right
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
clip: true
|
||||
|
||||
@@ -250,14 +253,6 @@ FloatingWindow {
|
||||
parentModal: settingsModal
|
||||
currentIndex: settingsModal.currentTabIndex
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
enabled: settingsModal.enableAnimations
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property int currentIndex: 0
|
||||
property var parentModal: null
|
||||
property var expandedCategories: ({})
|
||||
@@ -114,6 +117,12 @@ Rectangle {
|
||||
"text": I18n.tr("System Updater"),
|
||||
"icon": "refresh",
|
||||
"tabIndex": 20
|
||||
},
|
||||
{
|
||||
"id": "desktop_widgets",
|
||||
"text": I18n.tr("Desktop Widgets"),
|
||||
"icon": "widgets",
|
||||
"tabIndex": 27
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -8,6 +8,9 @@ import qs.Widgets
|
||||
Item {
|
||||
id: spotlightKeyHandler
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property alias appLauncher: appLauncher
|
||||
property alias searchField: searchField
|
||||
property alias fileSearchController: fileSearchController
|
||||
@@ -72,10 +75,10 @@ Item {
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
@@ -92,10 +95,10 @@ Item {
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
if (searchMode === "apps") {
|
||||
|
||||
@@ -95,6 +95,9 @@ DankPopout {
|
||||
Rectangle {
|
||||
id: launcherPanel
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property alias searchField: searchField
|
||||
|
||||
color: "transparent"
|
||||
@@ -179,8 +182,8 @@ DankPopout {
|
||||
mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious();
|
||||
|
||||
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow();
|
||||
mappings[Qt.Key_Left] = () => appLauncher.selectPreviousInRow();
|
||||
mappings[Qt.Key_Right] = () => I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||
mappings[Qt.Key_Left] = () => I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
|
||||
}
|
||||
|
||||
return mappings;
|
||||
@@ -211,13 +214,13 @@ DankPopout {
|
||||
return;
|
||||
case Qt.Key_L:
|
||||
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
case Qt.Key_H:
|
||||
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
|
||||
443
quickshell/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml
Normal file
443
quickshell/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml
Normal file
@@ -0,0 +1,443 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real widgetWidth: 280
|
||||
property real widgetHeight: 200
|
||||
|
||||
property string clockStyle: SettingsData.desktopClockStyle
|
||||
property bool forceSquare: clockStyle === "analog"
|
||||
|
||||
property real defaultWidth: {
|
||||
switch (clockStyle) {
|
||||
case "analog":
|
||||
return 200;
|
||||
case "stacked":
|
||||
return 160;
|
||||
default:
|
||||
return 280;
|
||||
}
|
||||
}
|
||||
property real defaultHeight: {
|
||||
switch (clockStyle) {
|
||||
case "analog":
|
||||
return 200;
|
||||
case "stacked":
|
||||
return 220;
|
||||
default:
|
||||
return 160;
|
||||
}
|
||||
}
|
||||
property real minWidth: {
|
||||
switch (clockStyle) {
|
||||
case "analog":
|
||||
return 120;
|
||||
case "stacked":
|
||||
return 100;
|
||||
default:
|
||||
return 140;
|
||||
}
|
||||
}
|
||||
property real minHeight: {
|
||||
switch (clockStyle) {
|
||||
case "analog":
|
||||
return 120;
|
||||
case "stacked":
|
||||
return 140;
|
||||
default:
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
property bool enabled: SettingsData.desktopClockEnabled
|
||||
property real transparency: SettingsData.desktopClockTransparency
|
||||
property string colorMode: SettingsData.desktopClockColorMode
|
||||
property color customColor: SettingsData.desktopClockCustomColor
|
||||
property bool showDate: SettingsData.desktopClockShowDate
|
||||
property bool showAnalogNumbers: SettingsData.desktopClockShowAnalogNumbers
|
||||
|
||||
readonly property real scaleFactor: Math.min(width, height) / 200
|
||||
|
||||
readonly property color accentColor: {
|
||||
if (colorMode === "primary")
|
||||
return Theme.primary;
|
||||
if (colorMode === "secondary")
|
||||
return Theme.secondary;
|
||||
if (colorMode === "custom")
|
||||
return customColor;
|
||||
return Theme.primary;
|
||||
}
|
||||
|
||||
readonly property color handColor: accentColor
|
||||
readonly property color handColorDim: Theme.withAlpha(accentColor, 0.65)
|
||||
readonly property color textColor: Theme.onSurface
|
||||
readonly property color subtleTextColor: Theme.onSurfaceVariant
|
||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surface, root.transparency)
|
||||
|
||||
readonly property bool needsSeconds: clockStyle === "analog" ? SettingsData.desktopClockShowAnalogSeconds : SettingsData.showSeconds
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
precision: root.needsSeconds ? SystemClock.Seconds : SystemClock.Minutes
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: root.backgroundColor
|
||||
visible: root.clockStyle !== "analog"
|
||||
}
|
||||
|
||||
OrganicBlobHourBulges {
|
||||
anchors.fill: parent
|
||||
fillColor: root.backgroundColor
|
||||
visible: root.clockStyle === "analog"
|
||||
lobes: 12
|
||||
rotationDeg: -90
|
||||
lobeAmount: 0.075
|
||||
hillPower: 0.92
|
||||
roundness: 0.22
|
||||
paddingFrac: 0.02
|
||||
segments: 144
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
sourceComponent: {
|
||||
if (root.clockStyle === "analog")
|
||||
return analogClock;
|
||||
if (root.clockStyle === "stacked")
|
||||
return stackedClock;
|
||||
return digitalClock;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: analogClock
|
||||
|
||||
Item {
|
||||
id: analogRoot
|
||||
|
||||
property real clockSize: Math.min(width, height)
|
||||
property real centerX: width / 2
|
||||
property real centerY: height / 2
|
||||
property real faceRadius: clockSize / 2 - 12
|
||||
|
||||
property int hours: systemClock.date?.getHours() % 12 ?? 0
|
||||
property int minutes: systemClock.date?.getMinutes() ?? 0
|
||||
property int seconds: systemClock.date?.getSeconds() ?? 0
|
||||
|
||||
Repeater {
|
||||
model: root.showAnalogNumbers ? 12 : 0
|
||||
|
||||
StyledText {
|
||||
required property int index
|
||||
property real angle: (index + 1) * 30 * Math.PI / 180
|
||||
property real numRadius: analogRoot.faceRadius + 10
|
||||
|
||||
x: analogRoot.centerX + numRadius * Math.sin(angle) - width / 2
|
||||
y: analogRoot.centerY - numRadius * Math.cos(angle) - height / 2
|
||||
text: index + 1
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: root.accentColor
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: hourHand
|
||||
property real angle: (analogRoot.hours + analogRoot.minutes / 60) * 30
|
||||
property real handWidth: Math.max(8, 12 * root.scaleFactor)
|
||||
property real mainLength: analogRoot.faceRadius * 0.55
|
||||
property real tailLength: handWidth * 0.5
|
||||
|
||||
x: analogRoot.centerX - width / 2
|
||||
y: analogRoot.centerY - mainLength
|
||||
width: handWidth
|
||||
height: mainLength + tailLength
|
||||
radius: width / 2
|
||||
color: root.handColor
|
||||
antialiasing: true
|
||||
|
||||
transform: Rotation {
|
||||
origin.x: hourHand.width / 2
|
||||
origin.y: hourHand.mainLength
|
||||
angle: hourHand.angle
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: minuteHand
|
||||
property real angle: (analogRoot.minutes + analogRoot.seconds / 60) * 6
|
||||
property real mainLength: analogRoot.faceRadius * 0.75
|
||||
property real tailLength: hourHand.handWidth * 0.5
|
||||
|
||||
x: analogRoot.centerX - width / 2
|
||||
y: analogRoot.centerY - mainLength
|
||||
width: hourHand.handWidth
|
||||
height: mainLength + tailLength
|
||||
radius: width / 2
|
||||
color: root.handColorDim
|
||||
antialiasing: true
|
||||
|
||||
transform: Rotation {
|
||||
origin.x: minuteHand.width / 2
|
||||
origin.y: minuteHand.mainLength
|
||||
angle: minuteHand.angle
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: secondDot
|
||||
visible: SettingsData.desktopClockShowAnalogSeconds
|
||||
|
||||
property real angle: analogRoot.seconds * 6 * Math.PI / 180
|
||||
property real orbitRadius: analogRoot.faceRadius * 0.92
|
||||
|
||||
x: analogRoot.centerX + orbitRadius * Math.sin(angle) - width / 2
|
||||
y: analogRoot.centerY - orbitRadius * Math.cos(angle) - height / 2
|
||||
width: Math.max(10, analogRoot.clockSize * 0.07)
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: root.accentColor
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: dateText
|
||||
visible: root.showDate
|
||||
|
||||
property real hourAngle: (analogRoot.hours + analogRoot.minutes / 60) * 30
|
||||
property real minuteAngle: analogRoot.minutes * 6
|
||||
|
||||
property string bestPosition: {
|
||||
const hRad = hourAngle * Math.PI / 180;
|
||||
const mRad = minuteAngle * Math.PI / 180;
|
||||
|
||||
const topWeight = Math.max(0, Math.cos(hRad)) + Math.max(0, Math.cos(mRad));
|
||||
const bottomWeight = Math.max(0, -Math.cos(hRad)) + Math.max(0, -Math.cos(mRad));
|
||||
const rightWeight = Math.max(0, Math.sin(hRad)) + Math.max(0, Math.sin(mRad));
|
||||
const leftWeight = Math.max(0, -Math.sin(hRad)) + Math.max(0, -Math.sin(mRad));
|
||||
|
||||
const minWeight = Math.min(topWeight, bottomWeight, leftWeight, rightWeight);
|
||||
|
||||
if (minWeight === bottomWeight)
|
||||
return "bottom";
|
||||
if (minWeight === topWeight)
|
||||
return "top";
|
||||
if (minWeight === rightWeight)
|
||||
return "right";
|
||||
return "left";
|
||||
}
|
||||
|
||||
x: {
|
||||
if (bestPosition === "left")
|
||||
return analogRoot.centerX - analogRoot.faceRadius * 0.5 - width / 2;
|
||||
if (bestPosition === "right")
|
||||
return analogRoot.centerX + analogRoot.faceRadius * 0.5 - width / 2;
|
||||
return analogRoot.centerX - width / 2;
|
||||
}
|
||||
y: {
|
||||
if (bestPosition === "top")
|
||||
return analogRoot.centerY - analogRoot.faceRadius * 0.5 - height / 2;
|
||||
if (bestPosition === "bottom")
|
||||
return analogRoot.centerY + analogRoot.faceRadius * 0.5 - height / 2;
|
||||
return analogRoot.centerY - height / 2;
|
||||
}
|
||||
|
||||
text: {
|
||||
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
|
||||
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
|
||||
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.accentColor
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: digitalClock
|
||||
|
||||
Item {
|
||||
id: digitalRoot
|
||||
|
||||
property real baseSize: Math.max(28, height * 0.38)
|
||||
property real smallSize: Math.max(12, baseSize * 0.32)
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
visible: root.showDate
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: {
|
||||
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
|
||||
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
|
||||
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
|
||||
}
|
||||
font.pixelSize: digitalRoot.smallSize
|
||||
color: root.accentColor
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: {
|
||||
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
|
||||
const minutes = String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0');
|
||||
return hours + ":" + minutes;
|
||||
}
|
||||
font.pixelSize: digitalRoot.baseSize
|
||||
font.weight: Font.Normal
|
||||
color: root.accentColor
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: !SettingsData.use24HourClock || SettingsData.showSeconds
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
visible: SettingsData.showSeconds
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "timer"
|
||||
size: Math.max(10, digitalRoot.baseSize * 0.25)
|
||||
color: root.subtleTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
|
||||
font.pixelSize: digitalRoot.smallSize
|
||||
color: root.subtleTextColor
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: !SettingsData.use24HourClock
|
||||
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
|
||||
font.pixelSize: digitalRoot.smallSize
|
||||
font.weight: Font.Medium
|
||||
color: root.accentColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: stackedClock
|
||||
|
||||
Item {
|
||||
id: stackedRoot
|
||||
|
||||
property real baseSize: Math.max(32, height * 0.32)
|
||||
property real smallSize: Math.max(12, baseSize * 0.28)
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: -baseSize * 0.1
|
||||
|
||||
StyledText {
|
||||
visible: root.showDate
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
bottomPadding: Theme.spacingS
|
||||
text: {
|
||||
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
|
||||
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
|
||||
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
|
||||
}
|
||||
font.pixelSize: stackedRoot.smallSize
|
||||
color: root.accentColor
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: {
|
||||
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
|
||||
return String(hours).padStart(2, '0');
|
||||
}
|
||||
font.pixelSize: stackedRoot.baseSize
|
||||
font.weight: Font.Normal
|
||||
color: root.accentColor
|
||||
lineHeight: 0.85
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0')
|
||||
font.pixelSize: stackedRoot.baseSize
|
||||
font.weight: Font.Normal
|
||||
color: root.accentColor
|
||||
lineHeight: 0.85
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: SettingsData.showSeconds || !SettingsData.use24HourClock
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
topPadding: Theme.spacingXS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
visible: SettingsData.showSeconds
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "timer"
|
||||
size: Math.max(10, stackedRoot.baseSize * 0.28)
|
||||
color: root.subtleTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
|
||||
font.pixelSize: stackedRoot.smallSize
|
||||
color: root.subtleTextColor
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: !SettingsData.use24HourClock
|
||||
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
|
||||
font.pixelSize: stackedRoot.smallSize
|
||||
font.weight: Font.Medium
|
||||
color: root.accentColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property color fillColor: "transparent"
|
||||
|
||||
property int lobes: 12
|
||||
property real lobeAmount: 0.070
|
||||
property real roundness: 0.22
|
||||
property real hillPower: 0.78
|
||||
property real paddingFrac: 0.020
|
||||
property real inset: 0
|
||||
property int segments: 120
|
||||
property real rotationDeg: -90
|
||||
|
||||
layer.enabled: true
|
||||
layer.samples: 4
|
||||
|
||||
function sgn(v) {
|
||||
return v < 0 ? -1 : 1;
|
||||
}
|
||||
function clamp(v, a, b) {
|
||||
return Math.max(a, Math.min(b, v));
|
||||
}
|
||||
|
||||
function squircleMap(u, v, p) {
|
||||
const ax = sgn(u) * Math.pow(Math.abs(u), p);
|
||||
const ay = sgn(v) * Math.pow(Math.abs(v), p);
|
||||
return {
|
||||
x: ax,
|
||||
y: ay
|
||||
};
|
||||
}
|
||||
|
||||
function buildPathD(w, h) {
|
||||
const x0 = inset, y0 = inset;
|
||||
const x1 = w - inset, y1 = h - inset;
|
||||
const iw = Math.max(2, x1 - x0);
|
||||
const ih = Math.max(2, y1 - y0);
|
||||
|
||||
const cx = x0 + iw * 0.5;
|
||||
const cy = y0 + ih * 0.5;
|
||||
|
||||
const rx = iw * 0.5;
|
||||
const ry = ih * 0.5;
|
||||
const rMin = Math.min(rx, ry);
|
||||
|
||||
const amp = clamp(lobeAmount, 0.0, 0.14) * rMin;
|
||||
const extraPad = paddingFrac * rMin + 1.5;
|
||||
const pad = amp + extraPad;
|
||||
|
||||
const rxBase = Math.max(2, rx - pad);
|
||||
const ryBase = Math.max(2, ry - pad);
|
||||
|
||||
const blend = clamp(roundness, 0.0, 0.45);
|
||||
const squirclePow = 1.0 + blend * 2.8;
|
||||
|
||||
const N = Math.max(48, segments);
|
||||
const rot = rotationDeg * Math.PI / 180.0;
|
||||
const dt = (Math.PI * 2.0) / N;
|
||||
|
||||
function hillWave(a) {
|
||||
const t = (Math.cos(lobes * a) + 1.0) * 0.5;
|
||||
return Math.pow(t, hillPower);
|
||||
}
|
||||
|
||||
function P(t) {
|
||||
const a = t + rot;
|
||||
const u = Math.cos(a);
|
||||
const v = Math.sin(a);
|
||||
|
||||
const m = 1.0 + (amp / rMin) * hillWave(a);
|
||||
|
||||
const ex = u * rxBase * m;
|
||||
const ey = v * ryBase * m;
|
||||
|
||||
const sm = squircleMap(u, v, 1.0 / squirclePow);
|
||||
const sx = sm.x * rxBase * m;
|
||||
const sy = sm.y * ryBase * m;
|
||||
|
||||
const x = ex * (1.0 - blend) + sx * blend;
|
||||
const y = ey * (1.0 - blend) + sy * blend;
|
||||
return {
|
||||
x: cx + x,
|
||||
y: cy + y
|
||||
};
|
||||
}
|
||||
|
||||
function dP(t) {
|
||||
const eps = dt * 0.25;
|
||||
const p1 = P(t - eps);
|
||||
const p2 = P(t + eps);
|
||||
return {
|
||||
x: (p2.x - p1.x) / (2 * eps),
|
||||
y: (p2.y - p1.y) / (2 * eps)
|
||||
};
|
||||
}
|
||||
|
||||
const p0 = P(0.0);
|
||||
let d = `M ${p0.x.toFixed(2)} ${p0.y.toFixed(2)} `;
|
||||
|
||||
for (let i = 0; i < N; i++) {
|
||||
const tA = i * dt;
|
||||
const tB = (i + 1) * dt;
|
||||
|
||||
const A = P(tA);
|
||||
const B = P(tB);
|
||||
const dA = dP(tA);
|
||||
const dB = dP(tB);
|
||||
|
||||
const c1x = A.x + (dt / 3.0) * dA.x;
|
||||
const c1y = A.y + (dt / 3.0) * dA.y;
|
||||
const c2x = B.x - (dt / 3.0) * dB.x;
|
||||
const c2y = B.y - (dt / 3.0) * dB.y;
|
||||
|
||||
d += `C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${B.x.toFixed(2)} ${B.y.toFixed(2)} `;
|
||||
}
|
||||
|
||||
d += "Z";
|
||||
return d;
|
||||
}
|
||||
|
||||
Shape {
|
||||
anchors.fill: parent
|
||||
|
||||
ShapePath {
|
||||
fillColor: root.fillColor
|
||||
strokeColor: "transparent"
|
||||
|
||||
PathSvg {
|
||||
path: root.buildPathD(root.width, root.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
736
quickshell/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml
Normal file
736
quickshell/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml
Normal file
@@ -0,0 +1,736 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real widgetWidth: 320
|
||||
property real widgetHeight: 480
|
||||
property real defaultWidth: 320
|
||||
property real defaultHeight: 480
|
||||
property real minWidth: {
|
||||
const tileCount = enabledTiles.length;
|
||||
if (tileCount === 0)
|
||||
return 80;
|
||||
if (tileCount === 1)
|
||||
return 100;
|
||||
return 160;
|
||||
}
|
||||
property real minHeight: {
|
||||
const tileCount = enabledTiles.length;
|
||||
if (tileCount === 0)
|
||||
return 60;
|
||||
if (tileCount === 1)
|
||||
return 80;
|
||||
if (tileCount <= 2)
|
||||
return 120;
|
||||
return 180;
|
||||
}
|
||||
|
||||
property string variantId: ""
|
||||
property var variantData: null
|
||||
|
||||
readonly property var cfg: variantData?.config ?? null
|
||||
readonly property bool isVariant: variantId !== "" && cfg !== null
|
||||
|
||||
property bool enabled: SettingsData.systemMonitorEnabled
|
||||
property bool showHeader: isVariant ? (cfg.showHeader ?? true) : SettingsData.systemMonitorShowHeader
|
||||
property real transparency: isVariant ? (cfg.transparency ?? 0.8) : SettingsData.systemMonitorTransparency
|
||||
property string colorMode: isVariant ? (cfg.colorMode ?? "primary") : SettingsData.systemMonitorColorMode
|
||||
property color customColor: isVariant ? (cfg.customColor ?? "#ffffff") : SettingsData.systemMonitorCustomColor
|
||||
property bool showCpu: isVariant ? (cfg.showCpu ?? true) : SettingsData.systemMonitorShowCpu
|
||||
property bool showCpuGraph: isVariant ? (cfg.showCpuGraph ?? true) : SettingsData.systemMonitorShowCpuGraph
|
||||
property bool showCpuTemp: isVariant ? (cfg.showCpuTemp ?? true) : SettingsData.systemMonitorShowCpuTemp
|
||||
property bool showGpuTemp: isVariant ? (cfg.showGpuTemp ?? false) : SettingsData.systemMonitorShowGpuTemp
|
||||
property string selectedGpuPciId: isVariant ? (cfg.gpuPciId ?? "") : SettingsData.systemMonitorGpuPciId
|
||||
property bool showMemory: isVariant ? (cfg.showMemory ?? true) : SettingsData.systemMonitorShowMemory
|
||||
property bool showMemoryGraph: isVariant ? (cfg.showMemoryGraph ?? true) : SettingsData.systemMonitorShowMemoryGraph
|
||||
property bool showNetwork: isVariant ? (cfg.showNetwork ?? true) : SettingsData.systemMonitorShowNetwork
|
||||
property bool showNetworkGraph: isVariant ? (cfg.showNetworkGraph ?? true) : SettingsData.systemMonitorShowNetworkGraph
|
||||
property bool showDisk: isVariant ? (cfg.showDisk ?? true) : SettingsData.systemMonitorShowDisk
|
||||
property bool showTopProcesses: isVariant ? (cfg.showTopProcesses ?? false) : SettingsData.systemMonitorShowTopProcesses
|
||||
property int topProcessCount: isVariant ? (cfg.topProcessCount ?? 3) : SettingsData.systemMonitorTopProcessCount
|
||||
property string topProcessSortBy: isVariant ? (cfg.topProcessSortBy ?? "cpu") : SettingsData.systemMonitorTopProcessSortBy
|
||||
property string layoutMode: isVariant ? (cfg.layoutMode ?? "auto") : SettingsData.systemMonitorLayoutMode
|
||||
property int graphInterval: isVariant ? (cfg.graphInterval ?? 60) : SettingsData.systemMonitorGraphInterval
|
||||
|
||||
readonly property color accentColor: {
|
||||
switch (colorMode) {
|
||||
case "secondary":
|
||||
return Theme.secondary;
|
||||
case "custom":
|
||||
return customColor;
|
||||
default:
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property color bgColor: Theme.withAlpha(Theme.surface, root.transparency)
|
||||
readonly property color tileBg: Theme.withAlpha(Theme.surfaceContainerHigh, root.transparency)
|
||||
readonly property color textColor: Theme.surfaceText
|
||||
readonly property color dimColor: Theme.surfaceVariantText
|
||||
|
||||
property string currentGpuPciIdRef: ""
|
||||
|
||||
property var cpuHistory: []
|
||||
property var memHistory: []
|
||||
property var netRxHistory: []
|
||||
property var netTxHistory: []
|
||||
property var diskReadHistory: []
|
||||
property var diskWriteHistory: []
|
||||
readonly property int historySize: 60
|
||||
|
||||
readonly property int sampleInterval: {
|
||||
switch (graphInterval) {
|
||||
case 60:
|
||||
return 1000;
|
||||
case 300:
|
||||
return 5000;
|
||||
case 600:
|
||||
return 10000;
|
||||
case 900:
|
||||
return 15000;
|
||||
case 1800:
|
||||
return 30000;
|
||||
default:
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var enabledTiles: {
|
||||
var tiles = [];
|
||||
if (showCpu)
|
||||
tiles.push("cpu");
|
||||
if (showMemory)
|
||||
tiles.push("mem");
|
||||
if (showNetwork)
|
||||
tiles.push("net");
|
||||
if (showDisk)
|
||||
tiles.push("disk");
|
||||
if (showGpuTemp && selectedGpuPciId)
|
||||
tiles.push("gpu");
|
||||
return tiles;
|
||||
}
|
||||
|
||||
readonly property var sortedProcesses: {
|
||||
if (!showTopProcesses || !DgopService.processes)
|
||||
return [];
|
||||
var procs = DgopService.processes.slice();
|
||||
if (topProcessSortBy === "memory") {
|
||||
procs.sort((a, b) => (b.memoryKB || 0) - (a.memoryKB || 0));
|
||||
} else {
|
||||
procs.sort((a, b) => (b.cpu || 0) - (a.cpu || 0));
|
||||
}
|
||||
return procs.slice(0, topProcessCount);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
var modules = ["system"];
|
||||
if (showCpu || showCpuTemp || showCpuGraph)
|
||||
modules.push("cpu");
|
||||
if (showMemory || showMemoryGraph)
|
||||
modules.push("memory");
|
||||
if (showNetwork || showNetworkGraph)
|
||||
modules.push("network");
|
||||
if (showDisk)
|
||||
modules.push("disk", "diskmounts");
|
||||
if (showTopProcesses)
|
||||
modules.push("processes");
|
||||
DgopService.addRef(modules);
|
||||
updateGpuRef();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef();
|
||||
if (currentGpuPciIdRef)
|
||||
DgopService.removeGpuPciId(currentGpuPciIdRef);
|
||||
}
|
||||
|
||||
onShowGpuTempChanged: updateGpuRef()
|
||||
onSelectedGpuPciIdChanged: updateGpuRef()
|
||||
onShowTopProcessesChanged: {
|
||||
if (showTopProcesses)
|
||||
DgopService.addRef(["processes"]);
|
||||
}
|
||||
|
||||
function updateGpuRef() {
|
||||
if (currentGpuPciIdRef && currentGpuPciIdRef !== selectedGpuPciId) {
|
||||
DgopService.removeGpuPciId(currentGpuPciIdRef);
|
||||
currentGpuPciIdRef = "";
|
||||
}
|
||||
if (!showGpuTemp || !selectedGpuPciId) {
|
||||
if (currentGpuPciIdRef) {
|
||||
DgopService.removeGpuPciId(currentGpuPciIdRef);
|
||||
currentGpuPciIdRef = "";
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (selectedGpuPciId && !currentGpuPciIdRef) {
|
||||
DgopService.addGpuPciId(selectedGpuPciId);
|
||||
currentGpuPciIdRef = selectedGpuPciId;
|
||||
}
|
||||
}
|
||||
|
||||
function getGpuInfo() {
|
||||
if (!selectedGpuPciId || !DgopService.availableGpus)
|
||||
return null;
|
||||
return DgopService.availableGpus.find(g => g.pciId === selectedGpuPciId);
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024)
|
||||
return bytes.toFixed(0) + "B";
|
||||
if (bytes < 1024 * 1024)
|
||||
return (bytes / 1024).toFixed(0) + "K";
|
||||
if (bytes < 1024 * 1024 * 1024)
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + "M";
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + "G";
|
||||
}
|
||||
|
||||
function formatMemKB(kb) {
|
||||
if (kb < 1024)
|
||||
return kb.toFixed(0) + "K";
|
||||
if (kb < 1024 * 1024)
|
||||
return (kb / 1024).toFixed(0) + "M";
|
||||
return (kb / (1024 * 1024)).toFixed(1) + "G";
|
||||
}
|
||||
|
||||
function addToHistory(arr, val) {
|
||||
var newArr = arr.slice();
|
||||
newArr.push(val);
|
||||
if (newArr.length > historySize)
|
||||
newArr.shift();
|
||||
return newArr;
|
||||
}
|
||||
|
||||
function sampleData() {
|
||||
if (showCpuGraph)
|
||||
cpuHistory = addToHistory(cpuHistory, DgopService.cpuUsage);
|
||||
if (showMemoryGraph)
|
||||
memHistory = addToHistory(memHistory, DgopService.memoryUsage);
|
||||
if (showNetworkGraph) {
|
||||
netRxHistory = addToHistory(netRxHistory, DgopService.networkRxRate);
|
||||
netTxHistory = addToHistory(netTxHistory, DgopService.networkTxRate);
|
||||
}
|
||||
if (showDisk) {
|
||||
diskReadHistory = addToHistory(diskReadHistory, DgopService.diskReadRate);
|
||||
diskWriteHistory = addToHistory(diskWriteHistory, DgopService.diskWriteRate);
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int sampleSeconds: sampleInterval / 1000
|
||||
|
||||
SystemClock {
|
||||
id: sampleClock
|
||||
precision: SystemClock.Seconds
|
||||
onDateChanged: {
|
||||
var sec = date.getSeconds();
|
||||
if (sec % root.sampleSeconds === 0)
|
||||
root.sampleData();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: root.bgColor
|
||||
border.width: 0
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: (root.enabledTiles.length === 1 && !root.showHeader) ? 0 : Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingS
|
||||
visible: root.showHeader
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
text: DgopService.cpuModel || DgopService.hostname || "System"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.textColor
|
||||
elide: Text.ElideRight
|
||||
Layout.maximumWidth: root.width - Theme.spacingM * 2
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: DgopService.shortUptime && DgopService.shortUptime.length > 0
|
||||
text: DgopService.shortUptime
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.dimColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: tileGrid
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
columns: {
|
||||
if (root.layoutMode === "list")
|
||||
return 1;
|
||||
if (root.layoutMode === "grid")
|
||||
return 2;
|
||||
// auto
|
||||
if (root.width < 280)
|
||||
return 1;
|
||||
if (root.width < 500)
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
rowSpacing: Theme.spacingXS
|
||||
columnSpacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: root.enabledTiles
|
||||
|
||||
Rectangle {
|
||||
id: tile
|
||||
readonly property int span: Layout.columnSpan
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.columnSpan: {
|
||||
if (root.layoutMode === "list")
|
||||
return 1;
|
||||
|
||||
var cols = tileGrid.columns;
|
||||
if (cols <= 1)
|
||||
return 1;
|
||||
|
||||
var count = root.enabledTiles.length;
|
||||
var idx = index;
|
||||
|
||||
if (idx !== count - 1)
|
||||
return 1;
|
||||
|
||||
var remainder = count % cols;
|
||||
if (remainder === 0)
|
||||
return 1;
|
||||
|
||||
if (!tile.hasGraph)
|
||||
return 1;
|
||||
|
||||
return cols - remainder + 1;
|
||||
}
|
||||
Layout.minimumHeight: 60
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: root.tileBg
|
||||
border.width: 0
|
||||
clip: true
|
||||
|
||||
readonly property string tileType: modelData
|
||||
readonly property bool hasGraph: {
|
||||
switch (tileType) {
|
||||
case "cpu":
|
||||
return root.showCpuGraph;
|
||||
case "mem":
|
||||
return root.showMemoryGraph;
|
||||
case "net":
|
||||
return root.showNetworkGraph;
|
||||
case "disk":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: tileGraph
|
||||
anchors.fill: parent
|
||||
visible: tile.hasGraph
|
||||
renderStrategy: Canvas.Cooperative
|
||||
|
||||
property var hist: {
|
||||
switch (tile.tileType) {
|
||||
case "cpu":
|
||||
return root.cpuHistory;
|
||||
case "mem":
|
||||
return root.memHistory;
|
||||
case "net":
|
||||
return root.netRxHistory;
|
||||
case "disk":
|
||||
return root.diskReadHistory;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
property var hist2: {
|
||||
switch (tile.tileType) {
|
||||
case "net":
|
||||
return root.netTxHistory;
|
||||
case "disk":
|
||||
return root.diskWriteHistory;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
onHistChanged: requestPaint()
|
||||
onHist2Changed: requestPaint()
|
||||
onWidthChanged: requestPaint()
|
||||
onHeightChanged: requestPaint()
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
if (!hist || hist.length < 2)
|
||||
return;
|
||||
var maxVal = 100;
|
||||
if (tile.tileType === "net" || tile.tileType === "disk") {
|
||||
maxVal = 1;
|
||||
for (var k = 0; k < hist.length; k++)
|
||||
maxVal = Math.max(maxVal, hist[k]);
|
||||
if (hist2)
|
||||
for (var l = 0; l < hist2.length; l++)
|
||||
maxVal = Math.max(maxVal, hist2[l]);
|
||||
}
|
||||
|
||||
var c = root.accentColor;
|
||||
var grad = ctx.createLinearGradient(0, 0, 0, height);
|
||||
grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.3));
|
||||
grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.05));
|
||||
|
||||
ctx.fillStyle = grad;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, height);
|
||||
for (var i = 0; i < hist.length; i++) {
|
||||
var x = (width / (root.historySize - 1)) * i;
|
||||
var y = height - (hist[i] / maxVal) * height * 0.85;
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.lineTo((width / (root.historySize - 1)) * (hist.length - 1), height);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.6);
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath();
|
||||
for (var j = 0; j < hist.length; j++) {
|
||||
var px = (width / (root.historySize - 1)) * j;
|
||||
var py = height - (hist[j] / maxVal) * height * 0.85;
|
||||
j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
if (hist2 && hist2.length >= 2) {
|
||||
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.3);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
for (var m = 0; m < hist2.length; m++) {
|
||||
var sx = (width / (root.historySize - 1)) * m;
|
||||
var sy = height - (hist2[m] / maxVal) * height * 0.85;
|
||||
m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 2
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: tile.tileType.toUpperCase()
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: root.accentColor
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: tile.tileType === "cpu" && root.showCpuTemp && DgopService.cpuTemperature > 0
|
||||
text: DgopService.cpuTemperature.toFixed(0) + "°"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: DgopService.cpuTemperature > 80 ? Theme.error : (DgopService.cpuTemperature > 60 ? Theme.warning : root.dimColor)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: tile.tileType === "mem"
|
||||
text: DgopService.formatSystemMemory(DgopService.usedMemoryKB)
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.dimColor
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: tile.tileType === "cpu"
|
||||
text: DgopService.cpuUsage.toFixed(0) + "%"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
font.weight: Font.Medium
|
||||
color: root.textColor
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: tile.tileType === "mem"
|
||||
text: DgopService.memoryUsage.toFixed(0) + "%"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
font.weight: Font.Medium
|
||||
color: root.textColor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: tile.tileType === "net"
|
||||
spacing: Theme.spacingM
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
StyledText {
|
||||
text: "↓"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.accentColor
|
||||
}
|
||||
StyledText {
|
||||
text: root.formatBytes(DgopService.networkRxRate) + "/s"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: root.textColor
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
StyledText {
|
||||
text: "↑"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.dimColor
|
||||
}
|
||||
StyledText {
|
||||
text: root.formatBytes(DgopService.networkTxRate) + "/s"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: root.textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: tile.tileType === "disk"
|
||||
spacing: Theme.spacingM
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
StyledText {
|
||||
text: "R"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.accentColor
|
||||
}
|
||||
StyledText {
|
||||
text: root.formatBytes(DgopService.diskReadRate) + "/s"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: root.textColor
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
StyledText {
|
||||
text: "W"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.dimColor
|
||||
}
|
||||
StyledText {
|
||||
text: root.formatBytes(DgopService.diskWriteRate) + "/s"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: root.textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: tile.tileType === "gpu"
|
||||
spacing: 0
|
||||
property var gpu: root.getGpuInfo()
|
||||
Layout.alignment: tile.span > 1 ? Qt.AlignHCenter : Qt.AlignLeft
|
||||
|
||||
StyledText {
|
||||
property real temp: parent.gpu?.temperature ?? 0
|
||||
text: temp > 0 ? temp.toFixed(0) + "°C" : "--"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
font.weight: Font.Medium
|
||||
color: root.textColor
|
||||
Layout.alignment: tile.span > 1 ? Qt.AlignHCenter : Qt.AlignLeft
|
||||
}
|
||||
StyledText {
|
||||
text: parent.gpu?.displayName ?? ""
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.dimColor
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: tile.span > 1 ? Text.AlignHCenter : Text.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tile.tileType === "cpu" || tile.tileType === "mem"
|
||||
Layout.fillWidth: true
|
||||
height: 4
|
||||
radius: 2
|
||||
color: Theme.withAlpha(Theme.outline, 0.2)
|
||||
|
||||
Rectangle {
|
||||
property real pct: tile.tileType === "cpu" ? DgopService.cpuUsage / 100 : DgopService.memoryUsage / 100
|
||||
width: parent.width * Math.min(1, pct)
|
||||
height: parent.height
|
||||
radius: 2
|
||||
color: pct > 0.8 ? Theme.error : (pct > 0.6 ? Theme.warning : root.accentColor)
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingXS
|
||||
visible: root.showTopProcesses && root.sortedProcesses.length > 0
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Theme.withAlpha(Theme.outline, 0.15)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "TOP BY " + root.topProcessSortBy.toUpperCase()
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: root.accentColor
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.topProcessSortBy === "cpu" ? "CPU" : "MEM"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.dimColor
|
||||
Layout.preferredWidth: 48
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.sortedProcesses
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData.command || "unknown"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.textColor
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.topProcessSortBy === "cpu" ? (modelData.cpu || 0).toFixed(1) + "%" : root.formatMemKB(modelData.memoryKB || 0)
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.dimColor
|
||||
Layout.preferredWidth: 48
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingXS
|
||||
visible: root.showDisk && DgopService.diskMounts.length > 0
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Theme.withAlpha(Theme.outline, 0.15)
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: DgopService.diskMounts.filter(m => m.mountpoint === "/" || m.mountpoint === "/home")
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData.mountpoint
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.dimColor
|
||||
Layout.preferredWidth: 48
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 4
|
||||
radius: 2
|
||||
color: Theme.withAlpha(Theme.outline, 0.2)
|
||||
|
||||
Rectangle {
|
||||
property real pct: (modelData.used || 0) / Math.max(1, modelData.total || 1)
|
||||
width: parent.width * pct
|
||||
height: parent.height
|
||||
radius: 2
|
||||
color: pct > 0.9 ? Theme.error : (pct > 0.75 ? Theme.warning : root.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: ((modelData.used || 0) / (modelData.total || 1) * 100).toFixed(0) + "%"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.textColor
|
||||
Layout.preferredWidth: 32
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ PluginComponent {
|
||||
}
|
||||
|
||||
ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
|
||||
ccWidgetPrimaryText: "VPN"
|
||||
ccWidgetPrimaryText: I18n.tr("VPN")
|
||||
ccWidgetSecondaryText: {
|
||||
if (!DMSNetworkService.connected)
|
||||
return I18n.tr("Disconnected");
|
||||
|
||||
@@ -13,30 +13,30 @@ Item {
|
||||
|
||||
readonly property bool active: expandedSection !== ""
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingS
|
||||
sourceComponent: {
|
||||
if (!root.active) return null
|
||||
if (!root.active)
|
||||
return null;
|
||||
|
||||
if (expandedSection.startsWith("diskUsage_")) {
|
||||
return diskUsageDetailComponent
|
||||
return diskUsageDetailComponent;
|
||||
}
|
||||
|
||||
switch (expandedSection) {
|
||||
case "wifi": return networkDetailComponent
|
||||
case "bluetooth": return bluetoothDetailComponent
|
||||
case "audioOutput": return audioOutputDetailComponent
|
||||
case "audioInput": return audioInputDetailComponent
|
||||
case "battery": return batteryDetailComponent
|
||||
default: return null
|
||||
case "wifi":
|
||||
return networkDetailComponent;
|
||||
case "bluetooth":
|
||||
return bluetoothDetailComponent;
|
||||
case "audioOutput":
|
||||
return audioOutputDetailComponent;
|
||||
case "audioInput":
|
||||
return audioInputDetailComponent;
|
||||
case "battery":
|
||||
return batteryDetailComponent;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,18 +72,18 @@ Item {
|
||||
currentMountPath: root.expandedWidgetData?.mountPath || "/"
|
||||
instanceId: root.expandedWidgetData?.instanceId || ""
|
||||
|
||||
onMountPathChanged: (newMountPath) => {
|
||||
onMountPathChanged: newMountPath => {
|
||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const widgets = SettingsData.controlCenterWidgets || [];
|
||||
const newWidgets = widgets.map(w => {
|
||||
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||
const updatedWidget = Object.assign({}, w)
|
||||
updatedWidget.mountPath = newMountPath
|
||||
return updatedWidget
|
||||
const updatedWidget = Object.assign({}, w);
|
||||
updatedWidget.mountPath = newMountPath;
|
||||
return updatedWidget;
|
||||
}
|
||||
return w
|
||||
})
|
||||
SettingsData.set("controlCenterWidgets", newWidgets)
|
||||
return w;
|
||||
});
|
||||
SettingsData.set("controlCenterWidgets", newWidgets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,13 +51,13 @@ Rectangle {
|
||||
spacing: 2
|
||||
|
||||
Typography {
|
||||
text: UserInfoService.fullName || UserInfoService.username || "User"
|
||||
text: UserInfoService.fullName || UserInfoService.username || I18n.tr("User")
|
||||
style: Typography.Style.Subtitle
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: DgopService.uptime || "Unknown"
|
||||
text: DgopService.uptime || I18n.tr("Unknown")
|
||||
style: Typography.Style.Caption
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
@@ -110,6 +110,9 @@ DankPopout {
|
||||
Rectangle {
|
||||
id: controlContent
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
|
||||
property alias bluetoothCodecSelector: bluetoothCodecSelector
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.source ? "Active" : "Available"
|
||||
text: modelData === AudioService.source ? I18n.tr("Active") : I18n.tr("Available")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
@@ -241,7 +241,7 @@ Rectangle {
|
||||
StyledText {
|
||||
text: {
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
|
||||
return isThisDevicePinned ? "Pinned" : "Pin";
|
||||
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
|
||||
@@ -211,7 +211,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.sink ? "Active" : "Available"
|
||||
text: modelData === AudioService.sink ? I18n.tr("Active") : I18n.tr("Available")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
@@ -251,7 +251,7 @@ Rectangle {
|
||||
StyledText {
|
||||
text: {
|
||||
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
|
||||
return isThisDevicePinned ? "Pinned" : "Pin";
|
||||
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -66,7 +64,7 @@ Rectangle {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : "Power"
|
||||
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : I18n.tr("Power")
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
@@ -81,7 +79,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
|
||||
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : I18n.tr("Management")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
@@ -100,10 +98,10 @@ Rectangle {
|
||||
StyledText {
|
||||
text: {
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return "Power profile management available";
|
||||
return I18n.tr("Power profile management available");
|
||||
const time = BatteryService.formatTimeRemaining();
|
||||
if (time !== "Unknown") {
|
||||
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`;
|
||||
return BatteryService.isCharging ? I18n.tr("Time until full: %1").arg(time) : I18n.tr("Time remaining: %1").arg(time);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -176,7 +174,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : "Unknown"
|
||||
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : I18n.tr("Unknown")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -42,7 +39,7 @@ Item {
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
BluetoothService.getAvailableCodecs(device, function(codecs, current) {
|
||||
BluetoothService.getAvailableCodecs(device, function (codecs, current) {
|
||||
availableCodecs = codecs;
|
||||
currentCodec = current;
|
||||
isLoading = false;
|
||||
@@ -60,7 +57,7 @@ Item {
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
BluetoothService.switchCodec(device, profileName, function(success, message) {
|
||||
BluetoothService.switchCodec(device, profileName, function (success, message) {
|
||||
isLoading = false;
|
||||
if (success) {
|
||||
ToastService.showToast(message, ToastService.levelInfo);
|
||||
@@ -85,8 +82,12 @@ Item {
|
||||
propagateComposedEvents: false
|
||||
|
||||
onClicked: root.hide()
|
||||
onWheel: (wheel) => { wheel.accepted = true }
|
||||
onPositionChanged: (mouse) => { mouse.accepted = true }
|
||||
onWheel: wheel => {
|
||||
wheel.accepted = true;
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -111,8 +112,8 @@ Item {
|
||||
enabled: root.visible
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
root.hide()
|
||||
event.accepted = true
|
||||
root.hide();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,9 +134,15 @@ Item {
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
propagateComposedEvents: false
|
||||
onClicked: (mouse) => { mouse.accepted = true }
|
||||
onWheel: (wheel) => { wheel.accepted = true }
|
||||
onPositionChanged: (mouse) => { mouse.accepted = true }
|
||||
onClicked: mouse => {
|
||||
mouse.accepted = true;
|
||||
}
|
||||
onWheel: wheel => {
|
||||
wheel.accepted = true;
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -174,9 +181,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -186,7 +191,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: isLoading ? "Loading codecs..." : `Current: ${currentCodec}`
|
||||
text: isLoading ? I18n.tr("Loading codecs...") : I18n.tr("Current: %1").arg(currentCodec)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isLoading ? Theme.primary : Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
@@ -245,9 +250,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
@@ -271,14 +274,9 @@ Item {
|
||||
selectCodec(modelData.profile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -286,7 +284,6 @@ Item {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
@@ -294,8 +291,6 @@ Item {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Scanning" : "Scan"
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? I18n.tr("Scanning") : I18n.tr("Scan")
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
@@ -231,7 +231,7 @@ Rectangle {
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || modelData.deviceName || "Unknown Device"
|
||||
text: modelData.name || modelData.deviceName || I18n.tr("Unknown Device")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
||||
@@ -245,15 +245,15 @@ Rectangle {
|
||||
StyledText {
|
||||
text: {
|
||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||
return "Connecting..."
|
||||
return I18n.tr("Connecting...")
|
||||
if (modelData.connected) {
|
||||
let status = "Connected"
|
||||
let status = I18n.tr("Connected")
|
||||
if (currentCodec) {
|
||||
status += " • " + currentCodec
|
||||
}
|
||||
return status
|
||||
}
|
||||
return "Paired"
|
||||
return I18n.tr("Paired")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
@@ -320,7 +320,7 @@ Rectangle {
|
||||
StyledText {
|
||||
text: {
|
||||
const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address
|
||||
return isThisDevicePinned ? "Pinned" : "Pin"
|
||||
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
@@ -458,7 +458,7 @@ Rectangle {
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || modelData.deviceName || "Unknown Device"
|
||||
text: modelData.name || modelData.deviceName || I18n.tr("Unknown Device")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
@@ -470,8 +470,8 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (modelData.pairing || isBusy) return "Pairing..."
|
||||
if (modelData.blocked) return "Blocked"
|
||||
if (modelData.pairing || isBusy) return I18n.tr("Pairing...")
|
||||
if (modelData.blocked) return I18n.tr("Blocked")
|
||||
return BluetoothService.getSignalStrength(modelData)
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -493,9 +493,9 @@ Rectangle {
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (isBusy) return "Pairing..."
|
||||
if (!canConnect) return "Cannot pair"
|
||||
return "Pair"
|
||||
if (isBusy) return I18n.tr("Pairing...")
|
||||
if (!canConnect) return I18n.tr("Cannot pair")
|
||||
return I18n.tr("Pair")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: (canConnect && !isBusy) ? Theme.primary : Theme.surfaceVariantText
|
||||
@@ -546,7 +546,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? "Disconnect" : "Connect"
|
||||
text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? I18n.tr("Disconnect") : I18n.tr("Connect")
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
|
||||
@@ -141,7 +141,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: DisplayService.brightnessAvailable ? "No brightness devices available" : "Brightness control not available"
|
||||
text: DisplayService.brightnessAvailable ? I18n.tr("No brightness devices available") : I18n.tr("Brightness control not available")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -173,7 +173,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.getScreenPinKey() || "Unknown Monitor"
|
||||
text: root.getScreenPinKey() || I18n.tr("Unknown Monitor")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -201,7 +201,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: isPinnedToScreen ? "Pinned" : "Pin"
|
||||
text: isPinnedToScreen ? I18n.tr("Pinned") : I18n.tr("Pin")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -317,11 +317,11 @@ Rectangle {
|
||||
text: {
|
||||
const deviceClass = modelData.class || "";
|
||||
if (deviceClass === "backlight")
|
||||
return "Backlight device";
|
||||
return I18n.tr("Backlight device");
|
||||
if (deviceClass === "ddc")
|
||||
return "DDC/CI monitor";
|
||||
return I18n.tr("DDC/CI monitor");
|
||||
if (deviceClass === "leds")
|
||||
return "LED device";
|
||||
return I18n.tr("LED device");
|
||||
return deviceClass;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -430,7 +430,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SessionData.getBrightnessExponential(modelData.name) ? "Exponential" : "Linear"
|
||||
text: SessionData.getBrightnessExponential(modelData.name) ? I18n.tr("Exponential") : I18n.tr("Linear")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: SessionData.getBrightnessExponential(modelData.name) ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -20,11 +18,11 @@ Rectangle {
|
||||
border.width: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["diskmounts"])
|
||||
DgopService.addRef(["diskmounts"]);
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["diskmounts"])
|
||||
DgopService.removeRef(["diskmounts"]);
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
@@ -61,7 +59,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: DgopService.dgopAvailable ? "No disk data available" : "dgop not available"
|
||||
text: DgopService.dgopAvailable ? I18n.tr("No disk data available") : I18n.tr("dgop not available")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -96,20 +94,22 @@ Rectangle {
|
||||
name: "storage"
|
||||
size: Theme.iconSize
|
||||
color: {
|
||||
const percentStr = modelData.percent?.replace("%", "") || "0"
|
||||
const percent = parseFloat(percentStr) || 0
|
||||
if (percent > 90) return Theme.error
|
||||
if (percent > 75) return Theme.warning
|
||||
return modelData.mount === currentMountPath ? Theme.primary : Theme.surfaceText
|
||||
const percentStr = modelData.percent?.replace("%", "") || "0";
|
||||
const percent = parseFloat(percentStr) || 0;
|
||||
if (percent > 90)
|
||||
return Theme.error;
|
||||
if (percent > 75)
|
||||
return Theme.warning;
|
||||
return modelData.mount === currentMountPath ? Theme.primary : Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const percentStr = modelData.percent?.replace("%", "") || "0"
|
||||
const percent = parseFloat(percentStr) || 0
|
||||
return percent.toFixed(0) + "%"
|
||||
const percentStr = modelData.percent?.replace("%", "") || "0";
|
||||
const percent = parseFloat(percentStr) || 0;
|
||||
return percent.toFixed(0) + "%";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
@@ -122,7 +122,7 @@ Rectangle {
|
||||
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: modelData.mount === "/" ? "Root Filesystem" : modelData.mount
|
||||
text: modelData.mount === "/" ? I18n.tr("Root Filesystem") : modelData.mount
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.mount === currentMountPath ? Font.Medium : Font.Normal
|
||||
@@ -154,11 +154,10 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
currentMountPath = modelData.mount
|
||||
mountPathChanged(modelData.mount)
|
||||
currentMountPath = modelData.mount;
|
||||
mountPathChanged(modelData.mount);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ Rectangle {
|
||||
buttonHeight: 28
|
||||
textSize: Theme.fontSizeSmall
|
||||
|
||||
model: ["Ethernet", "WiFi"]
|
||||
model: [I18n.tr("Ethernet"), I18n.tr("WiFi")]
|
||||
currentIndex: currentPreferenceIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
@@ -173,7 +173,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
|
||||
text: NetworkService.wifiEnabled ? I18n.tr("Disabling WiFi...") : I18n.tr("Enabling WiFi...")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -303,7 +303,7 @@ Rectangle {
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.id || "Unknown Config"
|
||||
text: modelData.id || I18n.tr("Unknown Config")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData.isActive ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData.isActive ? Font.Medium : Font.Normal
|
||||
@@ -549,7 +549,7 @@ Rectangle {
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.ssid || "Unknown Network"
|
||||
text: modelData.ssid || I18n.tr("Unknown Network")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.ssid === NetworkService.currentWifiSSID ? Font.Medium : Font.Normal
|
||||
@@ -561,13 +561,13 @@ Rectangle {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected •" : (modelData.secured ? "Secured •" : "Open •")
|
||||
text: modelData.ssid === NetworkService.currentWifiSSID ? I18n.tr("Connected") + " •" : (modelData.secured ? I18n.tr("Secured") + " •" : I18n.tr("Open") + " •")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.saved ? "Saved" : ""
|
||||
text: modelData.saved ? I18n.tr("Saved") : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
visible: text.length > 0
|
||||
@@ -635,7 +635,7 @@ Rectangle {
|
||||
StyledText {
|
||||
text: {
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
|
||||
return isThisNetworkPinned ? "Pinned" : "Pin";
|
||||
return isThisNetworkPinned ? I18n.tr("Pinned") : I18n.tr("Pin");
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
@@ -714,7 +714,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: networkContextMenu.currentConnected ? "Disconnect" : "Connect"
|
||||
text: networkContextMenu.currentConnected ? I18n.tr("Disconnect") : I18n.tr("Connect")
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
|
||||
@@ -65,141 +65,141 @@ QtObject {
|
||||
readonly property var coreWidgetDefinitions: [
|
||||
{
|
||||
"id": "nightMode",
|
||||
"text": "Night Mode",
|
||||
"description": "Blue light filter",
|
||||
"text": I18n.tr("Night Mode"),
|
||||
"description": I18n.tr("Blue light filter"),
|
||||
"icon": "nightlight",
|
||||
"type": "toggle",
|
||||
"enabled": DisplayService.automationAvailable,
|
||||
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
|
||||
"warning": !DisplayService.automationAvailable ? I18n.tr("Requires night mode support") : undefined
|
||||
},
|
||||
{
|
||||
"id": "darkMode",
|
||||
"text": "Dark Mode",
|
||||
"description": "System theme toggle",
|
||||
"text": I18n.tr("Dark Mode"),
|
||||
"description": I18n.tr("System theme toggle"),
|
||||
"icon": "contrast",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "doNotDisturb",
|
||||
"text": "Do Not Disturb",
|
||||
"description": "Block notifications",
|
||||
"text": I18n.tr("Do Not Disturb"),
|
||||
"description": I18n.tr("Block notifications"),
|
||||
"icon": "do_not_disturb_on",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "idleInhibitor",
|
||||
"text": "Keep Awake",
|
||||
"description": "Prevent screen timeout",
|
||||
"text": I18n.tr("Keep Awake"),
|
||||
"description": I18n.tr("Prevent screen timeout"),
|
||||
"icon": "motion_sensor_active",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "wifi",
|
||||
"text": "Network",
|
||||
"description": "Wi-Fi and Ethernet connection",
|
||||
"text": I18n.tr("Network"),
|
||||
"description": I18n.tr("Wi-Fi and Ethernet connection"),
|
||||
"icon": "wifi",
|
||||
"type": "connection",
|
||||
"enabled": NetworkService.wifiAvailable,
|
||||
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
|
||||
"warning": !NetworkService.wifiAvailable ? I18n.tr("Wi-Fi not available") : undefined
|
||||
},
|
||||
{
|
||||
"id": "bluetooth",
|
||||
"text": "Bluetooth",
|
||||
"description": "Device connections",
|
||||
"text": I18n.tr("Bluetooth"),
|
||||
"description": I18n.tr("Device connections"),
|
||||
"icon": "bluetooth",
|
||||
"type": "connection",
|
||||
"enabled": BluetoothService.available,
|
||||
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined
|
||||
"warning": !BluetoothService.available ? I18n.tr("Bluetooth not available") : undefined
|
||||
},
|
||||
{
|
||||
"id": "audioOutput",
|
||||
"text": "Audio Output",
|
||||
"description": "Speaker settings",
|
||||
"text": I18n.tr("Audio Output"),
|
||||
"description": I18n.tr("Speaker settings"),
|
||||
"icon": "volume_up",
|
||||
"type": "connection",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "audioInput",
|
||||
"text": "Audio Input",
|
||||
"description": "Microphone settings",
|
||||
"text": I18n.tr("Audio Input"),
|
||||
"description": I18n.tr("Microphone settings"),
|
||||
"icon": "mic",
|
||||
"type": "connection",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "volumeSlider",
|
||||
"text": "Volume Slider",
|
||||
"description": "Audio volume control",
|
||||
"text": I18n.tr("Volume Slider"),
|
||||
"description": I18n.tr("Audio volume control"),
|
||||
"icon": "volume_up",
|
||||
"type": "slider",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "brightnessSlider",
|
||||
"text": "Brightness Slider",
|
||||
"description": "Display brightness control",
|
||||
"text": I18n.tr("Brightness Slider"),
|
||||
"description": I18n.tr("Display brightness control"),
|
||||
"icon": "brightness_6",
|
||||
"type": "slider",
|
||||
"enabled": DisplayService.brightnessAvailable,
|
||||
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined,
|
||||
"warning": !DisplayService.brightnessAvailable ? I18n.tr("Brightness control not available") : undefined,
|
||||
"allowMultiple": true
|
||||
},
|
||||
{
|
||||
"id": "inputVolumeSlider",
|
||||
"text": "Input Volume Slider",
|
||||
"description": "Microphone volume control",
|
||||
"text": I18n.tr("Input Volume Slider"),
|
||||
"description": I18n.tr("Microphone volume control"),
|
||||
"icon": "mic",
|
||||
"type": "slider",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "battery",
|
||||
"text": "Battery",
|
||||
"description": "Battery and power management",
|
||||
"text": I18n.tr("Battery"),
|
||||
"description": I18n.tr("Battery and power management"),
|
||||
"icon": "battery_std",
|
||||
"type": "action",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "diskUsage",
|
||||
"text": "Disk Usage",
|
||||
"description": "Filesystem usage monitoring",
|
||||
"text": I18n.tr("Disk Usage"),
|
||||
"description": I18n.tr("Filesystem usage monitoring"),
|
||||
"icon": "storage",
|
||||
"type": "action",
|
||||
"enabled": DgopService.dgopAvailable,
|
||||
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
|
||||
"warning": !DgopService.dgopAvailable ? I18n.tr("Requires 'dgop' tool") : undefined,
|
||||
"allowMultiple": true
|
||||
},
|
||||
{
|
||||
"id": "colorPicker",
|
||||
"text": "Color Picker",
|
||||
"description": "Choose colors from palette",
|
||||
"text": I18n.tr("Color Picker"),
|
||||
"description": I18n.tr("Choose colors from palette"),
|
||||
"icon": "palette",
|
||||
"type": "action",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "builtin_vpn",
|
||||
"text": "VPN",
|
||||
"description": "VPN connections",
|
||||
"text": I18n.tr("VPN"),
|
||||
"description": I18n.tr("VPN connections"),
|
||||
"icon": "vpn_key",
|
||||
"type": "builtin_plugin",
|
||||
"enabled": DMSNetworkService.available,
|
||||
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
|
||||
"warning": !DMSNetworkService.available ? I18n.tr("VPN not available") : undefined,
|
||||
"isBuiltinPlugin": true
|
||||
},
|
||||
{
|
||||
"id": "builtin_cups",
|
||||
"text": "Printers",
|
||||
"description": "Print Server Management",
|
||||
"text": I18n.tr("Printers"),
|
||||
"description": I18n.tr("Print Server Management"),
|
||||
"icon": "Print",
|
||||
"type": "builtin_plugin",
|
||||
"enabled": CupsService.available,
|
||||
"warning": !CupsService.available ? "CUPS not available" : undefined,
|
||||
"warning": !CupsService.available ? I18n.tr("CUPS not available") : undefined,
|
||||
"isBuiltinPlugin": true
|
||||
}
|
||||
]
|
||||
@@ -235,7 +235,7 @@ QtObject {
|
||||
plugins.push({
|
||||
"id": "plugin_" + plugin.id,
|
||||
"pluginId": plugin.id,
|
||||
"text": plugin.name || "Plugin",
|
||||
"text": plugin.name || I18n.tr("Plugin"),
|
||||
"description": plugin.description || "",
|
||||
"icon": plugin.icon || "extension",
|
||||
"type": "plugin",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
CompoundPill {
|
||||
@@ -14,35 +12,35 @@ CompoundPill {
|
||||
|
||||
primaryText: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return "No battery"
|
||||
return I18n.tr("No battery");
|
||||
}
|
||||
return "Battery"
|
||||
return I18n.tr("Battery");
|
||||
}
|
||||
|
||||
secondaryText: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return "Not available"
|
||||
return I18n.tr("Not available");
|
||||
}
|
||||
if (BatteryService.isCharging) {
|
||||
return `${BatteryService.batteryLevel}% • Charging`
|
||||
return `${BatteryService.batteryLevel}% • ` + I18n.tr("Charging");
|
||||
}
|
||||
if (BatteryService.isPluggedIn) {
|
||||
return `${BatteryService.batteryLevel}% • Plugged in`
|
||||
return `${BatteryService.batteryLevel}% • ` + I18n.tr("Plugged in");
|
||||
}
|
||||
return `${BatteryService.batteryLevel}%`
|
||||
return `${BatteryService.batteryLevel}%`;
|
||||
}
|
||||
|
||||
iconColor: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
return Theme.error;
|
||||
}
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||
return Theme.primary
|
||||
return Theme.primary;
|
||||
}
|
||||
return Theme.surfaceText
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
expandClicked()
|
||||
expandClicked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
CompoundPill {
|
||||
@@ -14,20 +10,20 @@ CompoundPill {
|
||||
isActive: true
|
||||
iconName: "palette"
|
||||
iconColor: Theme.primary
|
||||
primaryText: "Color Picker"
|
||||
secondaryText: "Choose a color"
|
||||
primaryText: I18n.tr("Color Picker")
|
||||
secondaryText: I18n.tr("Choose a color")
|
||||
|
||||
onToggled: {
|
||||
console.log("ColorPickerPill toggled, modal:", colorPickerModal)
|
||||
console.log("ColorPickerPill toggled, modal:", colorPickerModal);
|
||||
if (colorPickerModal) {
|
||||
colorPickerModal.show()
|
||||
colorPickerModal.show();
|
||||
}
|
||||
}
|
||||
|
||||
onExpandClicked: {
|
||||
console.log("ColorPickerPill expandClicked, modal:", colorPickerModal)
|
||||
console.log("ColorPickerPill expandClicked, modal:", colorPickerModal);
|
||||
if (colorPickerModal) {
|
||||
colorPickerModal.show()
|
||||
colorPickerModal.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
CompoundPill {
|
||||
@@ -15,64 +13,64 @@ CompoundPill {
|
||||
|
||||
property var selectedMount: {
|
||||
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetMount = DgopService.diskMounts.find(mount => mount.mount === mountPath)
|
||||
return targetMount || DgopService.diskMounts.find(mount => mount.mount === "/") || DgopService.diskMounts[0]
|
||||
const targetMount = DgopService.diskMounts.find(mount => mount.mount === mountPath);
|
||||
return targetMount || DgopService.diskMounts.find(mount => mount.mount === "/") || DgopService.diskMounts[0];
|
||||
}
|
||||
|
||||
property real usagePercent: {
|
||||
if (!selectedMount || !selectedMount.percent) {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
const percentStr = selectedMount.percent.replace("%", "")
|
||||
return parseFloat(percentStr) || 0
|
||||
const percentStr = selectedMount.percent.replace("%", "");
|
||||
return parseFloat(percentStr) || 0;
|
||||
}
|
||||
|
||||
isActive: DgopService.dgopAvailable && selectedMount !== null
|
||||
|
||||
primaryText: {
|
||||
if (!DgopService.dgopAvailable) {
|
||||
return "Disk Usage"
|
||||
return I18n.tr("Disk Usage");
|
||||
}
|
||||
if (!selectedMount) {
|
||||
return "No disk data"
|
||||
return I18n.tr("No disk data");
|
||||
}
|
||||
return selectedMount.mount
|
||||
return selectedMount.mount;
|
||||
}
|
||||
|
||||
secondaryText: {
|
||||
if (!DgopService.dgopAvailable) {
|
||||
return "dgop not available"
|
||||
return I18n.tr("dgop not available");
|
||||
}
|
||||
if (!selectedMount) {
|
||||
return "No disk data available"
|
||||
return I18n.tr("No disk data available");
|
||||
}
|
||||
return `${selectedMount.used} / ${selectedMount.size} (${usagePercent.toFixed(0)}%)`
|
||||
return `${selectedMount.used} / ${selectedMount.size} (${usagePercent.toFixed(0)}%)`;
|
||||
}
|
||||
|
||||
iconColor: {
|
||||
if (!DgopService.dgopAvailable || !selectedMount) {
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5);
|
||||
}
|
||||
if (usagePercent > 90) {
|
||||
return Theme.error
|
||||
return Theme.error;
|
||||
}
|
||||
if (usagePercent > 75) {
|
||||
return Theme.warning
|
||||
return Theme.warning;
|
||||
}
|
||||
return Theme.surfaceText
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["diskmounts"])
|
||||
DgopService.addRef(["diskmounts"]);
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["diskmounts"])
|
||||
DgopService.removeRef(["diskmounts"]);
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
expandClicked()
|
||||
expandClicked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ DankPopout {
|
||||
Rectangle {
|
||||
id: batteryContent
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||
color: "transparent"
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
@@ -20,21 +19,21 @@ BasePill {
|
||||
|
||||
readonly property real minTooltipY: {
|
||||
if (!parentScreen || !isVerticalOrientation) {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isAutoHideBar) {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (parentScreen.y > 0) {
|
||||
return barThickness + barSpacing
|
||||
return barThickness + barSpacing;
|
||||
}
|
||||
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
signal toggleVpnPopup()
|
||||
signal toggleVpnPopup
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
@@ -72,56 +71,63 @@ BasePill {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
enabled: !DMSNetworkService.isBusy
|
||||
onPressed: {
|
||||
root.toggleVpnPopup()
|
||||
onPressed: event => {
|
||||
switch (event.button) {
|
||||
case Qt.RightButton:
|
||||
DMSNetworkService.toggleVpn();
|
||||
return;
|
||||
case Qt.LeftButton:
|
||||
root.toggleVpnPopup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
if (!root.parentScreen || (popoutTarget?.shouldBeVisible)) return
|
||||
|
||||
tooltipLoader.active = true
|
||||
if (!tooltipLoader.item) return
|
||||
|
||||
let tooltipText = ""
|
||||
if (!root.parentScreen || (popoutTarget?.shouldBeVisible))
|
||||
return;
|
||||
tooltipLoader.active = true;
|
||||
if (!tooltipLoader.item)
|
||||
return;
|
||||
let tooltipText = "";
|
||||
if (!DMSNetworkService.connected) {
|
||||
tooltipText = "VPN Disconnected"
|
||||
tooltipText = "VPN Disconnected";
|
||||
} else {
|
||||
const names = DMSNetworkService.activeNames || []
|
||||
const names = DMSNetworkService.activeNames || [];
|
||||
if (names.length <= 1) {
|
||||
const name = names[0] || ""
|
||||
const maxLength = 25
|
||||
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name
|
||||
tooltipText = "VPN Connected • " + displayName
|
||||
const name = names[0] || "";
|
||||
const maxLength = 25;
|
||||
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name;
|
||||
tooltipText = "VPN Connected • " + displayName;
|
||||
} else {
|
||||
const name = names[0]
|
||||
const maxLength = 20
|
||||
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name
|
||||
tooltipText = "VPN Connected • " + displayName + " +" + (names.length - 1)
|
||||
const name = names[0];
|
||||
const maxLength = 20;
|
||||
const displayName = name.length > maxLength ? name.substring(0, maxLength) + "..." : name;
|
||||
tooltipText = "VPN Connected • " + displayName + " +" + (names.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (root.isVerticalOrientation) {
|
||||
const globalPos = mapToGlobal(width / 2, height / 2)
|
||||
const currentScreen = root.parentScreen || Screen
|
||||
const screenX = currentScreen ? currentScreen.x : 0
|
||||
const screenY = currentScreen ? currentScreen.y : 0
|
||||
const relativeY = globalPos.y - screenY
|
||||
const adjustedY = relativeY + root.minTooltipY
|
||||
const tooltipX = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (currentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS)
|
||||
const isLeft = root.axis?.edge === "left"
|
||||
tooltipLoader.item.show(tooltipText, screenX + tooltipX, adjustedY, currentScreen, isLeft, !isLeft)
|
||||
const globalPos = mapToGlobal(width / 2, height / 2);
|
||||
const currentScreen = root.parentScreen || Screen;
|
||||
const screenX = currentScreen ? currentScreen.x : 0;
|
||||
const screenY = currentScreen ? currentScreen.y : 0;
|
||||
const relativeY = globalPos.y - screenY;
|
||||
const adjustedY = relativeY + root.minTooltipY;
|
||||
const tooltipX = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (currentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
|
||||
const isLeft = root.axis?.edge === "left";
|
||||
tooltipLoader.item.show(tooltipText, screenX + tooltipX, adjustedY, currentScreen, isLeft, !isLeft);
|
||||
} else {
|
||||
const globalPos = mapToGlobal(width / 2, height)
|
||||
const tooltipY = root.barThickness + root.barSpacing + Theme.spacingXS
|
||||
tooltipLoader.item.show(tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false)
|
||||
const globalPos = mapToGlobal(width / 2, height);
|
||||
const tooltipY = root.barThickness + root.barSpacing + Theme.spacingXS;
|
||||
tooltipLoader.item.show(tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hide()
|
||||
tooltipLoader.item.hide();
|
||||
}
|
||||
tooltipLoader.active = false
|
||||
tooltipLoader.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function getWorkspaceIndex(modelData) {
|
||||
function getWorkspaceIndex(modelData, index) {
|
||||
let isPlaceholder;
|
||||
if (root.useExtWorkspace) {
|
||||
isPlaceholder = modelData?.hidden === true;
|
||||
@@ -976,7 +976,7 @@ Item {
|
||||
StyledText {
|
||||
id: wsIndexText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.getWorkspaceIndex(modelData)
|
||||
text: root.getWorkspaceIndex(modelData, index)
|
||||
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||
@@ -1203,12 +1203,12 @@ Item {
|
||||
Loader {
|
||||
id: indexLoader
|
||||
anchors.fill: parent
|
||||
active: !isPlaceholder && SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
|
||||
active: SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
|
||||
sourceComponent: Item {
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
return root.getWorkspaceIndex(modelData);
|
||||
return root.getWorkspaceIndex(modelData, index);
|
||||
}
|
||||
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||
|
||||
@@ -166,6 +166,9 @@ DankPopout {
|
||||
Rectangle {
|
||||
id: mainContainer
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
implicitHeight: contentColumn.height + Theme.spacingM * 2
|
||||
color: "transparent"
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
@@ -8,6 +8,9 @@ import qs.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property int dropdownType: 0
|
||||
property var activePlayer: null
|
||||
property var allPlayers: []
|
||||
|
||||
@@ -10,6 +10,9 @@ import qs.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
property var allPlayers: MprisController.availablePlayers
|
||||
property var targetScreen: null
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user