mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-27 21:45:19 -04:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e573bdba92 | |||
| d8cd15d361 | |||
| 1db3907838 | |||
| 72cfd37ab7 | |||
| 1e67ee995e | |||
| 6c26b4080c | |||
| 0dbd59b223 | |||
| b2066c60d1 | |||
| 8d7ae324ff | |||
| c0d3c4f875 | |||
| 27a771648a | |||
| 86affc7304 | |||
| d939b99628 | |||
| 1fcf777f3d | |||
| 7a8e23faa9 | |||
| 73a4dd3321 | |||
| 13ce873a69 |
+245
-258
@@ -1,19 +1,16 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
push:
|
||||||
inputs:
|
tags:
|
||||||
tag:
|
- "v*"
|
||||||
description: "Tag to release (e.g., v1.0.1)"
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
actions: write
|
actions: write
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: release-${{ inputs.tag }}
|
group: release-${{ github.ref_name }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -27,14 +24,10 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: core
|
working-directory: core
|
||||||
|
|
||||||
env:
|
|
||||||
TAG: ${{ inputs.tag }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.tag }}
|
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
@@ -61,7 +54,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -eux
|
set -eux
|
||||||
cd cmd/dankinstall
|
cd cmd/dankinstall
|
||||||
go build -trimpath -ldflags "-s -w -X main.Version=${TAG}" \
|
go build -trimpath -ldflags "-s -w -X main.Version=${GITHUB_REF#refs/tags/}" \
|
||||||
-o ../../dankinstall-${{ matrix.arch }}
|
-o ../../dankinstall-${{ matrix.arch }}
|
||||||
cd ../..
|
cd ../..
|
||||||
gzip -9 -k dankinstall-${{ matrix.arch }}
|
gzip -9 -k dankinstall-${{ matrix.arch }}
|
||||||
@@ -75,7 +68,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -eux
|
set -eux
|
||||||
cd cmd/dms
|
cd cmd/dms
|
||||||
go build -trimpath -ldflags "-s -w -X main.Version=${TAG}" \
|
go build -trimpath -ldflags "-s -w -X main.Version=${GITHUB_REF#refs/tags/}" \
|
||||||
-o ../../dms-${{ matrix.arch }}
|
-o ../../dms-${{ matrix.arch }}
|
||||||
cd ../..
|
cd ../..
|
||||||
gzip -9 -k dms-${{ matrix.arch }}
|
gzip -9 -k dms-${{ matrix.arch }}
|
||||||
@@ -98,7 +91,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -eux
|
set -eux
|
||||||
cd cmd/dms
|
cd cmd/dms
|
||||||
go build -trimpath -tags distro_binary -ldflags "-s -w -X main.Version=${TAG}" \
|
go build -trimpath -tags distro_binary -ldflags "-s -w -X main.Version=${GITHUB_REF#refs/tags/}" \
|
||||||
-o ../../dms-distropkg-${{ matrix.arch }}
|
-o ../../dms-distropkg-${{ matrix.arch }}
|
||||||
cd ../..
|
cd ../..
|
||||||
gzip -9 -k dms-distropkg-${{ matrix.arch }}
|
gzip -9 -k dms-distropkg-${{ matrix.arch }}
|
||||||
@@ -178,18 +171,17 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: [build-core] #, update-versions]
|
needs: [build-core] #, update-versions]
|
||||||
env:
|
env:
|
||||||
TAG: ${{ inputs.tag }}
|
TAG: ${{ github.ref_name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.tag }}
|
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Fetch updated tag after version bump
|
- name: Fetch updated tag after version bump
|
||||||
run: |
|
run: |
|
||||||
git fetch origin --force tag ${TAG}
|
git fetch origin --force tag ${{ github.ref_name }}
|
||||||
git checkout ${TAG}
|
git checkout ${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Download core artifacts
|
- name: Download core artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -280,6 +272,9 @@ jobs:
|
|||||||
# Create QML source package (exclude build artifacts and git files)
|
# Create QML source package (exclude build artifacts and git files)
|
||||||
# Copy root LICENSE and CONTRIBUTING.md to quickshell/ for packaging
|
# Copy root LICENSE and CONTRIBUTING.md to quickshell/ for packaging
|
||||||
cp LICENSE CONTRIBUTING.md quickshell/
|
cp LICENSE CONTRIBUTING.md quickshell/
|
||||||
|
|
||||||
|
# Copy root assets directory to quickshell for systemd service and desktop file
|
||||||
|
cp -r assets quickshell/
|
||||||
|
|
||||||
# Tar the CONTENTS of quickshell/, not the directory itself
|
# Tar the CONTENTS of quickshell/, not the directory itself
|
||||||
(cd quickshell && tar --exclude='.git' \
|
(cd quickshell && tar --exclude='.git' \
|
||||||
@@ -396,296 +391,288 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# trigger-obs-update:
|
trigger-obs-update:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# needs: release
|
needs: release
|
||||||
# env:
|
steps:
|
||||||
# TAG: ${{ inputs.tag }}
|
- name: Checkout
|
||||||
# steps:
|
uses: actions/checkout@v4
|
||||||
# - name: Checkout
|
|
||||||
# uses: actions/checkout@v4
|
|
||||||
# with:
|
|
||||||
# ref: ${{ inputs.tag }}
|
|
||||||
|
|
||||||
# - name: Install OSC
|
- name: Install OSC
|
||||||
# run: |
|
run: |
|
||||||
# sudo apt-get update
|
sudo apt-get update
|
||||||
# sudo apt-get install -y osc
|
sudo apt-get install -y osc
|
||||||
|
|
||||||
# mkdir -p ~/.config/osc
|
mkdir -p ~/.config/osc
|
||||||
# cat > ~/.config/osc/oscrc << EOF
|
cat > ~/.config/osc/oscrc << EOF
|
||||||
# [general]
|
[general]
|
||||||
# apiurl = https://api.opensuse.org
|
apiurl = https://api.opensuse.org
|
||||||
|
|
||||||
# [https://api.opensuse.org]
|
[https://api.opensuse.org]
|
||||||
# user = ${{ secrets.OBS_USERNAME }}
|
user = ${{ secrets.OBS_USERNAME }}
|
||||||
# pass = ${{ secrets.OBS_PASSWORD }}
|
pass = ${{ secrets.OBS_PASSWORD }}
|
||||||
# EOF
|
EOF
|
||||||
# chmod 600 ~/.config/osc/oscrc
|
chmod 600 ~/.config/osc/oscrc
|
||||||
|
|
||||||
# - name: Update OBS packages
|
- name: Update OBS packages
|
||||||
# run: |
|
run: |
|
||||||
# cd distro
|
VERSION="${{ github.ref_name }}"
|
||||||
# bash scripts/obs-upload.sh dms "Update to ${TAG}"
|
cd distro
|
||||||
|
bash scripts/obs-upload.sh dms "Update to $VERSION"
|
||||||
|
|
||||||
# trigger-ppa-update:
|
trigger-ppa-update:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# needs: release
|
needs: release
|
||||||
# env:
|
steps:
|
||||||
# TAG: ${{ inputs.tag }}
|
- name: Checkout
|
||||||
# steps:
|
uses: actions/checkout@v4
|
||||||
# - name: Checkout
|
|
||||||
# uses: actions/checkout@v4
|
|
||||||
# with:
|
|
||||||
# ref: ${{ inputs.tag }}
|
|
||||||
|
|
||||||
# - name: Install build dependencies
|
- name: Install build dependencies
|
||||||
# run: |
|
run: |
|
||||||
# sudo apt-get update
|
sudo apt-get update
|
||||||
# sudo apt-get install -y \
|
sudo apt-get install -y \
|
||||||
# debhelper \
|
debhelper \
|
||||||
# devscripts \
|
devscripts \
|
||||||
# dput \
|
dput \
|
||||||
# lftp \
|
lftp \
|
||||||
# build-essential \
|
build-essential \
|
||||||
# fakeroot \
|
fakeroot \
|
||||||
# dpkg-dev
|
dpkg-dev
|
||||||
|
|
||||||
# - name: Configure GPG
|
- name: Configure GPG
|
||||||
# env:
|
env:
|
||||||
# GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
# run: |
|
run: |
|
||||||
# echo "$GPG_KEY" | gpg --import
|
echo "$GPG_KEY" | gpg --import
|
||||||
# GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | awk '{print $2}' | cut -d'/' -f2)
|
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
|
echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV
|
||||||
|
|
||||||
# - name: Upload to PPA
|
- name: Upload to PPA
|
||||||
# run: |
|
run: |
|
||||||
# cd distro/ubuntu/ppa
|
VERSION="${{ github.ref_name }}"
|
||||||
# bash create-and-upload.sh ../dms dms questing
|
cd distro/ubuntu/ppa
|
||||||
|
bash create-and-upload.sh ../dms dms questing
|
||||||
|
|
||||||
# copr-build:
|
copr-build:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# needs: release
|
needs: release
|
||||||
# env:
|
env:
|
||||||
# TAG: ${{ inputs.tag }}
|
TAG: ${{ github.ref_name }}
|
||||||
|
|
||||||
# steps:
|
steps:
|
||||||
# - name: Checkout repository
|
- name: Checkout repository
|
||||||
# uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
# with:
|
|
||||||
# ref: ${{ inputs.tag }}
|
|
||||||
|
|
||||||
# - name: Determine version
|
- name: Determine version
|
||||||
# id: version
|
id: version
|
||||||
# run: |
|
run: |
|
||||||
# VERSION="${TAG#v}"
|
VERSION="${TAG#v}"
|
||||||
# echo "version=$VERSION" >> $GITHUB_OUTPUT
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
# echo "Building DMS stable version: $VERSION"
|
echo "Building DMS stable version: $VERSION"
|
||||||
|
|
||||||
# - name: Setup build environment
|
- name: Setup build environment
|
||||||
# run: |
|
run: |
|
||||||
# sudo apt-get update
|
sudo apt-get update
|
||||||
# sudo apt-get install -y rpm wget curl jq gzip
|
sudo apt-get install -y rpm wget curl jq gzip
|
||||||
# mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
|
|
||||||
# - name: Download release assets
|
- name: Download release assets
|
||||||
# run: |
|
run: |
|
||||||
# VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
# cd ~/rpmbuild/SOURCES
|
cd ~/rpmbuild/SOURCES
|
||||||
|
|
||||||
# wget "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
|
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}"
|
echo "Failed to download dms-qml.tar.gz for v${VERSION}"
|
||||||
# exit 1
|
exit 1
|
||||||
# }
|
}
|
||||||
|
|
||||||
# - name: Generate stable spec file
|
- name: Generate stable spec file
|
||||||
# run: |
|
run: |
|
||||||
# VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
# CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
||||||
|
|
||||||
# cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
||||||
# # Spec for DMS stable releases - Generated by GitHub Actions
|
# Spec for DMS stable releases - Generated by GitHub Actions
|
||||||
|
|
||||||
# %global debug_package %{nil}
|
%global debug_package %{nil}
|
||||||
# %global version VERSION_PLACEHOLDER
|
%global version VERSION_PLACEHOLDER
|
||||||
# %global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||||
|
|
||||||
# Name: dms
|
Name: dms
|
||||||
# Version: %{version}
|
Version: %{version}
|
||||||
# Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
# Summary: %{pkg_summary}
|
Summary: %{pkg_summary}
|
||||||
|
|
||||||
# License: MIT
|
License: MIT
|
||||||
# URL: https://github.com/AvengeMedia/DankMaterialShell
|
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||||
|
|
||||||
# Source0: dms-qml.tar.gz
|
Source0: dms-qml.tar.gz
|
||||||
|
|
||||||
# BuildRequires: gzip
|
BuildRequires: gzip
|
||||||
# BuildRequires: wget
|
BuildRequires: wget
|
||||||
# BuildRequires: systemd-rpm-macros
|
BuildRequires: systemd-rpm-macros
|
||||||
|
|
||||||
# Requires: (quickshell or quickshell-git)
|
Requires: (quickshell or quickshell-git)
|
||||||
# Requires: accountsservice
|
Requires: accountsservice
|
||||||
# Requires: dms-cli = %{version}-%{release}
|
Requires: dms-cli = %{version}-%{release}
|
||||||
# Requires: dgop
|
Requires: dgop
|
||||||
|
|
||||||
# Recommends: cava
|
Recommends: cava
|
||||||
# Recommends: cliphist
|
Recommends: cliphist
|
||||||
# Recommends: danksearch
|
Recommends: danksearch
|
||||||
# Recommends: matugen
|
Recommends: matugen
|
||||||
# Recommends: wl-clipboard
|
Recommends: wl-clipboard
|
||||||
# Recommends: NetworkManager
|
Recommends: NetworkManager
|
||||||
# Recommends: qt6-qtmultimedia
|
Recommends: qt6-qtmultimedia
|
||||||
# Suggests: qt6ct
|
Suggests: qt6ct
|
||||||
|
|
||||||
# %description
|
%description
|
||||||
# DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||||
# and optimized for the niri and hyprland compositors. Features notifications,
|
and optimized for the niri and hyprland compositors. Features notifications,
|
||||||
# app launcher, wallpaper customization, and fully customizable with plugins.
|
app launcher, wallpaper customization, and fully customizable with plugins.
|
||||||
|
|
||||||
# Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||||
# process monitoring, notification center, clipboard history, dock, control center,
|
process monitoring, notification center, clipboard history, dock, control center,
|
||||||
# lock screen, and comprehensive plugin system.
|
lock screen, and comprehensive plugin system.
|
||||||
|
|
||||||
# %package -n dms-cli
|
%package -n dms-cli
|
||||||
# Summary: DankMaterialShell CLI tool
|
Summary: DankMaterialShell CLI tool
|
||||||
# License: MIT
|
License: MIT
|
||||||
# URL: https://github.com/AvengeMedia/DankMaterialShell
|
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||||
|
|
||||||
# %description -n dms-cli
|
%description -n dms-cli
|
||||||
# Command-line interface for DankMaterialShell configuration and management.
|
Command-line interface for DankMaterialShell configuration and management.
|
||||||
# Provides native DBus bindings, NetworkManager integration, and system utilities.
|
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||||
|
|
||||||
# %prep
|
%prep
|
||||||
# %setup -q -c -n dms-qml
|
%setup -q -c -n dms-qml
|
||||||
|
|
||||||
# # Download architecture-specific binaries during build
|
# Download architecture-specific binaries during build
|
||||||
# case "%{_arch}" in
|
case "%{_arch}" in
|
||||||
# x86_64)
|
x86_64)
|
||||||
# ARCH_SUFFIX="amd64"
|
ARCH_SUFFIX="amd64"
|
||||||
# ;;
|
;;
|
||||||
# aarch64)
|
aarch64)
|
||||||
# ARCH_SUFFIX="arm64"
|
ARCH_SUFFIX="arm64"
|
||||||
# ;;
|
;;
|
||||||
# *)
|
*)
|
||||||
# echo "Unsupported architecture: %{_arch}"
|
echo "Unsupported architecture: %{_arch}"
|
||||||
# exit 1
|
exit 1
|
||||||
# ;;
|
;;
|
||||||
# esac
|
esac
|
||||||
|
|
||||||
# wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
|
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}"
|
echo "Failed to download dms-cli for architecture %{_arch}"
|
||||||
# exit 1
|
exit 1
|
||||||
# }
|
}
|
||||||
# gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
||||||
# chmod +x %{_builddir}/dms-cli
|
chmod +x %{_builddir}/dms-cli
|
||||||
|
|
||||||
# %build
|
%build
|
||||||
|
|
||||||
# %install
|
%install
|
||||||
# install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
||||||
|
|
||||||
# install -d %{buildroot}%{_datadir}/bash-completion/completions
|
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||||
# install -d %{buildroot}%{_datadir}/zsh/site-functions
|
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||||
# install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
||||||
# %{_builddir}/dms-cli completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
%{_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 zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||||
# %{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
%{_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/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||||
|
|
||||||
# install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
|
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 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||||
|
|
||||||
# install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||||
# cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
|
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||||
|
|
||||||
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
||||||
# rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
||||||
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||||
# rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
||||||
|
|
||||||
# echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
||||||
|
|
||||||
# %posttrans
|
%posttrans
|
||||||
# if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
||||||
# rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
||||||
# rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||||
# rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||||
# fi
|
fi
|
||||||
# # Signal running DMS instances to reload
|
# Signal running DMS instances to reload
|
||||||
# pkill -USR1 -x dms >/dev/null 2>&1 || :
|
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||||
|
|
||||||
# %files
|
%files
|
||||||
# %license LICENSE
|
%license LICENSE
|
||||||
# %doc README.md CONTRIBUTING.md
|
%doc README.md CONTRIBUTING.md
|
||||||
# %{_datadir}/quickshell/dms/
|
%{_datadir}/quickshell/dms/
|
||||||
# %{_userunitdir}/dms.service
|
%{_userunitdir}/dms.service
|
||||||
# %{_datadir}/applications/dms-open.desktop
|
%{_datadir}/applications/dms-open.desktop
|
||||||
# %{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||||
|
|
||||||
# %files -n dms-cli
|
%files -n dms-cli
|
||||||
# %{_bindir}/dms
|
%{_bindir}/dms
|
||||||
# %{_datadir}/bash-completion/completions/dms
|
%{_datadir}/bash-completion/completions/dms
|
||||||
# %{_datadir}/zsh/site-functions/_dms
|
%{_datadir}/zsh/site-functions/_dms
|
||||||
# %{_datadir}/fish/vendor_completions.d/dms.fish
|
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||||
|
|
||||||
# %changelog
|
%changelog
|
||||||
# * CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
||||||
# - Stable release VERSION_PLACEHOLDER
|
- Stable release VERSION_PLACEHOLDER
|
||||||
# - Built from GitHub release
|
- Built from GitHub release
|
||||||
# SPECEOF
|
SPECEOF
|
||||||
|
|
||||||
# sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
# sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
|
|
||||||
# - name: Build SRPM
|
- name: Build SRPM
|
||||||
# id: build
|
id: build
|
||||||
# run: |
|
run: |
|
||||||
# cd ~/rpmbuild/SPECS
|
cd ~/rpmbuild/SPECS
|
||||||
# rpmbuild -bs dms.spec
|
rpmbuild -bs dms.spec
|
||||||
|
|
||||||
# SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
|
SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
|
||||||
# SRPM_NAME=$(basename "$SRPM")
|
SRPM_NAME=$(basename "$SRPM")
|
||||||
|
|
||||||
# echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
|
echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
|
||||||
# echo "srpm_name=$SRPM_NAME" >> $GITHUB_OUTPUT
|
echo "srpm_name=$SRPM_NAME" >> $GITHUB_OUTPUT
|
||||||
# echo "SRPM built: $SRPM_NAME"
|
echo "SRPM built: $SRPM_NAME"
|
||||||
|
|
||||||
# - name: Upload SRPM artifact
|
- name: Upload SRPM artifact
|
||||||
# uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
# with:
|
with:
|
||||||
# name: dms-stable-srpm-${{ steps.version.outputs.version }}
|
name: dms-stable-srpm-${{ steps.version.outputs.version }}
|
||||||
# path: ${{ steps.build.outputs.srpm_path }}
|
path: ${{ steps.build.outputs.srpm_path }}
|
||||||
# retention-days: 90
|
retention-days: 90
|
||||||
|
|
||||||
# - name: Install Copr CLI
|
- name: Install Copr CLI
|
||||||
# run: |
|
run: |
|
||||||
# sudo apt-get install -y python3-pip
|
sudo apt-get install -y python3-pip
|
||||||
# pip3 install copr-cli
|
pip3 install copr-cli
|
||||||
|
|
||||||
# mkdir -p ~/.config
|
mkdir -p ~/.config
|
||||||
# cat > ~/.config/copr << EOF
|
cat > ~/.config/copr << EOF
|
||||||
# [copr-cli]
|
[copr-cli]
|
||||||
# login = ${{ secrets.COPR_LOGIN }}
|
login = ${{ secrets.COPR_LOGIN }}
|
||||||
# username = avengemedia
|
username = avengemedia
|
||||||
# token = ${{ secrets.COPR_TOKEN }}
|
token = ${{ secrets.COPR_TOKEN }}
|
||||||
# copr_url = https://copr.fedorainfracloud.org
|
copr_url = https://copr.fedorainfracloud.org
|
||||||
# EOF
|
EOF
|
||||||
# chmod 600 ~/.config/copr
|
chmod 600 ~/.config/copr
|
||||||
|
|
||||||
# - name: Upload to Copr
|
- name: Upload to Copr
|
||||||
# run: |
|
run: |
|
||||||
# SRPM="${{ steps.build.outputs.srpm_path }}"
|
SRPM="${{ steps.build.outputs.srpm_path }}"
|
||||||
# VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
# echo "Uploading SRPM to avengemedia/dms..."
|
echo "Uploading SRPM to avengemedia/dms..."
|
||||||
# BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
|
BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
|
||||||
# echo "$BUILD_OUTPUT"
|
echo "$BUILD_OUTPUT"
|
||||||
|
|
||||||
# BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
|
BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
|
||||||
|
|
||||||
# if [ "$BUILD_ID" != "unknown" ]; then
|
if [ "$BUILD_ID" != "unknown" ]; then
|
||||||
# echo "Build submitted: https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
|
echo "Build submitted: https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
|
||||||
# fi
|
fi
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||||
|
|
||||||
|
# Export to ensure it's available to subprocesses
|
||||||
|
if [ -n "$REBUILD_RELEASE" ]; then
|
||||||
|
export REBUILD_RELEASE
|
||||||
|
echo "✓ Using rebuild release number: ppa$REBUILD_RELEASE"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGES" == "all" ]]; then
|
if [[ "$PACKAGES" == "all" ]]; then
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo "Uploading dms to PPA..."
|
echo "Uploading dms to PPA..."
|
||||||
@@ -137,25 +143,25 @@ jobs:
|
|||||||
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
||||||
fi
|
fi
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms" dms questing
|
REBUILD_RELEASE="$REBUILD_RELEASE" bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms" dms questing
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo "Uploading dms-git to PPA..."
|
echo "Uploading dms-git to PPA..."
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-git" dms-git questing
|
REBUILD_RELEASE="$REBUILD_RELEASE" bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-git" dms-git questing
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo "Uploading dms-greeter to PPA..."
|
echo "Uploading dms-greeter to PPA..."
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-greeter" danklinux questing
|
REBUILD_RELEASE="$REBUILD_RELEASE" bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-greeter" danklinux questing
|
||||||
else
|
else
|
||||||
PPA_NAME="$PACKAGES"
|
PPA_NAME="$PACKAGES"
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo "Uploading $PACKAGES to PPA..."
|
echo "Uploading $PACKAGES to PPA..."
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
bash distro/scripts/ppa-upload.sh "distro/ubuntu/$PACKAGES" "$PPA_NAME" questing
|
REBUILD_RELEASE="$REBUILD_RELEASE" bash distro/scripts/ppa-upload.sh "distro/ubuntu/$PACKAGES" "$PPA_NAME" questing
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
|
|||||||
@@ -1,314 +0,0 @@
|
|||||||
package colorpicker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSurfaceState_ConcurrentPointerMotion(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 50
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s.OnPointerMotion(float64(id*10+j), float64(id*10+j))
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_ConcurrentScaleAccess(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 30
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s.SetScale(int32(id%3 + 1))
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
scale := s.Scale()
|
|
||||||
assert.GreaterOrEqual(t, scale, int32(1))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_ConcurrentLogicalSize(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
_ = s.OnLayerConfigure(1920+id, 1080+j)
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
w, h := s.LogicalSize()
|
|
||||||
_ = w
|
|
||||||
_ = h
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_ConcurrentIsDone(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 30
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/3; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s.OnPointerButton(0x110, 1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/3; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s.OnKey(1, 1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/3; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
picked, cancelled := s.IsDone()
|
|
||||||
_ = picked
|
|
||||||
_ = cancelled
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_ConcurrentIsReady(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
_ = s.IsReady()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_ConcurrentSwapBuffers(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s.SwapBuffers()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_ZeroScale(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
s.SetScale(0)
|
|
||||||
assert.Equal(t, int32(1), s.Scale())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_NegativeScale(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
s.SetScale(-5)
|
|
||||||
assert.Equal(t, int32(1), s.Scale())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_ZeroDimensionConfigure(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
|
|
||||||
err := s.OnLayerConfigure(0, 100)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.OnLayerConfigure(100, 0)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.OnLayerConfigure(-1, 100)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
w, h := s.LogicalSize()
|
|
||||||
assert.Equal(t, 0, w)
|
|
||||||
assert.Equal(t, 0, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_PickColorNilBuffer(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
color, ok := s.PickColor()
|
|
||||||
assert.False(t, ok)
|
|
||||||
assert.Equal(t, Color{}, color)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_RedrawNilBuffer(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
buf := s.Redraw()
|
|
||||||
assert.Nil(t, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_RedrawScreenOnlyNilBuffer(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
buf := s.RedrawScreenOnly()
|
|
||||||
assert.Nil(t, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_FrontRenderBufferNil(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
buf := s.FrontRenderBuffer()
|
|
||||||
assert.Nil(t, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_ScreenBufferNil(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
buf := s.ScreenBuffer()
|
|
||||||
assert.Nil(t, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSurfaceState_DestroyMultipleTimes(t *testing.T) {
|
|
||||||
s := NewSurfaceState(FormatHex, false)
|
|
||||||
s.Destroy()
|
|
||||||
s.Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClamp(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
v, lo, hi, expected int
|
|
||||||
}{
|
|
||||||
{5, 0, 10, 5},
|
|
||||||
{-5, 0, 10, 0},
|
|
||||||
{15, 0, 10, 10},
|
|
||||||
{0, 0, 10, 0},
|
|
||||||
{10, 0, 10, 10},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
result := clamp(tt.v, tt.lo, tt.hi)
|
|
||||||
assert.Equal(t, tt.expected, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClampF(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
v, lo, hi, expected float64
|
|
||||||
}{
|
|
||||||
{5.0, 0.0, 10.0, 5.0},
|
|
||||||
{-5.0, 0.0, 10.0, 0.0},
|
|
||||||
{15.0, 0.0, 10.0, 10.0},
|
|
||||||
{0.0, 0.0, 10.0, 0.0},
|
|
||||||
{10.0, 0.0, 10.0, 10.0},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
result := clampF(tt.v, tt.lo, tt.hi)
|
|
||||||
assert.InDelta(t, tt.expected, result, 0.001)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAbs(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
v, expected int
|
|
||||||
}{
|
|
||||||
{5, 5},
|
|
||||||
{-5, 5},
|
|
||||||
{0, 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
result := abs(tt.v)
|
|
||||||
assert.Equal(t, tt.expected, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBlendColors(t *testing.T) {
|
|
||||||
bg := Color{R: 0, G: 0, B: 0, A: 255}
|
|
||||||
fg := Color{R: 255, G: 255, B: 255, A: 255}
|
|
||||||
|
|
||||||
result := blendColors(bg, fg, 0.0)
|
|
||||||
assert.Equal(t, bg.R, result.R)
|
|
||||||
assert.Equal(t, bg.G, result.G)
|
|
||||||
assert.Equal(t, bg.B, result.B)
|
|
||||||
|
|
||||||
result = blendColors(bg, fg, 1.0)
|
|
||||||
assert.Equal(t, fg.R, result.R)
|
|
||||||
assert.Equal(t, fg.G, result.G)
|
|
||||||
assert.Equal(t, fg.B, result.B)
|
|
||||||
|
|
||||||
result = blendColors(bg, fg, 0.5)
|
|
||||||
assert.InDelta(t, 127, int(result.R), 1)
|
|
||||||
assert.InDelta(t, 127, int(result.G), 1)
|
|
||||||
assert.InDelta(t, 127, int(result.B), 1)
|
|
||||||
|
|
||||||
result = blendColors(bg, fg, -1.0)
|
|
||||||
assert.Equal(t, bg.R, result.R)
|
|
||||||
|
|
||||||
result = blendColors(bg, fg, 2.0)
|
|
||||||
assert.Equal(t, fg.R, result.R)
|
|
||||||
}
|
|
||||||
@@ -277,4 +277,4 @@ bind = CTRL, Print, exec, dms screenshot full
|
|||||||
bind = ALT, Print, exec, dms screenshot window
|
bind = ALT, Print, exec, dms screenshot window
|
||||||
|
|
||||||
# === System Controls ===
|
# === System Controls ===
|
||||||
bind = $mod SHIFT, P, dpms, toggle
|
bind = $mod SHIFT, P, dpms, off
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
|
|||||||
a.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
a.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.EnableDMSService(ctx, wm); err != nil {
|
if err := a.EnableDMSService(ctx); err != nil {
|
||||||
a.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
a.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -582,20 +581,12 @@ func (b *BaseDistribution) WriteEnvironmentConfig(terminal deps.Terminal) error
|
|||||||
terminalCmd = "ghostty"
|
terminalCmd = "ghostty"
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! This deviates from master branch so it doesnt need a hotfix
|
content := fmt.Sprintf(`QT_QPA_PLATFORM=wayland
|
||||||
var content string
|
|
||||||
if utils.CommandExists("plasmashell") || utils.CommandExists("plasma-session") || utils.CommandExists("plasma_session") {
|
|
||||||
content = fmt.Sprintf(`ELECTRON_OZONE_PLATFORM_HINT=auto
|
|
||||||
TERMINAL=%s
|
|
||||||
`, terminalCmd)
|
|
||||||
} else {
|
|
||||||
content = fmt.Sprintf(`QT_QPA_PLATFORM=wayland
|
|
||||||
ELECTRON_OZONE_PLATFORM_HINT=auto
|
ELECTRON_OZONE_PLATFORM_HINT=auto
|
||||||
QT_QPA_PLATFORMTHEME=gtk3
|
QT_QPA_PLATFORMTHEME=gtk3
|
||||||
QT_QPA_PLATFORMTHEME_QT6=gtk3
|
QT_QPA_PLATFORMTHEME_QT6=gtk3
|
||||||
TERMINAL=%s
|
TERMINAL=%s
|
||||||
`, terminalCmd)
|
`, terminalCmd)
|
||||||
}
|
|
||||||
|
|
||||||
envFile := filepath.Join(envDir, "90-dms.conf")
|
envFile := filepath.Join(envDir, "90-dms.conf")
|
||||||
if err := os.WriteFile(envFile, []byte(content), 0644); err != nil {
|
if err := os.WriteFile(envFile, []byte(content), 0644); err != nil {
|
||||||
@@ -606,18 +597,12 @@ TERMINAL=%s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BaseDistribution) EnableDMSService(ctx context.Context, wm deps.WindowManager) error {
|
func (b *BaseDistribution) EnableDMSService(ctx context.Context) error {
|
||||||
switch wm {
|
cmd := exec.CommandContext(ctx, "systemctl", "--user", "enable", "--now", "dms")
|
||||||
case deps.WindowManagerNiri:
|
if err := cmd.Run(); err != nil {
|
||||||
if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "niri.service", "dms").Run(); err != nil {
|
return fmt.Errorf("failed to enable dms service: %w", err)
|
||||||
b.log("Warning: failed to add dms as a want for niri.service")
|
|
||||||
}
|
|
||||||
case deps.WindowManagerHyprland:
|
|
||||||
if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "hyprland-session.target", "dms").Run(); err != nil {
|
|
||||||
b.log("Warning: failed to add dms as a want for hyprland-session.target")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
b.log("Enabled dms systemd user service")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
|
|||||||
d.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
d.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.EnableDMSService(ctx, wm); err != nil {
|
if err := d.EnableDMSService(ctx); err != nil {
|
||||||
d.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
d.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ func (f *FedoraDistribution) InstallPackages(ctx context.Context, dependencies [
|
|||||||
f.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
f.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := f.EnableDMSService(ctx, wm); err != nil {
|
if err := f.EnableDMSService(ctx); err != nil {
|
||||||
f.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
f.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ func (g *GentooDistribution) InstallPackages(ctx context.Context, dependencies [
|
|||||||
g.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
g.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.EnableDMSService(ctx, wm); err != nil {
|
if err := g.EnableDMSService(ctx); err != nil {
|
||||||
g.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
g.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
|||||||
o.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
o.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := o.EnableDMSService(ctx, wm); err != nil {
|
if err := o.EnableDMSService(ctx); err != nil {
|
||||||
o.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
o.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ func (u *UbuntuDistribution) InstallPackages(ctx context.Context, dependencies [
|
|||||||
u.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
u.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.EnableDMSService(ctx, wm); err != nil {
|
if err := u.EnableDMSService(ctx); err != nil {
|
||||||
u.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
u.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
@@ -154,7 +153,6 @@ func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string, co
|
|||||||
Subcategory: subcategory,
|
Subcategory: subcategory,
|
||||||
Source: source,
|
Source: source,
|
||||||
HideOnOverlay: kb.HideOnOverlay,
|
HideOnOverlay: kb.HideOnOverlay,
|
||||||
CooldownMs: kb.CooldownMs,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if source == "dms" && conflicts != nil {
|
if source == "dms" && conflicts != nil {
|
||||||
@@ -312,9 +310,7 @@ func (n *NiriProvider) extractOptions(node *document.Node) map[string]any {
|
|||||||
opts["repeat"] = val.String() == "true"
|
opts["repeat"] = val.String() == "true"
|
||||||
}
|
}
|
||||||
if val, ok := node.Properties.Get("cooldown-ms"); ok {
|
if val, ok := node.Properties.Get("cooldown-ms"); ok {
|
||||||
if ms, err := strconv.Atoi(val.String()); err == nil {
|
opts["cooldown-ms"] = val.String()
|
||||||
opts["cooldown-ms"] = ms
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if val, ok := node.Properties.Get("allow-when-locked"); ok {
|
if val, ok := node.Properties.Get("allow-when-locked"); ok {
|
||||||
opts["allow-when-locked"] = val.String() == "true"
|
opts["allow-when-locked"] = val.String() == "true"
|
||||||
@@ -340,14 +336,7 @@ func (n *NiriProvider) buildBindNode(bind *overrideBind) *document.Node {
|
|||||||
node.AddProperty("repeat", false, "")
|
node.AddProperty("repeat", false, "")
|
||||||
}
|
}
|
||||||
if v, ok := bind.Options["cooldown-ms"]; ok {
|
if v, ok := bind.Options["cooldown-ms"]; ok {
|
||||||
switch val := v.(type) {
|
node.AddProperty("cooldown-ms", v, "")
|
||||||
case int:
|
|
||||||
node.AddProperty("cooldown-ms", val, "")
|
|
||||||
case string:
|
|
||||||
if ms, err := strconv.Atoi(val); err == nil {
|
|
||||||
node.AddProperty("cooldown-ms", ms, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if v, ok := bind.Options["allow-when-locked"]; ok && v == true {
|
if v, ok := bind.Options["allow-when-locked"]; ok && v == true {
|
||||||
node.AddProperty("allow-when-locked", true, "")
|
node.AddProperty("allow-when-locked", true, "")
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sblinch/kdl-go"
|
"github.com/sblinch/kdl-go"
|
||||||
@@ -18,7 +17,6 @@ type NiriKeyBinding struct {
|
|||||||
Args []string
|
Args []string
|
||||||
Description string
|
Description string
|
||||||
HideOnOverlay bool
|
HideOnOverlay bool
|
||||||
CooldownMs int
|
|
||||||
Source string
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +275,6 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
|
|||||||
|
|
||||||
var description string
|
var description string
|
||||||
var hideOnOverlay bool
|
var hideOnOverlay bool
|
||||||
var cooldownMs int
|
|
||||||
if node.Properties != nil {
|
if node.Properties != nil {
|
||||||
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
||||||
switch val.ValueString() {
|
switch val.ValueString() {
|
||||||
@@ -287,9 +284,6 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
|
|||||||
description = val.ValueString()
|
description = val.ValueString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if val, ok := node.Properties.Get("cooldown-ms"); ok {
|
|
||||||
cooldownMs, _ = strconv.Atoi(val.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &NiriKeyBinding{
|
return &NiriKeyBinding{
|
||||||
@@ -299,7 +293,6 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
|
|||||||
Args: args,
|
Args: args,
|
||||||
Description: description,
|
Description: description,
|
||||||
HideOnOverlay: hideOnOverlay,
|
HideOnOverlay: hideOnOverlay,
|
||||||
CooldownMs: cooldownMs,
|
|
||||||
Source: p.currentSource,
|
Source: p.currentSource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ type Keybind struct {
|
|||||||
Subcategory string `json:"subcat,omitempty"`
|
Subcategory string `json:"subcat,omitempty"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
HideOnOverlay bool `json:"hideOnOverlay,omitempty"`
|
HideOnOverlay bool `json:"hideOnOverlay,omitempty"`
|
||||||
CooldownMs int `json:"cooldownMs,omitempty"`
|
|
||||||
Conflict *Keybind `json:"conflict,omitempty"`
|
Conflict *Keybind `json:"conflict,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,352 +0,0 @@
|
|||||||
package dwl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStateChanged_BothNil(t *testing.T) {
|
|
||||||
assert.True(t, stateChanged(nil, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OneNil(t *testing.T) {
|
|
||||||
s := &State{TagCount: 9}
|
|
||||||
assert.True(t, stateChanged(s, nil))
|
|
||||||
assert.True(t, stateChanged(nil, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_TagCountDiffers(t *testing.T) {
|
|
||||||
a := &State{TagCount: 9, Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
|
||||||
b := &State{TagCount: 10, Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_LayoutLengthDiffers(t *testing.T) {
|
|
||||||
a := &State{TagCount: 9, Layouts: []string{"tile"}, Outputs: make(map[string]*OutputState)}
|
|
||||||
b := &State{TagCount: 9, Layouts: []string{"tile", "monocle"}, Outputs: make(map[string]*OutputState)}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_ActiveOutputDiffers(t *testing.T) {
|
|
||||||
a := &State{TagCount: 9, ActiveOutput: "eDP-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
|
||||||
b := &State{TagCount: 9, ActiveOutput: "HDMI-A-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputCountDiffers(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Outputs: map[string]*OutputState{"eDP-1": {}},
|
|
||||||
Layouts: []string{},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Outputs: map[string]*OutputState{},
|
|
||||||
Layouts: []string{},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputFieldsDiffer(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Active: 1, Layout: 0, Title: "Firefox"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Active: 0, Layout: 0, Title: "Firefox"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
|
|
||||||
b.Outputs["eDP-1"].Active = 1
|
|
||||||
b.Outputs["eDP-1"].Layout = 1
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
|
|
||||||
b.Outputs["eDP-1"].Layout = 0
|
|
||||||
b.Outputs["eDP-1"].Title = "Code"
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_TagsDiffer(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Tags: []TagState{{Tag: 1, State: 2, Clients: 2, Focused: 1}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
|
|
||||||
b.Outputs["eDP-1"].Tags[0].State = 1
|
|
||||||
b.Outputs["eDP-1"].Tags[0].Clients = 3
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_Equal(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
ActiveOutput: "eDP-1",
|
|
||||||
Layouts: []string{"tile", "monocle"},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {
|
|
||||||
Name: "eDP-1",
|
|
||||||
Active: 1,
|
|
||||||
Layout: 0,
|
|
||||||
LayoutSymbol: "[]=",
|
|
||||||
Title: "Firefox",
|
|
||||||
AppID: "firefox",
|
|
||||||
KbLayout: "us",
|
|
||||||
Keymode: "",
|
|
||||||
Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
ActiveOutput: "eDP-1",
|
|
||||||
Layouts: []string{"tile", "monocle"},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {
|
|
||||||
Name: "eDP-1",
|
|
||||||
Active: 1,
|
|
||||||
Layout: 0,
|
|
||||||
LayoutSymbol: "[]=",
|
|
||||||
Title: "Firefox",
|
|
||||||
AppID: "firefox",
|
|
||||||
KbLayout: "us",
|
|
||||||
Keymode: "",
|
|
||||||
Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.False(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentGetState(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
state: &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{"tile"},
|
|
||||||
Outputs: map[string]*OutputState{"eDP-1": {Name: "eDP-1"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 50
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s := m.GetState()
|
|
||||||
_ = s.TagCount
|
|
||||||
_ = s.Outputs
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.stateMutex.Lock()
|
|
||||||
m.state = &State{
|
|
||||||
TagCount: uint32(j % 10),
|
|
||||||
Layouts: []string{"tile", "monocle"},
|
|
||||||
Outputs: map[string]*OutputState{"eDP-1": {Active: uint32(j % 2)}},
|
|
||||||
}
|
|
||||||
m.stateMutex.Unlock()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
subID := string(rune('a' + id))
|
|
||||||
ch := m.Subscribe(subID)
|
|
||||||
assert.NotNil(t, ch)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
m.Unsubscribe(subID)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 30
|
|
||||||
const iterations = 50
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
key := uint32(id)
|
|
||||||
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
state := &outputState{
|
|
||||||
id: key,
|
|
||||||
name: "test-output",
|
|
||||||
active: uint32(j % 2),
|
|
||||||
tags: []TagState{{Tag: uint32(j), State: 1}},
|
|
||||||
}
|
|
||||||
m.outputs.Store(key, state)
|
|
||||||
|
|
||||||
if loaded, ok := m.outputs.Load(key); ok {
|
|
||||||
assert.Equal(t, key, loaded.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.outputs.Range(func(k uint32, v *outputState) bool {
|
|
||||||
_ = v.name
|
|
||||||
_ = v.active
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
m.outputs.Delete(key)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
m.notifySubscribers()
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(t, m.dirty, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_PostQueueFull(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
cmdq: make(chan cmd, 2),
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
|
|
||||||
assert.Len(t, m.cmdq, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_GetStateNilState(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
s := m.GetState()
|
|
||||||
assert.NotNil(t, s.Outputs)
|
|
||||||
assert.NotNil(t, s.Layouts)
|
|
||||||
assert.Equal(t, uint32(0), s.TagCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTagState_Fields(t *testing.T) {
|
|
||||||
tag := TagState{
|
|
||||||
Tag: 1,
|
|
||||||
State: 2,
|
|
||||||
Clients: 3,
|
|
||||||
Focused: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, uint32(1), tag.Tag)
|
|
||||||
assert.Equal(t, uint32(2), tag.State)
|
|
||||||
assert.Equal(t, uint32(3), tag.Clients)
|
|
||||||
assert.Equal(t, uint32(1), tag.Focused)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputState_Fields(t *testing.T) {
|
|
||||||
out := OutputState{
|
|
||||||
Name: "eDP-1",
|
|
||||||
Active: 1,
|
|
||||||
Tags: []TagState{{Tag: 1}},
|
|
||||||
Layout: 0,
|
|
||||||
LayoutSymbol: "[]=",
|
|
||||||
Title: "Firefox",
|
|
||||||
AppID: "firefox",
|
|
||||||
KbLayout: "us",
|
|
||||||
Keymode: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "eDP-1", out.Name)
|
|
||||||
assert.Equal(t, uint32(1), out.Active)
|
|
||||||
assert.Len(t, out.Tags, 1)
|
|
||||||
assert.Equal(t, "[]=", out.LayoutSymbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_NewOutputAppears(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Name: "eDP-1"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Name: "eDP-1"},
|
|
||||||
"HDMI-A-1": {Name: "HDMI-A-1"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_TagsLengthDiffers(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Tags: []TagState{{Tag: 1}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Tags: []TagState{{Tag: 1}, {Tag: 2}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
@@ -880,24 +880,29 @@ func (b *NetworkManagerBackend) ImportVPN(filePath string, name string) (*VPNImp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string) (*VPNImportResult, error) {
|
func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string) (*VPNImportResult, error) {
|
||||||
vpnTypes := []string{"openvpn", "wireguard", "vpnc", "pptp", "l2tp", "openconnect", "strongswan"}
|
args := []string{"connection", "import", "type", "openvpn", "file", filePath}
|
||||||
|
cmd := exec.Command("nmcli", args...)
|
||||||
var output []byte
|
output, err := cmd.CombinedOutput()
|
||||||
var err error
|
|
||||||
for _, vpnType := range vpnTypes {
|
|
||||||
args := []string{"connection", "import", "type", vpnType, "file", filePath}
|
|
||||||
cmd := exec.Command("nmcli", args...)
|
|
||||||
output, err = cmd.CombinedOutput()
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &VPNImportResult{
|
outputStr := string(output)
|
||||||
Success: false,
|
if strings.Contains(outputStr, "vpnc") || strings.Contains(outputStr, "unknown connection type") {
|
||||||
Error: fmt.Sprintf("import failed: %s", strings.TrimSpace(string(output))),
|
for _, vpnType := range []string{"vpnc", "pptp", "l2tp", "openconnect", "strongswan", "wireguard"} {
|
||||||
}, nil
|
args = []string{"connection", "import", "type", vpnType, "file", filePath}
|
||||||
|
cmd = exec.Command("nmcli", args...)
|
||||||
|
output, err = cmd.CombinedOutput()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return &VPNImportResult{
|
||||||
|
Success: false,
|
||||||
|
Error: fmt.Sprintf("import failed: %s", strings.TrimSpace(string(output))),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputStr := string(output)
|
outputStr := string(output)
|
||||||
|
|||||||
@@ -103,16 +103,18 @@ func (m *Manager) waylandActor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) anyOutputReady() bool {
|
func (m *Manager) allOutputsReady() bool {
|
||||||
anyReady := false
|
hasOutputs := false
|
||||||
|
allReady := true
|
||||||
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
||||||
if out.rampSize > 0 && !out.failed {
|
hasOutputs = true
|
||||||
anyReady = true
|
if out.rampSize == 0 || out.failed {
|
||||||
return false // stop iteration
|
allReady = false
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return anyReady
|
return hasOutputs && allReady
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) setupDBusMonitor() error {
|
func (m *Manager) setupDBusMonitor() error {
|
||||||
@@ -266,37 +268,31 @@ func (m *Manager) setupOutputControls(outputs []*wlclient.Output, manager *wlr_g
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) setupControlHandlers(state *outputState, control *wlr_gamma_control.ZwlrGammaControlV1) {
|
func (m *Manager) setupControlHandlers(state *outputState, control *wlr_gamma_control.ZwlrGammaControlV1) {
|
||||||
outputID := state.id
|
|
||||||
|
|
||||||
control.SetGammaSizeHandler(func(e wlr_gamma_control.ZwlrGammaControlV1GammaSizeEvent) {
|
control.SetGammaSizeHandler(func(e wlr_gamma_control.ZwlrGammaControlV1GammaSizeEvent) {
|
||||||
size := e.Size
|
if out, ok := m.outputs.Load(state.id); ok {
|
||||||
|
out.rampSize = e.Size
|
||||||
|
out.failed = false
|
||||||
|
out.retryCount = 0
|
||||||
|
}
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
if out, ok := m.outputs.Load(outputID); ok {
|
m.applyCurrentTemp()
|
||||||
out.rampSize = size
|
|
||||||
out.failed = false
|
|
||||||
out.retryCount = 0
|
|
||||||
}
|
|
||||||
m.lastAppliedTemp = 0
|
|
||||||
m.applyCurrentTemp("gamma_size")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
control.SetFailedHandler(func(_ wlr_gamma_control.ZwlrGammaControlV1FailedEvent) {
|
control.SetFailedHandler(func(_ wlr_gamma_control.ZwlrGammaControlV1FailedEvent) {
|
||||||
m.post(func() {
|
out, ok := m.outputs.Load(state.id)
|
||||||
out, ok := m.outputs.Load(outputID)
|
if !ok {
|
||||||
if !ok {
|
return
|
||||||
return
|
}
|
||||||
}
|
out.failed = true
|
||||||
out.failed = true
|
out.rampSize = 0
|
||||||
out.rampSize = 0
|
out.retryCount++
|
||||||
out.retryCount++
|
out.lastFailTime = time.Now()
|
||||||
out.lastFailTime = time.Now()
|
|
||||||
|
|
||||||
backoff := time.Duration(300<<uint(min(out.retryCount-1, 4))) * time.Millisecond
|
backoff := time.Duration(300<<uint(min(out.retryCount-1, 4))) * time.Millisecond
|
||||||
time.AfterFunc(backoff, func() {
|
time.AfterFunc(backoff, func() {
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
m.recreateOutputControl(out)
|
m.recreateOutputControl(out)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -527,9 +523,8 @@ func (m *Manager) getNextDeadline(now time.Time) time.Time {
|
|||||||
return m.tomorrow(now)
|
return m.tomorrow(now)
|
||||||
case StateNormal:
|
case StateNormal:
|
||||||
return m.getDeadlineNormal(now, sched)
|
return m.getDeadlineNormal(now, sched)
|
||||||
default:
|
|
||||||
return m.tomorrow(now)
|
|
||||||
}
|
}
|
||||||
|
return m.tomorrow(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) getDeadlineNormal(now time.Time, sched sunSchedule) time.Time {
|
func (m *Manager) getDeadlineNormal(now time.Time, sched sunSchedule) time.Time {
|
||||||
@@ -588,7 +583,7 @@ func (m *Manager) schedulerLoop() {
|
|||||||
m.configMutex.RUnlock()
|
m.configMutex.RUnlock()
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
m.post(func() { m.applyCurrentTemp("startup") })
|
m.applyCurrentTemp()
|
||||||
}
|
}
|
||||||
|
|
||||||
var timer *time.Timer
|
var timer *time.Timer
|
||||||
@@ -630,34 +625,21 @@ func (m *Manager) schedulerLoop() {
|
|||||||
enabled := m.config.Enabled
|
enabled := m.config.Enabled
|
||||||
m.configMutex.RUnlock()
|
m.configMutex.RUnlock()
|
||||||
if enabled {
|
if enabled {
|
||||||
m.post(func() { m.applyCurrentTemp("updateTrigger") })
|
m.applyCurrentTemp()
|
||||||
}
|
}
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
m.configMutex.RLock()
|
m.configMutex.RLock()
|
||||||
enabled := m.config.Enabled
|
enabled := m.config.Enabled
|
||||||
m.configMutex.RUnlock()
|
m.configMutex.RUnlock()
|
||||||
if enabled {
|
if enabled {
|
||||||
m.post(func() { m.applyCurrentTemp("timer") })
|
m.applyCurrentTemp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) applyCurrentTemp(_ string) {
|
func (m *Manager) applyCurrentTemp() {
|
||||||
if !m.controlsInitialized || !m.anyOutputReady() {
|
if !m.controlsInitialized || !m.allOutputsReady() {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure schedule is up-to-date (handles display wake after overnight sleep)
|
|
||||||
m.recalcSchedule(time.Now())
|
|
||||||
|
|
||||||
m.configMutex.RLock()
|
|
||||||
low, high := m.config.LowTemp, m.config.HighTemp
|
|
||||||
m.configMutex.RUnlock()
|
|
||||||
|
|
||||||
if low == high {
|
|
||||||
m.applyGamma(low)
|
|
||||||
m.updateStateFromSchedule()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,10 +665,6 @@ func (m *Manager) applyGamma(temp int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.lastAppliedTemp == temp && m.lastAppliedGamma == gamma {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var outs []*outputState
|
var outs []*outputState
|
||||||
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
||||||
outs = append(outs, out)
|
outs = append(outs, out)
|
||||||
@@ -722,7 +700,6 @@ func (m *Manager) applyGamma(temp int) {
|
|||||||
|
|
||||||
for _, j := range jobs {
|
for _, j := range jobs {
|
||||||
if err := m.setGammaBytes(j.out, j.data); err != nil {
|
if err := m.setGammaBytes(j.out, j.data); err != nil {
|
||||||
log.Warnf("gamma: failed to set output %d: %v", j.out.id, err)
|
|
||||||
j.out.failed = true
|
j.out.failed = true
|
||||||
j.out.rampSize = 0
|
j.out.rampSize = 0
|
||||||
outID := j.out.id
|
outID := j.out.id
|
||||||
@@ -735,9 +712,6 @@ func (m *Manager) applyGamma(temp int) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m.lastAppliedTemp = temp
|
|
||||||
m.lastAppliedGamma = gamma
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) setGammaBytes(out *outputState, data []byte) error {
|
func (m *Manager) setGammaBytes(out *outputState, data []byte) error {
|
||||||
@@ -912,10 +886,6 @@ func (m *Manager) SetConfig(config Config) error {
|
|||||||
|
|
||||||
func (m *Manager) SetTemperature(low, high int) error {
|
func (m *Manager) SetTemperature(low, high int) error {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
if m.config.LowTemp == low && m.config.HighTemp == high {
|
|
||||||
m.configMutex.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.config.LowTemp = low
|
m.config.LowTemp = low
|
||||||
m.config.HighTemp = high
|
m.config.HighTemp = high
|
||||||
err := m.config.Validate()
|
err := m.config.Validate()
|
||||||
@@ -929,11 +899,6 @@ func (m *Manager) SetTemperature(low, high int) error {
|
|||||||
|
|
||||||
func (m *Manager) SetLocation(lat, lon float64) error {
|
func (m *Manager) SetLocation(lat, lon float64) error {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
if m.config.Latitude != nil && m.config.Longitude != nil &&
|
|
||||||
*m.config.Latitude == lat && *m.config.Longitude == lon && !m.config.UseIPLocation {
|
|
||||||
m.configMutex.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.config.Latitude = &lat
|
m.config.Latitude = &lat
|
||||||
m.config.Longitude = &lon
|
m.config.Longitude = &lon
|
||||||
m.config.UseIPLocation = false
|
m.config.UseIPLocation = false
|
||||||
@@ -948,10 +913,6 @@ func (m *Manager) SetLocation(lat, lon float64) error {
|
|||||||
|
|
||||||
func (m *Manager) SetUseIPLocation(use bool) {
|
func (m *Manager) SetUseIPLocation(use bool) {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
if m.config.UseIPLocation == use {
|
|
||||||
m.configMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.config.UseIPLocation = use
|
m.config.UseIPLocation = use
|
||||||
if use {
|
if use {
|
||||||
m.config.Latitude = nil
|
m.config.Latitude = nil
|
||||||
@@ -970,12 +931,6 @@ func (m *Manager) SetUseIPLocation(use bool) {
|
|||||||
|
|
||||||
func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
|
func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
if m.config.ManualSunrise != nil && m.config.ManualSunset != nil &&
|
|
||||||
m.config.ManualSunrise.Hour() == sunrise.Hour() && m.config.ManualSunrise.Minute() == sunrise.Minute() &&
|
|
||||||
m.config.ManualSunset.Hour() == sunset.Hour() && m.config.ManualSunset.Minute() == sunset.Minute() {
|
|
||||||
m.configMutex.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.config.ManualSunrise = &sunrise
|
m.config.ManualSunrise = &sunrise
|
||||||
m.config.ManualSunset = &sunset
|
m.config.ManualSunset = &sunset
|
||||||
err := m.config.Validate()
|
err := m.config.Validate()
|
||||||
@@ -989,10 +944,6 @@ func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
|
|||||||
|
|
||||||
func (m *Manager) ClearManualTimes() {
|
func (m *Manager) ClearManualTimes() {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
if m.config.ManualSunrise == nil && m.config.ManualSunset == nil {
|
|
||||||
m.configMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.config.ManualSunrise = nil
|
m.config.ManualSunrise = nil
|
||||||
m.config.ManualSunset = nil
|
m.config.ManualSunset = nil
|
||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
@@ -1001,10 +952,6 @@ func (m *Manager) ClearManualTimes() {
|
|||||||
|
|
||||||
func (m *Manager) SetGamma(gamma float64) error {
|
func (m *Manager) SetGamma(gamma float64) error {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
if m.config.Gamma == gamma {
|
|
||||||
m.configMutex.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.config.Gamma = gamma
|
m.config.Gamma = gamma
|
||||||
err := m.config.Validate()
|
err := m.config.Validate()
|
||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
@@ -1018,10 +965,6 @@ func (m *Manager) SetGamma(gamma float64) error {
|
|||||||
func (m *Manager) SetEnabled(enabled bool) {
|
func (m *Manager) SetEnabled(enabled bool) {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
wasEnabled := m.config.Enabled
|
wasEnabled := m.config.Enabled
|
||||||
if wasEnabled == enabled {
|
|
||||||
m.configMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.config.Enabled = enabled
|
m.config.Enabled = enabled
|
||||||
highTemp := m.config.HighTemp
|
highTemp := m.config.HighTemp
|
||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
@@ -1031,7 +974,7 @@ func (m *Manager) SetEnabled(enabled bool) {
|
|||||||
m.post(func() {
|
m.post(func() {
|
||||||
gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
|
gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
|
||||||
if err := m.setupOutputControls(m.availableOutputs, gammaMgr); err != nil {
|
if err := m.setupOutputControls(m.availableOutputs, gammaMgr); err != nil {
|
||||||
log.Errorf("gamma: failed to create controls: %v", err)
|
log.Errorf("Failed to create gamma controls: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.controlsInitialized = true
|
m.controlsInitialized = true
|
||||||
|
|||||||
@@ -1,386 +0,0 @@
|
|||||||
package wayland
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestManager_ActorSerializesOutputStateAccess(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
cmdq: make(chan cmd, 128),
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
m.wg.Add(1)
|
|
||||||
go m.waylandActor()
|
|
||||||
|
|
||||||
state := &outputState{
|
|
||||||
id: 1,
|
|
||||||
registryName: 100,
|
|
||||||
rampSize: 256,
|
|
||||||
}
|
|
||||||
m.outputs.Store(state.id, state)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 50
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.post(func() {
|
|
||||||
if out, ok := m.outputs.Load(state.id); ok {
|
|
||||||
out.rampSize = uint32(j)
|
|
||||||
out.failed = j%2 == 0
|
|
||||||
out.retryCount = j
|
|
||||||
out.lastFailTime = time.Now()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
m.post(func() { close(done) })
|
|
||||||
<-done
|
|
||||||
|
|
||||||
close(m.stopChan)
|
|
||||||
m.wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
updateTrigger: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
subID := string(rune('a' + id))
|
|
||||||
ch := m.Subscribe(subID)
|
|
||||||
assert.NotNil(t, ch)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
m.Unsubscribe(subID)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentGetState(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
state: &State{
|
|
||||||
CurrentTemp: 5000,
|
|
||||||
IsDay: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 50
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s := m.GetState()
|
|
||||||
assert.GreaterOrEqual(t, s.CurrentTemp, 0)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.stateMutex.Lock()
|
|
||||||
m.state = &State{
|
|
||||||
CurrentTemp: 4000 + i*100,
|
|
||||||
IsDay: j%2 == 0,
|
|
||||||
}
|
|
||||||
m.stateMutex.Unlock()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentConfigAccess(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
config: DefaultConfig(),
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 30
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.configMutex.RLock()
|
|
||||||
_ = m.config.LowTemp
|
|
||||||
_ = m.config.HighTemp
|
|
||||||
_ = m.config.Enabled
|
|
||||||
m.configMutex.RUnlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.configMutex.Lock()
|
|
||||||
m.config.LowTemp = 3000 + j
|
|
||||||
m.config.HighTemp = 7000 - j
|
|
||||||
m.config.Enabled = j%2 == 0
|
|
||||||
m.configMutex.Unlock()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 30
|
|
||||||
const iterations = 50
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
key := uint32(id)
|
|
||||||
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
state := &outputState{
|
|
||||||
id: key,
|
|
||||||
rampSize: uint32(j),
|
|
||||||
failed: j%2 == 0,
|
|
||||||
}
|
|
||||||
m.outputs.Store(key, state)
|
|
||||||
|
|
||||||
if loaded, ok := m.outputs.Load(key); ok {
|
|
||||||
assert.Equal(t, key, loaded.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.outputs.Range(func(k uint32, v *outputState) bool {
|
|
||||||
_ = v.rampSize
|
|
||||||
_ = v.failed
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
m.outputs.Delete(key)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_LocationCacheConcurrentAccess(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.locationMutex.RLock()
|
|
||||||
_ = m.cachedIPLat
|
|
||||||
_ = m.cachedIPLon
|
|
||||||
m.locationMutex.RUnlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
lat := float64(40 + i)
|
|
||||||
lon := float64(-74 + j)
|
|
||||||
m.locationMutex.Lock()
|
|
||||||
m.cachedIPLat = &lat
|
|
||||||
m.cachedIPLon = &lon
|
|
||||||
m.locationMutex.Unlock()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ScheduleConcurrentAccess(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
m := &Manager{
|
|
||||||
schedule: sunSchedule{
|
|
||||||
times: SunTimes{
|
|
||||||
Dawn: now,
|
|
||||||
Sunrise: now.Add(time.Hour),
|
|
||||||
Sunset: now.Add(12 * time.Hour),
|
|
||||||
Night: now.Add(13 * time.Hour),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.scheduleMutex.RLock()
|
|
||||||
_ = m.schedule.times.Dawn
|
|
||||||
_ = m.schedule.times.Sunrise
|
|
||||||
_ = m.schedule.times.Sunset
|
|
||||||
_ = m.schedule.condition
|
|
||||||
m.scheduleMutex.RUnlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.scheduleMutex.Lock()
|
|
||||||
m.schedule.times.Dawn = time.Now()
|
|
||||||
m.schedule.times.Sunrise = time.Now().Add(time.Hour)
|
|
||||||
m.schedule.condition = SunNormal
|
|
||||||
m.scheduleMutex.Unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInterpolate_EdgeCases(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
now time.Time
|
|
||||||
start time.Time
|
|
||||||
stop time.Time
|
|
||||||
expected float64
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "same start and stop",
|
|
||||||
now: now,
|
|
||||||
start: now,
|
|
||||||
stop: now,
|
|
||||||
expected: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "now before start",
|
|
||||||
now: now,
|
|
||||||
start: now.Add(time.Hour),
|
|
||||||
stop: now.Add(2 * time.Hour),
|
|
||||||
expected: 0.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "now after stop",
|
|
||||||
now: now.Add(3 * time.Hour),
|
|
||||||
start: now,
|
|
||||||
stop: now.Add(time.Hour),
|
|
||||||
expected: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "now at midpoint",
|
|
||||||
now: now.Add(30 * time.Minute),
|
|
||||||
start: now,
|
|
||||||
stop: now.Add(time.Hour),
|
|
||||||
expected: 0.5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "now equals start",
|
|
||||||
now: now,
|
|
||||||
start: now,
|
|
||||||
stop: now.Add(time.Hour),
|
|
||||||
expected: 0.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "now equals stop",
|
|
||||||
now: now.Add(time.Hour),
|
|
||||||
start: now,
|
|
||||||
stop: now.Add(time.Hour),
|
|
||||||
expected: 1.0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := interpolate(tt.now, tt.start, tt.stop)
|
|
||||||
assert.InDelta(t, tt.expected, result, 0.01)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateGammaRamp_ZeroSize(t *testing.T) {
|
|
||||||
ramp := GenerateGammaRamp(0, 5000, 1.0)
|
|
||||||
assert.Empty(t, ramp.Red)
|
|
||||||
assert.Empty(t, ramp.Green)
|
|
||||||
assert.Empty(t, ramp.Blue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateGammaRamp_ValidSizes(t *testing.T) {
|
|
||||||
sizes := []uint32{1, 256, 1024}
|
|
||||||
temps := []int{1000, 4000, 6500, 10000}
|
|
||||||
gammas := []float64{0.5, 1.0, 2.0}
|
|
||||||
|
|
||||||
for _, size := range sizes {
|
|
||||||
for _, temp := range temps {
|
|
||||||
for _, gamma := range gammas {
|
|
||||||
ramp := GenerateGammaRamp(size, temp, gamma)
|
|
||||||
assert.Len(t, ramp.Red, int(size))
|
|
||||||
assert.Len(t, ramp.Green, int(size))
|
|
||||||
assert.Len(t, ramp.Blue, int(size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifySubscribers_NonBlocking(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
m.notifySubscribers()
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(t, m.dirty, 1)
|
|
||||||
}
|
|
||||||
@@ -96,9 +96,6 @@ type Manager struct {
|
|||||||
|
|
||||||
dbusConn *dbus.Conn
|
dbusConn *dbus.Conn
|
||||||
dbusSignal chan *dbus.Signal
|
dbusSignal chan *dbus.Signal
|
||||||
|
|
||||||
lastAppliedTemp int
|
|
||||||
lastAppliedGamma float64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type outputState struct {
|
type outputState struct {
|
||||||
|
|||||||
@@ -1,400 +0,0 @@
|
|||||||
package wlroutput
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStateChanged_BothNil(t *testing.T) {
|
|
||||||
assert.True(t, stateChanged(nil, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OneNil(t *testing.T) {
|
|
||||||
s := &State{Serial: 1}
|
|
||||||
assert.True(t, stateChanged(s, nil))
|
|
||||||
assert.True(t, stateChanged(nil, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_SerialDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{}}
|
|
||||||
b := &State{Serial: 2, Outputs: []Output{}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputCountDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputNameDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: true}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "HDMI-A-1", Enabled: true}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputEnabledDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: true}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: false}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputPositionDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", X: 0, Y: 0}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", X: 1920, Y: 0}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputTransformDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Transform: 0}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Transform: 1}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputScaleDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Scale: 1.0}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Scale: 2.0}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputAdaptiveSyncDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", AdaptiveSync: 0}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", AdaptiveSync: 1}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_CurrentModeNilVsNonNil(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: &OutputMode{Width: 1920}}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_CurrentModeDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{
|
|
||||||
Name: "eDP-1",
|
|
||||||
CurrentMode: &OutputMode{Width: 1920, Height: 1080, Refresh: 60000},
|
|
||||||
}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{
|
|
||||||
Name: "eDP-1",
|
|
||||||
CurrentMode: &OutputMode{Width: 2560, Height: 1440, Refresh: 60000},
|
|
||||||
}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
|
|
||||||
b.Outputs[0].CurrentMode.Width = 1920
|
|
||||||
b.Outputs[0].CurrentMode.Height = 1080
|
|
||||||
b.Outputs[0].CurrentMode.Refresh = 144000
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_ModesLengthDiffers(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Modes: []OutputMode{{Width: 1920}}}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Modes: []OutputMode{{Width: 1920}, {Width: 1280}}}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_Equal(t *testing.T) {
|
|
||||||
mode := OutputMode{Width: 1920, Height: 1080, Refresh: 60000, Preferred: true}
|
|
||||||
a := &State{
|
|
||||||
Serial: 5,
|
|
||||||
Outputs: []Output{{
|
|
||||||
Name: "eDP-1",
|
|
||||||
Description: "Built-in display",
|
|
||||||
Make: "BOE",
|
|
||||||
Model: "0x0ABC",
|
|
||||||
SerialNumber: "12345",
|
|
||||||
PhysicalWidth: 309,
|
|
||||||
PhysicalHeight: 174,
|
|
||||||
Enabled: true,
|
|
||||||
X: 0,
|
|
||||||
Y: 0,
|
|
||||||
Transform: 0,
|
|
||||||
Scale: 1.0,
|
|
||||||
CurrentMode: &mode,
|
|
||||||
Modes: []OutputMode{mode},
|
|
||||||
AdaptiveSync: 0,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
Serial: 5,
|
|
||||||
Outputs: []Output{{
|
|
||||||
Name: "eDP-1",
|
|
||||||
Description: "Built-in display",
|
|
||||||
Make: "BOE",
|
|
||||||
Model: "0x0ABC",
|
|
||||||
SerialNumber: "12345",
|
|
||||||
PhysicalWidth: 309,
|
|
||||||
PhysicalHeight: 174,
|
|
||||||
Enabled: true,
|
|
||||||
X: 0,
|
|
||||||
Y: 0,
|
|
||||||
Transform: 0,
|
|
||||||
Scale: 1.0,
|
|
||||||
CurrentMode: &mode,
|
|
||||||
Modes: []OutputMode{mode},
|
|
||||||
AdaptiveSync: 0,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
assert.False(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentGetState(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
state: &State{
|
|
||||||
Serial: 1,
|
|
||||||
Outputs: []Output{{Name: "eDP-1", Enabled: true}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 50
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s := m.GetState()
|
|
||||||
_ = s.Serial
|
|
||||||
_ = s.Outputs
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.stateMutex.Lock()
|
|
||||||
m.state = &State{
|
|
||||||
Serial: uint32(j),
|
|
||||||
Outputs: []Output{{Name: "eDP-1", Scale: float64(j % 3)}},
|
|
||||||
}
|
|
||||||
m.stateMutex.Unlock()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
subID := string(rune('a' + id))
|
|
||||||
ch := m.Subscribe(subID)
|
|
||||||
assert.NotNil(t, ch)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
m.Unsubscribe(subID)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_SyncmapHeadsConcurrentAccess(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 30
|
|
||||||
const iterations = 50
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
key := uint32(id)
|
|
||||||
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
state := &headState{
|
|
||||||
id: key,
|
|
||||||
name: "test-head",
|
|
||||||
enabled: j%2 == 0,
|
|
||||||
scale: float64(j % 3),
|
|
||||||
modeIDs: []uint32{uint32(j)},
|
|
||||||
}
|
|
||||||
m.heads.Store(key, state)
|
|
||||||
|
|
||||||
if loaded, ok := m.heads.Load(key); ok {
|
|
||||||
assert.Equal(t, key, loaded.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.heads.Range(func(k uint32, v *headState) bool {
|
|
||||||
_ = v.name
|
|
||||||
_ = v.enabled
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
m.heads.Delete(key)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_SyncmapModesConcurrentAccess(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 30
|
|
||||||
const iterations = 50
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
key := uint32(id)
|
|
||||||
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
state := &modeState{
|
|
||||||
id: key,
|
|
||||||
width: int32(1920 + j),
|
|
||||||
height: int32(1080 + j),
|
|
||||||
refresh: 60000,
|
|
||||||
preferred: j == 0,
|
|
||||||
}
|
|
||||||
m.modes.Store(key, state)
|
|
||||||
|
|
||||||
if loaded, ok := m.modes.Load(key); ok {
|
|
||||||
assert.Equal(t, key, loaded.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.modes.Range(func(k uint32, v *modeState) bool {
|
|
||||||
_ = v.width
|
|
||||||
_ = v.height
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
m.modes.Delete(key)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
m.notifySubscribers()
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(t, m.dirty, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_PostQueueFull(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
cmdq: make(chan cmd, 2),
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
|
|
||||||
assert.Len(t, m.cmdq, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_GetStateNilState(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
s := m.GetState()
|
|
||||||
assert.NotNil(t, s.Outputs)
|
|
||||||
assert.Equal(t, uint32(0), s.Serial)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_FatalErrorChannel(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
fatalError: make(chan error, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
ch := m.FatalError()
|
|
||||||
assert.NotNil(t, ch)
|
|
||||||
|
|
||||||
m.fatalError <- assert.AnError
|
|
||||||
err := <-ch
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputMode_Fields(t *testing.T) {
|
|
||||||
mode := OutputMode{
|
|
||||||
Width: 1920,
|
|
||||||
Height: 1080,
|
|
||||||
Refresh: 60000,
|
|
||||||
Preferred: true,
|
|
||||||
ID: 42,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, int32(1920), mode.Width)
|
|
||||||
assert.Equal(t, int32(1080), mode.Height)
|
|
||||||
assert.Equal(t, int32(60000), mode.Refresh)
|
|
||||||
assert.True(t, mode.Preferred)
|
|
||||||
assert.Equal(t, uint32(42), mode.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutput_Fields(t *testing.T) {
|
|
||||||
out := Output{
|
|
||||||
Name: "eDP-1",
|
|
||||||
Description: "Built-in display",
|
|
||||||
Make: "BOE",
|
|
||||||
Model: "0x0ABC",
|
|
||||||
SerialNumber: "12345",
|
|
||||||
PhysicalWidth: 309,
|
|
||||||
PhysicalHeight: 174,
|
|
||||||
Enabled: true,
|
|
||||||
X: 0,
|
|
||||||
Y: 0,
|
|
||||||
Transform: 0,
|
|
||||||
Scale: 1.5,
|
|
||||||
AdaptiveSync: 1,
|
|
||||||
ID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "eDP-1", out.Name)
|
|
||||||
assert.Equal(t, "Built-in display", out.Description)
|
|
||||||
assert.True(t, out.Enabled)
|
|
||||||
assert.Equal(t, float64(1.5), out.Scale)
|
|
||||||
assert.Equal(t, uint32(1), out.AdaptiveSync)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeadState_ModeIDsSlice(t *testing.T) {
|
|
||||||
head := &headState{
|
|
||||||
id: 1,
|
|
||||||
modeIDs: make([]uint32, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
head.modeIDs = append(head.modeIDs, 1, 2, 3)
|
|
||||||
assert.Len(t, head.modeIDs, 3)
|
|
||||||
assert.Equal(t, uint32(1), head.modeIDs[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_BothCurrentModeNil(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
|
|
||||||
assert.False(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_IndexOutOfBounds(t *testing.T) {
|
|
||||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}}}
|
|
||||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}, {Name: "HDMI-A-1"}}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
dms-git (0.6.2+git2419.993f14a3) nightly; urgency=medium
|
dms-git (1.0.0+git2419.993f14a3) nightly; urgency=medium
|
||||||
|
|
||||||
|
* Major stable release v1.0.0
|
||||||
* widgets: make dank icon picker a popup
|
* widgets: make dank icon picker a popup
|
||||||
* Previous updates included in build
|
* Previous updates included in build
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,19 @@
|
|||||||
<service name="download_url">
|
<service name="download_url">
|
||||||
<param name="protocol">https</param>
|
<param name="protocol">https</param>
|
||||||
<param name="host">github.com</param>
|
<param name="host">github.com</param>
|
||||||
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v0.6.2.tar.gz</param>
|
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.0.tar.gz</param>
|
||||||
<param name="filename">dms-source.tar.gz</param>
|
<param name="filename">dms-source.tar.gz</param>
|
||||||
</service>
|
</service>
|
||||||
<!-- Download amd64 binary -->
|
<!-- Download amd64 binary -->
|
||||||
<service name="download_url">
|
<service name="download_url">
|
||||||
<param name="protocol">https</param>
|
<param name="protocol">https</param>
|
||||||
<param name="host">github.com</param>
|
<param name="host">github.com</param>
|
||||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v0.6.2/dms-distropkg-amd64.gz</param>
|
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.0/dms-distropkg-amd64.gz</param>
|
||||||
</service>
|
</service>
|
||||||
<!-- Download arm64 binary -->
|
<!-- Download arm64 binary -->
|
||||||
<service name="download_url">
|
<service name="download_url">
|
||||||
<param name="protocol">https</param>
|
<param name="protocol">https</param>
|
||||||
<param name="host">github.com</param>
|
<param name="host">github.com</param>
|
||||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v0.6.2/dms-distropkg-arm64.gz</param>
|
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.0/dms-distropkg-arm64.gz</param>
|
||||||
</service>
|
</service>
|
||||||
</services>
|
</services>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
dms (0.6.2) stable; urgency=medium
|
dms (1.0.0) stable; urgency=medium
|
||||||
|
|
||||||
* Update to v0.6.2 release
|
* Update to v1.0.0 release
|
||||||
|
* Major stable release
|
||||||
* Fix binary download paths for OBS builds
|
* Fix binary download paths for OBS builds
|
||||||
* Native format: removed revisions
|
* Native format: removed revisions
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,10 @@ override_dh_auto_build:
|
|||||||
elif [ -f dms-source.tar.gz ]; then \
|
elif [ -f dms-source.tar.gz ]; then \
|
||||||
tar -xzf dms-source.tar.gz; \
|
tar -xzf dms-source.tar.gz; \
|
||||||
fi; \
|
fi; \
|
||||||
if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ] && [ -d DankMaterialShell-0.6.2 ]; then \
|
SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" ! -name "DankMaterialShell-$(UPSTREAM_VERSION)" | head -n1); \
|
||||||
mv DankMaterialShell-0.6.2 DankMaterialShell-$(UPSTREAM_VERSION); \
|
if [ -n "$$SOURCE_DIR" ] && [ "$$SOURCE_DIR" != "./DankMaterialShell-$(UPSTREAM_VERSION)" ]; then \
|
||||||
|
echo "Renaming $$SOURCE_DIR to DankMaterialShell-$(UPSTREAM_VERSION)"; \
|
||||||
|
mv "$$SOURCE_DIR" DankMaterialShell-$(UPSTREAM_VERSION); \
|
||||||
fi; \
|
fi; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -53,8 +55,12 @@ override_dh_auto_install:
|
|||||||
|
|
||||||
mkdir -p debian/dms/usr/share/quickshell/dms debian/dms/usr/lib/systemd/user
|
mkdir -p debian/dms/usr/share/quickshell/dms debian/dms/usr/lib/systemd/user
|
||||||
# Handle directory name mismatch again for install step if needed
|
# Handle directory name mismatch again for install step if needed
|
||||||
if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ] && [ -d DankMaterialShell-0.6.2 ]; then \
|
if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ]; then \
|
||||||
mv DankMaterialShell-0.6.2 DankMaterialShell-$(UPSTREAM_VERSION); \
|
SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" | head -n1); \
|
||||||
|
if [ -n "$$SOURCE_DIR" ]; then \
|
||||||
|
echo "Renaming $$SOURCE_DIR to DankMaterialShell-$(UPSTREAM_VERSION) for install"; \
|
||||||
|
mv "$$SOURCE_DIR" DankMaterialShell-$(UPSTREAM_VERSION); \
|
||||||
|
fi; \
|
||||||
fi
|
fi
|
||||||
if [ -d DankMaterialShell-$(UPSTREAM_VERSION) ]; then \
|
if [ -d DankMaterialShell-$(UPSTREAM_VERSION) ]; then \
|
||||||
cp -r DankMaterialShell-$(UPSTREAM_VERSION)/quickshell/* debian/dms/usr/share/quickshell/dms/; \
|
cp -r DankMaterialShell-$(UPSTREAM_VERSION)/quickshell/* debian/dms/usr/share/quickshell/dms/; \
|
||||||
|
|||||||
+5
-14
@@ -9,17 +9,7 @@ let
|
|||||||
cfg = config.programs.dankMaterialShell;
|
cfg = config.programs.dankMaterialShell;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
qmlPath = "${dmsPkgs.dms-shell}/share/quickshell/dms";
|
|
||||||
|
|
||||||
packages = [
|
packages = [
|
||||||
pkgs.material-symbols
|
|
||||||
pkgs.inter
|
|
||||||
pkgs.fira-code
|
|
||||||
|
|
||||||
pkgs.ddcutil
|
|
||||||
pkgs.libsForQt5.qt5ct
|
|
||||||
pkgs.kdePackages.qt6ct
|
|
||||||
|
|
||||||
dmsPkgs.dms-shell
|
dmsPkgs.dms-shell
|
||||||
]
|
]
|
||||||
++ lib.optional cfg.enableSystemMonitoring dmsPkgs.dgop
|
++ lib.optional cfg.enableSystemMonitoring dmsPkgs.dgop
|
||||||
@@ -31,10 +21,11 @@ in
|
|||||||
pkgs.glib
|
pkgs.glib
|
||||||
pkgs.networkmanager
|
pkgs.networkmanager
|
||||||
]
|
]
|
||||||
++ lib.optional cfg.enableBrightnessControl pkgs.brightnessctl
|
|
||||||
++ lib.optional cfg.enableColorPicker pkgs.hyprpicker
|
|
||||||
++ lib.optional cfg.enableDynamicTheming pkgs.matugen
|
++ lib.optional cfg.enableDynamicTheming pkgs.matugen
|
||||||
++ lib.optional cfg.enableAudioWavelength pkgs.cava
|
++ lib.optional cfg.enableAudioWavelength pkgs.cava
|
||||||
++ lib.optional cfg.enableCalendarEvents pkgs.khal
|
++ lib.optional cfg.enableCalendarEvents pkgs.khal;
|
||||||
++ lib.optional cfg.enableSystemSound pkgs.kdePackages.qtmultimedia;
|
|
||||||
|
plugins = lib.mapAttrs (name: plugin: {
|
||||||
|
source = plugin.src;
|
||||||
|
}) (lib.filterAttrs (n: v: v.enable) cfg.plugins);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ in
|
|||||||
plugins = lib.mkOption {
|
plugins = lib.mkOption {
|
||||||
type = attrsOf (
|
type = attrsOf (
|
||||||
types.submodule (
|
types.submodule (
|
||||||
{ config, ... }:
|
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
enable = lib.mkOption {
|
enable = lib.mkOption {
|
||||||
@@ -73,8 +72,6 @@ in
|
|||||||
programs.quickshell = {
|
programs.quickshell = {
|
||||||
enable = true;
|
enable = true;
|
||||||
inherit (cfg.quickshell) package;
|
inherit (cfg.quickshell) package;
|
||||||
|
|
||||||
configs.dms = common.qmlPath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
|
systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
|
||||||
@@ -82,7 +79,6 @@ in
|
|||||||
Description = "DankMaterialShell";
|
Description = "DankMaterialShell";
|
||||||
PartOf = [ config.wayland.systemd.target ];
|
PartOf = [ config.wayland.systemd.target ];
|
||||||
After = [ config.wayland.systemd.target ];
|
After = [ config.wayland.systemd.target ];
|
||||||
X-Restart-Triggers = lib.optional cfg.systemd.restartIfChanged common.qmlPath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Service = {
|
Service = {
|
||||||
|
|||||||
+8
-10
@@ -63,6 +63,14 @@ in
|
|||||||
allow-when-locked = true;
|
allow-when-locked = true;
|
||||||
action = dms-ipc "audio" "micmute";
|
action = dms-ipc "audio" "micmute";
|
||||||
};
|
};
|
||||||
|
"XF86MonBrightnessUp" = {
|
||||||
|
allow-when-locked = true;
|
||||||
|
action = dms-ipc "brightness" "increment" "5" "";
|
||||||
|
};
|
||||||
|
"XF86MonBrightnessDown" = {
|
||||||
|
allow-when-locked = true;
|
||||||
|
action = dms-ipc "brightness" "decrement" "5" "";
|
||||||
|
};
|
||||||
"Mod+Alt+N" = {
|
"Mod+Alt+N" = {
|
||||||
allow-when-locked = true;
|
allow-when-locked = true;
|
||||||
action = dms-ipc "night" "toggle";
|
action = dms-ipc "night" "toggle";
|
||||||
@@ -80,16 +88,6 @@ in
|
|||||||
action = dms-ipc "clipboard" "toggle";
|
action = dms-ipc "clipboard" "toggle";
|
||||||
hotkey-overlay.title = "Toggle Clipboard Manager";
|
hotkey-overlay.title = "Toggle Clipboard Manager";
|
||||||
};
|
};
|
||||||
}
|
|
||||||
// lib.attrsets.optionalAttrs cfg.enableBrightnessControl {
|
|
||||||
"XF86MonBrightnessUp" = {
|
|
||||||
allow-when-locked = true;
|
|
||||||
action = dms-ipc "brightness" "increment" "5" "";
|
|
||||||
};
|
|
||||||
"XF86MonBrightnessDown" = {
|
|
||||||
allow-when-locked = true;
|
|
||||||
action = dms-ipc "brightness" "decrement" "5" "";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
environment.etc."xdg/quickshell/dms".source = "${dmsPkgs.dms-shell}/share/quickshell/dms";
|
|
||||||
|
|
||||||
systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
|
systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
|
||||||
description = "DankMaterialShell";
|
description = "DankMaterialShell";
|
||||||
path = lib.mkForce [ ];
|
path = lib.mkForce [ ];
|
||||||
@@ -31,7 +29,7 @@ in
|
|||||||
partOf = [ "graphical-session.target" ];
|
partOf = [ "graphical-session.target" ];
|
||||||
after = [ "graphical-session.target" ];
|
after = [ "graphical-session.target" ];
|
||||||
wantedBy = [ "graphical-session.target" ];
|
wantedBy = [ "graphical-session.target" ];
|
||||||
restartTriggers = lib.optional cfg.systemd.restartIfChanged common.qmlPath;
|
restartIfChanged = cfg.systemd.restartIfChanged;
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = lib.getExe dmsPkgs.dms-shell + " run --session";
|
ExecStart = lib.getExe dmsPkgs.dms-shell + " run --session";
|
||||||
@@ -40,5 +38,8 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
environment.systemPackages = [ cfg.quickshell.package ] ++ common.packages;
|
environment.systemPackages = [ cfg.quickshell.package ] ++ common.packages;
|
||||||
|
|
||||||
|
services.power-profiles-daemon.enable = lib.mkDefault true;
|
||||||
|
services.accounts-daemon.enable = lib.mkDefault true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-16
@@ -5,11 +5,24 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) types;
|
inherit (lib) types;
|
||||||
|
path = [
|
||||||
|
"programs"
|
||||||
|
"dankMaterialShell"
|
||||||
|
];
|
||||||
|
|
||||||
|
builtInRemovedMsg = "This is now built-in in DMS and doesn't need additional dependencies.";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
imports = [
|
||||||
|
(lib.mkRemovedOptionModule (path ++ [ "enableBrightnessControl" ]) builtInRemovedMsg)
|
||||||
|
(lib.mkRemovedOptionModule (path ++ [ "enableColorPicker" ]) builtInRemovedMsg)
|
||||||
|
(lib.mkRemovedOptionModule (
|
||||||
|
path ++ [ "enableSystemSound" ]
|
||||||
|
) "qtmultimedia is now included on dms-shell package.")
|
||||||
|
];
|
||||||
|
|
||||||
options.programs.dankMaterialShell = {
|
options.programs.dankMaterialShell = {
|
||||||
enable = lib.mkEnableOption "DankMaterialShell";
|
enable = lib.mkEnableOption "DankMaterialShell";
|
||||||
|
|
||||||
systemd = {
|
systemd = {
|
||||||
enable = lib.mkEnableOption "DankMaterialShell systemd startup";
|
enable = lib.mkEnableOption "DankMaterialShell systemd startup";
|
||||||
restartIfChanged = lib.mkOption {
|
restartIfChanged = lib.mkOption {
|
||||||
@@ -33,16 +46,6 @@ in
|
|||||||
default = true;
|
default = true;
|
||||||
description = "Add needed dependencies to use the VPN widget";
|
description = "Add needed dependencies to use the VPN widget";
|
||||||
};
|
};
|
||||||
enableBrightnessControl = lib.mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Add needed dependencies to have brightness/backlight support";
|
|
||||||
};
|
|
||||||
enableColorPicker = lib.mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Add needed dependencies to have color picking support";
|
|
||||||
};
|
|
||||||
enableDynamicTheming = lib.mkOption {
|
enableDynamicTheming = lib.mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
@@ -58,15 +61,46 @@ in
|
|||||||
default = true;
|
default = true;
|
||||||
description = "Add calendar events support via khal";
|
description = "Add calendar events support via khal";
|
||||||
};
|
};
|
||||||
enableSystemSound = lib.mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Add needed dependencies to have system sound support";
|
|
||||||
};
|
|
||||||
quickshell = {
|
quickshell = {
|
||||||
package = lib.mkPackageOption dmsPkgs "quickshell" {
|
package = lib.mkPackageOption dmsPkgs "quickshell" {
|
||||||
extraDescription = "The quickshell package to use (defaults to be built from source, in the commit 26531f due to unreleased features used by DMS).";
|
extraDescription = "The quickshell package to use (defaults to be built from source, in the commit 26531f due to unreleased features used by DMS).";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
plugins = lib.mkOption {
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to enable this plugin";
|
||||||
|
};
|
||||||
|
src = lib.mkOption {
|
||||||
|
type = types.package;
|
||||||
|
description = "Source of the plugin package or path";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
description = "DMS Plugins to install and enable";
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
DockerManager = {
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "LuckShiba";
|
||||||
|
repo = "DmsDockerManager";
|
||||||
|
rev = "v1.2.0";
|
||||||
|
sha256 = "sha256-VoJCaygWnKpv0s0pqTOmzZnPM922qPDMHk4EPcgVnaU=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
AnotherPlugin = {
|
||||||
|
enable = true;
|
||||||
|
src = pkgs.another-plugin;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
%global debug_package %{nil}
|
%global debug_package %{nil}
|
||||||
|
|
||||||
Name: dms
|
Name: dms
|
||||||
Version: 0.6.2
|
Version: 1.0.0
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||||
|
|
||||||
|
|||||||
Executable
+228
@@ -0,0 +1,228 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
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
|
||||||
|
|
||||||
|
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..."
|
||||||
|
|
||||||
|
# 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
|
||||||
|
fi
|
||||||
|
# Signal running DMS instances to reload
|
||||||
|
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||||
|
|
||||||
|
%files
|
||||||
|
%license LICENSE
|
||||||
|
%doc README.md CONTRIBUTING.md
|
||||||
|
%{_datadir}/quickshell/dms/
|
||||||
|
%{_userunitdir}/dms.service
|
||||||
|
%{_datadir}/applications/dms-open.desktop
|
||||||
|
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||||
|
|
||||||
|
%files -n dms-cli
|
||||||
|
%{_bindir}/dms
|
||||||
|
%{_datadir}/bash-completion/completions/dms
|
||||||
|
%{_datadir}/zsh/site-functions/_dms
|
||||||
|
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
||||||
|
- Stable release VERSION_PLACEHOLDER
|
||||||
|
- Built locally with corrected tarball
|
||||||
|
SPECEOF
|
||||||
|
|
||||||
|
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
|
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
|
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
|
|
||||||
|
# Build SRPM
|
||||||
|
echo "Building SRPM..."
|
||||||
|
cd ~/rpmbuild/SPECS
|
||||||
|
rpmbuild -bs dms.spec
|
||||||
|
|
||||||
|
SRPM=$(ls ~/rpmbuild/SRPMS/dms-${VERSION}-*.src.rpm | tail -n 1)
|
||||||
|
if [ ! -f "$SRPM" ]; then
|
||||||
|
echo "Error: SRPM not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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 " 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"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload to Copr
|
||||||
|
echo ""
|
||||||
|
echo "Uploading to Copr..."
|
||||||
|
if copr-cli build avengemedia/dms "$SRPM" --nowait; then
|
||||||
|
echo ""
|
||||||
|
echo "Build submitted successfully! Check status at:"
|
||||||
|
echo "https://copr.fedorainfracloud.org/coprs/avengemedia/dms/builds/"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Copr upload failed. You can manually upload the SRPM:"
|
||||||
|
echo " copr-cli build avengemedia/dms $SRPM"
|
||||||
|
echo ""
|
||||||
|
echo "Or upload via web interface:"
|
||||||
|
echo " https://copr.fedorainfracloud.org/coprs/avengemedia/dms/builds/"
|
||||||
|
echo ""
|
||||||
|
echo "SRPM location: $SRPM"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -672,7 +672,20 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$SOURCE_FORMAT" == *"native"* ]] && [[
|
|||||||
if [[ "$IS_MANUAL" == true ]]; then
|
if [[ "$IS_MANUAL" == true ]]; then
|
||||||
echo "==> Detected rebuild of same base version $CHANGELOG_BASE, incrementing version"
|
echo "==> Detected rebuild of same base version $CHANGELOG_BASE, incrementing version"
|
||||||
|
|
||||||
if [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git$ ]]; then
|
# If REBUILD_RELEASE is set, use that number directly
|
||||||
|
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
||||||
|
if [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git([0-9]+)(\.[a-f0-9]+)?$ ]]; then
|
||||||
|
BASE_VERSION="${BASH_REMATCH[1]}"
|
||||||
|
GIT_NUM="${BASH_REMATCH[2]}"
|
||||||
|
GIT_HASH="${BASH_REMATCH[3]}"
|
||||||
|
NEW_VERSION="${BASE_VERSION}+git${GIT_NUM}${GIT_HASH}ppa${REBUILD_RELEASE}"
|
||||||
|
echo " Using REBUILD_RELEASE=$REBUILD_RELEASE: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||||
|
else
|
||||||
|
BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
|
||||||
|
NEW_VERSION="${BASE_VERSION}ppa${REBUILD_RELEASE}"
|
||||||
|
echo " Using REBUILD_RELEASE=$REBUILD_RELEASE: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||||
|
fi
|
||||||
|
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git$ ]]; then
|
||||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
BASE_VERSION="${BASH_REMATCH[1]}"
|
||||||
NEW_VERSION="${BASE_VERSION}+gitppa1"
|
NEW_VERSION="${BASE_VERSION}+gitppa1"
|
||||||
echo " Adding PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
echo " Adding PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||||
@@ -704,11 +717,27 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$SOURCE_FORMAT" == *"native"* ]] && [[
|
|||||||
fi
|
fi
|
||||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)(-([0-9]+))?$ ]]; then
|
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)(-([0-9]+))?$ ]]; then
|
||||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
BASE_VERSION="${BASH_REMATCH[1]}"
|
||||||
NEW_VERSION="${BASE_VERSION}ppa1"
|
# Check if old DSC has ppa suffix even if changelog doesn't
|
||||||
echo " Warning: Native format cannot have Debian revision, converting to PPA format: $CHANGELOG_VERSION -> $NEW_VERSION"
|
if [[ "$OLD_DSC_VERSION" =~ ppa([0-9]+)$ ]]; then
|
||||||
|
OLD_PPA_NUM="${BASH_REMATCH[1]}"
|
||||||
|
NEW_PPA_NUM=$((OLD_PPA_NUM + 1))
|
||||||
|
NEW_VERSION="${BASE_VERSION}ppa${NEW_PPA_NUM}"
|
||||||
|
echo " Incrementing PPA number from old DSC: $OLD_DSC_VERSION -> $NEW_VERSION"
|
||||||
|
else
|
||||||
|
NEW_VERSION="${BASE_VERSION}ppa1"
|
||||||
|
echo " Adding PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
NEW_VERSION="${CHANGELOG_VERSION}ppa1"
|
# Check if old DSC has ppa suffix for unknown formats
|
||||||
echo " Warning: Could not parse version format, appending ppa1: $CHANGELOG_VERSION -> $NEW_VERSION"
|
if [[ "$OLD_DSC_VERSION" =~ ppa([0-9]+)$ ]]; then
|
||||||
|
OLD_PPA_NUM="${BASH_REMATCH[1]}"
|
||||||
|
NEW_PPA_NUM=$((OLD_PPA_NUM + 1))
|
||||||
|
NEW_VERSION="${CHANGELOG_VERSION}ppa${NEW_PPA_NUM}"
|
||||||
|
echo " Incrementing PPA number from old DSC: $OLD_DSC_VERSION -> $NEW_VERSION"
|
||||||
|
else
|
||||||
|
NEW_VERSION="${CHANGELOG_VERSION}ppa1"
|
||||||
|
echo " Warning: Could not parse version format, appending ppa1: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$SOURCE_DIR" ]] || [[ ! -d "$SOURCE_DIR" ]] || [[ ! -d "$SOURCE_DIR/debian" ]]; then
|
if [[ -z "$SOURCE_DIR" ]] || [[ ! -d "$SOURCE_DIR" ]] || [[ ! -d "$SOURCE_DIR/debian" ]]; then
|
||||||
@@ -763,7 +792,15 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$SOURCE_FORMAT" == *"native"* ]] && [[
|
|||||||
echo " Renaming $EXTRACTED to $EXPECTED_SOURCE_DIR"
|
echo " Renaming $EXTRACTED to $EXPECTED_SOURCE_DIR"
|
||||||
mv "$EXTRACTED" "$EXPECTED_SOURCE_DIR"
|
mv "$EXTRACTED" "$EXPECTED_SOURCE_DIR"
|
||||||
rm -f "$WORK_DIR/dms-source.tar.gz"
|
rm -f "$WORK_DIR/dms-source.tar.gz"
|
||||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/dms-source.tar.gz" "$EXPECTED_SOURCE_DIR"
|
if ! tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/dms-source.tar.gz" "$EXPECTED_SOURCE_DIR"; then
|
||||||
|
echo " Error: Failed to create dms-source.tar.gz"
|
||||||
|
ls -lah "$EXPECTED_SOURCE_DIR" | head -20
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$WORK_DIR/dms-source.tar.gz" ]]; then
|
||||||
|
echo " Error: dms-source.tar.gz was not created"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
ROOT_DIR=$(tar -tf "$WORK_DIR/dms-source.tar.gz" | head -1 | cut -d/ -f1)
|
ROOT_DIR=$(tar -tf "$WORK_DIR/dms-source.tar.gz" | head -1 | cut -d/ -f1)
|
||||||
if [[ "$ROOT_DIR" != "$EXPECTED_SOURCE_DIR" ]]; then
|
if [[ "$ROOT_DIR" != "$EXPECTED_SOURCE_DIR" ]]; then
|
||||||
echo " Error: Recreated tarball has wrong root directory: $ROOT_DIR (expected $EXPECTED_SOURCE_DIR)"
|
echo " Error: Recreated tarball has wrong root directory: $ROOT_DIR (expected $EXPECTED_SOURCE_DIR)"
|
||||||
@@ -839,11 +876,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$SOURCE_FORMAT" == *"native"* ]] && [[
|
|||||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
echo "==> Detected same version. Not a manual run, skipping Debian version increment."
|
|
||||||
echo "✅ No changes needed for Debian. Exiting."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
TARBALL_SIZE=$(stat -c%s "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null || stat -f%z "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null)
|
TARBALL_SIZE=$(stat -c%s "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null || stat -f%z "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null)
|
||||||
TARBALL_MD5=$(md5sum "$WORK_DIR/$COMBINED_TARBALL" | cut -d' ' -f1)
|
TARBALL_MD5=$(md5sum "$WORK_DIR/$COMBINED_TARBALL" | cut -d' ' -f1)
|
||||||
|
|
||||||
@@ -884,15 +917,40 @@ Files:
|
|||||||
$TARBALL_MD5 $TARBALL_SIZE $COMBINED_TARBALL
|
$TARBALL_MD5 $TARBALL_SIZE $COMBINED_TARBALL
|
||||||
EOF
|
EOF
|
||||||
echo " - Updated changelog and recreated tarball with version $NEW_VERSION"
|
echo " - Updated changelog and recreated tarball with version $NEW_VERSION"
|
||||||
|
else
|
||||||
|
echo "==> Detected same version. Not a manual run, skipping Debian version increment."
|
||||||
|
echo "✅ No changes needed for Debian. Exiting."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Ensure we're in WORK_DIR and it exists
|
||||||
|
if [[ ! -d "$WORK_DIR" ]]; then
|
||||||
|
echo "ERROR: WORK_DIR does not exist: $WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$WORK_DIR" || {
|
||||||
|
echo "ERROR: Cannot cd to WORK_DIR: $WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
find . -maxdepth 1 -type f \( -name "*.dsc" -o -name "*.spec" \) -exec grep -l "^<<<<<<< " {} \; 2>/dev/null | while read -r conflicted_file; do
|
find . -maxdepth 1 -type f \( -name "*.dsc" -o -name "*.spec" \) -exec grep -l "^<<<<<<< " {} \; 2>/dev/null | while read -r conflicted_file; do
|
||||||
echo " Removing conflicted text file: $conflicted_file"
|
echo " Removing conflicted text file: $conflicted_file"
|
||||||
rm -f "$conflicted_file"
|
rm -f "$conflicted_file"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Ensure we're STILL in WORK_DIR before running osc commands
|
||||||
|
cd "$WORK_DIR" || {
|
||||||
|
echo "ERROR: Cannot cd to WORK_DIR: $WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "DEBUG: Current directory: $(pwd)"
|
||||||
|
echo "DEBUG: WORK_DIR=$WORK_DIR"
|
||||||
|
echo "DEBUG: Files in directory:"
|
||||||
|
ls -la 2>&1 | head -20
|
||||||
|
|
||||||
echo "==> Staging changes"
|
echo "==> Staging changes"
|
||||||
echo "Files to upload:"
|
echo "Files to upload:"
|
||||||
if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
||||||
|
|||||||
+58
-27
@@ -50,9 +50,15 @@ fi
|
|||||||
# Get absolute path
|
# Get absolute path
|
||||||
PACKAGE_DIR=$(cd "$PACKAGE_DIR" && pwd)
|
PACKAGE_DIR=$(cd "$PACKAGE_DIR" && pwd)
|
||||||
PACKAGE_NAME=$(basename "$PACKAGE_DIR")
|
PACKAGE_NAME=$(basename "$PACKAGE_DIR")
|
||||||
|
PACKAGE_PARENT=$(dirname "$PACKAGE_DIR")
|
||||||
|
|
||||||
|
# Create temporary working directory (like OBS)
|
||||||
|
TEMP_WORK_DIR=$(mktemp -d -t ppa_build_work_XXXXXX)
|
||||||
|
trap "rm -rf '$TEMP_WORK_DIR'" EXIT
|
||||||
|
|
||||||
info "Building source package for: $PACKAGE_NAME"
|
info "Building source package for: $PACKAGE_NAME"
|
||||||
info "Package directory: $PACKAGE_DIR"
|
info "Package directory: $PACKAGE_DIR"
|
||||||
|
info "Working directory: $TEMP_WORK_DIR"
|
||||||
info "Target Ubuntu series: $UBUNTU_SERIES"
|
info "Target Ubuntu series: $UBUNTU_SERIES"
|
||||||
|
|
||||||
# Check for required files
|
# Check for required files
|
||||||
@@ -119,8 +125,13 @@ elif [[ -z "${GITHUB_ACTIONS:-}" ]] && [[ -z "${CI:-}" ]]; then
|
|||||||
echo "==> Local/manual run detected (not in CI)"
|
echo "==> Local/manual run detected (not in CI)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Copy package to temp working directory
|
||||||
|
info "Copying package to working directory..."
|
||||||
|
cp -r "$PACKAGE_DIR" "$TEMP_WORK_DIR/"
|
||||||
|
WORK_PACKAGE_DIR="$TEMP_WORK_DIR/$PACKAGE_NAME"
|
||||||
|
|
||||||
# Detect package type and update version automatically
|
# Detect package type and update version automatically
|
||||||
cd "$PACKAGE_DIR"
|
cd "$WORK_PACKAGE_DIR"
|
||||||
|
|
||||||
# Function to get latest tag from GitHub
|
# Function to get latest tag from GitHub
|
||||||
get_latest_tag() {
|
get_latest_tag() {
|
||||||
@@ -283,22 +294,29 @@ if [ "$IS_GIT_PACKAGE" = true ] && [ -n "$GIT_REPO" ]; then
|
|||||||
# Check if we're rebuilding the same commit (increment PPA number if so)
|
# Check if we're rebuilding the same commit (increment PPA number if so)
|
||||||
BASE_VERSION="${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}"
|
BASE_VERSION="${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}"
|
||||||
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
||||||
PPA_NUM=1
|
|
||||||
|
|
||||||
# If current version matches the base version, increment PPA number
|
# Use REBUILD_RELEASE if provided, otherwise auto-increment
|
||||||
# Escape special regex characters in BASE_VERSION for pattern matching
|
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
||||||
ESCAPED_BASE=$(echo "$BASE_VERSION" | sed 's/\./\\./g' | sed 's/+/\\+/g')
|
PPA_NUM=$REBUILD_RELEASE
|
||||||
if [[ "$CURRENT_VERSION" =~ ^${ESCAPED_BASE}ppa([0-9]+)$ ]]; then
|
info "Using REBUILD_RELEASE=$REBUILD_RELEASE for PPA number"
|
||||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
|
||||||
if [[ "$IS_MANUAL" == true ]]; then
|
|
||||||
info "Detected rebuild of same commit (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
|
||||||
else
|
|
||||||
info "Detected rebuild of same commit (current: $CURRENT_VERSION). Not a manual run, skipping."
|
|
||||||
success "No changes needed (commit matches)."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
info "New commit or first build, using PPA number $PPA_NUM"
|
PPA_NUM=1
|
||||||
|
|
||||||
|
# If current version matches the base version, increment PPA number
|
||||||
|
# Escape special regex characters in BASE_VERSION for pattern matching
|
||||||
|
ESCAPED_BASE=$(echo "$BASE_VERSION" | sed 's/\./\\./g' | sed 's/+/\\+/g')
|
||||||
|
if [[ "$CURRENT_VERSION" =~ ^${ESCAPED_BASE}ppa([0-9]+)$ ]]; then
|
||||||
|
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
||||||
|
if [[ "$IS_MANUAL" == true ]]; then
|
||||||
|
info "Detected rebuild of same commit (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
||||||
|
else
|
||||||
|
info "Detected rebuild of same commit (current: $CURRENT_VERSION). Not a manual run, skipping."
|
||||||
|
success "No changes needed (commit matches)."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info "New commit or first build, using PPA number $PPA_NUM"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||||
@@ -429,16 +447,23 @@ elif [ -n "$GIT_REPO" ]; then
|
|||||||
|
|
||||||
# Get current version to check if we need to increment PPA number
|
# Get current version to check if we need to increment PPA number
|
||||||
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
||||||
PPA_NUM=1
|
|
||||||
|
# Use REBUILD_RELEASE if provided, otherwise auto-increment
|
||||||
|
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
||||||
|
PPA_NUM=$REBUILD_RELEASE
|
||||||
|
info "Using REBUILD_RELEASE=$REBUILD_RELEASE for PPA number"
|
||||||
|
else
|
||||||
|
PPA_NUM=1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$SOURCE_FORMAT" == *"native"* ]]; then
|
if [[ "$SOURCE_FORMAT" == *"native"* ]]; then
|
||||||
# Native format: 0.2.1ppa1 (no dash, no revision)
|
# Native format: 0.2.1ppa1 (no dash, no revision)
|
||||||
BASE_VERSION="${LATEST_TAG}"
|
BASE_VERSION="${LATEST_TAG}"
|
||||||
# Check if we're rebuilding the same version (increment PPA number if so)
|
# Check if we're rebuilding the same version (increment PPA number if so)
|
||||||
if [[ "$CURRENT_VERSION" =~ ^${LATEST_TAG}ppa([0-9]+)$ ]]; then
|
if [[ -z "${REBUILD_RELEASE:-}" ]] && [[ "$CURRENT_VERSION" =~ ^${LATEST_TAG}ppa([0-9]+)$ ]]; then
|
||||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
||||||
info "Detected rebuild of same version (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
info "Detected rebuild of same version (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
||||||
else
|
elif [[ -z "${REBUILD_RELEASE:-}" ]]; then
|
||||||
info "New version or first build, using PPA number $PPA_NUM"
|
info "New version or first build, using PPA number $PPA_NUM"
|
||||||
fi
|
fi
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||||
@@ -447,7 +472,7 @@ elif [ -n "$GIT_REPO" ]; then
|
|||||||
BASE_VERSION="${LATEST_TAG}-1"
|
BASE_VERSION="${LATEST_TAG}-1"
|
||||||
# Check if we're rebuilding the same version (increment PPA number if so)
|
# Check if we're rebuilding the same version (increment PPA number if so)
|
||||||
ESCAPED_BASE=$(echo "$BASE_VERSION" | sed 's/\./\\./g' | sed 's/-/\\-/g')
|
ESCAPED_BASE=$(echo "$BASE_VERSION" | sed 's/\./\\./g' | sed 's/-/\\-/g')
|
||||||
if [[ "$CURRENT_VERSION" =~ ^${ESCAPED_BASE}ppa([0-9]+)$ ]]; then
|
if [[ -z "${REBUILD_RELEASE:-}" ]] && [[ "$CURRENT_VERSION" =~ ^${ESCAPED_BASE}ppa([0-9]+)$ ]]; then
|
||||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
||||||
if [[ "$IS_MANUAL" == true ]]; then
|
if [[ "$IS_MANUAL" == true ]]; then
|
||||||
info "Detected rebuild of same version (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
info "Detected rebuild of same version (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
||||||
@@ -456,7 +481,7 @@ elif [ -n "$GIT_REPO" ]; then
|
|||||||
success "No changes needed (version matches)."
|
success "No changes needed (version matches)."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
else
|
elif [[ -z "${REBUILD_RELEASE:-}" ]]; then
|
||||||
info "New version or first build, using PPA number $PPA_NUM"
|
info "New version or first build, using PPA number $PPA_NUM"
|
||||||
fi
|
fi
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||||
@@ -575,10 +600,12 @@ info "Building source package..."
|
|||||||
echo
|
echo
|
||||||
|
|
||||||
# Determine if we need to include orig tarball (-sa) or just debian changes (-sd)
|
# Determine if we need to include orig tarball (-sa) or just debian changes (-sd)
|
||||||
# Check if .orig.tar.xz already exists in parent directory (previous build)
|
# Check if .orig.tar.xz already exists in real parent directory (previous build)
|
||||||
ORIG_TARBALL="${PACKAGE_NAME}_${VERSION%.ppa*}.orig.tar.xz"
|
ORIG_TARBALL="${PACKAGE_NAME}_${VERSION%.ppa*}.orig.tar.xz"
|
||||||
if [ -f "../$ORIG_TARBALL" ]; then
|
if [ -f "$PACKAGE_PARENT/$ORIG_TARBALL" ]; then
|
||||||
info "Found existing orig tarball, using -sd (debian changes only)"
|
info "Found existing orig tarball in $PACKAGE_PARENT, using -sd (debian changes only)"
|
||||||
|
# Copy it to temp parent so debuild can find it
|
||||||
|
cp "$PACKAGE_PARENT/$ORIG_TARBALL" "$TEMP_WORK_DIR/"
|
||||||
DEBUILD_SOURCE_FLAG="-sd"
|
DEBUILD_SOURCE_FLAG="-sd"
|
||||||
else
|
else
|
||||||
info "No existing orig tarball found, using -sa (include original source)"
|
info "No existing orig tarball found, using -sa (include original source)"
|
||||||
@@ -592,15 +619,19 @@ if yes | DEBIAN_FRONTEND=noninteractive debuild -S $DEBUILD_SOURCE_FLAG -d; then
|
|||||||
echo
|
echo
|
||||||
success "Source package built successfully!"
|
success "Source package built successfully!"
|
||||||
|
|
||||||
|
# Copy build artifacts back to parent directory
|
||||||
|
info "Copying build artifacts to $PACKAGE_PARENT..."
|
||||||
|
cp -v "$TEMP_WORK_DIR"/${SOURCE_NAME}_${CHANGELOG_VERSION}* "$PACKAGE_PARENT/" 2>/dev/null || true
|
||||||
|
|
||||||
# List generated files
|
# List generated files
|
||||||
info "Generated files in $(dirname "$PACKAGE_DIR"):"
|
info "Generated files in $PACKAGE_PARENT:"
|
||||||
ls -lh "$(dirname "$PACKAGE_DIR")"/${SOURCE_NAME}_${CHANGELOG_VERSION}* 2>/dev/null || true
|
ls -lh "$PACKAGE_PARENT"/${SOURCE_NAME}_${CHANGELOG_VERSION}* 2>/dev/null || true
|
||||||
|
|
||||||
# Show what to do next
|
# Show what to do next
|
||||||
echo
|
echo
|
||||||
info "Next steps:"
|
info "Next steps:"
|
||||||
echo " 1. Review the source package:"
|
echo " 1. Review the source package:"
|
||||||
echo " cd $(dirname "$PACKAGE_DIR")"
|
echo " cd $PACKAGE_PARENT"
|
||||||
echo " ls -lh ${SOURCE_NAME}_${CHANGELOG_VERSION}*"
|
echo " ls -lh ${SOURCE_NAME}_${CHANGELOG_VERSION}*"
|
||||||
echo
|
echo
|
||||||
echo " 2. Upload to PPA (stable):"
|
echo " 2. Upload to PPA (stable):"
|
||||||
@@ -610,7 +641,7 @@ if yes | DEBIAN_FRONTEND=noninteractive debuild -S $DEBUILD_SOURCE_FLAG -d; then
|
|||||||
echo " dput ppa:avengemedia/dms-git ${SOURCE_NAME}_${CHANGELOG_VERSION}_source.changes"
|
echo " dput ppa:avengemedia/dms-git ${SOURCE_NAME}_${CHANGELOG_VERSION}_source.changes"
|
||||||
echo
|
echo
|
||||||
echo " 4. Or use the upload script:"
|
echo " 4. Or use the upload script:"
|
||||||
echo " ./upload-ppa.sh $(dirname "$PACKAGE_DIR")/${SOURCE_NAME}_${CHANGELOG_VERSION}_source.changes dms"
|
echo " ./upload-ppa.sh $PACKAGE_PARENT/${SOURCE_NAME}_${CHANGELOG_VERSION}_source.changes dms"
|
||||||
|
|
||||||
else
|
else
|
||||||
error "Source package build failed!"
|
error "Source package build failed!"
|
||||||
|
|||||||
@@ -150,7 +150,38 @@ fi
|
|||||||
info "Uploading to Launchpad..."
|
info "Uploading to Launchpad..."
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if dput "ppa:avengemedia/$PPA_NAME" "$CHANGES_FILE"; then
|
UPLOAD_SUCCESS=false
|
||||||
|
|
||||||
|
if [ "$UPLOAD_METHOD" = "dput" ]; then
|
||||||
|
if dput "ppa:avengemedia/$PPA_NAME" "$CHANGES_FILE"; then
|
||||||
|
UPLOAD_SUCCESS=true
|
||||||
|
fi
|
||||||
|
elif [ "$UPLOAD_METHOD" = "lftp" ]; then
|
||||||
|
# Use lftp to upload to Launchpad PPA
|
||||||
|
CHANGES_DIR=$(dirname "$CHANGES_FILE")
|
||||||
|
CHANGES_BASENAME=$(basename "$CHANGES_FILE")
|
||||||
|
|
||||||
|
# Extract files to upload from .changes file
|
||||||
|
FILES_TO_UPLOAD=("$CHANGES_BASENAME")
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ "$line" =~ ^\ [a-f0-9]+\ [0-9]+\ [^\ ]+\ [^\ ]+\ (.+)$ ]]; then
|
||||||
|
FILES_TO_UPLOAD+=("${BASH_REMATCH[1]}")
|
||||||
|
fi
|
||||||
|
done < "$CHANGES_FILE"
|
||||||
|
|
||||||
|
# Build lftp command to upload all files
|
||||||
|
LFTP_COMMANDS="set ftp:ssl-allow no; open ftp://ppa.launchpad.net; user anonymous ''; cd ~avengemedia/ubuntu/$PPA_NAME/;"
|
||||||
|
for file in "${FILES_TO_UPLOAD[@]}"; do
|
||||||
|
LFTP_COMMANDS="$LFTP_COMMANDS put '$CHANGES_DIR/$file';"
|
||||||
|
done
|
||||||
|
LFTP_COMMANDS="$LFTP_COMMANDS bye"
|
||||||
|
|
||||||
|
if echo "$LFTP_COMMANDS" | lftp; then
|
||||||
|
UPLOAD_SUCCESS=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$UPLOAD_SUCCESS" = true ]; then
|
||||||
echo
|
echo
|
||||||
success "Upload successful!"
|
success "Upload successful!"
|
||||||
echo
|
echo
|
||||||
@@ -166,7 +197,6 @@ if dput "ppa:avengemedia/$PPA_NAME" "$CHANGES_FILE"; then
|
|||||||
echo " sudo add-apt-repository ppa:avengemedia/$PPA_NAME"
|
echo " sudo add-apt-repository ppa:avengemedia/$PPA_NAME"
|
||||||
echo " sudo apt update"
|
echo " sudo apt update"
|
||||||
echo " sudo apt install $PACKAGE_NAME"
|
echo " sudo apt install $PACKAGE_NAME"
|
||||||
|
|
||||||
else
|
else
|
||||||
error "Upload failed!"
|
error "Upload failed!"
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
dms (0.6.2ppa3) questing; urgency=medium
|
dms (1.0.0ppa4) questing; urgency=medium
|
||||||
|
|
||||||
* Rebuild for packaging fixes (ppa3)
|
* Rebuild for packaging fixes (ppa4)
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sun, 23 Nov 2025 00:40:41 -0500
|
-- Avenge Media <AvengeMedia.US@gmail.com> Wed, 10 Dec 2025 12:56:23 -0500
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
dms_0.6.2ppa3_source.buildinfo x11 optional
|
dms_1.0.0ppa4_source.buildinfo x11 optional
|
||||||
|
|||||||
@@ -29,25 +29,22 @@ override_dh_auto_build:
|
|||||||
# Extract source tarball for QML files
|
# Extract source tarball for QML files
|
||||||
tar -xzf dms-source.tar.gz
|
tar -xzf dms-source.tar.gz
|
||||||
# Find the extracted directory (it might have various names)
|
# Find the extracted directory (it might have various names)
|
||||||
# and create a symlink to expected name for consistent install
|
# and rename it to the expected BASE_VERSION name for consistent install
|
||||||
SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell*" | head -n1); \
|
SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" ! -name "DankMaterialShell-$(BASE_VERSION)" | head -n1); \
|
||||||
if [ -n "$$SOURCE_DIR" ]; then \
|
if [ -n "$$SOURCE_DIR" ] && [ "$$SOURCE_DIR" != "./DankMaterialShell-$(BASE_VERSION)" ]; then \
|
||||||
ln -sf $$SOURCE_DIR DankMaterialShell-$(BASE_VERSION); \
|
echo "Renaming $$SOURCE_DIR to DankMaterialShell-$(BASE_VERSION)"; \
|
||||||
|
mv "$$SOURCE_DIR" DankMaterialShell-$(BASE_VERSION); \
|
||||||
|
elif [ ! -d "DankMaterialShell-$(BASE_VERSION)" ]; then \
|
||||||
|
echo "ERROR: No DankMaterialShell directory found after extraction!"; \
|
||||||
|
ls -la; \
|
||||||
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
override_dh_auto_install:
|
override_dh_auto_install:
|
||||||
# Install binary
|
# Install binary
|
||||||
install -Dm755 dms debian/dms/usr/bin/dms
|
install -Dm755 dms debian/dms/usr/bin/dms
|
||||||
|
|
||||||
# Install QML files from source tarball
|
# Install systemd user service (before copying everything else)
|
||||||
mkdir -p debian/dms/usr/share/quickshell/dms
|
|
||||||
cp -r DankMaterialShell-$(BASE_VERSION)/* debian/dms/usr/share/quickshell/dms/
|
|
||||||
|
|
||||||
# Remove unnecessary directories
|
|
||||||
rm -rf debian/dms/usr/share/quickshell/dms/core
|
|
||||||
rm -rf debian/dms/usr/share/quickshell/dms/distro
|
|
||||||
|
|
||||||
# Install systemd user service
|
|
||||||
install -Dm644 DankMaterialShell-$(BASE_VERSION)/assets/systemd/dms.service \
|
install -Dm644 DankMaterialShell-$(BASE_VERSION)/assets/systemd/dms.service \
|
||||||
debian/dms/usr/lib/systemd/user/dms.service
|
debian/dms/usr/lib/systemd/user/dms.service
|
||||||
|
|
||||||
@@ -57,6 +54,15 @@ override_dh_auto_install:
|
|||||||
install -Dm644 DankMaterialShell-$(BASE_VERSION)/assets/danklogo.svg \
|
install -Dm644 DankMaterialShell-$(BASE_VERSION)/assets/danklogo.svg \
|
||||||
debian/dms/usr/share/icons/hicolor/scalable/apps/danklogo.svg
|
debian/dms/usr/share/icons/hicolor/scalable/apps/danklogo.svg
|
||||||
|
|
||||||
|
# Install QML files from source tarball
|
||||||
|
mkdir -p debian/dms/usr/share/quickshell/dms
|
||||||
|
cp -r DankMaterialShell-$(BASE_VERSION)/* debian/dms/usr/share/quickshell/dms/
|
||||||
|
|
||||||
|
# Remove unnecessary directories
|
||||||
|
rm -rf debian/dms/usr/share/quickshell/dms/core
|
||||||
|
rm -rf debian/dms/usr/share/quickshell/dms/distro
|
||||||
|
rm -rf debian/dms/usr/share/quickshell/dms/assets
|
||||||
|
|
||||||
# Create VERSION file for Quickshell (stable release format)
|
# Create VERSION file for Quickshell (stable release format)
|
||||||
echo "$(BASE_VERSION)" > debian/dms/usr/share/quickshell/dms/VERSION
|
echo "$(BASE_VERSION)" > debian/dms/usr/share/quickshell/dms/VERSION
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,16 @@
|
|||||||
(import path (args // { dmsPkgs = buildDmsPkgs pkgs; }))
|
(import path (args // { dmsPkgs = buildDmsPkgs pkgs; }))
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
mkQmlImportPath =
|
||||||
|
pkgs: qmlPkgs:
|
||||||
|
pkgs.lib.concatStringsSep ":" (map (o: "${o}/${pkgs.qt6.qtbase.qtQmlPrefix}") qmlPkgs);
|
||||||
|
|
||||||
|
qmlPkgs =
|
||||||
|
pkgs: with pkgs.kdePackages; [
|
||||||
|
kirigami.unwrapped
|
||||||
|
sonnet
|
||||||
|
qtmultimedia
|
||||||
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages = forEachSystem (
|
packages = forEachSystem (
|
||||||
@@ -96,7 +106,9 @@
|
|||||||
install -D ${rootSrc}/core/assets/danklogo.svg \
|
install -D ${rootSrc}/core/assets/danklogo.svg \
|
||||||
$out/share/hicolor/scalable/apps/danklogo.svg
|
$out/share/hicolor/scalable/apps/danklogo.svg
|
||||||
|
|
||||||
wrapProgram $out/bin/dms --add-flags "-c $out/share/quickshell/dms"
|
wrapProgram $out/bin/dms \
|
||||||
|
--add-flags "-c $out/share/quickshell/dms" \
|
||||||
|
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs (qmlPkgs pkgs)}"
|
||||||
|
|
||||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||||
$out/lib/systemd/user/dms.service
|
$out/lib/systemd/user/dms.service
|
||||||
@@ -143,15 +155,11 @@
|
|||||||
devShells = forEachSystem (
|
devShells = forEachSystem (
|
||||||
system: pkgs:
|
system: pkgs:
|
||||||
let
|
let
|
||||||
qmlPkgs = [
|
devQmlPkgs = [
|
||||||
quickshell.packages.${system}.default
|
quickshell.packages.${system}.default
|
||||||
|
pkgs.kdePackages.qtdeclarative
|
||||||
]
|
]
|
||||||
++ (with pkgs.kdePackages; [
|
++ (qmlPkgs pkgs);
|
||||||
qtdeclarative
|
|
||||||
kirigami.unwrapped
|
|
||||||
sonnet
|
|
||||||
qtmultimedia
|
|
||||||
]);
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
@@ -164,13 +172,13 @@
|
|||||||
go-tools
|
go-tools
|
||||||
gnumake
|
gnumake
|
||||||
]
|
]
|
||||||
++ qmlPkgs;
|
++ devQmlPkgs;
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
touch quickshell/.qmlls.ini 2>/dev/null
|
touch quickshell/.qmlls.ini 2>/dev/null
|
||||||
'';
|
'';
|
||||||
|
|
||||||
QML2_IMPORT_PATH = pkgs.lib.concatStringsSep ":" (map (o: "${o}/lib/qt-6/qml") qmlPkgs);
|
QML2_IMPORT_PATH = mkQmlImportPath pkgs devQmlPkgs;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
The Dark Knight
|
Spicy Miso
|
||||||
@@ -203,6 +203,10 @@ Singleton {
|
|||||||
"value": "scheme-vibrant",
|
"value": "scheme-vibrant",
|
||||||
"label": "Vibrant",
|
"label": "Vibrant",
|
||||||
"description": I18n.tr("Lively palette with saturated accents.")
|
"description": I18n.tr("Lively palette with saturated accents.")
|
||||||
|
}), ({
|
||||||
|
"value": "scheme-dynamic-contrast",
|
||||||
|
"label": "Dynamic Contrast",
|
||||||
|
"description": I18n.tr("High-contrast palette for strong visual distinction.")
|
||||||
}), ({
|
}), ({
|
||||||
"value": "scheme-content",
|
"value": "scheme-content",
|
||||||
"label": "Content",
|
"label": "Content",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ DankModal {
|
|||||||
layerNamespace: "dms:clipboard"
|
layerNamespace: "dms:clipboard"
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: [clipboardHistoryModal.contentWindow, clipboardHistoryModal.backgroundWindow]
|
windows: [clipboardHistoryModal.contentWindow]
|
||||||
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
|
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,14 +49,12 @@ Item {
|
|||||||
readonly property alias backgroundWindow: backgroundWindow
|
readonly property alias backgroundWindow: backgroundWindow
|
||||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||||
|
|
||||||
readonly property bool useSingleWindow: root.useHyprlandFocusGrab
|
|
||||||
|
|
||||||
signal opened
|
signal opened
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
signal backgroundClicked
|
signal backgroundClicked
|
||||||
|
|
||||||
property bool animationsEnabled: true
|
property bool animationsEnabled: true
|
||||||
readonly property bool useBackgroundWindow: !useSingleWindow
|
readonly property bool useBackgroundWindow: true
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
ModalManager.openModal(root);
|
ModalManager.openModal(root);
|
||||||
@@ -207,7 +205,7 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.useBackgroundWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
const clickX = mouse.x;
|
const clickX = mouse.x;
|
||||||
const clickY = mouse.y;
|
const clickY = mouse.y;
|
||||||
@@ -224,7 +222,7 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||||
visible: root.useBackgroundWindow && root.showBackground && SettingsData.modalDarkenBackground
|
visible: root.showBackground && SettingsData.modalDarkenBackground
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled
|
||||||
@@ -273,19 +271,15 @@ Item {
|
|||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
right: root.useSingleWindow
|
|
||||||
bottom: root.useSingleWindow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.margins {
|
WlrLayershell.margins {
|
||||||
left: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
left: Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
||||||
top: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
top: Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
||||||
right: 0
|
|
||||||
bottom: 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: root.useSingleWindow ? undefined : root.alignedWidth + (shadowBuffer * 2)
|
implicitWidth: root.alignedWidth + (shadowBuffer * 2)
|
||||||
implicitHeight: root.useSingleWindow ? undefined : root.alignedHeight + (shadowBuffer * 2)
|
implicitHeight: root.alignedHeight + (shadowBuffer * 2)
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
@@ -298,48 +292,13 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
|
||||||
z: -2
|
|
||||||
onClicked: root.backgroundClicked()
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: -1
|
|
||||||
color: "black"
|
|
||||||
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
|
||||||
visible: root.useSingleWindow && root.showBackground && SettingsData.modalDarkenBackground
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
enabled: root.animationsEnabled
|
|
||||||
NumberAnimation {
|
|
||||||
duration: root.animationDuration
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: modalContainer
|
||||||
x: root.useSingleWindow ? root.alignedX : shadowBuffer
|
x: shadowBuffer
|
||||||
y: root.useSingleWindow ? root.alignedY : shadowBuffer
|
y: shadowBuffer
|
||||||
|
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.alignedHeight
|
height: root.alignedHeight
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.useSingleWindow
|
|
||||||
hoverEnabled: false
|
|
||||||
acceptedButtons: Qt.AllButtons
|
|
||||||
onPressed: mouse.accepted = true
|
|
||||||
onClicked: mouse.accepted = true
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool slide: root.animationType === "slide"
|
readonly property bool slide: root.animationType === "slide"
|
||||||
readonly property real offsetX: slide ? 15 : 0
|
readonly property real offsetX: slide ? 15 : 0
|
||||||
readonly property real offsetY: slide ? -30 : root.animationOffset
|
readonly property real offsetY: slide ? -30 : root.animationOffset
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ DankModal {
|
|||||||
keepPopoutsOpen: true
|
keepPopoutsOpen: true
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: [root.contentWindow, root.backgroundWindow]
|
windows: [root.contentWindow]
|
||||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ FloatingWindow {
|
|||||||
title: I18n.tr("Settings", "settings window title")
|
title: I18n.tr("Settings", "settings window title")
|
||||||
minimumSize: Qt.size(500, 400)
|
minimumSize: Qt.size(500, 400)
|
||||||
implicitWidth: 800
|
implicitWidth: 800
|
||||||
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
|
implicitHeight: 940
|
||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ DankModal {
|
|||||||
layerNamespace: "dms:spotlight"
|
layerNamespace: "dms:spotlight"
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: [spotlightModal.contentWindow, spotlightModal.backgroundWindow]
|
windows: [spotlightModal.contentWindow]
|
||||||
active: spotlightModal.useHyprlandFocusGrab && spotlightModal.shouldHaveFocus
|
active: spotlightModal.useHyprlandFocusGrab && spotlightModal.shouldHaveFocus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,12 +79,10 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!source) {
|
if (source) {
|
||||||
isInitialized = true;
|
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
|
||||||
return;
|
setWallpaperImmediate(formattedSource);
|
||||||
}
|
}
|
||||||
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,23 +93,22 @@ Variants {
|
|||||||
property bool useNextForEffect: false
|
property bool useNextForEffect: false
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
if (!source || source.startsWith("#")) {
|
const isColor = source.startsWith("#");
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
setWallpaperImmediate("");
|
setWallpaperImmediate("");
|
||||||
return;
|
} else if (isColor) {
|
||||||
|
setWallpaperImmediate("");
|
||||||
|
} else {
|
||||||
|
if (!isInitialized || !currentWallpaper.source) {
|
||||||
|
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source);
|
||||||
|
isInitialized = true;
|
||||||
|
} else if (CompositorService.isNiri && SessionData.isSwitchingMode) {
|
||||||
|
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source);
|
||||||
|
} else {
|
||||||
|
changeWallpaper(source.startsWith("file://") ? source : "file://" + source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
|
|
||||||
|
|
||||||
if (!isInitialized || !currentWallpaper.source) {
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
isInitialized = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (CompositorService.isNiri && SessionData.isSwitchingMode) {
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
changeWallpaper(formattedSource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWallpaperImmediate(newSource) {
|
function setWallpaperImmediate(newSource) {
|
||||||
@@ -123,18 +120,15 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startTransition() {
|
function startTransition() {
|
||||||
|
currentWallpaper.cache = true;
|
||||||
|
nextWallpaper.cache = true;
|
||||||
root.useNextForEffect = true;
|
root.useNextForEffect = true;
|
||||||
root.effectActive = true;
|
root.effectActive = true;
|
||||||
if (srcNext.scheduleUpdate)
|
if (srcNext.scheduleUpdate)
|
||||||
srcNext.scheduleUpdate();
|
srcNext.scheduleUpdate();
|
||||||
transitionDelayTimer.start();
|
Qt.callLater(() => {
|
||||||
}
|
transitionAnimation.start();
|
||||||
|
});
|
||||||
Timer {
|
|
||||||
id: transitionDelayTimer
|
|
||||||
interval: 16
|
|
||||||
repeat: false
|
|
||||||
onTriggered: transitionAnimation.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeWallpaper(newPath) {
|
function changeWallpaper(newPath) {
|
||||||
@@ -149,6 +143,7 @@ Variants {
|
|||||||
currentWallpaper.source = nextWallpaper.source;
|
currentWallpaper.source = nextWallpaper.source;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentWallpaper.source) {
|
if (!currentWallpaper.source) {
|
||||||
setWallpaperImmediate(newPath);
|
setWallpaperImmediate(newPath);
|
||||||
return;
|
return;
|
||||||
@@ -156,8 +151,9 @@ Variants {
|
|||||||
|
|
||||||
nextWallpaper.source = newPath;
|
nextWallpaper.source = newPath;
|
||||||
|
|
||||||
if (nextWallpaper.status === Image.Ready)
|
if (nextWallpaper.status === Image.Ready) {
|
||||||
root.startTransition();
|
root.startTransition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -170,9 +166,9 @@ Variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property int maxTextureSize: 8192
|
property real screenScale: CompositorService.getScreenScale(modelData)
|
||||||
property int textureWidth: Math.min(modelData.width, maxTextureSize)
|
property int physicalWidth: Math.round(modelData.width * screenScale)
|
||||||
property int textureHeight: Math.min(modelData.height, maxTextureSize)
|
property int physicalHeight: Math.round(modelData.height * screenScale)
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: currentWallpaper
|
id: currentWallpaper
|
||||||
@@ -182,7 +178,7 @@ Variants {
|
|||||||
asynchronous: true
|
asynchronous: true
|
||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: true
|
||||||
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
sourceSize: Qt.size(root.physicalWidth, root.physicalHeight)
|
||||||
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
|
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +189,8 @@ Variants {
|
|||||||
opacity: 0
|
opacity: 0
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: false
|
||||||
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
sourceSize: Qt.size(root.physicalWidth, root.physicalHeight)
|
||||||
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
|
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
@@ -213,7 +209,7 @@ Variants {
|
|||||||
live: root.effectActive
|
live: root.effectActive
|
||||||
mipmap: false
|
mipmap: false
|
||||||
recursive: false
|
recursive: false
|
||||||
textureSize: Qt.size(root.textureWidth, root.textureHeight)
|
textureSize: root.effectActive ? Qt.size(root.physicalWidth, root.physicalHeight) : Qt.size(1, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -269,12 +265,19 @@ Variants {
|
|||||||
duration: 1000
|
duration: 1000
|
||||||
easing.type: Easing.InOutCubic
|
easing.type: Easing.InOutCubic
|
||||||
onFinished: {
|
onFinished: {
|
||||||
if (nextWallpaper.source && nextWallpaper.status === Image.Ready)
|
if (nextWallpaper.source && nextWallpaper.status === Image.Ready) {
|
||||||
currentWallpaper.source = nextWallpaper.source;
|
currentWallpaper.source = nextWallpaper.source;
|
||||||
|
}
|
||||||
root.useNextForEffect = false;
|
root.useNextForEffect = false;
|
||||||
nextWallpaper.source = "";
|
Qt.callLater(() => {
|
||||||
root.transitionProgress = 0.0;
|
nextWallpaper.source = "";
|
||||||
root.effectActive = false;
|
Qt.callLater(() => {
|
||||||
|
root.effectActive = false;
|
||||||
|
currentWallpaper.cache = true;
|
||||||
|
nextWallpaper.cache = false;
|
||||||
|
root.transitionProgress = 0.0;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ PluginComponent {
|
|||||||
|
|
||||||
ccDetailContent: Component {
|
ccDetailContent: Component {
|
||||||
VpnDetailContent {
|
VpnDetailContent {
|
||||||
listHeight: 260
|
listHeight: 180
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,13 @@ Item {
|
|||||||
|
|
||||||
function getDetailHeight(section) {
|
function getDetailHeight(section) {
|
||||||
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
|
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
|
||||||
switch (true) {
|
if (section === "wifi")
|
||||||
case section === "wifi":
|
|
||||||
case section === "bluetooth":
|
|
||||||
case section === "builtin_vpn":
|
|
||||||
return Math.min(350, maxAvailable);
|
return Math.min(350, maxAvailable);
|
||||||
case section.startsWith("brightnessSlider_"):
|
if (section === "bluetooth")
|
||||||
|
return Math.min(350, maxAvailable);
|
||||||
|
if (section.startsWith("brightnessSlider_"))
|
||||||
return Math.min(400, maxAvailable);
|
return Math.min(400, maxAvailable);
|
||||||
default:
|
return Math.min(250, maxAvailable);
|
||||||
return Math.min(250, maxAvailable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
|||||||
@@ -1088,7 +1088,6 @@ Item {
|
|||||||
id: layoutComponent
|
id: layoutComponent
|
||||||
|
|
||||||
DWLLayout {
|
DWLLayout {
|
||||||
id: layoutWidget
|
|
||||||
layoutPopupVisible: layoutPopoutLoader.item ? layoutPopoutLoader.item.shouldBeVisible : false
|
layoutPopupVisible: layoutPopoutLoader.item ? layoutPopoutLoader.item.shouldBeVisible : false
|
||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
@@ -1101,19 +1100,14 @@ Item {
|
|||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onToggleLayoutPopup: {
|
onToggleLayoutPopup: {
|
||||||
layoutPopoutLoader.active = true;
|
layoutPopoutLoader.active = true;
|
||||||
if (!layoutPopoutLoader.item)
|
|
||||||
return;
|
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
const effectiveBarConfig = topBarContent.barConfig;
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
||||||
|
if (layoutPopoutLoader.item && layoutPopoutLoader.item.setBarContext) {
|
||||||
if (layoutPopoutLoader.item.setTriggerPosition) {
|
layoutPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
||||||
const globalPos = layoutWidget.mapToGlobal(0, 0);
|
}
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, layoutWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
if (layoutPopoutLoader.item) {
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "center";
|
PopoutManager.requestPopout(layoutPopoutLoader.item, undefined, "layout");
|
||||||
layoutPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PopoutManager.requestPopout(layoutPopoutLoader.item, undefined, "layout");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -552,31 +552,22 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
DankButtonGroup {
|
||||||
width: parent.width
|
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||||
height: profileButtonGroup.height * profileButtonGroup.scale
|
property int currentProfileIndex: {
|
||||||
|
if (typeof PowerProfiles === "undefined")
|
||||||
|
return 1;
|
||||||
|
return profileModel.findIndex(profile => root.isActiveProfile(profile));
|
||||||
|
}
|
||||||
|
|
||||||
DankButtonGroup {
|
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
||||||
id: profileButtonGroup
|
currentIndex: currentProfileIndex
|
||||||
|
selectionMode: "single"
|
||||||
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
property int currentProfileIndex: {
|
onSelectionChanged: (index, selected) => {
|
||||||
if (typeof PowerProfiles === "undefined")
|
if (!selected)
|
||||||
return 1;
|
return;
|
||||||
return profileModel.findIndex(profile => root.isActiveProfile(profile));
|
root.setProfile(profileModel[index]);
|
||||||
}
|
|
||||||
|
|
||||||
scale: Math.min(1, parent.width / implicitWidth)
|
|
||||||
transformOrigin: Item.Center
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
|
||||||
currentIndex: currentProfileIndex
|
|
||||||
selectionMode: "single"
|
|
||||||
onSelectionChanged: (index, selected) => {
|
|
||||||
if (!selected)
|
|
||||||
return;
|
|
||||||
root.setProfile(profileModel[index]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ DankPopout {
|
|||||||
triggerY = y;
|
triggerY = y;
|
||||||
triggerWidth = width;
|
triggerWidth = width;
|
||||||
triggerSection = section;
|
triggerSection = section;
|
||||||
triggerScreen = screen;
|
|
||||||
root.screen = screen;
|
root.screen = screen;
|
||||||
|
|
||||||
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
|
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
|
||||||
@@ -103,8 +102,6 @@ DankPopout {
|
|||||||
screen: triggerScreen
|
screen: triggerScreen
|
||||||
shouldBeVisible: false
|
shouldBeVisible: false
|
||||||
|
|
||||||
onBackgroundClicked: close()
|
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: layoutContent
|
id: layoutContent
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
@@ -246,14 +245,11 @@ Item {
|
|||||||
if (!byApp[key]) {
|
if (!byApp[key]) {
|
||||||
const moddedId = Paths.moddedAppId(keyBase);
|
const moddedId = Paths.moddedAppId(keyBase);
|
||||||
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
|
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
|
||||||
const isQuickshell = keyBase === "org.quickshell";
|
const icon = isSteamApp ? "" : DesktopService.resolveIconPath(moddedId);
|
||||||
const desktopEntry = DesktopEntries.heuristicLookup(keyBase);
|
|
||||||
const icon = isSteamApp ? "" : Paths.getAppIcon(keyBase, desktopEntry);
|
|
||||||
byApp[key] = {
|
byApp[key] = {
|
||||||
"type": "icon",
|
"type": "icon",
|
||||||
"icon": icon,
|
"icon": icon,
|
||||||
"isSteamApp": isSteamApp,
|
"isSteamApp": isSteamApp,
|
||||||
"isQuickshell": isQuickshell,
|
|
||||||
"active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
|
"active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"windowId": w.address || w.id,
|
"windowId": w.address || w.id,
|
||||||
@@ -450,7 +446,6 @@ Item {
|
|||||||
readonly property real padding: Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
readonly property real padding: Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||||
readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
|
readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
|
||||||
readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
|
readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
|
||||||
readonly property real appIconSize: Theme.barIconSize(barThickness, -6)
|
|
||||||
|
|
||||||
function getRealWorkspaces() {
|
function getRealWorkspaces() {
|
||||||
return root.workspaceList.filter(ws => {
|
return root.workspaceList.filter(ws => {
|
||||||
@@ -724,14 +719,14 @@ Item {
|
|||||||
readonly property real iconsExtraWidth: {
|
readonly property real iconsExtraWidth: {
|
||||||
if (!root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
if (!root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
||||||
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
|
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
|
||||||
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
return numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
readonly property real iconsExtraHeight: {
|
readonly property real iconsExtraHeight: {
|
||||||
if (root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
if (root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
||||||
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
|
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
|
||||||
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
return numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -902,7 +897,7 @@ Item {
|
|||||||
Item {
|
Item {
|
||||||
visible: loadedHasIcon && loadedIconData?.type === "icon"
|
visible: loadedHasIcon && loadedIconData?.type === "icon"
|
||||||
width: wsIcon.width + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
width: wsIcon.width + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
||||||
height: root.appIconSize
|
height: 18
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
id: wsIcon
|
id: wsIcon
|
||||||
@@ -917,7 +912,7 @@ Item {
|
|||||||
Item {
|
Item {
|
||||||
visible: loadedHasIcon && loadedIconData?.type === "text"
|
visible: loadedHasIcon && loadedIconData?.type === "text"
|
||||||
width: wsText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
width: wsText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
||||||
height: root.appIconSize
|
height: 18
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: wsText
|
id: wsText
|
||||||
@@ -932,7 +927,7 @@ Item {
|
|||||||
Item {
|
Item {
|
||||||
visible: SettingsData.showWorkspaceIndex && !loadedHasIcon
|
visible: SettingsData.showWorkspaceIndex && !loadedHasIcon
|
||||||
width: wsIndexText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
width: wsIndexText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
||||||
height: root.appIconSize
|
height: 18
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: wsIndexText
|
id: wsIndexText
|
||||||
@@ -949,61 +944,48 @@ Item {
|
|||||||
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
||||||
}
|
}
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: root.appIconSize
|
width: 18
|
||||||
height: root.appIconSize
|
height: 18
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
id: rowAppIcon
|
id: appIcon
|
||||||
|
property var windowId: modelData.windowId
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: modelData.icon
|
source: modelData.icon
|
||||||
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
visible: !modelData.isSteamApp && !modelData.isQuickshell
|
visible: !modelData.isSteamApp
|
||||||
}
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
anchors.fill: parent
|
|
||||||
source: modelData.icon
|
|
||||||
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
||||||
visible: modelData.isQuickshell
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
saturation: 0
|
|
||||||
colorization: 1
|
|
||||||
colorizationColor: isActive ? Theme.primaryContainer : Theme.primary
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: root.appIconSize
|
size: 18
|
||||||
name: "sports_esports"
|
name: "sports_esports"
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
visible: modelData.isSteamApp
|
visible: modelData.isSteamApp
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: rowAppMouseArea
|
id: appMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: isActive
|
enabled: isActive
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const winId = modelData.windowId;
|
if (!appIcon.windowId)
|
||||||
if (!winId)
|
|
||||||
return;
|
return;
|
||||||
if (CompositorService.isHyprland) {
|
if (CompositorService.isHyprland) {
|
||||||
Hyprland.dispatch(`focuswindow address:${winId}`);
|
Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`);
|
||||||
} else if (CompositorService.isNiri) {
|
} else if (CompositorService.isNiri) {
|
||||||
NiriService.focusWindow(winId);
|
NiriService.focusWindow(appIcon.windowId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: modelData.count > 1 && !isActive
|
visible: modelData.count > 1 && !isActive
|
||||||
width: root.appIconSize * 0.67
|
width: 12
|
||||||
height: root.appIconSize * 0.67
|
height: 12
|
||||||
radius: root.appIconSize * 0.33
|
radius: 6
|
||||||
color: "black"
|
color: "black"
|
||||||
border.color: "white"
|
border.color: "white"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
@@ -1014,7 +996,7 @@ Item {
|
|||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: modelData.count
|
text: modelData.count
|
||||||
font.pixelSize: root.appIconSize * 0.44
|
font.pixelSize: 8
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1052,61 +1034,48 @@ Item {
|
|||||||
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
||||||
}
|
}
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: root.appIconSize
|
width: 18
|
||||||
height: root.appIconSize
|
height: 18
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
id: colAppIcon
|
id: appIcon
|
||||||
|
property var windowId: modelData.windowId
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: modelData.icon
|
source: modelData.icon
|
||||||
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
visible: !modelData.isSteamApp && !modelData.isQuickshell
|
visible: !modelData.isSteamApp
|
||||||
}
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
anchors.fill: parent
|
|
||||||
source: modelData.icon
|
|
||||||
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
||||||
visible: modelData.isQuickshell
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
saturation: 0
|
|
||||||
colorization: 1
|
|
||||||
colorizationColor: isActive ? Theme.primaryContainer : Theme.primary
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: root.appIconSize
|
size: 18
|
||||||
name: "sports_esports"
|
name: "sports_esports"
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
visible: modelData.isSteamApp
|
visible: modelData.isSteamApp
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: colAppMouseArea
|
id: appMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: isActive
|
enabled: isActive
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const winId = modelData.windowId;
|
if (!appIcon.windowId)
|
||||||
if (!winId)
|
|
||||||
return;
|
return;
|
||||||
if (CompositorService.isHyprland) {
|
if (CompositorService.isHyprland) {
|
||||||
Hyprland.dispatch(`focuswindow address:${winId}`);
|
Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`);
|
||||||
} else if (CompositorService.isNiri) {
|
} else if (CompositorService.isNiri) {
|
||||||
NiriService.focusWindow(winId);
|
NiriService.focusWindow(appIcon.windowId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: modelData.count > 1 && !isActive
|
visible: modelData.count > 1 && !isActive
|
||||||
width: root.appIconSize * 0.67
|
width: 12
|
||||||
height: root.appIconSize * 0.67
|
height: 12
|
||||||
radius: root.appIconSize * 0.33
|
radius: 6
|
||||||
color: "black"
|
color: "black"
|
||||||
border.color: "white"
|
border.color: "white"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
@@ -1117,7 +1086,7 @@ Item {
|
|||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: modelData.count
|
text: modelData.count
|
||||||
font.pixelSize: root.appIconSize * 0.44
|
font.pixelSize: 8
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ Item {
|
|||||||
DankTextField {
|
DankTextField {
|
||||||
id: searchField
|
id: searchField
|
||||||
width: parent.width - addButton.width - Theme.spacingM
|
width: parent.width - addButton.width - Theme.spacingM
|
||||||
height: Math.round(Theme.fontSizeMedium * 3)
|
height: 44
|
||||||
placeholderText: I18n.tr("Search keybinds...")
|
placeholderText: I18n.tr("Search keybinds...")
|
||||||
leftIconName: "search"
|
leftIconName: "search"
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
@@ -240,8 +240,8 @@ Item {
|
|||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: addButton
|
id: addButton
|
||||||
width: searchField.height
|
width: 44
|
||||||
height: searchField.height
|
height: 44
|
||||||
circular: false
|
circular: false
|
||||||
iconName: "add"
|
iconName: "add"
|
||||||
iconSize: Theme.iconSize
|
iconSize: Theme.iconSize
|
||||||
@@ -331,7 +331,7 @@ Item {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: fixButton
|
id: fixButton
|
||||||
width: fixButtonText.implicitWidth + Theme.spacingL * 2
|
width: fixButtonText.implicitWidth + Theme.spacingL * 2
|
||||||
height: Math.round(Theme.fontSizeMedium * 2.5)
|
height: 36
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
visible: warningBox.showError || warningBox.showSetup
|
visible: warningBox.showError || warningBox.showSetup
|
||||||
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
|
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
|
||||||
@@ -382,10 +382,9 @@ Item {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
readonly property real chipHeight: allChip.implicitHeight + Theme.spacingM
|
|
||||||
width: allChip.implicitWidth + Theme.spacingL
|
width: allChip.implicitWidth + Theme.spacingL
|
||||||
height: chipHeight
|
height: 32
|
||||||
radius: chipHeight / 2
|
radius: 16
|
||||||
color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest
|
color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -413,10 +412,9 @@ Item {
|
|||||||
required property string modelData
|
required property string modelData
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
readonly property real chipHeight: catText.implicitHeight + Theme.spacingM
|
|
||||||
width: catText.implicitWidth + Theme.spacingL
|
width: catText.implicitWidth + Theme.spacingL
|
||||||
height: chipHeight
|
height: 32
|
||||||
radius: chipHeight / 2
|
radius: 16
|
||||||
color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest)
|
color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest)
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|||||||
@@ -57,11 +57,15 @@ Variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onTransitionTypeChanged: {
|
onTransitionTypeChanged: {
|
||||||
if (transitionType !== "random") {
|
if (transitionType === "random") {
|
||||||
|
if (SessionData.includedTransitions.length === 0) {
|
||||||
|
actualTransitionType = "none";
|
||||||
|
} else {
|
||||||
|
actualTransitionType = SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
actualTransitionType = transitionType;
|
actualTransitionType = transitionType;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
actualTransitionType = SessionData.includedTransitions.length === 0 ? "none" : SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property real transitionProgress: 0
|
property real transitionProgress: 0
|
||||||
@@ -104,33 +108,30 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!source) {
|
if (source) {
|
||||||
isInitialized = true;
|
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
|
||||||
return;
|
setWallpaperImmediate(formattedSource);
|
||||||
}
|
}
|
||||||
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
if (!source || source.startsWith("#")) {
|
const isColor = source.startsWith("#");
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
setWallpaperImmediate("");
|
setWallpaperImmediate("");
|
||||||
return;
|
} else if (isColor) {
|
||||||
|
setWallpaperImmediate("");
|
||||||
|
} else {
|
||||||
|
if (!isInitialized || !currentWallpaper.source) {
|
||||||
|
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source);
|
||||||
|
isInitialized = true;
|
||||||
|
} else if (CompositorService.isNiri && SessionData.isSwitchingMode) {
|
||||||
|
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source);
|
||||||
|
} else {
|
||||||
|
changeWallpaper(source.startsWith("file://") ? source : "file://" + source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
|
|
||||||
|
|
||||||
if (!isInitialized || !currentWallpaper.source) {
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
isInitialized = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (CompositorService.isNiri && SessionData.isSwitchingMode) {
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
changeWallpaper(formattedSource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWallpaperImmediate(newSource) {
|
function setWallpaperImmediate(newSource) {
|
||||||
@@ -142,6 +143,8 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startTransition() {
|
function startTransition() {
|
||||||
|
currentWallpaper.cache = true;
|
||||||
|
nextWallpaper.cache = true;
|
||||||
currentWallpaper.layer.enabled = true;
|
currentWallpaper.layer.enabled = true;
|
||||||
nextWallpaper.layer.enabled = true;
|
nextWallpaper.layer.enabled = true;
|
||||||
root.useNextForEffect = true;
|
root.useNextForEffect = true;
|
||||||
@@ -150,14 +153,9 @@ Variants {
|
|||||||
srcCurrent.scheduleUpdate();
|
srcCurrent.scheduleUpdate();
|
||||||
if (srcNext.scheduleUpdate)
|
if (srcNext.scheduleUpdate)
|
||||||
srcNext.scheduleUpdate();
|
srcNext.scheduleUpdate();
|
||||||
transitionDelayTimer.start();
|
Qt.callLater(() => {
|
||||||
}
|
transitionAnimation.start();
|
||||||
|
});
|
||||||
Timer {
|
|
||||||
id: transitionDelayTimer
|
|
||||||
interval: 16
|
|
||||||
repeat: false
|
|
||||||
onTriggered: transitionAnimation.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeWallpaper(newPath, force) {
|
function changeWallpaper(newPath, force) {
|
||||||
@@ -165,17 +163,23 @@ Variants {
|
|||||||
return;
|
return;
|
||||||
if (!newPath || newPath.startsWith("#"))
|
if (!newPath || newPath.startsWith("#"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (root.transitioning || root.effectActive) {
|
if (root.transitioning || root.effectActive) {
|
||||||
root.pendingWallpaper = newPath;
|
root.pendingWallpaper = newPath;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentWallpaper.source) {
|
if (!currentWallpaper.source) {
|
||||||
setWallpaperImmediate(newPath);
|
setWallpaperImmediate(newPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.transitionType === "random") {
|
if (root.transitionType === "random") {
|
||||||
root.actualTransitionType = SessionData.includedTransitions.length === 0 ? "none" : SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)];
|
if (SessionData.includedTransitions.length === 0) {
|
||||||
|
root.actualTransitionType = "none";
|
||||||
|
} else {
|
||||||
|
root.actualTransitionType = SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.actualTransitionType === "none") {
|
if (root.actualTransitionType === "none") {
|
||||||
@@ -183,26 +187,21 @@ Variants {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (root.actualTransitionType) {
|
if (root.actualTransitionType === "wipe") {
|
||||||
case "wipe":
|
|
||||||
root.wipeDirection = Math.random() * 4;
|
root.wipeDirection = Math.random() * 4;
|
||||||
break;
|
} else if (root.actualTransitionType === "disc" || root.actualTransitionType === "pixelate" || root.actualTransitionType === "portal") {
|
||||||
case "disc":
|
|
||||||
case "pixelate":
|
|
||||||
case "portal":
|
|
||||||
root.discCenterX = Math.random();
|
root.discCenterX = Math.random();
|
||||||
root.discCenterY = Math.random();
|
root.discCenterY = Math.random();
|
||||||
break;
|
} else if (root.actualTransitionType === "stripes") {
|
||||||
case "stripes":
|
|
||||||
root.stripesCount = Math.round(Math.random() * 20 + 4);
|
root.stripesCount = Math.round(Math.random() * 20 + 4);
|
||||||
root.stripesAngle = Math.random() * 360;
|
root.stripesAngle = Math.random() * 360;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextWallpaper.source = newPath;
|
nextWallpaper.source = newPath;
|
||||||
|
|
||||||
if (nextWallpaper.status === Image.Ready)
|
if (nextWallpaper.status === Image.Ready) {
|
||||||
root.startTransition();
|
root.startTransition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -215,10 +214,9 @@ Variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property int maxTextureSize: 8192
|
|
||||||
property real screenScale: CompositorService.getScreenScale(modelData)
|
property real screenScale: CompositorService.getScreenScale(modelData)
|
||||||
property int textureWidth: Math.min(Math.round(modelData.width * screenScale), maxTextureSize)
|
property int physicalWidth: Math.round(modelData.width * screenScale)
|
||||||
property int textureHeight: Math.min(Math.round(modelData.height * screenScale), maxTextureSize)
|
property int physicalHeight: Math.round(modelData.height * screenScale)
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: currentWallpaper
|
id: currentWallpaper
|
||||||
@@ -229,7 +227,7 @@ Variants {
|
|||||||
asynchronous: true
|
asynchronous: true
|
||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: true
|
||||||
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
sourceSize: Qt.size(root.physicalWidth, root.physicalHeight)
|
||||||
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
|
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,8 +239,8 @@ Variants {
|
|||||||
layer.enabled: false
|
layer.enabled: false
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: false
|
||||||
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
sourceSize: Qt.size(root.physicalWidth, root.physicalHeight)
|
||||||
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
|
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
@@ -265,7 +263,7 @@ Variants {
|
|||||||
live: root.effectActive
|
live: root.effectActive
|
||||||
mipmap: false
|
mipmap: false
|
||||||
recursive: false
|
recursive: false
|
||||||
textureSize: Qt.size(root.textureWidth, root.textureHeight)
|
textureSize: root.effectActive ? Qt.size(root.physicalWidth, root.physicalHeight) : Qt.size(1, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderEffectSource {
|
ShaderEffectSource {
|
||||||
@@ -275,7 +273,7 @@ Variants {
|
|||||||
live: root.effectActive
|
live: root.effectActive
|
||||||
mipmap: false
|
mipmap: false
|
||||||
recursive: false
|
recursive: false
|
||||||
textureSize: Qt.size(root.textureWidth, root.textureHeight)
|
textureSize: root.effectActive ? Qt.size(root.physicalWidth, root.physicalHeight) : Qt.size(1, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -299,9 +297,8 @@ Variants {
|
|||||||
id: effectLoader
|
id: effectLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.effectActive
|
active: root.effectActive
|
||||||
|
sourceComponent: {
|
||||||
function getTransitionComponent(type) {
|
switch (root.actualTransitionType) {
|
||||||
switch (type) {
|
|
||||||
case "fade":
|
case "fade":
|
||||||
return fadeComp;
|
return fadeComp;
|
||||||
case "wipe":
|
case "wipe":
|
||||||
@@ -320,8 +317,6 @@ Variants {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceComponent: getTransitionComponent(root.actualTransitionType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
@@ -496,13 +491,17 @@ Variants {
|
|||||||
root.transitionProgress = 0.0;
|
root.transitionProgress = 0.0;
|
||||||
currentWallpaper.layer.enabled = false;
|
currentWallpaper.layer.enabled = false;
|
||||||
nextWallpaper.layer.enabled = false;
|
nextWallpaper.layer.enabled = false;
|
||||||
|
currentWallpaper.cache = true;
|
||||||
|
nextWallpaper.cache = false;
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
|
|
||||||
if (!root.pendingWallpaper)
|
if (root.pendingWallpaper) {
|
||||||
return;
|
var pending = root.pendingWallpaper;
|
||||||
var pending = root.pendingWallpaper;
|
root.pendingWallpaper = "";
|
||||||
root.pendingWallpaper = "";
|
Qt.callLater(() => {
|
||||||
Qt.callLater(() => root.changeWallpaper(pending, true));
|
root.changeWallpaper(pending, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Singleton {
|
|||||||
id: cavaProcess
|
id: cavaProcess
|
||||||
|
|
||||||
running: root.cavaAvailable && root.refCount > 0
|
running: root.cavaAvailable && root.refCount > 0
|
||||||
command: ["sh", "-c", "printf '[general]\\nframerate=25\\nbars=6\\nautosens=0\\nsensitivity=30\\nlower_cutoff_freq=50\\nhigher_cutoff_freq=12000\\n[input]\\nsample_rate=48000\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nchannels=mono\\nmono_option=average\\n[smoothing]\\nnoise_reduction=35\\nintegral=90\\ngravity=95\\nignore=2\\nmonstercat=1.5' | cava -p /dev/stdin"]
|
command: ["sh", "-c", "printf '[general]\\nframerate=25\\nbars=6\\nautosens=0\\nsensitivity=30\\nlower_cutoff_freq=50\\nhigher_cutoff_freq=12000\\n[input]\\nmethod=pipewire\\nsource=auto\\nsample_rate=48000\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nchannels=mono\\nmono_option=average\\n[smoothing]\\nnoise_reduction=35\\nintegral=90\\ngravity=95\\nignore=2\\nmonstercat=1.5' | cava -p /dev/stdin"]
|
||||||
|
|
||||||
onRunningChanged: {
|
onRunningChanged: {
|
||||||
if (!running) {
|
if (!running) {
|
||||||
|
|||||||
@@ -314,8 +314,7 @@ Singleton {
|
|||||||
const keyData = {
|
const keyData = {
|
||||||
key: bind.key || "",
|
key: bind.key || "",
|
||||||
source: bind.source || "config",
|
source: bind.source || "config",
|
||||||
isOverride: bind.source === "dms",
|
isOverride: bind.source === "dms"
|
||||||
cooldownMs: bind.cooldownMs || 0
|
|
||||||
};
|
};
|
||||||
if (actionMap[action]) {
|
if (actionMap[action]) {
|
||||||
actionMap[action].keys.push(keyData);
|
actionMap[action].keys.push(keyData);
|
||||||
@@ -379,8 +378,6 @@ Singleton {
|
|||||||
const cmd = ["dms", "keybinds", "set", currentProvider, bindData.key, bindData.action, "--desc", bindData.desc || ""];
|
const cmd = ["dms", "keybinds", "set", currentProvider, bindData.key, bindData.action, "--desc", bindData.desc || ""];
|
||||||
if (originalKey && originalKey !== bindData.key)
|
if (originalKey && originalKey !== bindData.key)
|
||||||
cmd.push("--replace-key", originalKey);
|
cmd.push("--replace-key", originalKey);
|
||||||
if (bindData.cooldownMs > 0)
|
|
||||||
cmd.push("--cooldown-ms", String(bindData.cooldownMs));
|
|
||||||
saveProcess.command = cmd;
|
saveProcess.command = cmd;
|
||||||
saveProcess.running = true;
|
saveProcess.running = true;
|
||||||
bindSaved(bindData.key);
|
bindSaved(bindData.key);
|
||||||
|
|||||||
@@ -66,19 +66,6 @@ Singleton {
|
|||||||
onTriggered: root.doGenerateNiriLayoutConfig()
|
onTriggered: root.doGenerateNiriLayoutConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
property int _lastGapValue: -1
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onBarConfigsChanged() {
|
|
||||||
const newGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
|
|
||||||
if (newGaps === root._lastGapValue)
|
|
||||||
return;
|
|
||||||
root._lastGapValue = newGaps;
|
|
||||||
generateNiriLayoutConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: validateProcess
|
id: validateProcess
|
||||||
command: ["niri", "validate"]
|
command: ["niri", "validate"]
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
v1.0.3
|
v1.2-unstable
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ StyledRect {
|
|||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if (activeFocus) {
|
if (activeFocus) {
|
||||||
textInput.forceActiveFocus();
|
textInput.forceActiveFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,26 +53,26 @@ StyledRect {
|
|||||||
signal focusStateChanged(bool hasFocus)
|
signal focusStateChanged(bool hasFocus)
|
||||||
|
|
||||||
function getActiveFocus() {
|
function getActiveFocus() {
|
||||||
return textInput.activeFocus;
|
return textInput.activeFocus
|
||||||
}
|
}
|
||||||
function setFocus(value) {
|
function setFocus(value) {
|
||||||
textInput.focus = value;
|
textInput.focus = value
|
||||||
}
|
}
|
||||||
function forceActiveFocus() {
|
function forceActiveFocus() {
|
||||||
textInput.forceActiveFocus();
|
textInput.forceActiveFocus()
|
||||||
}
|
}
|
||||||
function selectAll() {
|
function selectAll() {
|
||||||
textInput.selectAll();
|
textInput.selectAll()
|
||||||
}
|
}
|
||||||
function clear() {
|
function clear() {
|
||||||
textInput.clear();
|
textInput.clear()
|
||||||
}
|
}
|
||||||
function insertText(str) {
|
function insertText(str) {
|
||||||
textInput.insert(textInput.cursorPosition, str);
|
textInput.insert(textInput.cursorPosition, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
width: 200
|
width: 200
|
||||||
height: Math.round(Theme.fontSizeMedium * 3.4)
|
height: 48
|
||||||
radius: cornerRadius
|
radius: cornerRadius
|
||||||
color: backgroundColor
|
color: backgroundColor
|
||||||
border.color: textInput.activeFocus ? focusedBorderColor : normalBorderColor
|
border.color: textInput.activeFocus ? focusedBorderColor : normalBorderColor
|
||||||
@@ -111,30 +112,30 @@ StyledRect {
|
|||||||
onActiveFocusChanged: root.focusStateChanged(activeFocus)
|
onActiveFocusChanged: root.focusStateChanged(activeFocus)
|
||||||
Keys.forwardTo: root.keyForwardTargets
|
Keys.forwardTo: root.keyForwardTargets
|
||||||
Keys.onLeftPressed: event => {
|
Keys.onLeftPressed: event => {
|
||||||
if (root.ignoreLeftRightKeys) {
|
if (root.ignoreLeftRightKeys) {
|
||||||
event.accepted = true;
|
event.accepted = true
|
||||||
} else {
|
} else {
|
||||||
// Allow normal TextInput cursor movement
|
// Allow normal TextInput cursor movement
|
||||||
event.accepted = false;
|
event.accepted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onRightPressed: event => {
|
Keys.onRightPressed: event => {
|
||||||
if (root.ignoreLeftRightKeys) {
|
if (root.ignoreLeftRightKeys) {
|
||||||
event.accepted = true;
|
event.accepted = true
|
||||||
} else {
|
} else {
|
||||||
event.accepted = false;
|
event.accepted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
||||||
event.accepted = false;
|
event.accepted = false
|
||||||
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||||
if (root.keyForwardTargets[i]) {
|
if (root.keyForwardTargets[i]) {
|
||||||
root.keyForwardTargets[i].Keys.pressed(event);
|
root.keyForwardTargets[i].Keys.pressed(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -170,7 +171,7 @@ StyledRect {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
textInput.text = "";
|
textInput.text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ Item {
|
|||||||
property string editKey: ""
|
property string editKey: ""
|
||||||
property string editAction: ""
|
property string editAction: ""
|
||||||
property string editDesc: ""
|
property string editDesc: ""
|
||||||
property int editCooldownMs: 0
|
|
||||||
property int _savedCooldownMs: -1
|
|
||||||
property bool hasChanges: false
|
property bool hasChanges: false
|
||||||
property string _actionType: ""
|
property string _actionType: ""
|
||||||
property bool addingNewKey: false
|
property bool addingNewKey: false
|
||||||
@@ -54,12 +52,6 @@ Item {
|
|||||||
readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : []
|
readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : []
|
||||||
readonly property bool hasConflict: _conflicts.length > 0
|
readonly property bool hasConflict: _conflicts.length > 0
|
||||||
|
|
||||||
readonly property real _inputHeight: Math.round(Theme.fontSizeMedium * 3)
|
|
||||||
readonly property real _chipHeight: Math.round(Theme.fontSizeSmall * 2.3)
|
|
||||||
readonly property real _buttonHeight: Math.round(Theme.fontSizeMedium * 2.3)
|
|
||||||
readonly property real _keysColumnWidth: Math.round(Theme.fontSizeSmall * 12)
|
|
||||||
readonly property real _labelWidth: Math.round(Theme.fontSizeSmall * 5)
|
|
||||||
|
|
||||||
signal toggleExpand
|
signal toggleExpand
|
||||||
signal saveBind(string originalKey, var newData)
|
signal saveBind(string originalKey, var newData)
|
||||||
signal removeBind(string key)
|
signal removeBind(string key)
|
||||||
@@ -98,12 +90,6 @@ Item {
|
|||||||
editKey = keyToFind;
|
editKey = keyToFind;
|
||||||
editAction = bindData.action || "";
|
editAction = bindData.action || "";
|
||||||
editDesc = bindData.desc || "";
|
editDesc = bindData.desc || "";
|
||||||
if (_savedCooldownMs >= 0) {
|
|
||||||
editCooldownMs = _savedCooldownMs;
|
|
||||||
_savedCooldownMs = -1;
|
|
||||||
} else {
|
|
||||||
editCooldownMs = keys[i].cooldownMs || 0;
|
|
||||||
}
|
|
||||||
hasChanges = false;
|
hasChanges = false;
|
||||||
_actionType = Actions.getActionType(editAction);
|
_actionType = Actions.getActionType(editAction);
|
||||||
useCustomCompositor = _actionType === "compositor" && editAction && !Actions.isKnownCompositorAction(editAction);
|
useCustomCompositor = _actionType === "compositor" && editAction && !Actions.isKnownCompositorAction(editAction);
|
||||||
@@ -123,7 +109,6 @@ Item {
|
|||||||
editKey = editingKeyIndex >= 0 ? keys[editingKeyIndex].key : "";
|
editKey = editingKeyIndex >= 0 ? keys[editingKeyIndex].key : "";
|
||||||
editAction = bindData.action || "";
|
editAction = bindData.action || "";
|
||||||
editDesc = bindData.desc || "";
|
editDesc = bindData.desc || "";
|
||||||
editCooldownMs = editingKeyIndex >= 0 ? (keys[editingKeyIndex].cooldownMs || 0) : 0;
|
|
||||||
hasChanges = false;
|
hasChanges = false;
|
||||||
_actionType = Actions.getActionType(editAction);
|
_actionType = Actions.getActionType(editAction);
|
||||||
useCustomCompositor = _actionType === "compositor" && editAction && !Actions.isKnownCompositorAction(editAction);
|
useCustomCompositor = _actionType === "compositor" && editAction && !Actions.isKnownCompositorAction(editAction);
|
||||||
@@ -142,7 +127,6 @@ Item {
|
|||||||
addingNewKey = false;
|
addingNewKey = false;
|
||||||
editingKeyIndex = index;
|
editingKeyIndex = index;
|
||||||
editKey = keys[index].key;
|
editKey = keys[index].key;
|
||||||
editCooldownMs = keys[index].cooldownMs || 0;
|
|
||||||
hasChanges = false;
|
hasChanges = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,11 +137,8 @@ Item {
|
|||||||
editAction = changes.action;
|
editAction = changes.action;
|
||||||
if (changes.desc !== undefined)
|
if (changes.desc !== undefined)
|
||||||
editDesc = changes.desc;
|
editDesc = changes.desc;
|
||||||
if (changes.cooldownMs !== undefined)
|
|
||||||
editCooldownMs = changes.cooldownMs;
|
|
||||||
const origKey = editingKeyIndex >= 0 && editingKeyIndex < keys.length ? keys[editingKeyIndex].key : "";
|
const origKey = editingKeyIndex >= 0 && editingKeyIndex < keys.length ? keys[editingKeyIndex].key : "";
|
||||||
const origCooldown = editingKeyIndex >= 0 && editingKeyIndex < keys.length ? (keys[editingKeyIndex].cooldownMs || 0) : 0;
|
hasChanges = editKey !== origKey || editAction !== (bindData.action || "") || editDesc !== (bindData.desc || "");
|
||||||
hasChanges = editKey !== origKey || editAction !== (bindData.action || "") || editDesc !== (bindData.desc || "") || editCooldownMs !== origCooldown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function canSave() {
|
function canSave() {
|
||||||
@@ -175,12 +156,10 @@ Item {
|
|||||||
let desc = editDesc;
|
let desc = editDesc;
|
||||||
if (expandedLoader.item?.currentTitle !== undefined)
|
if (expandedLoader.item?.currentTitle !== undefined)
|
||||||
desc = expandedLoader.item.currentTitle;
|
desc = expandedLoader.item.currentTitle;
|
||||||
_savedCooldownMs = editCooldownMs;
|
|
||||||
saveBind(origKey, {
|
saveBind(origKey, {
|
||||||
key: editKey,
|
key: editKey,
|
||||||
action: editAction,
|
action: editAction,
|
||||||
desc: desc,
|
desc: desc
|
||||||
cooldownMs: editCooldownMs
|
|
||||||
});
|
});
|
||||||
hasChanges = false;
|
hasChanges = false;
|
||||||
addingNewKey = false;
|
addingNewKey = false;
|
||||||
@@ -229,7 +208,7 @@ Item {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: collapsedRect
|
id: collapsedRect
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: Math.max(root._inputHeight + Theme.spacingM, keysColumn.implicitHeight + Theme.spacingM * 2)
|
height: Math.max(52, keysColumn.implicitHeight + Theme.spacingM * 2)
|
||||||
radius: root.isExpanded ? 0 : Theme.cornerRadius
|
radius: root.isExpanded ? 0 : Theme.cornerRadius
|
||||||
topLeftRadius: Theme.cornerRadius
|
topLeftRadius: Theme.cornerRadius
|
||||||
topRightRadius: Theme.cornerRadius
|
topRightRadius: Theme.cornerRadius
|
||||||
@@ -246,7 +225,7 @@ Item {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: keysColumn
|
id: keysColumn
|
||||||
Layout.preferredWidth: root._keysColumnWidth
|
Layout.preferredWidth: 140
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
@@ -259,9 +238,9 @@ Item {
|
|||||||
|
|
||||||
property bool isSelected: root.isExpanded && root.editingKeyIndex === index && !root.addingNewKey
|
property bool isSelected: root.isExpanded && root.editingKeyIndex === index && !root.addingNewKey
|
||||||
|
|
||||||
width: root._keysColumnWidth
|
width: 140
|
||||||
height: root._chipHeight
|
height: 28
|
||||||
radius: root._chipHeight / 4
|
radius: 6
|
||||||
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -338,7 +317,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "warning"
|
name: "warning"
|
||||||
size: Theme.iconSizeSmall
|
size: 14
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
visible: root.hasConfigConflict
|
visible: root.hasConfigConflict
|
||||||
}
|
}
|
||||||
@@ -358,7 +337,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: root.isExpanded ? "expand_less" : "expand_more"
|
name: root.isExpanded ? "expand_less" : "expand_more"
|
||||||
size: Theme.iconSize - 4
|
size: 20
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
@@ -366,7 +345,7 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: root._keysColumnWidth + Theme.spacingM * 2
|
anchors.leftMargin: 140 + Theme.spacingM * 2
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: root.toggleExpand()
|
onClicked: root.toggleExpand()
|
||||||
}
|
}
|
||||||
@@ -426,7 +405,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "warning"
|
name: "warning"
|
||||||
size: Theme.iconSizeSmall
|
size: 16
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +446,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
@@ -484,8 +463,8 @@ Item {
|
|||||||
property bool isSelected: root.editingKeyIndex === index && !root.addingNewKey
|
property bool isSelected: root.editingKeyIndex === index && !root.addingNewKey
|
||||||
|
|
||||||
width: editKeyChipText.implicitWidth + Theme.spacingM
|
width: editKeyChipText.implicitWidth + Theme.spacingM
|
||||||
height: root._chipHeight
|
height: 28
|
||||||
radius: root._chipHeight / 4
|
radius: 6
|
||||||
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -515,9 +494,9 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: root._chipHeight
|
width: 28
|
||||||
height: root._chipHeight
|
height: 28
|
||||||
radius: root._chipHeight / 4
|
radius: 6
|
||||||
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
||||||
visible: !root.isNew
|
visible: !root.isNew
|
||||||
|
|
||||||
@@ -529,7 +508,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "add"
|
name: "add"
|
||||||
size: Theme.iconSizeSmall
|
size: 16
|
||||||
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -554,13 +533,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
id: captureScope
|
id: captureScope
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
focus: root.recording
|
focus: root.recording
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -602,12 +581,12 @@ Item {
|
|||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: recordBtn
|
id: recordBtn
|
||||||
width: root._chipHeight
|
width: 28
|
||||||
height: root._chipHeight
|
height: 28
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
circular: false
|
circular: false
|
||||||
iconName: root.recording ? "close" : "radio_button_checked"
|
iconName: root.recording ? "close" : "radio_button_checked"
|
||||||
iconSize: Theme.iconSizeSmall
|
iconSize: 16
|
||||||
iconColor: root.recording ? Theme.error : Theme.primary
|
iconColor: root.recording ? Theme.error : Theme.primary
|
||||||
onClicked: root.recording ? root.stopRecording() : root.startRecording()
|
onClicked: root.recording ? root.stopRecording() : root.startRecording()
|
||||||
}
|
}
|
||||||
@@ -709,8 +688,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: root._inputHeight
|
Layout.preferredWidth: 40
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
||||||
visible: root.keys.length === 1 && !root.isNew
|
visible: root.keys.length === 1 && !root.isNew
|
||||||
@@ -723,7 +702,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "add"
|
name: "add"
|
||||||
size: Theme.iconSizeSmall + 2
|
size: 18
|
||||||
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -742,11 +721,11 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: root.hasConflict
|
visible: root.hasConflict
|
||||||
Layout.leftMargin: root._labelWidth + Theme.spacingM
|
Layout.leftMargin: 60 + Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "warning"
|
name: "warning"
|
||||||
size: Theme.iconSizeSmall
|
size: 16
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,7 +747,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -791,7 +770,7 @@ Item {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root._buttonHeight
|
Layout.preferredHeight: 36
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: root._actionType === modelData.id ? Theme.surfaceContainerHighest : Theme.surfaceContainer
|
color: root._actionType === modelData.id ? Theme.surfaceContainerHighest : Theme.surfaceContainer
|
||||||
border.color: root._actionType === modelData.id ? Theme.outline : (typeArea.containsMouse ? Theme.outlineVariant : "transparent")
|
border.color: root._actionType === modelData.id ? Theme.outline : (typeArea.containsMouse ? Theme.outlineVariant : "transparent")
|
||||||
@@ -803,7 +782,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: typeDelegate.modelData.icon
|
name: typeDelegate.modelData.icon
|
||||||
size: Theme.iconSizeSmall
|
size: 16
|
||||||
color: root._actionType === typeDelegate.modelData.id ? Theme.surfaceText : Theme.surfaceVariantText
|
color: root._actionType === typeDelegate.modelData.id ? Theme.surfaceText : Theme.surfaceVariantText
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,7 +854,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
@@ -919,14 +898,14 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
visible: dmsArgsRow.hasAmountArg
|
visible: dmsArgsRow.hasAmountArg
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: dmsAmountField
|
id: dmsAmountField
|
||||||
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 5.5)
|
Layout.preferredWidth: 80
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
placeholderText: "5"
|
placeholderText: "5"
|
||||||
visible: dmsArgsRow.hasAmountArg
|
visible: dmsArgsRow.hasAmountArg
|
||||||
|
|
||||||
@@ -967,14 +946,14 @@ Item {
|
|||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.leftMargin: dmsArgsRow.hasAmountArg ? Theme.spacingM : 0
|
Layout.leftMargin: dmsArgsRow.hasAmountArg ? Theme.spacingM : 0
|
||||||
Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : root._labelWidth
|
Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : 60
|
||||||
visible: dmsArgsRow.hasDeviceArg
|
visible: dmsArgsRow.hasDeviceArg
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: dmsDeviceField
|
id: dmsDeviceField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
placeholderText: I18n.tr("leave empty for default")
|
placeholderText: I18n.tr("leave empty for default")
|
||||||
visible: dmsArgsRow.hasDeviceArg
|
visible: dmsArgsRow.hasDeviceArg
|
||||||
|
|
||||||
@@ -1012,7 +991,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
visible: dmsArgsRow.hasTabArg
|
visible: dmsArgsRow.hasTabArg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,12 +1049,12 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
id: compositorCatDropdown
|
id: compositorCatDropdown
|
||||||
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 8.5)
|
Layout.preferredWidth: 120
|
||||||
compactMode: true
|
compactMode: true
|
||||||
currentValue: {
|
currentValue: {
|
||||||
const base = root.editAction.split(" ")[0];
|
const base = root.editAction.split(" ")[0];
|
||||||
@@ -1114,8 +1093,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: root._inputHeight
|
Layout.preferredWidth: 40
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariant
|
color: Theme.surfaceVariant
|
||||||
|
|
||||||
@@ -1127,7 +1106,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "edit"
|
name: "edit"
|
||||||
size: Theme.iconSizeSmall + 2
|
size: 18
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -1156,7 +1135,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -1166,7 +1145,7 @@ Item {
|
|||||||
DankTextField {
|
DankTextField {
|
||||||
id: argValueField
|
id: argValueField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
visible: {
|
visible: {
|
||||||
const cfg = optionsRow.argConfig;
|
const cfg = optionsRow.argConfig;
|
||||||
if (!cfg?.config?.args)
|
if (!cfg?.config?.args)
|
||||||
@@ -1314,13 +1293,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: customCompositorField
|
id: customCompositorField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10")
|
placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10")
|
||||||
text: root._actionType === "compositor" ? root.editAction : ""
|
text: root._actionType === "compositor" ? root.editAction : ""
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
@@ -1333,8 +1312,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: root._inputHeight
|
Layout.preferredWidth: 40
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariant
|
color: Theme.surfaceVariant
|
||||||
|
|
||||||
@@ -1346,7 +1325,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "list"
|
name: "list"
|
||||||
size: Theme.iconSizeSmall + 2
|
size: 18
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -1377,13 +1356,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: spawnTextField
|
id: spawnTextField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
placeholderText: I18n.tr("e.g., firefox, kitty --title foo")
|
placeholderText: I18n.tr("e.g., firefox, kitty --title foo")
|
||||||
readonly property var _parsed: root._actionType === "spawn" ? Actions.parseSpawnCommand(root.editAction) : null
|
readonly property var _parsed: root._actionType === "spawn" ? Actions.parseSpawnCommand(root.editAction) : null
|
||||||
text: _parsed ? (_parsed.command + " " + _parsed.args.join(" ")).trim() : ""
|
text: _parsed ? (_parsed.command + " " + _parsed.args.join(" ")).trim() : ""
|
||||||
@@ -1409,13 +1388,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: shellTextField
|
id: shellTextField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
placeholderText: I18n.tr("e.g., notify-send 'Hello' && sleep 1")
|
placeholderText: I18n.tr("e.g., notify-send 'Hello' && sleep 1")
|
||||||
text: root._actionType === "shell" ? Actions.parseShellCommand(root.editAction) : ""
|
text: root._actionType === "shell" ? Actions.parseShellCommand(root.editAction) : ""
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
@@ -1437,13 +1416,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: root._labelWidth
|
Layout.preferredWidth: 60
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: titleField
|
id: titleField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root._inputHeight
|
Layout.preferredHeight: 40
|
||||||
placeholderText: I18n.tr("Hotkey overlay title (optional)")
|
placeholderText: I18n.tr("Hotkey overlay title (optional)")
|
||||||
text: root.editDesc
|
text: root.editDesc
|
||||||
onTextChanged: root.updateEdit({
|
onTextChanged: root.updateEdit({
|
||||||
@@ -1452,57 +1431,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Cooldown")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
Layout.preferredWidth: root._labelWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: cooldownField
|
|
||||||
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 7)
|
|
||||||
Layout.preferredHeight: root._inputHeight
|
|
||||||
placeholderText: "0"
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root
|
|
||||||
function onEditCooldownMsChanged() {
|
|
||||||
const newText = root.editCooldownMs > 0 ? String(root.editCooldownMs) : "";
|
|
||||||
if (cooldownField.text !== newText)
|
|
||||||
cooldownField.text = newText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
text = root.editCooldownMs > 0 ? String(root.editCooldownMs) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
onTextChanged: {
|
|
||||||
const val = parseInt(text) || 0;
|
|
||||||
if (val !== root.editCooldownMs)
|
|
||||||
root.updateEdit({
|
|
||||||
cooldownMs: val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("ms")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 1
|
Layout.preferredHeight: 1
|
||||||
@@ -1514,8 +1442,8 @@ Item {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
Layout.preferredWidth: root._buttonHeight
|
Layout.preferredWidth: 32
|
||||||
Layout.preferredHeight: root._buttonHeight
|
Layout.preferredHeight: 32
|
||||||
circular: false
|
circular: false
|
||||||
iconName: "delete"
|
iconName: "delete"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
@@ -1537,7 +1465,7 @@ Item {
|
|||||||
|
|
||||||
DankButton {
|
DankButton {
|
||||||
text: I18n.tr("Cancel")
|
text: I18n.tr("Cancel")
|
||||||
buttonHeight: root._buttonHeight
|
buttonHeight: 32
|
||||||
backgroundColor: Theme.surfaceContainer
|
backgroundColor: Theme.surfaceContainer
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
visible: root.hasChanges || root.isNew
|
visible: root.hasChanges || root.isNew
|
||||||
@@ -1553,7 +1481,7 @@ Item {
|
|||||||
|
|
||||||
DankButton {
|
DankButton {
|
||||||
text: root.isNew ? I18n.tr("Add") : I18n.tr("Save")
|
text: root.isNew ? I18n.tr("Add") : I18n.tr("Save")
|
||||||
buttonHeight: root._buttonHeight
|
buttonHeight: 32
|
||||||
enabled: root.canSave()
|
enabled: root.canSave()
|
||||||
visible: root.hasChanges || root.isNew
|
visible: root.hasChanges || root.isNew
|
||||||
onClicked: root.doSave()
|
onClicked: root.doSave()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Modals.FileBrowser
|
import qs.Modals.FileBrowser
|
||||||
@@ -14,7 +13,7 @@ Rectangle {
|
|||||||
property string expandedUuid: ""
|
property string expandedUuid: ""
|
||||||
property int listHeight: 180
|
property int listHeight: 180
|
||||||
|
|
||||||
implicitHeight: 32 + 1 + listHeight + Theme.spacingS * 4 + Theme.spacingM * 2
|
implicitHeight: contentColumn.implicitHeight + Theme.spacingM * 2
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
|
||||||
@@ -154,71 +153,328 @@ Rectangle {
|
|||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
DankFlickable {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: root.listHeight
|
height: root.listHeight
|
||||||
|
contentHeight: listCol.height
|
||||||
|
clip: true
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
id: listCol
|
||||||
spacing: Theme.spacingS
|
width: parent.width
|
||||||
visible: DMSNetworkService.profiles.length === 0
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "vpn_key_off"
|
|
||||||
size: 36
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("No VPN profiles")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Click Import to add a .ovpn or .conf")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankListView {
|
|
||||||
id: vpnListView
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: DMSNetworkService.profiles.length > 0
|
|
||||||
spacing: 4
|
spacing: 4
|
||||||
cacheBuffer: 200
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
model: ScriptModel {
|
Item {
|
||||||
values: DMSNetworkService.profiles
|
width: parent.width
|
||||||
objectProp: "uuid"
|
height: DMSNetworkService.profiles.length === 0 ? 100 : 0
|
||||||
|
visible: height > 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "vpn_key_off"
|
||||||
|
size: 36
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("No VPN profiles")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Click Import to add a .ovpn or .conf")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: VpnProfileDelegate {
|
Repeater {
|
||||||
required property var modelData
|
model: DMSNetworkService.profiles
|
||||||
width: vpnListView.width
|
|
||||||
profile: modelData
|
delegate: Rectangle {
|
||||||
isExpanded: root.expandedUuid === modelData.uuid
|
id: profileRow
|
||||||
onToggleExpand: {
|
required property var modelData
|
||||||
if (root.expandedUuid === modelData.uuid) {
|
required property int index
|
||||||
root.expandedUuid = "";
|
|
||||||
return;
|
readonly property bool isActive: DMSNetworkService.isActiveUuid(modelData.uuid)
|
||||||
|
readonly property bool isExpanded: root.expandedUuid === modelData.uuid
|
||||||
|
readonly property bool isHovered: rowArea.containsMouse || expandBtn.containsMouse || deleteBtn.containsMouse
|
||||||
|
readonly property var configData: isExpanded ? VPNService.editConfig : null
|
||||||
|
|
||||||
|
width: listCol.width
|
||||||
|
height: isExpanded ? 46 + expandedContent.height : 46
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: isHovered ? Theme.primaryHoverLight : (isActive ? Theme.primaryPressed : Theme.surfaceLight)
|
||||||
|
border.width: isActive ? 2 : 1
|
||||||
|
border.color: isActive ? Theme.primary : Theme.outlineLight
|
||||||
|
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: rowArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
|
||||||
|
enabled: !DMSNetworkService.isBusy
|
||||||
|
onClicked: DMSNetworkService.toggle(modelData.uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
height: 46 - Theme.spacingS * 2
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: isActive ? "vpn_lock" : "vpn_key_off"
|
||||||
|
size: 20
|
||||||
|
color: isActive ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 1
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: parent.width - 20 - 28 - 28 - Theme.spacingS * 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: isActive ? Theme.primary : Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: VPNService.getVpnTypeFromProfile(modelData)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: Theme.spacingXS
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: expandBtnRect
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 14
|
||||||
|
color: expandBtn.containsMouse ? Theme.surfacePressed : "transparent"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: isExpanded ? "expand_less" : "expand_more"
|
||||||
|
size: 18
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: expandBtn
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (isExpanded) {
|
||||||
|
root.expandedUuid = "";
|
||||||
|
} else {
|
||||||
|
root.expandedUuid = modelData.uuid;
|
||||||
|
VPNService.getConfig(modelData.uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: deleteBtnRect
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 14
|
||||||
|
color: deleteBtn.containsMouse ? Theme.errorHover : "transparent"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "delete"
|
||||||
|
size: 18
|
||||||
|
color: deleteBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: deleteBtn
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
deleteConfirm.showWithOptions({
|
||||||
|
title: I18n.tr("Delete VPN"),
|
||||||
|
message: I18n.tr("Delete \"") + modelData.name + "\"?",
|
||||||
|
confirmText: I18n.tr("Delete"),
|
||||||
|
confirmColor: Theme.error,
|
||||||
|
onConfirm: () => VPNService.deleteVpn(modelData.uuid)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: expandedContent
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: isExpanded
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outlineLight
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: VPNService.configLoading ? 40 : 0
|
||||||
|
visible: VPNService.configLoading
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "sync"
|
||||||
|
size: 16
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Loading...")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: !VPNService.configLoading && configData
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: {
|
||||||
|
if (!configData)
|
||||||
|
return [];
|
||||||
|
const fields = [];
|
||||||
|
const data = configData.data || {};
|
||||||
|
|
||||||
|
if (data.remote)
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("Server"),
|
||||||
|
value: data.remote
|
||||||
|
});
|
||||||
|
if (configData.username || data.username)
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("Username"),
|
||||||
|
value: configData.username || data.username
|
||||||
|
});
|
||||||
|
if (data.cipher)
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("Cipher"),
|
||||||
|
value: data.cipher
|
||||||
|
});
|
||||||
|
if (data.auth)
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("Auth"),
|
||||||
|
value: data.auth
|
||||||
|
});
|
||||||
|
if (data["proto-tcp"] === "yes" || data["proto-tcp"] === "no")
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("Protocol"),
|
||||||
|
value: data["proto-tcp"] === "yes" ? "TCP" : "UDP"
|
||||||
|
});
|
||||||
|
if (data["tunnel-mtu"])
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("MTU"),
|
||||||
|
value: data["tunnel-mtu"]
|
||||||
|
});
|
||||||
|
if (data["connection-type"])
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("Auth Type"),
|
||||||
|
value: data["connection-type"]
|
||||||
|
});
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("Autoconnect"),
|
||||||
|
value: configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
|
||||||
|
});
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: fieldContent.width + Theme.spacingM * 2
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius - 2
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.outlineLight
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: fieldContent
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.label + ":"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.value
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: Theme.spacingXS
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
root.expandedUuid = modelData.uuid;
|
|
||||||
VPNService.getConfig(modelData.uuid);
|
|
||||||
}
|
|
||||||
onDeleteRequested: {
|
|
||||||
deleteConfirm.showWithOptions({
|
|
||||||
"title": I18n.tr("Delete VPN"),
|
|
||||||
"message": I18n.tr("Delete \"") + modelData.name + "\"?",
|
|
||||||
"confirmText": I18n.tr("Delete"),
|
|
||||||
"confirmColor": Theme.error,
|
|
||||||
"onConfirm": () => VPNService.deleteVpn(modelData.uuid)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,275 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import qs.Common
|
|
||||||
import qs.Services
|
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property var profile
|
|
||||||
property bool isExpanded: false
|
|
||||||
|
|
||||||
signal toggleExpand
|
|
||||||
signal deleteRequested
|
|
||||||
|
|
||||||
readonly property bool isActive: DMSNetworkService.activeUuids?.includes(profile?.uuid) ?? false
|
|
||||||
readonly property bool isHovered: rowArea.containsMouse || expandBtn.containsMouse || deleteBtn.containsMouse
|
|
||||||
readonly property var configData: isExpanded ? VPNService.editConfig : null
|
|
||||||
readonly property var configFields: buildConfigFields()
|
|
||||||
|
|
||||||
height: isExpanded ? 46 + expandedContent.height : 46
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: isHovered ? Theme.primaryHoverLight : (isActive ? Theme.primaryPressed : Theme.surfaceLight)
|
|
||||||
border.width: isActive ? 2 : 1
|
|
||||||
border.color: isActive ? Theme.primary : Theme.outlineLight
|
|
||||||
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
function buildConfigFields() {
|
|
||||||
if (!configData)
|
|
||||||
return [];
|
|
||||||
const fields = [];
|
|
||||||
const data = configData.data || {};
|
|
||||||
if (data.remote)
|
|
||||||
fields.push({
|
|
||||||
"key": "server",
|
|
||||||
"label": I18n.tr("Server"),
|
|
||||||
"value": data.remote
|
|
||||||
});
|
|
||||||
if (configData.username || data.username)
|
|
||||||
fields.push({
|
|
||||||
"key": "user",
|
|
||||||
"label": I18n.tr("Username"),
|
|
||||||
"value": configData.username || data.username
|
|
||||||
});
|
|
||||||
if (data.cipher)
|
|
||||||
fields.push({
|
|
||||||
"key": "cipher",
|
|
||||||
"label": I18n.tr("Cipher"),
|
|
||||||
"value": data.cipher
|
|
||||||
});
|
|
||||||
if (data.auth)
|
|
||||||
fields.push({
|
|
||||||
"key": "auth",
|
|
||||||
"label": I18n.tr("Auth"),
|
|
||||||
"value": data.auth
|
|
||||||
});
|
|
||||||
if (data["proto-tcp"] === "yes" || data["proto-tcp"] === "no")
|
|
||||||
fields.push({
|
|
||||||
"key": "proto",
|
|
||||||
"label": I18n.tr("Protocol"),
|
|
||||||
"value": data["proto-tcp"] === "yes" ? "TCP" : "UDP"
|
|
||||||
});
|
|
||||||
if (data["tunnel-mtu"])
|
|
||||||
fields.push({
|
|
||||||
"key": "mtu",
|
|
||||||
"label": I18n.tr("MTU"),
|
|
||||||
"value": data["tunnel-mtu"]
|
|
||||||
});
|
|
||||||
if (data["connection-type"])
|
|
||||||
fields.push({
|
|
||||||
"key": "conntype",
|
|
||||||
"label": I18n.tr("Auth Type"),
|
|
||||||
"value": data["connection-type"]
|
|
||||||
});
|
|
||||||
fields.push({
|
|
||||||
"key": "auto",
|
|
||||||
"label": I18n.tr("Autoconnect"),
|
|
||||||
"value": configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
|
|
||||||
});
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: rowArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
|
|
||||||
enabled: !DMSNetworkService.isBusy
|
|
||||||
onClicked: DMSNetworkService.toggle(profile.uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: 46 - Theme.spacingS * 2
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: isActive ? "vpn_lock" : "vpn_key_off"
|
|
||||||
size: 20
|
|
||||||
color: isActive ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 1
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: parent.width - 20 - 28 - 28 - Theme.spacingS * 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: profile?.name ?? ""
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: isActive ? Theme.primary : Theme.surfaceText
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: VPNService.getVpnTypeFromProfile(profile)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: Theme.spacingXS
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: expandBtn.containsMouse ? Theme.surfacePressed : "transparent"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: isExpanded ? "expand_less" : "expand_more"
|
|
||||||
size: 18
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: expandBtn
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.toggleExpand()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: deleteBtn.containsMouse ? Theme.errorHover : "transparent"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "delete"
|
|
||||||
size: 18
|
|
||||||
color: deleteBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: deleteBtn
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.deleteRequested()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: expandedContent
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
visible: isExpanded
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Theme.outlineLight
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: VPNService.configLoading ? 40 : 0
|
|
||||||
visible: VPNService.configLoading
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "sync"
|
|
||||||
size: 16
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Loading...")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
visible: !VPNService.configLoading && configData
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: configFields
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
required property var modelData
|
|
||||||
|
|
||||||
width: fieldContent.width + Theme.spacingM * 2
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius - 2
|
|
||||||
color: Theme.surfaceContainerHigh
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.outlineLight
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: fieldContent
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.label + ":"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.value
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 1
|
|
||||||
height: Theme.spacingXS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user