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

Compare commits

...

11 Commits

Author SHA1 Message Date
purian23
5b8edb13d8 distro: OBS updates 2025-12-12 15:42:21 -05:00
bbedward
c595727b94 osd: optimize surface damage
fixes #994
2025-12-12 15:37:39 -05:00
bbedward
d46302588a clipboard: add shift+enter to paste from clipboard history modal
fixes #358
2025-12-12 15:29:10 -05:00
bbedward
0ff9fdb365 notifications: add swipe to dismiss functionality
fixes #927
2025-12-12 14:39:51 -05:00
purian23
e95f7ce367 Update Copr specs 2025-12-12 12:30:18 -05:00
Pi Home Server
df1a8f4066 Add lock screen layout settings (#981)
* Add lock screen layout settings

* Update translation keys
2025-12-12 11:45:00 -05:00
bbedward
32e6c1660f wallpaper: clamp max texture size 2025-12-12 11:17:28 -05:00
bbedward
d6b9b72e9b ci: disable pkg builds from main release wf 2025-12-12 10:16:24 -05:00
bbedward
179ad03fa4 ci: switch to dispatch-based release flow 2025-12-12 10:01:44 -05:00
bbedward
c3cb82c84e dankinstall: call add-wants for niri/hyprland with dms service 2025-12-12 09:58:12 -05:00
bbedward
4b52e2ed9e niri: fix keybind handling of cooldown-ms parameter 2025-12-12 09:52:35 -05:00
35 changed files with 1019 additions and 555 deletions

View File

@@ -1,16 +1,19 @@
name: Release name: Release
on: on:
push: workflow_dispatch:
tags: inputs:
- "v*" tag:
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-${{ github.ref_name }} group: release-${{ inputs.tag }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
@@ -24,10 +27,14 @@ 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
@@ -54,7 +61,7 @@ jobs:
run: | run: |
set -eux set -eux
cd cmd/dankinstall cd cmd/dankinstall
go build -trimpath -ldflags "-s -w -X main.Version=${GITHUB_REF#refs/tags/}" \ go build -trimpath -ldflags "-s -w -X main.Version=${TAG}" \
-o ../../dankinstall-${{ matrix.arch }} -o ../../dankinstall-${{ matrix.arch }}
cd ../.. cd ../..
gzip -9 -k dankinstall-${{ matrix.arch }} gzip -9 -k dankinstall-${{ matrix.arch }}
@@ -68,7 +75,7 @@ jobs:
run: | run: |
set -eux set -eux
cd cmd/dms cd cmd/dms
go build -trimpath -ldflags "-s -w -X main.Version=${GITHUB_REF#refs/tags/}" \ go build -trimpath -ldflags "-s -w -X main.Version=${TAG}" \
-o ../../dms-${{ matrix.arch }} -o ../../dms-${{ matrix.arch }}
cd ../.. cd ../..
gzip -9 -k dms-${{ matrix.arch }} gzip -9 -k dms-${{ matrix.arch }}
@@ -91,7 +98,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=${GITHUB_REF#refs/tags/}" \ go build -trimpath -tags distro_binary -ldflags "-s -w -X main.Version=${TAG}" \
-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 }}
@@ -171,17 +178,18 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: [build-core] #, update-versions] needs: [build-core] #, update-versions]
env: env:
TAG: ${{ github.ref_name }} 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: Fetch updated tag after version bump - name: Fetch updated tag after version bump
run: | run: |
git fetch origin --force tag ${{ github.ref_name }} git fetch origin --force tag ${TAG}
git checkout ${{ github.ref_name }} git checkout ${TAG}
- name: Download core artifacts - name: Download core artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@@ -391,288 +399,296 @@ 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
steps: # env:
- name: Checkout # TAG: ${{ inputs.tag }}
uses: actions/checkout@v4 # steps:
# - 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: |
VERSION="${{ github.ref_name }}" # cd distro
cd distro # bash scripts/obs-upload.sh dms "Update to ${TAG}"
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
steps: # env:
- name: Checkout # TAG: ${{ inputs.tag }}
uses: actions/checkout@v4 # steps:
# - 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: |
VERSION="${{ github.ref_name }}" # cd distro/ubuntu/ppa
cd distro/ubuntu/ppa # bash create-and-upload.sh ../dms dms questing
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: ${{ github.ref_name }} # TAG: ${{ inputs.tag }}
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

View File

@@ -215,13 +215,44 @@ jobs:
# Update openSUSE dms spec (stable only) # Update openSUSE dms spec (stable only)
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
# Update Debian _service files # Update openSUSE spec changelog
DATE_STR=$(date "+%a %b %d %Y")
CHANGELOG_ENTRY="* $DATE_STR AvengeMedia <maintainer@avengemedia.com> - ${VERSION_NO_V}-1\\n- Update to stable $VERSION release\\n- Bug fixes and improvements"
sed -i "/%changelog/a\\$CHANGELOG_ENTRY\\n" distro/opensuse/dms.spec
# Update Debian _service files (both tar_scm and download_url formats)
for service in distro/debian/*/_service; do for service in distro/debian/*/_service; do
if [[ -f "$service" ]]; then if [[ -f "$service" ]]; then
# Update tar_scm revision parameter (for dms-git)
sed -i "s|<param name=\"revision\">v[0-9.]*</param>|<param name=\"revision\">$VERSION</param>|" "$service" sed -i "s|<param name=\"revision\">v[0-9.]*</param>|<param name=\"revision\">$VERSION</param>|" "$service"
# Update download_url paths (for dms stable)
sed -i "s|/v[0-9.]\+/|/$VERSION/|g" "$service"
sed -i "s|/tags/v[0-9.]\+\.tar\.gz|/tags/$VERSION.tar.gz|g" "$service"
fi fi
done done
# Update Debian changelog for dms stable
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
CHANGELOG_DATE=$(date -R)
TEMP_CHANGELOG=$(mktemp)
cat > "$TEMP_CHANGELOG" << EOF
dms ($VERSION_NO_V) stable; urgency=medium
* Update to $VERSION stable release
* Bug fixes and improvements
-- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE
EOF
cat "distro/debian/dms/debian/changelog" >> "$TEMP_CHANGELOG"
mv "$TEMP_CHANGELOG" "distro/debian/dms/debian/changelog"
echo "✓ Updated Debian changelog to $VERSION_NO_V"
fi
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:

6
CHANGELOG.MD Normal file
View File

@@ -0,0 +1,6 @@
# 1.2.0
- Added clipboard and clipboard history integration
- Added swipe to dismiss notification popups and from center
- Added paste from clipboard history view - requires wtype
- Optimize surface damage of OSD & Toast

View File

@@ -340,7 +340,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); err != nil { if err := a.EnableDMSService(ctx, wm); 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))
} }

View File

@@ -566,12 +566,24 @@ TERMINAL=%s
return nil return nil
} }
func (b *BaseDistribution) EnableDMSService(ctx context.Context) error { func (b *BaseDistribution) EnableDMSService(ctx context.Context, wm deps.WindowManager) error {
cmd := exec.CommandContext(ctx, "systemctl", "--user", "enable", "--now", "dms") cmd := exec.CommandContext(ctx, "systemctl", "--user", "enable", "--now", "dms")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to enable dms service: %w", err) return fmt.Errorf("failed to enable dms service: %w", err)
} }
b.log("Enabled dms systemd user service") b.log("Enabled dms systemd user service")
switch wm {
case deps.WindowManagerNiri:
if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "niri.service", "dms").Run(); err != nil {
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")
}
}
return nil return nil
} }

View File

@@ -309,7 +309,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); err != nil { if err := d.EnableDMSService(ctx, wm); 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))
} }

View File

