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

Compare commits

...

14 Commits

Author SHA1 Message Date
bbedward
891f53cf6f battery: fix button group sclaing 2025-12-14 17:22:54 -05:00
bbedward
848991cf5b idle: implement screensaver interface
- Mainly used to create the idle inhibitor when an app requests
  screensaver inhibit
2025-12-14 16:49:59 -05:00
bbedward
d37ddd1d41 vpn: optim cc and dankbar widget 2025-12-14 16:12:46 -05:00
Pi Home Server
00d12acd5e Add hide option for updater widget (#1028) 2025-12-14 15:55:47 -05:00
bbedward
3bbc78a44f dankbar: make control center widget per-instance not global
fixes #1017
2025-12-14 15:52:46 -05:00
bbedward
b0a6652cc6 ci: simplify changelog handling 2025-12-14 14:23:27 -05:00
bbedward
cb710b2e5f notifications: fix redundant height animation 2025-12-14 13:40:21 -05:00
tsukasa
ca5fe6f7db Fixed having to click twice to exit out of Spotlight/Cliphist/Powermenu (#1022)
There's possibly more but this fix the need of having to click the
background twice to close those modals.
2025-12-14 11:16:25 -05:00
bbedward
fb75f4c68b lock/greeter: fix font alignment
fixes #1018
2025-12-14 11:13:48 -05:00
bbedward
5e2a418485 binds: fix to scale with arbitrary font sizes 2025-12-14 10:56:03 -05:00
bbedward
24fe215067 ci: pull changelogs from obs/launchpad APIs
- Get changelog from OBS/Launchpad API endpoints, instead of storing in
  git
2025-12-14 10:42:00 -05:00
bbedward
ab2e8875ac runningapps: round icon margin to integer 2025-12-14 10:25:36 -05:00
dms-ci[bot]
dec5740c74 ci: Auto-update PPA packages [dms-git]
🤖 Automated by GitHub Actions
2025-12-14 15:22:12 +00:00
bbedward
208266dfa3 dwl: fix layout popout 2025-12-14 10:17:58 -05:00
40 changed files with 2404 additions and 1439 deletions

383
.github/workflows/backup/run-obs.yml.bak vendored Normal file
View File

@@ -0,0 +1,383 @@
name: Update OBS Packages
on:
workflow_dispatch:
inputs:
package:
description: "Package to update (dms, dms-git, or all)"
required: false
default: "all"
force_upload:
description: "Force upload without version check"
required: false
default: "false"
type: choice
options:
- "false"
- "true"
rebuild_release:
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
required: false
default: ""
push:
tags:
- "v*"
schedule:
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
jobs:
check-updates:
name: Check for updates
runs-on: ubuntu-latest
outputs:
has_updates: ${{ steps.check.outputs.has_updates }}
packages: ${{ steps.check.outputs.packages }}
version: ${{ steps.check.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install OSC
run: |
sudo apt-get update
sudo apt-get install -y osc
mkdir -p ~/.config/osc
cat > ~/.config/osc/oscrc << EOF
[general]
apiurl = https://api.opensuse.org
[https://api.opensuse.org]
user = ${{ secrets.OBS_USERNAME }}
pass = ${{ secrets.OBS_PASSWORD }}
EOF
chmod 600 ~/.config/osc/oscrc
- name: Check for updates
id: check
run: |
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
echo "packages=dms" >> $GITHUB_OUTPUT
VERSION="${GITHUB_REF#refs/tags/}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "Triggered by tag: $VERSION (always update)"
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
echo "packages=dms-git" >> $GITHUB_OUTPUT
echo "Checking if dms-git source has changed..."
# Get current commit hash (8 chars to match spec format)
CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
# Check OBS for last uploaded commit
OBS_BASE="$HOME/.cache/osc-checkouts"
mkdir -p "$OBS_BASE"
OBS_PROJECT="home:AvengeMedia:dms-git"
if [[ -d "$OBS_BASE/$OBS_PROJECT/dms-git" ]]; then
cd "$OBS_BASE/$OBS_PROJECT/dms-git"
osc up -q 2>/dev/null || true
# Extract commit hash from spec Version line & format like; 0.6.2+git2264.a679be68
if [[ -f "dms-git.spec" ]]; then
OBS_COMMIT=$(grep "^Version:" "dms-git.spec" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
if [[ -n "$OBS_COMMIT" ]]; then
if [[ "$CURRENT_COMMIT" == "$OBS_COMMIT" ]]; then
echo "has_updates=false" >> $GITHUB_OUTPUT
echo "📋 Commit $CURRENT_COMMIT already uploaded to OBS, skipping"
else
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 New commit detected: $CURRENT_COMMIT (OBS has $OBS_COMMIT)"
fi
else
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 Could not extract OBS commit, proceeding with update"
fi
else
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 No spec file in OBS, proceeding with update"
fi
cd "${{ github.workspace }}"
else
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 First upload to OBS, update needed"
fi
elif [[ "${{ github.event.inputs.force_upload }}" == "true" ]]; then
PKG="${{ github.event.inputs.package }}"
if [[ -z "$PKG" || "$PKG" == "all" ]]; then
echo "packages=all" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "🚀 Force upload: all packages"
else
echo "packages=$PKG" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "🚀 Force upload: $PKG"
fi
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "Manual trigger: ${{ github.event.inputs.package }}"
else
echo "packages=all" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
fi
update-obs:
name: Upload to OBS
needs: check-updates
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
if: |
github.event.inputs.force_upload == 'true' ||
github.event_name == 'workflow_dispatch' ||
needs.check-updates.outputs.has_updates == 'true'
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.generate_token.outputs.token }}
- name: Check if last commit was automated
id: check-loop
run: |
LAST_COMMIT_MSG=$(git log -1 --pretty=%B | head -1)
if [[ "$LAST_COMMIT_MSG" == "ci: Auto-update PPA packages"* ]] || [[ "$LAST_COMMIT_MSG" == "ci: Auto-update OBS packages"* ]]; then
echo "⏭️ Last commit was automated ($LAST_COMMIT_MSG), skipping to prevent infinite loop"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "✅ Last commit was not automated, proceeding"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Determine packages to update
if: steps.check-loop.outputs.skip != 'true'
id: packages
run: |
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
echo "packages=dms" >> $GITHUB_OUTPUT
VERSION="${GITHUB_REF#refs/tags/}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Triggered by tag: $VERSION"
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "Triggered by schedule: updating git package"
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
echo "Manual trigger: ${{ github.event.inputs.package }}"
else
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
fi
- name: Update dms-git spec version
if: steps.check-loop.outputs.skip != 'true' && (contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all')
run: |
# Get commit info for dms-git versioning
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD)
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
# Update version in spec
sed -i "s/^Version:.*/Version: $NEW_VERSION/" distro/opensuse/dms-git.spec
# Add changelog entry
DATE_STR=$(date "+%a %b %d %Y")
CHANGELOG_ENTRY="* $DATE_STR Avenge Media <AvengeMedia.US@gmail.com> - ${NEW_VERSION}-1\n- Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
sed -i "/%changelog/a\\$CHANGELOG_ENTRY" distro/opensuse/dms-git.spec
- name: Update Debian dms-git changelog version
if: steps.check-loop.outputs.skip != 'true' && (contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all')
run: |
# Get commit info for dms-git versioning
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD)
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
# Debian version format: 0.6.2+git2256.9162e314
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
CHANGELOG_DATE=$(date -R)
CHANGELOG_FILE="distro/debian/dms-git/debian/changelog"
# Get current version from changelog
CURRENT_VERSION=$(head -1 "$CHANGELOG_FILE" | sed 's/.*(\([^)]*\)).*/\1/')
echo "Current Debian version: $CURRENT_VERSION"
echo "New version: $NEW_VERSION"
# Only update if version changed
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
# Create new changelog entry at top
TEMP_CHANGELOG=$(mktemp)
cat > "$TEMP_CHANGELOG" << EOF
dms-git ($NEW_VERSION) nightly; urgency=medium
* Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)
-- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE
EOF
# Prepend to existing changelog
cat "$CHANGELOG_FILE" >> "$TEMP_CHANGELOG"
mv "$TEMP_CHANGELOG" "$CHANGELOG_FILE"
echo "✓ Updated Debian changelog: $CURRENT_VERSION → $NEW_VERSION"
else
echo "✓ Debian changelog already at version $NEW_VERSION"
fi
- name: Update dms stable version
if: steps.check-loop.outputs.skip != 'true' && steps.packages.outputs.version != ''
run: |
VERSION="${{ steps.packages.outputs.version }}"
VERSION_NO_V="${VERSION#v}"
echo "Updating packaging to version $VERSION_NO_V"
# Update openSUSE dms spec (stable only)
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
# 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
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"
# 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
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
if: steps.check-loop.outputs.skip != 'true'
uses: actions/setup-go@v5
with:
go-version: "1.24"
- name: Install OSC
if: steps.check-loop.outputs.skip != 'true'
run: |
sudo apt-get update
sudo apt-get install -y osc
mkdir -p ~/.config/osc
cat > ~/.config/osc/oscrc << EOF
[general]
apiurl = https://api.opensuse.org
[https://api.opensuse.org]
user = ${{ secrets.OBS_USERNAME }}
pass = ${{ secrets.OBS_PASSWORD }}
EOF
chmod 600 ~/.config/osc/oscrc
- name: Upload to OBS
if: steps.check-loop.outputs.skip != 'true'
env:
FORCE_UPLOAD: ${{ github.event.inputs.force_upload }}
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
run: |
PACKAGES="${{ steps.packages.outputs.packages }}"
MESSAGE="Automated update from GitHub Actions"
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
fi
if [[ "$PACKAGES" == "all" ]]; then
bash distro/scripts/obs-upload.sh dms "$MESSAGE"
bash distro/scripts/obs-upload.sh dms-git "Automated git update"
else
bash distro/scripts/obs-upload.sh "$PACKAGES" "$MESSAGE"
fi
- name: Get changed packages
if: steps.check-loop.outputs.skip != 'true'
id: changed-packages
run: |
# Check if there are any changes to commit
if git diff --exit-code distro/debian/ distro/opensuse/ >/dev/null 2>&1; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "📋 No changelog or spec changes to commit"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
# Get list of changed packages for commit message
CHANGED_DEB=$(git diff --name-only distro/debian/ 2>/dev/null | grep 'debian/changelog' | xargs dirname 2>/dev/null | xargs dirname 2>/dev/null | xargs basename 2>/dev/null | tr '\n' ', ' | sed 's/, $//' || echo "")
CHANGED_SUSE=$(git diff --name-only distro/opensuse/ 2>/dev/null | grep '\.spec$' | sed 's|distro/opensuse/||' | sed 's/\.spec$//' | tr '\n' ', ' | sed 's/, $//' || echo "")
PKGS=$(echo "$CHANGED_DEB,$CHANGED_SUSE" | tr ',' '\n' | grep -v '^$' | sort -u | tr '\n' ',' | sed 's/,$//')
echo "packages=$PKGS" >> $GITHUB_OUTPUT
echo "📋 Changed packages: $PKGS"
fi
- name: Commit packaging changes
if: steps.check-loop.outputs.skip != 'true' && steps.changed-packages.outputs.has_changes == 'true'
run: |
git config user.name "dms-ci[bot]"
git config user.email "dms-ci[bot]@users.noreply.github.com"
git add distro/debian/*/debian/changelog distro/opensuse/*.spec
git commit -m "ci: Auto-update OBS packages [${{ steps.changed-packages.outputs.packages }}]" -m "🤖 Automated by GitHub Actions"
git pull --rebase origin master
git push
- name: Summary
run: |
echo "### OBS Package Update Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Packages**: ${{ steps.packages.outputs.packages }}" >> $GITHUB_STEP_SUMMARY
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
echo "- **Version**: ${{ steps.packages.outputs.version }}" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.check-updates.outputs.has_updates }}" == "false" ]]; then
echo "- **Status**: Skipped (no changes detected)" >> $GITHUB_STEP_SUMMARY
fi
echo "- **Project**: https://build.opensuse.org/project/show/home:AvengeMedia" >> $GITHUB_STEP_SUMMARY

298
.github/workflows/backup/run-ppa.yml.bak vendored Normal file
View File

@@ -0,0 +1,298 @@
name: Update PPA Packages
on:
workflow_dispatch:
inputs:
package:
description: "Package to upload (dms, dms-git, dms-greeter, or all)"
required: false
default: "dms-git"
force_upload:
description: "Force upload without version check"
required: false
default: "false"
type: choice
options:
- "false"
- "true"
rebuild_release:
description: "Release number for rebuilds (e.g., 2, 3, 4 for ppa2, ppa3, ppa4)"
required: false
default: ""
schedule:
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
jobs:
check-updates:
name: Check for updates
runs-on: ubuntu-latest
outputs:
has_updates: ${{ steps.check.outputs.has_updates }}
packages: ${{ steps.check.outputs.packages }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for updates
id: check
run: |
if [[ "${{ github.event_name }}" == "schedule" ]]; then
echo "packages=dms-git" >> $GITHUB_OUTPUT
echo "Checking if dms-git source has changed..."
# Get current commit hash (8 chars to match changelog format)
CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
# Extract commit hash from changelog
# Format: dms-git (0.6.2+git2264.c5c5ce84) questing; urgency=medium
CHANGELOG_FILE="distro/ubuntu/dms-git/debian/changelog"
if [[ -f "$CHANGELOG_FILE" ]]; then
CHANGELOG_COMMIT=$(head -1 "$CHANGELOG_FILE" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
if [[ -n "$CHANGELOG_COMMIT" ]]; then
if [[ "$CURRENT_COMMIT" == "$CHANGELOG_COMMIT" ]]; then
echo "has_updates=false" >> $GITHUB_OUTPUT
echo "📋 Commit $CURRENT_COMMIT already in changelog, skipping upload"
else
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 New commit detected: $CURRENT_COMMIT (changelog has $CHANGELOG_COMMIT)"
fi
else
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 Could not extract commit from changelog, proceeding with upload"
fi
else
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 No changelog file found, proceeding with upload"
fi
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "Manual trigger: ${{ github.event.inputs.package }}"
else
echo "packages=dms-git" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT
fi
upload-ppa:
name: Upload to PPA
needs: check-updates
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
if: |
github.event.inputs.force_upload == 'true' ||
github.event_name == 'workflow_dispatch' ||
needs.check-updates.outputs.has_updates == 'true'
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.generate_token.outputs.token }}
- name: Check if last commit was automated
id: check-loop
run: |
LAST_COMMIT_MSG=$(git log -1 --pretty=%B | head -1)
if [[ "$LAST_COMMIT_MSG" == "ci: Auto-update PPA packages"* ]] || [[ "$LAST_COMMIT_MSG" == "ci: Auto-update OBS packages"* ]]; then
echo "⏭️ Last commit was automated ($LAST_COMMIT_MSG), skipping to prevent infinite loop"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "✅ Last commit was not automated, proceeding"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Set up Go
if: steps.check-loop.outputs.skip != 'true'
uses: actions/setup-go@v5
with:
go-version: "1.24"
cache: false
- name: Install build dependencies
if: steps.check-loop.outputs.skip != 'true'
run: |
sudo apt-get update
sudo apt-get install -y \
debhelper \
devscripts \
dput \
lftp \
build-essential \
fakeroot \
dpkg-dev
- name: Configure GPG
if: steps.check-loop.outputs.skip != 'true'
env:
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: |
echo "$GPG_KEY" | gpg --import
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | awk '{print $2}' | cut -d'/' -f2)
echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV
- name: Determine packages to upload
if: steps.check-loop.outputs.skip != 'true'
id: packages
run: |
if [[ "${{ github.event.inputs.force_upload }}" == "true" ]]; then
PKG="${{ github.event.inputs.package }}"
if [[ -z "$PKG" || "$PKG" == "all" ]]; then
echo "packages=all" >> $GITHUB_OUTPUT
echo "🚀 Force upload: all packages"
else
echo "packages=$PKG" >> $GITHUB_OUTPUT
echo "🚀 Force upload: $PKG"
fi
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "Triggered by schedule: uploading git package"
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
# Manual package selection should respect change detection
SELECTED_PKG="${{ github.event.inputs.package }}"
UPDATED_PKG="${{ needs.check-updates.outputs.packages }}"
# Check if manually selected package is in the updated list
if [[ "$UPDATED_PKG" == *"$SELECTED_PKG"* ]] || [[ "$SELECTED_PKG" == "all" ]]; then
echo "packages=$SELECTED_PKG" >> $GITHUB_OUTPUT
echo "📦 Manual selection (has updates): $SELECTED_PKG"
else
echo "packages=" >> $GITHUB_OUTPUT
echo "⚠️ Manual selection '$SELECTED_PKG' has no updates - skipping (use force_upload to override)"
fi
else
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
fi
- name: Upload to PPA
if: steps.check-loop.outputs.skip != 'true'
run: |
PACKAGES="${{ steps.packages.outputs.packages }}"
REBUILD_RELEASE="${{ github.event.inputs.rebuild_release }}"
if [[ -z "$PACKAGES" ]]; then
echo "No packages selected for upload. Skipping."
exit 0
fi
# Build command arguments
BUILD_ARGS=()
if [[ -n "$REBUILD_RELEASE" ]]; then
BUILD_ARGS+=("$REBUILD_RELEASE")
echo "✓ Using rebuild release number: ppa$REBUILD_RELEASE"
fi
if [[ "$PACKAGES" == "all" ]]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Uploading dms to PPA..."
if [ -n "$REBUILD_RELEASE" ]; then
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bash distro/scripts/ppa-upload.sh dms dms questing "${BUILD_ARGS[@]}"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Uploading dms-git to PPA..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bash distro/scripts/ppa-upload.sh dms-git dms-git questing "${BUILD_ARGS[@]}"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Uploading dms-greeter to PPA..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bash distro/scripts/ppa-upload.sh dms-greeter danklinux questing "${BUILD_ARGS[@]}"
else
# Map package to PPA name
case "$PACKAGES" in
dms)
PPA_NAME="dms"
;;
dms-git)
PPA_NAME="dms-git"
;;
dms-greeter)
PPA_NAME="danklinux"
;;
*)
PPA_NAME="$PACKAGES"
;;
esac
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Uploading $PACKAGES to PPA..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bash distro/scripts/ppa-upload.sh "$PACKAGES" "$PPA_NAME" questing "${BUILD_ARGS[@]}"
fi
- name: Get changed packages
if: steps.check-loop.outputs.skip != 'true'
id: changed-packages
run: |
# Check if there are any changelog changes to commit
if git diff --exit-code distro/ubuntu/ >/dev/null 2>&1; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "📋 No changelog changes to commit"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
# Get list of changed packages for commit message (deduplicate)
CHANGED=$(git diff --name-only distro/ubuntu/ | grep 'debian/changelog' | sed 's|/debian/changelog||' | xargs -I{} basename {} | sort -u | tr '\n' ',' | sed 's/,$//')
echo "packages=$CHANGED" >> $GITHUB_OUTPUT
echo "📋 Changed packages: $CHANGED"
echo "📋 Debug - Changed files:"
git diff --name-only distro/ubuntu/ | grep 'debian/changelog' || echo "No changelog files found"
fi
- name: Commit changelog changes
if: steps.check-loop.outputs.skip != 'true' && steps.changed-packages.outputs.has_changes == 'true'
run: |
git config user.name "dms-ci[bot]"
git config user.email "dms-ci[bot]@users.noreply.github.com"
git add distro/ubuntu/*/debian/changelog
git commit -m "ci: Auto-update PPA packages [${{ steps.changed-packages.outputs.packages }}]" -m "🤖 Automated by GitHub Actions"
git pull --rebase origin master
git push
- name: Summary
run: |
echo "### PPA Package Upload Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Packages**: ${{ steps.packages.outputs.packages }}" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.check-updates.outputs.has_updates }}" == "false" ]]; then
echo "- **Status**: Skipped (no changes detected)" >> $GITHUB_STEP_SUMMARY
fi
PACKAGES="${{ steps.packages.outputs.packages }}"
if [[ "$PACKAGES" == "all" ]]; then
echo "- **PPA dms**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
echo "- **PPA dms-git**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
echo "- **PPA danklinux**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
elif [[ "$PACKAGES" == "dms" ]]; then
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
elif [[ "$PACKAGES" == "dms-git" ]]; then
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
elif [[ "$PACKAGES" == "dms-greeter" ]]; then
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
fi
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
echo "- **Version**: ${{ steps.packages.outputs.version }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "Builds will appear once Launchpad processes the uploads." >> $GITHUB_STEP_SUMMARY

View File

@@ -41,24 +41,11 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install OSC
run: |
sudo apt-get update
sudo apt-get install -y osc
mkdir -p ~/.config/osc
cat > ~/.config/osc/oscrc << EOF
[general]
apiurl = https://api.opensuse.org
[https://api.opensuse.org]
user = ${{ secrets.OBS_USERNAME }}
pass = ${{ secrets.OBS_PASSWORD }}
EOF
chmod 600 ~/.config/osc/oscrc
- name: Check for updates - name: Check for updates
id: check id: check
env:
OBS_USERNAME: ${{ secrets.OBS_USERNAME }}
OBS_PASSWORD: ${{ secrets.OBS_PASSWORD }}
run: | run: |
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
echo "packages=dms" >> $GITHUB_OUTPUT echo "packages=dms" >> $GITHUB_OUTPUT
@@ -73,18 +60,13 @@ jobs:
# Get current commit hash (8 chars to match spec format) # Get current commit hash (8 chars to match spec format)
CURRENT_COMMIT=$(git rev-parse --short=8 HEAD) CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
# Check OBS for last uploaded commit # Query OBS API for current spec file version
OBS_BASE="$HOME/.cache/osc-checkouts" # Format: Version: 1.0.2+git2528.d336866f
mkdir -p "$OBS_BASE" OBS_SPEC=$(curl -s -u "$OBS_USERNAME:$OBS_PASSWORD" "https://api.opensuse.org/source/home:AvengeMedia:dms-git/dms-git/dms-git.spec" 2>/dev/null || echo "")
OBS_PROJECT="home:AvengeMedia:dms-git"
if [[ -d "$OBS_BASE/$OBS_PROJECT/dms-git" ]]; then if [[ -n "$OBS_SPEC" && "$OBS_SPEC" != *"error"* ]]; then
cd "$OBS_BASE/$OBS_PROJECT/dms-git" # Extract commit hash from Version line (e.g., d336866f from 1.0.2+git2528.d336866f)
osc up -q 2>/dev/null || true OBS_COMMIT=$(echo "$OBS_SPEC" | grep "^Version:" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
# Extract commit hash from spec Version line & format like; 0.6.2+git2264.a679be68
if [[ -f "dms-git.spec" ]]; then
OBS_COMMIT=$(grep "^Version:" "dms-git.spec" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
if [[ -n "$OBS_COMMIT" ]]; then if [[ -n "$OBS_COMMIT" ]]; then
if [[ "$CURRENT_COMMIT" == "$OBS_COMMIT" ]]; then if [[ "$CURRENT_COMMIT" == "$OBS_COMMIT" ]]; then
@@ -100,13 +82,7 @@ jobs:
fi fi
else else
echo "has_updates=true" >> $GITHUB_OUTPUT echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 No spec file in OBS, proceeding with update" echo "📋 Could not fetch OBS spec, proceeding with update"
fi
cd "${{ github.workspace }}"
else
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 First upload to OBS, update needed"
fi fi
elif [[ "${{ github.event.inputs.force_upload }}" == "true" ]]; then elif [[ "${{ github.event.inputs.force_upload }}" == "true" ]]; then
PKG="${{ github.event.inputs.package }}" PKG="${{ github.event.inputs.package }}"
@@ -132,42 +108,18 @@ jobs:
name: Upload to OBS name: Upload to OBS
needs: check-updates needs: check-updates
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
if: | if: |
github.event.inputs.force_upload == 'true' || github.event.inputs.force_upload == 'true' ||
github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_dispatch' ||
needs.check-updates.outputs.has_updates == 'true' needs.check-updates.outputs.has_updates == 'true'
steps: steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ steps.generate_token.outputs.token }}
- name: Check if last commit was automated
id: check-loop
run: |
LAST_COMMIT_MSG=$(git log -1 --pretty=%B | head -1)
if [[ "$LAST_COMMIT_MSG" == "ci: Auto-update PPA packages"* ]] || [[ "$LAST_COMMIT_MSG" == "ci: Auto-update OBS packages"* ]]; then
echo "⏭️ Last commit was automated ($LAST_COMMIT_MSG), skipping to prevent infinite loop"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "✅ Last commit was not automated, proceeding"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Determine packages to update - name: Determine packages to update
if: steps.check-loop.outputs.skip != 'true'
id: packages id: packages
run: | run: |
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
@@ -186,83 +138,63 @@ jobs:
fi fi
- name: Update dms-git spec version - name: Update dms-git spec version
if: steps.check-loop.outputs.skip != 'true' && (contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all') if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
run: | run: |
# Get commit info for dms-git versioning
COMMIT_HASH=$(git rev-parse --short=8 HEAD) COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD) COMMIT_COUNT=$(git rev-list --count HEAD)
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2") BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}" NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating dms-git.spec to version: $NEW_VERSION" echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
# Update version in spec
sed -i "s/^Version:.*/Version: $NEW_VERSION/" distro/opensuse/dms-git.spec sed -i "s/^Version:.*/Version: $NEW_VERSION/" distro/opensuse/dms-git.spec
# Add changelog entry # Single changelog entry (git snapshots don't need history)
DATE_STR=$(date "+%a %b %d %Y") DATE_STR=$(date "+%a %b %d %Y")
CHANGELOG_ENTRY="* $DATE_STR Avenge Media <AvengeMedia.US@gmail.com> - ${NEW_VERSION}-1\n- Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)" LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms-git.spec)
sed -i "/%changelog/a\\$CHANGELOG_ENTRY" distro/opensuse/dms-git.spec {
echo "$LOCAL_SPEC_HEAD"
echo "%changelog"
echo "* $DATE_STR Avenge Media <AvengeMedia.US@gmail.com> - ${NEW_VERSION}-1"
echo "- Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
} > distro/opensuse/dms-git.spec
- name: Update Debian dms-git changelog version - name: Update Debian dms-git changelog version
if: steps.check-loop.outputs.skip != 'true' && (contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all') if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
run: | run: |
# Get commit info for dms-git versioning
COMMIT_HASH=$(git rev-parse --short=8 HEAD) COMMIT_HASH=$(git rev-parse --short=8 HEAD)
COMMIT_COUNT=$(git rev-list --count HEAD) COMMIT_COUNT=$(git rev-list --count HEAD)
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2") BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
# Debian version format: 0.6.2+git2256.9162e314
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}" NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION" echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
# Single changelog entry (git snapshots don't need history)
CHANGELOG_DATE=$(date -R) CHANGELOG_DATE=$(date -R)
{
CHANGELOG_FILE="distro/debian/dms-git/debian/changelog" echo "dms-git ($NEW_VERSION) nightly; urgency=medium"
echo ""
# Get current version from changelog echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
CURRENT_VERSION=$(head -1 "$CHANGELOG_FILE" | sed 's/.*(\([^)]*\)).*/\1/') echo ""
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
echo "Current Debian version: $CURRENT_VERSION" } > "distro/debian/dms-git/debian/changelog"
echo "New version: $NEW_VERSION"
# Only update if version changed
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
# Create new changelog entry at top
TEMP_CHANGELOG=$(mktemp)
cat > "$TEMP_CHANGELOG" << EOF
dms-git ($NEW_VERSION) nightly; urgency=medium
* Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)
-- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE
EOF
# Prepend to existing changelog
cat "$CHANGELOG_FILE" >> "$TEMP_CHANGELOG"
mv "$TEMP_CHANGELOG" "$CHANGELOG_FILE"
echo "✓ Updated Debian changelog: $CURRENT_VERSION → $NEW_VERSION"
else
echo "✓ Debian changelog already at version $NEW_VERSION"
fi
- name: Update dms stable version - name: Update dms stable version
if: steps.check-loop.outputs.skip != 'true' && steps.packages.outputs.version != '' if: steps.packages.outputs.version != ''
run: | run: |
VERSION="${{ steps.packages.outputs.version }}" VERSION="${{ steps.packages.outputs.version }}"
VERSION_NO_V="${VERSION#v}" VERSION_NO_V="${VERSION#v}"
echo "Updating packaging to version $VERSION_NO_V" echo "Updating packaging to version $VERSION_NO_V"
# 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 openSUSE spec changelog # Single changelog entry (full history on OBS website)
DATE_STR=$(date "+%a %b %d %Y") 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" LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
sed -i "/%changelog/a\\$CHANGELOG_ENTRY\\n" distro/opensuse/dms.spec {
echo "$LOCAL_SPEC_HEAD"
echo "%changelog"
echo "* $DATE_STR AvengeMedia <maintainer@avengemedia.com> - ${VERSION_NO_V}-1"
echo "- Update to stable $VERSION release"
} > distro/opensuse/dms.spec
# Update Debian _service files (both tar_scm and download_url formats) # 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
@@ -276,35 +208,25 @@ jobs:
fi fi
done done
# Update Debian changelog for dms stable # Update Debian changelog for dms stable (single entry, history on OBS website)
if [[ -f "distro/debian/dms/debian/changelog" ]]; then if [[ -f "distro/debian/dms/debian/changelog" ]]; then
CHANGELOG_DATE=$(date -R) CHANGELOG_DATE=$(date -R)
TEMP_CHANGELOG=$(mktemp) {
echo "dms ($VERSION_NO_V) stable; urgency=medium"
cat > "$TEMP_CHANGELOG" << EOF echo ""
dms ($VERSION_NO_V) stable; urgency=medium echo " * Update to $VERSION stable release"
echo ""
* Update to $VERSION stable release echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
* Bug fixes and improvements } > "distro/debian/dms/debian/changelog"
-- 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" echo "✓ Updated Debian changelog to $VERSION_NO_V"
fi fi
- name: Install Go - name: Install Go
if: steps.check-loop.outputs.skip != 'true'
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "1.24" go-version: "1.24"
- name: Install OSC - name: Install OSC
if: steps.check-loop.outputs.skip != 'true'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y osc sudo apt-get install -y osc
@@ -321,7 +243,6 @@ jobs:
chmod 600 ~/.config/osc/oscrc chmod 600 ~/.config/osc/oscrc
- name: Upload to OBS - name: Upload to OBS
if: steps.check-loop.outputs.skip != 'true'
env: env:
FORCE_UPLOAD: ${{ github.event.inputs.force_upload }} FORCE_UPLOAD: ${{ github.event.inputs.force_upload }}
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }} REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
@@ -340,35 +261,6 @@ jobs:
bash distro/scripts/obs-upload.sh "$PACKAGES" "$MESSAGE" bash distro/scripts/obs-upload.sh "$PACKAGES" "$MESSAGE"
fi fi
- name: Get changed packages
if: steps.check-loop.outputs.skip != 'true'
id: changed-packages
run: |
# Check if there are any changes to commit
if git diff --exit-code distro/debian/ distro/opensuse/ >/dev/null 2>&1; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "📋 No changelog or spec changes to commit"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
# Get list of changed packages for commit message
CHANGED_DEB=$(git diff --name-only distro/debian/ 2>/dev/null | grep 'debian/changelog' | xargs dirname 2>/dev/null | xargs dirname 2>/dev/null | xargs basename 2>/dev/null | tr '\n' ', ' | sed 's/, $//' || echo "")
CHANGED_SUSE=$(git diff --name-only distro/opensuse/ 2>/dev/null | grep '\.spec$' | sed 's|distro/opensuse/||' | sed 's/\.spec$//' | tr '\n' ', ' | sed 's/, $//' || echo "")
PKGS=$(echo "$CHANGED_DEB,$CHANGED_SUSE" | tr ',' '\n' | grep -v '^$' | sort -u | tr '\n' ',' | sed 's/,$//')
echo "packages=$PKGS" >> $GITHUB_OUTPUT
echo "📋 Changed packages: $PKGS"
fi
- name: Commit packaging changes
if: steps.check-loop.outputs.skip != 'true' && steps.changed-packages.outputs.has_changes == 'true'
run: |
git config user.name "dms-ci[bot]"
git config user.email "dms-ci[bot]@users.noreply.github.com"
git add distro/debian/*/debian/changelog distro/opensuse/*.spec
git commit -m "ci: Auto-update OBS packages [${{ steps.changed-packages.outputs.packages }}]" -m "🤖 Automated by GitHub Actions"
git pull --rebase origin master
git push
- name: Summary - name: Summary
run: | run: |
echo "### OBS Package Update Complete" >> $GITHUB_STEP_SUMMARY echo "### OBS Package Update Complete" >> $GITHUB_STEP_SUMMARY

View File

@@ -44,31 +44,32 @@ jobs:
echo "packages=dms-git" >> $GITHUB_OUTPUT echo "packages=dms-git" >> $GITHUB_OUTPUT
echo "Checking if dms-git source has changed..." echo "Checking if dms-git source has changed..."
# Get current commit hash (8 chars to match changelog format) # Get current commit hash (8 chars to match PPA version format)
CURRENT_COMMIT=$(git rev-parse --short=8 HEAD) CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
# Extract commit hash from changelog # Query Launchpad API for last published version
# Format: dms-git (0.6.2+git2264.c5c5ce84) questing; urgency=medium # Format: 1.0.2+git2528.d336866fppa1
CHANGELOG_FILE="distro/ubuntu/dms-git/debian/changelog" PPA_VERSION=$(curl -s "https://api.launchpad.net/1.0/~avengemedia/+archive/ubuntu/dms-git?ws.op=getPublishedSources&source_name=dms-git&status=Published" | grep -oP '"source_package_version":\s*"\K[^"]+' | head -1 || echo "")
if [[ -f "$CHANGELOG_FILE" ]]; then if [[ -n "$PPA_VERSION" ]]; then
CHANGELOG_COMMIT=$(head -1 "$CHANGELOG_FILE" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "") # Extract commit hash from version (e.g., d336866f from 1.0.2+git2528.d336866fppa1)
PPA_COMMIT=$(echo "$PPA_VERSION" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
if [[ -n "$CHANGELOG_COMMIT" ]]; then if [[ -n "$PPA_COMMIT" ]]; then
if [[ "$CURRENT_COMMIT" == "$CHANGELOG_COMMIT" ]]; then if [[ "$CURRENT_COMMIT" == "$PPA_COMMIT" ]]; then
echo "has_updates=false" >> $GITHUB_OUTPUT echo "has_updates=false" >> $GITHUB_OUTPUT
echo "📋 Commit $CURRENT_COMMIT already in changelog, skipping upload" echo "📋 Commit $CURRENT_COMMIT already uploaded to PPA, skipping"
else else
echo "has_updates=true" >> $GITHUB_OUTPUT echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 New commit detected: $CURRENT_COMMIT (changelog has $CHANGELOG_COMMIT)" echo "📋 New commit detected: $CURRENT_COMMIT (PPA has $PPA_COMMIT)"
fi fi
else else
echo "has_updates=true" >> $GITHUB_OUTPUT echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 Could not extract commit from changelog, proceeding with upload" echo "📋 Could not extract PPA commit, proceeding with upload"
fi fi
else else
echo "has_updates=true" >> $GITHUB_OUTPUT echo "has_updates=true" >> $GITHUB_OUTPUT
echo "📋 No changelog file found, proceeding with upload" echo "📋 No published version found in PPA, proceeding with upload"
fi fi
elif [[ -n "${{ github.event.inputs.package }}" ]]; then elif [[ -n "${{ github.event.inputs.package }}" ]]; then
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
@@ -83,49 +84,24 @@ jobs:
name: Upload to PPA name: Upload to PPA
needs: check-updates needs: check-updates
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
if: | if: |
github.event.inputs.force_upload == 'true' || github.event.inputs.force_upload == 'true' ||
github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_dispatch' ||
needs.check-updates.outputs.has_updates == 'true' needs.check-updates.outputs.has_updates == 'true'
steps: steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ steps.generate_token.outputs.token }}
- name: Check if last commit was automated
id: check-loop
run: |
LAST_COMMIT_MSG=$(git log -1 --pretty=%B | head -1)
if [[ "$LAST_COMMIT_MSG" == "ci: Auto-update PPA packages"* ]] || [[ "$LAST_COMMIT_MSG" == "ci: Auto-update OBS packages"* ]]; then
echo "⏭️ Last commit was automated ($LAST_COMMIT_MSG), skipping to prevent infinite loop"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "✅ Last commit was not automated, proceeding"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Set up Go - name: Set up Go
if: steps.check-loop.outputs.skip != 'true'
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "1.24" go-version: "1.24"
cache: false cache: false
- name: Install build dependencies - name: Install build dependencies
if: steps.check-loop.outputs.skip != 'true'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y \ sudo apt-get install -y \
@@ -138,7 +114,6 @@ jobs:
dpkg-dev dpkg-dev
- name: Configure GPG - name: Configure GPG
if: steps.check-loop.outputs.skip != 'true'
env: env:
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: | run: |
@@ -147,7 +122,6 @@ jobs:
echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV
- name: Determine packages to upload - name: Determine packages to upload
if: steps.check-loop.outputs.skip != 'true'
id: packages id: packages
run: | run: |
if [[ "${{ github.event.inputs.force_upload }}" == "true" ]]; then if [[ "${{ github.event.inputs.force_upload }}" == "true" ]]; then
@@ -180,7 +154,6 @@ jobs:
fi fi
- name: Upload to PPA - name: Upload to PPA
if: steps.check-loop.outputs.skip != 'true'
run: | run: |
PACKAGES="${{ steps.packages.outputs.packages }}" PACKAGES="${{ steps.packages.outputs.packages }}"
REBUILD_RELEASE="${{ github.event.inputs.rebuild_release }}" REBUILD_RELEASE="${{ github.event.inputs.rebuild_release }}"
@@ -240,34 +213,6 @@ jobs:
bash distro/scripts/ppa-upload.sh "$PACKAGES" "$PPA_NAME" questing "${BUILD_ARGS[@]}" bash distro/scripts/ppa-upload.sh "$PACKAGES" "$PPA_NAME" questing "${BUILD_ARGS[@]}"
fi fi
- name: Get changed packages
if: steps.check-loop.outputs.skip != 'true'
id: changed-packages
run: |
# Check if there are any changelog changes to commit
if git diff --exit-code distro/ubuntu/ >/dev/null 2>&1; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "📋 No changelog changes to commit"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
# Get list of changed packages for commit message (deduplicate)
CHANGED=$(git diff --name-only distro/ubuntu/ | grep 'debian/changelog' | sed 's|/debian/changelog||' | xargs -I{} basename {} | sort -u | tr '\n' ',' | sed 's/,$//')
echo "packages=$CHANGED" >> $GITHUB_OUTPUT
echo "📋 Changed packages: $CHANGED"
echo "📋 Debug - Changed files:"
git diff --name-only distro/ubuntu/ | grep 'debian/changelog' || echo "No changelog files found"
fi
- name: Commit changelog changes
if: steps.check-loop.outputs.skip != 'true' && steps.changed-packages.outputs.has_changes == 'true'
run: |
git config user.name "dms-ci[bot]"
git config user.email "dms-ci[bot]@users.noreply.github.com"
git add distro/ubuntu/*/debian/changelog
git commit -m "ci: Auto-update PPA packages [${{ steps.changed-packages.outputs.packages }}]" -m "🤖 Automated by GitHub Actions"
git pull --rebase origin master
git push
- name: Summary - name: Summary
run: | run: |
echo "### PPA Package Upload Complete" >> $GITHUB_STEP_SUMMARY echo "### PPA Package Upload Complete" >> $GITHUB_STEP_SUMMARY

