mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-30 00:12:50 -05:00
Compare commits
14 Commits
32f218d58c
...
891f53cf6f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
891f53cf6f | ||
|
|
848991cf5b | ||
|
|
d37ddd1d41 | ||
|
|
00d12acd5e | ||
|
|
3bbc78a44f | ||
|
|
b0a6652cc6 | ||
|
|
cb710b2e5f | ||
|
|
ca5fe6f7db | ||
|
|
fb75f4c68b | ||
|
|
5e2a418485 | ||
|
|
24fe215067 | ||
|
|
ab2e8875ac | ||
|
|
dec5740c74 | ||
|
|
208266dfa3 |
383
.github/workflows/backup/run-obs.yml.bak
vendored
Normal file
383
.github/workflows/backup/run-obs.yml.bak
vendored
Normal 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
298
.github/workflows/backup/run-ppa.yml.bak
vendored
Normal 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
|
||||
210
.github/workflows/run-obs.yml
vendored
210
.github/workflows/run-obs.yml
vendored
@@ -41,24 +41,11 @@ jobs:
|
||||
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
|
||||
env:
|
||||
OBS_USERNAME: ${{ secrets.OBS_USERNAME }}
|
||||
OBS_PASSWORD: ${{ secrets.OBS_PASSWORD }}
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||
@@ -73,40 +60,29 @@ jobs:
|
||||
# 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"
|
||||
# Query OBS API for current spec file version
|
||||
# Format: Version: 1.0.2+git2528.d336866f
|
||||
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 "")
|
||||
|
||||
if [[ -d "$OBS_BASE/$OBS_PROJECT/dms-git" ]]; then
|
||||
cd "$OBS_BASE/$OBS_PROJECT/dms-git"
|
||||
osc up -q 2>/dev/null || true
|
||||
if [[ -n "$OBS_SPEC" && "$OBS_SPEC" != *"error"* ]]; then
|
||||
# Extract commit hash from Version line (e.g., d336866f from 1.0.2+git2528.d336866f)
|
||||
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 [[ "$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
|
||||
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 "📋 Could not extract OBS commit, proceeding with update"
|
||||
echo "📋 New commit detected: $CURRENT_COMMIT (OBS has $OBS_COMMIT)"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 No spec file in OBS, proceeding with update"
|
||||
echo "📋 Could not extract OBS commit, proceeding with update"
|
||||
fi
|
||||
|
||||
cd "${{ github.workspace }}"
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 First upload to OBS, update needed"
|
||||
echo "📋 Could not fetch OBS spec, proceeding with update"
|
||||
fi
|
||||
elif [[ "${{ github.event.inputs.force_upload }}" == "true" ]]; then
|
||||
PKG="${{ github.event.inputs.package }}"
|
||||
@@ -132,42 +108,18 @@ jobs:
|
||||
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
|
||||
@@ -186,83 +138,63 @@ jobs:
|
||||
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')
|
||||
if: 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
|
||||
# Single changelog entry (git snapshots don't need history)
|
||||
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
|
||||
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' 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
|
||||
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: |
|
||||
# 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"
|
||||
|
||||
# Single changelog entry (git snapshots don't need history)
|
||||
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
|
||||
{
|
||||
echo "dms-git ($NEW_VERSION) nightly; urgency=medium"
|
||||
echo ""
|
||||
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||
echo ""
|
||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||
} > "distro/debian/dms-git/debian/changelog"
|
||||
|
||||
- name: Update dms stable version
|
||||
if: steps.check-loop.outputs.skip != 'true' && steps.packages.outputs.version != ''
|
||||
if: 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
|
||||
# Single changelog entry (full history on OBS website)
|
||||
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
|
||||
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' 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)
|
||||
for service in distro/debian/*/_service; do
|
||||
@@ -276,35 +208,25 @@ jobs:
|
||||
fi
|
||||
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
|
||||
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 "dms ($VERSION_NO_V) stable; urgency=medium"
|
||||
echo ""
|
||||
echo " * Update to $VERSION stable release"
|
||||
echo ""
|
||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||
} > "distro/debian/dms/debian/changelog"
|
||||
echo "✓ Updated Debian changelog to $VERSION_NO_V"
|
||||
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
|
||||
@@ -321,7 +243,6 @@ jobs:
|
||||
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 }}
|
||||
@@ -340,35 +261,6 @@ jobs:
|
||||
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
|
||||
|
||||
81
.github/workflows/run-ppa.yml
vendored
81
.github/workflows/run-ppa.yml
vendored
@@ -44,31 +44,32 @@ jobs:
|
||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||
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)
|
||||
|
||||
# Extract commit hash from changelog
|
||||
# Format: dms-git (0.6.2+git2264.c5c5ce84) questing; urgency=medium
|
||||
CHANGELOG_FILE="distro/ubuntu/dms-git/debian/changelog"
|
||||
# Query Launchpad API for last published version
|
||||
# Format: 1.0.2+git2528.d336866fppa1
|
||||
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
|
||||
CHANGELOG_COMMIT=$(head -1 "$CHANGELOG_FILE" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
|
||||
if [[ -n "$PPA_VERSION" ]]; then
|
||||
# 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 [[ "$CURRENT_COMMIT" == "$CHANGELOG_COMMIT" ]]; then
|
||||
if [[ -n "$PPA_COMMIT" ]]; then
|
||||
if [[ "$CURRENT_COMMIT" == "$PPA_COMMIT" ]]; then
|
||||
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
|
||||
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
|
||||
else
|
||||
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
|
||||
else
|
||||
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
|
||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
||||
@@ -83,49 +84,24 @@ jobs:
|
||||
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 \
|
||||
@@ -138,7 +114,6 @@ jobs:
|
||||
dpkg-dev
|
||||
|
||||
- name: Configure GPG
|
||||
if: steps.check-loop.outputs.skip != 'true'
|
||||
env:
|
||||
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
@@ -147,7 +122,6 @@ jobs:
|
||||
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
|
||||
@@ -180,7 +154,6 @@ jobs:
|
||||
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 }}"
|
||||
@@ -240,34 +213,6 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -11,4 +11,9 @@ const (
|
||||
dbusPortalSettingsInterface = "org.freedesktop.portal.Settings"
|
||||
|
||||
dbusPropsInterface = "org.freedesktop.DBus.Properties"
|
||||
|
||||
dbusScreensaverName = "org.freedesktop.ScreenSaver"
|
||||
dbusScreensaverPath = "/ScreenSaver"
|
||||
dbusScreensaverPath2 = "/org/freedesktop/ScreenSaver"
|
||||
dbusScreensaverInterface = "org.freedesktop.ScreenSaver"
|
||||
)
|
||||
|
||||
@@ -22,8 +22,9 @@ func NewManager() (*Manager, error) {
|
||||
|
||||
m := &Manager{
|
||||
state: &FreedeskState{
|
||||
Accounts: AccountsState{},
|
||||
Settings: SettingsState{},
|
||||
Accounts: AccountsState{},
|
||||
Settings: SettingsState{},
|
||||
Screensaver: ScreensaverState{},
|
||||
},
|
||||
stateMutex: sync.RWMutex{},
|
||||
systemConn: systemConn,
|
||||
@@ -33,6 +34,7 @@ func NewManager() (*Manager, error) {
|
||||
|
||||
m.initializeAccounts()
|
||||
m.initializeSettings()
|
||||
m.initializeScreensaver()
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
250
core/internal/server/freedesktop/screensaver.go
Normal file
250
core/internal/server/freedesktop/screensaver.go
Normal 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
|
||||
})
|
||||
}
|
||||
@@ -29,18 +29,35 @@ type SettingsState struct {
|
||||
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 {
|
||||
Accounts AccountsState `json:"accounts"`
|
||||
Settings SettingsState `json:"settings"`
|
||||
Accounts AccountsState `json:"accounts"`
|
||||
Settings SettingsState `json:"settings"`
|
||||
Screensaver ScreensaverState `json:"screensaver"`
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
state *FreedeskState
|
||||
stateMutex sync.RWMutex
|
||||
systemConn *dbus.Conn
|
||||
sessionConn *dbus.Conn
|
||||
accountsObj dbus.BusObject
|
||||
settingsObj dbus.BusObject
|
||||
currentUID uint64
|
||||
subscribers syncmap.Map[string, chan FreedeskState]
|
||||
state *FreedeskState
|
||||
stateMutex sync.RWMutex
|
||||
systemConn *dbus.Conn
|
||||
sessionConn *dbus.Conn
|
||||
accountsObj dbus.BusObject
|
||||
settingsObj dbus.BusObject
|
||||
currentUID uint64
|
||||
subscribers syncmap.Map[string, chan FreedeskState]
|
||||
screensaverSubscribers syncmap.Map[string, chan ScreensaverState]
|
||||
screensaverCookieCounter uint32
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||
)
|
||||
|
||||
const APIVersion = 23
|
||||
const APIVersion = 24
|
||||
|
||||
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 {
|
||||
wg.Add(1)
|
||||
waylandChan := waylandManager.Subscribe(clientID + "-gamma")
|
||||
|
||||
@@ -255,29 +255,20 @@ if [ "$IS_GIT_PACKAGE" = false ] && [ -n "$GIT_REPO" ]; then
|
||||
else
|
||||
info "Updating changelog to latest tag: $LATEST_TAG"
|
||||
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
|
||||
CHANGELOG_MSG="Rebuild for packaging fixes (ppa${PPA_NUM})"
|
||||
else
|
||||
CHANGELOG_MSG="Upstream release ${LATEST_TAG}"
|
||||
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}
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
||||
echo "$CHANGELOG_ENTRY" >debian/changelog
|
||||
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||
echo "" >>debian/changelog
|
||||
echo "$CHANGELOG_CONTENT" >>debian/changelog
|
||||
fi
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)
|
||||
EOF
|
||||
success "Version updated to $NEW_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}"
|
||||
|
||||
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
|
||||
|
||||
CHANGELOG_ENTRY="${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
|
||||
# Single changelog entry (git snapshots don't need history)
|
||||
cat >debian/changelog <<EOF
|
||||
${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
|
||||
|
||||
* Git snapshot (commit ${GIT_COMMIT_COUNT}: ${GIT_COMMIT_HASH})
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
||||
|
||||
echo "$CHANGELOG_ENTRY" >debian/changelog
|
||||
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||
echo "" >>debian/changelog
|
||||
echo "$CHANGELOG_CONTENT" >>debian/changelog
|
||||
fi
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)
|
||||
EOF
|
||||
success "Version updated to $NEW_VERSION"
|
||||
CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store
|
||||
Singleton {
|
||||
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"
|
||||
|
||||
@@ -352,6 +352,7 @@ Singleton {
|
||||
property string customPowerActionReboot: ""
|
||||
property string customPowerActionPowerOff: ""
|
||||
|
||||
property bool updaterHideWidget: false
|
||||
property bool updaterUseCustomCommand: false
|
||||
property string updaterCustomCommand: ""
|
||||
property string updaterTerminalAdditionalParams: ""
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pragma Singleton
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
@@ -17,40 +16,67 @@ Singleton {
|
||||
pciId: "",
|
||||
mountPath: "/",
|
||||
minimumWidth: true,
|
||||
showSwap: false
|
||||
}
|
||||
leftModel.append(dummy)
|
||||
centerModel.append(dummy)
|
||||
rightModel.append(dummy)
|
||||
showSwap: false,
|
||||
mediaSize: 1,
|
||||
showNetworkIcon: true,
|
||||
showBluetoothIcon: true,
|
||||
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(centerModel, center)
|
||||
update(rightModel, right)
|
||||
update(leftModel, left);
|
||||
update(centerModel, center);
|
||||
update(rightModel, right);
|
||||
}
|
||||
|
||||
function update(model, order) {
|
||||
model.clear()
|
||||
model.clear();
|
||||
for (var i = 0; i < order.length; i++) {
|
||||
var widgetId = typeof order[i] === "string" ? order[i] : order[i].id
|
||||
var enabled = typeof order[i] === "string" ? true : order[i].enabled
|
||||
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 isObj = typeof order[i] !== "string";
|
||||
var widgetId = isObj ? order[i].id : order[i];
|
||||
var item = {
|
||||
widgetId: widgetId,
|
||||
enabled: enabled
|
||||
}
|
||||
if (size !== undefined) item.size = size
|
||||
if (selectedGpuIndex !== undefined) item.selectedGpuIndex = selectedGpuIndex
|
||||
if (pciId !== undefined) item.pciId = pciId
|
||||
if (mountPath !== undefined) item.mountPath = mountPath
|
||||
if (minimumWidth !== undefined) item.minimumWidth = minimumWidth
|
||||
if (showSwap !== undefined) item.showSwap = showSwap
|
||||
enabled: isObj ? order[i].enabled : true
|
||||
};
|
||||
if (isObj && order[i].size !== undefined)
|
||||
item.size = order[i].size;
|
||||
if (isObj && order[i].selectedGpuIndex !== undefined)
|
||||
item.selectedGpuIndex = order[i].selectedGpuIndex;
|
||||
if (isObj && order[i].pciId !== undefined)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ var SPEC = {
|
||||
customPowerActionReboot: { def: "" },
|
||||
customPowerActionPowerOff: { def: "" },
|
||||
|
||||
updaterHideWidget: { def: false },
|
||||
updaterUseCustomCommand: { def: false },
|
||||
updaterCustomCommand: { def: "" },
|
||||
updaterTerminalAdditionalParams: { def: "" },
|
||||
|
||||
@@ -113,6 +113,12 @@ function migrateToVersion(obj, targetVersion) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ DankModal {
|
||||
layerNamespace: "dms:clipboard"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [clipboardHistoryModal.contentWindow]
|
||||
windows: [clipboardHistoryModal.contentWindow, clipboardHistoryModal.backgroundWindow]
|
||||
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ DankModal {
|
||||
keepPopoutsOpen: true
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
windows: [root.contentWindow, root.backgroundWindow]
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ DankModal {
|
||||
layerNamespace: "dms:spotlight"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [spotlightModal.contentWindow]
|
||||
windows: [spotlightModal.contentWindow, spotlightModal.backgroundWindow]
|
||||
active: spotlightModal.useHyprlandFocusGrab && spotlightModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ PluginComponent {
|
||||
|
||||
ccDetailContent: Component {
|
||||
VpnDetailContent {
|
||||
listHeight: 180
|
||||
listHeight: 260
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,16 @@ Item {
|
||||
|
||||
function getDetailHeight(section) {
|
||||
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);
|
||||
if (section === "bluetooth")
|
||||
return Math.min(350, maxAvailable);
|
||||
if (section.startsWith("brightnessSlider_"))
|
||||
case section.startsWith("brightnessSlider_"):
|
||||
return Math.min(400, maxAvailable);
|
||||
return Math.min(250, maxAvailable);
|
||||
default:
|
||||
return Math.min(250, maxAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
||||
@@ -552,22 +552,31 @@ DankPopout {
|
||||
}
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||
property int currentProfileIndex: {
|
||||
if (typeof PowerProfiles === "undefined")
|
||||
return 1;
|
||||
return profileModel.findIndex(profile => root.isActiveProfile(profile));
|
||||
}
|
||||
Item {
|
||||
width: parent.width
|
||||
height: profileButtonGroup.height * profileButtonGroup.scale
|
||||
|
||||
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
||||
currentIndex: currentProfileIndex
|
||||
selectionMode: "single"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
root.setProfile(profileModel[index]);
|
||||
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 int currentProfileIndex: {
|
||||
if (typeof PowerProfiles === "undefined")
|
||||
return 1;
|
||||
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))
|
||||
currentIndex: currentProfileIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
root.setProfile(profileModel[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ DankPopout {
|
||||
triggerY = y;
|
||||
triggerWidth = width;
|
||||
triggerSection = section;
|
||||
triggerScreen = screen;
|
||||
root.screen = screen;
|
||||
|
||||
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
|
||||
@@ -102,6 +103,8 @@ DankPopout {
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: false
|
||||
|
||||
onBackgroundClicked: close()
|
||||
|
||||
content: Component {
|
||||
Rectangle {
|
||||
id: layoutContent
|
||||
|
||||
@@ -13,14 +13,14 @@ BasePill {
|
||||
property var widgetData: null
|
||||
property string screenName: ""
|
||||
property string screenModel: ""
|
||||
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
|
||||
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
|
||||
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
|
||||
property bool showVpnIcon: SettingsData.controlCenterShowVpnIcon
|
||||
property bool showBrightnessIcon: SettingsData.controlCenterShowBrightnessIcon
|
||||
property bool showMicIcon: SettingsData.controlCenterShowMicIcon
|
||||
property bool showBatteryIcon: SettingsData.controlCenterShowBatteryIcon
|
||||
property bool showPrinterIcon: SettingsData.controlCenterShowPrinterIcon
|
||||
property bool showNetworkIcon: widgetData?.showNetworkIcon !== undefined ? widgetData.showNetworkIcon : SettingsData.controlCenterShowNetworkIcon
|
||||
property bool showBluetoothIcon: widgetData?.showBluetoothIcon !== undefined ? widgetData.showBluetoothIcon : SettingsData.controlCenterShowBluetoothIcon
|
||||
property bool showAudioIcon: widgetData?.showAudioIcon !== undefined ? widgetData.showAudioIcon : SettingsData.controlCenterShowAudioIcon
|
||||
property bool showVpnIcon: widgetData?.showVpnIcon !== undefined ? widgetData.showVpnIcon : SettingsData.controlCenterShowVpnIcon
|
||||
property bool showBrightnessIcon: widgetData?.showBrightnessIcon !== undefined ? widgetData.showBrightnessIcon : SettingsData.controlCenterShowBrightnessIcon
|
||||
property bool showMicIcon: widgetData?.showMicIcon !== undefined ? widgetData.showMicIcon : SettingsData.controlCenterShowMicIcon
|
||||
property bool showBatteryIcon: widgetData?.showBatteryIcon !== undefined ? widgetData.showBatteryIcon : SettingsData.controlCenterShowBatteryIcon
|
||||
property bool showPrinterIcon: widgetData?.showPrinterIcon !== undefined ? widgetData.showPrinterIcon : SettingsData.controlCenterShowPrinterIcon
|
||||
|
||||
Loader {
|
||||
active: root.showPrinterIcon
|
||||
|
||||
@@ -358,7 +358,7 @@ Item {
|
||||
IconImage {
|
||||
id: iconImg
|
||||
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
|
||||
width: Theme.barIconSize(root.barThickness)
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
@@ -385,7 +385,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
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
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
name: "sports_esports"
|
||||
@@ -607,7 +607,7 @@ Item {
|
||||
IconImage {
|
||||
id: iconImg
|
||||
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
|
||||
width: Theme.barIconSize(root.barThickness)
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
@@ -634,7 +634,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
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
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
name: "sports_esports"
|
||||
|
||||
@@ -11,6 +11,9 @@ BasePill {
|
||||
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
|
||||
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 {
|
||||
service: SystemUpdateService
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Greetd
|
||||
import Quickshell.Services.Pam
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -22,74 +20,67 @@ Item {
|
||||
property string hyprlandCurrentLayout: ""
|
||||
property string hyprlandKeyboard: ""
|
||||
property int hyprlandLayoutCount: 0
|
||||
property bool isPrimaryScreen: {
|
||||
if (!Qt.application.screens || Qt.application.screens.length === 0)
|
||||
return true
|
||||
if (!screenName || screenName === "")
|
||||
return true
|
||||
return screenName === Qt.application.screens[0].name
|
||||
}
|
||||
property bool isPrimaryScreen: !Quickshell.screens?.length || screenName === Quickshell.screens[0]?.name
|
||||
|
||||
signal launchRequested
|
||||
|
||||
function pickRandomFact() {
|
||||
randomFact = Facts.getRandomFact()
|
||||
randomFact = Facts.getRandomFact();
|
||||
}
|
||||
|
||||
property bool weatherInitialized: false
|
||||
|
||||
function initWeatherService() {
|
||||
if (weatherInitialized)
|
||||
return
|
||||
return;
|
||||
if (!GreetdSettings.settingsLoaded)
|
||||
return
|
||||
return;
|
||||
if (!GreetdSettings.weatherEnabled)
|
||||
return
|
||||
|
||||
weatherInitialized = true
|
||||
WeatherService.addRef()
|
||||
WeatherService.forceRefresh()
|
||||
return;
|
||||
weatherInitialized = true;
|
||||
WeatherService.addRef();
|
||||
WeatherService.forceRefresh();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: GreetdSettings
|
||||
function onSettingsLoadedChanged() {
|
||||
if (GreetdSettings.settingsLoaded)
|
||||
initWeatherService()
|
||||
initWeatherService();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
pickRandomFact()
|
||||
initWeatherService()
|
||||
pickRandomFact();
|
||||
initWeatherService();
|
||||
|
||||
if (isPrimaryScreen) {
|
||||
sessionListProc.running = true
|
||||
applyLastSuccessfulUser()
|
||||
sessionListProc.running = true;
|
||||
applyLastSuccessfulUser();
|
||||
}
|
||||
|
||||
if (CompositorService.isHyprland)
|
||||
updateHyprlandLayout()
|
||||
updateHyprlandLayout();
|
||||
}
|
||||
|
||||
function applyLastSuccessfulUser() {
|
||||
const lastUser = GreetdMemory.lastSuccessfulUser
|
||||
const lastUser = GreetdMemory.lastSuccessfulUser;
|
||||
if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) {
|
||||
GreeterState.username = lastUser
|
||||
GreeterState.usernameInput = lastUser
|
||||
GreeterState.showPasswordInput = true
|
||||
PortalService.getGreeterUserProfileImage(lastUser)
|
||||
GreeterState.username = lastUser;
|
||||
GreeterState.usernameInput = lastUser;
|
||||
GreeterState.showPasswordInput = true;
|
||||
PortalService.getGreeterUserProfileImage(lastUser);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (weatherInitialized)
|
||||
WeatherService.removeRef()
|
||||
WeatherService.removeRef();
|
||||
}
|
||||
|
||||
function updateHyprlandLayout() {
|
||||
if (CompositorService.isHyprland) {
|
||||
hyprlandLayoutProcess.running = true
|
||||
hyprlandLayoutProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,27 +91,27 @@ Item {
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const data = JSON.parse(text)
|
||||
const mainKeyboard = data.keyboards.find(kb => kb.main === true)
|
||||
hyprlandKeyboard = mainKeyboard.name
|
||||
const data = JSON.parse(text);
|
||||
const mainKeyboard = data.keyboards.find(kb => kb.main === true);
|
||||
hyprlandKeyboard = mainKeyboard.name;
|
||||
if (mainKeyboard && mainKeyboard.active_keymap) {
|
||||
const parts = mainKeyboard.active_keymap.split(" ")
|
||||
const parts = mainKeyboard.active_keymap.split(" ");
|
||||
if (parts.length > 0) {
|
||||
hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase()
|
||||
hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase();
|
||||
} else {
|
||||
hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase()
|
||||
hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase();
|
||||
}
|
||||
} else {
|
||||
hyprlandCurrentLayout = ""
|
||||
hyprlandCurrentLayout = "";
|
||||
}
|
||||
if (mainKeyboard && mainKeyboard.layout_names) {
|
||||
hyprlandLayoutCount = mainKeyboard.layout_names.length
|
||||
hyprlandLayoutCount = mainKeyboard.layout_names.length;
|
||||
} else {
|
||||
hyprlandLayoutCount = 0
|
||||
hyprlandLayoutCount = 0;
|
||||
}
|
||||
} catch (e) {
|
||||
hyprlandCurrentLayout = ""
|
||||
hyprlandLayoutCount = 0
|
||||
hyprlandCurrentLayout = "";
|
||||
hyprlandLayoutCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +123,7 @@ Item {
|
||||
|
||||
function onRawEvent(event) {
|
||||
if (event.name === "activelayout")
|
||||
updateHyprlandLayout()
|
||||
updateHyprlandLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +131,7 @@ Item {
|
||||
target: GreetdMemory
|
||||
enabled: isPrimaryScreen
|
||||
function onLastSuccessfulUserChanged() {
|
||||
applyLastSuccessfulUser()
|
||||
applyLastSuccessfulUser();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +139,7 @@ Item {
|
||||
target: GreeterState
|
||||
function onUsernameChanged() {
|
||||
if (GreeterState.username) {
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username)
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,10 +148,10 @@ Item {
|
||||
anchors.fill: parent
|
||||
screenName: root.screenName
|
||||
visible: {
|
||||
var _ = SessionData.perMonitorWallpaper
|
||||
var __ = SessionData.monitorWallpapers
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
return !currentWallpaper || currentWallpaper === "" || (currentWallpaper && currentWallpaper.startsWith("#"))
|
||||
var _ = SessionData.perMonitorWallpaper;
|
||||
var __ = SessionData.monitorWallpapers;
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
return !currentWallpaper || currentWallpaper === "" || (currentWallpaper && currentWallpaper.startsWith("#"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,10 +160,10 @@ Item {
|
||||
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
var _ = SessionData.perMonitorWallpaper
|
||||
var __ = SessionData.monitorWallpapers
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""
|
||||
var _ = SessionData.perMonitorWallpaper;
|
||||
var __ = SessionData.monitorWallpapers;
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : "";
|
||||
}
|
||||
fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode)
|
||||
smooth: true
|
||||
@@ -213,10 +204,12 @@ Item {
|
||||
color: "transparent"
|
||||
|
||||
Item {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -100
|
||||
id: clockContainer
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.verticalCenter
|
||||
anchors.bottomMargin: 60
|
||||
width: parent.width
|
||||
height: 140
|
||||
height: clockText.implicitHeight
|
||||
|
||||
Row {
|
||||
id: clockText
|
||||
@@ -225,10 +218,8 @@ Item {
|
||||
spacing: 0
|
||||
|
||||
property string fullTimeStr: {
|
||||
const format = GreetdSettings.use24HourClock
|
||||
? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm")
|
||||
: (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP")
|
||||
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
|
||||
const format = GreetdSettings.use24HourClock ? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm") : (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP");
|
||||
return systemClock.date.toLocaleTimeString(Qt.locale(), format);
|
||||
}
|
||||
property var timeParts: fullTimeStr.split(':')
|
||||
property string hours: timeParts[0] || ""
|
||||
@@ -236,8 +227,8 @@ Item {
|
||||
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
|
||||
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
|
||||
property string ampm: {
|
||||
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i)
|
||||
return match ? match[0].trim() : ""
|
||||
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i);
|
||||
return match ? match[0].trim() : "";
|
||||
}
|
||||
property bool hasSeconds: timeParts.length > 2
|
||||
|
||||
@@ -332,13 +323,15 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -10
|
||||
id: dateText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: clockContainer.bottom
|
||||
anchors.topMargin: 4
|
||||
text: {
|
||||
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
|
||||
color: "white"
|
||||
@@ -346,8 +339,9 @@ Item {
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: 80
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: dateText.bottom
|
||||
anchors.topMargin: Theme.spacingL
|
||||
width: 380
|
||||
height: 140
|
||||
|
||||
@@ -364,14 +358,14 @@ Item {
|
||||
Layout.preferredHeight: 60
|
||||
imageSource: {
|
||||
if (PortalService.profileImage === "") {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
if (PortalService.profileImage.startsWith("/")) {
|
||||
return "file://" + PortalService.profileImage
|
||||
return "file://" + PortalService.profileImage;
|
||||
}
|
||||
|
||||
return PortalService.profileImage
|
||||
return PortalService.profileImage;
|
||||
}
|
||||
fallbackIcon: "person"
|
||||
}
|
||||
@@ -405,57 +399,58 @@ Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
|
||||
anchors.rightMargin: {
|
||||
let margin = Theme.spacingM
|
||||
let margin = Theme.spacingM;
|
||||
if (GreeterState.showPasswordInput && revealButton.visible) {
|
||||
margin += revealButton.width
|
||||
margin += revealButton.width;
|
||||
}
|
||||
if (virtualKeyboardButton.visible) {
|
||||
margin += virtualKeyboardButton.width
|
||||
margin += virtualKeyboardButton.width;
|
||||
}
|
||||
if (enterButton.visible) {
|
||||
margin += enterButton.width + 2
|
||||
margin += enterButton.width + 2;
|
||||
}
|
||||
return margin
|
||||
return margin;
|
||||
}
|
||||
opacity: 0
|
||||
focus: true
|
||||
echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal
|
||||
onTextChanged: {
|
||||
if (syncingFromState) return
|
||||
if (syncingFromState)
|
||||
return;
|
||||
if (GreeterState.showPasswordInput) {
|
||||
GreeterState.passwordBuffer = text
|
||||
GreeterState.passwordBuffer = text;
|
||||
} else {
|
||||
GreeterState.usernameInput = text
|
||||
GreeterState.usernameInput = text;
|
||||
}
|
||||
}
|
||||
onAccepted: {
|
||||
if (GreeterState.showPasswordInput) {
|
||||
if (Greetd.state === GreetdState.Inactive && GreeterState.username) {
|
||||
Greetd.createSession(GreeterState.username)
|
||||
Greetd.createSession(GreeterState.username);
|
||||
}
|
||||
} else {
|
||||
if (text.trim()) {
|
||||
GreeterState.username = text.trim()
|
||||
GreeterState.showPasswordInput = true
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username)
|
||||
GreeterState.passwordBuffer = ""
|
||||
syncingFromState = true
|
||||
text = ""
|
||||
syncingFromState = false
|
||||
GreeterState.username = text.trim();
|
||||
GreeterState.showPasswordInput = true;
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||
GreeterState.passwordBuffer = "";
|
||||
syncingFromState = true;
|
||||
text = "";
|
||||
syncingFromState = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
syncingFromState = true
|
||||
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput
|
||||
syncingFromState = false
|
||||
syncingFromState = true;
|
||||
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput;
|
||||
syncingFromState = false;
|
||||
if (isPrimaryScreen && !powerMenu.isVisible)
|
||||
forceActiveFocus()
|
||||
forceActiveFocus();
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible && isPrimaryScreen && !powerMenu.isVisible)
|
||||
forceActiveFocus()
|
||||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,15 +470,15 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (GreeterState.unlocking) {
|
||||
return "Logging in..."
|
||||
return "Logging in...";
|
||||
}
|
||||
if (Greetd.state !== GreetdState.Inactive) {
|
||||
return "Authenticating..."
|
||||
return "Authenticating...";
|
||||
}
|
||||
if (GreeterState.showPasswordInput) {
|
||||
return "Password..."
|
||||
return "Password...";
|
||||
}
|
||||
return "Username..."
|
||||
return "Username...";
|
||||
}
|
||||
color: GreeterState.unlocking ? Theme.primary : (Greetd.state !== GreetdState.Inactive ? Theme.primary : Theme.outline)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -513,11 +508,11 @@ Item {
|
||||
text: {
|
||||
if (GreeterState.showPasswordInput) {
|
||||
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
|
||||
font.pixelSize: (GreeterState.showPasswordInput && !parent.showPassword) ? Theme.fontSizeLarge : Theme.fontSizeMedium
|
||||
@@ -558,9 +553,9 @@ Item {
|
||||
enabled: visible
|
||||
onClicked: {
|
||||
if (keyboard_controller.isKeyboardActive) {
|
||||
keyboard_controller.hide()
|
||||
keyboard_controller.hide();
|
||||
} else {
|
||||
keyboard_controller.show()
|
||||
keyboard_controller.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -578,15 +573,15 @@ Item {
|
||||
onClicked: {
|
||||
if (GreeterState.showPasswordInput) {
|
||||
if (GreeterState.username) {
|
||||
Greetd.createSession(GreeterState.username)
|
||||
Greetd.createSession(GreeterState.username);
|
||||
}
|
||||
} else {
|
||||
if (inputField.text.trim()) {
|
||||
GreeterState.username = inputField.text.trim()
|
||||
GreeterState.showPasswordInput = true
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username)
|
||||
GreeterState.passwordBuffer = ""
|
||||
inputField.text = ""
|
||||
GreeterState.username = inputField.text.trim();
|
||||
GreeterState.showPasswordInput = true;
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -615,10 +610,10 @@ Item {
|
||||
Layout.bottomMargin: -Theme.spacingS
|
||||
text: {
|
||||
if (GreeterState.pamState === "error")
|
||||
return "Authentication error - try again"
|
||||
return "Authentication error - try again";
|
||||
if (GreeterState.pamState === "fail")
|
||||
return "Incorrect password"
|
||||
return ""
|
||||
return "Incorrect password";
|
||||
return "";
|
||||
}
|
||||
color: Theme.error
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -675,9 +670,9 @@ Item {
|
||||
cornerRadius: parent.radius
|
||||
enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput
|
||||
onClicked: {
|
||||
GreeterState.reset()
|
||||
inputField.text = ""
|
||||
PortalService.profileImage = ""
|
||||
GreeterState.reset();
|
||||
inputField.text = "";
|
||||
PortalService.profileImage = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -696,11 +691,11 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: {
|
||||
if (CompositorService.isNiri) {
|
||||
return NiriService.keyboardLayoutNames.length > 1
|
||||
return NiriService.keyboardLayoutNames.length > 1;
|
||||
} else if (CompositorService.isHyprland) {
|
||||
return hyprlandLayoutCount > 1
|
||||
return hyprlandLayoutCount > 1;
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -726,17 +721,18 @@ Item {
|
||||
StyledText {
|
||||
text: {
|
||||
if (CompositorService.isNiri) {
|
||||
const layout = NiriService.getCurrentKeyboardLayoutName()
|
||||
if (!layout) return ""
|
||||
const parts = layout.split(" ")
|
||||
const layout = NiriService.getCurrentKeyboardLayoutName();
|
||||
if (!layout)
|
||||
return "";
|
||||
const parts = layout.split(" ");
|
||||
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) {
|
||||
return hyprlandCurrentLayout
|
||||
return hyprlandCurrentLayout;
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Light
|
||||
@@ -753,15 +749,10 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (CompositorService.isNiri) {
|
||||
NiriService.cycleKeyboardLayout()
|
||||
NiriService.cycleKeyboardLayout();
|
||||
} else if (CompositorService.isHyprland) {
|
||||
Quickshell.execDetached([
|
||||
"hyprctl",
|
||||
"switchxkblayout",
|
||||
hyprlandKeyboard,
|
||||
"next"
|
||||
])
|
||||
updateHyprlandLayout()
|
||||
Quickshell.execDetached(["hyprctl", "switchxkblayout", hyprlandKeyboard, "next"]);
|
||||
updateHyprlandLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -773,9 +764,8 @@ Item {
|
||||
color: Qt.rgba(255, 255, 255, 0.2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: {
|
||||
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) ||
|
||||
(CompositorService.isHyprland && hyprlandLayoutCount > 1)
|
||||
return keyboardVisible && GreetdSettings.weatherEnabled && WeatherService.weather.available
|
||||
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) || (CompositorService.isHyprland && hyprlandLayoutCount > 1);
|
||||
return keyboardVisible && GreetdSettings.weatherEnabled && WeatherService.weather.available;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,15 +822,15 @@ Item {
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!AudioService.sink?.audio) {
|
||||
return "volume_up"
|
||||
return "volume_up";
|
||||
}
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
||||
return "volume_off"
|
||||
return "volume_off";
|
||||
}
|
||||
if (AudioService.sink.audio.volume * 100 < 33) {
|
||||
return "volume_down"
|
||||
return "volume_down";
|
||||
}
|
||||
return "volume_up"
|
||||
return "volume_up";
|
||||
}
|
||||
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"
|
||||
@@ -866,95 +856,95 @@ Item {
|
||||
name: {
|
||||
if (BatteryService.isCharging) {
|
||||
if (BatteryService.batteryLevel >= 90) {
|
||||
return "battery_charging_full"
|
||||
return "battery_charging_full";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 80) {
|
||||
return "battery_charging_90"
|
||||
return "battery_charging_90";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 60) {
|
||||
return "battery_charging_80"
|
||||
return "battery_charging_80";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 50) {
|
||||
return "battery_charging_60"
|
||||
return "battery_charging_60";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 30) {
|
||||
return "battery_charging_50"
|
||||
return "battery_charging_50";
|
||||
}
|
||||
|
||||
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.batteryLevel >= 90) {
|
||||
return "battery_charging_full"
|
||||
return "battery_charging_full";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 80) {
|
||||
return "battery_charging_90"
|
||||
return "battery_charging_90";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 60) {
|
||||
return "battery_charging_80"
|
||||
return "battery_charging_80";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 50) {
|
||||
return "battery_charging_60"
|
||||
return "battery_charging_60";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 30) {
|
||||
return "battery_charging_50"
|
||||
return "battery_charging_50";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 20) {
|
||||
return "battery_charging_30"
|
||||
return "battery_charging_30";
|
||||
}
|
||||
|
||||
return "battery_charging_20"
|
||||
return "battery_charging_20";
|
||||
}
|
||||
if (BatteryService.batteryLevel >= 95) {
|
||||
return "battery_full"
|
||||
return "battery_full";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 85) {
|
||||
return "battery_6_bar"
|
||||
return "battery_6_bar";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 70) {
|
||||
return "battery_5_bar"
|
||||
return "battery_5_bar";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 55) {
|
||||
return "battery_4_bar"
|
||||
return "battery_4_bar";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 40) {
|
||||
return "battery_3_bar"
|
||||
return "battery_3_bar";
|
||||
}
|
||||
|
||||
if (BatteryService.batteryLevel >= 25) {
|
||||
return "battery_2_bar"
|
||||
return "battery_2_bar";
|
||||
}
|
||||
|
||||
return "battery_1_bar"
|
||||
return "battery_1_bar";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
return Theme.error;
|
||||
}
|
||||
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||
return Theme.primary
|
||||
return Theme.primary;
|
||||
}
|
||||
|
||||
return "white"
|
||||
return "white";
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -1007,14 +997,14 @@ Item {
|
||||
}
|
||||
|
||||
property real longestSessionWidth: {
|
||||
let maxWidth = 0
|
||||
let maxWidth = 0;
|
||||
for (var i = 0; i < sessionMetricsRepeater.count; i++) {
|
||||
const item = sessionMetricsRepeater.itemAt(i)
|
||||
const item = sessionMetricsRepeater.itemAt(i);
|
||||
if (item && item.width > maxWidth) {
|
||||
maxWidth = item.width
|
||||
maxWidth = item.width;
|
||||
}
|
||||
}
|
||||
return maxWidth
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
@@ -1038,52 +1028,42 @@ Item {
|
||||
openUpwards: true
|
||||
alignPopupRight: true
|
||||
onValueChanged: value => {
|
||||
const idx = GreeterState.sessionList.indexOf(value)
|
||||
if (idx >= 0) {
|
||||
GreeterState.currentSessionIndex = idx
|
||||
GreeterState.selectedSession = GreeterState.sessionExecs[idx]
|
||||
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[idx])
|
||||
}
|
||||
}
|
||||
const idx = GreeterState.sessionList.indexOf(value);
|
||||
if (idx >= 0) {
|
||||
GreeterState.currentSessionIndex = idx;
|
||||
GreeterState.selectedSession = GreeterState.sessionExecs[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 int pendingParsers: 0
|
||||
|
||||
|
||||
function finalizeSessionSelection() {
|
||||
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) {
|
||||
for (var i = 0; i < GreeterState.sessionPaths.length; i++) {
|
||||
if (GreeterState.sessionPaths[i] === savedSession) {
|
||||
GreeterState.currentSessionIndex = i
|
||||
foundSaved = true
|
||||
break
|
||||
GreeterState.currentSessionIndex = i;
|
||||
foundSaved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -1091,48 +1071,43 @@ Item {
|
||||
property string homeDir: Quickshell.env("HOME") || ""
|
||||
property string xdgDirs: xdgDataDirs || ""
|
||||
command: {
|
||||
var paths = [
|
||||
"/usr/share/wayland-sessions",
|
||||
"/usr/share/xsessions",
|
||||
"/usr/local/share/wayland-sessions",
|
||||
"/usr/local/share/xsessions"
|
||||
]
|
||||
var paths = ["/usr/share/wayland-sessions", "/usr/share/xsessions", "/usr/local/share/wayland-sessions", "/usr/local/share/xsessions"];
|
||||
if (homeDir) {
|
||||
paths.push(homeDir + "/.local/share/wayland-sessions")
|
||||
paths.push(homeDir + "/.local/share/xsessions")
|
||||
paths.push(homeDir + "/.local/share/wayland-sessions");
|
||||
paths.push(homeDir + "/.local/share/xsessions");
|
||||
}
|
||||
// Add XDG_DATA_DIRS paths
|
||||
if (xdgDirs) {
|
||||
xdgDirs.split(":").forEach(function(dir) {
|
||||
xdgDirs.split(":").forEach(function (dir) {
|
||||
if (dir) {
|
||||
paths.push(dir + "/wayland-sessions")
|
||||
paths.push(dir + "/xsessions")
|
||||
paths.push(dir + "/wayland-sessions");
|
||||
paths.push(dir + "/xsessions");
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
// 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
|
||||
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"
|
||||
return ["sh", "-c", findCmd]
|
||||
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";
|
||||
return ["sh", "-c", findCmd];
|
||||
}
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
if (data.trim()) {
|
||||
root.pendingParsers++
|
||||
parseDesktopFile(data.trim())
|
||||
}
|
||||
}
|
||||
if (data.trim()) {
|
||||
root.pendingParsers++;
|
||||
parseDesktopFile(data.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseDesktopFile(path) {
|
||||
const parser = desktopParser.createObject(null, {
|
||||
"desktopPath": path
|
||||
})
|
||||
"desktopPath": path
|
||||
});
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -1144,42 +1119,41 @@ Item {
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const lines = text.split("\n")
|
||||
let name = ""
|
||||
let exec = ""
|
||||
const lines = text.split("\n");
|
||||
let name = "";
|
||||
let exec = "";
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("Name=")) {
|
||||
name = line.substring(5).trim()
|
||||
name = line.substring(5).trim();
|
||||
} else if (line.startsWith("Exec=")) {
|
||||
exec = line.substring(5).trim()
|
||||
exec = line.substring(5).trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (name && exec) {
|
||||
if (!GreeterState.sessionList.includes(name)) {
|
||||
let newList = GreeterState.sessionList.slice()
|
||||
let newExecs = GreeterState.sessionExecs.slice()
|
||||
let newPaths = GreeterState.sessionPaths.slice()
|
||||
newList.push(name)
|
||||
newExecs.push(exec)
|
||||
newPaths.push(desktopPath)
|
||||
GreeterState.sessionList = newList
|
||||
GreeterState.sessionExecs = newExecs
|
||||
GreeterState.sessionPaths = newPaths
|
||||
root.sessionCount = GreeterState.sessionList.length
|
||||
let newList = GreeterState.sessionList.slice();
|
||||
let newExecs = GreeterState.sessionExecs.slice();
|
||||
let newPaths = GreeterState.sessionPaths.slice();
|
||||
newList.push(name);
|
||||
newExecs.push(exec);
|
||||
newPaths.push(desktopPath);
|
||||
GreeterState.sessionList = newList;
|
||||
GreeterState.sessionExecs = newExecs;
|
||||
GreeterState.sessionPaths = newPaths;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: code => {
|
||||
root.pendingParsers--
|
||||
if (root.pendingParsers === 0) {
|
||||
Qt.callLater(root.finalizeSessionSelection)
|
||||
}
|
||||
destroy()
|
||||
}
|
||||
root.pendingParsers--;
|
||||
if (root.pendingParsers === 0) {
|
||||
Qt.callLater(root.finalizeSessionSelection);
|
||||
}
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1189,34 +1163,34 @@ Item {
|
||||
|
||||
function onAuthMessage(message, error, responseRequired, echoResponse) {
|
||||
if (responseRequired) {
|
||||
Greetd.respond(GreeterState.passwordBuffer)
|
||||
GreeterState.passwordBuffer = ""
|
||||
inputField.text = ""
|
||||
Greetd.respond(GreeterState.passwordBuffer);
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
} else if (!error) {
|
||||
Greetd.respond("")
|
||||
Greetd.respond("");
|
||||
}
|
||||
}
|
||||
|
||||
function onReadyToLaunch() {
|
||||
GreeterState.unlocking = true
|
||||
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
|
||||
GreeterState.unlocking = true;
|
||||
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex];
|
||||
if (sessionCmd) {
|
||||
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex])
|
||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username)
|
||||
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"])
|
||||
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex]);
|
||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username);
|
||||
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"]);
|
||||
}
|
||||
}
|
||||
|
||||
function onAuthFailure(message) {
|
||||
GreeterState.pamState = "fail"
|
||||
GreeterState.passwordBuffer = ""
|
||||
inputField.text = ""
|
||||
placeholderDelay.restart()
|
||||
GreeterState.pamState = "fail";
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
placeholderDelay.restart();
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
GreeterState.pamState = "error"
|
||||
placeholderDelay.restart()
|
||||
GreeterState.pamState = "error";
|
||||
placeholderDelay.restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1231,7 +1205,7 @@ Item {
|
||||
showLogout: false
|
||||
onClosed: {
|
||||
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
|
||||
Qt.callLater(() => inputField.forceActiveFocus())
|
||||
Qt.callLater(() => inputField.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,10 +212,12 @@ Item {
|
||||
color: "transparent"
|
||||
|
||||
Item {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -100
|
||||
id: clockContainer
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.verticalCenter
|
||||
anchors.bottomMargin: 60
|
||||
width: parent.width
|
||||
height: 140
|
||||
height: clockText.implicitHeight
|
||||
visible: SettingsData.lockScreenShowTime
|
||||
|
||||
Row {
|
||||
@@ -330,8 +332,10 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -25
|
||||
id: dateText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: clockContainer.bottom
|
||||
anchors.topMargin: 4
|
||||
visible: SettingsData.lockScreenShowDate
|
||||
text: {
|
||||
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
|
||||
@@ -346,8 +350,9 @@ Item {
|
||||
|
||||
ColumnLayout {
|
||||
id: passwordLayout
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: 50
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: dateText.visible ? dateText.bottom : clockContainer.bottom
|
||||
anchors.topMargin: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
width: 380
|
||||
|
||||
@@ -384,7 +389,6 @@ Item {
|
||||
border.width: passwordField.activeFocus ? 2 : 1
|
||||
visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0
|
||||
|
||||
|
||||
Item {
|
||||
id: lockIconContainer
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -9,6 +9,7 @@ DankListView {
|
||||
property var keyboardController: null
|
||||
property bool keyboardActive: false
|
||||
property bool autoScrollDisabled: false
|
||||
property bool isAnimatingExpansion: false
|
||||
property alias count: listView.count
|
||||
property alias listContentHeight: listView.contentHeight
|
||||
|
||||
@@ -29,8 +30,19 @@ DankListView {
|
||||
Timer {
|
||||
id: positionPreservationTimer
|
||||
interval: 200
|
||||
running: keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled
|
||||
running: keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled && !isAnimatingExpansion
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled && !isAnimatingExpansion) {
|
||||
keyboardController.ensureVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: expansionEnsureVisibleTimer
|
||||
interval: Theme.mediumDuration + 50
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
|
||||
keyboardController.ensureVisible();
|
||||
@@ -68,14 +80,7 @@ DankListView {
|
||||
|
||||
width: ListView.view.width
|
||||
height: isDismissing ? 0 : notificationCard.height
|
||||
clip: true
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
clip: isDismissing
|
||||
|
||||
NotificationCard {
|
||||
id: notificationCard
|
||||
@@ -84,6 +89,23 @@ DankListView {
|
||||
notificationGroup: modelData
|
||||
keyboardNavigationActive: listView.keyboardActive
|
||||
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: {
|
||||
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
|
||||
@@ -173,23 +195,15 @@ DankListView {
|
||||
}
|
||||
|
||||
function onExpandedGroupsChanged() {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive) {
|
||||
Qt.callLater(() => {
|
||||
if (!autoScrollDisabled) {
|
||||
keyboardController.ensureVisible();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!keyboardController || !keyboardController.keyboardNavigationActive)
|
||||
return;
|
||||
expansionEnsureVisibleTimer.restart();
|
||||
}
|
||||
|
||||
function onExpandedMessagesChanged() {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive) {
|
||||
Qt.callLater(() => {
|
||||
if (!autoScrollDisabled) {
|
||||
keyboardController.ensureVisible();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!keyboardController || !keyboardController.keyboardNavigationActive)
|
||||
return;
|
||||
expansionEnsureVisibleTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Services.Notifications
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -13,9 +10,9 @@ Rectangle {
|
||||
|
||||
property var notificationGroup
|
||||
property bool expanded: (NotificationService.expandedGroups[notificationGroup && notificationGroup.key] || false)
|
||||
property bool descriptionExpanded: (NotificationService.expandedMessages[(notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification
|
||||
&& notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""] || false)
|
||||
property bool descriptionExpanded: (NotificationService.expandedMessages[(notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""] || false)
|
||||
property bool userInitiatedExpansion: false
|
||||
property bool isAnimating: false
|
||||
|
||||
property bool isGroupSelected: false
|
||||
property int selectedNotificationIndex: -1
|
||||
@@ -24,13 +21,13 @@ Rectangle {
|
||||
width: parent ? parent.width : 400
|
||||
height: {
|
||||
if (expanded) {
|
||||
return expandedContent.height + 28
|
||||
return expandedContent.height + 28;
|
||||
}
|
||||
const baseHeight = 116
|
||||
const baseHeight = 116;
|
||||
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
|
||||
|
||||
@@ -43,36 +40,36 @@ Rectangle {
|
||||
|
||||
color: {
|
||||
if (isGroupSelected && keyboardNavigationActive) {
|
||||
return Theme.primaryPressed
|
||||
return Theme.primaryPressed;
|
||||
}
|
||||
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: {
|
||||
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) {
|
||||
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) {
|
||||
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: {
|
||||
if (isGroupSelected && keyboardNavigationActive) {
|
||||
return 1.5
|
||||
return 1.5;
|
||||
}
|
||||
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
||||
return 1
|
||||
return 1;
|
||||
}
|
||||
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
|
||||
return 2
|
||||
return 2;
|
||||
}
|
||||
return 1
|
||||
return 1;
|
||||
}
|
||||
clip: true
|
||||
|
||||
@@ -121,21 +118,21 @@ Rectangle {
|
||||
|
||||
imageSource: {
|
||||
if (hasNotificationImage)
|
||||
return notificationGroup.latestNotification.cleanImage
|
||||
return notificationGroup.latestNotification.cleanImage;
|
||||
if (notificationGroup?.latestNotification?.appIcon) {
|
||||
const appIcon = notificationGroup.latestNotification.appIcon
|
||||
const appIcon = notificationGroup.latestNotification.appIcon;
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon
|
||||
return Quickshell.iconPath(appIcon, true)
|
||||
return appIcon;
|
||||
return Quickshell.iconPath(appIcon, true);
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
fallbackIcon: ""
|
||||
fallbackText: {
|
||||
const appName = notificationGroup?.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
const appName = notificationGroup?.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -195,9 +192,9 @@ Rectangle {
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: {
|
||||
const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || ""
|
||||
const appName = (notificationGroup && notificationGroup.appName) || ""
|
||||
return timeStr.length > 0 ? `${appName} • ${timeStr}` : appName
|
||||
const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || "";
|
||||
const appName = (notificationGroup && notificationGroup.appName) || "";
|
||||
return timeStr.length > 0 ? `${appName} • ${timeStr}` : appName;
|
||||
}
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -238,24 +235,23 @@ Rectangle {
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (parent.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onClicked: mouse => {
|
||||
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
|
||||
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification
|
||||
&& notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""
|
||||
NotificationService.toggleMessageExpansion(messageId)
|
||||
}
|
||||
}
|
||||
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
|
||||
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "";
|
||||
NotificationService.toggleMessageExpansion(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
propagateComposedEvents: true
|
||||
onPressed: mouse => {
|
||||
if (parent.hoveredLink) {
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
if (parent.hoveredLink) {
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
onReleased: mouse => {
|
||||
if (parent.hoveredLink) {
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
if (parent.hoveredLink) {
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,15 +331,15 @@ Rectangle {
|
||||
|
||||
width: parent.width
|
||||
height: {
|
||||
const baseHeight = 120
|
||||
const baseHeight = 120;
|
||||
if (messageExpanded) {
|
||||
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2
|
||||
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2;
|
||||
if (bodyText.implicitHeight > twoLineHeight + 2) {
|
||||
const extraHeight = bodyText.implicitHeight - twoLineHeight
|
||||
return baseHeight + extraHeight
|
||||
const extraHeight = bodyText.implicitHeight - twoLineHeight;
|
||||
return baseHeight + extraHeight;
|
||||
}
|
||||
}
|
||||
return baseHeight
|
||||
return baseHeight;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
@@ -379,23 +375,23 @@ Rectangle {
|
||||
|
||||
imageSource: {
|
||||
if (hasNotificationImage)
|
||||
return modelData.cleanImage
|
||||
return modelData.cleanImage;
|
||||
|
||||
if (modelData?.appIcon) {
|
||||
const appIcon = modelData.appIcon
|
||||
const appIcon = modelData.appIcon;
|
||||
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: ""
|
||||
|
||||
fallbackText: {
|
||||
const appName = modelData?.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
const appName = modelData?.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,22 +452,22 @@ Rectangle {
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (bodyText.hasMoreText || messageExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onClicked: mouse => {
|
||||
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
|
||||
NotificationService.toggleMessageExpansion(modelData?.notification?.id || "")
|
||||
}
|
||||
}
|
||||
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
|
||||
NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
|
||||
}
|
||||
}
|
||||
|
||||
propagateComposedEvents: true
|
||||
onPressed: mouse => {
|
||||
if (parent.hoveredLink) {
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
if (parent.hoveredLink) {
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
onReleased: mouse => {
|
||||
if (parent.hoveredLink) {
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
if (parent.hoveredLink) {
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,11 +498,11 @@ Rectangle {
|
||||
StyledText {
|
||||
id: actionText
|
||||
text: {
|
||||
const baseText = modelData.text || "View"
|
||||
const baseText = modelData.text || "View";
|
||||
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0)) {
|
||||
return `${baseText} (${index + 1})`
|
||||
return `${baseText} (${index + 1})`;
|
||||
}
|
||||
return baseText
|
||||
return baseText;
|
||||
}
|
||||
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -523,7 +519,7 @@ Rectangle {
|
||||
onExited: parent.isHovered = false
|
||||
onClicked: {
|
||||
if (modelData && modelData.invoke) {
|
||||
modelData.invoke()
|
||||
modelData.invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -587,11 +583,11 @@ Rectangle {
|
||||
StyledText {
|
||||
id: actionText
|
||||
text: {
|
||||
const baseText = modelData.text || "View"
|
||||
const baseText = modelData.text || "View";
|
||||
if (keyboardNavigationActive && isGroupSelected) {
|
||||
return `${baseText} (${index + 1})`
|
||||
return `${baseText} (${index + 1})`;
|
||||
}
|
||||
return baseText
|
||||
return baseText;
|
||||
}
|
||||
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -608,7 +604,7 @@ Rectangle {
|
||||
onExited: parent.isHovered = false
|
||||
onClicked: {
|
||||
if (modelData && modelData.invoke) {
|
||||
modelData.invoke()
|
||||
modelData.invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -654,8 +650,8 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
|
||||
onClicked: {
|
||||
root.userInitiatedExpansion = true
|
||||
NotificationService.toggleGroupExpansion(notificationGroup?.key || "")
|
||||
root.userInitiatedExpansion = true;
|
||||
NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
|
||||
}
|
||||
z: -1
|
||||
}
|
||||
@@ -677,8 +673,8 @@ Rectangle {
|
||||
iconSize: 18
|
||||
buttonSize: 28
|
||||
onClicked: {
|
||||
root.userInitiatedExpansion = true
|
||||
NotificationService.toggleGroupExpansion(notificationGroup?.key || "")
|
||||
root.userInitiatedExpansion = true;
|
||||
NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,7 +693,14 @@ Rectangle {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
onFinished: root.userInitiatedExpansion = false
|
||||
onRunningChanged: {
|
||||
if (running) {
|
||||
root.isAnimating = true;
|
||||
} else {
|
||||
root.isAnimating = false;
|
||||
root.userInitiatedExpansion = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
QtObject {
|
||||
@@ -23,530 +22,518 @@ QtObject {
|
||||
property bool isRebuilding: false
|
||||
|
||||
function rebuildFlatNavigation() {
|
||||
isRebuilding = true
|
||||
isRebuilding = true;
|
||||
|
||||
const nav = []
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const nav = [];
|
||||
const groups = NotificationService.groupedNotifications;
|
||||
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
const group = groups[i]
|
||||
const isExpanded = NotificationService.expandedGroups[group.key] || false
|
||||
const group = groups[i];
|
||||
const isExpanded = NotificationService.expandedGroups[group.key] || false;
|
||||
|
||||
nav.push({
|
||||
"type": "group",
|
||||
"groupIndex": i,
|
||||
"notificationIndex": -1,
|
||||
"groupKey": group.key,
|
||||
"notificationId": ""
|
||||
})
|
||||
"type": "group",
|
||||
"groupIndex": i,
|
||||
"notificationIndex": -1,
|
||||
"groupKey": group.key,
|
||||
"notificationId": ""
|
||||
});
|
||||
|
||||
if (isExpanded) {
|
||||
const notifications = group.notifications || []
|
||||
const maxNotifications = Math.min(notifications.length, 10)
|
||||
const notifications = group.notifications || [];
|
||||
const maxNotifications = Math.min(notifications.length, 10);
|
||||
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({
|
||||
"type": "notification",
|
||||
"groupIndex": i,
|
||||
"notificationIndex": j,
|
||||
"groupKey": group.key,
|
||||
"notificationId": notifId
|
||||
})
|
||||
"type": "notification",
|
||||
"groupIndex": i,
|
||||
"notificationIndex": j,
|
||||
"groupKey": group.key,
|
||||
"notificationId": notifId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flatNavigation = nav
|
||||
updateSelectedIndexFromId()
|
||||
isRebuilding = false
|
||||
flatNavigation = nav;
|
||||
updateSelectedIndexFromId();
|
||||
isRebuilding = false;
|
||||
}
|
||||
|
||||
function updateSelectedIndexFromId() {
|
||||
if (!keyboardNavigationActive) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
selectedFlatIndex = i
|
||||
selectionVersion++ // Trigger UI update
|
||||
return
|
||||
selectedFlatIndex = i;
|
||||
selectionVersion++; // Trigger UI update
|
||||
return;
|
||||
} else if (selectedItemType === "notification" && item.type === "notification" && String(item.notificationId) === String(selectedNotificationId)) {
|
||||
selectedFlatIndex = i
|
||||
selectionVersion++ // Trigger UI update
|
||||
return
|
||||
selectedFlatIndex = i;
|
||||
selectionVersion++; // Trigger UI update
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, try to find the same group but select the group header instead
|
||||
if (selectedItemType === "notification") {
|
||||
for (var j = 0; j < flatNavigation.length; j++) {
|
||||
const groupItem = flatNavigation[j]
|
||||
const groupItem = flatNavigation[j];
|
||||
if (groupItem.type === "group" && groupItem.groupKey === selectedGroupKey) {
|
||||
selectedFlatIndex = j
|
||||
selectedItemType = "group"
|
||||
selectedNotificationId = ""
|
||||
selectionVersion++ // Trigger UI update
|
||||
return
|
||||
selectedFlatIndex = j;
|
||||
selectedItemType = "group";
|
||||
selectedNotificationId = "";
|
||||
selectionVersion++; // Trigger UI update
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, clamp to valid range and update
|
||||
if (flatNavigation.length > 0) {
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1)
|
||||
selectedFlatIndex = Math.max(selectedFlatIndex, 0)
|
||||
updateSelectedIdFromIndex()
|
||||
selectionVersion++ // Trigger UI update
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1);
|
||||
selectedFlatIndex = Math.max(selectedFlatIndex, 0);
|
||||
updateSelectedIdFromIndex();
|
||||
selectionVersion++; // Trigger UI update
|
||||
}
|
||||
}
|
||||
|
||||
function updateSelectedIdFromIndex() {
|
||||
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatNavigation.length) {
|
||||
const item = flatNavigation[selectedFlatIndex]
|
||||
selectedItemType = item.type
|
||||
selectedGroupKey = item.groupKey
|
||||
selectedNotificationId = item.notificationId
|
||||
const item = flatNavigation[selectedFlatIndex];
|
||||
selectedItemType = item.type;
|
||||
selectedGroupKey = item.groupKey;
|
||||
selectedNotificationId = item.notificationId;
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
selectedFlatIndex = 0
|
||||
keyboardNavigationActive = false
|
||||
showKeyboardHints = false
|
||||
selectedFlatIndex = 0;
|
||||
keyboardNavigationActive = false;
|
||||
showKeyboardHints = false;
|
||||
// Reset keyboardActive when modal is reset
|
||||
if (listView) {
|
||||
listView.keyboardActive = false
|
||||
listView.keyboardActive = false;
|
||||
}
|
||||
rebuildFlatNavigation()
|
||||
rebuildFlatNavigation();
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
keyboardNavigationActive = true
|
||||
keyboardNavigationActive = true;
|
||||
if (flatNavigation.length === 0)
|
||||
return
|
||||
return;
|
||||
|
||||
// Re-enable auto-scrolling when arrow keys are used
|
||||
if (listView && listView.enableAutoScroll) {
|
||||
listView.enableAutoScroll()
|
||||
listView.enableAutoScroll();
|
||||
}
|
||||
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1)
|
||||
updateSelectedIdFromIndex()
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1);
|
||||
updateSelectedIdFromIndex();
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
}
|
||||
|
||||
function selectNextWrapping() {
|
||||
keyboardNavigationActive = true
|
||||
keyboardNavigationActive = true;
|
||||
if (flatNavigation.length === 0)
|
||||
return
|
||||
return;
|
||||
|
||||
// Re-enable auto-scrolling when arrow keys are used
|
||||
if (listView && listView.enableAutoScroll) {
|
||||
listView.enableAutoScroll()
|
||||
listView.enableAutoScroll();
|
||||
}
|
||||
|
||||
selectedFlatIndex = (selectedFlatIndex + 1) % flatNavigation.length
|
||||
updateSelectedIdFromIndex()
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
selectedFlatIndex = (selectedFlatIndex + 1) % flatNavigation.length;
|
||||
updateSelectedIdFromIndex();
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
keyboardNavigationActive = true
|
||||
keyboardNavigationActive = true;
|
||||
if (flatNavigation.length === 0)
|
||||
return
|
||||
return;
|
||||
|
||||
// Re-enable auto-scrolling when arrow keys are used
|
||||
if (listView && listView.enableAutoScroll) {
|
||||
listView.enableAutoScroll()
|
||||
listView.enableAutoScroll();
|
||||
}
|
||||
|
||||
selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0)
|
||||
updateSelectedIdFromIndex()
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0);
|
||||
updateSelectedIdFromIndex();
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
}
|
||||
|
||||
function toggleGroupExpanded() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
return;
|
||||
const currentItem = flatNavigation[selectedFlatIndex];
|
||||
const groups = NotificationService.groupedNotifications;
|
||||
const group = groups[currentItem.groupIndex];
|
||||
if (!group)
|
||||
return
|
||||
return;
|
||||
|
||||
// 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)
|
||||
return
|
||||
return;
|
||||
const wasExpanded = NotificationService.expandedGroups[group.key] || false;
|
||||
const groupIndex = currentItem.groupIndex;
|
||||
|
||||
const wasExpanded = NotificationService.expandedGroups[group.key] || false
|
||||
const groupIndex = currentItem.groupIndex
|
||||
|
||||
isTogglingGroup = true
|
||||
NotificationService.toggleGroupExpansion(group.key)
|
||||
rebuildFlatNavigation()
|
||||
isTogglingGroup = true;
|
||||
NotificationService.toggleGroupExpansion(group.key);
|
||||
rebuildFlatNavigation();
|
||||
|
||||
// Smart selection after toggle
|
||||
if (!wasExpanded) {
|
||||
// Just expanded - move to first notification in the group
|
||||
for (var i = 0; i < flatNavigation.length; i++) {
|
||||
if (flatNavigation[i].type === "notification" && flatNavigation[i].groupIndex === groupIndex) {
|
||||
selectedFlatIndex = i
|
||||
break
|
||||
selectedFlatIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Just collapsed - stay on the group header
|
||||
for (var i = 0; i < flatNavigation.length; i++) {
|
||||
if (flatNavigation[i].type === "group" && flatNavigation[i].groupIndex === groupIndex) {
|
||||
selectedFlatIndex = i
|
||||
break
|
||||
selectedFlatIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isTogglingGroup = false
|
||||
ensureVisible()
|
||||
isTogglingGroup = false;
|
||||
}
|
||||
|
||||
function handleEnterKey() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
return;
|
||||
const currentItem = flatNavigation[selectedFlatIndex];
|
||||
const groups = NotificationService.groupedNotifications;
|
||||
const group = groups[currentItem.groupIndex];
|
||||
if (!group)
|
||||
return
|
||||
|
||||
return;
|
||||
if (currentItem.type === "group") {
|
||||
const notificationCount = group.notifications ? group.notifications.length : 0
|
||||
const notificationCount = group.notifications ? group.notifications.length : 0;
|
||||
if (notificationCount >= 2) {
|
||||
toggleGroupExpanded()
|
||||
toggleGroupExpanded();
|
||||
} else {
|
||||
executeAction(0)
|
||||
executeAction(0);
|
||||
}
|
||||
} else if (currentItem.type === "notification") {
|
||||
executeAction(0)
|
||||
executeAction(0);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTextExpanded() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
return;
|
||||
const currentItem = flatNavigation[selectedFlatIndex];
|
||||
const groups = NotificationService.groupedNotifications;
|
||||
const group = groups[currentItem.groupIndex];
|
||||
if (!group)
|
||||
return
|
||||
|
||||
let messageId = ""
|
||||
return;
|
||||
let messageId = "";
|
||||
|
||||
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) {
|
||||
messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc"
|
||||
messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc";
|
||||
}
|
||||
|
||||
if (messageId) {
|
||||
NotificationService.toggleMessageExpansion(messageId)
|
||||
NotificationService.toggleMessageExpansion(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
function executeAction(actionIndex) {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
return;
|
||||
const currentItem = flatNavigation[selectedFlatIndex];
|
||||
const groups = NotificationService.groupedNotifications;
|
||||
const group = groups[currentItem.groupIndex];
|
||||
if (!group)
|
||||
return
|
||||
|
||||
let actions = []
|
||||
return;
|
||||
let actions = [];
|
||||
|
||||
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) {
|
||||
actions = group.notifications[currentItem.notificationIndex]?.actions || []
|
||||
actions = group.notifications[currentItem.notificationIndex]?.actions || [];
|
||||
}
|
||||
|
||||
if (actionIndex >= 0 && actionIndex < actions.length) {
|
||||
const action = actions[actionIndex]
|
||||
const action = actions[actionIndex];
|
||||
if (action.invoke) {
|
||||
action.invoke()
|
||||
action.invoke();
|
||||
if (onClose)
|
||||
onClose()
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearSelected() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
return;
|
||||
const currentItem = flatNavigation[selectedFlatIndex];
|
||||
const groups = NotificationService.groupedNotifications;
|
||||
const group = groups[currentItem.groupIndex];
|
||||
if (!group)
|
||||
return
|
||||
|
||||
return;
|
||||
if (currentItem.type === "group") {
|
||||
NotificationService.dismissGroup(group.key)
|
||||
NotificationService.dismissGroup(group.key);
|
||||
} else if (currentItem.type === "notification") {
|
||||
const notification = group.notifications[currentItem.notificationIndex]
|
||||
NotificationService.dismissNotification(notification)
|
||||
const notification = group.notifications[currentItem.notificationIndex];
|
||||
NotificationService.dismissNotification(notification);
|
||||
}
|
||||
|
||||
rebuildFlatNavigation()
|
||||
rebuildFlatNavigation();
|
||||
|
||||
if (flatNavigation.length === 0) {
|
||||
keyboardNavigationActive = false
|
||||
keyboardNavigationActive = false;
|
||||
if (listView) {
|
||||
listView.keyboardActive = false
|
||||
listView.keyboardActive = false;
|
||||
}
|
||||
} else {
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1)
|
||||
updateSelectedIdFromIndex()
|
||||
ensureVisible()
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1);
|
||||
updateSelectedIdFromIndex();
|
||||
ensureVisible();
|
||||
}
|
||||
}
|
||||
|
||||
function findRepeater(parent) {
|
||||
if (!parent || !parent.children) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
for (var i = 0; i < parent.children.length; i++) {
|
||||
const child = parent.children[i]
|
||||
const child = parent.children[i];
|
||||
if (child.objectName === "notificationRepeater") {
|
||||
return child
|
||||
return child;
|
||||
}
|
||||
const found = findRepeater(child)
|
||||
const found = findRepeater(child);
|
||||
if (found) {
|
||||
return found
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
function ensureVisible() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length || !listView)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
return;
|
||||
const currentItem = flatNavigation[selectedFlatIndex];
|
||||
|
||||
if (keyboardNavigationActive && currentItem && currentItem.groupIndex >= 0) {
|
||||
if (currentItem.type === "notification") {
|
||||
const groupDelegate = listView.itemAtIndex(currentItem.groupIndex)
|
||||
const groupDelegate = listView.itemAtIndex(currentItem.groupIndex);
|
||||
if (groupDelegate && groupDelegate.children && groupDelegate.children.length > 0) {
|
||||
const notificationCard = groupDelegate.children[0]
|
||||
const repeater = findRepeater(notificationCard)
|
||||
const notificationCard = groupDelegate.children[0];
|
||||
const repeater = findRepeater(notificationCard);
|
||||
|
||||
if (repeater && currentItem.notificationIndex < repeater.count) {
|
||||
const notificationItem = repeater.itemAt(currentItem.notificationIndex)
|
||||
const notificationItem = repeater.itemAt(currentItem.notificationIndex);
|
||||
if (notificationItem) {
|
||||
const itemPos = notificationItem.mapToItem(listView.contentItem, 0, 0)
|
||||
const itemY = itemPos.y
|
||||
const itemHeight = notificationItem.height
|
||||
const itemPos = notificationItem.mapToItem(listView.contentItem, 0, 0);
|
||||
const itemY = itemPos.y;
|
||||
const itemHeight = notificationItem.height;
|
||||
|
||||
const viewportTop = listView.contentY
|
||||
const viewportBottom = listView.contentY + listView.height
|
||||
const viewportTop = listView.contentY;
|
||||
const viewportBottom = listView.contentY + listView.height;
|
||||
|
||||
if (itemY < viewportTop) {
|
||||
listView.contentY = itemY - 20
|
||||
listView.contentY = itemY - 20;
|
||||
} else if (itemY + itemHeight > viewportBottom) {
|
||||
listView.contentY = itemY + itemHeight - listView.height + 20
|
||||
listView.contentY = itemY + itemHeight - listView.height + 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listView.positionViewAtIndex(currentItem.groupIndex, ListView.Contain)
|
||||
listView.positionViewAtIndex(currentItem.groupIndex, ListView.Contain);
|
||||
}
|
||||
|
||||
listView.forceLayout()
|
||||
listView.forceLayout();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKey(event) {
|
||||
if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) {
|
||||
NotificationService.clearAllNotifications()
|
||||
rebuildFlatNavigation()
|
||||
NotificationService.clearAllNotifications();
|
||||
rebuildFlatNavigation();
|
||||
if (flatNavigation.length === 0) {
|
||||
keyboardNavigationActive = false
|
||||
keyboardNavigationActive = false;
|
||||
if (listView) {
|
||||
listView.keyboardActive = false
|
||||
listView.keyboardActive = false;
|
||||
}
|
||||
} else {
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
selectedFlatIndex = 0;
|
||||
updateSelectedIdFromIndex();
|
||||
}
|
||||
selectionVersion++
|
||||
event.accepted = true
|
||||
return
|
||||
selectionVersion++;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (keyboardNavigationActive) {
|
||||
keyboardNavigationActive = false
|
||||
event.accepted = true
|
||||
keyboardNavigationActive = false;
|
||||
event.accepted = true;
|
||||
} else {
|
||||
if (onClose)
|
||||
onClose()
|
||||
event.accepted = true
|
||||
onClose();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (event.key === Qt.Key_Down || event.key === 16777237) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true
|
||||
rebuildFlatNavigation() // Ensure we have fresh navigation data
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
keyboardNavigationActive = true;
|
||||
rebuildFlatNavigation(); // Ensure we have fresh navigation data
|
||||
selectedFlatIndex = 0;
|
||||
updateSelectedIdFromIndex();
|
||||
// Set keyboardActive on listView to show highlight
|
||||
if (listView) {
|
||||
listView.keyboardActive = true
|
||||
listView.keyboardActive = true;
|
||||
}
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
event.accepted = true
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
event.accepted = true;
|
||||
} else {
|
||||
selectNext()
|
||||
event.accepted = true
|
||||
selectNext();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (event.key === Qt.Key_Up || event.key === 16777235) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true
|
||||
rebuildFlatNavigation() // Ensure we have fresh navigation data
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
keyboardNavigationActive = true;
|
||||
rebuildFlatNavigation(); // Ensure we have fresh navigation data
|
||||
selectedFlatIndex = 0;
|
||||
updateSelectedIdFromIndex();
|
||||
// Set keyboardActive on listView to show highlight
|
||||
if (listView) {
|
||||
listView.keyboardActive = true
|
||||
listView.keyboardActive = true;
|
||||
}
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
event.accepted = true
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
event.accepted = true;
|
||||
} else if (selectedFlatIndex === 0) {
|
||||
keyboardNavigationActive = false
|
||||
keyboardNavigationActive = false;
|
||||
// Reset keyboardActive when navigation is disabled
|
||||
if (listView) {
|
||||
listView.keyboardActive = false
|
||||
listView.keyboardActive = false;
|
||||
}
|
||||
selectionVersion++
|
||||
event.accepted = true
|
||||
return
|
||||
selectionVersion++;
|
||||
event.accepted = true;
|
||||
return;
|
||||
} else {
|
||||
selectPrevious()
|
||||
event.accepted = true
|
||||
selectPrevious();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true
|
||||
rebuildFlatNavigation()
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
keyboardNavigationActive = true;
|
||||
rebuildFlatNavigation();
|
||||
selectedFlatIndex = 0;
|
||||
updateSelectedIdFromIndex();
|
||||
if (listView) {
|
||||
listView.keyboardActive = true
|
||||
listView.keyboardActive = true;
|
||||
}
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
} else {
|
||||
selectNext()
|
||||
selectNext();
|
||||
}
|
||||
event.accepted = true
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true
|
||||
rebuildFlatNavigation()
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
keyboardNavigationActive = true;
|
||||
rebuildFlatNavigation();
|
||||
selectedFlatIndex = 0;
|
||||
updateSelectedIdFromIndex();
|
||||
if (listView) {
|
||||
listView.keyboardActive = true
|
||||
listView.keyboardActive = true;
|
||||
}
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
} else if (selectedFlatIndex === 0) {
|
||||
keyboardNavigationActive = false
|
||||
keyboardNavigationActive = false;
|
||||
if (listView) {
|
||||
listView.keyboardActive = false
|
||||
listView.keyboardActive = false;
|
||||
}
|
||||
selectionVersion++
|
||||
selectionVersion++;
|
||||
} else {
|
||||
selectPrevious()
|
||||
selectPrevious();
|
||||
}
|
||||
event.accepted = true
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true
|
||||
rebuildFlatNavigation()
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
keyboardNavigationActive = true;
|
||||
rebuildFlatNavigation();
|
||||
selectedFlatIndex = 0;
|
||||
updateSelectedIdFromIndex();
|
||||
if (listView) {
|
||||
listView.keyboardActive = true
|
||||
listView.keyboardActive = true;
|
||||
}
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
} else {
|
||||
selectNext()
|
||||
selectNext();
|
||||
}
|
||||
event.accepted = true
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true
|
||||
rebuildFlatNavigation()
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
keyboardNavigationActive = true;
|
||||
rebuildFlatNavigation();
|
||||
selectedFlatIndex = 0;
|
||||
updateSelectedIdFromIndex();
|
||||
if (listView) {
|
||||
listView.keyboardActive = true
|
||||
listView.keyboardActive = true;
|
||||
}
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
selectionVersion++;
|
||||
ensureVisible();
|
||||
} else if (selectedFlatIndex === 0) {
|
||||
keyboardNavigationActive = false
|
||||
keyboardNavigationActive = false;
|
||||
if (listView) {
|
||||
listView.keyboardActive = false
|
||||
listView.keyboardActive = false;
|
||||
}
|
||||
selectionVersion++
|
||||
selectionVersion++;
|
||||
} else {
|
||||
selectPrevious()
|
||||
selectPrevious();
|
||||
}
|
||||
event.accepted = true
|
||||
event.accepted = true;
|
||||
} else if (keyboardNavigationActive) {
|
||||
if (event.key === Qt.Key_Space) {
|
||||
toggleGroupExpanded()
|
||||
event.accepted = true
|
||||
toggleGroupExpanded();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
handleEnterKey()
|
||||
event.accepted = true
|
||||
handleEnterKey();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_E) {
|
||||
toggleTextExpanded()
|
||||
event.accepted = true
|
||||
toggleTextExpanded();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
|
||||
clearSelected()
|
||||
event.accepted = true
|
||||
clearSelected();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
selectNext()
|
||||
event.accepted = true
|
||||
selectNext();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
selectPrevious()
|
||||
event.accepted = true
|
||||
selectPrevious();
|
||||
event.accepted = true;
|
||||
} else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
|
||||
const actionIndex = event.key - Qt.Key_1
|
||||
executeAction(actionIndex)
|
||||
event.accepted = true
|
||||
const actionIndex = event.key - Qt.Key_1;
|
||||
executeAction(actionIndex);
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_F10) {
|
||||
showKeyboardHints = !showKeyboardHints
|
||||
event.accepted = true
|
||||
showKeyboardHints = !showKeyboardHints;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,13 +544,13 @@ QtObject {
|
||||
"type": "",
|
||||
"groupIndex": -1,
|
||||
"notificationIndex": -1
|
||||
}
|
||||
};
|
||||
}
|
||||
const result = flatNavigation[selectedFlatIndex] || {
|
||||
"type": "",
|
||||
"groupIndex": -1,
|
||||
"notificationIndex": -1
|
||||
}
|
||||
return result
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ Item {
|
||||
DankTextField {
|
||||
id: searchField
|
||||
width: parent.width - addButton.width - Theme.spacingM
|
||||
height: 44
|
||||
height: Math.round(Theme.fontSizeMedium * 3)
|
||||
placeholderText: I18n.tr("Search keybinds...")
|
||||
leftIconName: "search"
|
||||
onTextChanged: {
|
||||
@@ -240,8 +240,8 @@ Item {
|
||||
|
||||
DankActionButton {
|
||||
id: addButton
|
||||
width: 44
|
||||
height: 44
|
||||
width: searchField.height
|
||||
height: searchField.height
|
||||
circular: false
|
||||
iconName: "add"
|
||||
iconSize: Theme.iconSize
|
||||
@@ -331,7 +331,7 @@ Item {
|
||||
Rectangle {
|
||||
id: fixButton
|
||||
width: fixButtonText.implicitWidth + Theme.spacingL * 2
|
||||
height: 36
|
||||
height: Math.round(Theme.fontSizeMedium * 2.5)
|
||||
radius: Theme.cornerRadius
|
||||
visible: warningBox.showError || warningBox.showSetup
|
||||
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
|
||||
@@ -382,9 +382,10 @@ Item {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
readonly property real chipHeight: allChip.implicitHeight + Theme.spacingM
|
||||
width: allChip.implicitWidth + Theme.spacingL
|
||||
height: 32
|
||||
radius: 16
|
||||
height: chipHeight
|
||||
radius: chipHeight / 2
|
||||
color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest
|
||||
|
||||
StyledText {
|
||||
@@ -412,9 +413,10 @@ Item {
|
||||
required property string modelData
|
||||
required property int index
|
||||
|
||||
readonly property real chipHeight: catText.implicitHeight + Theme.spacingM
|
||||
width: catText.implicitWidth + Theme.spacingL
|
||||
height: 32
|
||||
radius: 16
|
||||
height: chipHeight
|
||||
radius: chipHeight / 2
|
||||
color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest)
|
||||
|
||||
StyledText {
|
||||
|
||||
@@ -23,6 +23,15 @@ Item {
|
||||
iconName: "refresh"
|
||||
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 {
|
||||
text: I18n.tr("Use Custom Command")
|
||||
description: I18n.tr("Use custom command for update your system")
|
||||
|
||||
@@ -371,9 +371,14 @@ Item {
|
||||
widgetObj.pciId = "";
|
||||
}
|
||||
if (widgetId === "controlCenterButton") {
|
||||
widgetObj.showNetworkIcon = true;
|
||||
widgetObj.showBluetoothIcon = true;
|
||||
widgetObj.showAudioIcon = true;
|
||||
widgetObj.showNetworkIcon = SettingsData.controlCenterShowNetworkIcon;
|
||||
widgetObj.showBluetoothIcon = SettingsData.controlCenterShowBluetoothIcon;
|
||||
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")
|
||||
widgetObj.mountPath = "/";
|
||||
@@ -423,9 +428,14 @@ Item {
|
||||
else if (widget.id === "gpuTemp")
|
||||
newWidget.pciId = "";
|
||||
if (widget.id === "controlCenterButton") {
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||
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;
|
||||
break;
|
||||
@@ -471,9 +481,14 @@ Item {
|
||||
if (widget.pciId !== undefined)
|
||||
newWidget.pciId = widget.pciId;
|
||||
if (widget.id === "controlCenterButton") {
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||
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;
|
||||
setWidgetsForSection(sectionId, widgets);
|
||||
@@ -541,41 +556,48 @@ Item {
|
||||
if (widget.pciId !== undefined)
|
||||
newWidget.pciId = widget.pciId;
|
||||
if (widget.id === "controlCenterButton") {
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||
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;
|
||||
setWidgetsForSection(sectionId, widgets);
|
||||
}
|
||||
|
||||
function handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) {
|
||||
switch (settingName) {
|
||||
case "showNetworkIcon":
|
||||
SettingsData.set("controlCenterShowNetworkIcon", value);
|
||||
break;
|
||||
case "showBluetoothIcon":
|
||||
SettingsData.set("controlCenterShowBluetoothIcon", value);
|
||||
break;
|
||||
case "showAudioIcon":
|
||||
SettingsData.set("controlCenterShowAudioIcon", value);
|
||||
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 widgets = getWidgetsForSection(sectionId).slice();
|
||||
if (widgetIndex < 0 || widgetIndex >= widgets.length)
|
||||
return;
|
||||
|
||||
var widget = widgets[widgetIndex];
|
||||
if (typeof widget === "string") {
|
||||
widget = {
|
||||
"id": widget,
|
||||
"enabled": true
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -626,9 +648,14 @@ Item {
|
||||
if (widget.showSwap !== undefined)
|
||||
newWidget.showSwap = widget.showSwap;
|
||||
if (widget.id === "controlCenterButton") {
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||
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;
|
||||
setWidgetsForSection(sectionId, widgets);
|
||||
@@ -678,9 +705,14 @@ Item {
|
||||
if (widget.keyboardLayoutNameCompactMode !== undefined)
|
||||
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
|
||||
if (widget.id === "controlCenterButton") {
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||
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;
|
||||
setWidgetsForSection(sectionId, widgets);
|
||||
@@ -730,9 +762,14 @@ Item {
|
||||
if (widget.keyboardLayoutNameCompactMode !== undefined)
|
||||
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
|
||||
if (widget.id === "controlCenterButton") {
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||
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;
|
||||
widget = newWidget;
|
||||
@@ -789,6 +826,16 @@ Item {
|
||||
item.showBluetoothIcon = widget.showBluetoothIcon;
|
||||
if (widget.showAudioIcon !== undefined)
|
||||
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)
|
||||
item.minimumWidth = widget.minimumWidth;
|
||||
if (widget.showSwap !== undefined)
|
||||
|
||||
@@ -806,50 +806,42 @@ Column {
|
||||
{
|
||||
icon: "lan",
|
||||
label: I18n.tr("Network"),
|
||||
setting: "showNetworkIcon",
|
||||
checked: SettingsData.controlCenterShowNetworkIcon
|
||||
setting: "showNetworkIcon"
|
||||
},
|
||||
{
|
||||
icon: "vpn_lock",
|
||||
label: I18n.tr("VPN"),
|
||||
setting: "showVpnIcon",
|
||||
checked: SettingsData.controlCenterShowVpnIcon
|
||||
setting: "showVpnIcon"
|
||||
},
|
||||
{
|
||||
icon: "bluetooth",
|
||||
label: I18n.tr("Bluetooth"),
|
||||
setting: "showBluetoothIcon",
|
||||
checked: SettingsData.controlCenterShowBluetoothIcon
|
||||
setting: "showBluetoothIcon"
|
||||
},
|
||||
{
|
||||
icon: "volume_up",
|
||||
label: I18n.tr("Audio"),
|
||||
setting: "showAudioIcon",
|
||||
checked: SettingsData.controlCenterShowAudioIcon
|
||||
setting: "showAudioIcon"
|
||||
},
|
||||
{
|
||||
icon: "mic",
|
||||
label: I18n.tr("Microphone"),
|
||||
setting: "showMicIcon",
|
||||
checked: SettingsData.controlCenterShowMicIcon
|
||||
setting: "showMicIcon"
|
||||
},
|
||||
{
|
||||
icon: "brightness_high",
|
||||
label: I18n.tr("Brightness"),
|
||||
setting: "showBrightnessIcon",
|
||||
checked: SettingsData.controlCenterShowBrightnessIcon
|
||||
setting: "showBrightnessIcon"
|
||||
},
|
||||
{
|
||||
icon: "battery_full",
|
||||
label: I18n.tr("Battery"),
|
||||
setting: "showBatteryIcon",
|
||||
checked: SettingsData.controlCenterShowBatteryIcon
|
||||
setting: "showBatteryIcon"
|
||||
},
|
||||
{
|
||||
icon: "print",
|
||||
label: I18n.tr("Printer"),
|
||||
setting: "showPrinterIcon",
|
||||
checked: SettingsData.controlCenterShowPrinterIcon
|
||||
setting: "showPrinterIcon"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -857,6 +849,30 @@ Column {
|
||||
required property var modelData
|
||||
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
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
@@ -891,7 +907,7 @@ Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 20
|
||||
checked: modelData.checked
|
||||
checked: getCheckedState()
|
||||
onToggled: {
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled);
|
||||
}
|
||||
|
||||
@@ -53,10 +53,13 @@ Singleton {
|
||||
signal gammaStateUpdate(var data)
|
||||
signal openUrlRequested(string url)
|
||||
signal appPickerRequested(var data)
|
||||
signal screensaverStateUpdate(var data)
|
||||
|
||||
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: {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
@@ -371,6 +374,10 @@ Singleton {
|
||||
} else if (data.url) {
|
||||
openUrlRequested(data.url);
|
||||
}
|
||||
} else if (service === "freedesktop.screensaver") {
|
||||
screensaverInhibited = data.inhibited || false;
|
||||
screensaverInhibitors = data.inhibitors || [];
|
||||
screensaverStateUpdate(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ Singleton {
|
||||
property bool respectInhibitors: true
|
||||
property bool _enableGate: true
|
||||
|
||||
readonly property bool externalInhibitActive: DMSService.screensaverInhibited
|
||||
|
||||
readonly property bool isOnBattery: BatteryService.batteryAvailable && !BatteryService.isPluggedIn
|
||||
readonly property int monitorTimeout: isOnBattery ? SettingsData.batteryMonitorTimeout : SettingsData.acMonitorTimeout
|
||||
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: {
|
||||
if (!idleMonitorAvailable) {
|
||||
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");
|
||||
createIdleMonitors();
|
||||
}
|
||||
|
||||
if (externalInhibitActive) {
|
||||
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
|
||||
SessionService.idleInhibited = true;
|
||||
SessionService.inhibitReason = "External app: " + (apps || "unknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
@@ -13,7 +12,7 @@ StyledRect {
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
textInput.forceActiveFocus()
|
||||
textInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,26 +52,26 @@ StyledRect {
|
||||
signal focusStateChanged(bool hasFocus)
|
||||
|
||||
function getActiveFocus() {
|
||||
return textInput.activeFocus
|
||||
return textInput.activeFocus;
|
||||
}
|
||||
function setFocus(value) {
|
||||
textInput.focus = value
|
||||
textInput.focus = value;
|
||||
}
|
||||
function forceActiveFocus() {
|
||||
textInput.forceActiveFocus()
|
||||
textInput.forceActiveFocus();
|
||||
}
|
||||
function selectAll() {
|
||||
textInput.selectAll()
|
||||
textInput.selectAll();
|
||||
}
|
||||
function clear() {
|
||||
textInput.clear()
|
||||
textInput.clear();
|
||||
}
|
||||
function insertText(str) {
|
||||
textInput.insert(textInput.cursorPosition, str)
|
||||
textInput.insert(textInput.cursorPosition, str);
|
||||
}
|
||||
|
||||
width: 200
|
||||
height: 48
|
||||
height: Math.round(Theme.fontSizeMedium * 3.4)
|
||||
radius: cornerRadius
|
||||
color: backgroundColor
|
||||
border.color: textInput.activeFocus ? focusedBorderColor : normalBorderColor
|
||||
@@ -112,30 +111,30 @@ StyledRect {
|
||||
onActiveFocusChanged: root.focusStateChanged(activeFocus)
|
||||
Keys.forwardTo: root.keyForwardTargets
|
||||
Keys.onLeftPressed: event => {
|
||||
if (root.ignoreLeftRightKeys) {
|
||||
event.accepted = true
|
||||
} else {
|
||||
// Allow normal TextInput cursor movement
|
||||
event.accepted = false
|
||||
}
|
||||
}
|
||||
if (root.ignoreLeftRightKeys) {
|
||||
event.accepted = true;
|
||||
} else {
|
||||
// Allow normal TextInput cursor movement
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
Keys.onRightPressed: event => {
|
||||
if (root.ignoreLeftRightKeys) {
|
||||
event.accepted = true
|
||||
} else {
|
||||
event.accepted = false
|
||||
}
|
||||
}
|
||||
if (root.ignoreLeftRightKeys) {
|
||||
event.accepted = true;
|
||||
} else {
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
||||
event.accepted = false
|
||||
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||
if (root.keyForwardTargets[i]) {
|
||||
root.keyForwardTargets[i].Keys.pressed(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
||||
event.accepted = false;
|
||||
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||
if (root.keyForwardTargets[i]) {
|
||||
root.keyForwardTargets[i].Keys.pressed(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -171,7 +170,7 @@ StyledRect {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
textInput.text = ""
|
||||
textInput.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,12 @@ Item {
|
||||
readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : []
|
||||
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 saveBind(string originalKey, var newData)
|
||||
signal removeBind(string key)
|
||||
@@ -223,7 +229,7 @@ Item {
|
||||
Rectangle {
|
||||
id: collapsedRect
|
||||
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
|
||||
topLeftRadius: Theme.cornerRadius
|
||||
topRightRadius: Theme.cornerRadius
|
||||
@@ -240,7 +246,7 @@ Item {
|
||||
|
||||
Column {
|
||||
id: keysColumn
|
||||
Layout.preferredWidth: 140
|
||||
Layout.preferredWidth: root._keysColumnWidth
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
@@ -253,9 +259,9 @@ Item {
|
||||
|
||||
property bool isSelected: root.isExpanded && root.editingKeyIndex === index && !root.addingNewKey
|
||||
|
||||
width: 140
|
||||
height: 28
|
||||
radius: 6
|
||||
width: root._keysColumnWidth
|
||||
height: root._chipHeight
|
||||
radius: root._chipHeight / 4
|
||||
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
||||
|
||||
Rectangle {
|
||||
@@ -332,7 +338,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: "warning"
|
||||
size: 14
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.primary
|
||||
visible: root.hasConfigConflict
|
||||
}
|
||||
@@ -352,7 +358,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: root.isExpanded ? "expand_less" : "expand_more"
|
||||
size: 20
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
@@ -360,7 +366,7 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 140 + Theme.spacingM * 2
|
||||
anchors.leftMargin: root._keysColumnWidth + Theme.spacingM * 2
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.toggleExpand()
|
||||
}
|
||||
@@ -420,7 +426,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: "warning"
|
||||
size: 16
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
@@ -461,7 +467,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
Flow {
|
||||
@@ -478,8 +484,8 @@ Item {
|
||||
property bool isSelected: root.editingKeyIndex === index && !root.addingNewKey
|
||||
|
||||
width: editKeyChipText.implicitWidth + Theme.spacingM
|
||||
height: 28
|
||||
radius: 6
|
||||
height: root._chipHeight
|
||||
radius: root._chipHeight / 4
|
||||
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
||||
|
||||
Rectangle {
|
||||
@@ -509,9 +515,9 @@ Item {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 6
|
||||
width: root._chipHeight
|
||||
height: root._chipHeight
|
||||
radius: root._chipHeight / 4
|
||||
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
||||
visible: !root.isNew
|
||||
|
||||
@@ -523,7 +529,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: "add"
|
||||
size: 16
|
||||
size: Theme.iconSizeSmall
|
||||
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
@@ -548,13 +554,13 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: captureScope
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
focus: root.recording
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -596,12 +602,12 @@ Item {
|
||||
|
||||
DankActionButton {
|
||||
id: recordBtn
|
||||
width: 28
|
||||
height: 28
|
||||
width: root._chipHeight
|
||||
height: root._chipHeight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
circular: false
|
||||
iconName: root.recording ? "close" : "radio_button_checked"
|
||||
iconSize: 16
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconColor: root.recording ? Theme.error : Theme.primary
|
||||
onClicked: root.recording ? root.stopRecording() : root.startRecording()
|
||||
}
|
||||
@@ -703,8 +709,8 @@ Item {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: root._inputHeight
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
||||
visible: root.keys.length === 1 && !root.isNew
|
||||
@@ -717,7 +723,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: "add"
|
||||
size: 18
|
||||
size: Theme.iconSizeSmall + 2
|
||||
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
@@ -736,11 +742,11 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingS
|
||||
visible: root.hasConflict
|
||||
Layout.leftMargin: 60 + Theme.spacingM
|
||||
Layout.leftMargin: root._labelWidth + Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "warning"
|
||||
size: 16
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
@@ -762,7 +768,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -785,7 +791,7 @@ Item {
|
||||
})
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
Layout.preferredHeight: root._buttonHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: root._actionType === modelData.id ? Theme.surfaceContainerHighest : Theme.surfaceContainer
|
||||
border.color: root._actionType === modelData.id ? Theme.outline : (typeArea.containsMouse ? Theme.outlineVariant : "transparent")
|
||||
@@ -797,7 +803,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: typeDelegate.modelData.icon
|
||||
size: 16
|
||||
size: Theme.iconSizeSmall
|
||||
color: root._actionType === typeDelegate.modelData.id ? Theme.surfaceText : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
@@ -869,7 +875,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -913,14 +919,14 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
visible: dmsArgsRow.hasAmountArg
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: dmsAmountField
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 5.5)
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
placeholderText: "5"
|
||||
visible: dmsArgsRow.hasAmountArg
|
||||
|
||||
@@ -961,14 +967,14 @@ Item {
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.leftMargin: dmsArgsRow.hasAmountArg ? Theme.spacingM : 0
|
||||
Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : 60
|
||||
Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : root._labelWidth
|
||||
visible: dmsArgsRow.hasDeviceArg
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: dmsDeviceField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
placeholderText: I18n.tr("leave empty for default")
|
||||
visible: dmsArgsRow.hasDeviceArg
|
||||
|
||||
@@ -1006,7 +1012,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
visible: dmsArgsRow.hasTabArg
|
||||
}
|
||||
|
||||
@@ -1064,12 +1070,12 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
id: compositorCatDropdown
|
||||
Layout.preferredWidth: 120
|
||||
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 8.5)
|
||||
compactMode: true
|
||||
currentValue: {
|
||||
const base = root.editAction.split(" ")[0];
|
||||
@@ -1108,8 +1114,8 @@ Item {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: root._inputHeight
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
@@ -1121,7 +1127,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: "edit"
|
||||
size: 18
|
||||
size: Theme.iconSizeSmall + 2
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
@@ -1150,7 +1156,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -1160,7 +1166,7 @@ Item {
|
||||
DankTextField {
|
||||
id: argValueField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
visible: {
|
||||
const cfg = optionsRow.argConfig;
|
||||
if (!cfg?.config?.args)
|
||||
@@ -1308,13 +1314,13 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customCompositorField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10")
|
||||
text: root._actionType === "compositor" ? root.editAction : ""
|
||||
onTextChanged: {
|
||||
@@ -1327,8 +1333,8 @@ Item {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: root._inputHeight
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
@@ -1340,7 +1346,7 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: "list"
|
||||
size: 18
|
||||
size: Theme.iconSizeSmall + 2
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
@@ -1371,13 +1377,13 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: spawnTextField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
placeholderText: I18n.tr("e.g., firefox, kitty --title foo")
|
||||
readonly property var _parsed: root._actionType === "spawn" ? Actions.parseSpawnCommand(root.editAction) : null
|
||||
text: _parsed ? (_parsed.command + " " + _parsed.args.join(" ")).trim() : ""
|
||||
@@ -1403,13 +1409,13 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: shellTextField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
placeholderText: I18n.tr("e.g., notify-send 'Hello' && sleep 1")
|
||||
text: root._actionType === "shell" ? Actions.parseShellCommand(root.editAction) : ""
|
||||
onTextChanged: {
|
||||
@@ -1431,13 +1437,13 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: titleField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
placeholderText: I18n.tr("Hotkey overlay title (optional)")
|
||||
text: root.editDesc
|
||||
onTextChanged: root.updateEdit({
|
||||
@@ -1455,13 +1461,13 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
Layout.preferredWidth: root._labelWidth
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: cooldownField
|
||||
Layout.preferredWidth: 100
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 7)
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
placeholderText: "0"
|
||||
|
||||
Connections {
|
||||
@@ -1508,8 +1514,8 @@ Item {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankActionButton {
|
||||
Layout.preferredWidth: 32
|
||||
Layout.preferredHeight: 32
|
||||
Layout.preferredWidth: root._buttonHeight
|
||||
Layout.preferredHeight: root._buttonHeight
|
||||
circular: false
|
||||
iconName: "delete"
|
||||
iconSize: Theme.iconSize - 4
|
||||
@@ -1531,7 +1537,7 @@ Item {
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Cancel")
|
||||
buttonHeight: 32
|
||||
buttonHeight: root._buttonHeight
|
||||
backgroundColor: Theme.surfaceContainer
|
||||
textColor: Theme.surfaceText
|
||||
visible: root.hasChanges || root.isNew
|
||||
@@ -1547,7 +1553,7 @@ Item {
|
||||
|
||||
DankButton {
|
||||
text: root.isNew ? I18n.tr("Add") : I18n.tr("Save")
|
||||
buttonHeight: 32
|
||||
buttonHeight: root._buttonHeight
|
||||
enabled: root.canSave()
|
||||
visible: root.hasChanges || root.isNew
|
||||
onClicked: root.doSave()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Modals.FileBrowser
|
||||
@@ -13,7 +14,7 @@ Rectangle {
|
||||
property string expandedUuid: ""
|
||||
property int listHeight: 180
|
||||
|
||||
implicitHeight: contentColumn.implicitHeight + Theme.spacingM * 2
|
||||
implicitHeight: 32 + 1 + listHeight + Theme.spacingS * 4 + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
|
||||
@@ -153,328 +154,71 @@ Rectangle {
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.listHeight
|
||||
contentHeight: listCol.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: listCol
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: DMSNetworkService.profiles.length === 0
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: DMSNetworkService.profiles.length === 0 ? 100 : 0
|
||||
visible: height > 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "vpn_key_off"
|
||||
size: 36
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("No VPN profiles")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Click Import to add a .ovpn or .conf")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
DankIcon {
|
||||
name: "vpn_key_off"
|
||||
size: 36
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: DMSNetworkService.profiles
|
||||
StyledText {
|
||||
text: I18n.tr("No VPN profiles")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: profileRow
|
||||
required property var modelData
|
||||
required property int index
|
||||
StyledText {
|
||||
text: I18n.tr("Click Import to add a .ovpn or .conf")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
DankListView {
|
||||
id: vpnListView
|
||||
anchors.fill: parent
|
||||
visible: DMSNetworkService.profiles.length > 0
|
||||
spacing: 4
|
||||
cacheBuffer: 200
|
||||
clip: true
|
||||
|
||||
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
|
||||
model: ScriptModel {
|
||||
values: DMSNetworkService.profiles
|
||||
objectProp: "uuid"
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rowArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
|
||||
enabled: !DMSNetworkService.isBusy
|
||||
onClicked: DMSNetworkService.toggle(modelData.uuid)
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 46 - Theme.spacingS * 2
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: isActive ? "vpn_lock" : "vpn_key_off"
|
||||
size: 20
|
||||
color: isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 20 - 28 - 28 - Theme.spacingS * 4
|
||||
|
||||
StyledText {
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: isActive ? Theme.primary : Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: VPNService.getVpnTypeFromProfile(modelData)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
wrapMode: Text.NoWrap
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Theme.spacingXS
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: expandBtnRect
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: expandBtn.containsMouse ? Theme.surfacePressed : "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: isExpanded ? "expand_less" : "expand_more"
|
||||
size: 18
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: expandBtn
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (isExpanded) {
|
||||
root.expandedUuid = "";
|
||||
} else {
|
||||
root.expandedUuid = modelData.uuid;
|
||||
VPNService.getConfig(modelData.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: deleteBtnRect
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: deleteBtn.containsMouse ? Theme.errorHover : "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "delete"
|
||||
size: 18
|
||||
color: deleteBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deleteBtn
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
deleteConfirm.showWithOptions({
|
||||
title: I18n.tr("Delete VPN"),
|
||||
message: I18n.tr("Delete \"") + modelData.name + "\"?",
|
||||
confirmText: I18n.tr("Delete"),
|
||||
confirmColor: Theme.error,
|
||||
onConfirm: () => VPNService.deleteVpn(modelData.uuid)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: expandedContent
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: isExpanded
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outlineLight
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: VPNService.configLoading ? 40 : 0
|
||||
visible: VPNService.configLoading
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "sync"
|
||||
size: 16
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Loading...")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: !VPNService.configLoading && configData
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
if (!configData)
|
||||
return [];
|
||||
const fields = [];
|
||||
const data = configData.data || {};
|
||||
|
||||
if (data.remote)
|
||||
fields.push({
|
||||
label: I18n.tr("Server"),
|
||||
value: data.remote
|
||||
});
|
||||
if (configData.username || data.username)
|
||||
fields.push({
|
||||
label: I18n.tr("Username"),
|
||||
value: configData.username || data.username
|
||||
});
|
||||
if (data.cipher)
|
||||
fields.push({
|
||||
label: I18n.tr("Cipher"),
|
||||
value: data.cipher
|
||||
});
|
||||
if (data.auth)
|
||||
fields.push({
|
||||
label: I18n.tr("Auth"),
|
||||
value: data.auth
|
||||
});
|
||||
if (data["proto-tcp"] === "yes" || data["proto-tcp"] === "no")
|
||||
fields.push({
|
||||
label: I18n.tr("Protocol"),
|
||||
value: data["proto-tcp"] === "yes" ? "TCP" : "UDP"
|
||||
});
|
||||
if (data["tunnel-mtu"])
|
||||
fields.push({
|
||||
label: I18n.tr("MTU"),
|
||||
value: data["tunnel-mtu"]
|
||||
});
|
||||
if (data["connection-type"])
|
||||
fields.push({
|
||||
label: I18n.tr("Auth Type"),
|
||||
value: data["connection-type"]
|
||||
});
|
||||
fields.push({
|
||||
label: I18n.tr("Autoconnect"),
|
||||
value: configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
|
||||
});
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: fieldContent.width + Theme.spacingM * 2
|
||||
height: 32
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.width: 1
|
||||
border.color: Theme.outlineLight
|
||||
|
||||
Row {
|
||||
id: fieldContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData.label + ":"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.value
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: Theme.spacingXS
|
||||
}
|
||||
}
|
||||
delegate: VpnProfileDelegate {
|
||||
required property var modelData
|
||||
width: vpnListView.width
|
||||
profile: modelData
|
||||
isExpanded: root.expandedUuid === modelData.uuid
|
||||
onToggleExpand: {
|
||||
if (root.expandedUuid === modelData.uuid) {
|
||||
root.expandedUuid = "";
|
||||
return;
|
||||
}
|
||||
root.expandedUuid = modelData.uuid;
|
||||
VPNService.getConfig(modelData.uuid);
|
||||
}
|
||||
onDeleteRequested: {
|
||||
deleteConfirm.showWithOptions({
|
||||
"title": I18n.tr("Delete VPN"),
|
||||
"message": I18n.tr("Delete \"") + modelData.name + "\"?",
|
||||
"confirmText": I18n.tr("Delete"),
|
||||
"confirmColor": Theme.error,
|
||||
"onConfirm": () => VPNService.deleteVpn(modelData.uuid)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
275
quickshell/Widgets/VpnProfileDelegate.qml
Normal file
275
quickshell/Widgets/VpnProfileDelegate.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user