@@ -349,7 +349,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); err != nil { if err := f.EnableDMSService(ctx, wm); 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))
} }

View File

@@ -407,7 +407,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); err != nil { if err := g.EnableDMSService(ctx, wm); 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))
} }

View File

@@ -367,7 +367,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); err != nil { if err := o.EnableDMSService(ctx, wm); 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))
} }

View File

@@ -327,7 +327,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); err != nil { if err := u.EnableDMSService(ctx, wm); 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))
} }

View File

@@ -6,6 +6,7 @@ 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"
@@ -156,6 +157,7 @@ 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 {
@@ -313,7 +315,9 @@ 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 {
opts["cooldown-ms"] = val.String() if ms, err := strconv.Atoi(val.String()); err == nil {
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"
@@ -339,7 +343,14 @@ 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 {
node.AddProperty("cooldown-ms", v, "") switch val := v.(type) {
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, "")

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/sblinch/kdl-go" "github.com/sblinch/kdl-go"
@@ -17,6 +18,7 @@ type NiriKeyBinding struct {
Args []string Args []string
Description string Description string
HideOnOverlay bool HideOnOverlay bool
CooldownMs int
Source string Source string
} }
@@ -275,6 +277,7 @@ 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() {
@@ -284,6 +287,9 @@ 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{
@@ -293,6 +299,7 @@ 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,
} }
} }

View File

@@ -7,6 +7,7 @@ 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"`
} }

View File

@@ -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/v1.0.0.tar.gz</param> <param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.2.tar.gz</param>
<param name="filename">dms-source.tar.gz</param> <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/v1.0.0/dms-distropkg-amd64.gz</param> <param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/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/v1.0.0/dms-distropkg-arm64.gz</param> <param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-arm64.gz</param>
</service> </service>
</services> </services>

View File

@@ -1,3 +1,10 @@
dms (1.0.2) stable; urgency=medium
* Update to v1.0.2 stable release
* Bug fixes and improvements
-- Avenge Media <AvengeMedia.US@gmail.com> Thu, 12 Dec 2025 14:30:00 -0500
dms (1.0.0) stable; urgency=medium dms (1.0.0) stable; urgency=medium
* Update to v1.0.0 release * Update to v1.0.0 release

View File

@@ -36,17 +36,19 @@ override_dh_auto_build:
fi fi
chmod +x dms chmod +x dms
# Extract source if needed
if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ]; then \ if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ]; then \
if [ -f ../SOURCES/dms-source.tar.gz ]; then \ if [ -f ../SOURCES/dms-source.tar.gz ]; then \
tar -xzf ../SOURCES/dms-source.tar.gz; \ tar -xzf ../SOURCES/dms-source.tar.gz; \
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; \
fi
# Rename directory to match expected version
SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" ! -name "DankMaterialShell-$(UPSTREAM_VERSION)" | head -n1); \ SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" ! -name "DankMaterialShell-$(UPSTREAM_VERSION)" | head -n1); \
if [ -n "$$SOURCE_DIR" ] && [ "$$SOURCE_DIR" != "./DankMaterialShell-$(UPSTREAM_VERSION)" ]; then \ if [ -n "$$SOURCE_DIR" ]; then \
echo "Renaming $$SOURCE_DIR to DankMaterialShell-$(UPSTREAM_VERSION)"; \ echo "Renaming $$SOURCE_DIR to DankMaterialShell-$(UPSTREAM_VERSION)"; \
mv "$$SOURCE_DIR" DankMaterialShell-$(UPSTREAM_VERSION); \ mv "$$SOURCE_DIR" DankMaterialShell-$(UPSTREAM_VERSION); \
fi; \
fi fi
@@ -54,13 +56,11 @@ override_dh_auto_install:
install -Dm755 dms debian/dms/usr/bin/dms install -Dm755 dms debian/dms/usr/bin/dms
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 # Ensure directory has correct version name for install step
if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ]; then \ SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" ! -name "DankMaterialShell-$(UPSTREAM_VERSION)" | head -n1); \
SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" | head -n1); \
if [ -n "$$SOURCE_DIR" ]; then \ if [ -n "$$SOURCE_DIR" ]; then \
echo "Renaming $$SOURCE_DIR to DankMaterialShell-$(UPSTREAM_VERSION) for install"; \ echo "Renaming $$SOURCE_DIR to DankMaterialShell-$(UPSTREAM_VERSION) for install"; \
mv "$$SOURCE_DIR" DankMaterialShell-$(UPSTREAM_VERSION); \ 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/; \

View File

@@ -15,7 +15,6 @@ VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}} Source0: {{{ git_repo_pack }}}
BuildRequires: git-core BuildRequires: git-core
BuildRequires: rpkg
# For the _tmpfilesdir macro. # For the _tmpfilesdir macro.
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros

View File

@@ -16,7 +16,6 @@ VCS: {{{ git_repo_vcs }}}
Source0: {{{ git_repo_pack }}} Source0: {{{ git_repo_pack }}}
BuildRequires: git-core BuildRequires: git-core
BuildRequires: rpkg
BuildRequires: gzip BuildRequires: gzip
BuildRequires: golang >= 1.24 BuildRequires: golang >= 1.24
BuildRequires: make BuildRequires: make

View File

@@ -3,7 +3,7 @@
%global debug_package %{nil} %global debug_package %{nil}
Name: dms Name: dms
Version: 1.0.0 Version: 1.0.2
Release: 1%{?dist} Release: 1%{?dist}
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
@@ -105,6 +105,10 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg %{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
%changelog %changelog
* Fri Dec 12 2025 AvengeMedia <maintainer@avengemedia.com> - 1.0.2-1
- Update to stable v1.0.2 release
- Bug fixes and improvements
* Fri Nov 22 2025 AvengeMedia <maintainer@avengemedia.com> - 0.6.2-1 * Fri Nov 22 2025 AvengeMedia <maintainer@avengemedia.com> - 0.6.2-1
- Stable release build with pre-built binaries - Stable release build with pre-built binaries
- Multi-arch support (x86_64, aarch64) - Multi-arch support (x86_64, aarch64)

View File

@@ -311,6 +311,12 @@ Singleton {
property bool modalDarkenBackground: true property bool modalDarkenBackground: true
property bool lockScreenShowPowerActions: true property bool lockScreenShowPowerActions: true
property bool lockScreenShowSystemIcons: true
property bool lockScreenShowTime: true
property bool lockScreenShowDate: true
property bool lockScreenShowProfileImage: true
property bool lockScreenShowPasswordField: true
property bool enableFprint: false property bool enableFprint: false
property int maxFprintTries: 15 property int maxFprintTries: 15
property bool fprintdAvailable: false property bool fprintdAvailable: false

View File

@@ -210,6 +210,11 @@ var SPEC = {
modalDarkenBackground: { def: true }, modalDarkenBackground: { def: true },
lockScreenShowPowerActions: { def: true }, lockScreenShowPowerActions: { def: true },
lockScreenShowSystemIcons: { def: true },
lockScreenShowTime: { def: true },
lockScreenShowDate: { def: true },
lockScreenShowProfileImage: { def: true },
lockScreenShowPasswordField: { def: true },
enableFprint: { def: false }, enableFprint: { def: false },
maxFprintTries: { def: 15 }, maxFprintTries: { def: 15 },
fprintdAvailable: { def: false, persist: false }, fprintdAvailable: { def: false, persist: false },

View File

@@ -158,5 +158,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
visible: modal.showKeyboardHints visible: modal.showKeyboardHints
wtypeAvailable: modal.wtypeAvailable
} }
} }

View File