View File

@@ -11,4 +11,9 @@ const (
dbusPortalSettingsInterface = "org.freedesktop.portal.Settings" dbusPortalSettingsInterface = "org.freedesktop.portal.Settings"
dbusPropsInterface = "org.freedesktop.DBus.Properties" dbusPropsInterface = "org.freedesktop.DBus.Properties"
dbusScreensaverName = "org.freedesktop.ScreenSaver"
dbusScreensaverPath = "/ScreenSaver"
dbusScreensaverPath2 = "/org/freedesktop/ScreenSaver"
dbusScreensaverInterface = "org.freedesktop.ScreenSaver"
) )

View File

@@ -24,6 +24,7 @@ func NewManager() (*Manager, error) {
state: &FreedeskState{ state: &FreedeskState{
Accounts: AccountsState{}, Accounts: AccountsState{},
Settings: SettingsState{}, Settings: SettingsState{},
Screensaver: ScreensaverState{},
}, },
stateMutex: sync.RWMutex{}, stateMutex: sync.RWMutex{},
systemConn: systemConn, systemConn: systemConn,
@@ -33,6 +34,7 @@ func NewManager() (*Manager, error) {
m.initializeAccounts() m.initializeAccounts()
m.initializeSettings() m.initializeSettings()
m.initializeScreensaver()
return m, nil return m, nil
} }

View File