@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Io
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services import qs.Services
@@ -26,6 +27,50 @@ DankModal {
property int activeImageLoads: 0 property int activeImageLoads: 0
readonly property int maxConcurrentLoads: 3 readonly property int maxConcurrentLoads: 3
readonly property bool clipboardAvailable: DMSService.isConnected && DMSService.capabilities.includes("clipboard") readonly property bool clipboardAvailable: DMSService.isConnected && DMSService.capabilities.includes("clipboard")
property bool wtypeAvailable: false
Process {
id: wtypeCheck
command: ["which", "wtype"]
running: true
onExited: exitCode => {
clipboardHistoryModal.wtypeAvailable = (exitCode === 0);
}
}
Process {
id: wtypeProcess
command: ["wtype", "-M", "ctrl", "-P", "v", "-p", "v", "-m", "ctrl"]
running: false
}
Timer {
id: pasteTimer
interval: 200
repeat: false
onTriggered: wtypeProcess.running = true
}
function pasteSelected() {
if (!keyboardNavigationActive || clipboardEntries.length === 0 || selectedIndex < 0 || selectedIndex >= clipboardEntries.length) {
return;
}
if (!wtypeAvailable) {
ToastService.showError(I18n.tr("wtype not available - install wtype for paste support"));
return;
}
const entry = clipboardEntries[selectedIndex];
DMSService.sendRequest("clipboard.copyEntry", {
"id": entry.id
}, function (response) {
if (response.error) {
ToastService.showError(I18n.tr("Failed to copy entry"));
return;
}
instantClose();
pasteTimer.start();
});
}
function updateFilteredModel() { function updateFilteredModel() {
const query = searchText.trim(); const query = searchText.trim();

View File

@@ -114,11 +114,21 @@ QtObject {
} }
} }
if (event.modifiers & Qt.ShiftModifier && event.key === Qt.Key_Delete) { if (event.modifiers & Qt.ShiftModifier) {
switch (event.key) {
case Qt.Key_Delete:
modal.clearAll(); modal.clearAll();
modal.hide(); modal.hide();
event.accepted = true; event.accepted = true;
return; return;
case Qt.Key_Return:
case Qt.Key_Enter:
if (modal.keyboardNavigationActive) {
modal.pasteSelected();
event.accepted = true;
}
return;
}
} }
if (modal.keyboardNavigationActive) { if (modal.keyboardNavigationActive) {

View File

@@ -5,7 +5,8 @@ import qs.Widgets
Rectangle { Rectangle {
id: keyboardHints id: keyboardHints
readonly property string hintsText: I18n.tr("Shift+Del: Clear All • Esc: Close") property bool wtypeAvailable: false
readonly property string hintsText: wtypeAvailable ? I18n.tr("Shift+Enter: Paste • Shift+Del: Clear All • Esc: Close") : I18n.tr("Shift+Del: Clear All • Esc: Close")
height: ClipboardConstants.keyboardHintsHeight height: ClipboardConstants.keyboardHintsHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius

View File

@@ -79,10 +79,12 @@ Variants {
} }
Component.onCompleted: { Component.onCompleted: {
if (source) { if (!source) {
isInitialized = true;
return;
}
const formattedSource = source.startsWith("file://") ? source : "file://" + source; const formattedSource = source.startsWith("file://") ? source : "file://" + source;
setWallpaperImmediate(formattedSource); setWallpaperImmediate(formattedSource);
}
isInitialized = true; isInitialized = true;
} }
@@ -93,22 +95,23 @@ Variants {
property bool useNextForEffect: false property bool useNextForEffect: false
onSourceChanged: { onSourceChanged: {
const isColor = source.startsWith("#"); if (!source || source.startsWith("#")) {
setWallpaperImmediate("");
return;
}
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
if (!source) {
setWallpaperImmediate("");
} else if (isColor) {
setWallpaperImmediate("");
} else {
if (!isInitialized || !currentWallpaper.source) { if (!isInitialized || !currentWallpaper.source) {
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source); setWallpaperImmediate(formattedSource);
isInitialized = true; isInitialized = true;
} else if (CompositorService.isNiri && SessionData.isSwitchingMode) { return;
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source);
} else {
changeWallpaper(source.startsWith("file://") ? source : "file://" + source);
} }
if (CompositorService.isNiri && SessionData.isSwitchingMode) {
setWallpaperImmediate(formattedSource);
return;
} }
changeWallpaper(formattedSource);
} }
function setWallpaperImmediate(newSource) { function setWallpaperImmediate(newSource) {
@@ -120,15 +123,18 @@ 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();
Qt.callLater(() => { transitionDelayTimer.start();
transitionAnimation.start(); }
});
Timer {
id: transitionDelayTimer
interval: 16
repeat: false
onTriggered: transitionAnimation.start()
} }
function changeWallpaper(newPath) { function changeWallpaper(newPath) {
@@ -143,7 +149,6 @@ 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;
@@ -151,10 +156,9 @@ Variants {
nextWallpaper.source = newPath; nextWallpaper.source = newPath;
if (nextWallpaper.status === Image.Ready) { if (nextWallpaper.status === Image.Ready)
root.startTransition(); root.startTransition();
} }
}
Loader { Loader {
anchors.fill: parent anchors.fill: parent
@@ -166,9 +170,9 @@ Variants {
} }
} }
property real screenScale: CompositorService.getScreenScale(modelData) readonly property int maxTextureSize: 8192
property int physicalWidth: Math.round(modelData.width * screenScale) property int textureWidth: Math.min(modelData.width, maxTextureSize)
property int physicalHeight: Math.round(modelData.height * screenScale) property int textureHeight: Math.min(modelData.height, maxTextureSize)
Image { Image {
id: currentWallpaper id: currentWallpaper
@@ -178,7 +182,7 @@ Variants {
asynchronous: true asynchronous: true
smooth: true smooth: true
cache: true cache: true
sourceSize: Qt.size(root.physicalWidth, root.physicalHeight) sourceSize: Qt.size(root.textureWidth, root.textureHeight)
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode) fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
} }
@@ -189,8 +193,8 @@ Variants {
opacity: 0 opacity: 0
asynchronous: true asynchronous: true
smooth: true smooth: true
cache: false cache: true
sourceSize: Qt.size(root.physicalWidth, root.physicalHeight) sourceSize: Qt.size(root.textureWidth, root.textureHeight)
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode) fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
onStatusChanged: { onStatusChanged: {
@@ -209,7 +213,7 @@ Variants {
live: root.effectActive live: root.effectActive
mipmap: false mipmap: false
recursive: false recursive: false
textureSize: root.effectActive ? Qt.size(root.physicalWidth, root.physicalHeight) : Qt.size(1, 1) textureSize: Qt.size(root.textureWidth, root.textureHeight)
} }
Rectangle { Rectangle {
@@ -265,19 +269,12 @@ 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;
Qt.callLater(() => {
nextWallpaper.source = ""; nextWallpaper.source = "";
Qt.callLater(() => {
root.effectActive = false;
currentWallpaper.cache = true;
nextWallpaper.cache = false;
root.transitionProgress = 0.0; root.transitionProgress = 0.0;
}); root.effectActive = false;
});
} }
} }
} }

View File

@@ -216,6 +216,7 @@ Item {
anchors.verticalCenterOffset: -100 anchors.verticalCenterOffset: -100
width: parent.width width: parent.width
height: 140 height: 140
visible: SettingsData.lockScreenShowTime
Row { Row {
id: clockText id: clockText
@@ -331,6 +332,7 @@ Item {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: -25 anchors.verticalCenterOffset: -25
visible: SettingsData.lockScreenShowDate
text: { text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) { if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat); return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat);
@@ -368,6 +370,7 @@ Item {
return PortalService.profileImage; return PortalService.profileImage;
} }
fallbackIcon: "person" fallbackIcon: "person"
visible: SettingsData.lockScreenShowProfileImage
} }
Rectangle { Rectangle {
@@ -379,6 +382,8 @@ Item {
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.9) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.9)
border.color: passwordField.activeFocus ? Theme.primary : Qt.rgba(1, 1, 1, 0.3) border.color: passwordField.activeFocus ? Theme.primary : Qt.rgba(1, 1, 1, 0.3)
border.width: passwordField.activeFocus ? 2 : 1 border.width: passwordField.activeFocus ? 2 : 1
visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0
Item { Item {
id: lockIconContainer id: lockIconContainer
@@ -797,6 +802,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Theme.spacingXL anchors.margins: Theme.spacingXL
spacing: Theme.spacingL spacing: Theme.spacingL
visible: SettingsData.lockScreenShowSystemIcons
Item { Item {
width: keyboardLayoutRow.width width: keyboardLayoutRow.width

View File

@@ -18,12 +18,12 @@ DankListView {
onIsUserScrollingChanged: { onIsUserScrollingChanged: {
if (isUserScrolling && keyboardController && keyboardController.keyboardNavigationActive) { if (isUserScrolling && keyboardController && keyboardController.keyboardNavigationActive) {
autoScrollDisabled = true autoScrollDisabled = true;
} }
} }
function enableAutoScroll() { function enableAutoScroll() {
autoScrollDisabled = false autoScrollDisabled = false;
} }
Timer { Timer {
@@ -33,7 +33,7 @@ DankListView {
repeat: true repeat: true
onTriggered: { onTriggered: {
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) { if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
keyboardController.ensureVisible() keyboardController.ensureVisible();
} }
} }
} }
@@ -46,49 +46,106 @@ DankListView {
onModelChanged: { onModelChanged: {
if (!keyboardController || !keyboardController.keyboardNavigationActive) { if (!keyboardController || !keyboardController.keyboardNavigationActive) {
return return;
} }
keyboardController.rebuildFlatNavigation() keyboardController.rebuildFlatNavigation();
Qt.callLater(() => { Qt.callLater(() => {
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) { if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
keyboardController.ensureVisible() keyboardController.ensureVisible();
} }
}) });
} }
delegate: Item { delegate: Item {
id: delegateRoot
required property var modelData required property var modelData
required property int index required property int index
readonly property bool isExpanded: (NotificationService.expandedGroups[modelData && modelData.key] || false) readonly property bool isExpanded: (NotificationService.expandedGroups[modelData && modelData.key] || false)
property real swipeOffset: 0
property bool isDismissing: false
readonly property real dismissThreshold: width * 0.35
width: ListView.view.width width: ListView.view.width
height: notificationCard.height height: isDismissing ? 0 : notificationCard.height
clip: true
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
NotificationCard { NotificationCard {
id: notificationCard id: notificationCard
width: parent.width width: parent.width
x: delegateRoot.swipeOffset
notificationGroup: modelData notificationGroup: modelData
keyboardNavigationActive: listView.keyboardActive keyboardNavigationActive: listView.keyboardActive
opacity: 1 - Math.abs(delegateRoot.swipeOffset) / (delegateRoot.width * 0.5)
isGroupSelected: { isGroupSelected: {
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive) { if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
return false return false;
} keyboardController.selectionVersion;
keyboardController.selectionVersion const selection = keyboardController.getCurrentSelection();
const selection = keyboardController.getCurrentSelection() return selection.type === "group" && selection.groupIndex === index;
return selection.type === "group" && selection.groupIndex === index
} }
selectedNotificationIndex: { selectedNotificationIndex: {
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive) { if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
return -1 return -1;
keyboardController.selectionVersion;
const selection = keyboardController.getCurrentSelection();
return (selection.type === "notification" && selection.groupIndex === index) ? selection.notificationIndex : -1;
} }
keyboardController.selectionVersion
const selection = keyboardController.getCurrentSelection() Behavior on x {
return (selection.type === "notification" && selection.groupIndex === index) ? selection.notificationIndex : -1 enabled: !swipeDragHandler.active
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
} }
} }
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
}
}
}
DragHandler {
id: swipeDragHandler
target: null
yAxis.enabled: false
xAxis.enabled: true
onActiveChanged: {
if (active || delegateRoot.isDismissing)
return;
if (Math.abs(delegateRoot.swipeOffset) > delegateRoot.dismissThreshold) {
delegateRoot.isDismissing = true;
delegateRoot.swipeOffset = delegateRoot.swipeOffset > 0 ? delegateRoot.width : -delegateRoot.width;
dismissTimer.start();
} else {
delegateRoot.swipeOffset = 0;
}
}
onTranslationChanged: {
if (delegateRoot.isDismissing)
return;
delegateRoot.swipeOffset = translation.x;
}
}
Timer {
id: dismissTimer
interval: Theme.shortDuration
onTriggered: NotificationService.dismissGroup(delegateRoot.modelData?.key || "")
}
} }
Connections { Connections {
@@ -96,22 +153,22 @@ DankListView {
function onGroupedNotificationsChanged() { function onGroupedNotificationsChanged() {
if (!keyboardController) { if (!keyboardController) {
return return;
} }
if (keyboardController.isTogglingGroup) { if (keyboardController.isTogglingGroup) {
keyboardController.rebuildFlatNavigation() keyboardController.rebuildFlatNavigation();
return return;
} }
keyboardController.rebuildFlatNavigation() keyboardController.rebuildFlatNavigation();
if (keyboardController.keyboardNavigationActive) { if (keyboardController.keyboardNavigationActive) {
Qt.callLater(() => { Qt.callLater(() => {
if (!autoScrollDisabled) { if (!autoScrollDisabled) {
keyboardController.ensureVisible() keyboardController.ensureVisible();
} }
}) });
} }
} }
@@ -119,9 +176,9 @@ DankListView {
if (keyboardController && keyboardController.keyboardNavigationActive) { if (keyboardController && keyboardController.keyboardNavigationActive) {
Qt.callLater(() => { Qt.callLater(() => {
if (!autoScrollDisabled) { if (!autoScrollDisabled) {
keyboardController.ensureVisible() keyboardController.ensureVisible();
} }
}) });
} }
} }
@@ -129,9 +186,9 @@ DankListView {
if (keyboardController && keyboardController.keyboardNavigationActive) { if (keyboardController && keyboardController.keyboardNavigationActive) {
Qt.callLater(() => { Qt.callLater(() => {
if (!autoScrollDisabled) { if (!autoScrollDisabled) {
keyboardController.ensureVisible() keyboardController.ensureVisible();
} }
}) });
} }
} }
} }

View File