@@ -0,0 +1,250 @@
package freedesktop
import (
"path/filepath"
"strings"
"sync/atomic"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
)
type screensaverHandler struct {
manager *Manager
}
func (m *Manager) initializeScreensaver() error {
if m.sessionConn == nil {
m.stateMutex.Lock()
m.state.Screensaver.Available = false
m.stateMutex.Unlock()
return nil
}
reply, err := m.sessionConn.RequestName(dbusScreensaverName, dbus.NameFlagDoNotQueue)
if err != nil {
log.Warnf("Failed to request screensaver name: %v", err)
m.stateMutex.Lock()
m.state.Screensaver.Available = false
m.stateMutex.Unlock()
return nil
}
if reply != dbus.RequestNameReplyPrimaryOwner {
log.Warnf("Screensaver name already owned by another process")
m.stateMutex.Lock()
m.state.Screensaver.Available = false
m.stateMutex.Unlock()
return nil
}
handler := &screensaverHandler{manager: m}
if err := m.sessionConn.Export(handler, dbusScreensaverPath, dbusScreensaverInterface); err != nil {
log.Warnf("Failed to export screensaver on %s: %v", dbusScreensaverPath, err)
return nil
}
if err := m.sessionConn.Export(handler, dbusScreensaverPath2, dbusScreensaverInterface); err != nil {
log.Warnf("Failed to export screensaver on %s: %v", dbusScreensaverPath2, err)
return nil
}
introNode := &introspect.Node{
Name: dbusScreensaverPath,
Interfaces: []introspect.Interface{
introspect.IntrospectData,
{Name: dbusScreensaverInterface},
},
}
if err := m.sessionConn.Export(introspect.NewIntrospectable(introNode), dbusScreensaverPath, "org.freedesktop.DBus.Introspectable"); err != nil {
log.Warnf("Failed to export introspectable on %s: %v", dbusScreensaverPath, err)
}
introNode2 := &introspect.Node{
Name: dbusScreensaverPath2,
Interfaces: []introspect.Interface{
introspect.IntrospectData,
{Name: dbusScreensaverInterface},
},
}
if err := m.sessionConn.Export(introspect.NewIntrospectable(introNode2), dbusScreensaverPath2, "org.freedesktop.DBus.Introspectable"); err != nil {
log.Warnf("Failed to export introspectable on %s: %v", dbusScreensaverPath2, err)
}
go m.watchPeerDisconnects()
m.stateMutex.Lock()
m.state.Screensaver.Available = true
m.state.Screensaver.Inhibited = false
m.state.Screensaver.Inhibitors = []ScreensaverInhibitor{}
m.stateMutex.Unlock()
log.Info("Screensaver inhibit listener initialized")
return nil
}
func (h *screensaverHandler) Inhibit(sender dbus.Sender, appName, reason string) (uint32, *dbus.Error) {
if appName == "" {
return 0, dbus.NewError("org.freedesktop.DBus.Error.InvalidArgs", []any{"application name required"})
}
if reason == "" {
return 0, dbus.NewError("org.freedesktop.DBus.Error.InvalidArgs", []any{"reason required"})
}
if strings.Contains(strings.ToLower(reason), "audio") && !strings.Contains(strings.ToLower(reason), "video") {
log.Debugf("Ignoring audio-only inhibit from %s: %s", appName, reason)
return 0, nil
}
if idx := strings.LastIndex(appName, "/"); idx != -1 && idx < len(appName)-1 {
appName = appName[idx+1:]
}
appName = filepath.Base(appName)
cookie := atomic.AddUint32(&h.manager.screensaverCookieCounter, 1)
inhibitor := ScreensaverInhibitor{
Cookie: cookie,
AppName: appName,
Reason: reason,
Peer: string(sender),
StartTime: time.Now().Unix(),
}
h.manager.stateMutex.Lock()
h.manager.state.Screensaver.Inhibitors = append(h.manager.state.Screensaver.Inhibitors, inhibitor)
h.manager.state.Screensaver.Inhibited = len(h.manager.state.Screensaver.Inhibitors) > 0
h.manager.stateMutex.Unlock()
log.Infof("Screensaver inhibited by %s (%s): %s -> cookie %08X", appName, sender, reason, cookie)
h.manager.NotifyScreensaverSubscribers()
return cookie, nil
}
func (h *screensaverHandler) UnInhibit(sender dbus.Sender, cookie uint32) *dbus.Error {
h.manager.stateMutex.Lock()
defer h.manager.stateMutex.Unlock()
found := false
inhibitors := h.manager.state.Screensaver.Inhibitors
for i, inh := range inhibitors {
if inh.Cookie != cookie {
continue
}
log.Infof("Screensaver uninhibited by %s (%s) cookie %08X", inh.AppName, sender, cookie)
h.manager.state.Screensaver.Inhibitors = append(inhibitors[:i], inhibitors[i+1:]...)
found = true
break
}
if !found {
log.Debugf("UnInhibit: no match for cookie %08X", cookie)
return nil
}
h.manager.state.Screensaver.Inhibited = len(h.manager.state.Screensaver.Inhibitors) > 0
go h.manager.NotifyScreensaverSubscribers()
return nil
}
func (m *Manager) watchPeerDisconnects() {
if m.sessionConn == nil {
return
}
if err := m.sessionConn.AddMatchSignal(
dbus.WithMatchInterface("org.freedesktop.DBus"),
dbus.WithMatchMember("NameOwnerChanged"),
); err != nil {
log.Warnf("Failed to watch peer disconnects: %v", err)
return
}
signals := make(chan *dbus.Signal, 64)
m.sessionConn.Signal(signals)
for sig := range signals {
if sig.Name != "org.freedesktop.DBus.NameOwnerChanged" {
continue
}
if len(sig.Body) < 3 {
continue
}
name, ok1 := sig.Body[0].(string)
newOwner, ok2 := sig.Body[2].(string)
if !ok1 || !ok2 {
continue
}
if newOwner != "" {
continue
}
m.removeInhibitorsByPeer(name)
}
}
func (m *Manager) removeInhibitorsByPeer(peer string) {
m.stateMutex.Lock()
defer m.stateMutex.Unlock()
var remaining []ScreensaverInhibitor
var removed []ScreensaverInhibitor
for _, inh := range m.state.Screensaver.Inhibitors {
if inh.Peer == peer {
removed = append(removed, inh)
continue
}
remaining = append(remaining, inh)
}
if len(removed) == 0 {
return
}
for _, inh := range removed {
log.Infof("Screensaver: peer %s died, removing inhibitor from %s (cookie %08X)", peer, inh.AppName, inh.Cookie)
}
m.state.Screensaver.Inhibitors = remaining
m.state.Screensaver.Inhibited = len(remaining) > 0
go m.NotifyScreensaverSubscribers()
}
func (m *Manager) GetScreensaverState() ScreensaverState {
m.stateMutex.RLock()
defer m.stateMutex.RUnlock()
return m.state.Screensaver
}
func (m *Manager) SubscribeScreensaver(id string) chan ScreensaverState {
ch := make(chan ScreensaverState, 64)
m.screensaverSubscribers.Store(id, ch)
return ch
}
func (m *Manager) UnsubscribeScreensaver(id string) {
if val, ok := m.screensaverSubscribers.LoadAndDelete(id); ok {
close(val)
}
}
func (m *Manager) NotifyScreensaverSubscribers() {
state := m.GetScreensaverState()
m.screensaverSubscribers.Range(func(key string, ch chan ScreensaverState) bool {
select {
case ch <- state:
default:
}
return true
})
}

View File

@@ -29,9 +29,24 @@ type SettingsState struct {
ColorScheme uint32 `json:"colorScheme"` ColorScheme uint32 `json:"colorScheme"`
} }
type ScreensaverInhibitor struct {
Cookie uint32 `json:"cookie"`
AppName string `json:"appName"`
Reason string `json:"reason"`
Peer string `json:"peer"`
StartTime int64 `json:"startTime"`
}
type ScreensaverState struct {
Available bool `json:"available"`
Inhibited bool `json:"inhibited"`
Inhibitors []ScreensaverInhibitor `json:"inhibitors"`
}
type FreedeskState struct { type FreedeskState struct {
Accounts AccountsState `json:"accounts"` Accounts AccountsState `json:"accounts"`
Settings SettingsState `json:"settings"` Settings SettingsState `json:"settings"`
Screensaver ScreensaverState `json:"screensaver"`
} }
type Manager struct { type Manager struct {
@@ -43,4 +58,6 @@ type Manager struct {
settingsObj dbus.BusObject settingsObj dbus.BusObject
currentUID uint64 currentUID uint64
subscribers syncmap.Map[string, chan FreedeskState] subscribers syncmap.Map[string, chan FreedeskState]
screensaverSubscribers syncmap.Map[string, chan ScreensaverState]
screensaverCookieCounter uint32
} }

View File

@@ -33,7 +33,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
) )
const APIVersion = 23 const APIVersion = 24
var CLIVersion = "dev" var CLIVersion = "dev"
@@ -702,6 +702,38 @@ func handleSubscribe(conn net.Conn, req models.Request) {
}() }()
} }
if shouldSubscribe("freedesktop.screensaver") && freedesktopManager != nil {
wg.Add(1)
screensaverChan := freedesktopManager.SubscribeScreensaver(clientID + "-screensaver")
go func() {
defer wg.Done()
defer freedesktopManager.UnsubscribeScreensaver(clientID + "-screensaver")
initialState := freedesktopManager.GetScreensaverState()
select {
case eventChan <- ServiceEvent{Service: "freedesktop.screensaver", Data: initialState}:
case <-stopChan:
return
}
for {
select {
case state, ok := <-screensaverChan:
if !ok {
return
}
select {
case eventChan <- ServiceEvent{Service: "freedesktop.screensaver", Data: state}:
case <-stopChan:
return
}
case <-stopChan:
return
}
}
}()
}
if shouldSubscribe("gamma") && waylandManager != nil { if shouldSubscribe("gamma") && waylandManager != nil {
wg.Add(1) wg.Add(1)
waylandChan := waylandManager.Subscribe(clientID + "-gamma") waylandChan := waylandManager.Subscribe(clientID + "-gamma")

View File

@@ -255,29 +255,20 @@ if [ "$IS_GIT_PACKAGE" = false ] && [ -n "$GIT_REPO" ]; then
else else
info "Updating changelog to latest tag: $LATEST_TAG" info "Updating changelog to latest tag: $LATEST_TAG"
fi fi
OLD_ENTRY_START=$(grep -n "^${SOURCE_NAME} (" debian/changelog | sed -n '2p' | cut -d: -f1)
if [ -n "$OLD_ENTRY_START" ]; then
CHANGELOG_CONTENT=$(tail -n +"$OLD_ENTRY_START" debian/changelog)
else
CHANGELOG_CONTENT=""
fi
if [ "$PPA_NUM" -gt 1 ]; then if [ "$PPA_NUM" -gt 1 ]; then
CHANGELOG_MSG="Rebuild for packaging fixes (ppa${PPA_NUM})" CHANGELOG_MSG="Rebuild for packaging fixes (ppa${PPA_NUM})"
else else
CHANGELOG_MSG="Upstream release ${LATEST_TAG}" CHANGELOG_MSG="Upstream release ${LATEST_TAG}"
fi fi
CHANGELOG_ENTRY="${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium # Single changelog entry (full history available on Launchpad)
cat >debian/changelog <<EOF
${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
* ${CHANGELOG_MSG} * ${CHANGELOG_MSG}
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)" -- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)
echo "$CHANGELOG_ENTRY" >debian/changelog EOF
if [ -n "$CHANGELOG_CONTENT" ]; then
echo "" >>debian/changelog
echo "$CHANGELOG_CONTENT" >>debian/changelog
fi
success "Version updated to $NEW_VERSION" success "Version updated to $NEW_VERSION"
CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version) CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version)
@@ -406,24 +397,14 @@ if [ "$IS_GIT_PACKAGE" = true ] && [ -n "$GIT_REPO" ]; then
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}" NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
OLD_ENTRY_START=$(grep -n "^${SOURCE_NAME} (" debian/changelog | sed -n '2p' | cut -d: -f1) # Single changelog entry (git snapshots don't need history)
if [ -n "$OLD_ENTRY_START" ]; then cat >debian/changelog <<EOF
CHANGELOG_CONTENT=$(tail -n +"$OLD_ENTRY_START" debian/changelog) ${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
else
CHANGELOG_CONTENT=""
fi
CHANGELOG_ENTRY="${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
* Git snapshot (commit ${GIT_COMMIT_COUNT}: ${GIT_COMMIT_HASH}) * Git snapshot (commit ${GIT_COMMIT_COUNT}: ${GIT_COMMIT_HASH})
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)" -- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)
EOF
echo "$CHANGELOG_ENTRY" >debian/changelog
if [ -n "$CHANGELOG_CONTENT" ]; then
echo "" >>debian/changelog
echo "$CHANGELOG_CONTENT" >>debian/changelog
fi
success "Version updated to $NEW_VERSION" success "Version updated to $NEW_VERSION"
CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version) CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version)

View File

@@ -1,5 +1,5 @@
dms-git (1.0.2+git2528.d336866fppa1) questing; urgency=medium dms-git (1.0.2+git2531.208266dfppa1) questing; urgency=medium
* Git snapshot (commit 2528: d336866f) * Git snapshot (commit 2531: 208266df)
-- Avenge Media <AvengeMedia.US@gmail.com> Sun, 14 Dec 2025 03:57:33 +0000 -- Avenge Media <AvengeMedia.US@gmail.com> Sun, 14 Dec 2025 15:20:45 +0000

View File

@@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store
Singleton { Singleton {
id: root id: root
readonly property int settingsConfigVersion: 2 readonly property int settingsConfigVersion: 3
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true" readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
@@ -352,6 +352,7 @@ Singleton {
property string customPowerActionReboot: "" property string customPowerActionReboot: ""
property string customPowerActionPowerOff: "" property string customPowerActionPowerOff: ""
property bool updaterHideWidget: false
property bool updaterUseCustomCommand: false property bool updaterUseCustomCommand: false
property string updaterCustomCommand: "" property string updaterCustomCommand: ""
property string updaterTerminalAdditionalParams: "" property string updaterTerminalAdditionalParams: ""

View File

@@ -1,5 +1,4 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell import Quickshell
@@ -17,40 +16,67 @@ Singleton {
pciId: "", pciId: "",
mountPath: "/", mountPath: "/",
minimumWidth: true, minimumWidth: true,
showSwap: false showSwap: false,
} mediaSize: 1,
leftModel.append(dummy) showNetworkIcon: true,
centerModel.append(dummy) showBluetoothIcon: true,
rightModel.append(dummy) showAudioIcon: true,
showVpnIcon: true,
showBrightnessIcon: false,
showMicIcon: false,
showBatteryIcon: false,
showPrinterIcon: false
};
leftModel.append(dummy);
centerModel.append(dummy);
rightModel.append(dummy);
update(leftModel, left) update(leftModel, left);
update(centerModel, center) update(centerModel, center);
update(rightModel, right) update(rightModel, right);
} }
function update(model, order) { function update(model, order) {
model.clear() model.clear();
for (var i = 0; i < order.length; i++) { for (var i = 0; i < order.length; i++) {
var widgetId = typeof order[i] === "string" ? order[i] : order[i].id var isObj = typeof order[i] !== "string";
var enabled = typeof order[i] === "string" ? true : order[i].enabled var widgetId = isObj ? order[i].id : order[i];
var size = typeof order[i] === "string" ? undefined : order[i].size
var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex
var pciId = typeof order[i] === "string" ? undefined : order[i].pciId
var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath
var minimumWidth = typeof order[i] === "string" ? undefined : order[i].minimumWidth
var showSwap = typeof order[i] === "string" ? undefined : order[i].showSwap
var item = { var item = {
widgetId: widgetId, widgetId: widgetId,
enabled: enabled enabled: isObj ? order[i].enabled : true
} };
if (size !== undefined) item.size = size if (isObj && order[i].size !== undefined)
if (selectedGpuIndex !== undefined) item.selectedGpuIndex = selectedGpuIndex item.size = order[i].size;
if (pciId !== undefined) item.pciId = pciId if (isObj && order[i].selectedGpuIndex !== undefined)
if (mountPath !== undefined) item.mountPath = mountPath item.selectedGpuIndex = order[i].selectedGpuIndex;
if (minimumWidth !== undefined) item.minimumWidth = minimumWidth if (isObj && order[i].pciId !== undefined)
if (showSwap !== undefined) item.showSwap = showSwap item.pciId = order[i].pciId;
if (isObj && order[i].mountPath !== undefined)
item.mountPath = order[i].mountPath;
if (isObj && order[i].minimumWidth !== undefined)
item.minimumWidth = order[i].minimumWidth;
if (isObj && order[i].showSwap !== undefined)
item.showSwap = order[i].showSwap;
if (isObj && order[i].mediaSize !== undefined)
item.mediaSize = order[i].mediaSize;
if (isObj && order[i].showNetworkIcon !== undefined)
item.showNetworkIcon = order[i].showNetworkIcon;
if (isObj && order[i].showBluetoothIcon !== undefined)
item.showBluetoothIcon = order[i].showBluetoothIcon;
if (isObj && order[i].showAudioIcon !== undefined)
item.showAudioIcon = order[i].showAudioIcon;
if (isObj && order[i].showVpnIcon !== undefined)
item.showVpnIcon = order[i].showVpnIcon;
if (isObj && order[i].showBrightnessIcon !== undefined)
item.showBrightnessIcon = order[i].showBrightnessIcon;
if (isObj && order[i].showMicIcon !== undefined)
item.showMicIcon = order[i].showMicIcon;
if (isObj && order[i].showBatteryIcon !== undefined)
item.showBatteryIcon = order[i].showBatteryIcon;
if (isObj && order[i].showPrinterIcon !== undefined)
item.showPrinterIcon = order[i].showPrinterIcon;
model.append(item) model.append(item);
} }
} }
} }

View File

@@ -250,6 +250,7 @@ var SPEC = {
customPowerActionReboot: { def: "" }, customPowerActionReboot: { def: "" },
customPowerActionPowerOff: { def: "" }, customPowerActionPowerOff: { def: "" },
updaterHideWidget: { def: false },
updaterUseCustomCommand: { def: false }, updaterUseCustomCommand: { def: false },
updaterCustomCommand: { def: "" }, updaterCustomCommand: { def: "" },
updaterTerminalAdditionalParams: { def: "" }, updaterTerminalAdditionalParams: { def: "" },

View File

@@ -113,6 +113,12 @@ function migrateToVersion(obj, targetVersion) {
settings.configVersion = 2; settings.configVersion = 2;
} }
if (currentVersion < 3) {
console.info("Migrating settings from version", currentVersion, "to version 3");
console.info("Per-widget controlCenterButton config now supported via widgetData properties");
settings.configVersion = 3;
}
return settings; return settings;
} }

View File

@@ -13,7 +13,7 @@ DankModal {
layerNamespace: "dms:clipboard" layerNamespace: "dms:clipboard"
HyprlandFocusGrab { HyprlandFocusGrab {
windows: [clipboardHistoryModal.contentWindow] windows: [clipboardHistoryModal.contentWindow, clipboardHistoryModal.backgroundWindow]
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
} }

View File

@@ -14,7 +14,7 @@ DankModal {
keepPopoutsOpen: true keepPopoutsOpen: true
HyprlandFocusGrab { HyprlandFocusGrab {
windows: [root.contentWindow] windows: [root.contentWindow, root.backgroundWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus active: root.useHyprlandFocusGrab && root.shouldHaveFocus
} }

View File

@@ -11,7 +11,7 @@ DankModal {
layerNamespace: "dms:spotlight" layerNamespace: "dms:spotlight"
HyprlandFocusGrab { HyprlandFocusGrab {
windows: [spotlightModal.contentWindow] windows: [spotlightModal.contentWindow, spotlightModal.backgroundWindow]
active: spotlightModal.useHyprlandFocusGrab && spotlightModal.shouldHaveFocus active: spotlightModal.useHyprlandFocusGrab && spotlightModal.shouldHaveFocus
} }

View File

@@ -27,7 +27,7 @@ PluginComponent {
ccDetailContent: Component { ccDetailContent: Component {
VpnDetailContent { VpnDetailContent {
listHeight: 180 listHeight: 260
} }
} }
} }

View File

@@ -18,14 +18,17 @@ Item {
function getDetailHeight(section) { function getDetailHeight(section) {
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999; const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
if (section === "wifi") switch (true) {
case section === "wifi":
case section === "bluetooth":
case section === "builtin_vpn":
return Math.min(350, maxAvailable); return Math.min(350, maxAvailable);
if (section === "bluetooth") case section.startsWith("brightnessSlider_"):
return Math.min(350, maxAvailable);
if (section.startsWith("brightnessSlider_"))
return Math.min(400, maxAvailable); return Math.min(400, maxAvailable);
default:
return Math.min(250, maxAvailable); return Math.min(250, maxAvailable);
} }
}
Loader { Loader {
id: pluginDetailLoader id: pluginDetailLoader

View File

@@ -552,7 +552,13 @@ DankPopout {
} }
} }
Item {
width: parent.width
height: profileButtonGroup.height * profileButtonGroup.scale
DankButtonGroup { DankButtonGroup {
id: profileButtonGroup
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
property int currentProfileIndex: { property int currentProfileIndex: {
if (typeof PowerProfiles === "undefined") if (typeof PowerProfiles === "undefined")
@@ -560,16 +566,19 @@ DankPopout {
return profileModel.findIndex(profile => root.isActiveProfile(profile)); return profileModel.findIndex(profile => root.isActiveProfile(profile));
} }
scale: Math.min(1, parent.width / implicitWidth)
transformOrigin: Item.Center
anchors.horizontalCenter: parent.horizontalCenter
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile)) model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
currentIndex: currentProfileIndex currentIndex: currentProfileIndex
selectionMode: "single" selectionMode: "single"
anchors.horizontalCenter: parent.horizontalCenter
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (!selected) if (!selected)
return; return;
root.setProfile(profileModel[index]); root.setProfile(profileModel[index]);
} }
} }
}
StyledRect { StyledRect {
width: parent.width width: parent.width

View File

@@ -15,6 +15,7 @@ DankPopout {
triggerY = y; triggerY = y;
triggerWidth = width; triggerWidth = width;
triggerSection = section; triggerSection = section;
triggerScreen = screen;
root.screen = screen; root.screen = screen;
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4); storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
@@ -102,6 +103,8 @@ DankPopout {
screen: triggerScreen screen: triggerScreen
shouldBeVisible: false shouldBeVisible: false
onBackgroundClicked: close()
content: Component { content: Component {
Rectangle { Rectangle {
id: layoutContent id: layoutContent

View File

@@ -13,14 +13,14 @@ BasePill {
property var widgetData: null property var widgetData: null
property string screenName: "" property string screenName: ""
property string screenModel: "" property string screenModel: ""
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon property bool showNetworkIcon: widgetData?.showNetworkIcon !== undefined ? widgetData.showNetworkIcon : SettingsData.controlCenterShowNetworkIcon
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon property bool showBluetoothIcon: widgetData?.showBluetoothIcon !== undefined ? widgetData.showBluetoothIcon : SettingsData.controlCenterShowBluetoothIcon
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon property bool showAudioIcon: widgetData?.showAudioIcon !== undefined ? widgetData.showAudioIcon : SettingsData.controlCenterShowAudioIcon
property bool showVpnIcon: SettingsData.controlCenterShowVpnIcon property bool showVpnIcon: widgetData?.showVpnIcon !== undefined ? widgetData.showVpnIcon : SettingsData.controlCenterShowVpnIcon
property bool showBrightnessIcon: SettingsData.controlCenterShowBrightnessIcon property bool showBrightnessIcon: widgetData?.showBrightnessIcon !== undefined ? widgetData.showBrightnessIcon : SettingsData.controlCenterShowBrightnessIcon
property bool showMicIcon: SettingsData.controlCenterShowMicIcon property bool showMicIcon: widgetData?.showMicIcon !== undefined ? widgetData.showMicIcon : SettingsData.controlCenterShowMicIcon
property bool showBatteryIcon: SettingsData.controlCenterShowBatteryIcon property bool showBatteryIcon: widgetData?.showBatteryIcon !== undefined ? widgetData.showBatteryIcon : SettingsData.controlCenterShowBatteryIcon
property bool showPrinterIcon: SettingsData.controlCenterShowPrinterIcon property bool showPrinterIcon: widgetData?.showPrinterIcon !== undefined ? widgetData.showPrinterIcon : SettingsData.controlCenterShowPrinterIcon
Loader { Loader {
active: root.showPrinterIcon active: root.showPrinterIcon

View File

@@ -358,7 +358,7 @@ Item {
IconImage { IconImage {
id: iconImg id: iconImg
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? (parent.width - Theme.barIconSize(root.barThickness)) / 2 : Theme.spacingXS anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: Theme.barIconSize(root.barThickness) width: Theme.barIconSize(root.barThickness)
height: Theme.barIconSize(root.barThickness) height: Theme.barIconSize(root.barThickness)
@@ -385,7 +385,7 @@ Item {
DankIcon { DankIcon {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? (parent.width - Theme.barIconSize(root.barThickness)) / 2 : Theme.spacingXS anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
size: Theme.barIconSize(root.barThickness) size: Theme.barIconSize(root.barThickness)
name: "sports_esports" name: "sports_esports"
@@ -607,7 +607,7 @@ Item {
IconImage { IconImage {
id: iconImg id: iconImg
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? (parent.width - Theme.barIconSize(root.barThickness)) / 2 : Theme.spacingXS anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: Theme.barIconSize(root.barThickness) width: Theme.barIconSize(root.barThickness)
height: Theme.barIconSize(root.barThickness) height: Theme.barIconSize(root.barThickness)
@@ -634,7 +634,7 @@ Item {
DankIcon { DankIcon {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? (parent.width - Theme.barIconSize(root.barThickness)) / 2 : Theme.spacingXS anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
size: Theme.barIconSize(root.barThickness) size: Theme.barIconSize(root.barThickness)
name: "sports_esports" name: "sports_esports"

View File

@@ -11,6 +11,9 @@ BasePill {
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0 readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
readonly property bool isChecking: SystemUpdateService.isChecking readonly property bool isChecking: SystemUpdateService.isChecking
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
width : (SettingsData.updaterHideWidget && !hasUpdates) ? 0 : (18 + horizontalPadding * 2)
Ref { Ref {
service: SystemUpdateService service: SystemUpdateService
} }

View File

@@ -1,13 +1,11 @@
import QtCore import QtCore
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.Greetd import Quickshell.Services.Greetd
import Quickshell.Services.Pam
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -22,74 +20,67 @@ Item {
property string hyprlandCurrentLayout: "" property string hyprlandCurrentLayout: ""
property string hyprlandKeyboard: "" property string hyprlandKeyboard: ""
property int hyprlandLayoutCount: 0 property int hyprlandLayoutCount: 0
property bool isPrimaryScreen: { property bool isPrimaryScreen: !Quickshell.screens?.length || screenName === Quickshell.screens[0]?.name
if (!Qt.application.screens || Qt.application.screens.length === 0)
return true
if (!screenName || screenName === "")
return true
return screenName === Qt.application.screens[0].name
}
signal launchRequested signal launchRequested
function pickRandomFact() { function pickRandomFact() {
randomFact = Facts.getRandomFact() randomFact = Facts.getRandomFact();
} }
property bool weatherInitialized: false property bool weatherInitialized: false
function initWeatherService() { function initWeatherService() {
if (weatherInitialized) if (weatherInitialized)
return return;
if (!GreetdSettings.settingsLoaded) if (!GreetdSettings.settingsLoaded)
return return;
if (!GreetdSettings.weatherEnabled) if (!GreetdSettings.weatherEnabled)
return return;
weatherInitialized = true;
weatherInitialized = true WeatherService.addRef();
WeatherService.addRef() WeatherService.forceRefresh();
WeatherService.forceRefresh()
} }
Connections { Connections {
target: GreetdSettings target: GreetdSettings
function onSettingsLoadedChanged() { function onSettingsLoadedChanged() {
if (GreetdSettings.settingsLoaded) if (GreetdSettings.settingsLoaded)
initWeatherService() initWeatherService();
} }
} }
Component.onCompleted: { Component.onCompleted: {
pickRandomFact() pickRandomFact();
initWeatherService() initWeatherService();
if (isPrimaryScreen) { if (isPrimaryScreen) {
sessionListProc.running = true sessionListProc.running = true;
applyLastSuccessfulUser() applyLastSuccessfulUser();
} }
if (CompositorService.isHyprland) if (CompositorService.isHyprland)
updateHyprlandLayout() updateHyprlandLayout();
} }
function applyLastSuccessfulUser() { function applyLastSuccessfulUser() {
const lastUser = GreetdMemory.lastSuccessfulUser const lastUser = GreetdMemory.lastSuccessfulUser;
if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) { if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) {
GreeterState.username = lastUser GreeterState.username = lastUser;
GreeterState.usernameInput = lastUser GreeterState.usernameInput = lastUser;
GreeterState.showPasswordInput = true GreeterState.showPasswordInput = true;
PortalService.getGreeterUserProfileImage(lastUser) PortalService.getGreeterUserProfileImage(lastUser);
} }
} }
Component.onDestruction: { Component.onDestruction: {
if (weatherInitialized) if (weatherInitialized)
WeatherService.removeRef() WeatherService.removeRef();
} }
function updateHyprlandLayout() { function updateHyprlandLayout() {
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
hyprlandLayoutProcess.running = true hyprlandLayoutProcess.running = true;
} }
} }
@@ -100,27 +91,27 @@ Item {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
try { try {
const data = JSON.parse(text) const data = JSON.parse(text);
const mainKeyboard = data.keyboards.find(kb => kb.main === true) const mainKeyboard = data.keyboards.find(kb => kb.main === true);
hyprlandKeyboard = mainKeyboard.name hyprlandKeyboard = mainKeyboard.name;
if (mainKeyboard && mainKeyboard.active_keymap) { if (mainKeyboard && mainKeyboard.active_keymap) {
const parts = mainKeyboard.active_keymap.split(" ") const parts = mainKeyboard.active_keymap.split(" ");
if (parts.length > 0) { if (parts.length > 0) {
hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase() hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase();
} else { } else {
hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase() hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase();
} }
} else { } else {
hyprlandCurrentLayout = "" hyprlandCurrentLayout = "";
} }
if (mainKeyboard && mainKeyboard.layout_names) { if (mainKeyboard && mainKeyboard.layout_names) {
hyprlandLayoutCount = mainKeyboard.layout_names.length hyprlandLayoutCount = mainKeyboard.layout_names.length;
} else { } else {
hyprlandLayoutCount = 0 hyprlandLayoutCount = 0;
} }
} catch (e) { } catch (e) {
hyprlandCurrentLayout = "" hyprlandCurrentLayout = "";
hyprlandLayoutCount = 0 hyprlandLayoutCount = 0;
} }
} }
} }
@@ -132,7 +123,7 @@ Item {
function onRawEvent(event) { function onRawEvent(event) {
if (event.name === "activelayout") if (event.name === "activelayout")
updateHyprlandLayout() updateHyprlandLayout();
} }
} }
@@ -140,7 +131,7 @@ Item {
target: GreetdMemory target: GreetdMemory
enabled: isPrimaryScreen enabled: isPrimaryScreen
function onLastSuccessfulUserChanged() { function onLastSuccessfulUserChanged() {
applyLastSuccessfulUser() applyLastSuccessfulUser();
} }
} }
@@ -148,7 +139,7 @@ Item {
target: GreeterState target: GreeterState
function onUsernameChanged() { function onUsernameChanged() {
if (GreeterState.username) { if (GreeterState.username) {
PortalService.getGreeterUserProfileImage(GreeterState.username) PortalService.getGreeterUserProfileImage(GreeterState.username);
} }
} }
} }
@@ -157,10 +148,10 @@ Item {
anchors.fill: parent anchors.fill: parent
screenName: root.screenName screenName: root.screenName
visible: { visible: {
var _ = SessionData.perMonitorWallpaper var _ = SessionData.perMonitorWallpaper;
var __ = SessionData.monitorWallpapers var __ = SessionData.monitorWallpapers;
var currentWallpaper = SessionData.getMonitorWallpaper(screenName) var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
return !currentWallpaper || currentWallpaper === "" || (currentWallpaper && currentWallpaper.startsWith("#")) return !currentWallpaper || currentWallpaper === "" || (currentWallpaper && currentWallpaper.startsWith("#"));
} }
} }
@@ -169,10 +160,10 @@ Item {
anchors.fill: parent anchors.fill: parent
source: { source: {
var _ = SessionData.perMonitorWallpaper var _ = SessionData.perMonitorWallpaper;
var __ = SessionData.monitorWallpapers var __ = SessionData.monitorWallpapers;
var currentWallpaper = SessionData.getMonitorWallpaper(screenName) var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : "" return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : "";
} }
fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode) fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode)
smooth: true smooth: true
@@ -213,10 +204,12 @@ Item {
color: "transparent" color: "transparent"
Item { Item {
anchors.centerIn: parent id: clockContainer
anchors.verticalCenterOffset: -100 anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.verticalCenter
anchors.bottomMargin: 60
width: parent.width width: parent.width
height: 140 height: clockText.implicitHeight
Row { Row {
id: clockText id: clockText
@@ -225,10 +218,8 @@ Item {
spacing: 0 spacing: 0
property string fullTimeStr: { property string fullTimeStr: {
const format = GreetdSettings.use24HourClock const format = GreetdSettings.use24HourClock ? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm") : (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP");
? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm") return systemClock.date.toLocaleTimeString(Qt.locale(), format);
: (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP")
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
} }
property var timeParts: fullTimeStr.split(':') property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || "" property string hours: timeParts[0] || ""
@@ -236,8 +227,8 @@ Item {
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : "" property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '') property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
property string ampm: { property string ampm: {
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i) const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i);
return match ? match[0].trim() : "" return match ? match[0].trim() : "";
} }
property bool hasSeconds: timeParts.length > 2 property bool hasSeconds: timeParts.length > 2
@@ -332,13 +323,15 @@ Item {
} }
StyledText { StyledText {
anchors.centerIn: parent id: dateText
anchors.verticalCenterOffset: -10 anchors.horizontalCenter: parent.horizontalCenter
anchors.top: clockContainer.bottom
anchors.topMargin: 4
text: { text: {
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) { if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat) return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat);
} }
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat) return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat);
} }
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: "white" color: "white"
@@ -346,8 +339,9 @@ Item {
} }
Item { Item {
anchors.centerIn: parent anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenterOffset: 80 anchors.top: dateText.bottom
anchors.topMargin: Theme.spacingL
width: 380 width: 380
height: 140 height: 140
@@ -364,14 +358,14 @@ Item {
Layout.preferredHeight: 60 Layout.preferredHeight: 60
imageSource: { imageSource: {
if (PortalService.profileImage === "") { if (PortalService.profileImage === "") {
return "" return "";
} }
if (PortalService.profileImage.startsWith("/")) { if (PortalService.profileImage.startsWith("/")) {
return "file://" + PortalService.profileImage return "file://" + PortalService.profileImage;
} }
return PortalService.profileImage return PortalService.profileImage;
} }
fallbackIcon: "person" fallbackIcon: "person"
} }
@@ -405,57 +399,58 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2 anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
anchors.rightMargin: { anchors.rightMargin: {
let margin = Theme.spacingM let margin = Theme.spacingM;
if (GreeterState.showPasswordInput && revealButton.visible) { if (GreeterState.showPasswordInput && revealButton.visible) {
margin += revealButton.width margin += revealButton.width;
} }
if (virtualKeyboardButton.visible) { if (virtualKeyboardButton.visible) {
margin += virtualKeyboardButton.width margin += virtualKeyboardButton.width;
} }
if (enterButton.visible) { if (enterButton.visible) {
margin += enterButton.width + 2 margin += enterButton.width + 2;
} }
return margin return margin;
} }
opacity: 0 opacity: 0
focus: true focus: true
echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal
onTextChanged: { onTextChanged: {
if (syncingFromState) return if (syncingFromState)
return;
if (GreeterState.showPasswordInput) { if (GreeterState.showPasswordInput) {
GreeterState.passwordBuffer = text GreeterState.passwordBuffer = text;
} else { } else {
GreeterState.usernameInput = text GreeterState.usernameInput = text;
} }
} }
onAccepted: { onAccepted: {
if (GreeterState.showPasswordInput) { if (GreeterState.showPasswordInput) {
if (Greetd.state === GreetdState.Inactive && GreeterState.username) { if (Greetd.state === GreetdState.Inactive && GreeterState.username) {
Greetd.createSession(GreeterState.username) Greetd.createSession(GreeterState.username);
} }
} else { } else {
if (text.trim()) { if (text.trim()) {
GreeterState.username = text.trim() GreeterState.username = text.trim();
GreeterState.showPasswordInput = true GreeterState.showPasswordInput = true;
PortalService.getGreeterUserProfileImage(GreeterState.username) PortalService.getGreeterUserProfileImage(GreeterState.username);
GreeterState.passwordBuffer = "" GreeterState.passwordBuffer = "";
syncingFromState = true syncingFromState = true;
text = "" text = "";
syncingFromState = false syncingFromState = false;
} }
} }
} }
Component.onCompleted: { Component.onCompleted: {
syncingFromState = true syncingFromState = true;
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput;
syncingFromState = false syncingFromState = false;
if (isPrimaryScreen && !powerMenu.isVisible) if (isPrimaryScreen && !powerMenu.isVisible)
forceActiveFocus() forceActiveFocus();
} }
onVisibleChanged: { onVisibleChanged: {
if (visible && isPrimaryScreen && !powerMenu.isVisible) if (visible && isPrimaryScreen && !powerMenu.isVisible)
forceActiveFocus() forceActiveFocus();
} }
} }
@@ -475,15 +470,15 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { text: {
if (GreeterState.unlocking) { if (GreeterState.unlocking) {
return "Logging in..." return "Logging in...";
} }
if (Greetd.state !== GreetdState.Inactive) { if (Greetd.state !== GreetdState.Inactive) {
return "Authenticating..." return "Authenticating...";
} }
if (GreeterState.showPasswordInput) { if (GreeterState.showPasswordInput) {
return "Password..." return "Password...";
} }
return "Username..." return "Username...";
} }
color: GreeterState.unlocking ? Theme.primary : (Greetd.state !== GreetdState.Inactive ? Theme.primary : Theme.outline) color: GreeterState.unlocking ? Theme.primary : (Greetd.state !== GreetdState.Inactive ? Theme.primary : Theme.outline)
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -513,11 +508,11 @@ Item {
text: { text: {
if (GreeterState.showPasswordInput) { if (GreeterState.showPasswordInput) {
if (parent.showPassword) { if (parent.showPassword) {
return GreeterState.passwordBuffer return GreeterState.passwordBuffer;
} }
return "•".repeat(GreeterState.passwordBuffer.length) return "•".repeat(GreeterState.passwordBuffer.length);
} }
return GreeterState.usernameInput return GreeterState.usernameInput;
} }
color: Theme.surfaceText color: Theme.surfaceText
font.pixelSize: (GreeterState.showPasswordInput && !parent.showPassword) ? Theme.fontSizeLarge : Theme.fontSizeMedium font.pixelSize: (GreeterState.showPasswordInput && !parent.showPassword) ? Theme.fontSizeLarge : Theme.fontSizeMedium
@@ -558,9 +553,9 @@ Item {
enabled: visible enabled: visible
onClicked: { onClicked: {
if (keyboard_controller.isKeyboardActive) { if (keyboard_controller.isKeyboardActive) {
keyboard_controller.hide() keyboard_controller.hide();
} else { } else {
keyboard_controller.show() keyboard_controller.show();
} }
} }
} }
@@ -578,15 +573,15 @@ Item {
onClicked: { onClicked: {
if (GreeterState.showPasswordInput) { if (GreeterState.showPasswordInput) {
if (GreeterState.username) { if (GreeterState.username) {
Greetd.createSession(GreeterState.username) Greetd.createSession(GreeterState.username);
} }
} else { } else {
if (inputField.text.trim()) { if (inputField.text.trim()) {
GreeterState.username = inputField.text.trim() GreeterState.username = inputField.text.trim();
GreeterState.showPasswordInput = true GreeterState.showPasswordInput = true;
PortalService.getGreeterUserProfileImage(GreeterState.username) PortalService.getGreeterUserProfileImage(GreeterState.username);
GreeterState.passwordBuffer = "" GreeterState.passwordBuffer = "";
inputField.text = "" inputField.text = "";
} }
} }
} }
@@ -615,10 +610,10 @@ Item {
Layout.bottomMargin: -Theme.spacingS Layout.bottomMargin: -Theme.spacingS
text: { text: {
if (GreeterState.pamState === "error") if (GreeterState.pamState === "error")
return "Authentication error - try again" return "Authentication error - try again";
if (GreeterState.pamState === "fail") if (GreeterState.pamState === "fail")
return "Incorrect password" return "Incorrect password";
return "" return "";
} }
color: Theme.error color: Theme.error
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -675,9 +670,9 @@ Item {
cornerRadius: parent.radius cornerRadius: parent.radius
enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput
onClicked: { onClicked: {
GreeterState.reset() GreeterState.reset();
inputField.text = "" inputField.text = "";
PortalService.profileImage = "" PortalService.profileImage = "";
} }
} }
} }
@@ -696,11 +691,11 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: { visible: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
return NiriService.keyboardLayoutNames.length > 1 return NiriService.keyboardLayoutNames.length > 1;
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
return hyprlandLayoutCount > 1 return hyprlandLayoutCount > 1;
} }
return false return false;
} }
Row { Row {
@@ -726,17 +721,18 @@ Item {
StyledText { StyledText {
text: { text: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
const layout = NiriService.getCurrentKeyboardLayoutName() const layout = NiriService.getCurrentKeyboardLayoutName();
if (!layout) return "" if (!layout)
const parts = layout.split(" ") return "";
const parts = layout.split(" ");
if (parts.length > 0) { if (parts.length > 0) {
return parts[0].substring(0, 2).toUpperCase() return parts[0].substring(0, 2).toUpperCase();
} }
return layout.substring(0, 2).toUpperCase() return layout.substring(0, 2).toUpperCase();
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
return hyprlandCurrentLayout return hyprlandCurrentLayout;
} }
return "" return "";
} }
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Light font.weight: Font.Light
@@ -753,15 +749,10 @@ Item {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
NiriService.cycleKeyboardLayout() NiriService.cycleKeyboardLayout();
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
Quickshell.execDetached([ Quickshell.execDetached(["hyprctl", "switchxkblayout", hyprlandKeyboard, "next"]);
"hyprctl", updateHyprlandLayout();
"switchxkblayout",
hyprlandKeyboard,
"next"
])
updateHyprlandLayout()
} }
} }
} }
@@ -773,9 +764,8 @@ Item {
color: Qt.rgba(255, 255, 255, 0.2) color: Qt.rgba(255, 255, 255, 0.2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: { visible: {
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) || const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) || (CompositorService.isHyprland && hyprlandLayoutCount > 1);
(CompositorService.isHyprland && hyprlandLayoutCount > 1) return keyboardVisible && GreetdSettings.weatherEnabled && WeatherService.weather.available;
return keyboardVisible && GreetdSettings.weatherEnabled && WeatherService.weather.available
} }
} }
@@ -832,15 +822,15 @@ Item {
DankIcon { DankIcon {
name: { name: {
if (!AudioService.sink?.audio) { if (!AudioService.sink?.audio) {
return "volume_up" return "volume_up";
} }
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) { if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off" return "volume_off";
} }
if (AudioService.sink.audio.volume * 100 < 33) { if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down" return "volume_down";
} }
return "volume_up" return "volume_up";
} }
size: Theme.iconSize - 2 size: Theme.iconSize - 2
color: (AudioService.sink && AudioService.sink.audio && (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white" color: (AudioService.sink && AudioService.sink.audio && (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white"
@@ -866,95 +856,95 @@ Item {
name: { name: {
if (BatteryService.isCharging) { if (BatteryService.isCharging) {
if (BatteryService.batteryLevel >= 90) { if (BatteryService.batteryLevel >= 90) {
return "battery_charging_full" return "battery_charging_full";
} }
if (BatteryService.batteryLevel >= 80) { if (BatteryService.batteryLevel >= 80) {
return "battery_charging_90" return "battery_charging_90";
} }
if (BatteryService.batteryLevel >= 60) { if (BatteryService.batteryLevel >= 60) {
return "battery_charging_80" return "battery_charging_80";
} }
if (BatteryService.batteryLevel >= 50) { if (BatteryService.batteryLevel >= 50) {
return "battery_charging_60" return "battery_charging_60";
} }
if (BatteryService.batteryLevel >= 30) { if (BatteryService.batteryLevel >= 30) {
return "battery_charging_50" return "battery_charging_50";
} }
if (BatteryService.batteryLevel >= 20) { if (BatteryService.batteryLevel >= 20) {
return "battery_charging_30" return "battery_charging_30";
} }
return "battery_charging_20" return "battery_charging_20";
} }
if (BatteryService.isPluggedIn) { if (BatteryService.isPluggedIn) {
if (BatteryService.batteryLevel >= 90) { if (BatteryService.batteryLevel >= 90) {
return "battery_charging_full" return "battery_charging_full";
} }
if (BatteryService.batteryLevel >= 80) { if (BatteryService.batteryLevel >= 80) {
return "battery_charging_90" return "battery_charging_90";
} }
if (BatteryService.batteryLevel >= 60) { if (BatteryService.batteryLevel >= 60) {
return "battery_charging_80" return "battery_charging_80";
} }
if (BatteryService.batteryLevel >= 50) { if (BatteryService.batteryLevel >= 50) {
return "battery_charging_60" return "battery_charging_60";
} }
if (BatteryService.batteryLevel >= 30) { if (BatteryService.batteryLevel >= 30) {
return "battery_charging_50" return "battery_charging_50";
} }
if (BatteryService.batteryLevel >= 20) { if (BatteryService.batteryLevel >= 20) {
return "battery_charging_30" return "battery_charging_30";
} }
return "battery_charging_20" return "battery_charging_20";
} }
if (BatteryService.batteryLevel >= 95) { if (BatteryService.batteryLevel >= 95) {
return "battery_full" return "battery_full";
} }
if (BatteryService.batteryLevel >= 85) { if (BatteryService.batteryLevel >= 85) {
return "battery_6_bar" return "battery_6_bar";
} }
if (BatteryService.batteryLevel >= 70) { if (BatteryService.batteryLevel >= 70) {
return "battery_5_bar" return "battery_5_bar";
} }
if (BatteryService.batteryLevel >= 55) { if (BatteryService.batteryLevel >= 55) {
return "battery_4_bar" return "battery_4_bar";
} }
if (BatteryService.batteryLevel >= 40) { if (BatteryService.batteryLevel >= 40) {
return "battery_3_bar" return "battery_3_bar";
} }
if (BatteryService.batteryLevel >= 25) { if (BatteryService.batteryLevel >= 25) {
return "battery_2_bar" return "battery_2_bar";
} }
return "battery_1_bar" return "battery_1_bar";
} }
size: Theme.iconSize size: Theme.iconSize
color: { color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) { if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error return Theme.error;
} }
if (BatteryService.isCharging || BatteryService.isPluggedIn) { if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary return Theme.primary;
} }
return "white" return "white";
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -1007,14 +997,14 @@ Item {
} }
property real longestSessionWidth: { property real longestSessionWidth: {
let maxWidth = 0 let maxWidth = 0;
for (var i = 0; i < sessionMetricsRepeater.count; i++) { for (var i = 0; i < sessionMetricsRepeater.count; i++) {
const item = sessionMetricsRepeater.itemAt(i) const item = sessionMetricsRepeater.itemAt(i);
if (item && item.width > maxWidth) { if (item && item.width > maxWidth) {
maxWidth = item.width maxWidth = item.width;
} }
} }
return maxWidth return maxWidth;
} }
Repeater { Repeater {
@@ -1038,52 +1028,42 @@ Item {
openUpwards: true openUpwards: true
alignPopupRight: true alignPopupRight: true
onValueChanged: value => { onValueChanged: value => {
const idx = GreeterState.sessionList.indexOf(value) const idx = GreeterState.sessionList.indexOf(value);
if (idx >= 0) { if (idx >= 0) {
GreeterState.currentSessionIndex = idx GreeterState.currentSessionIndex = idx;
GreeterState.selectedSession = GreeterState.sessionExecs[idx] GreeterState.selectedSession = GreeterState.sessionExecs[idx];
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[idx]) GreetdMemory.setLastSessionId(GreeterState.sessionPaths[idx]);
} }
} }
} }
} }
} }
FileView {
id: pamConfigWatcher
path: "/etc/pam.d/dankshell"
printErrors: false
}
property int sessionCount: 0
property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || "" property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || ""
property int pendingParsers: 0 property int pendingParsers: 0
function finalizeSessionSelection() { function finalizeSessionSelection() {
if (GreeterState.sessionList.length === 0) { if (GreeterState.sessionList.length === 0) {
return return;
} }
root.sessionCount = GreeterState.sessionList.length const savedSession = GreetdMemory.lastSessionId;
let foundSaved = false;
const savedSession = GreetdMemory.lastSessionId
let foundSaved = false
if (savedSession) { if (savedSession) {
for (var i = 0; i < GreeterState.sessionPaths.length; i++) { for (var i = 0; i < GreeterState.sessionPaths.length; i++) {
if (GreeterState.sessionPaths[i] === savedSession) { if (GreeterState.sessionPaths[i] === savedSession) {
GreeterState.currentSessionIndex = i GreeterState.currentSessionIndex = i;
foundSaved = true foundSaved = true;
break break;
} }
} }
} }
if (!foundSaved) { if (!foundSaved) {
GreeterState.currentSessionIndex = 0 GreeterState.currentSessionIndex = 0;
} }
GreeterState.selectedSession = GreeterState.sessionExecs[GreeterState.currentSessionIndex] || GreeterState.sessionExecs[0] || "" GreeterState.selectedSession = GreeterState.sessionExecs[GreeterState.currentSessionIndex] || GreeterState.sessionExecs[0] || "";
} }
Process { Process {
@@ -1091,39 +1071,34 @@ Item {
property string homeDir: Quickshell.env("HOME") || "" property string homeDir: Quickshell.env("HOME") || ""
property string xdgDirs: xdgDataDirs || "" property string xdgDirs: xdgDataDirs || ""
command: { command: {
var paths = [ var paths = ["/usr/share/wayland-sessions", "/usr/share/xsessions", "/usr/local/share/wayland-sessions", "/usr/local/share/xsessions"];
"/usr/share/wayland-sessions",
"/usr/share/xsessions",
"/usr/local/share/wayland-sessions",
"/usr/local/share/xsessions"
]
if (homeDir) { if (homeDir) {
paths.push(homeDir + "/.local/share/wayland-sessions") paths.push(homeDir + "/.local/share/wayland-sessions");
paths.push(homeDir + "/.local/share/xsessions") paths.push(homeDir + "/.local/share/xsessions");
} }
// Add XDG_DATA_DIRS paths // Add XDG_DATA_DIRS paths
if (xdgDirs) { if (xdgDirs) {
xdgDirs.split(":").forEach(function(dir) { xdgDirs.split(":").forEach(function (dir) {
if (dir) { if (dir) {
paths.push(dir + "/wayland-sessions") paths.push(dir + "/wayland-sessions");
paths.push(dir + "/xsessions") paths.push(dir + "/xsessions");
} }
}) });
} }
// 1. Explicit system/user paths // 1. Explicit system/user paths
var explicitFind = "find " + paths.join(" ") + " -maxdepth 1 -name '*.desktop' -type f -follow 2>/dev/null" var explicitFind = "find " + paths.join(" ") + " -maxdepth 1 -name '*.desktop' -type f -follow 2>/dev/null";
// 2. Scan all /home user directories for local session files // 2. Scan all /home user directories for local session files
var homeScan = "find /home -maxdepth 5 \\( -path '*/wayland-sessions/*.desktop' -o -path '*/xsessions/*.desktop' \\) -type f -follow 2>/dev/null" var homeScan = "find /home -maxdepth 5 \\( -path '*/wayland-sessions/*.desktop' -o -path '*/xsessions/*.desktop' \\) -type f -follow 2>/dev/null";
var findCmd = "(" + explicitFind + "; " + homeScan + ") | sort -u" var findCmd = "(" + explicitFind + "; " + homeScan + ") | sort -u";
return ["sh", "-c", findCmd] return ["sh", "-c", findCmd];
} }
running: false running: false
stdout: SplitParser { stdout: SplitParser {
onRead: data => { onRead: data => {
if (data.trim()) { if (data.trim()) {
root.pendingParsers++ root.pendingParsers++;
parseDesktopFile(data.trim()) parseDesktopFile(data.trim());
} }
} }
} }
@@ -1132,7 +1107,7 @@ Item {
function parseDesktopFile(path) { function parseDesktopFile(path) {
const parser = desktopParser.createObject(null, { const parser = desktopParser.createObject(null, {
"desktopPath": path "desktopPath": path
}) });
} }
Component { Component {
@@ -1144,41 +1119,40 @@ Item {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
const lines = text.split("\n") const lines = text.split("\n");
let name = "" let name = "";
let exec = "" let exec = "";
for (const line of lines) { for (const line of lines) {
if (line.startsWith("Name=")) { if (line.startsWith("Name=")) {
name = line.substring(5).trim() name = line.substring(5).trim();
} else if (line.startsWith("Exec=")) { } else if (line.startsWith("Exec=")) {
exec = line.substring(5).trim() exec = line.substring(5).trim();
} }
} }
if (name && exec) { if (name && exec) {
if (!GreeterState.sessionList.includes(name)) { if (!GreeterState.sessionList.includes(name)) {
let newList = GreeterState.sessionList.slice() let newList = GreeterState.sessionList.slice();
let newExecs = GreeterState.sessionExecs.slice() let newExecs = GreeterState.sessionExecs.slice();
let newPaths = GreeterState.sessionPaths.slice() let newPaths = GreeterState.sessionPaths.slice();
newList.push(name) newList.push(name);
newExecs.push(exec) newExecs.push(exec);
newPaths.push(desktopPath) newPaths.push(desktopPath);
GreeterState.sessionList = newList GreeterState.sessionList = newList;
GreeterState.sessionExecs = newExecs GreeterState.sessionExecs = newExecs;
GreeterState.sessionPaths = newPaths GreeterState.sessionPaths = newPaths;
root.sessionCount = GreeterState.sessionList.length
} }
} }
} }
} }
onExited: code => { onExited: code => {
root.pendingParsers-- root.pendingParsers--;
if (root.pendingParsers === 0) { if (root.pendingParsers === 0) {
Qt.callLater(root.finalizeSessionSelection) Qt.callLater(root.finalizeSessionSelection);
} }
destroy() destroy();
} }
} }
} }
@@ -1189,34 +1163,34 @@ Item {
function onAuthMessage(message, error, responseRequired, echoResponse) { function onAuthMessage(message, error, responseRequired, echoResponse) {
if (responseRequired) { if (responseRequired) {
Greetd.respond(GreeterState.passwordBuffer) Greetd.respond(GreeterState.passwordBuffer);
GreeterState.passwordBuffer = "" GreeterState.passwordBuffer = "";
inputField.text = "" inputField.text = "";
} else if (!error) { } else if (!error) {
Greetd.respond("") Greetd.respond("");
} }
} }
function onReadyToLaunch() { function onReadyToLaunch() {
GreeterState.unlocking = true GreeterState.unlocking = true;
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex] const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex];
if (sessionCmd) { if (sessionCmd) {
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex]) GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex]);
GreetdMemory.setLastSuccessfulUser(GreeterState.username) GreetdMemory.setLastSuccessfulUser(GreeterState.username);
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"]) Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"]);
} }
} }
function onAuthFailure(message) { function onAuthFailure(message) {
GreeterState.pamState = "fail" GreeterState.pamState = "fail";
GreeterState.passwordBuffer = "" GreeterState.passwordBuffer = "";
inputField.text = "" inputField.text = "";
placeholderDelay.restart() placeholderDelay.restart();
} }
function onError(error) { function onError(error) {
GreeterState.pamState = "error" GreeterState.pamState = "error";
placeholderDelay.restart() placeholderDelay.restart();
} }
} }
@@ -1231,7 +1205,7 @@ Item {
showLogout: false showLogout: false
onClosed: { onClosed: {
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) { if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
Qt.callLater(() => inputField.forceActiveFocus()) Qt.callLater(() => inputField.forceActiveFocus());
} }
} }
} }