@@ -4,7 +4,6 @@ import QtQuick.Effects
import QtQuick.Shapes import QtQuick.Shapes
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -29,63 +28,63 @@ PanelWindow {
function startExit() { function startExit() {
if (exiting || _isDestroying) { if (exiting || _isDestroying) {
return return;
} }
exiting = true exiting = true;
exitAnim.restart() exitAnim.restart();
exitWatchdog.restart() exitWatchdog.restart();
if (NotificationService.removeFromVisibleNotifications) if (NotificationService.removeFromVisibleNotifications)
NotificationService.removeFromVisibleNotifications(win.notificationData) NotificationService.removeFromVisibleNotifications(win.notificationData);
} }
function forceExit() { function forceExit() {
if (_isDestroying) { if (_isDestroying) {
return return;
} }
_isDestroying = true _isDestroying = true;
exiting = true exiting = true;
visible = false visible = false;
exitWatchdog.stop() exitWatchdog.stop();
finalizeExit("forced") finalizeExit("forced");
} }
function finalizeExit(reason) { function finalizeExit(reason) {
if (_finalized) { if (_finalized) {
return return;
} }
_finalized = true _finalized = true;
_isDestroying = true _isDestroying = true;
exitWatchdog.stop() exitWatchdog.stop();
wrapperConn.enabled = false wrapperConn.enabled = false;
wrapperConn.target = null wrapperConn.target = null;
win.exitFinished() win.exitFinished();
} }
visible: hasValidData visible: hasValidData
WlrLayershell.layer: { WlrLayershell.layer: {
const envLayer = Quickshell.env("DMS_NOTIFICATION_LAYER") const envLayer = Quickshell.env("DMS_NOTIFICATION_LAYER");
if (envLayer) { if (envLayer) {
switch (envLayer) { switch (envLayer) {
case "bottom": case "bottom":
return WlrLayershell.Bottom return WlrLayershell.Bottom;
case "overlay": case "overlay":
return WlrLayershell.Overlay return WlrLayershell.Overlay;
case "background": case "background":
return WlrLayershell.Background return WlrLayershell.Background;
case "top": case "top":
return WlrLayershell.Top return WlrLayershell.Top;
} }
} }
if (!notificationData) if (!notificationData)
return WlrLayershell.Top return WlrLayershell.Top;
SettingsData.notificationOverlayEnabled SettingsData.notificationOverlayEnabled;
const shouldUseOverlay = (SettingsData.notificationOverlayEnabled) || (notificationData.urgency === NotificationUrgency.Critical) const shouldUseOverlay = (SettingsData.notificationOverlayEnabled) || (notificationData.urgency === NotificationUrgency.Critical);
return shouldUseOverlay ? WlrLayershell.Overlay : WlrLayershell.Top return shouldUseOverlay ? WlrLayershell.Overlay : WlrLayershell.Top;
} }
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
@@ -94,32 +93,32 @@ PanelWindow {
implicitHeight: 122 implicitHeight: 122
onHasValidDataChanged: { onHasValidDataChanged: {
if (!hasValidData && !exiting && !_isDestroying) { if (!hasValidData && !exiting && !_isDestroying) {
forceExit() forceExit();
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (hasValidData) { if (hasValidData) {
Qt.callLater(() => enterX.restart()) Qt.callLater(() => enterX.restart());
} else { } else {
forceExit() forceExit();
} }
} }
onNotificationDataChanged: { onNotificationDataChanged: {
if (!_isDestroying) { if (!_isDestroying) {
wrapperConn.target = win.notificationData || null wrapperConn.target = win.notificationData || null;
notificationConn.target = (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null notificationConn.target = (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null;
} }
} }
onEntered: { onEntered: {
if (!_isDestroying) { if (!_isDestroying) {
enterDelay.start() enterDelay.start();
} }
} }
Component.onDestruction: { Component.onDestruction: {
_isDestroying = true _isDestroying = true;
exitWatchdog.stop() exitWatchdog.stop();
if (notificationData && notificationData.timer) { if (notificationData && notificationData.timer) {
notificationData.timer.stop() notificationData.timer.stop();
} }
} }
@@ -138,54 +137,66 @@ PanelWindow {
} }
function getBarInfo() { function getBarInfo() {
if (!screen) return { topBar: 0, bottomBar: 0, leftBar: 0, rightBar: 0 } if (!screen)
return {
topBar: 0,
bottomBar: 0,
leftBar: 0,
rightBar: 0
};
return SettingsData.getAdjacentBarInfo(screen, SettingsData.notificationPopupPosition, { return SettingsData.getAdjacentBarInfo(screen, SettingsData.notificationPopupPosition, {
id: "notification-popup", id: "notification-popup",
screenPreferences: [screen.name], screenPreferences: [screen.name],
autoHide: false autoHide: false
}) });
} }
function getTopMargin() { function getTopMargin() {
const popupPos = SettingsData.notificationPopupPosition const popupPos = SettingsData.notificationPopupPosition;
const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left;
if (!isTop) return 0 if (!isTop)
return 0;
const barInfo = getBarInfo() const barInfo = getBarInfo();
const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance;
return base + screenY return base + screenY;
} }
function getBottomMargin() { function getBottomMargin() {
const popupPos = SettingsData.notificationPopupPosition const popupPos = SettingsData.notificationPopupPosition;
const isBottom = popupPos === SettingsData.Position.Bottom || popupPos === SettingsData.Position.Right const isBottom = popupPos === SettingsData.Position.Bottom || popupPos === SettingsData.Position.Right;
if (!isBottom) return 0 if (!isBottom)
return 0;
const barInfo = getBarInfo() const barInfo = getBarInfo();
const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance;
return base + screenY return base + screenY;
} }
function getLeftMargin() { function getLeftMargin() {
if (isTopCenter) return (screen.width - implicitWidth) / 2 if (isTopCenter)
return (screen.width - implicitWidth) / 2;
const popupPos = SettingsData.notificationPopupPosition const popupPos = SettingsData.notificationPopupPosition;
const isLeft = popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom const isLeft = popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom;
if (!isLeft) return 0 if (!isLeft)
return 0;
const barInfo = getBarInfo() const barInfo = getBarInfo();
return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance;
} }
function getRightMargin() { function getRightMargin() {
if (isTopCenter) return 0 if (isTopCenter)
return 0;
const popupPos = SettingsData.notificationPopupPosition const popupPos = SettingsData.notificationPopupPosition;
const isRight = popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Right const isRight = popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Right;
if (!isRight) return 0 if (!isRight)
return 0;
const barInfo = getBarInfo() const barInfo = getBarInfo();
return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance;
} }
readonly property real dpr: CompositorService.getScreenScale(win.screen) readonly property real dpr: CompositorService.getScreenScale(win.screen)
@@ -201,6 +212,11 @@ PanelWindow {
height: alignedHeight height: alignedHeight
visible: win.hasValidData visible: win.hasValidData
property real swipeOffset: 0
readonly property real dismissThreshold: isTopCenter ? height * 0.4 : width * 0.35
readonly property bool swipeActive: swipeDragHandler.active
property bool swipeDismissing: false
property real shadowBlurPx: 10 property real shadowBlurPx: 10
property real shadowSpreadPx: 0 property real shadowSpreadPx: 0
property real shadowBaseAlpha: 0.60 property real shadowBaseAlpha: 0.60
@@ -227,8 +243,8 @@ PanelWindow {
shadowBlur: Math.max(0, Math.min(1, content.shadowBlurPx / bgShadowLayer.blurMax)) shadowBlur: Math.max(0, Math.min(1, content.shadowBlurPx / bgShadowLayer.blurMax))
shadowScale: 1 + (2 * content.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height)) shadowScale: 1 + (2 * content.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
shadowColor: { shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, content.effectiveShadowAlpha) return Theme.withAlpha(baseColor, content.effectiveShadowAlpha);
} }
} }
@@ -250,14 +266,46 @@ PanelWindow {
startX: backgroundShape.radius startX: backgroundShape.radius
startY: 0 startY: 0
PathLine { x: backgroundShape.width - backgroundShape.radius; y: 0 } PathLine {
PathQuad { x: backgroundShape.width; y: backgroundShape.radius; controlX: backgroundShape.width; controlY: 0 } x: backgroundShape.width - backgroundShape.radius
PathLine { x: backgroundShape.width; y: backgroundShape.height - backgroundShape.radius } y: 0
PathQuad { x: backgroundShape.width - backgroundShape.radius; y: backgroundShape.height; controlX: backgroundShape.width; controlY: backgroundShape.height } }
PathLine { x: backgroundShape.radius; y: backgroundShape.height } PathQuad {
PathQuad { x: 0; y: backgroundShape.height - backgroundShape.radius; controlX: 0; controlY: backgroundShape.height } x: backgroundShape.width
PathLine { x: 0; y: backgroundShape.radius } y: backgroundShape.radius
PathQuad { x: backgroundShape.radius; y: 0; controlX: 0; controlY: 0 } controlX: backgroundShape.width
controlY: 0
}
PathLine {
x: backgroundShape.width
y: backgroundShape.height - backgroundShape.radius
}
PathQuad {
x: backgroundShape.width - backgroundShape.radius
y: backgroundShape.height
controlX: backgroundShape.width
controlY: backgroundShape.height
}
PathLine {
x: backgroundShape.radius
y: backgroundShape.height
}
PathQuad {
x: 0
y: backgroundShape.height - backgroundShape.radius
controlX: 0
controlY: backgroundShape.height
}
PathLine {
x: 0
y: backgroundShape.radius
}
PathQuad {
x: backgroundShape.radius
y: 0
controlX: 0
controlY: 0
}
} }
} }
@@ -318,26 +366,26 @@ PanelWindow {
imageSource: { imageSource: {
if (!notificationData) if (!notificationData)
return "" return "";
if (hasNotificationImage) if (hasNotificationImage)
return notificationData.cleanImage || "" return notificationData.cleanImage || "";
if (notificationData.appIcon) { if (notificationData.appIcon) {
const appIcon = notificationData.appIcon const appIcon = notificationData.appIcon;
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
return appIcon return appIcon;
return Quickshell.iconPath(appIcon, true) return Quickshell.iconPath(appIcon, true);
} }
return "" return "";
} }
hasImage: hasNotificationImage hasImage: hasNotificationImage
fallbackIcon: "" fallbackIcon: ""
fallbackText: { fallbackText: {
const appName = notificationData?.appName || "?" const appName = notificationData?.appName || "?";
return appName.charAt(0).toUpperCase() return appName.charAt(0).toUpperCase();
} }
} }
@@ -367,14 +415,14 @@ PanelWindow {
width: parent.width width: parent.width
text: { text: {
if (!notificationData) if (!notificationData)
return "" return "";
const appName = notificationData.appName || "" const appName = notificationData.appName || "";
const timeStr = notificationData.timeStr || "" const timeStr = notificationData.timeStr || "";
if (timeStr.length > 0) if (timeStr.length > 0)
return appName + " • " + timeStr return appName + " • " + timeStr;
else else
return appName return appName;
} }
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -405,7 +453,7 @@ PanelWindow {
visible: text.length > 0 visible: text.length > 0
linkColor: Theme.primary linkColor: Theme.primary
onLinkActivated: link => { onLinkActivated: link => {
return Qt.openUrlExternally(link) return Qt.openUrlExternally(link);
} }
MouseArea { MouseArea {
@@ -432,7 +480,7 @@ PanelWindow {
z: 15 z: 15
onClicked: { onClicked: {
if (notificationData && !win.exiting) if (notificationData && !win.exiting)
notificationData.popup = false notificationData.popup = false;
} }
} }
@@ -475,10 +523,10 @@ PanelWindow {
onExited: parent.isHovered = false onExited: parent.isHovered = false
onClicked: { onClicked: {
if (modelData && modelData.invoke) if (modelData && modelData.invoke)
modelData.invoke() modelData.invoke();
if (notificationData && !win.exiting) if (notificationData && !win.exiting)
notificationData.popup = false notificationData.popup = false;
} }
} }
} }
@@ -519,7 +567,7 @@ PanelWindow {
onExited: clearButton.isHovered = false onExited: clearButton.isHovered = false
onClicked: { onClicked: {
if (notificationData && !win.exiting) if (notificationData && !win.exiting)
NotificationService.dismissNotification(notificationData) NotificationService.dismissNotification(notificationData);
} }
} }
} }
@@ -534,40 +582,108 @@ PanelWindow {
z: -1 z: -1
onEntered: { onEntered: {
if (notificationData && notificationData.timer) if (notificationData && notificationData.timer)
notificationData.timer.stop() notificationData.timer.stop();
} }
onExited: { onExited: {
if (notificationData && notificationData.popup && notificationData.timer) if (notificationData && notificationData.popup && notificationData.timer)
notificationData.timer.restart() notificationData.timer.restart();
} }
onClicked: (mouse) => { onClicked: mouse => {
if (!notificationData || win.exiting) if (!notificationData || win.exiting)
return return;
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
NotificationService.dismissNotification(notificationData) NotificationService.dismissNotification(notificationData);
} else if (mouse.button === Qt.LeftButton) { } else if (mouse.button === Qt.LeftButton) {
if (notificationData.actions && notificationData.actions.length > 0) { if (notificationData.actions && notificationData.actions.length > 0) {
notificationData.actions[0].invoke() notificationData.actions[0].invoke();
NotificationService.dismissNotification(notificationData) NotificationService.dismissNotification(notificationData);
} else { } else {
notificationData.popup = false notificationData.popup = false;
} }
} }
} }
} }
} }
transform: Translate { DragHandler {
id: swipeDragHandler
target: null
xAxis.enabled: !isTopCenter
yAxis.enabled: isTopCenter
onActiveChanged: {
if (active || win.exiting || content.swipeDismissing)
return;
if (Math.abs(content.swipeOffset) > content.dismissThreshold) {
content.swipeDismissing = true;
swipeDismissAnim.start();
} else {
content.swipeOffset = 0;
}
}
onTranslationChanged: {
if (win.exiting)
return;
const raw = isTopCenter ? translation.y : translation.x;
if (isTopCenter) {
content.swipeOffset = Math.min(0, raw);
} else {
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
content.swipeOffset = isLeft ? Math.min(0, raw) : Math.max(0, raw);
}
}
}
opacity: 1 - Math.abs(content.swipeOffset) / (isTopCenter ? content.height : content.width * 0.6)
Behavior on opacity {
enabled: !content.swipeActive
NumberAnimation {
duration: Theme.shortDuration
}
}
Behavior on swipeOffset {
enabled: !content.swipeActive && !content.swipeDismissing
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
NumberAnimation {
id: swipeDismissAnim
target: content
property: "swipeOffset"
to: isTopCenter ? -content.height : (SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom ? -content.width : content.width)
duration: Anims.durShort
easing.type: Easing.OutCubic
onStopped: {
NotificationService.dismissNotification(notificationData);
win.forceExit();
}
}
transform: [
Translate {
id: swipeTx
x: isTopCenter ? 0 : content.swipeOffset
y: isTopCenter ? content.swipeOffset : 0
},
Translate {
id: tx id: tx
x: { x: {
if (isTopCenter) return 0 if (isTopCenter)
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom return 0;
return isLeft ? -Anims.slidePx : Anims.slidePx const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
return isLeft ? -Anims.slidePx : Anims.slidePx;
} }
y: isTopCenter ? -Anims.slidePx : 0 y: isTopCenter ? -Anims.slidePx : 0
} }
]
} }
NumberAnimation { NumberAnimation {
@@ -576,9 +692,10 @@ PanelWindow {
target: tx target: tx
property: isTopCenter ? "y" : "x" property: isTopCenter ? "y" : "x"
from: { from: {
if (isTopCenter) return -Anims.slidePx if (isTopCenter)
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom return -Anims.slidePx;
return isLeft ? -Anims.slidePx : Anims.slidePx const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
return isLeft ? -Anims.slidePx : Anims.slidePx;
} }
to: 0 to: 0
duration: Anims.durMed duration: Anims.durMed
@@ -587,9 +704,11 @@ PanelWindow {
onStopped: { onStopped: {
if (!win.exiting && !win._isDestroying) { if (!win.exiting && !win._isDestroying) {
if (isTopCenter) { if (isTopCenter) {
if (Math.abs(tx.y) < 0.5) win.entered() if (Math.abs(tx.y) < 0.5)
win.entered();
} else { } else {
if (Math.abs(tx.x) < 0.5) win.entered() if (Math.abs(tx.x) < 0.5)
win.entered();
} }
} }
} }
@@ -605,9 +724,10 @@ PanelWindow {
property: isTopCenter ? "y" : "x" property: isTopCenter ? "y" : "x"
from: 0 from: 0
to: { to: {
if (isTopCenter) return -Anims.slidePx if (isTopCenter)
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom return -Anims.slidePx;
return isLeft ? -Anims.slidePx : Anims.slidePx const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
return isLeft ? -Anims.slidePx : Anims.slidePx;
} }
duration: Anims.durShort duration: Anims.durShort
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
@@ -640,10 +760,9 @@ PanelWindow {
function onPopupChanged() { function onPopupChanged() {
if (!win.notificationData || win._isDestroying) if (!win.notificationData || win._isDestroying)
return return;
if (!win.notificationData.popup && !win.exiting) if (!win.notificationData.popup && !win.exiting)
startExit() startExit();
} }
target: win.notificationData || null target: win.notificationData || null
@@ -656,7 +775,7 @@ PanelWindow {
function onDropped() { function onDropped() {
if (!win._isDestroying && !win.exiting) if (!win._isDestroying && !win.exiting)
forceExit() forceExit();
} }
target: (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null target: (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null
@@ -671,7 +790,7 @@ PanelWindow {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (notificationData && notificationData.timer && !exiting && !_isDestroying) if (notificationData && notificationData.timer && !exiting && !_isDestroying)
notificationData.timer.start() notificationData.timer.start();
} }
} }

View File

@@ -23,15 +23,51 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "lock" iconName: "lock"
title: I18n.tr("Lock Screen") title: I18n.tr("Lock Screen layout")
SettingsToggleRow { SettingsToggleRow {
text: I18n.tr("Show Power Actions") text: I18n.tr("Show Power Actions", "Enable power action icon on the lock screen window")
description: I18n.tr("Show power, restart, and logout buttons on the lock screen")
checked: SettingsData.lockScreenShowPowerActions checked: SettingsData.lockScreenShowPowerActions
onToggled: checked => SettingsData.set("lockScreenShowPowerActions", checked) onToggled: checked => SettingsData.set("lockScreenShowPowerActions", checked)
} }
SettingsToggleRow {
text: I18n.tr("Show System Icons", "Enable system status icons on the lock screen window")
checked: SettingsData.lockScreenShowSystemIcons
onToggled: checked => SettingsData.set("lockScreenShowSystemIcons", checked)
}
SettingsToggleRow {
text: I18n.tr("Show System Time", "Enable system time display on the lock screen window")
checked: SettingsData.lockScreenShowTime
onToggled: checked => SettingsData.set("lockScreenShowTime", checked)
}
SettingsToggleRow {
text: I18n.tr("Show System Date", "Enable system date display on the lock screen window")
checked: SettingsData.lockScreenShowDate
onToggled: checked => SettingsData.set("lockScreenShowDate", checked)
}
SettingsToggleRow {
text: I18n.tr("Show Profile Image", "Enable profile image display on the lock screen window")
checked: SettingsData.lockScreenShowProfileImage
onToggled: checked => SettingsData.set("lockScreenShowProfileImage", checked)
}
SettingsToggleRow {
text: I18n.tr("Show Password Field", "Enable password field display on the lock screen window")
description: I18n.tr("If the field is hidden, it will appear as soon as a key is pressed.")
checked: SettingsData.lockScreenShowPasswordField
onToggled: checked => SettingsData.set("lockScreenShowPasswordField", checked)
}
}
SettingsCard {
width: parent.width
iconName: "lock"
title: I18n.tr("Lock Screen behaviour")
StyledText { StyledText {
text: I18n.tr("loginctl not available - lock integration requires DMS socket connection") text: I18n.tr("loginctl not available - lock integration requires DMS socket connection")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall

View File

@@ -16,6 +16,10 @@ PanelWindow {
property real frozenWidth: 0 property real frozenWidth: 0
readonly property string copiedText: I18n.tr("Copied!") readonly property string copiedText: I18n.tr("Copied!")
readonly property real dpr: modelData ? CompositorService.getScreenScale(modelData) : 1
readonly property real shadowBuffer: 5
readonly property real toastY: Theme.barHeight - 4 + (SettingsData.barConfigs[0]?.spacing ?? 4) + 2
Connections { Connections {
target: ToastService target: ToastService
function onToastVisibleChanged() { function onToastVisibleChanged() {
@@ -23,7 +27,6 @@ PanelWindow {
shouldBeVisible = true; shouldBeVisible = true;
visible = true; visible = true;
} else { } else {
// Freeze the width before starting exit animation
frozenWidth = toast.width; frozenWidth = toast.width;
shouldBeVisible = false; shouldBeVisible = false;
closeTimer.restart(); closeTimer.restart();
@@ -48,13 +51,22 @@ PanelWindow {
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
readonly property real toastWidth: shouldBeVisible ? Math.min(900, messageText.implicitWidth + statusIcon.width + Theme.spacingM + (ToastService.hasDetails ? (expandButton.width + closeButton.width + 4) : (ToastService.currentLevel === ToastService.levelError ? closeButton.width + Theme.spacingS : 0)) + Theme.spacingL * 2 + Theme.spacingM * 2) : frozenWidth
readonly property real toastHeight: toastContent.height + Theme.spacingL * 2
anchors { anchors {
top: true top: true
left: true left: true
right: true
bottom: true
} }
WlrLayershell.margins {
left: Math.max(0, Theme.snap((modelData?.width ?? 1920) / 2 - toastWidth / 2 - shadowBuffer, dpr))
top: Math.max(0, Theme.snap(toastY - shadowBuffer, dpr))
}
implicitWidth: toastWidth + (shadowBuffer * 2)
implicitHeight: toastHeight + (shadowBuffer * 2)
Rectangle { Rectangle {
id: toast id: toast
@@ -67,10 +79,10 @@ PanelWindow {
} }
} }
width: shouldBeVisible ? Math.min(900, messageText.implicitWidth + statusIcon.width + Theme.spacingM + (ToastService.hasDetails ? (expandButton.width + closeButton.width + 4) : (ToastService.currentLevel === ToastService.levelError ? closeButton.width + Theme.spacingS : 0)) + Theme.spacingL * 2 + Theme.spacingM * 2) : frozenWidth x: shadowBuffer
height: toastContent.height + Theme.spacingL * 2 y: shadowBuffer
anchors.horizontalCenter: parent.horizontalCenter width: root.toastWidth
y: Theme.barHeight - 4 + (SettingsData.barConfigs[0]?.spacing ?? 4) + 2 height: root.toastHeight
color: { color: {
switch (ToastService.currentLevel) { switch (ToastService.currentLevel) {
case ToastService.levelError: case ToastService.levelError:

View File

@@ -57,15 +57,11 @@ 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
@@ -108,30 +104,33 @@ Variants {
} }
Component.onCompleted: { Component.onCompleted: {
if (source) { if (!source) {
isInitialized = true;
return;
}
const formattedSource = source.startsWith("file://") ? source : "file://" + source; const formattedSource = source.startsWith("file://") ? source : "file://" + source;
setWallpaperImmediate(formattedSource); setWallpaperImmediate(formattedSource);
}
isInitialized = true; isInitialized = true;
} }
onSourceChanged: { onSourceChanged: {
const isColor = source.startsWith("#"); if (!source || source.startsWith("#")) {
setWallpaperImmediate("");
return;
}
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
if (!source) {
setWallpaperImmediate("");
} else if (isColor) {
setWallpaperImmediate("");
} else {
if (!isInitialized || !currentWallpaper.source) { if (!isInitialized || !currentWallpaper.source) {
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source); setWallpaperImmediate(formattedSource);
isInitialized = true; isInitialized = true;
} else if (CompositorService.isNiri && SessionData.isSwitchingMode) { return;
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source);
} else {
changeWallpaper(source.startsWith("file://") ? source : "file://" + source);
} }
if (CompositorService.isNiri && SessionData.isSwitchingMode) {
setWallpaperImmediate(formattedSource);
return;
} }
changeWallpaper(formattedSource);
} }
function setWallpaperImmediate(newSource) { function setWallpaperImmediate(newSource) {
@@ -143,8 +142,6 @@ 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;
@@ -153,9 +150,14 @@ Variants {
srcCurrent.scheduleUpdate(); srcCurrent.scheduleUpdate();
if (srcNext.scheduleUpdate) if (srcNext.scheduleUpdate)
srcNext.scheduleUpdate(); srcNext.scheduleUpdate();
Qt.callLater(() => { transitionDelayTimer.start();
transitionAnimation.start(); }
});
Timer {
id: transitionDelayTimer
interval: 16
repeat: false
onTriggered: transitionAnimation.start()
} }
function changeWallpaper(newPath, force) { function changeWallpaper(newPath, force) {
@@ -163,23 +165,17 @@ 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") {
if (SessionData.includedTransitions.length === 0) { root.actualTransitionType = SessionData.includedTransitions.length === 0 ? "none" : SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)];
root.actualTransitionType = "none";
} else {
root.actualTransitionType = SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)];
}
} }
if (root.actualTransitionType === "none") { if (root.actualTransitionType === "none") {
@@ -187,22 +183,27 @@ Variants {
return; return;
} }
if (root.actualTransitionType === "wipe") { switch (root.actualTransitionType) {
case "wipe":
root.wipeDirection = Math.random() * 4; root.wipeDirection = Math.random() * 4;
} else if (root.actualTransitionType === "disc" || root.actualTransitionType === "pixelate" || root.actualTransitionType === "portal") { break;
case "disc":
case "pixelate":
case "portal":
root.discCenterX = Math.random(); root.discCenterX = Math.random();
root.discCenterY = Math.random(); root.discCenterY = Math.random();
} else if (root.actualTransitionType === "stripes") { break;
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 {
anchors.fill: parent anchors.fill: parent
@@ -214,9 +215,9 @@ Variants {
} }
} }
property real screenScale: CompositorService.getScreenScale(modelData) readonly property int maxTextureSize: 8192
property int physicalWidth: Math.round(modelData.width * screenScale) property int textureWidth: Math.min(modelData.width, maxTextureSize)
property int physicalHeight: Math.round(modelData.height * screenScale) property int textureHeight: Math.min(modelData.height, maxTextureSize)
Image { Image {
id: currentWallpaper id: currentWallpaper
@@ -227,7 +228,7 @@ Variants {
asynchronous: true asynchronous: true
smooth: true smooth: true
cache: true cache: true
sourceSize: Qt.size(root.physicalWidth, root.physicalHeight) sourceSize: Qt.size(root.textureWidth, root.textureHeight)
fillMode: root.getFillMode(SettingsData.wallpaperFillMode) fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
} }
@@ -239,8 +240,8 @@ Variants {
layer.enabled: false layer.enabled: false
asynchronous: true asynchronous: true
smooth: true smooth: true
cache: false cache: true
sourceSize: Qt.size(root.physicalWidth, root.physicalHeight) sourceSize: Qt.size(root.textureWidth, root.textureHeight)
fillMode: root.getFillMode(SettingsData.wallpaperFillMode) fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
onStatusChanged: { onStatusChanged: {
@@ -263,7 +264,7 @@ Variants {
live: root.effectActive live: root.effectActive
mipmap: false mipmap: false
recursive: false recursive: false
textureSize: root.effectActive ? Qt.size(root.physicalWidth, root.physicalHeight) : Qt.size(1, 1) textureSize: Qt.size(root.textureWidth, root.textureHeight)
} }
ShaderEffectSource { ShaderEffectSource {
@@ -273,7 +274,7 @@ Variants {
live: root.effectActive live: root.effectActive
mipmap: false mipmap: false
recursive: false recursive: false
textureSize: root.effectActive ? Qt.size(root.physicalWidth, root.physicalHeight) : Qt.size(1, 1) textureSize: Qt.size(root.textureWidth, root.textureHeight)
} }
Rectangle { Rectangle {
@@ -297,8 +298,9 @@ Variants {
id: effectLoader id: effectLoader
anchors.fill: parent anchors.fill: parent
active: root.effectActive active: root.effectActive
sourceComponent: {
switch (root.actualTransitionType) { function getTransitionComponent(type) {
switch (type) {
case "fade": case "fade":
return fadeComp; return fadeComp;
case "wipe": case "wipe":
@@ -317,6 +319,8 @@ Variants {
return null; return null;
} }
} }
sourceComponent: getTransitionComponent(root.actualTransitionType)
} }
Component { Component {
@@ -491,17 +495,13 @@ 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);
});
}
} }
} }

View File

@@ -314,7 +314,8 @@ 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);
@@ -378,6 +379,8 @@ 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);

View File

@@ -74,6 +74,7 @@ PanelWindow {
readonly property real dpr: CompositorService.getScreenScale(screen) readonly property real dpr: CompositorService.getScreenScale(screen)
readonly property real screenWidth: screen.width readonly property real screenWidth: screen.width
readonly property real screenHeight: screen.height readonly property real screenHeight: screen.height
readonly property real shadowBuffer: 5
readonly property real alignedWidth: Theme.px(osdWidth, dpr) readonly property real alignedWidth: Theme.px(osdWidth, dpr)
readonly property real alignedHeight: Theme.px(osdHeight, dpr) readonly property real alignedHeight: Theme.px(osdHeight, dpr)
@@ -172,10 +173,16 @@ PanelWindow {
anchors { anchors {
top: true top: true
left: true left: true
right: true
bottom: true
} }
WlrLayershell.margins {
left: Math.max(0, Theme.snap(alignedX - shadowBuffer, dpr))
top: Math.max(0, Theme.snap(alignedY - shadowBuffer, dpr))
}
implicitWidth: alignedWidth + (shadowBuffer * 2)
implicitHeight: alignedHeight + (shadowBuffer * 2)
Timer { Timer {
id: hideTimer id: hideTimer
@@ -203,8 +210,8 @@ PanelWindow {
Item { Item {
id: osdContainer id: osdContainer
x: alignedX x: shadowBuffer
y: alignedY y: shadowBuffer
width: alignedWidth width: alignedWidth
height: alignedHeight height: alignedHeight
opacity: shouldBeVisible ? 1 : 0 opacity: shouldBeVisible ? 1 : 0

View File

@@ -23,6 +23,8 @@ 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
@@ -90,6 +92,12 @@ 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);
@@ -109,6 +117,7 @@ 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);
@@ -127,6 +136,7 @@ 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;
} }
@@ -137,8 +147,11 @@ 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 : "";
hasChanges = editKey !== origKey || editAction !== (bindData.action || "") || editDesc !== (bindData.desc || ""); const origCooldown = editingKeyIndex >= 0 && editingKeyIndex < keys.length ? (keys[editingKeyIndex].cooldownMs || 0) : 0;
hasChanges = editKey !== origKey || editAction !== (bindData.action || "") || editDesc !== (bindData.desc || "") || editCooldownMs !== origCooldown;
} }
function canSave() { function canSave() {
@@ -156,10 +169,12 @@ 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;
@@ -1431,6 +1446,57 @@ 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: 60
}
DankTextField {
id: cooldownField
Layout.preferredWidth: 100
Layout.preferredHeight: 40
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