View File

@@ -212,10 +212,12 @@ Item {
color: "transparent" color: "transparent"
Item { Item {
anchors.centerIn: parent id: clockContainer
anchors.verticalCenterOffset: -100 anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.verticalCenter
anchors.bottomMargin: 60
width: parent.width width: parent.width
height: 140 height: clockText.implicitHeight
visible: SettingsData.lockScreenShowTime visible: SettingsData.lockScreenShowTime
Row { Row {
@@ -330,8 +332,10 @@ Item {
} }
StyledText { StyledText {
anchors.centerIn: parent id: dateText
anchors.verticalCenterOffset: -25 anchors.horizontalCenter: parent.horizontalCenter
anchors.top: clockContainer.bottom
anchors.topMargin: 4
visible: SettingsData.lockScreenShowDate visible: SettingsData.lockScreenShowDate
text: { text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) { if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
@@ -346,8 +350,9 @@ Item {
ColumnLayout { ColumnLayout {
id: passwordLayout id: passwordLayout
anchors.centerIn: parent anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenterOffset: 50 anchors.top: dateText.visible ? dateText.bottom : clockContainer.bottom
anchors.topMargin: Theme.spacingL
spacing: Theme.spacingM spacing: Theme.spacingM
width: 380 width: 380
@@ -384,7 +389,6 @@ Item {
border.width: passwordField.activeFocus ? 2 : 1 border.width: passwordField.activeFocus ? 2 : 1
visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0 visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0
Item { Item {
id: lockIconContainer id: lockIconContainer
anchors.left: parent.left anchors.left: parent.left

View File

@@ -9,6 +9,7 @@ DankListView {
property var keyboardController: null property var keyboardController: null
property bool keyboardActive: false property bool keyboardActive: false
property bool autoScrollDisabled: false property bool autoScrollDisabled: false
property bool isAnimatingExpansion: false
property alias count: listView.count property alias count: listView.count
property alias listContentHeight: listView.contentHeight property alias listContentHeight: listView.contentHeight
@@ -29,8 +30,19 @@ DankListView {
Timer { Timer {
id: positionPreservationTimer id: positionPreservationTimer
interval: 200 interval: 200
running: keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled running: keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled && !isAnimatingExpansion
repeat: true repeat: true
onTriggered: {
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled && !isAnimatingExpansion) {
keyboardController.ensureVisible();
}
}
}
Timer {
id: expansionEnsureVisibleTimer
interval: Theme.mediumDuration + 50
repeat: false
onTriggered: { onTriggered: {
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) { if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
keyboardController.ensureVisible(); keyboardController.ensureVisible();
@@ -68,14 +80,7 @@ DankListView {
width: ListView.view.width width: ListView.view.width
height: isDismissing ? 0 : notificationCard.height height: isDismissing ? 0 : notificationCard.height
clip: true clip: isDismissing
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
NotificationCard { NotificationCard {
id: notificationCard id: notificationCard
@@ -84,6 +89,23 @@ DankListView {
notificationGroup: modelData notificationGroup: modelData
keyboardNavigationActive: listView.keyboardActive keyboardNavigationActive: listView.keyboardActive
opacity: 1 - Math.abs(delegateRoot.swipeOffset) / (delegateRoot.width * 0.5) opacity: 1 - Math.abs(delegateRoot.swipeOffset) / (delegateRoot.width * 0.5)
onIsAnimatingChanged: {
if (isAnimating) {
listView.isAnimatingExpansion = true;
} else {
Qt.callLater(() => {
let anyAnimating = false;
for (let i = 0; i < listView.count; i++) {
const item = listView.itemAtIndex(i);
if (item && item.children[0] && item.children[0].isAnimating) {
anyAnimating = true;
break;
}
}
listView.isAnimatingExpansion = anyAnimating;
});
}
}
isGroupSelected: { isGroupSelected: {
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive) if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
@@ -173,23 +195,15 @@ DankListView {
} }
function onExpandedGroupsChanged() { function onExpandedGroupsChanged() {
if (keyboardController && keyboardController.keyboardNavigationActive) { if (!keyboardController || !keyboardController.keyboardNavigationActive)
Qt.callLater(() => { return;
if (!autoScrollDisabled) { expansionEnsureVisibleTimer.restart();
keyboardController.ensureVisible();
}
});
}
} }
function onExpandedMessagesChanged() { function onExpandedMessagesChanged() {
if (keyboardController && keyboardController.keyboardNavigationActive) { if (!keyboardController || !keyboardController.keyboardNavigationActive)
Qt.callLater(() => { return;
if (!autoScrollDisabled) { expansionEnsureVisibleTimer.restart();
keyboardController.ensureVisible();
}
});
}
} }
} }
} }

View File

@@ -1,8 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -13,9 +10,9 @@ Rectangle {
property var notificationGroup property var notificationGroup
property bool expanded: (NotificationService.expandedGroups[notificationGroup && notificationGroup.key] || false) property bool expanded: (NotificationService.expandedGroups[notificationGroup && notificationGroup.key] || false)
property bool descriptionExpanded: (NotificationService.expandedMessages[(notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification property bool descriptionExpanded: (NotificationService.expandedMessages[(notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""] || false)
&& notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""] || false)
property bool userInitiatedExpansion: false property bool userInitiatedExpansion: false
property bool isAnimating: false
property bool isGroupSelected: false property bool isGroupSelected: false
property int selectedNotificationIndex: -1 property int selectedNotificationIndex: -1
@@ -24,13 +21,13 @@ Rectangle {
width: parent ? parent.width : 400 width: parent ? parent.width : 400
height: { height: {
if (expanded) { if (expanded) {
return expandedContent.height + 28 return expandedContent.height + 28;
} }
const baseHeight = 116 const baseHeight = 116;
if (descriptionExpanded) { if (descriptionExpanded) {
return baseHeight + descriptionText.contentHeight - (descriptionText.font.pixelSize * 1.2 * 2) return baseHeight + descriptionText.contentHeight - (descriptionText.font.pixelSize * 1.2 * 2);
} }
return baseHeight return baseHeight;
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -43,36 +40,36 @@ Rectangle {
color: { color: {
if (isGroupSelected && keyboardNavigationActive) { if (isGroupSelected && keyboardNavigationActive) {
return Theme.primaryPressed return Theme.primaryPressed;
} }
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) { if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
return Theme.primaryHoverLight return Theme.primaryHoverLight;
} }
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
} }
border.color: { border.color: {
if (isGroupSelected && keyboardNavigationActive) { if (isGroupSelected && keyboardNavigationActive) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5);
} }
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) { if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
} }
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) { if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
} }
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05) return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05);
} }
border.width: { border.width: {
if (isGroupSelected && keyboardNavigationActive) { if (isGroupSelected && keyboardNavigationActive) {
return 1.5 return 1.5;
} }
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) { if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
return 1 return 1;
} }
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) { if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
return 2 return 2;
} }
return 1 return 1;
} }
clip: true clip: true
@@ -121,21 +118,21 @@ Rectangle {
imageSource: { imageSource: {
if (hasNotificationImage) if (hasNotificationImage)
return notificationGroup.latestNotification.cleanImage return notificationGroup.latestNotification.cleanImage;
if (notificationGroup?.latestNotification?.appIcon) { if (notificationGroup?.latestNotification?.appIcon) {
const appIcon = notificationGroup.latestNotification.appIcon const appIcon = notificationGroup.latestNotification.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 = notificationGroup?.appName || "?" const appName = notificationGroup?.appName || "?";
return appName.charAt(0).toUpperCase() return appName.charAt(0).toUpperCase();
} }
Rectangle { Rectangle {
@@ -195,9 +192,9 @@ Rectangle {
StyledText { StyledText {
width: parent.width width: parent.width
text: { text: {
const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || "" const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || "";
const appName = (notificationGroup && notificationGroup.appName) || "" const appName = (notificationGroup && notificationGroup.appName) || "";
return timeStr.length > 0 ? `${appName} ${timeStr}` : appName return timeStr.length > 0 ? `${appName} ${timeStr}` : appName;
} }
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -239,21 +236,20 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) { if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "";
&& notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "" NotificationService.toggleMessageExpansion(messageId);
NotificationService.toggleMessageExpansion(messageId)
} }
} }
propagateComposedEvents: true propagateComposedEvents: true
onPressed: mouse => { onPressed: mouse => {
if (parent.hoveredLink) { if (parent.hoveredLink) {
mouse.accepted = false mouse.accepted = false;
} }
} }
onReleased: mouse => { onReleased: mouse => {
if (parent.hoveredLink) { if (parent.hoveredLink) {
mouse.accepted = false mouse.accepted = false;
} }
} }
} }
@@ -335,15 +331,15 @@ Rectangle {
width: parent.width width: parent.width
height: { height: {
const baseHeight = 120 const baseHeight = 120;
if (messageExpanded) { if (messageExpanded) {
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2 const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2;
if (bodyText.implicitHeight > twoLineHeight + 2) { if (bodyText.implicitHeight > twoLineHeight + 2) {
const extraHeight = bodyText.implicitHeight - twoLineHeight const extraHeight = bodyText.implicitHeight - twoLineHeight;
return baseHeight + extraHeight return baseHeight + extraHeight;
} }
} }
return baseHeight return baseHeight;
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
@@ -379,23 +375,23 @@ Rectangle {
imageSource: { imageSource: {
if (hasNotificationImage) if (hasNotificationImage)
return modelData.cleanImage return modelData.cleanImage;
if (modelData?.appIcon) { if (modelData?.appIcon) {
const appIcon = modelData.appIcon const appIcon = modelData.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 "";
} }
fallbackIcon: "" fallbackIcon: ""
fallbackText: { fallbackText: {
const appName = modelData?.appName || "?" const appName = modelData?.appName || "?";
return appName.charAt(0).toUpperCase() return appName.charAt(0).toUpperCase();
} }
} }
@@ -457,19 +453,19 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) { if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
NotificationService.toggleMessageExpansion(modelData?.notification?.id || "") NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
} }
} }
propagateComposedEvents: true propagateComposedEvents: true
onPressed: mouse => { onPressed: mouse => {
if (parent.hoveredLink) { if (parent.hoveredLink) {
mouse.accepted = false mouse.accepted = false;
} }
} }
onReleased: mouse => { onReleased: mouse => {
if (parent.hoveredLink) { if (parent.hoveredLink) {
mouse.accepted = false mouse.accepted = false;
} }
} }
} }
@@ -502,11 +498,11 @@ Rectangle {
StyledText { StyledText {
id: actionText id: actionText
text: { text: {
const baseText = modelData.text || "View" const baseText = modelData.text || "View";
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0)) { if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0)) {
return `${baseText} (${index + 1})` return `${baseText} (${index + 1})`;
} }
return baseText return baseText;
} }
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -523,7 +519,7 @@ Rectangle {
onExited: parent.isHovered = false onExited: parent.isHovered = false
onClicked: { onClicked: {
if (modelData && modelData.invoke) { if (modelData && modelData.invoke) {
modelData.invoke() modelData.invoke();
} }
} }
} }
@@ -587,11 +583,11 @@ Rectangle {
StyledText { StyledText {
id: actionText id: actionText
text: { text: {
const baseText = modelData.text || "View" const baseText = modelData.text || "View";
if (keyboardNavigationActive && isGroupSelected) { if (keyboardNavigationActive && isGroupSelected) {
return `${baseText} (${index + 1})` return `${baseText} (${index + 1})`;
} }
return baseText return baseText;
} }
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -608,7 +604,7 @@ Rectangle {
onExited: parent.isHovered = false onExited: parent.isHovered = false
onClicked: { onClicked: {
if (modelData && modelData.invoke) { if (modelData && modelData.invoke) {
modelData.invoke() modelData.invoke();
} }
} }
} }
@@ -654,8 +650,8 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
onClicked: { onClicked: {
root.userInitiatedExpansion = true root.userInitiatedExpansion = true;
NotificationService.toggleGroupExpansion(notificationGroup?.key || "") NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
} }
z: -1 z: -1
} }
@@ -677,8 +673,8 @@ Rectangle {
iconSize: 18 iconSize: 18
buttonSize: 28 buttonSize: 28
onClicked: { onClicked: {
root.userInitiatedExpansion = true root.userInitiatedExpansion = true;
NotificationService.toggleGroupExpansion(notificationGroup?.key || "") NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
} }
} }
@@ -697,7 +693,14 @@ Rectangle {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
onFinished: root.userInitiatedExpansion = false onRunningChanged: {
if (running) {
root.isAnimating = true;
} else {
root.isAnimating = false;
root.userInitiatedExpansion = false;
}
}
} }
} }
} }

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import qs.Common
import qs.Services import qs.Services
QtObject { QtObject {
@@ -23,14 +22,14 @@ QtObject {
property bool isRebuilding: false property bool isRebuilding: false
function rebuildFlatNavigation() { function rebuildFlatNavigation() {
isRebuilding = true isRebuilding = true;
const nav = [] const nav = [];
const groups = NotificationService.groupedNotifications const groups = NotificationService.groupedNotifications;
for (var i = 0; i < groups.length; i++) { for (var i = 0; i < groups.length; i++) {
const group = groups[i] const group = groups[i];
const isExpanded = NotificationService.expandedGroups[group.key] || false const isExpanded = NotificationService.expandedGroups[group.key] || false;
nav.push({ nav.push({
"type": "group", "type": "group",
@@ -38,515 +37,503 @@ QtObject {
"notificationIndex": -1, "notificationIndex": -1,
"groupKey": group.key, "groupKey": group.key,
"notificationId": "" "notificationId": ""
}) });
if (isExpanded) { if (isExpanded) {
const notifications = group.notifications || [] const notifications = group.notifications || [];
const maxNotifications = Math.min(notifications.length, 10) const maxNotifications = Math.min(notifications.length, 10);
for (var j = 0; j < maxNotifications; j++) { for (var j = 0; j < maxNotifications; j++) {
const notifId = String(notifications[j] && notifications[j].notification && notifications[j].notification.id ? notifications[j].notification.id : "") const notifId = String(notifications[j] && notifications[j].notification && notifications[j].notification.id ? notifications[j].notification.id : "");
nav.push({ nav.push({
"type": "notification", "type": "notification",
"groupIndex": i, "groupIndex": i,
"notificationIndex": j, "notificationIndex": j,
"groupKey": group.key, "groupKey": group.key,
"notificationId": notifId "notificationId": notifId
}) });
} }
} }
} }
flatNavigation = nav flatNavigation = nav;
updateSelectedIndexFromId() updateSelectedIndexFromId();
isRebuilding = false isRebuilding = false;
} }
function updateSelectedIndexFromId() { function updateSelectedIndexFromId() {
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
return return;
} }
for (var i = 0; i < flatNavigation.length; i++) { for (var i = 0; i < flatNavigation.length; i++) {
const item = flatNavigation[i] const item = flatNavigation[i];
if (selectedItemType === "group" && item.type === "group" && item.groupKey === selectedGroupKey) { if (selectedItemType === "group" && item.type === "group" && item.groupKey === selectedGroupKey) {
selectedFlatIndex = i selectedFlatIndex = i;
selectionVersion++ // Trigger UI update selectionVersion++; // Trigger UI update
return return;
} else if (selectedItemType === "notification" && item.type === "notification" && String(item.notificationId) === String(selectedNotificationId)) { } else if (selectedItemType === "notification" && item.type === "notification" && String(item.notificationId) === String(selectedNotificationId)) {
selectedFlatIndex = i selectedFlatIndex = i;
selectionVersion++ // Trigger UI update selectionVersion++; // Trigger UI update
return return;
} }
} }
// If not found, try to find the same group but select the group header instead // If not found, try to find the same group but select the group header instead
if (selectedItemType === "notification") { if (selectedItemType === "notification") {
for (var j = 0; j < flatNavigation.length; j++) { for (var j = 0; j < flatNavigation.length; j++) {
const groupItem = flatNavigation[j] const groupItem = flatNavigation[j];
if (groupItem.type === "group" && groupItem.groupKey === selectedGroupKey) { if (groupItem.type === "group" && groupItem.groupKey === selectedGroupKey) {
selectedFlatIndex = j selectedFlatIndex = j;
selectedItemType = "group" selectedItemType = "group";
selectedNotificationId = "" selectedNotificationId = "";
selectionVersion++ // Trigger UI update selectionVersion++; // Trigger UI update
return return;
} }
} }
} }
// If still not found, clamp to valid range and update // If still not found, clamp to valid range and update
if (flatNavigation.length > 0) { if (flatNavigation.length > 0) {
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1) selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1);
selectedFlatIndex = Math.max(selectedFlatIndex, 0) selectedFlatIndex = Math.max(selectedFlatIndex, 0);
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
selectionVersion++ // Trigger UI update selectionVersion++; // Trigger UI update
} }
} }
function updateSelectedIdFromIndex() { function updateSelectedIdFromIndex() {
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatNavigation.length) { if (selectedFlatIndex >= 0 && selectedFlatIndex < flatNavigation.length) {
const item = flatNavigation[selectedFlatIndex] const item = flatNavigation[selectedFlatIndex];
selectedItemType = item.type selectedItemType = item.type;
selectedGroupKey = item.groupKey selectedGroupKey = item.groupKey;
selectedNotificationId = item.notificationId selectedNotificationId = item.notificationId;
} }
} }
function reset() { function reset() {
selectedFlatIndex = 0 selectedFlatIndex = 0;
keyboardNavigationActive = false keyboardNavigationActive = false;
showKeyboardHints = false showKeyboardHints = false;
// Reset keyboardActive when modal is reset // Reset keyboardActive when modal is reset
if (listView) { if (listView) {
listView.keyboardActive = false listView.keyboardActive = false;
} }
rebuildFlatNavigation() rebuildFlatNavigation();
} }
function selectNext() { function selectNext() {
keyboardNavigationActive = true keyboardNavigationActive = true;
if (flatNavigation.length === 0) if (flatNavigation.length === 0)
return return;
// Re-enable auto-scrolling when arrow keys are used // Re-enable auto-scrolling when arrow keys are used
if (listView && listView.enableAutoScroll) { if (listView && listView.enableAutoScroll) {
listView.enableAutoScroll() listView.enableAutoScroll();
} }
selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1) selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1);
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
} }
function selectNextWrapping() { function selectNextWrapping() {
keyboardNavigationActive = true keyboardNavigationActive = true;
if (flatNavigation.length === 0) if (flatNavigation.length === 0)
return return;
// Re-enable auto-scrolling when arrow keys are used // Re-enable auto-scrolling when arrow keys are used
if (listView && listView.enableAutoScroll) { if (listView && listView.enableAutoScroll) {
listView.enableAutoScroll() listView.enableAutoScroll();
} }
selectedFlatIndex = (selectedFlatIndex + 1) % flatNavigation.length selectedFlatIndex = (selectedFlatIndex + 1) % flatNavigation.length;
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
} }
function selectPrevious() { function selectPrevious() {
keyboardNavigationActive = true keyboardNavigationActive = true;
if (flatNavigation.length === 0) if (flatNavigation.length === 0)
return return;
// Re-enable auto-scrolling when arrow keys are used // Re-enable auto-scrolling when arrow keys are used
if (listView && listView.enableAutoScroll) { if (listView && listView.enableAutoScroll) {
listView.enableAutoScroll() listView.enableAutoScroll();
} }
selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0) selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0);
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
} }
function toggleGroupExpanded() { function toggleGroupExpanded() {
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
return return;
const currentItem = flatNavigation[selectedFlatIndex];
const currentItem = flatNavigation[selectedFlatIndex] const groups = NotificationService.groupedNotifications;
const groups = NotificationService.groupedNotifications const group = groups[currentItem.groupIndex];
const group = groups[currentItem.groupIndex]
if (!group) if (!group)
return return;
// Prevent expanding groups with < 2 notifications // Prevent expanding groups with < 2 notifications
const notificationCount = group.notifications ? group.notifications.length : 0 const notificationCount = group.notifications ? group.notifications.length : 0;
if (notificationCount < 2) if (notificationCount < 2)
return return;
const wasExpanded = NotificationService.expandedGroups[group.key] || false;
const groupIndex = currentItem.groupIndex;
const wasExpanded = NotificationService.expandedGroups[group.key] || false isTogglingGroup = true;
const groupIndex = currentItem.groupIndex NotificationService.toggleGroupExpansion(group.key);
rebuildFlatNavigation();
isTogglingGroup = true
NotificationService.toggleGroupExpansion(group.key)
rebuildFlatNavigation()
// Smart selection after toggle // Smart selection after toggle
if (!wasExpanded) { if (!wasExpanded) {
// Just expanded - move to first notification in the group // Just expanded - move to first notification in the group
for (var i = 0; i < flatNavigation.length; i++) { for (var i = 0; i < flatNavigation.length; i++) {
if (flatNavigation[i].type === "notification" && flatNavigation[i].groupIndex === groupIndex) { if (flatNavigation[i].type === "notification" && flatNavigation[i].groupIndex === groupIndex) {
selectedFlatIndex = i selectedFlatIndex = i;
break break;
} }
} }
} else { } else {
// Just collapsed - stay on the group header // Just collapsed - stay on the group header
for (var i = 0; i < flatNavigation.length; i++) { for (var i = 0; i < flatNavigation.length; i++) {
if (flatNavigation[i].type === "group" && flatNavigation[i].groupIndex === groupIndex) { if (flatNavigation[i].type === "group" && flatNavigation[i].groupIndex === groupIndex) {
selectedFlatIndex = i selectedFlatIndex = i;
break break;
} }
} }
} }
isTogglingGroup = false isTogglingGroup = false;
ensureVisible()
} }
function handleEnterKey() { function handleEnterKey() {
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
return return;
const currentItem = flatNavigation[selectedFlatIndex];
const currentItem = flatNavigation[selectedFlatIndex] const groups = NotificationService.groupedNotifications;
const groups = NotificationService.groupedNotifications const group = groups[currentItem.groupIndex];
const group = groups[currentItem.groupIndex]
if (!group) if (!group)
return return;
if (currentItem.type === "group") { if (currentItem.type === "group") {
const notificationCount = group.notifications ? group.notifications.length : 0 const notificationCount = group.notifications ? group.notifications.length : 0;
if (notificationCount >= 2) { if (notificationCount >= 2) {
toggleGroupExpanded() toggleGroupExpanded();
} else { } else {
executeAction(0) executeAction(0);
} }
} else if (currentItem.type === "notification") { } else if (currentItem.type === "notification") {
executeAction(0) executeAction(0);
} }
} }
function toggleTextExpanded() { function toggleTextExpanded() {
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
return return;
const currentItem = flatNavigation[selectedFlatIndex];
const currentItem = flatNavigation[selectedFlatIndex] const groups = NotificationService.groupedNotifications;
const groups = NotificationService.groupedNotifications const group = groups[currentItem.groupIndex];
const group = groups[currentItem.groupIndex]
if (!group) if (!group)
return return;
let messageId = "";
let messageId = ""
if (currentItem.type === "group") { if (currentItem.type === "group") {
messageId = group.latestNotification?.notification?.id + "_desc" messageId = group.latestNotification?.notification?.id + "_desc";
} else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) { } else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc" messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc";
} }
if (messageId) { if (messageId) {
NotificationService.toggleMessageExpansion(messageId) NotificationService.toggleMessageExpansion(messageId);
} }
} }
function executeAction(actionIndex) { function executeAction(actionIndex) {
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
return return;
const currentItem = flatNavigation[selectedFlatIndex];
const currentItem = flatNavigation[selectedFlatIndex] const groups = NotificationService.groupedNotifications;
const groups = NotificationService.groupedNotifications const group = groups[currentItem.groupIndex];
const group = groups[currentItem.groupIndex]
if (!group) if (!group)
return return;
let actions = [];
let actions = []
if (currentItem.type === "group") { if (currentItem.type === "group") {
actions = group.latestNotification?.actions || [] actions = group.latestNotification?.actions || [];
} else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) { } else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
actions = group.notifications[currentItem.notificationIndex]?.actions || [] actions = group.notifications[currentItem.notificationIndex]?.actions || [];
} }
if (actionIndex >= 0 && actionIndex < actions.length) { if (actionIndex >= 0 && actionIndex < actions.length) {
const action = actions[actionIndex] const action = actions[actionIndex];
if (action.invoke) { if (action.invoke) {
action.invoke() action.invoke();
if (onClose) if (onClose)
onClose() onClose();
} }
} }
} }
function clearSelected() { function clearSelected() {
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
return return;
const currentItem = flatNavigation[selectedFlatIndex];
const currentItem = flatNavigation[selectedFlatIndex] const groups = NotificationService.groupedNotifications;
const groups = NotificationService.groupedNotifications const group = groups[currentItem.groupIndex];
const group = groups[currentItem.groupIndex]
if (!group) if (!group)
return return;
if (currentItem.type === "group") { if (currentItem.type === "group") {
NotificationService.dismissGroup(group.key) NotificationService.dismissGroup(group.key);
} else if (currentItem.type === "notification") { } else if (currentItem.type === "notification") {
const notification = group.notifications[currentItem.notificationIndex] const notification = group.notifications[currentItem.notificationIndex];
NotificationService.dismissNotification(notification) NotificationService.dismissNotification(notification);
} }
rebuildFlatNavigation() rebuildFlatNavigation();
if (flatNavigation.length === 0) { if (flatNavigation.length === 0) {
keyboardNavigationActive = false keyboardNavigationActive = false;
if (listView) { if (listView) {
listView.keyboardActive = false listView.keyboardActive = false;
} }
} else { } else {
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1) selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1);
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
ensureVisible() ensureVisible();
} }
} }
function findRepeater(parent) { function findRepeater(parent) {
if (!parent || !parent.children) { if (!parent || !parent.children) {
return null return null;
} }
for (var i = 0; i < parent.children.length; i++) { for (var i = 0; i < parent.children.length; i++) {
const child = parent.children[i] const child = parent.children[i];
if (child.objectName === "notificationRepeater") { if (child.objectName === "notificationRepeater") {
return child return child;
} }
const found = findRepeater(child) const found = findRepeater(child);
if (found) { if (found) {
return found return found;
} }
} }
return null return null;
} }
function ensureVisible() { function ensureVisible() {
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length || !listView) if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length || !listView)
return return;
const currentItem = flatNavigation[selectedFlatIndex];
const currentItem = flatNavigation[selectedFlatIndex]
if (keyboardNavigationActive && currentItem && currentItem.groupIndex >= 0) { if (keyboardNavigationActive && currentItem && currentItem.groupIndex >= 0) {
if (currentItem.type === "notification") { if (currentItem.type === "notification") {
const groupDelegate = listView.itemAtIndex(currentItem.groupIndex) const groupDelegate = listView.itemAtIndex(currentItem.groupIndex);
if (groupDelegate && groupDelegate.children && groupDelegate.children.length > 0) { if (groupDelegate && groupDelegate.children && groupDelegate.children.length > 0) {
const notificationCard = groupDelegate.children[0] const notificationCard = groupDelegate.children[0];
const repeater = findRepeater(notificationCard) const repeater = findRepeater(notificationCard);
if (repeater && currentItem.notificationIndex < repeater.count) { if (repeater && currentItem.notificationIndex < repeater.count) {
const notificationItem = repeater.itemAt(currentItem.notificationIndex) const notificationItem = repeater.itemAt(currentItem.notificationIndex);
if (notificationItem) { if (notificationItem) {
const itemPos = notificationItem.mapToItem(listView.contentItem, 0, 0) const itemPos = notificationItem.mapToItem(listView.contentItem, 0, 0);
const itemY = itemPos.y const itemY = itemPos.y;
const itemHeight = notificationItem.height const itemHeight = notificationItem.height;
const viewportTop = listView.contentY const viewportTop = listView.contentY;
const viewportBottom = listView.contentY + listView.height const viewportBottom = listView.contentY + listView.height;
if (itemY < viewportTop) { if (itemY < viewportTop) {
listView.contentY = itemY - 20 listView.contentY = itemY - 20;
} else if (itemY + itemHeight > viewportBottom) { } else if (itemY + itemHeight > viewportBottom) {
listView.contentY = itemY + itemHeight - listView.height + 20 listView.contentY = itemY + itemHeight - listView.height + 20;
} }
} }
} }
} }
} else { } else {
listView.positionViewAtIndex(currentItem.groupIndex, ListView.Contain) listView.positionViewAtIndex(currentItem.groupIndex, ListView.Contain);
} }
listView.forceLayout() listView.forceLayout();
} }
} }
function handleKey(event) { function handleKey(event) {
if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) { if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) {
NotificationService.clearAllNotifications() NotificationService.clearAllNotifications();
rebuildFlatNavigation() rebuildFlatNavigation();
if (flatNavigation.length === 0) { if (flatNavigation.length === 0) {
keyboardNavigationActive = false keyboardNavigationActive = false;
if (listView) { if (listView) {
listView.keyboardActive = false listView.keyboardActive = false;
} }
} else { } else {
selectedFlatIndex = 0 selectedFlatIndex = 0;
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
} }
selectionVersion++ selectionVersion++;
event.accepted = true event.accepted = true;
return return;
} }
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
if (keyboardNavigationActive) { if (keyboardNavigationActive) {
keyboardNavigationActive = false keyboardNavigationActive = false;
event.accepted = true event.accepted = true;
} else { } else {
if (onClose) if (onClose)
onClose() onClose();
event.accepted = true event.accepted = true;
} }
} else if (event.key === Qt.Key_Down || event.key === 16777237) { } else if (event.key === Qt.Key_Down || event.key === 16777237) {
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
keyboardNavigationActive = true keyboardNavigationActive = true;
rebuildFlatNavigation() // Ensure we have fresh navigation data rebuildFlatNavigation(); // Ensure we have fresh navigation data
selectedFlatIndex = 0 selectedFlatIndex = 0;
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
// Set keyboardActive on listView to show highlight // Set keyboardActive on listView to show highlight
if (listView) { if (listView) {
listView.keyboardActive = true listView.keyboardActive = true;
} }
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
event.accepted = true event.accepted = true;
} else { } else {
selectNext() selectNext();
event.accepted = true event.accepted = true;
} }
} else if (event.key === Qt.Key_Up || event.key === 16777235) { } else if (event.key === Qt.Key_Up || event.key === 16777235) {
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
keyboardNavigationActive = true keyboardNavigationActive = true;
rebuildFlatNavigation() // Ensure we have fresh navigation data rebuildFlatNavigation(); // Ensure we have fresh navigation data
selectedFlatIndex = 0 selectedFlatIndex = 0;
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
// Set keyboardActive on listView to show highlight // Set keyboardActive on listView to show highlight
if (listView) { if (listView) {
listView.keyboardActive = true listView.keyboardActive = true;
} }
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
event.accepted = true event.accepted = true;
} else if (selectedFlatIndex === 0) { } else if (selectedFlatIndex === 0) {
keyboardNavigationActive = false keyboardNavigationActive = false;
// Reset keyboardActive when navigation is disabled // Reset keyboardActive when navigation is disabled
if (listView) { if (listView) {
listView.keyboardActive = false listView.keyboardActive = false;
} }
selectionVersion++ selectionVersion++;
event.accepted = true event.accepted = true;
return return;
} else { } else {
selectPrevious() selectPrevious();
event.accepted = true event.accepted = true;
} }
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) { } else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
keyboardNavigationActive = true keyboardNavigationActive = true;
rebuildFlatNavigation() rebuildFlatNavigation();
selectedFlatIndex = 0 selectedFlatIndex = 0;
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
if (listView) { if (listView) {
listView.keyboardActive = true listView.keyboardActive = true;
} }
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
} else { } else {
selectNext() selectNext();
} }
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) { } else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
keyboardNavigationActive = true keyboardNavigationActive = true;
rebuildFlatNavigation() rebuildFlatNavigation();
selectedFlatIndex = 0 selectedFlatIndex = 0;
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
if (listView) { if (listView) {
listView.keyboardActive = true listView.keyboardActive = true;
} }
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
} else if (selectedFlatIndex === 0) { } else if (selectedFlatIndex === 0) {
keyboardNavigationActive = false keyboardNavigationActive = false;
if (listView) { if (listView) {
listView.keyboardActive = false listView.keyboardActive = false;
} }
selectionVersion++ selectionVersion++;
} else { } else {
selectPrevious() selectPrevious();
} }
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) { } else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
keyboardNavigationActive = true keyboardNavigationActive = true;
rebuildFlatNavigation() rebuildFlatNavigation();
selectedFlatIndex = 0 selectedFlatIndex = 0;
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
if (listView) { if (listView) {
listView.keyboardActive = true listView.keyboardActive = true;
} }
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
} else { } else {
selectNext() selectNext();
} }
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) { } else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
keyboardNavigationActive = true keyboardNavigationActive = true;
rebuildFlatNavigation() rebuildFlatNavigation();
selectedFlatIndex = 0 selectedFlatIndex = 0;
updateSelectedIdFromIndex() updateSelectedIdFromIndex();
if (listView) { if (listView) {
listView.keyboardActive = true listView.keyboardActive = true;
} }
selectionVersion++ selectionVersion++;
ensureVisible() ensureVisible();
} else if (selectedFlatIndex === 0) { } else if (selectedFlatIndex === 0) {
keyboardNavigationActive = false keyboardNavigationActive = false;
if (listView) { if (listView) {
listView.keyboardActive = false listView.keyboardActive = false;
} }
selectionVersion++ selectionVersion++;
} else { } else {
selectPrevious() selectPrevious();
} }
event.accepted = true event.accepted = true;
} else if (keyboardNavigationActive) { } else if (keyboardNavigationActive) {
if (event.key === Qt.Key_Space) { if (event.key === Qt.Key_Space) {
toggleGroupExpanded() toggleGroupExpanded();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
handleEnterKey() handleEnterKey();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_E) { } else if (event.key === Qt.Key_E) {
toggleTextExpanded() toggleTextExpanded();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) { } else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
clearSelected() clearSelected();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Tab) { } else if (event.key === Qt.Key_Tab) {
selectNext() selectNext();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Backtab) { } else if (event.key === Qt.Key_Backtab) {
selectPrevious() selectPrevious();
event.accepted = true event.accepted = true;
} else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) { } else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
const actionIndex = event.key - Qt.Key_1 const actionIndex = event.key - Qt.Key_1;
executeAction(actionIndex) executeAction(actionIndex);
event.accepted = true event.accepted = true;
} }
} }
if (event.key === Qt.Key_F10) { if (event.key === Qt.Key_F10) {
showKeyboardHints = !showKeyboardHints showKeyboardHints = !showKeyboardHints;
event.accepted = true event.accepted = true;
} }
} }
@@ -557,13 +544,13 @@ QtObject {
"type": "", "type": "",
"groupIndex": -1, "groupIndex": -1,
"notificationIndex": -1 "notificationIndex": -1
} };
} }
const result = flatNavigation[selectedFlatIndex] || { const result = flatNavigation[selectedFlatIndex] || {
"type": "", "type": "",
"groupIndex": -1, "groupIndex": -1,
"notificationIndex": -1 "notificationIndex": -1
} };
return result return result;
} }
} }

View File

@@ -229,7 +229,7 @@ Item {
DankTextField { DankTextField {
id: searchField id: searchField
width: parent.width - addButton.width - Theme.spacingM width: parent.width - addButton.width - Theme.spacingM
height: 44 height: Math.round(Theme.fontSizeMedium * 3)
placeholderText: I18n.tr("Search keybinds...") placeholderText: I18n.tr("Search keybinds...")
leftIconName: "search" leftIconName: "search"
onTextChanged: { onTextChanged: {
@@ -240,8 +240,8 @@ Item {
DankActionButton { DankActionButton {
id: addButton id: addButton
width: 44 width: searchField.height
height: 44 height: searchField.height
circular: false circular: false
iconName: "add" iconName: "add"
iconSize: Theme.iconSize iconSize: Theme.iconSize
@@ -331,7 +331,7 @@ Item {
Rectangle { Rectangle {
id: fixButton id: fixButton
width: fixButtonText.implicitWidth + Theme.spacingL * 2 width: fixButtonText.implicitWidth + Theme.spacingL * 2
height: 36 height: Math.round(Theme.fontSizeMedium * 2.5)
radius: Theme.cornerRadius radius: Theme.cornerRadius
visible: warningBox.showError || warningBox.showSetup visible: warningBox.showError || warningBox.showSetup
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
@@ -382,9 +382,10 @@ Item {
spacing: Theme.spacingS spacing: Theme.spacingS
Rectangle { Rectangle {
readonly property real chipHeight: allChip.implicitHeight + Theme.spacingM
width: allChip.implicitWidth + Theme.spacingL width: allChip.implicitWidth + Theme.spacingL
height: 32 height: chipHeight
radius: 16 radius: chipHeight / 2
color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest
StyledText { StyledText {
@@ -412,9 +413,10 @@ Item {
required property string modelData required property string modelData
required property int index required property int index
readonly property real chipHeight: catText.implicitHeight + Theme.spacingM
width: catText.implicitWidth + Theme.spacingL width: catText.implicitWidth + Theme.spacingL
height: 32 height: chipHeight
radius: 16 radius: chipHeight / 2
color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest) color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest)
StyledText { StyledText {

View File

@@ -23,6 +23,15 @@ Item {
iconName: "refresh" iconName: "refresh"
title: I18n.tr("System Updater") title: I18n.tr("System Updater")
SettingsToggleRow {
text: I18n.tr("Hide Updater Widget", "When updater widget is used, then hide it if no update found")
description: I18n.tr("When updater widget is used, then hide it if no update found")
checked: SettingsData.updaterHideWidget
onToggled: checked => {
SettingsData.set("updaterHideWidget", checked);
}
}
SettingsToggleRow { SettingsToggleRow {
text: I18n.tr("Use Custom Command") text: I18n.tr("Use Custom Command")
description: I18n.tr("Use custom command for update your system") description: I18n.tr("Use custom command for update your system")

View File

@@ -371,9 +371,14 @@ Item {
widgetObj.pciId = ""; widgetObj.pciId = "";
} }
if (widgetId === "controlCenterButton") { if (widgetId === "controlCenterButton") {
widgetObj.showNetworkIcon = true; widgetObj.showNetworkIcon = SettingsData.controlCenterShowNetworkIcon;
widgetObj.showBluetoothIcon = true; widgetObj.showBluetoothIcon = SettingsData.controlCenterShowBluetoothIcon;
widgetObj.showAudioIcon = true; widgetObj.showAudioIcon = SettingsData.controlCenterShowAudioIcon;
widgetObj.showVpnIcon = SettingsData.controlCenterShowVpnIcon;
widgetObj.showBrightnessIcon = SettingsData.controlCenterShowBrightnessIcon;
widgetObj.showMicIcon = SettingsData.controlCenterShowMicIcon;
widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon;
widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon;
} }
if (widgetId === "diskUsage") if (widgetId === "diskUsage")
widgetObj.mountPath = "/"; widgetObj.mountPath = "/";
@@ -423,9 +428,14 @@ Item {
else if (widget.id === "gpuTemp") else if (widget.id === "gpuTemp")
newWidget.pciId = ""; newWidget.pciId = "";
if (widget.id === "controlCenterButton") { if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true; newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true; newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? true; newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
} }
widgets[i] = newWidget; widgets[i] = newWidget;
break; break;
@@ -471,9 +481,14 @@ Item {
if (widget.pciId !== undefined) if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId; newWidget.pciId = widget.pciId;
if (widget.id === "controlCenterButton") { if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true; newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true; newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? true; newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
} }
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
@@ -541,41 +556,48 @@ Item {
if (widget.pciId !== undefined) if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId; newWidget.pciId = widget.pciId;
if (widget.id === "controlCenterButton") { if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true; newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true; newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? true; newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
} }
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) { function handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) {
switch (settingName) { var widgets = getWidgetsForSection(sectionId).slice();
case "showNetworkIcon": if (widgetIndex < 0 || widgetIndex >= widgets.length)
SettingsData.set("controlCenterShowNetworkIcon", value); return;
break;
case "showBluetoothIcon": var widget = widgets[widgetIndex];
SettingsData.set("controlCenterShowBluetoothIcon", value); if (typeof widget === "string") {
break; widget = {
case "showAudioIcon": "id": widget,
SettingsData.set("controlCenterShowAudioIcon", value); "enabled": true
break; };
case "showVpnIcon":
SettingsData.set("controlCenterShowVpnIcon", value);
break;
case "showBrightnessIcon":
SettingsData.set("controlCenterShowBrightnessIcon", value);
break;
case "showMicIcon":
SettingsData.set("controlCenterShowMicIcon", value);
break;
case "showBatteryIcon":
SettingsData.set("controlCenterShowBatteryIcon", value);
break;
case "showPrinterIcon":
SettingsData.set("controlCenterShowPrinterIcon", value);
break;
} }
var newWidget = {
"id": widget.id,
"enabled": widget.enabled !== undefined ? widget.enabled : true,
"showNetworkIcon": widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon,
"showBluetoothIcon": widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon,
"showAudioIcon": widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon,
"showVpnIcon": widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon,
"showBrightnessIcon": widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon,
"showMicIcon": widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon,
"showBatteryIcon": widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon,
"showPrinterIcon": widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon
};
newWidget[settingName] = value;
widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets);
} }
function handlePrivacySettingChanged(sectionId, widgetIndex, settingName, value) { function handlePrivacySettingChanged(sectionId, widgetIndex, settingName, value) {
@@ -626,9 +648,14 @@ Item {
if (widget.showSwap !== undefined) if (widget.showSwap !== undefined)
newWidget.showSwap = widget.showSwap; newWidget.showSwap = widget.showSwap;
if (widget.id === "controlCenterButton") { if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true; newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true; newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? true; newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
} }
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
@@ -678,9 +705,14 @@ Item {
if (widget.keyboardLayoutNameCompactMode !== undefined) if (widget.keyboardLayoutNameCompactMode !== undefined)
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode; newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
if (widget.id === "controlCenterButton") { if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true; newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true; newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? true; newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
} }
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
@@ -730,9 +762,14 @@ Item {
if (widget.keyboardLayoutNameCompactMode !== undefined) if (widget.keyboardLayoutNameCompactMode !== undefined)
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode; newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
if (widget.id === "controlCenterButton") { if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true; newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true; newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? true; newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
} }
widgets[i] = newWidget; widgets[i] = newWidget;
widget = newWidget; widget = newWidget;
@@ -789,6 +826,16 @@ Item {
item.showBluetoothIcon = widget.showBluetoothIcon; item.showBluetoothIcon = widget.showBluetoothIcon;
if (widget.showAudioIcon !== undefined) if (widget.showAudioIcon !== undefined)
item.showAudioIcon = widget.showAudioIcon; item.showAudioIcon = widget.showAudioIcon;
if (widget.showVpnIcon !== undefined)
item.showVpnIcon = widget.showVpnIcon;
if (widget.showBrightnessIcon !== undefined)
item.showBrightnessIcon = widget.showBrightnessIcon;
if (widget.showMicIcon !== undefined)
item.showMicIcon = widget.showMicIcon;
if (widget.showBatteryIcon !== undefined)
item.showBatteryIcon = widget.showBatteryIcon;
if (widget.showPrinterIcon !== undefined)
item.showPrinterIcon = widget.showPrinterIcon;
if (widget.minimumWidth !== undefined) if (widget.minimumWidth !== undefined)
item.minimumWidth = widget.minimumWidth; item.minimumWidth = widget.minimumWidth;
if (widget.showSwap !== undefined) if (widget.showSwap !== undefined)

View File

@@ -806,50 +806,42 @@ Column {
{ {
icon: "lan", icon: "lan",
label: I18n.tr("Network"), label: I18n.tr("Network"),
setting: "showNetworkIcon", setting: "showNetworkIcon"
checked: SettingsData.controlCenterShowNetworkIcon
}, },
{ {
icon: "vpn_lock", icon: "vpn_lock",
label: I18n.tr("VPN"), label: I18n.tr("VPN"),
setting: "showVpnIcon", setting: "showVpnIcon"
checked: SettingsData.controlCenterShowVpnIcon
}, },
{ {
icon: "bluetooth", icon: "bluetooth",
label: I18n.tr("Bluetooth"), label: I18n.tr("Bluetooth"),
setting: "showBluetoothIcon", setting: "showBluetoothIcon"
checked: SettingsData.controlCenterShowBluetoothIcon
}, },
{ {
icon: "volume_up", icon: "volume_up",
label: I18n.tr("Audio"), label: I18n.tr("Audio"),
setting: "showAudioIcon", setting: "showAudioIcon"
checked: SettingsData.controlCenterShowAudioIcon
}, },
{ {
icon: "mic", icon: "mic",
label: I18n.tr("Microphone"), label: I18n.tr("Microphone"),
setting: "showMicIcon", setting: "showMicIcon"
checked: SettingsData.controlCenterShowMicIcon
}, },
{ {
icon: "brightness_high", icon: "brightness_high",
label: I18n.tr("Brightness"), label: I18n.tr("Brightness"),
setting: "showBrightnessIcon", setting: "showBrightnessIcon"
checked: SettingsData.controlCenterShowBrightnessIcon
}, },
{ {
icon: "battery_full", icon: "battery_full",
label: I18n.tr("Battery"), label: I18n.tr("Battery"),
setting: "showBatteryIcon", setting: "showBatteryIcon"
checked: SettingsData.controlCenterShowBatteryIcon
}, },
{ {
icon: "print", icon: "print",
label: I18n.tr("Printer"), label: I18n.tr("Printer"),
setting: "showPrinterIcon", setting: "showPrinterIcon"
checked: SettingsData.controlCenterShowPrinterIcon
} }
] ]
@@ -857,6 +849,30 @@ Column {
required property var modelData required property var modelData
required property int index required property int index
function getCheckedState() {
var wd = controlCenterContextMenu.widgetData;
switch (modelData.setting) {
case "showNetworkIcon":
return wd?.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
case "showVpnIcon":
return wd?.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
case "showBluetoothIcon":
return wd?.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
case "showAudioIcon":
return wd?.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
case "showMicIcon":
return wd?.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
case "showBrightnessIcon":
return wd?.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
case "showBatteryIcon":
return wd?.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
case "showPrinterIcon":
return wd?.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
default:
return false;
}
}
width: menuColumn.width width: menuColumn.width
height: 32 height: 32
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -891,7 +907,7 @@ Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 40 width: 40
height: 20 height: 20
checked: modelData.checked checked: getCheckedState()
onToggled: { onToggled: {
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled); root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled);
} }

View File

@@ -53,10 +53,13 @@ Singleton {
signal gammaStateUpdate(var data) signal gammaStateUpdate(var data)
signal openUrlRequested(string url) signal openUrlRequested(string url)
signal appPickerRequested(var data) signal appPickerRequested(var data)
signal screensaverStateUpdate(var data)
property bool capsLockState: false property bool capsLockState: false
property bool screensaverInhibited: false
property var screensaverInhibitors: []
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser"] property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser"]
Component.onCompleted: { Component.onCompleted: {
if (socketPath && socketPath.length > 0) { if (socketPath && socketPath.length > 0) {
@@ -371,6 +374,10 @@ Singleton {
} else if (data.url) { } else if (data.url) {
openUrlRequested(data.url); openUrlRequested(data.url);
} }
} else if (service === "freedesktop.screensaver") {
screensaverInhibited = data.inhibited || false;
screensaverInhibitors = data.inhibitors || [];
screensaverStateUpdate(data);
} }
} }

View File

@@ -29,6 +29,8 @@ Singleton {
property bool respectInhibitors: true property bool respectInhibitors: true
property bool _enableGate: true property bool _enableGate: true
readonly property bool externalInhibitActive: DMSService.screensaverInhibited
readonly property bool isOnBattery: BatteryService.batteryAvailable && !BatteryService.isPluggedIn readonly property bool isOnBattery: BatteryService.batteryAvailable && !BatteryService.isPluggedIn
readonly property int monitorTimeout: isOnBattery ? SettingsData.batteryMonitorTimeout : SettingsData.acMonitorTimeout readonly property int monitorTimeout: isOnBattery ? SettingsData.batteryMonitorTimeout : SettingsData.acMonitorTimeout
readonly property int lockTimeout: isOnBattery ? SettingsData.batteryLockTimeout : SettingsData.acLockTimeout readonly property int lockTimeout: isOnBattery ? SettingsData.batteryLockTimeout : SettingsData.acLockTimeout
@@ -141,6 +143,19 @@ Singleton {
} }
} }
onExternalInhibitActiveChanged: {
if (externalInhibitActive) {
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
console.info("IdleService: External idle inhibit active from:", apps || "unknown");
SessionService.idleInhibited = true;
SessionService.inhibitReason = "External app: " + (apps || "unknown");
} else {
console.info("IdleService: External idle inhibit released");
SessionService.idleInhibited = false;
SessionService.inhibitReason = "Keep system awake";
}
}
Component.onCompleted: { Component.onCompleted: {
if (!idleMonitorAvailable) { if (!idleMonitorAvailable) {
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell."); console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");
@@ -148,5 +163,11 @@ Singleton {
console.info("IdleService: Initialized with idle monitoring support"); console.info("IdleService: Initialized with idle monitoring support");
createIdleMonitors(); createIdleMonitors();
} }
if (externalInhibitActive) {
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
SessionService.idleInhibited = true;
SessionService.inhibitReason = "External app: " + (apps || "unknown");
}
} }
} }

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Controls
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
@@ -13,7 +12,7 @@ StyledRect {
onActiveFocusChanged: { onActiveFocusChanged: {
if (activeFocus) { if (activeFocus) {
textInput.forceActiveFocus() textInput.forceActiveFocus();
} }
} }
@@ -53,26 +52,26 @@ StyledRect {
signal focusStateChanged(bool hasFocus) signal focusStateChanged(bool hasFocus)
function getActiveFocus() { function getActiveFocus() {
return textInput.activeFocus return textInput.activeFocus;
} }
function setFocus(value) { function setFocus(value) {
textInput.focus = value textInput.focus = value;
} }
function forceActiveFocus() { function forceActiveFocus() {
textInput.forceActiveFocus() textInput.forceActiveFocus();
} }
function selectAll() { function selectAll() {
textInput.selectAll() textInput.selectAll();
} }
function clear() { function clear() {
textInput.clear() textInput.clear();
} }
function insertText(str) { function insertText(str) {
textInput.insert(textInput.cursorPosition, str) textInput.insert(textInput.cursorPosition, str);
} }
width: 200 width: 200
height: 48 height: Math.round(Theme.fontSizeMedium * 3.4)
radius: cornerRadius radius: cornerRadius
color: backgroundColor color: backgroundColor
border.color: textInput.activeFocus ? focusedBorderColor : normalBorderColor border.color: textInput.activeFocus ? focusedBorderColor : normalBorderColor
@@ -113,25 +112,25 @@ StyledRect {
Keys.forwardTo: root.keyForwardTargets Keys.forwardTo: root.keyForwardTargets
Keys.onLeftPressed: event => { Keys.onLeftPressed: event => {
if (root.ignoreLeftRightKeys) { if (root.ignoreLeftRightKeys) {
event.accepted = true event.accepted = true;
} else { } else {
// Allow normal TextInput cursor movement // Allow normal TextInput cursor movement
event.accepted = false event.accepted = false;
} }
} }
Keys.onRightPressed: event => { Keys.onRightPressed: event => {
if (root.ignoreLeftRightKeys) { if (root.ignoreLeftRightKeys) {
event.accepted = true event.accepted = true;
} else { } else {
event.accepted = false event.accepted = false;
} }
} }
Keys.onPressed: event => { Keys.onPressed: event => {
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) { if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
event.accepted = false event.accepted = false;
for (var i = 0; i < root.keyForwardTargets.length; i++) { for (var i = 0; i < root.keyForwardTargets.length; i++) {
if (root.keyForwardTargets[i]) { if (root.keyForwardTargets[i]) {
root.keyForwardTargets[i].Keys.pressed(event) root.keyForwardTargets[i].Keys.pressed(event);
} }
} }
} }
@@ -171,7 +170,7 @@ StyledRect {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
textInput.text = "" textInput.text = "";
} }
} }
} }

View File

@@ -54,6 +54,12 @@ Item {
readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : [] readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : []
readonly property bool hasConflict: _conflicts.length > 0 readonly property bool hasConflict: _conflicts.length > 0
readonly property real _inputHeight: Math.round(Theme.fontSizeMedium * 3)
readonly property real _chipHeight: Math.round(Theme.fontSizeSmall * 2.3)
readonly property real _buttonHeight: Math.round(Theme.fontSizeMedium * 2.3)
readonly property real _keysColumnWidth: Math.round(Theme.fontSizeSmall * 12)
readonly property real _labelWidth: Math.round(Theme.fontSizeSmall * 5)
signal toggleExpand signal toggleExpand
signal saveBind(string originalKey, var newData) signal saveBind(string originalKey, var newData)
signal removeBind(string key) signal removeBind(string key)
@@ -223,7 +229,7 @@ Item {
Rectangle { Rectangle {
id: collapsedRect id: collapsedRect
width: parent.width width: parent.width
height: Math.max(52, keysColumn.implicitHeight + Theme.spacingM * 2) height: Math.max(root._inputHeight + Theme.spacingM, keysColumn.implicitHeight + Theme.spacingM * 2)
radius: root.isExpanded ? 0 : Theme.cornerRadius radius: root.isExpanded ? 0 : Theme.cornerRadius
topLeftRadius: Theme.cornerRadius topLeftRadius: Theme.cornerRadius
topRightRadius: Theme.cornerRadius topRightRadius: Theme.cornerRadius
@@ -240,7 +246,7 @@ Item {
Column { Column {
id: keysColumn id: keysColumn
Layout.preferredWidth: 140 Layout.preferredWidth: root._keysColumnWidth
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
@@ -253,9 +259,9 @@ Item {
property bool isSelected: root.isExpanded && root.editingKeyIndex === index && !root.addingNewKey property bool isSelected: root.isExpanded && root.editingKeyIndex === index && !root.addingNewKey
width: 140 width: root._keysColumnWidth
height: 28 height: root._chipHeight
radius: 6 radius: root._chipHeight / 4
color: isSelected ? Theme.primary : Theme.surfaceVariant color: isSelected ? Theme.primary : Theme.surfaceVariant
Rectangle { Rectangle {
@@ -332,7 +338,7 @@ Item {
DankIcon { DankIcon {
name: "warning" name: "warning"
size: 14 size: Theme.iconSizeSmall
color: Theme.primary color: Theme.primary
visible: root.hasConfigConflict visible: root.hasConfigConflict
} }
@@ -352,7 +358,7 @@ Item {
DankIcon { DankIcon {
name: root.isExpanded ? "expand_less" : "expand_more" name: root.isExpanded ? "expand_less" : "expand_more"
size: 20 size: Theme.iconSize - 4
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@@ -360,7 +366,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 140 + Theme.spacingM * 2 anchors.leftMargin: root._keysColumnWidth + Theme.spacingM * 2
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: root.toggleExpand() onClicked: root.toggleExpand()
} }
@@ -420,7 +426,7 @@ Item {
DankIcon { DankIcon {
name: "warning" name: "warning"
size: 16 size: Theme.iconSizeSmall
color: Theme.primary color: Theme.primary
} }
@@ -461,7 +467,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
Flow { Flow {
@@ -478,8 +484,8 @@ Item {
property bool isSelected: root.editingKeyIndex === index && !root.addingNewKey property bool isSelected: root.editingKeyIndex === index && !root.addingNewKey
width: editKeyChipText.implicitWidth + Theme.spacingM width: editKeyChipText.implicitWidth + Theme.spacingM
height: 28 height: root._chipHeight
radius: 6 radius: root._chipHeight / 4
color: isSelected ? Theme.primary : Theme.surfaceVariant color: isSelected ? Theme.primary : Theme.surfaceVariant
Rectangle { Rectangle {
@@ -509,9 +515,9 @@ Item {
} }
Rectangle { Rectangle {
width: 28 width: root._chipHeight
height: 28 height: root._chipHeight
radius: 6 radius: root._chipHeight / 4
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
visible: !root.isNew visible: !root.isNew
@@ -523,7 +529,7 @@ Item {
DankIcon { DankIcon {
name: "add" name: "add"
size: 16 size: Theme.iconSizeSmall
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -548,13 +554,13 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
FocusScope { FocusScope {
id: captureScope id: captureScope
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
focus: root.recording focus: root.recording
Component.onCompleted: { Component.onCompleted: {
@@ -596,12 +602,12 @@ Item {
DankActionButton { DankActionButton {
id: recordBtn id: recordBtn
width: 28 width: root._chipHeight
height: 28 height: root._chipHeight
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
circular: false circular: false
iconName: root.recording ? "close" : "radio_button_checked" iconName: root.recording ? "close" : "radio_button_checked"
iconSize: 16 iconSize: Theme.iconSizeSmall
iconColor: root.recording ? Theme.error : Theme.primary iconColor: root.recording ? Theme.error : Theme.primary
onClicked: root.recording ? root.stopRecording() : root.startRecording() onClicked: root.recording ? root.stopRecording() : root.startRecording()
} }
@@ -703,8 +709,8 @@ Item {
} }
Rectangle { Rectangle {
Layout.preferredWidth: 40 Layout.preferredWidth: root._inputHeight
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
visible: root.keys.length === 1 && !root.isNew visible: root.keys.length === 1 && !root.isNew
@@ -717,7 +723,7 @@ Item {
DankIcon { DankIcon {
name: "add" name: "add"
size: 18 size: Theme.iconSizeSmall + 2
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -736,11 +742,11 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Theme.spacingS spacing: Theme.spacingS
visible: root.hasConflict visible: root.hasConflict
Layout.leftMargin: 60 + Theme.spacingM Layout.leftMargin: root._labelWidth + Theme.spacingM
DankIcon { DankIcon {
name: "warning" name: "warning"
size: 16 size: Theme.iconSizeSmall
color: Theme.primary color: Theme.primary
} }
@@ -762,7 +768,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
RowLayout { RowLayout {
@@ -785,7 +791,7 @@ Item {
}) })
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 36 Layout.preferredHeight: root._buttonHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: root._actionType === modelData.id ? Theme.surfaceContainerHighest : Theme.surfaceContainer color: root._actionType === modelData.id ? Theme.surfaceContainerHighest : Theme.surfaceContainer
border.color: root._actionType === modelData.id ? Theme.outline : (typeArea.containsMouse ? Theme.outlineVariant : "transparent") border.color: root._actionType === modelData.id ? Theme.outline : (typeArea.containsMouse ? Theme.outlineVariant : "transparent")
@@ -797,7 +803,7 @@ Item {
DankIcon { DankIcon {
name: typeDelegate.modelData.icon name: typeDelegate.modelData.icon
size: 16 size: Theme.iconSizeSmall
color: root._actionType === typeDelegate.modelData.id ? Theme.surfaceText : Theme.surfaceVariantText color: root._actionType === typeDelegate.modelData.id ? Theme.surfaceText : Theme.surfaceVariantText
} }
@@ -869,7 +875,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
DankDropdown { DankDropdown {
@@ -913,14 +919,14 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
visible: dmsArgsRow.hasAmountArg visible: dmsArgsRow.hasAmountArg
} }
DankTextField { DankTextField {
id: dmsAmountField id: dmsAmountField
Layout.preferredWidth: 80 Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 5.5)
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
placeholderText: "5" placeholderText: "5"
visible: dmsArgsRow.hasAmountArg visible: dmsArgsRow.hasAmountArg
@@ -961,14 +967,14 @@ Item {
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.leftMargin: dmsArgsRow.hasAmountArg ? Theme.spacingM : 0 Layout.leftMargin: dmsArgsRow.hasAmountArg ? Theme.spacingM : 0
Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : 60 Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : root._labelWidth
visible: dmsArgsRow.hasDeviceArg visible: dmsArgsRow.hasDeviceArg
} }
DankTextField { DankTextField {
id: dmsDeviceField id: dmsDeviceField
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
placeholderText: I18n.tr("leave empty for default") placeholderText: I18n.tr("leave empty for default")
visible: dmsArgsRow.hasDeviceArg visible: dmsArgsRow.hasDeviceArg
@@ -1006,7 +1012,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
visible: dmsArgsRow.hasTabArg visible: dmsArgsRow.hasTabArg
} }
@@ -1064,12 +1070,12 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
DankDropdown { DankDropdown {
id: compositorCatDropdown id: compositorCatDropdown
Layout.preferredWidth: 120 Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 8.5)
compactMode: true compactMode: true
currentValue: { currentValue: {
const base = root.editAction.split(" ")[0]; const base = root.editAction.split(" ")[0];
@@ -1108,8 +1114,8 @@ Item {
} }
Rectangle { Rectangle {
Layout.preferredWidth: 40 Layout.preferredWidth: root._inputHeight
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceVariant color: Theme.surfaceVariant
@@ -1121,7 +1127,7 @@ Item {
DankIcon { DankIcon {
name: "edit" name: "edit"
size: 18 size: Theme.iconSizeSmall + 2
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -1150,7 +1156,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
RowLayout { RowLayout {
@@ -1160,7 +1166,7 @@ Item {
DankTextField { DankTextField {
id: argValueField id: argValueField
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
visible: { visible: {
const cfg = optionsRow.argConfig; const cfg = optionsRow.argConfig;
if (!cfg?.config?.args) if (!cfg?.config?.args)
@@ -1308,13 +1314,13 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
DankTextField { DankTextField {
id: customCompositorField id: customCompositorField
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10") placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10")
text: root._actionType === "compositor" ? root.editAction : "" text: root._actionType === "compositor" ? root.editAction : ""
onTextChanged: { onTextChanged: {
@@ -1327,8 +1333,8 @@ Item {
} }
Rectangle { Rectangle {
Layout.preferredWidth: 40 Layout.preferredWidth: root._inputHeight
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceVariant color: Theme.surfaceVariant
@@ -1340,7 +1346,7 @@ Item {
DankIcon { DankIcon {
name: "list" name: "list"
size: 18 size: Theme.iconSizeSmall + 2
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -1371,13 +1377,13 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
DankTextField { DankTextField {
id: spawnTextField id: spawnTextField
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
placeholderText: I18n.tr("e.g., firefox, kitty --title foo") placeholderText: I18n.tr("e.g., firefox, kitty --title foo")
readonly property var _parsed: root._actionType === "spawn" ? Actions.parseSpawnCommand(root.editAction) : null readonly property var _parsed: root._actionType === "spawn" ? Actions.parseSpawnCommand(root.editAction) : null
text: _parsed ? (_parsed.command + " " + _parsed.args.join(" ")).trim() : "" text: _parsed ? (_parsed.command + " " + _parsed.args.join(" ")).trim() : ""
@@ -1403,13 +1409,13 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
DankTextField { DankTextField {
id: shellTextField id: shellTextField
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
placeholderText: I18n.tr("e.g., notify-send 'Hello' && sleep 1") placeholderText: I18n.tr("e.g., notify-send 'Hello' && sleep 1")
text: root._actionType === "shell" ? Actions.parseShellCommand(root.editAction) : "" text: root._actionType === "shell" ? Actions.parseShellCommand(root.editAction) : ""
onTextChanged: { onTextChanged: {
@@ -1431,13 +1437,13 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
DankTextField { DankTextField {
id: titleField id: titleField
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
placeholderText: I18n.tr("Hotkey overlay title (optional)") placeholderText: I18n.tr("Hotkey overlay title (optional)")
text: root.editDesc text: root.editDesc
onTextChanged: root.updateEdit({ onTextChanged: root.updateEdit({
@@ -1455,13 +1461,13 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
Layout.preferredWidth: 60 Layout.preferredWidth: root._labelWidth
} }
DankTextField { DankTextField {
id: cooldownField id: cooldownField
Layout.preferredWidth: 100 Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 7)
Layout.preferredHeight: 40 Layout.preferredHeight: root._inputHeight
placeholderText: "0" placeholderText: "0"
Connections { Connections {
@@ -1508,8 +1514,8 @@ Item {
spacing: Theme.spacingM spacing: Theme.spacingM
DankActionButton { DankActionButton {
Layout.preferredWidth: 32 Layout.preferredWidth: root._buttonHeight
Layout.preferredHeight: 32 Layout.preferredHeight: root._buttonHeight
circular: false circular: false
iconName: "delete" iconName: "delete"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4
@@ -1531,7 +1537,7 @@ Item {
DankButton { DankButton {
text: I18n.tr("Cancel") text: I18n.tr("Cancel")
buttonHeight: 32 buttonHeight: root._buttonHeight
backgroundColor: Theme.surfaceContainer backgroundColor: Theme.surfaceContainer
textColor: Theme.surfaceText textColor: Theme.surfaceText
visible: root.hasChanges || root.isNew visible: root.hasChanges || root.isNew
@@ -1547,7 +1553,7 @@ Item {
DankButton { DankButton {
text: root.isNew ? I18n.tr("Add") : I18n.tr("Save") text: root.isNew ? I18n.tr("Add") : I18n.tr("Save")
buttonHeight: 32 buttonHeight: root._buttonHeight
enabled: root.canSave() enabled: root.canSave()
visible: root.hasChanges || root.isNew visible: root.hasChanges || root.isNew
onClicked: root.doSave() onClicked: root.doSave()

View File

@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Modals.FileBrowser import qs.Modals.FileBrowser
@@ -13,7 +14,7 @@ Rectangle {
property string expandedUuid: "" property string expandedUuid: ""
property int listHeight: 180 property int listHeight: 180
implicitHeight: contentColumn.implicitHeight + Theme.spacingM * 2 implicitHeight: 32 + 1 + listHeight + Theme.spacingS * 4 + Theme.spacingM * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
@@ -153,25 +154,14 @@ Rectangle {
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
} }
DankFlickable {
width: parent.width
height: root.listHeight
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: 4
Item { Item {
width: parent.width width: parent.width
height: DMSNetworkService.profiles.length === 0 ? 100 : 0 height: root.listHeight
visible: height > 0
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
visible: DMSNetworkService.profiles.length === 0
DankIcon { DankIcon {
name: "vpn_key_off" name: "vpn_key_off"
@@ -194,292 +184,46 @@ Rectangle {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
}
Repeater { DankListView {
model: DMSNetworkService.profiles id: vpnListView
anchors.fill: parent
delegate: Rectangle { visible: DMSNetworkService.profiles.length > 0
id: profileRow spacing: 4
required property var modelData cacheBuffer: 200
required property int index
readonly property bool isActive: DMSNetworkService.isActiveUuid(modelData.uuid)
readonly property bool isExpanded: root.expandedUuid === modelData.uuid
readonly property bool isHovered: rowArea.containsMouse || expandBtn.containsMouse || deleteBtn.containsMouse
readonly property var configData: isExpanded ? VPNService.editConfig : null
width: listCol.width
height: isExpanded ? 46 + expandedContent.height : 46
radius: Theme.cornerRadius
color: isHovered ? Theme.primaryHoverLight : (isActive ? Theme.primaryPressed : Theme.surfaceLight)
border.width: isActive ? 2 : 1
border.color: isActive ? Theme.primary : Theme.outlineLight
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
clip: true clip: true
Behavior on height { model: ScriptModel {
NumberAnimation { values: DMSNetworkService.profiles
duration: 150 objectProp: "uuid"
easing.type: Easing.OutQuad
}
} }
MouseArea { delegate: VpnProfileDelegate {
id: rowArea required property var modelData
anchors.fill: parent width: vpnListView.width
hoverEnabled: true profile: modelData
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor isExpanded: root.expandedUuid === modelData.uuid
enabled: !DMSNetworkService.isBusy onToggleExpand: {
onClicked: DMSNetworkService.toggle(modelData.uuid) if (root.expandedUuid === modelData.uuid) {
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingS
Row {
width: parent.width
height: 46 - Theme.spacingS * 2
spacing: Theme.spacingS
DankIcon {
name: isActive ? "vpn_lock" : "vpn_key_off"
size: 20
color: isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
spacing: 1
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 20 - 28 - 28 - Theme.spacingS * 4
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: isActive ? Theme.primary : Theme.surfaceText
elide: Text.ElideRight
wrapMode: Text.NoWrap
width: parent.width
}
StyledText {
text: VPNService.getVpnTypeFromProfile(modelData)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
wrapMode: Text.NoWrap
width: parent.width
elide: Text.ElideRight
}
}
Item {
width: Theme.spacingXS
height: 1
}
Rectangle {
id: expandBtnRect
width: 28
height: 28
radius: 14
color: expandBtn.containsMouse ? Theme.surfacePressed : "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: isExpanded ? "expand_less" : "expand_more"
size: 18
color: Theme.surfaceText
}
MouseArea {
id: expandBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (isExpanded) {
root.expandedUuid = ""; root.expandedUuid = "";
} else { return;
}
root.expandedUuid = modelData.uuid; root.expandedUuid = modelData.uuid;
VPNService.getConfig(modelData.uuid); VPNService.getConfig(modelData.uuid);
} }
} onDeleteRequested: {
}
}
Rectangle {
id: deleteBtnRect
width: 28
height: 28
radius: 14
color: deleteBtn.containsMouse ? Theme.errorHover : "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 18
color: deleteBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: deleteBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
deleteConfirm.showWithOptions({ deleteConfirm.showWithOptions({
title: I18n.tr("Delete VPN"), "title": I18n.tr("Delete VPN"),
message: I18n.tr("Delete \"") + modelData.name + "\"?", "message": I18n.tr("Delete \"") + modelData.name + "\"?",
confirmText: I18n.tr("Delete"), "confirmText": I18n.tr("Delete"),
confirmColor: Theme.error, "confirmColor": Theme.error,
onConfirm: () => VPNService.deleteVpn(modelData.uuid) "onConfirm": () => VPNService.deleteVpn(modelData.uuid)
}); });
} }
} }
} }
} }
Column {
id: expandedContent
width: parent.width
spacing: Theme.spacingXS
visible: isExpanded
Rectangle {
width: parent.width
height: 1
color: Theme.outlineLight
}
Item {
width: parent.width
height: VPNService.configLoading ? 40 : 0
visible: VPNService.configLoading
Row {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "sync"
size: 16
color: Theme.surfaceVariantText
}
StyledText {
text: I18n.tr("Loading...")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
Flow {
width: parent.width
spacing: Theme.spacingXS
visible: !VPNService.configLoading && configData
Repeater {
model: {
if (!configData)
return [];
const fields = [];
const data = configData.data || {};
if (data.remote)
fields.push({
label: I18n.tr("Server"),
value: data.remote
});
if (configData.username || data.username)
fields.push({
label: I18n.tr("Username"),
value: configData.username || data.username
});
if (data.cipher)
fields.push({
label: I18n.tr("Cipher"),
value: data.cipher
});
if (data.auth)
fields.push({
label: I18n.tr("Auth"),
value: data.auth
});
if (data["proto-tcp"] === "yes" || data["proto-tcp"] === "no")
fields.push({
label: I18n.tr("Protocol"),
value: data["proto-tcp"] === "yes" ? "TCP" : "UDP"
});
if (data["tunnel-mtu"])
fields.push({
label: I18n.tr("MTU"),
value: data["tunnel-mtu"]
});
if (data["connection-type"])
fields.push({
label: I18n.tr("Auth Type"),
value: data["connection-type"]
});
fields.push({
label: I18n.tr("Autoconnect"),
value: configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
});
return fields;
}
delegate: Rectangle {
required property var modelData
required property int index
width: fieldContent.width + Theme.spacingM * 2
height: 32
radius: Theme.cornerRadius - 2
color: Theme.surfaceContainerHigh
border.width: 1
border.color: Theme.outlineLight
Row {
id: fieldContent
anchors.centerIn: parent
spacing: Theme.spacingXS
StyledText {
text: modelData.label + ":"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.value
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
Item {
width: 1
height: Theme.spacingXS
}
}
}
}
}
}
}
Item { Item {
width: 1 width: 1
height: Theme.spacingS height: Theme.spacingS

View File

@@ -0,0 +1,275 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
required property var profile
property bool isExpanded: false
signal toggleExpand
signal deleteRequested
readonly property bool isActive: DMSNetworkService.activeUuids?.includes(profile?.uuid) ?? false
readonly property bool isHovered: rowArea.containsMouse || expandBtn.containsMouse || deleteBtn.containsMouse
readonly property var configData: isExpanded ? VPNService.editConfig : null
readonly property var configFields: buildConfigFields()
height: isExpanded ? 46 + expandedContent.height : 46
radius: Theme.cornerRadius
color: isHovered ? Theme.primaryHoverLight : (isActive ? Theme.primaryPressed : Theme.surfaceLight)
border.width: isActive ? 2 : 1
border.color: isActive ? Theme.primary : Theme.outlineLight
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
clip: true
function buildConfigFields() {
if (!configData)
return [];
const fields = [];
const data = configData.data || {};
if (data.remote)
fields.push({
"key": "server",
"label": I18n.tr("Server"),
"value": data.remote
});
if (configData.username || data.username)
fields.push({
"key": "user",
"label": I18n.tr("Username"),
"value": configData.username || data.username
});
if (data.cipher)
fields.push({
"key": "cipher",
"label": I18n.tr("Cipher"),
"value": data.cipher
});
if (data.auth)
fields.push({
"key": "auth",
"label": I18n.tr("Auth"),
"value": data.auth
});
if (data["proto-tcp"] === "yes" || data["proto-tcp"] === "no")
fields.push({
"key": "proto",
"label": I18n.tr("Protocol"),
"value": data["proto-tcp"] === "yes" ? "TCP" : "UDP"
});
if (data["tunnel-mtu"])
fields.push({
"key": "mtu",
"label": I18n.tr("MTU"),
"value": data["tunnel-mtu"]
});
if (data["connection-type"])
fields.push({
"key": "conntype",
"label": I18n.tr("Auth Type"),
"value": data["connection-type"]
});
fields.push({
"key": "auto",
"label": I18n.tr("Autoconnect"),
"value": configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
});
return fields;
}
Behavior on height {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.toggle(profile.uuid)
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingS
Row {
width: parent.width
height: 46 - Theme.spacingS * 2
spacing: Theme.spacingS
DankIcon {
name: isActive ? "vpn_lock" : "vpn_key_off"
size: 20
color: isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
spacing: 1
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 20 - 28 - 28 - Theme.spacingS * 4
StyledText {
text: profile?.name ?? ""
font.pixelSize: Theme.fontSizeMedium
color: isActive ? Theme.primary : Theme.surfaceText
elide: Text.ElideRight
wrapMode: Text.NoWrap
width: parent.width
}
StyledText {
text: VPNService.getVpnTypeFromProfile(profile)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
wrapMode: Text.NoWrap
width: parent.width
elide: Text.ElideRight
}
}
Item {
width: Theme.spacingXS
height: 1
}
Rectangle {
width: 28
height: 28
radius: 14
color: expandBtn.containsMouse ? Theme.surfacePressed : "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: isExpanded ? "expand_less" : "expand_more"
size: 18
color: Theme.surfaceText
}
MouseArea {
id: expandBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.toggleExpand()
}
}
Rectangle {
width: 28
height: 28
radius: 14
color: deleteBtn.containsMouse ? Theme.errorHover : "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 18
color: deleteBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: deleteBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.deleteRequested()
}
}
}
Column {
id: expandedContent
width: parent.width
spacing: Theme.spacingXS
visible: isExpanded
Rectangle {
width: parent.width
height: 1
color: Theme.outlineLight
}
Item {
width: parent.width
height: VPNService.configLoading ? 40 : 0
visible: VPNService.configLoading
Row {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "sync"
size: 16
color: Theme.surfaceVariantText
}
StyledText {
text: I18n.tr("Loading...")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
Flow {
width: parent.width
spacing: Theme.spacingXS
visible: !VPNService.configLoading && configData
Repeater {
model: configFields
delegate: Rectangle {
required property var modelData
width: fieldContent.width + Theme.spacingM * 2
height: 32
radius: Theme.cornerRadius - 2
color: Theme.surfaceContainerHigh
border.width: 1
border.color: Theme.outlineLight
Row {
id: fieldContent
anchors.centerIn: parent
spacing: Theme.spacingXS
StyledText {
text: modelData.label + ":"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.value
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
Item {
width: 1
height: Theme.spacingXS
}
}
}
}