mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-13 07:42:46 -04:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9673078a75 | |||
| 9e8c93bfd7 | |||
| 43d6f4b1d3 | |||
| bafe1c5fee | |||
| 306d7b2ce0 | |||
| e9f6583c60 | |||
| 42a2835929 | |||
| c2c90c680e | |||
| cd01f6378c | |||
| 6033075de6 | |||
| 79794d3441 | |||
| 031f86b417 | |||
| 891f53cf6f | |||
| 848991cf5b | |||
| d37ddd1d41 | |||
| 00d12acd5e | |||
| 3bbc78a44f | |||
| b0a6652cc6 | |||
| cb710b2e5f | |||
| ca5fe6f7db | |||
| fb75f4c68b | |||
| 5e2a418485 | |||
| 24fe215067 | |||
| ab2e8875ac | |||
| dec5740c74 | |||
| 208266dfa3 | |||
| 32f218d58c | |||
| 6fdaab2ccd | |||
| d336866f44 | |||
| b40df5f1c4 | |||
| 3c9886ad1b | |||
| ea205ebd12 | |||
| 30dad46c94 | |||
| fbf79e62e9 | |||
| efcf72bc08 | |||
| 3b511e2f55 | |||
| e4e20fb43a | |||
| 48ccff67a6 | |||
| a783d6507b | |||
| fd94e60797 | |||
| a1bcb7ea30 | |||
| 31b67164c7 | |||
| 786c13f892 | |||
| c652659d54 | |||
| ca39196f13 | |||
| f02dd8fd4b | |||
| 0f89886ce7 | |||
| 1118404192 | |||
| f011ea6cce | |||
| b2ac9c6c1a | |||
| fbab41abd6 | |||
| 82f881af5b | |||
| 68de9b437d | |||
| 830a715b6d | |||
| ce4aca9a72 | |||
| 7641171a01 | |||
| 119e084e52 | |||
| 7c6d52913e | |||
| f63ab5cf7c | |||
| 50f1bc5017 | |||
| c3ab409b6a | |||
| 44f6ab4878 | |||
| 5fda6e0f12 | |||
| 38068e78c9 | |||
| 66d22727e9 | |||
| db2f68e35d | |||
| 352277ec15 | |||
| d6043e64f2 | |||
| d3f5b8f32e | |||
| 6c3c722674 |
@@ -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
|
||||||
@@ -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
|
||||||
+192
-136
@@ -4,18 +4,18 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
package:
|
package:
|
||||||
description: 'Package to update (dms, dms-git, or all)'
|
description: "Package to update (dms, dms-git, or all)"
|
||||||
required: false
|
required: false
|
||||||
default: 'all'
|
default: "all"
|
||||||
rebuild_release:
|
rebuild_release:
|
||||||
description: 'Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)'
|
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ""
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- "v*"
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 */3 * * *' # Every 3 hours for dms-git builds
|
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-updates:
|
check-updates:
|
||||||
@@ -33,78 +33,114 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install OSC
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y osc
|
|
||||||
|
|
||||||
mkdir -p ~/.config/osc
|
|
||||||
cat > ~/.config/osc/oscrc << EOF
|
|
||||||
[general]
|
|
||||||
apiurl = https://api.opensuse.org
|
|
||||||
|
|
||||||
[https://api.opensuse.org]
|
|
||||||
user = ${{ secrets.OBS_USERNAME }}
|
|
||||||
pass = ${{ secrets.OBS_PASSWORD }}
|
|
||||||
EOF
|
|
||||||
chmod 600 ~/.config/osc/oscrc
|
|
||||||
|
|
||||||
- name: Check for updates
|
- name: Check for updates
|
||||||
id: check
|
id: check
|
||||||
|
env:
|
||||||
|
OBS_USERNAME: ${{ secrets.OBS_USERNAME }}
|
||||||
|
OBS_PASSWORD: ${{ secrets.OBS_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
|
# Helper function to check dms-git commit
|
||||||
|
check_dms_git() {
|
||||||
|
local CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
|
||||||
|
local 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 "")
|
||||||
|
local OBS_COMMIT=$(echo "$OBS_SPEC" | grep "^Version:" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$OBS_COMMIT" && "$CURRENT_COMMIT" == "$OBS_COMMIT" ]]; then
|
||||||
|
echo "📋 dms-git: Commit $CURRENT_COMMIT already exists, skipping"
|
||||||
|
return 1 # No update needed
|
||||||
|
else
|
||||||
|
echo "📋 dms-git: New commit $CURRENT_COMMIT (OBS has ${OBS_COMMIT:-none})"
|
||||||
|
return 0 # Update needed
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to check dms stable tag
|
||||||
|
check_dms_stable() {
|
||||||
|
local LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "v\?\([^"]*\)".*/\1/' || echo "")
|
||||||
|
local OBS_SPEC=$(curl -s -u "$OBS_USERNAME:$OBS_PASSWORD" "https://api.opensuse.org/source/home:AvengeMedia:dms/dms/dms.spec" 2>/dev/null || echo "")
|
||||||
|
local OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$LATEST_TAG" && "$LATEST_TAG" == "$OBS_VERSION" ]]; then
|
||||||
|
echo "📋 dms: Tag $LATEST_TAG already exists, skipping"
|
||||||
|
return 1 # No update needed
|
||||||
|
else
|
||||||
|
echo "📋 dms: New tag ${LATEST_TAG:-unknown} (OBS has ${OBS_VERSION:-none})"
|
||||||
|
return 0 # Update needed
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main logic
|
||||||
|
REBUILD="${{ github.event.inputs.rebuild_release }}"
|
||||||
|
|
||||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||||
|
# Tag push - always update stable package
|
||||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||||
VERSION="${GITHUB_REF#refs/tags/}"
|
VERSION="${GITHUB_REF#refs/tags/}"
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
echo "Triggered by tag: $VERSION (always update)"
|
echo "Triggered by tag: $VERSION (always update)"
|
||||||
|
|
||||||
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||||
|
# Scheduled run - check dms-git only
|
||||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||||
echo "Checking if dms-git source has changed..."
|
if check_dms_git; then
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
# Get current commit hash (8 chars to match spec format)
|
else
|
||||||
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 "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
|
fi
|
||||||
|
|
||||||
cd "${{ github.workspace }}"
|
|
||||||
else
|
|
||||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "📋 First upload to OBS, update needed"
|
|
||||||
fi
|
|
||||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
# Manual workflow trigger
|
||||||
|
PKG="${{ github.event.inputs.package }}"
|
||||||
|
|
||||||
|
if [[ -n "$REBUILD" ]]; then
|
||||||
|
# Rebuild requested - always proceed
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
echo "🔄 Manual rebuild requested: $PKG (ppa$REBUILD)"
|
||||||
|
|
||||||
|
elif [[ "$PKG" == "all" ]]; then
|
||||||
|
# Check each package and build list of those needing updates
|
||||||
|
PACKAGES_TO_UPDATE=()
|
||||||
|
check_dms_git && PACKAGES_TO_UPDATE+=("dms-git")
|
||||||
|
check_dms_stable && PACKAGES_TO_UPDATE+=("dms")
|
||||||
|
|
||||||
|
if [[ ${#PACKAGES_TO_UPDATE[@]} -gt 0 ]]; then
|
||||||
|
echo "packages=${PACKAGES_TO_UPDATE[*]}" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "✓ Packages to update: ${PACKAGES_TO_UPDATE[*]}"
|
||||||
else
|
else
|
||||||
|
echo "packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "✓ All packages up to date"
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "$PKG" == "dms-git" ]]; then
|
||||||
|
if check_dms_git; then
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "$PKG" == "dms" ]]; then
|
||||||
|
if check_dms_stable; then
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
# Unknown package - proceed anyway
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Manual trigger: $PKG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Fallback - proceed
|
||||||
echo "packages=all" >> $GITHUB_OUTPUT
|
echo "packages=all" >> $GITHUB_OUTPUT
|
||||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
@@ -113,9 +149,7 @@ jobs:
|
|||||||
name: Upload to OBS
|
name: Upload to OBS
|
||||||
needs: check-updates
|
needs: check-updates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: |
|
if: needs.check-updates.outputs.has_updates == 'true'
|
||||||
github.event_name == 'workflow_dispatch' ||
|
|
||||||
needs.check-updates.outputs.has_updates == 'true'
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -135,8 +169,14 @@ jobs:
|
|||||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||||
echo "Triggered by schedule: updating git package"
|
echo "Triggered by schedule: updating git package"
|
||||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||||
|
# Use filtered packages from check-updates when package="all" and no rebuild requested
|
||||||
|
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||||
|
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||||
|
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
|
||||||
|
else
|
||||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
||||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
@@ -144,66 +184,42 @@ jobs:
|
|||||||
- name: Update dms-git spec version
|
- name: Update dms-git spec version
|
||||||
if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
|
if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
|
||||||
run: |
|
run: |
|
||||||
# Get commit info for dms-git versioning
|
|
||||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||||
|
|
||||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||||
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
|
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
|
||||||
|
|
||||||
# Update version in spec
|
|
||||||
sed -i "s/^Version:.*/Version: $NEW_VERSION/" distro/opensuse/dms-git.spec
|
sed -i "s/^Version:.*/Version: $NEW_VERSION/" distro/opensuse/dms-git.spec
|
||||||
|
|
||||||
# Add changelog entry
|
# Single changelog entry (git snapshots don't need history)
|
||||||
DATE_STR=$(date "+%a %b %d %Y")
|
DATE_STR=$(date "+%a %b %d %Y")
|
||||||
CHANGELOG_ENTRY="* $DATE_STR Avenge Media <AvengeMedia.US@gmail.com> - ${NEW_VERSION}-1\n- Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms-git.spec)
|
||||||
sed -i "/%changelog/a\\$CHANGELOG_ENTRY" distro/opensuse/dms-git.spec
|
{
|
||||||
|
echo "$LOCAL_SPEC_HEAD"
|
||||||
|
echo "%changelog"
|
||||||
|
echo "* $DATE_STR Avenge Media <AvengeMedia.US@gmail.com> - ${NEW_VERSION}-1"
|
||||||
|
echo "- Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||||
|
} > distro/opensuse/dms-git.spec
|
||||||
|
|
||||||
- name: Update Debian dms-git changelog version
|
- name: Update Debian dms-git changelog version
|
||||||
if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
|
if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
|
||||||
run: |
|
run: |
|
||||||
# Get commit info for dms-git versioning
|
|
||||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||||
|
|
||||||
# Debian version format: 0.6.2+git2256.9162e314
|
|
||||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||||
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
|
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
|
||||||
|
|
||||||
|
# Single changelog entry (git snapshots don't need history)
|
||||||
CHANGELOG_DATE=$(date -R)
|
CHANGELOG_DATE=$(date -R)
|
||||||
|
{
|
||||||
CHANGELOG_FILE="distro/debian/dms-git/debian/changelog"
|
echo "dms-git ($NEW_VERSION) nightly; urgency=medium"
|
||||||
|
echo ""
|
||||||
# Get current version from changelog
|
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||||
CURRENT_VERSION=$(head -1 "$CHANGELOG_FILE" | sed 's/.*(\([^)]*\)).*/\1/')
|
echo ""
|
||||||
|
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||||
echo "Current Debian version: $CURRENT_VERSION"
|
} > "distro/debian/dms-git/debian/changelog"
|
||||||
echo "New version: $NEW_VERSION"
|
|
||||||
|
|
||||||
# Only update if version changed
|
|
||||||
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
|
|
||||||
# Create new changelog entry at top
|
|
||||||
TEMP_CHANGELOG=$(mktemp)
|
|
||||||
|
|
||||||
cat > "$TEMP_CHANGELOG" << EOF
|
|
||||||
dms-git ($NEW_VERSION) nightly; urgency=medium
|
|
||||||
|
|
||||||
* Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)
|
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Prepend to existing changelog
|
|
||||||
cat "$CHANGELOG_FILE" >> "$TEMP_CHANGELOG"
|
|
||||||
mv "$TEMP_CHANGELOG" "$CHANGELOG_FILE"
|
|
||||||
|
|
||||||
echo "✓ Updated Debian changelog: $CURRENT_VERSION → $NEW_VERSION"
|
|
||||||
else
|
|
||||||
echo "✓ Debian changelog already at version $NEW_VERSION"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Update dms stable version
|
- name: Update dms stable version
|
||||||
if: steps.packages.outputs.version != ''
|
if: steps.packages.outputs.version != ''
|
||||||
@@ -212,13 +228,17 @@ jobs:
|
|||||||
VERSION_NO_V="${VERSION#v}"
|
VERSION_NO_V="${VERSION#v}"
|
||||||
echo "Updating packaging to version $VERSION_NO_V"
|
echo "Updating packaging to version $VERSION_NO_V"
|
||||||
|
|
||||||
# Update openSUSE dms spec (stable only)
|
|
||||||
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
||||||
|
|
||||||
# Update openSUSE spec changelog
|
# Single changelog entry (full history on OBS website)
|
||||||
DATE_STR=$(date "+%a %b %d %Y")
|
DATE_STR=$(date "+%a %b %d %Y")
|
||||||
CHANGELOG_ENTRY="* $DATE_STR AvengeMedia <maintainer@avengemedia.com> - ${VERSION_NO_V}-1\\n- Update to stable $VERSION release\\n- Bug fixes and improvements"
|
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
|
||||||
sed -i "/%changelog/a\\$CHANGELOG_ENTRY\\n" distro/opensuse/dms.spec
|
{
|
||||||
|
echo "$LOCAL_SPEC_HEAD"
|
||||||
|
echo "%changelog"
|
||||||
|
echo "* $DATE_STR AvengeMedia <maintainer@avengemedia.com> - ${VERSION_NO_V}-1"
|
||||||
|
echo "- Update to stable $VERSION release"
|
||||||
|
} > distro/opensuse/dms.spec
|
||||||
|
|
||||||
# Update Debian _service files (both tar_scm and download_url formats)
|
# Update Debian _service files (both tar_scm and download_url formats)
|
||||||
for service in distro/debian/*/_service; do
|
for service in distro/debian/*/_service; do
|
||||||
@@ -232,31 +252,23 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Update Debian changelog for dms stable
|
# Update Debian changelog for dms stable (single entry, history on OBS website)
|
||||||
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
||||||
CHANGELOG_DATE=$(date -R)
|
CHANGELOG_DATE=$(date -R)
|
||||||
TEMP_CHANGELOG=$(mktemp)
|
{
|
||||||
|
echo "dms ($VERSION_NO_V) stable; urgency=medium"
|
||||||
cat > "$TEMP_CHANGELOG" << EOF
|
echo ""
|
||||||
dms ($VERSION_NO_V) stable; urgency=medium
|
echo " * Update to $VERSION stable release"
|
||||||
|
echo ""
|
||||||
* Update to $VERSION stable release
|
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||||
* Bug fixes and improvements
|
} > "distro/debian/dms/debian/changelog"
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat "distro/debian/dms/debian/changelog" >> "$TEMP_CHANGELOG"
|
|
||||||
mv "$TEMP_CHANGELOG" "distro/debian/dms/debian/changelog"
|
|
||||||
|
|
||||||
echo "✓ Updated Debian changelog to $VERSION_NO_V"
|
echo "✓ Updated Debian changelog to $VERSION_NO_V"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: "1.24"
|
||||||
|
|
||||||
- name: Install OSC
|
- name: Install OSC
|
||||||
run: |
|
run: |
|
||||||
@@ -276,32 +288,76 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload to OBS
|
- name: Upload to OBS
|
||||||
env:
|
env:
|
||||||
FORCE_REBUILD: ${{ github.event_name == 'workflow_dispatch' && 'true' || '' }}
|
|
||||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
||||||
run: |
|
run: |
|
||||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||||
MESSAGE="Automated update from GitHub Actions"
|
|
||||||
|
|
||||||
|
if [[ -z "$PACKAGES" ]]; then
|
||||||
|
echo "✓ No packages need uploading. All up to date!"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
MESSAGE="Automated update from GitHub Actions"
|
||||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||||
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
|
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGES" == "all" ]]; then
|
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
|
||||||
bash distro/scripts/obs-upload.sh dms "$MESSAGE"
|
# Loop through each package and upload
|
||||||
|
for PKG in $PACKAGES; do
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "Uploading $PKG to OBS..."
|
||||||
|
if [[ -n "$REBUILD_RELEASE" ]]; then
|
||||||
|
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
||||||
|
fi
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
|
if [[ "$PKG" == "dms-git" ]]; then
|
||||||
bash distro/scripts/obs-upload.sh dms-git "Automated git update"
|
bash distro/scripts/obs-upload.sh dms-git "Automated git update"
|
||||||
else
|
else
|
||||||
bash distro/scripts/obs-upload.sh "$PACKAGES" "$MESSAGE"
|
bash distro/scripts/obs-upload.sh "$PKG" "$MESSAGE"
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
echo "### OBS Package Update Complete" >> $GITHUB_STEP_SUMMARY
|
echo "### OBS Package Upload Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **Packages**: ${{ steps.packages.outputs.packages }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||||
|
|
||||||
|
if [[ -z "$PACKAGES" ]]; then
|
||||||
|
echo "**Status:** ✅ All packages up to date (no uploads needed)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "All packages are current. Run completed successfully." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "**Packages Uploaded:**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
for PKG in $PACKAGES; do
|
||||||
|
case "$PKG" in
|
||||||
|
dms)
|
||||||
|
echo "- ✅ **dms** → [View builds](https://build.opensuse.org/package/show/home:AvengeMedia:dms/dms)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
;;
|
||||||
|
dms-git)
|
||||||
|
echo "- ✅ **dms-git** → [View builds](https://build.opensuse.org/package/show/home:AvengeMedia:dms-git/dms-git)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||||
|
echo "**Rebuild Number:** ppa${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||||
echo "- **Version**: ${{ steps.packages.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
echo "**Version:** ${{ steps.packages.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
if [[ "${{ needs.check-updates.outputs.has_updates }}" == "false" ]]; then
|
|
||||||
echo "- **Status**: Skipped (no changes detected)" >> $GITHUB_STEP_SUMMARY
|
echo "Monitor build progress on [OBS project page](https://build.opensuse.org/project/show/home:AvengeMedia)." >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
echo "- **Project**: https://build.opensuse.org/project/show/home:AvengeMedia" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|||||||
+174
-89
@@ -4,15 +4,15 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
package:
|
package:
|
||||||
description: 'Package to upload (dms, dms-git, dms-greeter, or all)'
|
description: "Package to upload (dms, dms-git, dms-greeter, or all)"
|
||||||
required: false
|
required: false
|
||||||
default: 'dms-git'
|
default: "dms-git"
|
||||||
rebuild_release:
|
rebuild_release:
|
||||||
description: 'Release number for rebuilds (e.g., 2, 3, 4 for ppa2, ppa3, ppa4)'
|
description: "Release number for rebuilds (e.g., 2, 3, 4 for ppa2, ppa3, ppa4)"
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ""
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 */3 * * *' # Every 3 hours for dms-git builds
|
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-updates:
|
check-updates:
|
||||||
@@ -32,41 +32,112 @@ jobs:
|
|||||||
- name: Check for updates
|
- name: Check for updates
|
||||||
id: check
|
id: check
|
||||||
run: |
|
run: |
|
||||||
|
# Helper function to check dms-git commit
|
||||||
|
check_dms_git() {
|
||||||
|
local CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
|
||||||
|
local 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 "")
|
||||||
|
local PPA_COMMIT=$(echo "$PPA_VERSION" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$PPA_COMMIT" && "$CURRENT_COMMIT" == "$PPA_COMMIT" ]]; then
|
||||||
|
echo "📋 dms-git: Commit $CURRENT_COMMIT already exists, skipping"
|
||||||
|
return 1 # No update needed
|
||||||
|
else
|
||||||
|
echo "📋 dms-git: New commit $CURRENT_COMMIT (PPA has ${PPA_COMMIT:-none})"
|
||||||
|
return 0 # Update needed
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to check stable package tag
|
||||||
|
check_stable_package() {
|
||||||
|
local PKG="$1"
|
||||||
|
local PPA_NAME="$2"
|
||||||
|
local LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "v\?\([^"]*\)".*/\1/' || echo "")
|
||||||
|
local PPA_VERSION=$(curl -s "https://api.launchpad.net/1.0/~avengemedia/+archive/ubuntu/$PPA_NAME?ws.op=getPublishedSources&source_name=$PKG&status=Published" | grep -oP '"source_package_version":\s*"\K[^"]+' | head -1 || echo "")
|
||||||
|
local PPA_BASE_VERSION=$(echo "$PPA_VERSION" | sed 's/ppa[0-9]*$//')
|
||||||
|
|
||||||
|
if [[ -n "$LATEST_TAG" && "$LATEST_TAG" == "$PPA_BASE_VERSION" ]]; then
|
||||||
|
echo "📋 $PKG: Tag $LATEST_TAG already exists, skipping"
|
||||||
|
return 1 # No update needed
|
||||||
|
else
|
||||||
|
echo "📋 $PKG: New tag ${LATEST_TAG:-unknown} (PPA has ${PPA_BASE_VERSION:-none})"
|
||||||
|
return 0 # Update needed
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main logic
|
||||||
|
REBUILD="${{ github.event.inputs.rebuild_release }}"
|
||||||
|
|
||||||
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||||
|
# Scheduled run - check dms-git only
|
||||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||||
echo "Checking if dms-git source has changed..."
|
if check_dms_git; then
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
# Get current commit hash (8 chars to match changelog format)
|
else
|
||||||
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 "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
|
fi
|
||||||
|
|
||||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
# Manual workflow trigger
|
||||||
|
PKG="${{ github.event.inputs.package }}"
|
||||||
|
|
||||||
|
if [[ -n "$REBUILD" ]]; then
|
||||||
|
# Rebuild requested - always proceed
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
echo "🔄 Manual rebuild requested: $PKG (ppa$REBUILD)"
|
||||||
|
|
||||||
|
elif [[ "$PKG" == "all" ]]; then
|
||||||
|
# Check each package and build list of those needing updates
|
||||||
|
PACKAGES_TO_UPDATE=()
|
||||||
|
check_dms_git && PACKAGES_TO_UPDATE+=("dms-git")
|
||||||
|
check_stable_package "dms" "dms" && PACKAGES_TO_UPDATE+=("dms")
|
||||||
|
check_stable_package "dms-greeter" "danklinux" && PACKAGES_TO_UPDATE+=("dms-greeter")
|
||||||
|
|
||||||
|
if [[ ${#PACKAGES_TO_UPDATE[@]} -gt 0 ]]; then
|
||||||
|
echo "packages=${PACKAGES_TO_UPDATE[*]}" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "✓ Packages to update: ${PACKAGES_TO_UPDATE[*]}"
|
||||||
else
|
else
|
||||||
|
echo "packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "✓ All packages up to date"
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "$PKG" == "dms-git" ]]; then
|
||||||
|
if check_dms_git; then
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "$PKG" == "dms" ]]; then
|
||||||
|
if check_stable_package "dms" "dms"; then
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "$PKG" == "dms-greeter" ]]; then
|
||||||
|
if check_stable_package "dms-greeter" "danklinux"; then
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
# Unknown package - proceed anyway
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Manual trigger: $PKG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Fallback
|
||||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
@@ -75,9 +146,7 @@ jobs:
|
|||||||
name: Upload to PPA
|
name: Upload to PPA
|
||||||
needs: check-updates
|
needs: check-updates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: |
|
if: needs.check-updates.outputs.has_updates == 'true'
|
||||||
github.event_name == 'workflow_dispatch' ||
|
|
||||||
needs.check-updates.outputs.has_updates == 'true'
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -88,7 +157,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: "1.24"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
@@ -114,81 +183,97 @@ jobs:
|
|||||||
- name: Determine packages to upload
|
- name: Determine packages to upload
|
||||||
id: packages
|
id: packages
|
||||||
run: |
|
run: |
|
||||||
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
# Use packages determined by check-updates job
|
||||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||||
echo "Triggered by schedule: uploading git package"
|
echo "Triggered by schedule: uploading git package"
|
||||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
echo "Manual trigger: ${{ needs.check-updates.outputs.packages }}"
|
||||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
|
||||||
else
|
|
||||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload to PPA
|
- name: Upload to PPA
|
||||||
env:
|
|
||||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
|
||||||
run: |
|
run: |
|
||||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||||
|
REBUILD_RELEASE="${{ github.event.inputs.rebuild_release }}"
|
||||||
|
|
||||||
# Export to ensure it's available to subprocesses
|
if [[ -z "$PACKAGES" ]]; then
|
||||||
if [ -n "$REBUILD_RELEASE" ]; then
|
echo "✓ No packages need uploading. All up to date!"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Export REBUILD_RELEASE so ppa-build.sh can use it
|
||||||
|
if [[ -n "$REBUILD_RELEASE" ]]; then
|
||||||
export REBUILD_RELEASE
|
export REBUILD_RELEASE
|
||||||
echo "✓ Using rebuild release number: ppa$REBUILD_RELEASE"
|
echo "✓ Using rebuild release number: ppa$REBUILD_RELEASE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGES" == "all" ]]; then
|
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
|
||||||
|
# Loop through each package and upload
|
||||||
|
for PKG in $PACKAGES; do
|
||||||
|
# Map package to PPA name
|
||||||
|
case "$PKG" in
|
||||||
|
dms)
|
||||||
|
PPA_NAME="dms"
|
||||||
|
;;
|
||||||
|
dms-git)
|
||||||
|
PPA_NAME="dms-git"
|
||||||
|
;;
|
||||||
|
dms-greeter)
|
||||||
|
PPA_NAME="danklinux"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "⚠️ Unknown package: $PKG, skipping"
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo ""
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo "Uploading dms to PPA..."
|
echo "Uploading $PKG to PPA $PPA_NAME..."
|
||||||
if [ -n "$REBUILD_RELEASE" ]; then
|
if [[ -n "$REBUILD_RELEASE" ]]; then
|
||||||
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
||||||
fi
|
fi
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
REBUILD_RELEASE="$REBUILD_RELEASE" bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms" dms questing
|
bash distro/scripts/ppa-upload.sh "$PKG" "$PPA_NAME" questing ${REBUILD_RELEASE:+"$REBUILD_RELEASE"}
|
||||||
|
done
|
||||||
echo ""
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo "Uploading dms-git to PPA..."
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
REBUILD_RELEASE="$REBUILD_RELEASE" bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-git" dms-git questing
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo "Uploading dms-greeter to PPA..."
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
REBUILD_RELEASE="$REBUILD_RELEASE" bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-greeter" danklinux questing
|
|
||||||
else
|
|
||||||
PPA_NAME="$PACKAGES"
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo "Uploading $PACKAGES to PPA..."
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
REBUILD_RELEASE="$REBUILD_RELEASE" bash distro/scripts/ppa-upload.sh "distro/ubuntu/$PACKAGES" "$PPA_NAME" questing
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
echo "### PPA Package Upload Complete" >> $GITHUB_STEP_SUMMARY
|
echo "### PPA Package Upload Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $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 }}"
|
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||||
if [[ "$PACKAGES" == "all" ]]; then
|
|
||||||
echo "- **PPA dms**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
|
if [[ -z "$PACKAGES" ]]; then
|
||||||
echo "- **PPA dms-git**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
|
echo "**Status:** ✅ All packages up to date (no uploads needed)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **PPA danklinux**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
elif [[ "$PACKAGES" == "dms" ]]; then
|
echo "All packages are current. Run will complete successfully." >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
|
else
|
||||||
elif [[ "$PACKAGES" == "dms-git" ]]; then
|
echo "**Packages Uploaded:**" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
elif [[ "$PACKAGES" == "dms-greeter" ]]; then
|
|
||||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
|
for PKG in $PACKAGES; do
|
||||||
|
case "$PKG" in
|
||||||
|
dms)
|
||||||
|
echo "- ✅ **dms** → [View builds](https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
;;
|
||||||
|
dms-git)
|
||||||
|
echo "- ✅ **dms-git** → [View builds](https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
;;
|
||||||
|
dms-greeter)
|
||||||
|
echo "- ✅ **dms-greeter** → [View builds](https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||||
|
echo "**Rebuild Number:** ppa${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
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
|
echo "Builds will appear once Launchpad processes the uploads." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
name: Update stable branch
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-stable:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Push to stable branch
|
||||||
|
run: git push origin HEAD:refs/heads/stable --force
|
||||||
@@ -104,12 +104,6 @@ go.work.sum
|
|||||||
|
|
||||||
bin/
|
bin/
|
||||||
|
|
||||||
# Extracted source trees in Ubuntu package directories
|
|
||||||
distro/ubuntu/*/dms-git-repo/
|
|
||||||
distro/ubuntu/*/DankMaterialShell-*/
|
|
||||||
distro/ubuntu/danklinux/*/dsearch-*/
|
|
||||||
distro/ubuntu/danklinux/*/dgop-*/
|
|
||||||
|
|
||||||
# direnv
|
# direnv
|
||||||
.envrc
|
.envrc
|
||||||
.direnv/
|
.direnv/
|
||||||
|
|||||||
@@ -2,6 +2,15 @@ repos:
|
|||||||
- repo: https://github.com/golangci/golangci-lint
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
rev: v2.6.2
|
rev: v2.6.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: golangci-lint-full
|
|
||||||
- id: golangci-lint-fmt
|
- id: golangci-lint-fmt
|
||||||
|
require_serial: true
|
||||||
|
- id: golangci-lint-full
|
||||||
- id: golangci-lint-config-verify
|
- id: golangci-lint-config-verify
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: go-test
|
||||||
|
name: go test
|
||||||
|
entry: go test ./...
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
types: [go]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
_ "golang.org/x/image/bmp"
|
_ "golang.org/x/image/bmp"
|
||||||
_ "golang.org/x/image/tiff"
|
_ "golang.org/x/image/tiff"
|
||||||
|
"hash/fnv"
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
@@ -39,6 +40,7 @@ type Entry struct {
|
|||||||
Size int
|
Size int
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
IsImage bool
|
IsImage bool
|
||||||
|
Hash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func Store(data []byte, mimeType string) error {
|
func Store(data []byte, mimeType string) error {
|
||||||
@@ -70,6 +72,7 @@ func StoreWithConfig(data []byte, mimeType string, cfg StoreConfig) error {
|
|||||||
Size: len(data),
|
Size: len(data),
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
IsImage: IsImageMimeType(mimeType),
|
IsImage: IsImageMimeType(mimeType),
|
||||||
|
Hash: computeHash(data),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -85,7 +88,7 @@ func StoreWithConfig(data []byte, mimeType string, cfg StoreConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deduplicateInTx(b, data); err != nil {
|
if err := deduplicateInTx(b, entry.Hash); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,19 +129,16 @@ func getDBPath() (string, error) {
|
|||||||
return filepath.Join(dbDir, "db"), nil
|
return filepath.Join(dbDir, "db"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deduplicateInTx(b *bolt.Bucket, data []byte) error {
|
func deduplicateInTx(b *bolt.Bucket, hash uint64) error {
|
||||||
c := b.Cursor()
|
c := b.Cursor()
|
||||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||||
entry, err := decodeEntry(v)
|
if extractHash(v) != hash {
|
||||||
if err != nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if bytes.Equal(entry.Data, data) {
|
|
||||||
if err := b.Delete(k); err != nil {
|
if err := b.Delete(k); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,54 +174,30 @@ func encodeEntry(e Entry) ([]byte, error) {
|
|||||||
} else {
|
} else {
|
||||||
buf.WriteByte(0)
|
buf.WriteByte(0)
|
||||||
}
|
}
|
||||||
|
binary.Write(buf, binary.BigEndian, e.Hash)
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeEntry(data []byte) (Entry, error) {
|
|
||||||
buf := bytes.NewReader(data)
|
|
||||||
var e Entry
|
|
||||||
|
|
||||||
binary.Read(buf, binary.BigEndian, &e.ID)
|
|
||||||
|
|
||||||
var dataLen uint32
|
|
||||||
binary.Read(buf, binary.BigEndian, &dataLen)
|
|
||||||
e.Data = make([]byte, dataLen)
|
|
||||||
buf.Read(e.Data)
|
|
||||||
|
|
||||||
var mimeLen uint32
|
|
||||||
binary.Read(buf, binary.BigEndian, &mimeLen)
|
|
||||||
mimeBytes := make([]byte, mimeLen)
|
|
||||||
buf.Read(mimeBytes)
|
|
||||||
e.MimeType = string(mimeBytes)
|
|
||||||
|
|
||||||
var prevLen uint32
|
|
||||||
binary.Read(buf, binary.BigEndian, &prevLen)
|
|
||||||
prevBytes := make([]byte, prevLen)
|
|
||||||
buf.Read(prevBytes)
|
|
||||||
e.Preview = string(prevBytes)
|
|
||||||
|
|
||||||
var size int32
|
|
||||||
binary.Read(buf, binary.BigEndian, &size)
|
|
||||||
e.Size = int(size)
|
|
||||||
|
|
||||||
var timestamp int64
|
|
||||||
binary.Read(buf, binary.BigEndian, ×tamp)
|
|
||||||
e.Timestamp = time.Unix(timestamp, 0)
|
|
||||||
|
|
||||||
var isImage byte
|
|
||||||
binary.Read(buf, binary.BigEndian, &isImage)
|
|
||||||
e.IsImage = isImage == 1
|
|
||||||
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func itob(v uint64) []byte {
|
func itob(v uint64) []byte {
|
||||||
b := make([]byte, 8)
|
b := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(b, v)
|
binary.BigEndian.PutUint64(b, v)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func computeHash(data []byte) uint64 {
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write(data)
|
||||||
|
return h.Sum64()
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractHash(data []byte) uint64 {
|
||||||
|
if len(data) < 8 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint64(data[len(data)-8:])
|
||||||
|
}
|
||||||
|
|
||||||
func textPreview(data []byte) string {
|
func textPreview(data []byte) string {
|
||||||
text := string(data)
|
text := string(data)
|
||||||
text = strings.TrimSpace(text)
|
text = strings.TrimSpace(text)
|
||||||
|
|||||||
@@ -276,4 +276,4 @@ bind = CTRL, Print, exec, dms screenshot full
|
|||||||
bind = ALT, Print, exec, dms screenshot window
|
bind = ALT, Print, exec, dms screenshot window
|
||||||
|
|
||||||
# === System Controls ===
|
# === System Controls ===
|
||||||
bind = $mod SHIFT, P, dpms, off
|
bind = $mod SHIFT, P, dpms, toggle
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
recent-windows {
|
recent-windows {
|
||||||
highlight {
|
highlight {
|
||||||
corner-radius 12
|
corner-radius 12
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
binds {
|
binds {
|
||||||
// === System & Overview ===
|
// === System & Overview ===
|
||||||
Mod+D repeat=false { toggle-overview; }
|
Mod+D repeat=false { toggle-overview; }
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
layout {
|
layout {
|
||||||
background-color "transparent"
|
background-color "transparent"
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
layout {
|
layout {
|
||||||
gaps 4
|
gaps 4
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,64 @@ gestures {
|
|||||||
input {
|
input {
|
||||||
keyboard {
|
keyboard {
|
||||||
xkb {
|
xkb {
|
||||||
|
// You can set rules, model, layout, variant and options.
|
||||||
|
// For more information, see xkeyboard-config(7).
|
||||||
|
|
||||||
|
// For example:
|
||||||
|
// layout "us,ru"
|
||||||
|
// options "grp:win_space_toggle,compose:ralt,ctrl:nocaps"
|
||||||
|
|
||||||
|
// If this section is empty, niri will fetch xkb settings
|
||||||
|
// from org.freedesktop.locale1. You can control these using
|
||||||
|
// localectl set-x11-keymap.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable numlock on startup, omitting this setting disables it.
|
||||||
numlock
|
numlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Next sections include libinput settings.
|
||||||
|
// Omitting settings disables them, or leaves them at their default values.
|
||||||
|
// All commented-out settings here are examples, not defaults.
|
||||||
touchpad {
|
touchpad {
|
||||||
|
// off
|
||||||
|
tap
|
||||||
|
// dwt
|
||||||
|
// dwtp
|
||||||
|
// drag false
|
||||||
|
// drag-lock
|
||||||
|
natural-scroll
|
||||||
|
// accel-speed 0.2
|
||||||
|
// accel-profile "flat"
|
||||||
|
// scroll-method "two-finger"
|
||||||
|
// disabled-on-external-mouse
|
||||||
}
|
}
|
||||||
|
|
||||||
mouse {
|
mouse {
|
||||||
|
// off
|
||||||
|
// natural-scroll
|
||||||
|
// accel-speed 0.2
|
||||||
|
// accel-profile "flat"
|
||||||
|
// scroll-method "no-scroll"
|
||||||
}
|
}
|
||||||
|
|
||||||
trackpoint {
|
trackpoint {
|
||||||
|
// off
|
||||||
|
// natural-scroll
|
||||||
|
// accel-speed 0.2
|
||||||
|
// accel-profile "flat"
|
||||||
|
// scroll-method "on-button-down"
|
||||||
|
// scroll-button 273
|
||||||
|
// scroll-button-lock
|
||||||
|
// middle-emulation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uncomment this to make the mouse warp to the center of newly focused windows.
|
||||||
|
// warp-mouse-to-focus
|
||||||
|
|
||||||
|
// Focus windows and outputs automatically when moving the mouse into them.
|
||||||
|
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
|
||||||
|
// focus-follows-mouse max-scroll-amount="0%"
|
||||||
}
|
}
|
||||||
// You can configure outputs by their name, which you can find
|
// You can configure outputs by their name, which you can find
|
||||||
// by running `niri msg outputs` while inside a niri instance.
|
// by running `niri msg outputs` while inside a niri instance.
|
||||||
|
|||||||
@@ -461,9 +461,16 @@ func (n *NiriProvider) getBindSortPriority(action string) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dmsWarningHeader = `// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
func (n *NiriProvider) generateBindsContent(binds map[string]*overrideBind) string {
|
func (n *NiriProvider) generateBindsContent(binds map[string]*overrideBind) string {
|
||||||
if len(binds) == 0 {
|
if len(binds) == 0 {
|
||||||
return "binds {}\n"
|
return dmsWarningHeader + "binds {}\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
var regularBinds, recentWindowsBinds []*overrideBind
|
var regularBinds, recentWindowsBinds []*overrideBind
|
||||||
@@ -490,6 +497,7 @@ func (n *NiriProvider) generateBindsContent(binds map[string]*overrideBind) stri
|
|||||||
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|
||||||
|
sb.WriteString(dmsWarningHeader)
|
||||||
sb.WriteString("binds {\n")
|
sb.WriteString("binds {\n")
|
||||||
for _, bind := range regularBinds {
|
for _, bind := range regularBinds {
|
||||||
n.writeBindNode(&sb, bind, " ")
|
n.writeBindNode(&sb, bind, " ")
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const testHeader = `// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
func TestNiriProviderName(t *testing.T) {
|
func TestNiriProviderName(t *testing.T) {
|
||||||
provider := NewNiriProvider("")
|
provider := NewNiriProvider("")
|
||||||
if provider.Name() != "niri" {
|
if provider.Name() != "niri" {
|
||||||
@@ -197,7 +204,7 @@ func TestNiriGenerateBindsContent(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty binds",
|
name: "empty binds",
|
||||||
binds: map[string]*overrideBind{},
|
binds: map[string]*overrideBind{},
|
||||||
expected: "binds {}\n",
|
expected: testHeader + "binds {}\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "simple spawn bind",
|
name: "simple spawn bind",
|
||||||
@@ -208,7 +215,7 @@ func TestNiriGenerateBindsContent(t *testing.T) {
|
|||||||
Description: "Open Terminal",
|
Description: "Open Terminal",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
|
Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -222,7 +229,7 @@ func TestNiriGenerateBindsContent(t *testing.T) {
|
|||||||
Description: "Application Launcher",
|
Description: "Application Launcher",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
Mod+Space hotkey-overlay-title="Application Launcher" { spawn "dms" "ipc" "call" "spotlight" "toggle"; }
|
Mod+Space hotkey-overlay-title="Application Launcher" { spawn "dms" "ipc" "call" "spotlight" "toggle"; }
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -236,7 +243,7 @@ func TestNiriGenerateBindsContent(t *testing.T) {
|
|||||||
Options: map[string]any{"allow-when-locked": true},
|
Options: map[string]any{"allow-when-locked": true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
XF86AudioMute allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "mute"; }
|
XF86AudioMute allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "mute"; }
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -250,7 +257,7 @@ func TestNiriGenerateBindsContent(t *testing.T) {
|
|||||||
Description: "Close Window",
|
Description: "Close Window",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
Mod+Q hotkey-overlay-title="Close Window" { close-window; }
|
Mod+Q hotkey-overlay-title="Close Window" { close-window; }
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -263,7 +270,7 @@ func TestNiriGenerateBindsContent(t *testing.T) {
|
|||||||
Action: "next-window",
|
Action: "next-window",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
}
|
}
|
||||||
|
|
||||||
recent-windows {
|
recent-windows {
|
||||||
@@ -415,7 +422,7 @@ func TestNiriGenerateBindsContentNumericArgs(t *testing.T) {
|
|||||||
Description: "Focus Workspace 1",
|
Description: "Focus Workspace 1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
Mod+1 hotkey-overlay-title="Focus Workspace 1" { focus-workspace 1; }
|
Mod+1 hotkey-overlay-title="Focus Workspace 1" { focus-workspace 1; }
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -429,7 +436,7 @@ func TestNiriGenerateBindsContentNumericArgs(t *testing.T) {
|
|||||||
Description: "Focus Workspace 10",
|
Description: "Focus Workspace 10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
Mod+0 hotkey-overlay-title="Focus Workspace 10" { focus-workspace 10; }
|
Mod+0 hotkey-overlay-title="Focus Workspace 10" { focus-workspace 10; }
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -443,7 +450,7 @@ func TestNiriGenerateBindsContentNumericArgs(t *testing.T) {
|
|||||||
Description: "Adjust Column Width -10%",
|
Description: "Adjust Column Width -10%",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
Super+Minus hotkey-overlay-title="Adjust Column Width -10%" { set-column-width "-10%"; }
|
Super+Minus hotkey-overlay-title="Adjust Column Width -10%" { set-column-width "-10%"; }
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -457,7 +464,7 @@ func TestNiriGenerateBindsContentNumericArgs(t *testing.T) {
|
|||||||
Description: "Adjust Column Width +10%",
|
Description: "Adjust Column Width +10%",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: `binds {
|
expected: testHeader + `binds {
|
||||||
Super+Equal hotkey-overlay-title="Adjust Column Width +10%" { set-column-width "+10%"; }
|
Super+Equal hotkey-overlay-title="Adjust Column Width +10%" { set-column-width "+10%"; }
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -486,7 +493,7 @@ func TestNiriGenerateActionWithUnquotedPercentArg(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content := provider.generateBindsContent(binds)
|
content := provider.generateBindsContent(binds)
|
||||||
expected := `binds {
|
expected := testHeader + `binds {
|
||||||
Super+Equal hotkey-overlay-title="Adjust Window Height +10%" { set-window-height "+10%"; }
|
Super+Equal hotkey-overlay-title="Adjust Window Height +10%" { set-window-height "+10%"; }
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@@ -507,7 +514,7 @@ func TestNiriGenerateSpawnWithNumericArgs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content := provider.generateBindsContent(binds)
|
content := provider.generateBindsContent(binds)
|
||||||
expected := `binds {
|
expected := testHeader + `binds {
|
||||||
XF86AudioLowerVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "decrement" "3"; }
|
XF86AudioLowerVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "decrement" "3"; }
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@@ -528,7 +535,7 @@ func TestNiriGenerateSpawnNumericArgFromCLI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content := provider.generateBindsContent(binds)
|
content := provider.generateBindsContent(binds)
|
||||||
expected := `binds {
|
expected := testHeader + `binds {
|
||||||
XF86AudioLowerVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "decrement" "3"; }
|
XF86AudioLowerVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "decrement" "3"; }
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ func (i *ZwlrOutputManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy == nil {
|
if proxy == nil || proxy.IsZombie() {
|
||||||
head := &ZwlrOutputHeadV1{}
|
head := &ZwlrOutputHeadV1{}
|
||||||
head.SetContext(i.Context())
|
head.SetContext(i.Context())
|
||||||
head.SetID(objectID)
|
head.SetID(objectID)
|
||||||
@@ -723,7 +723,7 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy == nil {
|
if proxy == nil || proxy.IsZombie() {
|
||||||
mode := &ZwlrOutputModeV1{}
|
mode := &ZwlrOutputModeV1{}
|
||||||
mode.SetContext(i.Context())
|
mode.SetContext(i.Context())
|
||||||
mode.SetID(objectID)
|
mode.SetID(objectID)
|
||||||
@@ -761,8 +761,8 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy == nil {
|
if proxy == nil || proxy.IsZombie() {
|
||||||
// Mode not yet registered, create it
|
// Mode not yet registered or zombie, create fresh
|
||||||
mode := &ZwlrOutputModeV1{}
|
mode := &ZwlrOutputModeV1{}
|
||||||
mode.SetContext(i.Context())
|
mode.SetContext(i.Context())
|
||||||
mode.SetID(objectID)
|
mode.SetID(objectID)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
_ "golang.org/x/image/bmp"
|
_ "golang.org/x/image/bmp"
|
||||||
_ "golang.org/x/image/tiff"
|
_ "golang.org/x/image/tiff"
|
||||||
|
"hash/fnv"
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
|
|
||||||
@@ -69,6 +70,10 @@ func NewManager(wlCtx wlcontext.WaylandContext, config Config) (*Manager, error)
|
|||||||
}
|
}
|
||||||
m.db = db
|
m.db = db
|
||||||
|
|
||||||
|
if err := m.migrateHashes(); err != nil {
|
||||||
|
log.Errorf("Failed to migrate hashes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if config.ClearAtStartup {
|
if config.ClearAtStartup {
|
||||||
if err := m.clearHistoryInternal(); err != nil {
|
if err := m.clearHistoryInternal(); err != nil {
|
||||||
log.Errorf("Failed to clear history at startup: %v", err)
|
log.Errorf("Failed to clear history at startup: %v", err)
|
||||||
@@ -244,7 +249,30 @@ func (m *Manager) setupDataDeviceSync() {
|
|||||||
|
|
||||||
m.mimeTypes = mimes
|
m.mimeTypes = mimes
|
||||||
|
|
||||||
go m.storeCurrentClipboard()
|
if len(mimes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preferredMime := m.selectMimeType(mimes)
|
||||||
|
if preferredMime == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
typedOffer := offer.(*ext_data_control.ExtDataControlOfferV1)
|
||||||
|
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := typedOffer.Receive(preferredMime, int(w.Fd())); err != nil {
|
||||||
|
r.Close()
|
||||||
|
w.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
go m.readAndStore(r, preferredMime)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := dataMgr.GetDataDeviceWithProxy(dataDevice, m.seat); err != nil {
|
if err := dataMgr.GetDataDeviceWithProxy(dataDevice, m.seat); err != nil {
|
||||||
@@ -262,60 +290,55 @@ func (m *Manager) setupDataDeviceSync() {
|
|||||||
log.Info("Data device setup complete")
|
log.Info("Data device setup complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) storeCurrentClipboard() {
|
func (m *Manager) readAndStore(r *os.File, mimeType string) {
|
||||||
if m.currentOffer == nil {
|
defer r.Close()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := m.getConfig()
|
cfg := m.getConfig()
|
||||||
|
|
||||||
offer := m.currentOffer.(*ext_data_control.ExtDataControlOfferV1)
|
done := make(chan []byte, 1)
|
||||||
|
go func() {
|
||||||
|
data, _ := io.ReadAll(r)
|
||||||
|
done <- data
|
||||||
|
}()
|
||||||
|
|
||||||
if len(m.mimeTypes) == 0 {
|
var data []byte
|
||||||
|
select {
|
||||||
|
case data = <-done:
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
allData := make(map[string][]byte)
|
|
||||||
var orderedMimes []string
|
|
||||||
|
|
||||||
for _, mime := range m.mimeTypes {
|
|
||||||
data, err := m.receiveData(offer, mime)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(data) == 0 || int64(len(data)) > cfg.MaxEntrySize {
|
if len(data) == 0 || int64(len(data)) > cfg.MaxEntrySize {
|
||||||
continue
|
|
||||||
}
|
|
||||||
allData[mime] = data
|
|
||||||
orderedMimes = append(orderedMimes, mime)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(allData) == 0 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
preferredMime := m.selectMimeType(orderedMimes)
|
|
||||||
if preferredMime == "" {
|
|
||||||
preferredMime = orderedMimes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
data := allData[preferredMime]
|
|
||||||
if len(bytes.TrimSpace(data)) == 0 {
|
if len(bytes.TrimSpace(data)) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.DisableHistory && m.db != nil {
|
if !cfg.DisableHistory && m.db != nil {
|
||||||
|
m.storeClipboardEntry(data, mimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.DisablePersist {
|
||||||
|
m.persistClipboard([]string{mimeType}, map[string][]byte{mimeType: data})
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateState()
|
||||||
|
m.notifySubscribers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) storeClipboardEntry(data []byte, mimeType string) {
|
||||||
entry := Entry{
|
entry := Entry{
|
||||||
Data: data,
|
Data: data,
|
||||||
MimeType: preferredMime,
|
MimeType: mimeType,
|
||||||
Size: len(data),
|
Size: len(data),
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
IsImage: m.isImageMimeType(preferredMime),
|
IsImage: m.isImageMimeType(mimeType),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case entry.IsImage:
|
case entry.IsImage:
|
||||||
entry.Preview = m.imagePreview(data, preferredMime)
|
entry.Preview = m.imagePreview(data, mimeType)
|
||||||
default:
|
default:
|
||||||
entry.Preview = m.textPreview(data)
|
entry.Preview = m.textPreview(data)
|
||||||
}
|
}
|
||||||
@@ -325,14 +348,6 @@ func (m *Manager) storeCurrentClipboard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.DisablePersist {
|
|
||||||
m.persistClipboard(orderedMimes, allData)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.updateState()
|
|
||||||
m.notifySubscribers()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) persistClipboard(mimeTypes []string, data map[string][]byte) {
|
func (m *Manager) persistClipboard(mimeTypes []string, data map[string][]byte) {
|
||||||
m.persistMutex.Lock()
|
m.persistMutex.Lock()
|
||||||
m.persistMimeTypes = mimeTypes
|
m.persistMimeTypes = mimeTypes
|
||||||
@@ -415,14 +430,34 @@ func (m *Manager) takePersistOwnership() {
|
|||||||
m.ownerLock.Unlock()
|
m.ownerLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) releaseOwnership() {
|
||||||
|
m.ownerLock.Lock()
|
||||||
|
m.isOwner = false
|
||||||
|
m.ownerLock.Unlock()
|
||||||
|
|
||||||
|
m.persistMutex.Lock()
|
||||||
|
m.persistData = nil
|
||||||
|
m.persistMimeTypes = nil
|
||||||
|
m.persistMutex.Unlock()
|
||||||
|
|
||||||
|
if m.currentSource != nil {
|
||||||
|
source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||||
|
source.Destroy()
|
||||||
|
m.currentSource = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) storeEntry(entry Entry) error {
|
func (m *Manager) storeEntry(entry Entry) error {
|
||||||
if m.db == nil {
|
if m.db == nil {
|
||||||
return fmt.Errorf("database not available")
|
return fmt.Errorf("database not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry.Hash = computeHash(entry.Data)
|
||||||
|
|
||||||
return m.db.Update(func(tx *bolt.Tx) error {
|
return m.db.Update(func(tx *bolt.Tx) error {
|
||||||
b := tx.Bucket([]byte("clipboard"))
|
b := tx.Bucket([]byte("clipboard"))
|
||||||
|
|
||||||
if err := m.deduplicateInTx(b, entry.Data); err != nil {
|
if err := m.deduplicateInTx(b, entry.Hash); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,19 +481,16 @@ func (m *Manager) storeEntry(entry Entry) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) deduplicateInTx(b *bolt.Bucket, data []byte) error {
|
func (m *Manager) deduplicateInTx(b *bolt.Bucket, hash uint64) error {
|
||||||
c := b.Cursor()
|
c := b.Cursor()
|
||||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||||
entry, err := decodeEntry(v)
|
if extractHash(v) != hash {
|
||||||
if err != nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if bytes.Equal(entry.Data, data) {
|
|
||||||
if err := b.Delete(k); err != nil {
|
if err := b.Delete(k); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,6 +526,7 @@ func encodeEntry(e Entry) ([]byte, error) {
|
|||||||
} else {
|
} else {
|
||||||
buf.WriteByte(0)
|
buf.WriteByte(0)
|
||||||
}
|
}
|
||||||
|
binary.Write(buf, binary.BigEndian, e.Hash)
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
@@ -533,6 +566,10 @@ func decodeEntry(data []byte) (Entry, error) {
|
|||||||
binary.Read(buf, binary.BigEndian, &isImage)
|
binary.Read(buf, binary.BigEndian, &isImage)
|
||||||
e.IsImage = isImage == 1
|
e.IsImage = isImage == 1
|
||||||
|
|
||||||
|
if buf.Len() >= 8 {
|
||||||
|
binary.Read(buf, binary.BigEndian, &e.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,6 +579,19 @@ func itob(v uint64) []byte {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func computeHash(data []byte) uint64 {
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write(data)
|
||||||
|
return h.Sum64()
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractHash(data []byte) uint64 {
|
||||||
|
if len(data) < 8 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint64(data[len(data)-8:])
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) selectMimeType(mimes []string) string {
|
func (m *Manager) selectMimeType(mimes []string) string {
|
||||||
preferredTypes := []string{
|
preferredTypes := []string{
|
||||||
"text/plain;charset=utf-8",
|
"text/plain;charset=utf-8",
|
||||||
@@ -551,8 +601,9 @@ func (m *Manager) selectMimeType(mimes []string) string {
|
|||||||
"TEXT",
|
"TEXT",
|
||||||
"image/png",
|
"image/png",
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
"image/bmp",
|
|
||||||
"image/gif",
|
"image/gif",
|
||||||
|
"image/bmp",
|
||||||
|
"image/tiff",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pref := range preferredTypes {
|
for _, pref := range preferredTypes {
|
||||||
@@ -563,6 +614,10 @@ func (m *Manager) selectMimeType(mimes []string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(mimes) > 0 {
|
||||||
|
return mimes[0]
|
||||||
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,37 +625,6 @@ func (m *Manager) isImageMimeType(mime string) bool {
|
|||||||
return strings.HasPrefix(mime, "image/")
|
return strings.HasPrefix(mime, "image/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) receiveData(offer *ext_data_control.ExtDataControlOfferV1, mimeType string) ([]byte, error) {
|
|
||||||
r, w, err := os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
if err := offer.Receive(mimeType, int(w.Fd())); err != nil {
|
|
||||||
w.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w.Close()
|
|
||||||
|
|
||||||
type result struct {
|
|
||||||
data []byte
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
done := make(chan result, 1)
|
|
||||||
go func() {
|
|
||||||
data, err := io.ReadAll(r)
|
|
||||||
done <- result{data, err}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case res := <-done:
|
|
||||||
return res.data, res.err
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
return nil, fmt.Errorf("timeout reading clipboard data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) textPreview(data []byte) string {
|
func (m *Manager) textPreview(data []byte) string {
|
||||||
text := string(data)
|
text := string(data)
|
||||||
text = strings.TrimSpace(text)
|
text = strings.TrimSpace(text)
|
||||||
@@ -1052,6 +1076,79 @@ func (m *Manager) clearOldEntries(days int) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) migrateHashes() error {
|
||||||
|
if m.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var needsMigration bool
|
||||||
|
if err := m.db.View(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("clipboard"))
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c := b.Cursor()
|
||||||
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
|
if extractHash(v) == 0 {
|
||||||
|
needsMigration = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !needsMigration {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Migrating clipboard entries to add hashes...")
|
||||||
|
|
||||||
|
return m.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("clipboard"))
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var updates []struct {
|
||||||
|
key []byte
|
||||||
|
entry Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
c := b.Cursor()
|
||||||
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
|
entry, err := decodeEntry(v)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if entry.Hash != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entry.Hash = computeHash(entry.Data)
|
||||||
|
keyCopy := make([]byte, len(k))
|
||||||
|
copy(keyCopy, k)
|
||||||
|
updates = append(updates, struct {
|
||||||
|
key []byte
|
||||||
|
entry Entry
|
||||||
|
}{keyCopy, entry})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range updates {
|
||||||
|
encoded, err := encodeEntry(u.entry)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := b.Put(u.key, encoded); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Migrated %d clipboard entries", len(updates))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) Search(params SearchParams) SearchResult {
|
func (m *Manager) Search(params SearchParams) SearchResult {
|
||||||
if m.db == nil {
|
if m.db == nil {
|
||||||
return SearchResult{}
|
return SearchResult{}
|
||||||
@@ -1224,23 +1321,6 @@ func (m *Manager) applyConfigChange(newCfg Config) {
|
|||||||
m.notifySubscribers()
|
m.notifySubscribers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) releaseOwnership() {
|
|
||||||
m.ownerLock.Lock()
|
|
||||||
m.isOwner = false
|
|
||||||
m.ownerLock.Unlock()
|
|
||||||
|
|
||||||
m.persistMutex.Lock()
|
|
||||||
m.persistData = nil
|
|
||||||
m.persistMimeTypes = nil
|
|
||||||
m.persistMutex.Unlock()
|
|
||||||
|
|
||||||
if m.currentSource != nil {
|
|
||||||
source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
|
||||||
source.Destroy()
|
|
||||||
m.currentSource = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) StoreData(data []byte, mimeType string) error {
|
func (m *Manager) StoreData(data []byte, mimeType string) error {
|
||||||
cfg := m.getConfig()
|
cfg := m.getConfig()
|
||||||
|
|
||||||
|
|||||||
@@ -409,8 +409,10 @@ func TestSelectMimeType(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{[]string{"text/plain;charset=utf-8", "text/html"}, "text/plain;charset=utf-8"},
|
{[]string{"text/plain;charset=utf-8", "text/html"}, "text/plain;charset=utf-8"},
|
||||||
{[]string{"text/html", "text/plain"}, "text/plain"},
|
{[]string{"text/html", "text/plain"}, "text/plain"},
|
||||||
{[]string{"application/json", "image/png"}, "image/png"},
|
{[]string{"text/html", "image/png"}, "image/png"},
|
||||||
{[]string{"application/json", "application/xml"}, ""},
|
{[]string{"image/png", "image/jpeg"}, "image/png"},
|
||||||
|
{[]string{"image/png"}, "image/png"},
|
||||||
|
{[]string{"application/octet-stream"}, "application/octet-stream"},
|
||||||
{[]string{}, ""},
|
{[]string{}, ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ type Entry struct {
|
|||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
IsImage bool `json:"isImage"`
|
IsImage bool `json:"isImage"`
|
||||||
|
Hash uint64 `json:"hash,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
@@ -140,6 +141,7 @@ type Manager struct {
|
|||||||
|
|
||||||
isOwner bool
|
isOwner bool
|
||||||
ownerLock sync.Mutex
|
ownerLock sync.Mutex
|
||||||
|
|
||||||
initialized bool
|
initialized bool
|
||||||
|
|
||||||
alive bool
|
alive bool
|
||||||
|
|||||||
@@ -11,4 +11,9 @@ const (
|
|||||||
dbusPortalSettingsInterface = "org.freedesktop.portal.Settings"
|
dbusPortalSettingsInterface = "org.freedesktop.portal.Settings"
|
||||||
|
|
||||||
dbusPropsInterface = "org.freedesktop.DBus.Properties"
|
dbusPropsInterface = "org.freedesktop.DBus.Properties"
|
||||||
|
|
||||||
|
dbusScreensaverName = "org.freedesktop.ScreenSaver"
|
||||||
|
dbusScreensaverPath = "/ScreenSaver"
|
||||||
|
dbusScreensaverPath2 = "/org/freedesktop/ScreenSaver"
|
||||||
|
dbusScreensaverInterface = "org.freedesktop.ScreenSaver"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ func NewManager() (*Manager, error) {
|
|||||||
state: &FreedeskState{
|
state: &FreedeskState{
|
||||||
Accounts: AccountsState{},
|
Accounts: AccountsState{},
|
||||||
Settings: SettingsState{},
|
Settings: SettingsState{},
|
||||||
|
Screensaver: ScreensaverState{},
|
||||||
},
|
},
|
||||||
stateMutex: sync.RWMutex{},
|
stateMutex: sync.RWMutex{},
|
||||||
systemConn: systemConn,
|
systemConn: systemConn,
|
||||||
@@ -33,6 +34,7 @@ func NewManager() (*Manager, error) {
|
|||||||
|
|
||||||
m.initializeAccounts()
|
m.initializeAccounts()
|
||||||
m.initializeSettings()
|
m.initializeSettings()
|
||||||
|
m.initializeScreensaver()
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,9 +29,24 @@ type SettingsState struct {
|
|||||||
ColorScheme uint32 `json:"colorScheme"`
|
ColorScheme uint32 `json:"colorScheme"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScreensaverInhibitor struct {
|
||||||
|
Cookie uint32 `json:"cookie"`
|
||||||
|
AppName string `json:"appName"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
Peer string `json:"peer"`
|
||||||
|
StartTime int64 `json:"startTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreensaverState struct {
|
||||||
|
Available bool `json:"available"`
|
||||||
|
Inhibited bool `json:"inhibited"`
|
||||||
|
Inhibitors []ScreensaverInhibitor `json:"inhibitors"`
|
||||||
|
}
|
||||||
|
|
||||||
type FreedeskState struct {
|
type FreedeskState struct {
|
||||||
Accounts AccountsState `json:"accounts"`
|
Accounts AccountsState `json:"accounts"`
|
||||||
Settings SettingsState `json:"settings"`
|
Settings SettingsState `json:"settings"`
|
||||||
|
Screensaver ScreensaverState `json:"screensaver"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@@ -43,4 +58,6 @@ type Manager struct {
|
|||||||
settingsObj dbus.BusObject
|
settingsObj dbus.BusObject
|
||||||
currentUID uint64
|
currentUID uint64
|
||||||
subscribers syncmap.Map[string, chan FreedeskState]
|
subscribers syncmap.Map[string, chan FreedeskState]
|
||||||
|
screensaverSubscribers syncmap.Map[string, chan ScreensaverState]
|
||||||
|
screensaverCookieCounter uint32
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -880,22 +880,18 @@ func (b *NetworkManagerBackend) ImportVPN(filePath string, name string) (*VPNImp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string) (*VPNImportResult, error) {
|
func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string) (*VPNImportResult, error) {
|
||||||
args := []string{"connection", "import", "type", "openvpn", "file", filePath}
|
vpnTypes := []string{"openvpn", "wireguard", "vpnc", "pptp", "l2tp", "openconnect", "strongswan"}
|
||||||
cmd := exec.Command("nmcli", args...)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
|
|
||||||
if err != nil {
|
var output []byte
|
||||||
outputStr := string(output)
|
var err error
|
||||||
if strings.Contains(outputStr, "vpnc") || strings.Contains(outputStr, "unknown connection type") {
|
for _, vpnType := range vpnTypes {
|
||||||
for _, vpnType := range []string{"vpnc", "pptp", "l2tp", "openconnect", "strongswan", "wireguard"} {
|
args := []string{"connection", "import", "type", vpnType, "file", filePath}
|
||||||
args = []string{"connection", "import", "type", vpnType, "file", filePath}
|
cmd := exec.Command("nmcli", args...)
|
||||||
cmd = exec.Command("nmcli", args...)
|
|
||||||
output, err = cmd.CombinedOutput()
|
output, err = cmd.CombinedOutput()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &VPNImportResult{
|
return &VPNImportResult{
|
||||||
@@ -903,7 +899,6 @@ func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string)
|
|||||||
Error: fmt.Sprintf("import failed: %s", strings.TrimSpace(string(output))),
|
Error: fmt.Sprintf("import failed: %s", strings.TrimSpace(string(output))),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
outputStr := string(output)
|
outputStr := string(output)
|
||||||
var connUUID, connName string
|
var connUUID, connName string
|
||||||
|
|||||||
@@ -149,6 +149,15 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "clipboard.") {
|
if strings.HasPrefix(req.Method, "clipboard.") {
|
||||||
|
switch req.Method {
|
||||||
|
case "clipboard.getConfig":
|
||||||
|
cfg := clipboard.LoadConfig()
|
||||||
|
models.Respond(conn, req.ID, cfg)
|
||||||
|
return
|
||||||
|
case "clipboard.setConfig":
|
||||||
|
handleClipboardSetConfig(conn, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
if clipboardManager == nil {
|
if clipboardManager == nil {
|
||||||
models.RespondError(conn, req.ID, "clipboard manager not initialized")
|
models.RespondError(conn, req.ID, "clipboard manager not initialized")
|
||||||
return
|
return
|
||||||
@@ -173,3 +182,36 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleClipboardSetConfig(conn net.Conn, req models.Request) {
|
||||||
|
cfg := clipboard.LoadConfig()
|
||||||
|
|
||||||
|
if v, ok := req.Params["maxHistory"].(float64); ok {
|
||||||
|
cfg.MaxHistory = int(v)
|
||||||
|
}
|
||||||
|
if v, ok := req.Params["maxEntrySize"].(float64); ok {
|
||||||
|
cfg.MaxEntrySize = int64(v)
|
||||||
|
}
|
||||||
|
if v, ok := req.Params["autoClearDays"].(float64); ok {
|
||||||
|
cfg.AutoClearDays = int(v)
|
||||||
|
}
|
||||||
|
if v, ok := req.Params["clearAtStartup"].(bool); ok {
|
||||||
|
cfg.ClearAtStartup = v
|
||||||
|
}
|
||||||
|
if v, ok := req.Params["disabled"].(bool); ok {
|
||||||
|
cfg.Disabled = v
|
||||||
|
}
|
||||||
|
if v, ok := req.Params["disableHistory"].(bool); ok {
|
||||||
|
cfg.DisableHistory = v
|
||||||
|
}
|
||||||
|
if v, ok := req.Params["disablePersist"].(bool); ok {
|
||||||
|
cfg.DisablePersist = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := clipboard.SaveConfig(cfg); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "config updated"})
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APIVersion = 23
|
const APIVersion = 24
|
||||||
|
|
||||||
var CLIVersion = "dev"
|
var CLIVersion = "dev"
|
||||||
|
|
||||||
@@ -702,6 +702,38 @@ func handleSubscribe(conn net.Conn, req models.Request) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldSubscribe("freedesktop.screensaver") && freedesktopManager != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
screensaverChan := freedesktopManager.SubscribeScreensaver(clientID + "-screensaver")
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer freedesktopManager.UnsubscribeScreensaver(clientID + "-screensaver")
|
||||||
|
|
||||||
|
initialState := freedesktopManager.GetScreensaverState()
|
||||||
|
select {
|
||||||
|
case eventChan <- ServiceEvent{Service: "freedesktop.screensaver", Data: initialState}:
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case state, ok := <-screensaverChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case eventChan <- ServiceEvent{Service: "freedesktop.screensaver", Data: state}:
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if shouldSubscribe("gamma") && waylandManager != nil {
|
if shouldSubscribe("gamma") && waylandManager != nil {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
waylandChan := waylandManager.Subscribe(clientID + "-gamma")
|
waylandChan := waylandManager.Subscribe(clientID + "-gamma")
|
||||||
|
|||||||
@@ -103,18 +103,16 @@ func (m *Manager) waylandActor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) allOutputsReady() bool {
|
func (m *Manager) anyOutputReady() bool {
|
||||||
hasOutputs := false
|
anyReady := false
|
||||||
allReady := true
|
|
||||||
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
||||||
hasOutputs = true
|
if out.rampSize > 0 && !out.failed {
|
||||||
if out.rampSize == 0 || out.failed {
|
anyReady = true
|
||||||
allReady = false
|
return false // stop iteration
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return hasOutputs && allReady
|
return anyReady
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) setupDBusMonitor() error {
|
func (m *Manager) setupDBusMonitor() error {
|
||||||
@@ -278,7 +276,8 @@ func (m *Manager) setupControlHandlers(state *outputState, control *wlr_gamma_co
|
|||||||
out.failed = false
|
out.failed = false
|
||||||
out.retryCount = 0
|
out.retryCount = 0
|
||||||
}
|
}
|
||||||
m.applyCurrentTemp()
|
m.lastAppliedTemp = 0
|
||||||
|
m.applyCurrentTemp("gamma_size")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -528,9 +527,10 @@ func (m *Manager) getNextDeadline(now time.Time) time.Time {
|
|||||||
return m.tomorrow(now)
|
return m.tomorrow(now)
|
||||||
case StateNormal:
|
case StateNormal:
|
||||||
return m.getDeadlineNormal(now, sched)
|
return m.getDeadlineNormal(now, sched)
|
||||||
}
|
default:
|
||||||
return m.tomorrow(now)
|
return m.tomorrow(now)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) getDeadlineNormal(now time.Time, sched sunSchedule) time.Time {
|
func (m *Manager) getDeadlineNormal(now time.Time, sched sunSchedule) time.Time {
|
||||||
times := sched.times
|
times := sched.times
|
||||||
@@ -588,7 +588,7 @@ func (m *Manager) schedulerLoop() {
|
|||||||
m.configMutex.RUnlock()
|
m.configMutex.RUnlock()
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
m.post(func() { m.applyCurrentTemp() })
|
m.post(func() { m.applyCurrentTemp("startup") })
|
||||||
}
|
}
|
||||||
|
|
||||||
var timer *time.Timer
|
var timer *time.Timer
|
||||||
@@ -630,24 +630,27 @@ func (m *Manager) schedulerLoop() {
|
|||||||
enabled := m.config.Enabled
|
enabled := m.config.Enabled
|
||||||
m.configMutex.RUnlock()
|
m.configMutex.RUnlock()
|
||||||
if enabled {
|
if enabled {
|
||||||
m.post(func() { m.applyCurrentTemp() })
|
m.post(func() { m.applyCurrentTemp("updateTrigger") })
|
||||||
}
|
}
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
m.configMutex.RLock()
|
m.configMutex.RLock()
|
||||||
enabled := m.config.Enabled
|
enabled := m.config.Enabled
|
||||||
m.configMutex.RUnlock()
|
m.configMutex.RUnlock()
|
||||||
if enabled {
|
if enabled {
|
||||||
m.post(func() { m.applyCurrentTemp() })
|
m.post(func() { m.applyCurrentTemp("timer") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) applyCurrentTemp() {
|
func (m *Manager) applyCurrentTemp(_ string) {
|
||||||
if !m.controlsInitialized || !m.allOutputsReady() {
|
if !m.controlsInitialized || !m.anyOutputReady() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure schedule is up-to-date (handles display wake after overnight sleep)
|
||||||
|
m.recalcSchedule(time.Now())
|
||||||
|
|
||||||
m.configMutex.RLock()
|
m.configMutex.RLock()
|
||||||
low, high := m.config.LowTemp, m.config.HighTemp
|
low, high := m.config.LowTemp, m.config.HighTemp
|
||||||
m.configMutex.RUnlock()
|
m.configMutex.RUnlock()
|
||||||
@@ -680,6 +683,10 @@ func (m *Manager) applyGamma(temp int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.lastAppliedTemp == temp && m.lastAppliedGamma == gamma {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var outs []*outputState
|
var outs []*outputState
|
||||||
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
||||||
outs = append(outs, out)
|
outs = append(outs, out)
|
||||||
@@ -715,6 +722,7 @@ func (m *Manager) applyGamma(temp int) {
|
|||||||
|
|
||||||
for _, j := range jobs {
|
for _, j := range jobs {
|
||||||
if err := m.setGammaBytes(j.out, j.data); err != nil {
|
if err := m.setGammaBytes(j.out, j.data); err != nil {
|
||||||
|
log.Warnf("gamma: failed to set output %d: %v", j.out.id, err)
|
||||||
j.out.failed = true
|
j.out.failed = true
|
||||||
j.out.rampSize = 0
|
j.out.rampSize = 0
|
||||||
outID := j.out.id
|
outID := j.out.id
|
||||||
@@ -727,6 +735,9 @@ func (m *Manager) applyGamma(temp int) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.lastAppliedTemp = temp
|
||||||
|
m.lastAppliedGamma = gamma
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) setGammaBytes(out *outputState, data []byte) error {
|
func (m *Manager) setGammaBytes(out *outputState, data []byte) error {
|
||||||
@@ -901,6 +912,10 @@ func (m *Manager) SetConfig(config Config) error {
|
|||||||
|
|
||||||
func (m *Manager) SetTemperature(low, high int) error {
|
func (m *Manager) SetTemperature(low, high int) error {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
|
if m.config.LowTemp == low && m.config.HighTemp == high {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
m.config.LowTemp = low
|
m.config.LowTemp = low
|
||||||
m.config.HighTemp = high
|
m.config.HighTemp = high
|
||||||
err := m.config.Validate()
|
err := m.config.Validate()
|
||||||
@@ -914,6 +929,11 @@ func (m *Manager) SetTemperature(low, high int) error {
|
|||||||
|
|
||||||
func (m *Manager) SetLocation(lat, lon float64) error {
|
func (m *Manager) SetLocation(lat, lon float64) error {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
|
if m.config.Latitude != nil && m.config.Longitude != nil &&
|
||||||
|
*m.config.Latitude == lat && *m.config.Longitude == lon && !m.config.UseIPLocation {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
m.config.Latitude = &lat
|
m.config.Latitude = &lat
|
||||||
m.config.Longitude = &lon
|
m.config.Longitude = &lon
|
||||||
m.config.UseIPLocation = false
|
m.config.UseIPLocation = false
|
||||||
@@ -928,6 +948,10 @@ func (m *Manager) SetLocation(lat, lon float64) error {
|
|||||||
|
|
||||||
func (m *Manager) SetUseIPLocation(use bool) {
|
func (m *Manager) SetUseIPLocation(use bool) {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
|
if m.config.UseIPLocation == use {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
m.config.UseIPLocation = use
|
m.config.UseIPLocation = use
|
||||||
if use {
|
if use {
|
||||||
m.config.Latitude = nil
|
m.config.Latitude = nil
|
||||||
@@ -946,6 +970,12 @@ func (m *Manager) SetUseIPLocation(use bool) {
|
|||||||
|
|
||||||
func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
|
func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
|
if m.config.ManualSunrise != nil && m.config.ManualSunset != nil &&
|
||||||
|
m.config.ManualSunrise.Hour() == sunrise.Hour() && m.config.ManualSunrise.Minute() == sunrise.Minute() &&
|
||||||
|
m.config.ManualSunset.Hour() == sunset.Hour() && m.config.ManualSunset.Minute() == sunset.Minute() {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
m.config.ManualSunrise = &sunrise
|
m.config.ManualSunrise = &sunrise
|
||||||
m.config.ManualSunset = &sunset
|
m.config.ManualSunset = &sunset
|
||||||
err := m.config.Validate()
|
err := m.config.Validate()
|
||||||
@@ -959,6 +989,10 @@ func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
|
|||||||
|
|
||||||
func (m *Manager) ClearManualTimes() {
|
func (m *Manager) ClearManualTimes() {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
|
if m.config.ManualSunrise == nil && m.config.ManualSunset == nil {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
m.config.ManualSunrise = nil
|
m.config.ManualSunrise = nil
|
||||||
m.config.ManualSunset = nil
|
m.config.ManualSunset = nil
|
||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
@@ -967,6 +1001,10 @@ func (m *Manager) ClearManualTimes() {
|
|||||||
|
|
||||||
func (m *Manager) SetGamma(gamma float64) error {
|
func (m *Manager) SetGamma(gamma float64) error {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
|
if m.config.Gamma == gamma {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
m.config.Gamma = gamma
|
m.config.Gamma = gamma
|
||||||
err := m.config.Validate()
|
err := m.config.Validate()
|
||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
@@ -980,6 +1018,10 @@ func (m *Manager) SetGamma(gamma float64) error {
|
|||||||
func (m *Manager) SetEnabled(enabled bool) {
|
func (m *Manager) SetEnabled(enabled bool) {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
wasEnabled := m.config.Enabled
|
wasEnabled := m.config.Enabled
|
||||||
|
if wasEnabled == enabled {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
m.config.Enabled = enabled
|
m.config.Enabled = enabled
|
||||||
highTemp := m.config.HighTemp
|
highTemp := m.config.HighTemp
|
||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
@@ -989,7 +1031,7 @@ func (m *Manager) SetEnabled(enabled bool) {
|
|||||||
m.post(func() {
|
m.post(func() {
|
||||||
gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
|
gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
|
||||||
if err := m.setupOutputControls(m.availableOutputs, gammaMgr); err != nil {
|
if err := m.setupOutputControls(m.availableOutputs, gammaMgr); err != nil {
|
||||||
log.Errorf("Failed to create gamma controls: %v", err)
|
log.Errorf("gamma: failed to create controls: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.controlsInitialized = true
|
m.controlsInitialized = true
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ type Manager struct {
|
|||||||
|
|
||||||
dbusConn *dbus.Conn
|
dbusConn *dbus.Conn
|
||||||
dbusSignal chan *dbus.Signal
|
dbusSignal chan *dbus.Signal
|
||||||
|
|
||||||
|
lastAppliedTemp int
|
||||||
|
lastAppliedGamma float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type outputState struct {
|
type outputState struct {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package wlcontext
|
package wlcontext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
@@ -27,6 +27,8 @@ type SharedContext struct {
|
|||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
fatalError chan error
|
fatalError chan error
|
||||||
cmdQueue chan func()
|
cmdQueue chan func()
|
||||||
|
wakeR int
|
||||||
|
wakeW int
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
started bool
|
started bool
|
||||||
@@ -38,11 +40,31 @@ func New() (*SharedContext, error) {
|
|||||||
return nil, fmt.Errorf("%w: %v", errdefs.ErrNoWaylandDisplay, err)
|
return nil, fmt.Errorf("%w: %v", errdefs.ErrNoWaylandDisplay, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fds := make([]int, 2)
|
||||||
|
if err := unix.Pipe(fds); err != nil {
|
||||||
|
display.Context().Close()
|
||||||
|
return nil, fmt.Errorf("failed to create wake pipe: %w", err)
|
||||||
|
}
|
||||||
|
if err := unix.SetNonblock(fds[0], true); err != nil {
|
||||||
|
unix.Close(fds[0])
|
||||||
|
unix.Close(fds[1])
|
||||||
|
display.Context().Close()
|
||||||
|
return nil, fmt.Errorf("failed to set wake pipe nonblock: %w", err)
|
||||||
|
}
|
||||||
|
if err := unix.SetNonblock(fds[1], true); err != nil {
|
||||||
|
unix.Close(fds[0])
|
||||||
|
unix.Close(fds[1])
|
||||||
|
display.Context().Close()
|
||||||
|
return nil, fmt.Errorf("failed to set wake pipe nonblock: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
sc := &SharedContext{
|
sc := &SharedContext{
|
||||||
display: display,
|
display: display,
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
fatalError: make(chan error, 1),
|
fatalError: make(chan error, 1),
|
||||||
cmdQueue: make(chan func(), 256),
|
cmdQueue: make(chan func(), 256),
|
||||||
|
wakeR: fds[0],
|
||||||
|
wakeW: fds[1],
|
||||||
started: false,
|
started: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +91,9 @@ func (sc *SharedContext) Display() *wlclient.Display {
|
|||||||
func (sc *SharedContext) Post(fn func()) {
|
func (sc *SharedContext) Post(fn func()) {
|
||||||
select {
|
select {
|
||||||
case sc.cmdQueue <- fn:
|
case sc.cmdQueue <- fn:
|
||||||
|
if _, err := unix.Write(sc.wakeW, []byte{1}); err != nil && err != unix.EAGAIN {
|
||||||
|
log.Errorf("wake pipe write error: %v", err)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +114,14 @@ func (sc *SharedContext) eventDispatcher() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ctx := sc.display.Context()
|
ctx := sc.display.Context()
|
||||||
|
wlFd := ctx.Fd()
|
||||||
|
|
||||||
|
pollFds := []unix.PollFd{
|
||||||
|
{Fd: int32(wlFd), Events: unix.POLLIN},
|
||||||
|
{Fd: int32(sc.wakeR), Events: unix.POLLIN},
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -100,23 +132,36 @@ func (sc *SharedContext) eventDispatcher() {
|
|||||||
|
|
||||||
sc.drainCmdQueue()
|
sc.drainCmdQueue()
|
||||||
|
|
||||||
if err := ctx.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
n, err := unix.Poll(pollFds, 50)
|
||||||
log.Errorf("Failed to set read deadline: %v", err)
|
if err != nil {
|
||||||
|
if err == unix.EINTR {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
err := ctx.Dispatch()
|
log.Errorf("Poll error: %v", err)
|
||||||
if err := ctx.SetReadDeadline(time.Time{}); err != nil {
|
return
|
||||||
log.Errorf("Failed to clear read deadline: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
if n == 0 {
|
||||||
case err == nil:
|
continue
|
||||||
case errors.Is(err, os.ErrDeadlineExceeded):
|
}
|
||||||
default:
|
|
||||||
|
if pollFds[1].Revents&unix.POLLIN != 0 {
|
||||||
|
var buf [64]byte
|
||||||
|
if _, err := unix.Read(sc.wakeR, buf[:]); err != nil && err != unix.EAGAIN {
|
||||||
|
log.Errorf("wake pipe read error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pollFds[0].Revents&unix.POLLIN != 0 {
|
||||||
|
if err := ctx.Dispatch(); err != nil {
|
||||||
|
if !os.IsTimeout(err) {
|
||||||
log.Errorf("Wayland connection error: %v", err)
|
log.Errorf("Wayland connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (sc *SharedContext) drainCmdQueue() {
|
func (sc *SharedContext) drainCmdQueue() {
|
||||||
for {
|
for {
|
||||||
@@ -133,6 +178,9 @@ func (sc *SharedContext) Close() {
|
|||||||
close(sc.stopChan)
|
close(sc.stopChan)
|
||||||
sc.wg.Wait()
|
sc.wg.Wait()
|
||||||
|
|
||||||
|
unix.Close(sc.wakeR)
|
||||||
|
unix.Close(sc.wakeW)
|
||||||
|
|
||||||
if sc.display != nil {
|
if sc.display != nil {
|
||||||
sc.display.Context().Close()
|
sc.display.Context().Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,21 +125,47 @@ func (m *Manager) ApplyConfiguration(heads []HeadConfig, test bool) error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
statusChan := make(chan error, 1)
|
responded := false
|
||||||
|
|
||||||
config.SetSucceededHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1SucceededEvent) {
|
config.SetSucceededHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1SucceededEvent) {
|
||||||
|
if responded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
responded = true
|
||||||
log.Info("WlrOutput: configuration succeeded")
|
log.Info("WlrOutput: configuration succeeded")
|
||||||
statusChan <- nil
|
config.Destroy()
|
||||||
|
resultChan <- nil
|
||||||
})
|
})
|
||||||
|
|
||||||
config.SetFailedHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1FailedEvent) {
|
config.SetFailedHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1FailedEvent) {
|
||||||
|
if responded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
responded = true
|
||||||
log.Warn("WlrOutput: configuration failed")
|
log.Warn("WlrOutput: configuration failed")
|
||||||
statusChan <- fmt.Errorf("compositor rejected configuration")
|
config.Destroy()
|
||||||
|
resultChan <- fmt.Errorf("compositor rejected configuration")
|
||||||
})
|
})
|
||||||
|
|
||||||
config.SetCancelledHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1CancelledEvent) {
|
config.SetCancelledHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1CancelledEvent) {
|
||||||
|
if responded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
responded = true
|
||||||
log.Warn("WlrOutput: configuration cancelled")
|
log.Warn("WlrOutput: configuration cancelled")
|
||||||
statusChan <- fmt.Errorf("configuration cancelled (outdated serial)")
|
config.Destroy()
|
||||||
|
resultChan <- fmt.Errorf("configuration cancelled (outdated serial)")
|
||||||
|
})
|
||||||
|
|
||||||
|
time.AfterFunc(time.Second, func() {
|
||||||
|
m.post(func() {
|
||||||
|
if responded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
responded = true
|
||||||
|
config.Destroy()
|
||||||
|
resultChan <- fmt.Errorf("timeout waiting for configuration response")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
headsByName := make(map[string]*headState)
|
headsByName := make(map[string]*headState)
|
||||||
@@ -241,6 +267,7 @@ func (m *Manager) ApplyConfiguration(heads []HeadConfig, test bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if applyErr != nil {
|
if applyErr != nil {
|
||||||
|
responded = true
|
||||||
config.Destroy()
|
config.Destroy()
|
||||||
action := "apply"
|
action := "apply"
|
||||||
if test {
|
if test {
|
||||||
@@ -249,17 +276,6 @@ func (m *Manager) ApplyConfiguration(heads []HeadConfig, test bool) error {
|
|||||||
resultChan <- fmt.Errorf("failed to %s configuration: %w", action, applyErr)
|
resultChan <- fmt.Errorf("failed to %s configuration: %w", action, applyErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case err := <-statusChan:
|
|
||||||
config.Destroy()
|
|
||||||
resultChan <- err
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
config.Destroy()
|
|
||||||
resultChan <- fmt.Errorf("timeout waiting for configuration response")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return <-resultChan
|
return <-resultChan
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
|
|||||||
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
|
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
|
||||||
log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name)
|
log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name)
|
||||||
head.name = e.Name
|
head.name = e.Name
|
||||||
|
head.ready = true
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
@@ -251,11 +252,11 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
|
|||||||
|
|
||||||
m.heads.Delete(headID)
|
m.heads.Delete(headID)
|
||||||
|
|
||||||
m.post(func() {
|
|
||||||
m.wlMutex.Lock()
|
m.wlMutex.Lock()
|
||||||
handle.Release()
|
handle.Release()
|
||||||
m.wlMutex.Unlock()
|
m.wlMutex.Unlock()
|
||||||
|
|
||||||
|
m.post(func() {
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -310,11 +311,11 @@ func (m *Manager) handleMode(headID uint32, e wlr_output_management.ZwlrOutputHe
|
|||||||
|
|
||||||
m.modes.Delete(modeID)
|
m.modes.Delete(modeID)
|
||||||
|
|
||||||
m.post(func() {
|
|
||||||
m.wlMutex.Lock()
|
m.wlMutex.Lock()
|
||||||
handle.Release()
|
handle.Release()
|
||||||
m.wlMutex.Unlock()
|
m.wlMutex.Unlock()
|
||||||
|
|
||||||
|
m.post(func() {
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -328,6 +329,10 @@ func (m *Manager) updateState() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !head.ready {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
modes := make([]OutputMode, 0)
|
modes := make([]OutputMode, 0)
|
||||||
var currentMode *OutputMode
|
var currentMode *OutputMode
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ type headState struct {
|
|||||||
modeIDs []uint32
|
modeIDs []uint32
|
||||||
adaptiveSync uint32
|
adaptiveSync uint32
|
||||||
finished bool
|
finished bool
|
||||||
|
ready bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type modeState struct {
|
type modeState struct {
|
||||||
|
|||||||
@@ -58,6 +58,18 @@ func (ctx *Context) SetReadDeadline(t time.Time) error {
|
|||||||
return ctx.conn.SetReadDeadline(t)
|
return ctx.conn.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Fd() int {
|
||||||
|
rawConn, err := ctx.conn.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
var fd int
|
||||||
|
rawConn.Control(func(f uintptr) {
|
||||||
|
fd = int(f)
|
||||||
|
})
|
||||||
|
return fd
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch reads and processes incoming messages and calls [client.Dispatcher.Dispatch] on the
|
// Dispatch reads and processes incoming messages and calls [client.Dispatcher.Dispatch] on the
|
||||||
// respective wayland protocol.
|
// respective wayland protocol.
|
||||||
// Dispatch must be called on the same goroutine as other interactions with the Context.
|
// Dispatch must be called on the same goroutine as other interactions with the Context.
|
||||||
|
|||||||
@@ -1,3 +1,39 @@
|
|||||||
|
dms-git (1.0.2+git2528.d336866f) nightly; urgency=medium
|
||||||
|
|
||||||
|
* Git snapshot (commit 2528: d336866f)
|
||||||
|
|
||||||
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sun, 14 Dec 2025 03:56:25 +0000
|
||||||
|
|
||||||
|
dms-git (1.0.2+git2521.3b511e2f) nightly; urgency=medium
|
||||||
|
|
||||||
|
* Git snapshot (commit 2521: 3b511e2f)
|
||||||
|
|
||||||
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 21:10:22 +0000
|
||||||
|
|
||||||
|
dms-git (1.0.2+git2518.a783d650) nightly; urgency=medium
|
||||||
|
|
||||||
|
* Git snapshot (commit 2518: a783d650)
|
||||||
|
|
||||||
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 15:11:40 +0000
|
||||||
|
|
||||||
|
dms-git (1.0.2+git2510.0f89886c) nightly; urgency=medium
|
||||||
|
|
||||||
|
* Git snapshot (commit 2510: 0f89886c)
|
||||||
|
|
||||||
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:46:43 +0000
|
||||||
|
|
||||||
|
dms-git (1.0.2+git2507.b2ac9c6c) nightly; urgency=medium
|
||||||
|
|
||||||
|
* Git snapshot (commit 2507: b2ac9c6c)
|
||||||
|
|
||||||
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:18:05 +0000
|
||||||
|
|
||||||
|
dms-git (1.0.2+git2505.82f881af) nightly; urgency=medium
|
||||||
|
|
||||||
|
* Git snapshot (commit 2505: 82f881af)
|
||||||
|
|
||||||
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 05:55:03 +0000
|
||||||
|
|
||||||
dms-git (1.0.0+git2419.993f14a3) nightly; urgency=medium
|
dms-git (1.0.0+git2419.993f14a3) nightly; urgency=medium
|
||||||
|
|
||||||
* Major stable release v1.0.0
|
* Major stable release v1.0.0
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
dms (1.0.2) stable; urgency=medium
|
dms (1.0.2ppa6) unstable; urgency=medium
|
||||||
|
|
||||||
* Update to v1.0.2 stable release
|
* Rebuild to fix repository metadata issues
|
||||||
* Bug fixes and improvements
|
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Thu, 12 Dec 2025 14:30:00 -0500
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:47:39 +0000
|
||||||
|
|
||||||
dms (1.0.0) stable; urgency=medium
|
dms (1.0.0) stable; urgency=medium
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
%global debug_package %{nil}
|
%global debug_package %{nil}
|
||||||
|
|
||||||
Name: dms-git
|
Name: dms-git
|
||||||
Version: 0.6.2+git2147.03073f68
|
Version: 1.0.2+git2528.d336866f
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Epoch: 2
|
Epoch: 2
|
||||||
Summary: DankMaterialShell - Material 3 inspired shell (git nightly)
|
Summary: DankMaterialShell - Material 3 inspired shell (git nightly)
|
||||||
@@ -135,6 +135,18 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
|
|||||||
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sun Dec 14 2025 Avenge Media <AvengeMedia.US@gmail.com> - 1.0.2+git2528.d336866f-1
|
||||||
|
- Git snapshot (commit 2528: d336866f)
|
||||||
|
* Sat Dec 13 2025 Avenge Media <AvengeMedia.US@gmail.com> - 1.0.2+git2521.3b511e2f-1
|
||||||
|
- Git snapshot (commit 2521: 3b511e2f)
|
||||||
|
* Sat Dec 13 2025 Avenge Media <AvengeMedia.US@gmail.com> - 1.0.2+git2518.a783d650-1
|
||||||
|
- Git snapshot (commit 2518: a783d650)
|
||||||
|
* Sat Dec 13 2025 Avenge Media <AvengeMedia.US@gmail.com> - 1.0.2+git2510.0f89886c-1
|
||||||
|
- Git snapshot (commit 2510: 0f89886c)
|
||||||
|
* Sat Dec 13 2025 Avenge Media <AvengeMedia.US@gmail.com> - 1.0.2+git2507.b2ac9c6c-1
|
||||||
|
- Git snapshot (commit 2507: b2ac9c6c)
|
||||||
|
* Sat Dec 13 2025 Avenge Media <AvengeMedia.US@gmail.com> - 1.0.2+git2505.82f881af-1
|
||||||
|
- Git snapshot (commit 2505: 82f881af)
|
||||||
* Tue Nov 25 2025 Avenge Media <AvengeMedia.US@gmail.com> - 0.6.2+git2147.03073f68-1
|
* Tue Nov 25 2025 Avenge Media <AvengeMedia.US@gmail.com> - 0.6.2+git2147.03073f68-1
|
||||||
- Git snapshot (commit 2147: 03073f68)
|
- Git snapshot (commit 2147: 03073f68)
|
||||||
* Fri Nov 22 2025 AvengeMedia <maintainer@avengemedia.com> - 0.6.2+git-5
|
* Fri Nov 22 2025 AvengeMedia <maintainer@avengemedia.com> - 0.6.2+git-5
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Name: dms
|
Name: dms
|
||||||
Version: 1.0.2
|
Version: 1.0.2
|
||||||
Release: 1%{?dist}
|
Release: 7%{?dist}
|
||||||
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||||
|
|
||||||
License: MIT
|
License: MIT
|
||||||
|
|||||||
+225
-305
@@ -1,12 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Unified OBS upload script for dms packages
|
# Unified OBS upload script for dms packages
|
||||||
# Handles Debian and OpenSUSE builds for both x86_64 and aarch64
|
# Handles Debian and OpenSUSE builds for both x86_64 and aarch64
|
||||||
# Usage: ./distro/scripts/obs-upload.sh [distro] <package-name> [commit-message]
|
# Usage: ./distro/scripts/obs-upload.sh [distro] <package-name> [commit-message|rebuild-number]
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# ./distro/scripts/obs-upload.sh dms "Update to v0.6.2"
|
# ./distro/scripts/obs-upload.sh dms "Update to v1.0.2"
|
||||||
# ./distro/scripts/obs-upload.sh debian dms
|
# ./distro/scripts/obs-upload.sh debian dms
|
||||||
# ./distro/scripts/obs-upload.sh opensuse dms-git
|
# ./distro/scripts/obs-upload.sh opensuse dms-git
|
||||||
|
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with ppa2 suffix
|
||||||
|
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with ppa2 suffix (flag syntax)
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -14,6 +16,8 @@ UPLOAD_DEBIAN=true
|
|||||||
UPLOAD_OPENSUSE=true
|
UPLOAD_OPENSUSE=true
|
||||||
PACKAGE=""
|
PACKAGE=""
|
||||||
MESSAGE=""
|
MESSAGE=""
|
||||||
|
REBUILD_RELEASE="${REBUILD_RELEASE:-}"
|
||||||
|
POSITIONAL_ARGS=()
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
@@ -25,16 +29,43 @@ for arg in "$@"; do
|
|||||||
UPLOAD_DEBIAN=false
|
UPLOAD_DEBIAN=false
|
||||||
UPLOAD_OPENSUSE=true
|
UPLOAD_OPENSUSE=true
|
||||||
;;
|
;;
|
||||||
|
--rebuild=*)
|
||||||
|
REBUILD_RELEASE="${arg#*=}"
|
||||||
|
;;
|
||||||
|
-r|--rebuild)
|
||||||
|
REBUILD_NEXT=true
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
if [[ -z "$PACKAGE" ]]; then
|
if [[ -n "${REBUILD_NEXT:-}" ]]; then
|
||||||
PACKAGE="$arg"
|
REBUILD_RELEASE="$arg"
|
||||||
elif [[ -z "$MESSAGE" ]]; then
|
REBUILD_NEXT=false
|
||||||
MESSAGE="$arg"
|
else
|
||||||
|
POSITIONAL_ARGS+=("$arg")
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Check if last positional argument is a number (rebuild release)
|
||||||
|
if [[ ${#POSITIONAL_ARGS[@]} -gt 0 ]]; then
|
||||||
|
LAST_INDEX=$((${#POSITIONAL_ARGS[@]} - 1))
|
||||||
|
LAST_ARG="${POSITIONAL_ARGS[$LAST_INDEX]}"
|
||||||
|
if [[ "$LAST_ARG" =~ ^[0-9]+$ ]] && [[ -z "$REBUILD_RELEASE" ]]; then
|
||||||
|
# Last argument is a number and no --rebuild flag was used
|
||||||
|
# Use it as rebuild release and remove from positional args
|
||||||
|
REBUILD_RELEASE="$LAST_ARG"
|
||||||
|
POSITIONAL_ARGS=("${POSITIONAL_ARGS[@]:0:$LAST_INDEX}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Assign remaining positional args to PACKAGE and MESSAGE
|
||||||
|
if [[ ${#POSITIONAL_ARGS[@]} -gt 0 ]]; then
|
||||||
|
PACKAGE="${POSITIONAL_ARGS[0]}"
|
||||||
|
if [[ ${#POSITIONAL_ARGS[@]} -gt 1 ]]; then
|
||||||
|
MESSAGE="${POSITIONAL_ARGS[1]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
OBS_BASE_PROJECT="home:AvengeMedia"
|
OBS_BASE_PROJECT="home:AvengeMedia"
|
||||||
OBS_BASE="$HOME/.cache/osc-checkouts"
|
OBS_BASE="$HOME/.cache/osc-checkouts"
|
||||||
AVAILABLE_PACKAGES=(dms dms-git)
|
AVAILABLE_PACKAGES=(dms dms-git)
|
||||||
@@ -70,6 +101,51 @@ if [[ ! -d "distro/debian" ]]; then
|
|||||||
echo "Error: Run this script from the repository root"
|
echo "Error: Run this script from the repository root"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
# Parameters:
|
||||||
|
# $1 = PROJECT
|
||||||
|
# $2 = PACKAGE
|
||||||
|
# $3 = VERSION
|
||||||
|
# $4 = CHECK_MODE - Exact version match, "commit" = check commit hash (default)
|
||||||
|
check_obs_version_exists() {
|
||||||
|
local PROJECT="$1"
|
||||||
|
local PACKAGE="$2"
|
||||||
|
local VERSION="$3"
|
||||||
|
local CHECK_MODE="${4:-commit}"
|
||||||
|
local OBS_SPEC=""
|
||||||
|
|
||||||
|
# Use osc api command (works in both local and CI environments)
|
||||||
|
if command -v osc &> /dev/null; then
|
||||||
|
OBS_SPEC=$(osc api "/source/$PROJECT/$PACKAGE/${PACKAGE}.spec" 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
echo "⚠️ osc command not found, skipping version check"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if we got valid spec content
|
||||||
|
if [[ -n "$OBS_SPEC" && "$OBS_SPEC" != *"error"* && "$OBS_SPEC" == *"Version:"* ]]; then
|
||||||
|
OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs)
|
||||||
|
# Commit hash check for -git packages
|
||||||
|
if [[ "$CHECK_MODE" == "commit" ]] && [[ "$PACKAGE" == *"-git" ]]; then
|
||||||
|
OBS_COMMIT=$(echo "$OBS_VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||||
|
NEW_COMMIT=$(echo "$VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$OBS_COMMIT" && -n "$NEW_COMMIT" && "$OBS_COMMIT" == "$NEW_COMMIT" ]]; then
|
||||||
|
echo "⚠️ Commit $NEW_COMMIT already exists in OBS (current version: $OBS_VERSION)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Exact version match check
|
||||||
|
if [[ "$OBS_VERSION" == "$VERSION" ]]; then
|
||||||
|
echo "⚠️ Version $VERSION already exists in OBS"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ Could not fetch OBS spec (API may be unavailable), proceeding anyway"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Handle "all" option
|
# Handle "all" option
|
||||||
if [[ "$PACKAGE" == "all" ]]; then
|
if [[ "$PACKAGE" == "all" ]]; then
|
||||||
@@ -145,9 +221,9 @@ IS_MANUAL=false
|
|||||||
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
||||||
IS_MANUAL=true
|
IS_MANUAL=true
|
||||||
echo "==> Manual rebuild detected (REBUILD_RELEASE=$REBUILD_RELEASE)"
|
echo "==> Manual rebuild detected (REBUILD_RELEASE=$REBUILD_RELEASE)"
|
||||||
elif [[ -n "${FORCE_REBUILD:-}" ]] && [[ "${FORCE_REBUILD}" == "true" ]]; then
|
elif [[ -n "${FORCE_UPLOAD:-}" ]] && [[ "${FORCE_UPLOAD}" == "true" ]]; then
|
||||||
IS_MANUAL=true
|
IS_MANUAL=true
|
||||||
echo "==> Manual workflow trigger detected (FORCE_REBUILD=true)"
|
echo "==> Force upload detected (FORCE_UPLOAD=true)"
|
||||||
elif [[ -z "${GITHUB_ACTIONS:-}" ]] && [[ -z "${CI:-}" ]]; then
|
elif [[ -z "${GITHUB_ACTIONS:-}" ]] && [[ -z "${CI:-}" ]]; then
|
||||||
IS_MANUAL=true
|
IS_MANUAL=true
|
||||||
echo "==> Local/manual run detected (not in CI)"
|
echo "==> Local/manual run detected (not in CI)"
|
||||||
@@ -183,7 +259,15 @@ fi
|
|||||||
|
|
||||||
CHANGELOG_VERSION=""
|
CHANGELOG_VERSION=""
|
||||||
if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
||||||
# Format: 0.6.2+git{COMMIT_COUNT}.{COMMIT_HASH} (e.g., 0.6.2+git2256.9162e314)
|
# For -git packages, generate version dynamically from git state (like workflows do)
|
||||||
|
if [[ "$PACKAGE" == *"-git" ]]; then
|
||||||
|
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 "1.0.2")
|
||||||
|
CHANGELOG_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||||
|
echo " - Generated git snapshot version: $CHANGELOG_VERSION"
|
||||||
|
else
|
||||||
|
# For stable packages: Format: 0.6.2+git{COMMIT_COUNT}.{COMMIT_HASH}
|
||||||
CHANGELOG_VERSION=$(grep -m1 "^$PACKAGE" "distro/debian/$PACKAGE/debian/changelog" 2>/dev/null | sed 's/.*(\([^)]*\)).*/\1/' || echo "")
|
CHANGELOG_VERSION=$(grep -m1 "^$PACKAGE" "distro/debian/$PACKAGE/debian/changelog" 2>/dev/null | sed 's/.*(\([^)]*\)).*/\1/' || echo "")
|
||||||
if [[ -n "$CHANGELOG_VERSION" ]] && [[ "$CHANGELOG_VERSION" == *"-"* ]]; then
|
if [[ -n "$CHANGELOG_VERSION" ]] && [[ "$CHANGELOG_VERSION" == *"-"* ]]; then
|
||||||
SOURCE_FORMAT_CHECK=$(cat "distro/debian/$PACKAGE/debian/source/format" 2>/dev/null || echo "3.0 (quilt)")
|
SOURCE_FORMAT_CHECK=$(cat "distro/debian/$PACKAGE/debian/source/format" 2>/dev/null || echo "3.0 (quilt)")
|
||||||
@@ -193,6 +277,47 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Apply rebuild suffix if specified (must happen before API check)
|
||||||
|
if [[ -n "$REBUILD_RELEASE" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
|
CHANGELOG_VERSION="${CHANGELOG_VERSION}ppa${REBUILD_RELEASE}"
|
||||||
|
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if this version already exists in OBS
|
||||||
|
if [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
|
if [[ -z "$REBUILD_RELEASE" ]]; then
|
||||||
|
if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION"; then
|
||||||
|
if [[ "$PACKAGE" == *"-git" ]]; then
|
||||||
|
echo "==> Error: This commit is already uploaded to OBS"
|
||||||
|
echo " The same git commit ($(echo "$CHANGELOG_VERSION" | grep -oP '[a-f0-9]{8}' | tail -1)) already exists on OBS."
|
||||||
|
echo " To rebuild the same commit, specify a rebuild number:"
|
||||||
|
echo " ./distro/scripts/obs-upload.sh $PACKAGE 2"
|
||||||
|
echo " ./distro/scripts/obs-upload.sh $PACKAGE 3"
|
||||||
|
echo " Or push a new commit first, then run:"
|
||||||
|
echo " ./distro/scripts/obs-upload.sh $PACKAGE"
|
||||||
|
else
|
||||||
|
echo "==> Error: Version $CHANGELOG_VERSION already exists in OBS"
|
||||||
|
echo " To rebuild with a different release number, try:"
|
||||||
|
echo " ./distro/scripts/obs-upload.sh $PACKAGE --rebuild=2"
|
||||||
|
echo " or positional syntax:"
|
||||||
|
echo " ./distro/scripts/obs-upload.sh $PACKAGE 2"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Rebuild number specified - check if this exact version already exists (exact mode)
|
||||||
|
if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION" "exact"; then
|
||||||
|
echo "==> Error: Version $CHANGELOG_VERSION already exists in OBS"
|
||||||
|
echo " This exact version (including ppa${REBUILD_RELEASE}) is already uploaded."
|
||||||
|
echo " To rebuild with a different release number, try incrementing:"
|
||||||
|
NEXT_NUM=$((REBUILD_RELEASE + 1))
|
||||||
|
echo " ./distro/scripts/obs-upload.sh $PACKAGE $NEXT_NUM"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ -f "distro/opensuse/$PACKAGE.spec" ]]; then
|
if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ -f "distro/opensuse/$PACKAGE.spec" ]]; then
|
||||||
echo " - Copying $PACKAGE.spec for OpenSUSE"
|
echo " - Copying $PACKAGE.spec for OpenSUSE"
|
||||||
cp "distro/opensuse/$PACKAGE.spec" "$WORK_DIR/"
|
cp "distro/opensuse/$PACKAGE.spec" "$WORK_DIR/"
|
||||||
@@ -204,25 +329,28 @@ if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ -f "distro/opensuse/$PACKAGE.spec" ]];
|
|||||||
OLD_RELEASE=$(grep "^Release:" "$WORK_DIR/.osc/$PACKAGE.spec" | sed 's/^Release:[[:space:]]*//' | sed 's/%{?dist}//' | head -1)
|
OLD_RELEASE=$(grep "^Release:" "$WORK_DIR/.osc/$PACKAGE.spec" | sed 's/^Release:[[:space:]]*//' | sed 's/%{?dist}//' | head -1)
|
||||||
|
|
||||||
if [[ "$NEW_VERSION" == "$OLD_VERSION" ]]; then
|
if [[ "$NEW_VERSION" == "$OLD_VERSION" ]]; then
|
||||||
if [[ "$OLD_RELEASE" =~ ^([0-9]+) ]]; then
|
if [[ "$IS_MANUAL" == true ]] && [[ -z "${GITHUB_ACTIONS:-}" ]] && [[ -z "${CI:-}" ]]; then
|
||||||
BASE_RELEASE="${BASH_REMATCH[1]}"
|
# Only error for true local manual runs, not CI/workflow runs
|
||||||
if [[ "$IS_MANUAL" == true ]]; then
|
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
||||||
NEXT_RELEASE=$((BASE_RELEASE + 1))
|
echo " 🔄 Using manual rebuild release number: $REBUILD_RELEASE"
|
||||||
echo " - Detected rebuild of same version $NEW_VERSION (release $OLD_RELEASE -> $NEXT_RELEASE)"
|
sed -i "s/^Release:[[:space:]]*${NEW_RELEASE}%{?dist}/Release: ${REBUILD_RELEASE}%{?dist}/" "$WORK_DIR/$PACKAGE.spec"
|
||||||
sed -i "s/^Release:[[:space:]]*${NEW_RELEASE}%{?dist}/Release: ${NEXT_RELEASE}%{?dist}/" "$WORK_DIR/$PACKAGE.spec"
|
cp "$WORK_DIR/$PACKAGE.spec" "$REPO_ROOT/distro/opensuse/$PACKAGE.spec"
|
||||||
else
|
else
|
||||||
echo " - Detected same version $NEW_VERSION (release $OLD_RELEASE). Not a manual run, skipping update."
|
echo " - Error: Same version detected ($NEW_VERSION) but no rebuild number specified"
|
||||||
# For automated runs with no version change, we should stop here to avoid unnecessary rebuilds
|
echo " To rebuild, explicitly specify a rebuild number:"
|
||||||
# However, we need to check if we are also updating Debian, or if this script is expected to continue.
|
echo " ./distro/scripts/obs-upload.sh opensuse $PACKAGE 2"
|
||||||
# If this is OpenSUSE only run, we can exit.
|
echo " or use flag syntax:"
|
||||||
if [[ "$UPLOAD_DEBIAN" == false ]]; then
|
echo " ./distro/scripts/obs-upload.sh opensuse $PACKAGE --rebuild=2"
|
||||||
echo "✅ No changes needed for OpenSUSE (not manual). Exiting."
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " - Detected same version $NEW_VERSION (release $OLD_RELEASE). No changes needed, skipping update."
|
||||||
|
echo "✅ No changes needed for this package. Exiting gracefully."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo " - New version detected: $OLD_VERSION -> $NEW_VERSION (keeping release $NEW_RELEASE)"
|
echo " - New version detected: $OLD_VERSION -> $NEW_VERSION (keeping release $NEW_RELEASE)"
|
||||||
|
cp "$WORK_DIR/$PACKAGE.spec" "$REPO_ROOT/distro/opensuse/$PACKAGE.spec"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " - First upload to OBS (no previous spec found)"
|
echo " - First upload to OBS (no previous spec found)"
|
||||||
@@ -518,13 +646,47 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
echo " - OpenSUSE source tarballs created"
|
echo " - OpenSUSE source tarballs created"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Copy and update OpenSUSE spec file with the correct version (for -git packages)
|
||||||
cp "distro/opensuse/$PACKAGE.spec" "$WORK_DIR/"
|
cp "distro/opensuse/$PACKAGE.spec" "$WORK_DIR/"
|
||||||
|
if [[ "$PACKAGE" == *"-git" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
|
echo " Updating OpenSUSE spec to version $CHANGELOG_VERSION"
|
||||||
|
sed -i "s/^Version:.*/Version: $CHANGELOG_VERSION/" "$WORK_DIR/$PACKAGE.spec"
|
||||||
|
|
||||||
|
# Update changelog in spec file
|
||||||
|
DATE_STR=$(date "+%a %b %d %Y")
|
||||||
|
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' "$WORK_DIR/$PACKAGE.spec")
|
||||||
|
{
|
||||||
|
echo "$LOCAL_SPEC_HEAD"
|
||||||
|
echo "%changelog"
|
||||||
|
echo "* $DATE_STR Avenge Media <AvengeMedia.US@gmail.com> - ${CHANGELOG_VERSION}-1"
|
||||||
|
echo "- Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||||
|
} > "$WORK_DIR/$PACKAGE.spec"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$UPLOAD_DEBIAN" == true ]]; then
|
if [[ "$UPLOAD_DEBIAN" == true ]]; then
|
||||||
echo " Copying debian/ directory into source"
|
echo " Copying debian/ directory into source"
|
||||||
cp -r "distro/debian/$PACKAGE/debian" "$SOURCE_DIR/"
|
cp -r "distro/debian/$PACKAGE/debian" "$SOURCE_DIR/"
|
||||||
|
|
||||||
|
# Update changelog with the correct version (for -git packages, use dynamically generated version)
|
||||||
|
if [[ -n "$CHANGELOG_VERSION" ]] && [[ -f "$SOURCE_DIR/debian/changelog" ]]; then
|
||||||
|
echo " Updating changelog to version $CHANGELOG_VERSION"
|
||||||
|
TEMP_CHANGELOG=$(mktemp)
|
||||||
|
{
|
||||||
|
echo "$PACKAGE ($CHANGELOG_VERSION) unstable; urgency=medium"
|
||||||
|
echo ""
|
||||||
|
if [[ "$PACKAGE" == *"-git" ]]; then
|
||||||
|
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||||
|
else
|
||||||
|
echo " * Automated update"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
||||||
|
} >"$TEMP_CHANGELOG"
|
||||||
|
cp "$TEMP_CHANGELOG" "$SOURCE_DIR/debian/changelog"
|
||||||
|
rm -f "$TEMP_CHANGELOG"
|
||||||
|
fi
|
||||||
|
|
||||||
# For dms, rename directory to match what debian/rules expects
|
# For dms, rename directory to match what debian/rules expects
|
||||||
# debian/rules uses UPSTREAM_VERSION which is the full version from changelog
|
# debian/rules uses UPSTREAM_VERSION which is the full version from changelog
|
||||||
if [[ "$PACKAGE" == "dms" ]]; then
|
if [[ "$PACKAGE" == "dms" ]]; then
|
||||||
@@ -636,293 +798,51 @@ fi
|
|||||||
|
|
||||||
cd "$WORK_DIR"
|
cd "$WORK_DIR"
|
||||||
|
|
||||||
|
# Server-side cleanup via API
|
||||||
|
echo "==> Cleaning old tarballs from OBS server (prevents downloading 100+ old versions)"
|
||||||
|
OBS_FILES=$(osc api "/source/$OBS_PROJECT/$PACKAGE" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$OBS_FILES" ]]; then
|
||||||
|
DELETED_COUNT=0
|
||||||
|
KEEP_PATTERN=""
|
||||||
|
if [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
|
BASE_KEEP_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
|
||||||
|
KEEP_PATTERN="${PACKAGE}_${BASE_KEEP_VERSION}"
|
||||||
|
echo " Keeping tarballs matching: ${KEEP_PATTERN}*"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for old_file in $(echo "$OBS_FILES" | grep -oP '(?<=name=")[^"]*\.(tar\.gz|tar\.xz|tar\.bz2)(?=")' || true); do
|
||||||
|
if [[ -n "$KEEP_PATTERN" ]] && [[ "$old_file" == ${KEEP_PATTERN}* ]]; then
|
||||||
|
echo " - Keeping current version: $old_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$old_file" == "${PACKAGE}-source.tar.gz" ]]; then
|
||||||
|
echo " - Keeping source tarball: $old_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " - Deleting from server: $old_file"
|
||||||
|
if osc api -X DELETE "/source/$OBS_PROJECT/$PACKAGE/$old_file" 2>/dev/null; then
|
||||||
|
((DELETED_COUNT++)) || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ $DELETED_COUNT -gt 0 ]]; then
|
||||||
|
echo " ✓ Deleted $DELETED_COUNT old tarball(s) from server"
|
||||||
|
else
|
||||||
|
echo " ✓ No old tarballs found on server (current version preserved)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ⚠️ Could not fetch file list from server, skipping cleanup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback update with --server-side-source-service-files flag only syncs metadata (spec, dsc, _service)
|
||||||
echo "==> Updating working copy"
|
echo "==> Updating working copy"
|
||||||
|
if ! osc up --server-side-source-service-files 2>/dev/null; then
|
||||||
|
echo " Note: Using regular update (--server-side-source-service-files not supported)"
|
||||||
if ! osc up; then
|
if ! osc up; then
|
||||||
echo "Error: Failed to update working copy"
|
echo "Error: Failed to update working copy"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Only auto-increment on manual runs (REBUILD_RELEASE set or not in CI), not automated workflows
|
|
||||||
OLD_DSC_FILE=""
|
|
||||||
if [[ -f "$WORK_DIR/.osc/sources/$PACKAGE.dsc" ]]; then
|
|
||||||
OLD_DSC_FILE="$WORK_DIR/.osc/sources/$PACKAGE.dsc"
|
|
||||||
elif [[ -f "$WORK_DIR/.osc/$PACKAGE.dsc" ]]; then
|
|
||||||
OLD_DSC_FILE="$WORK_DIR/.osc/$PACKAGE.dsc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$SOURCE_FORMAT" == *"native"* ]] && [[ -n "$OLD_DSC_FILE" ]]; then
|
|
||||||
OLD_DSC_VERSION=$(grep "^Version:" "$OLD_DSC_FILE" 2>/dev/null | awk '{print $2}' | head -1)
|
|
||||||
|
|
||||||
IS_MANUAL=false
|
|
||||||
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
|
||||||
IS_MANUAL=true
|
|
||||||
echo "==> Manual rebuild detected (REBUILD_RELEASE=$REBUILD_RELEASE)"
|
|
||||||
elif [[ -n "${FORCE_REBUILD:-}" ]] && [[ "${FORCE_REBUILD}" == "true" ]]; then
|
|
||||||
IS_MANUAL=true
|
|
||||||
echo "==> Manual workflow trigger detected (FORCE_REBUILD=true)"
|
|
||||||
elif [[ -z "${GITHUB_ACTIONS:-}" ]] && [[ -z "${CI:-}" ]]; then
|
|
||||||
IS_MANUAL=true
|
|
||||||
echo "==> Local/manual run detected (not in CI)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
CHANGELOG_BASE=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
|
|
||||||
OLD_DSC_BASE=$(echo "$OLD_DSC_VERSION" | sed 's/ppa[0-9]*$//')
|
|
||||||
|
|
||||||
if [[ -n "$OLD_DSC_VERSION" ]] && [[ "$OLD_DSC_BASE" == "$CHANGELOG_BASE" ]]; then
|
|
||||||
if [[ "$IS_MANUAL" == true ]]; then
|
|
||||||
echo "==> Detected rebuild of same base version $CHANGELOG_BASE, incrementing version"
|
|
||||||
|
|
||||||
# If REBUILD_RELEASE is set, use that number directly
|
|
||||||
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
|
||||||
if [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git([0-9]+)(\.[a-f0-9]+)?$ ]]; then
|
|
||||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
|
||||||
GIT_NUM="${BASH_REMATCH[2]}"
|
|
||||||
GIT_HASH="${BASH_REMATCH[3]}"
|
|
||||||
NEW_VERSION="${BASE_VERSION}+git${GIT_NUM}${GIT_HASH}ppa${REBUILD_RELEASE}"
|
|
||||||
echo " Using REBUILD_RELEASE=$REBUILD_RELEASE: $CHANGELOG_VERSION -> $NEW_VERSION"
|
|
||||||
else
|
|
||||||
BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
|
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${REBUILD_RELEASE}"
|
|
||||||
echo " Using REBUILD_RELEASE=$REBUILD_RELEASE: $CHANGELOG_VERSION -> $NEW_VERSION"
|
|
||||||
fi
|
|
||||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git$ ]]; then
|
|
||||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
|
||||||
NEW_VERSION="${BASE_VERSION}+gitppa1"
|
|
||||||
echo " Adding PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
|
||||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)ppa([0-9]+)$ ]]; then
|
|
||||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
|
||||||
PPA_NUM="${BASH_REMATCH[2]}"
|
|
||||||
NEW_PPA_NUM=$((PPA_NUM + 1))
|
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${NEW_PPA_NUM}"
|
|
||||||
echo " Incrementing PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
|
||||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git([0-9]+)(\.[a-f0-9]+)?(ppa([0-9]+))?$ ]]; then
|
|
||||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
|
||||||
GIT_NUM="${BASH_REMATCH[2]}"
|
|
||||||
GIT_HASH="${BASH_REMATCH[3]}"
|
|
||||||
PPA_NUM="${BASH_REMATCH[5]}"
|
|
||||||
|
|
||||||
# Check if old DSC has ppa suffix even if changelog doesn't
|
|
||||||
if [[ -z "$PPA_NUM" ]] && [[ "$OLD_DSC_VERSION" =~ ppa([0-9]+)$ ]]; then
|
|
||||||
OLD_PPA_NUM="${BASH_REMATCH[1]}"
|
|
||||||
NEW_PPA_NUM=$((OLD_PPA_NUM + 1))
|
|
||||||
NEW_VERSION="${BASE_VERSION}+git${GIT_NUM}${GIT_HASH}ppa${NEW_PPA_NUM}"
|
|
||||||
echo " Incrementing PPA number from old DSC: $OLD_DSC_VERSION -> $NEW_VERSION"
|
|
||||||
elif [[ -n "$PPA_NUM" ]]; then
|
|
||||||
NEW_PPA_NUM=$((PPA_NUM + 1))
|
|
||||||
NEW_VERSION="${BASE_VERSION}+git${GIT_NUM}${GIT_HASH}ppa${NEW_PPA_NUM}"
|
|
||||||
echo " Incrementing PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
|
||||||
else
|
|
||||||
NEW_VERSION="${BASE_VERSION}+git${GIT_NUM}${GIT_HASH}ppa1"
|
|
||||||
echo " Adding PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
|
||||||
fi
|
|
||||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)(-([0-9]+))?$ ]]; then
|
|
||||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
|
||||||
# Check if old DSC has ppa suffix even if changelog doesn't
|
|
||||||
if [[ "$OLD_DSC_VERSION" =~ ppa([0-9]+)$ ]]; then
|
|
||||||
OLD_PPA_NUM="${BASH_REMATCH[1]}"
|
|
||||||
NEW_PPA_NUM=$((OLD_PPA_NUM + 1))
|
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${NEW_PPA_NUM}"
|
|
||||||
echo " Incrementing PPA number from old DSC: $OLD_DSC_VERSION -> $NEW_VERSION"
|
|
||||||
else
|
|
||||||
NEW_VERSION="${BASE_VERSION}ppa1"
|
|
||||||
echo " Adding PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Check if old DSC has ppa suffix for unknown formats
|
|
||||||
if [[ "$OLD_DSC_VERSION" =~ ppa([0-9]+)$ ]]; then
|
|
||||||
OLD_PPA_NUM="${BASH_REMATCH[1]}"
|
|
||||||
NEW_PPA_NUM=$((OLD_PPA_NUM + 1))
|
|
||||||
NEW_VERSION="${CHANGELOG_VERSION}ppa${NEW_PPA_NUM}"
|
|
||||||
echo " Incrementing PPA number from old DSC: $OLD_DSC_VERSION -> $NEW_VERSION"
|
|
||||||
else
|
|
||||||
NEW_VERSION="${CHANGELOG_VERSION}ppa1"
|
|
||||||
echo " Warning: Could not parse version format, appending ppa1: $CHANGELOG_VERSION -> $NEW_VERSION"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$SOURCE_DIR" ]] || [[ ! -d "$SOURCE_DIR" ]] || [[ ! -d "$SOURCE_DIR/debian" ]]; then
|
|
||||||
echo " Error: Source directory with debian/ not found for version increment"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
SOURCE_CHANGELOG="$SOURCE_DIR/debian/changelog"
|
|
||||||
if [[ ! -f "$SOURCE_CHANGELOG" ]]; then
|
|
||||||
echo " Error: Changelog not found in source directory: $SOURCE_CHANGELOG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
REPO_CHANGELOG="$REPO_ROOT/distro/debian/$PACKAGE/debian/changelog"
|
|
||||||
TEMP_CHANGELOG=$(mktemp)
|
|
||||||
{
|
|
||||||
echo "$PACKAGE ($NEW_VERSION) unstable; urgency=medium"
|
|
||||||
echo ""
|
|
||||||
echo " * Rebuild to fix repository metadata issues"
|
|
||||||
echo ""
|
|
||||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
|
||||||
echo ""
|
|
||||||
if [[ -f "$REPO_CHANGELOG" ]]; then
|
|
||||||
OLD_ENTRY_START=$(grep -n "^$PACKAGE (" "$REPO_CHANGELOG" | sed -n '2p' | cut -d: -f1)
|
|
||||||
if [[ -n "$OLD_ENTRY_START" ]]; then
|
|
||||||
tail -n +"$OLD_ENTRY_START" "$REPO_CHANGELOG"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
} >"$TEMP_CHANGELOG"
|
|
||||||
cp "$TEMP_CHANGELOG" "$SOURCE_CHANGELOG"
|
|
||||||
rm -f "$TEMP_CHANGELOG"
|
|
||||||
|
|
||||||
CHANGELOG_VERSION="$NEW_VERSION"
|
|
||||||
VERSION="$NEW_VERSION"
|
|
||||||
COMBINED_TARBALL="${PACKAGE}_${VERSION}.tar.gz"
|
|
||||||
|
|
||||||
for old_tarball in "${PACKAGE}"_*.tar.gz; do
|
|
||||||
if [[ -f "$old_tarball" ]] && [[ "$old_tarball" != "${PACKAGE}_${NEW_VERSION}.tar.gz" ]]; then
|
|
||||||
echo " Removing old tarball from OBS: $old_tarball"
|
|
||||||
osc rm -f "$old_tarball" 2>/dev/null || rm -f "$old_tarball"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$PACKAGE" == "dms" ]] && [[ -f "$WORK_DIR/dms-source.tar.gz" ]]; then
|
|
||||||
echo " Recreating dms-source.tar.gz with new directory name for incremented version"
|
|
||||||
EXPECTED_SOURCE_DIR="DankMaterialShell-${NEW_VERSION}"
|
|
||||||
TEMP_SOURCE_DIR=$(mktemp -d)
|
|
||||||
cd "$TEMP_SOURCE_DIR"
|
|
||||||
tar -xzf "$WORK_DIR/dms-source.tar.gz" 2>/dev/null || tar -xJf "$WORK_DIR/dms-source.tar.gz" 2>/dev/null || tar -xjf "$WORK_DIR/dms-source.tar.gz" 2>/dev/null
|
|
||||||
EXTRACTED=$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" | head -1)
|
|
||||||
if [[ -n "$EXTRACTED" ]] && [[ "$EXTRACTED" != "./$EXPECTED_SOURCE_DIR" ]]; then
|
|
||||||
echo " Renaming $EXTRACTED to $EXPECTED_SOURCE_DIR"
|
|
||||||
mv "$EXTRACTED" "$EXPECTED_SOURCE_DIR"
|
|
||||||
rm -f "$WORK_DIR/dms-source.tar.gz"
|
|
||||||
if ! tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/dms-source.tar.gz" "$EXPECTED_SOURCE_DIR"; then
|
|
||||||
echo " Error: Failed to create dms-source.tar.gz"
|
|
||||||
ls -lah "$EXPECTED_SOURCE_DIR" | head -20
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ ! -f "$WORK_DIR/dms-source.tar.gz" ]]; then
|
|
||||||
echo " Error: dms-source.tar.gz was not created"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
ROOT_DIR=$(tar -tf "$WORK_DIR/dms-source.tar.gz" | head -1 | cut -d/ -f1)
|
|
||||||
if [[ "$ROOT_DIR" != "$EXPECTED_SOURCE_DIR" ]]; then
|
|
||||||
echo " Error: Recreated tarball has wrong root directory: $ROOT_DIR (expected $EXPECTED_SOURCE_DIR)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
cd "$REPO_ROOT"
|
|
||||||
rm -rf "$TEMP_SOURCE_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo " Recreating tarball with new version: $COMBINED_TARBALL"
|
|
||||||
if [[ -n "$SOURCE_DIR" ]] && [[ -d "$SOURCE_DIR" ]] && [[ -d "$SOURCE_DIR/debian" ]]; then
|
|
||||||
if [[ "$PACKAGE" == "dms" ]]; then
|
|
||||||
cd "$(dirname "$SOURCE_DIR")"
|
|
||||||
CURRENT_DIR=$(basename "$SOURCE_DIR")
|
|
||||||
EXPECTED_DIR="DankMaterialShell-${NEW_VERSION}"
|
|
||||||
if [[ "$CURRENT_DIR" != "$EXPECTED_DIR" ]]; then
|
|
||||||
echo " Renaming directory from $CURRENT_DIR to $EXPECTED_DIR to match debian/rules"
|
|
||||||
if [[ -d "$CURRENT_DIR" ]]; then
|
|
||||||
mv "$CURRENT_DIR" "$EXPECTED_DIR"
|
|
||||||
SOURCE_DIR="$(pwd)/$EXPECTED_DIR"
|
|
||||||
else
|
|
||||||
echo " Warning: Source directory $CURRENT_DIR not found, extracting from existing tarball"
|
|
||||||
OLD_TARBALL=$(ls "${PACKAGE}"_*.tar.gz 2>/dev/null | head -1)
|
|
||||||
if [[ -f "$OLD_TARBALL" ]]; then
|
|
||||||
EXTRACT_DIR=$(mktemp -d)
|
|
||||||
cd "$EXTRACT_DIR"
|
|
||||||
tar -xzf "$WORK_DIR/$OLD_TARBALL"
|
|
||||||
EXTRACTED_DIR=$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" | head -1)
|
|
||||||
if [[ -n "$EXTRACTED_DIR" ]] && [[ "$EXTRACTED_DIR" != "./$EXPECTED_DIR" ]]; then
|
|
||||||
mv "$EXTRACTED_DIR" "$EXPECTED_DIR"
|
|
||||||
if [[ -f "$EXPECTED_DIR/debian/changelog" ]]; then
|
|
||||||
ACTUAL_VER=$(grep -m1 "^$PACKAGE" "$EXPECTED_DIR/debian/changelog" 2>/dev/null | sed 's/.*(\([^)]*\)).*/\1/')
|
|
||||||
if [[ "$ACTUAL_VER" != "$NEW_VERSION" ]]; then
|
|
||||||
echo " Updating changelog version in extracted directory"
|
|
||||||
REPO_CHANGELOG="$REPO_ROOT/distro/debian/$PACKAGE/debian/changelog"
|
|
||||||
TEMP_CHANGELOG=$(mktemp)
|
|
||||||
{
|
|
||||||
echo "$PACKAGE ($NEW_VERSION) unstable; urgency=medium"
|
|
||||||
echo ""
|
|
||||||
echo " * Rebuild to fix repository metadata issues"
|
|
||||||
echo ""
|
|
||||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
|
||||||
echo ""
|
|
||||||
if [[ -f "$REPO_CHANGELOG" ]]; then
|
|
||||||
OLD_ENTRY_START=$(grep -n "^$PACKAGE (" "$REPO_CHANGELOG" | sed -n '2p' | cut -d: -f1)
|
|
||||||
if [[ -n "$OLD_ENTRY_START" ]]; then
|
|
||||||
tail -n +"$OLD_ENTRY_START" "$REPO_CHANGELOG"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
} >"$TEMP_CHANGELOG"
|
|
||||||
cp "$TEMP_CHANGELOG" "$EXPECTED_DIR/debian/changelog"
|
|
||||||
rm -f "$TEMP_CHANGELOG"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
SOURCE_DIR="$(pwd)/$EXPECTED_DIR"
|
|
||||||
cd "$REPO_ROOT"
|
|
||||||
else
|
|
||||||
echo " Error: Could not extract or find source directory"
|
|
||||||
rm -rf "$EXTRACT_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$WORK_DIR/$COMBINED_TARBALL"
|
|
||||||
|
|
||||||
echo " Creating combined tarball: $COMBINED_TARBALL"
|
|
||||||
cd "$(dirname "$SOURCE_DIR")"
|
|
||||||
TARBALL_BASE=$(basename "$SOURCE_DIR")
|
|
||||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
|
||||||
cd "$REPO_ROOT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
TARBALL_SIZE=$(stat -c%s "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null || stat -f%z "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null)
|
|
||||||
TARBALL_MD5=$(md5sum "$WORK_DIR/$COMBINED_TARBALL" | cut -d' ' -f1)
|
|
||||||
|
|
||||||
# Extract Build-Depends from debian/control using awk for proper multi-line parsing
|
|
||||||
if [[ -f "$REPO_ROOT/distro/debian/$PACKAGE/debian/control" ]]; then
|
|
||||||
BUILD_DEPS=$(awk '
|
|
||||||
/^Build-Depends:/ {
|
|
||||||
in_build_deps=1;
|
|
||||||
sub(/^Build-Depends:[[:space:]]*/, "");
|
|
||||||
printf "%s", $0;
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
in_build_deps && /^[[:space:]]/ {
|
|
||||||
sub(/^[[:space:]]+/, " ");
|
|
||||||
printf "%s", $0;
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
in_build_deps { exit; }
|
|
||||||
' "$REPO_ROOT/distro/debian/$PACKAGE/debian/control" | sed 's/[[:space:]]\+/ /g; s/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
||||||
|
|
||||||
# If extraction failed or is empty, use default fallback
|
|
||||||
if [[ -z "$BUILD_DEPS" ]]; then
|
|
||||||
BUILD_DEPS="debhelper-compat (= 13)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
BUILD_DEPS="debhelper-compat (= 13)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat >"$WORK_DIR/$PACKAGE.dsc" <<EOF
|
|
||||||
Format: 3.0 (native)
|
|
||||||
Source: $PACKAGE
|
|
||||||
Binary: $PACKAGE
|
|
||||||
Architecture: any
|
|
||||||
Version: $VERSION
|
|
||||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
|
||||||
Build-Depends: $BUILD_DEPS
|
|
||||||
Files:
|
|
||||||
$TARBALL_MD5 $TARBALL_SIZE $COMBINED_TARBALL
|
|
||||||
EOF
|
|
||||||
echo " - Updated changelog and recreated tarball with version $NEW_VERSION"
|
|
||||||
else
|
|
||||||
echo "==> Detected same version. Not a manual run, skipping Debian version increment."
|
|
||||||
echo "✅ No changes needed for Debian. Exiting."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure we're in WORK_DIR and it exists
|
# Ensure we're in WORK_DIR and it exists
|
||||||
|
|||||||
+247
-296
@@ -36,7 +36,6 @@ fi
|
|||||||
PACKAGE_DIR="$1"
|
PACKAGE_DIR="$1"
|
||||||
UBUNTU_SERIES="${2:-noble}"
|
UBUNTU_SERIES="${2:-noble}"
|
||||||
|
|
||||||
# Validate package directory
|
|
||||||
if [ ! -d "$PACKAGE_DIR" ]; then
|
if [ ! -d "$PACKAGE_DIR" ]; then
|
||||||
error "Package directory not found: $PACKAGE_DIR"
|
error "Package directory not found: $PACKAGE_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -47,21 +46,43 @@ if [ ! -d "$PACKAGE_DIR/debian" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get absolute path
|
|
||||||
PACKAGE_DIR=$(cd "$PACKAGE_DIR" && pwd)
|
PACKAGE_DIR=$(cd "$PACKAGE_DIR" && pwd)
|
||||||
PACKAGE_NAME=$(basename "$PACKAGE_DIR")
|
PACKAGE_NAME=$(basename "$PACKAGE_DIR")
|
||||||
PACKAGE_PARENT=$(dirname "$PACKAGE_DIR")
|
PACKAGE_PARENT=$(dirname "$PACKAGE_DIR")
|
||||||
|
|
||||||
# Create temporary working directory (like OBS)
|
# Choose temp directory: use /tmp in CI, ~/tmp locally (keeps artifacts out of repo)
|
||||||
TEMP_WORK_DIR=$(mktemp -d -t ppa_build_work_XXXXXX)
|
if [[ -n "${GITHUB_ACTIONS:-}" ]] || [[ -n "${CI:-}" ]]; then
|
||||||
trap 'rm -rf "$TEMP_WORK_DIR"' EXIT
|
TEMP_BASE="/tmp"
|
||||||
|
else
|
||||||
|
TEMP_BASE="$HOME/tmp"
|
||||||
|
mkdir -p "$TEMP_BASE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TEMP_WORK_DIR=$(mktemp -d "$TEMP_BASE/ppa_build_work_XXXXXX")
|
||||||
|
|
||||||
|
# Cleanup function for temp directories
|
||||||
|
cleanup_temp_dirs() {
|
||||||
|
if [[ -z "${PPA_UPLOAD_SCRIPT:-}" ]] && [[ -d "${TEMP_WORK_DIR:-}" ]]; then
|
||||||
|
rm -rf "$TEMP_WORK_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "${TEMP_CLONE:-}" ]]; then
|
||||||
|
rm -rf "$TEMP_CLONE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for temp_dir in "$TEMP_BASE"/ppa_clone_* "$TEMP_BASE"/ppa_tag_*; do
|
||||||
|
if [[ -d "$temp_dir" ]]; then
|
||||||
|
rm -rf "$temp_dir" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup_temp_dirs EXIT
|
||||||
|
|
||||||
info "Building source package for: $PACKAGE_NAME"
|
info "Building source package for: $PACKAGE_NAME"
|
||||||
info "Package directory: $PACKAGE_DIR"
|
info "Package directory: $PACKAGE_DIR"
|
||||||
info "Working directory: $TEMP_WORK_DIR"
|
info "Working directory: $TEMP_WORK_DIR"
|
||||||
info "Target Ubuntu series: $UBUNTU_SERIES"
|
info "Target Ubuntu series: $UBUNTU_SERIES"
|
||||||
|
|
||||||
# Check for required files
|
|
||||||
REQUIRED_FILES=(
|
REQUIRED_FILES=(
|
||||||
"debian/control"
|
"debian/control"
|
||||||
"debian/rules"
|
"debian/rules"
|
||||||
@@ -87,14 +108,64 @@ fi
|
|||||||
|
|
||||||
success "GPG key found"
|
success "GPG key found"
|
||||||
|
|
||||||
# Check if debuild is installed
|
# Function to get PPA name from package name
|
||||||
|
get_ppa_name() {
|
||||||
|
local pkg="$1"
|
||||||
|
case "$pkg" in
|
||||||
|
dms) echo "dms" ;;
|
||||||
|
dms-git) echo "dms-git" ;;
|
||||||
|
dms-greeter) echo "danklinux" ;;
|
||||||
|
*) echo "" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parameters:
|
||||||
|
# $1 = PPA_NAME
|
||||||
|
# $2 = SOURCE_NAME
|
||||||
|
# $3 = VERSION
|
||||||
|
# $4 = CHECK_MODE Exact version match, "commit" = check commit hash (default)
|
||||||
|
check_ppa_version_exists() {
|
||||||
|
local PPA_NAME="$1"
|
||||||
|
local SOURCE_NAME="$2"
|
||||||
|
local VERSION="$3"
|
||||||
|
local CHECK_MODE="${4:-commit}"
|
||||||
|
|
||||||
|
# Query Launchpad API
|
||||||
|
PPA_VERSION=$(curl -s \
|
||||||
|
"https://api.launchpad.net/1.0/~avengemedia/+archive/ubuntu/$PPA_NAME?ws.op=getPublishedSources&source_name=$SOURCE_NAME&status=Published" \
|
||||||
|
| grep -oP '"source_package_version":\s*"\K[^"]+' | head -1 || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$PPA_VERSION" ]]; then
|
||||||
|
# For git packages with "commit" mode, check if same commit already exists
|
||||||
|
if [[ "$CHECK_MODE" == "commit" ]] && [[ "$SOURCE_NAME" == *"-git" ]]; then
|
||||||
|
# Extract commit hash from versions (e.g., 79794d34 from 1.0.2+git2546.79794d34ppa2)
|
||||||
|
PPA_COMMIT=$(echo "$PPA_VERSION" | grep -oP '\.[a-f0-9]{8}(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||||
|
NEW_COMMIT=$(echo "$VERSION" | grep -oP '\.[a-f0-9]{8}(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$PPA_COMMIT" && -n "$NEW_COMMIT" && "$PPA_COMMIT" == "$NEW_COMMIT" ]]; then
|
||||||
|
warn "Commit $NEW_COMMIT already exists in PPA (current version: $PPA_VERSION)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Exact version match check (always performed)
|
||||||
|
if [[ "$PPA_VERSION" == "$VERSION" ]]; then
|
||||||
|
warn "Version $VERSION already exists in PPA"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not fetch PPA version (API may be unavailable), proceeding anyway"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if ! command -v debuild &>/dev/null; then
|
if ! command -v debuild &>/dev/null; then
|
||||||
error "debuild not found. Install devscripts:"
|
error "debuild not found. Install devscripts:"
|
||||||
error " sudo dnf install devscripts"
|
error " sudo dnf install devscripts"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Extract package info from changelog
|
|
||||||
cd "$PACKAGE_DIR"
|
cd "$PACKAGE_DIR"
|
||||||
CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version)
|
CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version)
|
||||||
SOURCE_NAME=$(dpkg-parsechangelog -S Source)
|
SOURCE_NAME=$(dpkg-parsechangelog -S Source)
|
||||||
@@ -102,41 +173,24 @@ SOURCE_NAME=$(dpkg-parsechangelog -S Source)
|
|||||||
info "Source package: $SOURCE_NAME"
|
info "Source package: $SOURCE_NAME"
|
||||||
info "Version: $CHANGELOG_VERSION"
|
info "Version: $CHANGELOG_VERSION"
|
||||||
|
|
||||||
# Check if version targets correct Ubuntu series
|
|
||||||
CHANGELOG_SERIES=$(dpkg-parsechangelog -S Distribution)
|
CHANGELOG_SERIES=$(dpkg-parsechangelog -S Distribution)
|
||||||
if [ "$CHANGELOG_SERIES" != "$UBUNTU_SERIES" ] && [ "$CHANGELOG_SERIES" != "UNRELEASED" ]; then
|
if [ "$CHANGELOG_SERIES" != "$UBUNTU_SERIES" ] && [ "$CHANGELOG_SERIES" != "UNRELEASED" ]; then
|
||||||
warn "Changelog targets '$CHANGELOG_SERIES' but building for '$UBUNTU_SERIES'"
|
warn "Changelog targets '$CHANGELOG_SERIES' but building for '$UBUNTU_SERIES'"
|
||||||
warn "Consider updating changelog with: dch -r '' -D $UBUNTU_SERIES"
|
warn "Consider updating changelog with: dch -r '' -D $UBUNTU_SERIES"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if this is a manual run or automated
|
|
||||||
IS_MANUAL=false
|
|
||||||
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
|
||||||
IS_MANUAL=true
|
|
||||||
echo "==> Manual rebuild detected (REBUILD_RELEASE=$REBUILD_RELEASE)"
|
|
||||||
elif [[ -n "${FORCE_REBUILD:-}" ]] && [[ "${FORCE_REBUILD}" == "true" ]]; then
|
|
||||||
IS_MANUAL=true
|
|
||||||
echo "==> Manual workflow trigger detected (FORCE_REBUILD=true)"
|
|
||||||
elif [[ "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" ]]; then
|
|
||||||
IS_MANUAL=true
|
|
||||||
echo "==> Manual workflow trigger detected (workflow_dispatch)"
|
|
||||||
elif [[ -z "${GITHUB_ACTIONS:-}" ]] && [[ -z "${CI:-}" ]]; then
|
|
||||||
IS_MANUAL=true
|
|
||||||
echo "==> Local/manual run detected (not in CI)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy package to temp working directory
|
|
||||||
info "Copying package to working directory..."
|
info "Copying package to working directory..."
|
||||||
cp -r "$PACKAGE_DIR" "$TEMP_WORK_DIR/"
|
cp -r "$PACKAGE_DIR" "$TEMP_WORK_DIR/"
|
||||||
WORK_PACKAGE_DIR="$TEMP_WORK_DIR/$PACKAGE_NAME"
|
WORK_PACKAGE_DIR="$TEMP_WORK_DIR/$PACKAGE_NAME"
|
||||||
|
|
||||||
# Detect package type and update version automatically
|
if [ -f "$WORK_PACKAGE_DIR/debian/files" ]; then
|
||||||
cd "$WORK_PACKAGE_DIR"
|
info "Removing old debian/files build artifact..."
|
||||||
|
rm -f "$WORK_PACKAGE_DIR/debian/files"
|
||||||
|
fi
|
||||||
|
|
||||||
# Function to get latest tag from GitHub
|
cd "$WORK_PACKAGE_DIR"
|
||||||
get_latest_tag() {
|
get_latest_tag() {
|
||||||
local repo="$1"
|
local repo="$1"
|
||||||
# Try GitHub API first (faster)
|
|
||||||
if command -v curl &>/dev/null; then
|
if command -v curl &>/dev/null; then
|
||||||
LATEST_TAG=$(curl -s "https://api.github.com/repos/$repo/releases/latest" 2>/dev/null | grep '"tag_name":' | sed 's/.*"tag_name": "\(.*\)".*/\1/' | head -1)
|
LATEST_TAG=$(curl -s "https://api.github.com/repos/$repo/releases/latest" 2>/dev/null | grep '"tag_name":' | sed 's/.*"tag_name": "\(.*\)".*/\1/' | head -1)
|
||||||
if [ -n "$LATEST_TAG" ]; then
|
if [ -n "$LATEST_TAG" ]; then
|
||||||
@@ -144,8 +198,7 @@ get_latest_tag() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Fallback: clone and get latest tag
|
TEMP_REPO=$(mktemp -d "$TEMP_BASE/ppa_tag_XXXXXX")
|
||||||
TEMP_REPO=$(mktemp -d)
|
|
||||||
if git clone --depth=1 --quiet "https://github.com/$repo.git" "$TEMP_REPO" 2>/dev/null; then
|
if git clone --depth=1 --quiet "https://github.com/$repo.git" "$TEMP_REPO" 2>/dev/null; then
|
||||||
LATEST_TAG=$(cd "$TEMP_REPO" && git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "")
|
LATEST_TAG=$(cd "$TEMP_REPO" && git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "")
|
||||||
rm -rf "$TEMP_REPO"
|
rm -rf "$TEMP_REPO"
|
||||||
@@ -153,27 +206,21 @@ get_latest_tag() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Detect if package is git-based
|
|
||||||
IS_GIT_PACKAGE=false
|
IS_GIT_PACKAGE=false
|
||||||
GIT_REPO=""
|
GIT_REPO=""
|
||||||
SOURCE_DIR=""
|
SOURCE_DIR=""
|
||||||
|
|
||||||
# Check package name for -git suffix
|
|
||||||
if [[ "$PACKAGE_NAME" == *"-git" ]]; then
|
if [[ "$PACKAGE_NAME" == *"-git" ]]; then
|
||||||
IS_GIT_PACKAGE=true
|
IS_GIT_PACKAGE=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check rules file for git clone patterns and extract repo
|
|
||||||
if grep -q "git clone" debian/rules 2>/dev/null; then
|
if grep -q "git clone" debian/rules 2>/dev/null; then
|
||||||
IS_GIT_PACKAGE=true
|
IS_GIT_PACKAGE=true
|
||||||
# Extract GitHub repo URL from rules
|
|
||||||
GIT_URL=$(grep -o "git clone.*https://github.com/[^/]*/[^/]*\.git" debian/rules 2>/dev/null | head -1 | sed 's/.*github\.com\///' | sed 's/\.git.*//' || echo "")
|
GIT_URL=$(grep -o "git clone.*https://github.com/[^/]*/[^/]*\.git" debian/rules 2>/dev/null | head -1 | sed 's/.*github\.com\///' | sed 's/\.git.*//' || echo "")
|
||||||
if [ -n "$GIT_URL" ]; then
|
if [ -n "$GIT_URL" ]; then
|
||||||
GIT_REPO="$GIT_URL"
|
GIT_REPO="$GIT_URL"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Special handling for known packages
|
|
||||||
case "$PACKAGE_NAME" in
|
case "$PACKAGE_NAME" in
|
||||||
dms-git)
|
dms-git)
|
||||||
IS_GIT_PACKAGE=true
|
IS_GIT_PACKAGE=true
|
||||||
@@ -182,12 +229,101 @@ dms-git)
|
|||||||
;;
|
;;
|
||||||
dms)
|
dms)
|
||||||
GIT_REPO="AvengeMedia/DankMaterialShell"
|
GIT_REPO="AvengeMedia/DankMaterialShell"
|
||||||
info "Downloading pre-built binaries and source for dms..."
|
;;
|
||||||
# Get version from changelog (remove ppa suffix for both quilt and native formats)
|
dms-greeter)
|
||||||
# Native: 0.5.2ppa1 -> 0.5.2, Quilt: 0.5.2-1ppa1 -> 0.5.2
|
GIT_REPO="AvengeMedia/DankMaterialShell"
|
||||||
|
;;
|
||||||
|
danksearch)
|
||||||
|
GIT_REPO="AvengeMedia/danksearch"
|
||||||
|
;;
|
||||||
|
dgop)
|
||||||
|
GIT_REPO="AvengeMedia/dgop"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Handle stable packages - update changelog FIRST before downloads
|
||||||
|
if [ "$IS_GIT_PACKAGE" = false ] && [ -n "$GIT_REPO" ]; then
|
||||||
|
info "Detected stable package: $PACKAGE_NAME"
|
||||||
|
info "Fetching latest tag from $GIT_REPO..."
|
||||||
|
|
||||||
|
LATEST_TAG=$(get_latest_tag "$GIT_REPO")
|
||||||
|
if [ -n "$LATEST_TAG" ]; then
|
||||||
|
SOURCE_FORMAT=$(head -1 debian/source/format 2>/dev/null || echo "3.0 (quilt)")
|
||||||
|
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
||||||
|
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
||||||
|
PPA_NUM=$REBUILD_RELEASE
|
||||||
|
info "Using REBUILD_RELEASE=$REBUILD_RELEASE for PPA number"
|
||||||
|
else
|
||||||
|
PPA_NUM=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$SOURCE_FORMAT" == *"native"* ]]; then
|
||||||
|
BASE_VERSION="${LATEST_TAG}"
|
||||||
|
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||||
|
else
|
||||||
|
BASE_VERSION="${LATEST_TAG}-1"
|
||||||
|
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if this version already exists in PPA (for stable packages, use exact match)
|
||||||
|
PPA_NAME=$(get_ppa_name "$PACKAGE_NAME")
|
||||||
|
if [[ -n "$PPA_NAME" ]]; then
|
||||||
|
info "Checking if version $NEW_VERSION already exists in PPA..."
|
||||||
|
if [[ -z "${REBUILD_RELEASE:-}" ]]; then
|
||||||
|
if check_ppa_version_exists "$PPA_NAME" "$SOURCE_NAME" "${BASE_VERSION}ppa1" "exact"; then
|
||||||
|
error "==> Error: Version ${BASE_VERSION}ppa1 already exists in PPA $PPA_NAME"
|
||||||
|
error " To rebuild with a different release number, use:"
|
||||||
|
error " ./distro/scripts/ppa-upload.sh $PACKAGE_NAME 2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if check_ppa_version_exists "$PPA_NAME" "$SOURCE_NAME" "$NEW_VERSION" "exact"; then
|
||||||
|
error "==> Error: Version $NEW_VERSION already exists in PPA $PPA_NAME"
|
||||||
|
NEXT_NUM=$((REBUILD_RELEASE + 1))
|
||||||
|
error " To rebuild with a different release number, use:"
|
||||||
|
error " ./distro/scripts/ppa-upload.sh $PACKAGE_NAME $NEXT_NUM"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
|
||||||
|
if [ "$PPA_NUM" -gt 1 ]; then
|
||||||
|
info "Updating changelog for rebuild (PPA number incremented to $PPA_NUM)"
|
||||||
|
else
|
||||||
|
info "Updating changelog to latest tag: $LATEST_TAG"
|
||||||
|
fi
|
||||||
|
if [ "$PPA_NUM" -gt 1 ]; then
|
||||||
|
CHANGELOG_MSG="Rebuild for packaging fixes (ppa${PPA_NUM})"
|
||||||
|
else
|
||||||
|
CHANGELOG_MSG="Upstream release ${LATEST_TAG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
EOF
|
||||||
|
success "Version updated to $NEW_VERSION"
|
||||||
|
CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version)
|
||||||
|
|
||||||
|
# Note: No longer writing back to repository (changelog stays as template)
|
||||||
|
else
|
||||||
|
info "Version already at latest tag: $LATEST_TAG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not determine latest tag for $GIT_REPO, using existing version"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download binaries/source using the updated version from changelog
|
||||||
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
|
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
|
||||||
|
|
||||||
# Download amd64 binary (will be included in source package)
|
case "$PACKAGE_NAME" in
|
||||||
|
dms)
|
||||||
|
info "Downloading pre-built binaries and source for dms..."
|
||||||
if [ ! -f "dms-distropkg-amd64.gz" ]; then
|
if [ ! -f "dms-distropkg-amd64.gz" ]; then
|
||||||
info "Downloading dms binary for amd64..."
|
info "Downloading dms binary for amd64..."
|
||||||
if wget -O dms-distropkg-amd64.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-distropkg-amd64.gz"; then
|
if wget -O dms-distropkg-amd64.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-distropkg-amd64.gz"; then
|
||||||
@@ -198,7 +334,6 @@ dms)
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Download source tarball for QML files
|
|
||||||
if [ ! -f "dms-source.tar.gz" ]; then
|
if [ ! -f "dms-source.tar.gz" ]; then
|
||||||
info "Downloading dms source for QML files..."
|
info "Downloading dms source for QML files..."
|
||||||
if wget -O dms-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then
|
if wget -O dms-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then
|
||||||
@@ -210,10 +345,7 @@ dms)
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
dms-greeter)
|
dms-greeter)
|
||||||
GIT_REPO="AvengeMedia/DankMaterialShell"
|
|
||||||
info "Downloading source for dms-greeter..."
|
info "Downloading source for dms-greeter..."
|
||||||
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
|
|
||||||
|
|
||||||
if [ ! -f "dms-greeter-source.tar.gz" ]; then
|
if [ ! -f "dms-greeter-source.tar.gz" ]; then
|
||||||
info "Downloading dms-greeter source..."
|
info "Downloading dms-greeter source..."
|
||||||
if wget -O dms-greeter-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then
|
if wget -O dms-greeter-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then
|
||||||
@@ -224,23 +356,14 @@ dms-greeter)
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
danksearch)
|
|
||||||
# danksearch uses pre-built binary from releases
|
|
||||||
GIT_REPO="AvengeMedia/danksearch"
|
|
||||||
;;
|
|
||||||
dgop)
|
|
||||||
# dgop uses pre-built binary from releases
|
|
||||||
GIT_REPO="AvengeMedia/dgop"
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
# Handle git packages
|
# Handle git packages
|
||||||
if [ "$IS_GIT_PACKAGE" = true ] && [ -n "$GIT_REPO" ]; then
|
if [ "$IS_GIT_PACKAGE" = true ] && [ -n "$GIT_REPO" ]; then
|
||||||
info "Detected git package: $PACKAGE_NAME"
|
info "Detected git package: $PACKAGE_NAME"
|
||||||
|
|
||||||
# Determine source directory name
|
|
||||||
if [ -z "$SOURCE_DIR" ]; then
|
if [ -z "$SOURCE_DIR" ]; then
|
||||||
# Default: use package name without -git suffix + -source or -repo
|
|
||||||
BASE_NAME=$(echo "$PACKAGE_NAME" | sed 's/-git$//')
|
BASE_NAME=$(echo "$PACKAGE_NAME" | sed 's/-git$//')
|
||||||
if [ -d "${BASE_NAME}-source" ] 2>/dev/null; then
|
if [ -d "${BASE_NAME}-source" ] 2>/dev/null; then
|
||||||
SOURCE_DIR="${BASE_NAME}-source"
|
SOURCE_DIR="${BASE_NAME}-source"
|
||||||
@@ -253,27 +376,18 @@ if [ "$IS_GIT_PACKAGE" = true ] && [ -n "$GIT_REPO" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Always clone fresh source to get latest commit info
|
|
||||||
info "Cloning $GIT_REPO from GitHub (getting latest commit info)..."
|
info "Cloning $GIT_REPO from GitHub (getting latest commit info)..."
|
||||||
TEMP_CLONE=$(mktemp -d)
|
TEMP_CLONE=$(mktemp -d "$TEMP_BASE/ppa_clone_XXXXXX")
|
||||||
if git clone "https://github.com/$GIT_REPO.git" "$TEMP_CLONE"; then
|
if git clone "https://github.com/$GIT_REPO.git" "$TEMP_CLONE"; then
|
||||||
# Get git commit info from fresh clone
|
|
||||||
GIT_COMMIT_HASH=$(cd "$TEMP_CLONE" && git rev-parse --short HEAD)
|
GIT_COMMIT_HASH=$(cd "$TEMP_CLONE" && git rev-parse --short HEAD)
|
||||||
GIT_COMMIT_COUNT=$(cd "$TEMP_CLONE" && git rev-list --count HEAD)
|
GIT_COMMIT_COUNT=$(cd "$TEMP_CLONE" && git rev-list --count HEAD)
|
||||||
|
|
||||||
# Get upstream version from latest git tag (e.g., 0.2.1)
|
|
||||||
# Sort all tags by version and get the latest one (not just the one reachable from HEAD)
|
|
||||||
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git tag -l "v*" | sed 's/^v//' | sort -V | tail -1)
|
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git tag -l "v*" | sed 's/^v//' | sort -V | tail -1)
|
||||||
if [ -z "$UPSTREAM_VERSION" ]; then
|
if [ -z "$UPSTREAM_VERSION" ]; then
|
||||||
# Fallback: try without v prefix
|
|
||||||
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1)
|
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1)
|
||||||
fi
|
fi
|
||||||
if [ -z "$UPSTREAM_VERSION" ]; then
|
if [ -z "$UPSTREAM_VERSION" ]; then
|
||||||
# Last resort: use git describe
|
|
||||||
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.1")
|
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.1")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify we got valid commit info
|
|
||||||
if [ -z "$GIT_COMMIT_COUNT" ] || [ "$GIT_COMMIT_COUNT" = "0" ]; then
|
if [ -z "$GIT_COMMIT_COUNT" ] || [ "$GIT_COMMIT_COUNT" = "0" ]; then
|
||||||
error "Failed to get commit count from $GIT_REPO"
|
error "Failed to get commit count from $GIT_REPO"
|
||||||
rm -rf "$TEMP_CLONE"
|
rm -rf "$TEMP_CLONE"
|
||||||
@@ -288,82 +402,85 @@ if [ "$IS_GIT_PACKAGE" = true ] && [ -n "$GIT_REPO" ]; then
|
|||||||
|
|
||||||
success "Got commit info: $GIT_COMMIT_COUNT ($GIT_COMMIT_HASH), upstream: $UPSTREAM_VERSION"
|
success "Got commit info: $GIT_COMMIT_COUNT ($GIT_COMMIT_HASH), upstream: $UPSTREAM_VERSION"
|
||||||
|
|
||||||
# Update changelog with git commit info
|
# Build base version (without ppa suffix yet)
|
||||||
info "Updating changelog with git commit info..."
|
|
||||||
# Format: 0.2.1+git705.fdbb86appa1
|
|
||||||
# Check if we're rebuilding the same commit (increment PPA number if so)
|
|
||||||
BASE_VERSION="${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}"
|
BASE_VERSION="${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}"
|
||||||
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
# Use REBUILD_RELEASE if provided, otherwise auto-increment
|
# EARLY VERSION CHECK
|
||||||
|
PPA_NAME=$(get_ppa_name "$PACKAGE_NAME")
|
||||||
|
if [[ -n "$PPA_NAME" ]]; then
|
||||||
|
if [[ -z "${REBUILD_RELEASE:-}" ]]; then
|
||||||
|
info "Checking if commit $GIT_COMMIT_HASH already exists in PPA..."
|
||||||
|
if check_ppa_version_exists "$PPA_NAME" "$SOURCE_NAME" "${BASE_VERSION}ppa1" "commit"; then
|
||||||
|
error "==> Error: This commit is already uploaded to PPA"
|
||||||
|
error " The same git commit ($GIT_COMMIT_HASH) already exists in PPA."
|
||||||
|
error " To rebuild the same commit, specify a rebuild number:"
|
||||||
|
error " ./distro/scripts/ppa-upload.sh $PACKAGE_NAME 2"
|
||||||
|
error " ./distro/scripts/ppa-upload.sh $PACKAGE_NAME 3"
|
||||||
|
error " Or with build script directly:"
|
||||||
|
error " REBUILD_RELEASE=2 ./distro/scripts/ppa-build.sh $PACKAGE_DIR"
|
||||||
|
error " Or push a new commit first, then run:"
|
||||||
|
error " ./distro/scripts/ppa-upload.sh $PACKAGE_NAME"
|
||||||
|
rm -rf "$TEMP_CLONE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
PPA_NUM=1
|
||||||
|
info "Using PPA number $PPA_NUM"
|
||||||
|
else
|
||||||
|
PPA_NUM=$REBUILD_RELEASE
|
||||||
|
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||||
|
info "Checking if version $NEW_VERSION already exists in PPA..."
|
||||||
|
if check_ppa_version_exists "$PPA_NAME" "$SOURCE_NAME" "$NEW_VERSION" "exact"; then
|
||||||
|
error "==> Error: Version $NEW_VERSION already exists in PPA"
|
||||||
|
error " This exact version (including ppa${PPA_NUM}) is already uploaded."
|
||||||
|
NEXT_NUM=$((PPA_NUM + 1))
|
||||||
|
error " To rebuild with a different release number, try incrementing:"
|
||||||
|
error " ./distro/scripts/ppa-upload.sh $PACKAGE_NAME $NEXT_NUM"
|
||||||
|
error " Or with build script directly:"
|
||||||
|
error " REBUILD_RELEASE=$NEXT_NUM ./distro/scripts/ppa-build.sh $PACKAGE_DIR"
|
||||||
|
rm -rf "$TEMP_CLONE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
info "Using REBUILD_RELEASE=$REBUILD_RELEASE for PPA number"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# No PPA name found, use default
|
||||||
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
||||||
PPA_NUM=$REBUILD_RELEASE
|
PPA_NUM=$REBUILD_RELEASE
|
||||||
info "Using REBUILD_RELEASE=$REBUILD_RELEASE for PPA number"
|
info "Using REBUILD_RELEASE=$REBUILD_RELEASE for PPA number"
|
||||||
else
|
else
|
||||||
PPA_NUM=1
|
PPA_NUM=1
|
||||||
|
info "Using PPA number $PPA_NUM"
|
||||||
# If current version matches the base version, increment PPA number
|
|
||||||
# Escape special regex characters in BASE_VERSION for pattern matching
|
|
||||||
ESCAPED_BASE=$(echo "$BASE_VERSION" | sed 's/\./\\./g' | sed 's/+/\\+/g')
|
|
||||||
if [[ "$CURRENT_VERSION" =~ ^${ESCAPED_BASE}ppa([0-9]+)$ ]]; then
|
|
||||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
|
||||||
if [[ "$IS_MANUAL" == true ]]; then
|
|
||||||
info "Detected rebuild of same commit (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
|
||||||
else
|
|
||||||
info "Detected rebuild of same commit (current: $CURRENT_VERSION). Not a manual run, skipping."
|
|
||||||
success "No changes needed (commit matches)."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
info "New commit or first build, using PPA number $PPA_NUM"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||||
|
info "Updating changelog with git commit info..."
|
||||||
|
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
||||||
|
|
||||||
# Use sed to update changelog (non-interactive, faster)
|
# Single changelog entry (git snapshots don't need history)
|
||||||
# Get current changelog content - find the next package header line (starts with package name)
|
cat >debian/changelog <<EOF
|
||||||
# Skip the first entry entirely by finding the second occurrence of the package name at start of line
|
${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
|
||||||
OLD_ENTRY_START=$(grep -n "^${SOURCE_NAME} (" debian/changelog | sed -n '2p' | cut -d: -f1)
|
|
||||||
if [ -n "$OLD_ENTRY_START" ]; then
|
|
||||||
# Found second entry, use everything from there
|
|
||||||
CHANGELOG_CONTENT=$(tail -n +"$OLD_ENTRY_START" debian/changelog)
|
|
||||||
else
|
|
||||||
# No second entry found, changelog will only have new entry
|
|
||||||
CHANGELOG_CONTENT=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create new changelog entry with proper format
|
|
||||||
CHANGELOG_ENTRY="${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
|
|
||||||
|
|
||||||
* Git snapshot (commit ${GIT_COMMIT_COUNT}: ${GIT_COMMIT_HASH})
|
* Git snapshot (commit ${GIT_COMMIT_COUNT}: ${GIT_COMMIT_HASH})
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)
|
||||||
|
EOF
|
||||||
# Write new changelog (new entry, blank line, then old entries)
|
|
||||||
echo "$CHANGELOG_ENTRY" >debian/changelog
|
|
||||||
if [ -n "$CHANGELOG_CONTENT" ]; then
|
|
||||||
echo "" >>debian/changelog
|
|
||||||
echo "$CHANGELOG_CONTENT" >>debian/changelog
|
|
||||||
fi
|
|
||||||
success "Version updated to $NEW_VERSION"
|
success "Version updated to $NEW_VERSION"
|
||||||
|
CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version)
|
||||||
|
|
||||||
|
# Note: No longer writing back to repository (changelog stays as template)
|
||||||
|
|
||||||
# Now clone to source directory (without .git for inclusion in package)
|
|
||||||
rm -rf "$SOURCE_DIR"
|
rm -rf "$SOURCE_DIR"
|
||||||
cp -r "$TEMP_CLONE" "$SOURCE_DIR"
|
cp -r "$TEMP_CLONE" "$SOURCE_DIR"
|
||||||
|
|
||||||
# Save version info for dms-git build process
|
|
||||||
if [ "$PACKAGE_NAME" = "dms-git" ]; then
|
if [ "$PACKAGE_NAME" = "dms-git" ]; then
|
||||||
info "Saving version info to .dms-version for build process..."
|
info "Saving version info to .dms-version for build process..."
|
||||||
echo "VERSION=${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}" >"$SOURCE_DIR/.dms-version"
|
echo "VERSION=${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}" >"$SOURCE_DIR/.dms-version"
|
||||||
echo "COMMIT=${GIT_COMMIT_HASH}" >>"$SOURCE_DIR/.dms-version"
|
echo "COMMIT=${GIT_COMMIT_HASH}" >>"$SOURCE_DIR/.dms-version"
|
||||||
success "Version info saved: ${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}"
|
success "Version info saved: ${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}"
|
||||||
|
|
||||||
# Vendor Go dependencies (Launchpad has no internet access)
|
|
||||||
info "Vendoring Go dependencies for offline build..."
|
info "Vendoring Go dependencies for offline build..."
|
||||||
cd "$SOURCE_DIR/core"
|
cd "$SOURCE_DIR/core"
|
||||||
|
|
||||||
# Create vendor directory with all dependencies
|
|
||||||
go mod vendor
|
go mod vendor
|
||||||
|
|
||||||
if [ ! -d "vendor" ]; then
|
if [ ! -d "vendor" ]; then
|
||||||
@@ -378,167 +495,21 @@ if [ "$IS_GIT_PACKAGE" = true ] && [ -n "$GIT_REPO" ]; then
|
|||||||
rm -rf "$SOURCE_DIR/.git"
|
rm -rf "$SOURCE_DIR/.git"
|
||||||
rm -rf "$TEMP_CLONE"
|
rm -rf "$TEMP_CLONE"
|
||||||
|
|
||||||
# Vendor Rust dependencies for packages that need it
|
|
||||||
if false; then
|
|
||||||
# No current packages need Rust vendoring
|
|
||||||
if [ -f "$SOURCE_DIR/Cargo.toml" ]; then
|
|
||||||
info "Vendoring Rust dependencies (Launchpad has no internet access)..."
|
|
||||||
cd "$SOURCE_DIR"
|
|
||||||
|
|
||||||
# Clean up any existing vendor directory and .orig files
|
|
||||||
# (prevents cargo from including .orig files in checksums)
|
|
||||||
rm -rf vendor .cargo
|
|
||||||
find . -type f -name "*.orig" -exec rm -f {} + || true
|
|
||||||
|
|
||||||
# Download all dependencies (crates.io + git repos) to vendor/
|
|
||||||
# cargo vendor outputs the config to stderr, capture it
|
|
||||||
mkdir -p .cargo
|
|
||||||
cargo vendor 2>&1 | awk '
|
|
||||||
/^\[source\.crates-io\]/ { printing=1 }
|
|
||||||
printing { print }
|
|
||||||
/^directory = "vendor"$/ { exit }
|
|
||||||
' >.cargo/config.toml
|
|
||||||
|
|
||||||
# Verify vendor directory was created
|
|
||||||
if [ ! -d "vendor" ]; then
|
|
||||||
error "Failed to vendor dependencies"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Verify config was created
|
|
||||||
if [ ! -s .cargo/config.toml ]; then
|
|
||||||
error "Failed to create cargo config"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# CRITICAL: Remove ALL .orig files from vendor directory
|
|
||||||
# These break cargo checksums when dh_clean tries to use them
|
|
||||||
info "Cleaning .orig files from vendor directory..."
|
|
||||||
find vendor -type f -name "*.orig" -exec rm -fv {} + || true
|
|
||||||
find vendor -type f -name "*.rej" -exec rm -fv {} + || true
|
|
||||||
|
|
||||||
# Verify no .orig files remain
|
|
||||||
ORIG_COUNT=$(find vendor -type f -name "*.orig" | wc -l)
|
|
||||||
if [ "$ORIG_COUNT" -gt 0 ]; then
|
|
||||||
warn "Found $ORIG_COUNT .orig files still in vendor directory"
|
|
||||||
fi
|
|
||||||
|
|
||||||
success "Rust dependencies vendored (including git dependencies)"
|
|
||||||
cd "$PACKAGE_DIR"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
success "Source prepared for packaging"
|
success "Source prepared for packaging"
|
||||||
else
|
else
|
||||||
error "Failed to clone $GIT_REPO"
|
error "Failed to clone $GIT_REPO"
|
||||||
rm -rf "$TEMP_CLONE"
|
rm -rf "$TEMP_CLONE"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# Handle stable packages - get latest tag
|
|
||||||
elif [ -n "$GIT_REPO" ]; then
|
|
||||||
info "Detected stable package: $PACKAGE_NAME"
|
|
||||||
info "Fetching latest tag from $GIT_REPO..."
|
|
||||||
|
|
||||||
LATEST_TAG=$(get_latest_tag "$GIT_REPO")
|
|
||||||
if [ -n "$LATEST_TAG" ]; then
|
|
||||||
# Check source format - native packages can't use dashes
|
|
||||||
SOURCE_FORMAT=$(head -1 debian/source/format 2>/dev/null || echo "3.0 (quilt)")
|
|
||||||
|
|
||||||
# Get current version to check if we need to increment PPA number
|
|
||||||
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
# Use REBUILD_RELEASE if provided, otherwise auto-increment
|
|
||||||
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
|
||||||
PPA_NUM=$REBUILD_RELEASE
|
|
||||||
info "Using REBUILD_RELEASE=$REBUILD_RELEASE for PPA number"
|
|
||||||
else
|
|
||||||
PPA_NUM=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$SOURCE_FORMAT" == *"native"* ]]; then
|
|
||||||
# Native format: 0.2.1ppa1 (no dash, no revision)
|
|
||||||
BASE_VERSION="${LATEST_TAG}"
|
|
||||||
# Check if we're rebuilding the same version (increment PPA number if so)
|
|
||||||
if [[ -z "${REBUILD_RELEASE:-}" ]] && [[ "$CURRENT_VERSION" =~ ^${LATEST_TAG}ppa([0-9]+)$ ]]; then
|
|
||||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
|
||||||
info "Detected rebuild of same version (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
|
||||||
elif [[ -z "${REBUILD_RELEASE:-}" ]]; then
|
|
||||||
info "New version or first build, using PPA number $PPA_NUM"
|
|
||||||
fi
|
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
|
||||||
else
|
|
||||||
# Quilt format: 0.2.1-1ppa1 (with revision)
|
|
||||||
BASE_VERSION="${LATEST_TAG}-1"
|
|
||||||
# Check if we're rebuilding the same version (increment PPA number if so)
|
|
||||||
ESCAPED_BASE=$(echo "$BASE_VERSION" | sed 's/\./\\./g' | sed 's/-/\\-/g')
|
|
||||||
if [[ -z "${REBUILD_RELEASE:-}" ]] && [[ "$CURRENT_VERSION" =~ ^${ESCAPED_BASE}ppa([0-9]+)$ ]]; then
|
|
||||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
|
||||||
if [[ "$IS_MANUAL" == true ]]; then
|
|
||||||
info "Detected rebuild of same version (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
|
||||||
else
|
|
||||||
info "Detected rebuild of same version (current: $CURRENT_VERSION). Not a manual run, skipping."
|
|
||||||
success "No changes needed (version matches)."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
elif [[ -z "${REBUILD_RELEASE:-}" ]]; then
|
|
||||||
info "New version or first build, using PPA number $PPA_NUM"
|
|
||||||
fi
|
|
||||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if version needs updating (either new version or PPA number changed)
|
|
||||||
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
|
|
||||||
if [ "$PPA_NUM" -gt 1 ]; then
|
|
||||||
info "Updating changelog for rebuild (PPA number incremented to $PPA_NUM)"
|
|
||||||
else
|
|
||||||
info "Updating changelog to latest tag: $LATEST_TAG"
|
|
||||||
fi
|
|
||||||
# Use sed to update changelog (non-interactive)
|
|
||||||
# Get current changelog content - find the next package header line
|
|
||||||
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
|
|
||||||
|
|
||||||
# Create appropriate changelog message
|
|
||||||
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
|
|
||||||
|
|
||||||
* ${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
|
|
||||||
success "Version updated to $NEW_VERSION"
|
|
||||||
else
|
|
||||||
info "Version already at latest tag: $LATEST_TAG"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
warn "Could not determine latest tag for $GIT_REPO, using existing version"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Handle packages that need pre-built binaries downloaded
|
# Handle packages that need pre-built binaries downloaded
|
||||||
cd "$PACKAGE_DIR"
|
cd "$WORK_PACKAGE_DIR"
|
||||||
case "$PACKAGE_NAME" in
|
case "$PACKAGE_NAME" in
|
||||||
danksearch)
|
danksearch)
|
||||||
info "Downloading pre-built binaries for danksearch..."
|
info "Downloading pre-built binaries for danksearch..."
|
||||||
# Get version from changelog (remove ppa suffix for both quilt and native formats)
|
|
||||||
# Native: 0.5.2ppa1 -> 0.5.2, Quilt: 0.5.2-1ppa1 -> 0.5.2
|
|
||||||
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
|
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
|
||||||
|
|
||||||
# Download both amd64 and arm64 binaries (will be included in source package)
|
|
||||||
# Launchpad can't download during build, so we include both architectures
|
|
||||||
if [ ! -f "dsearch-amd64" ]; then
|
if [ ! -f "dsearch-amd64" ]; then
|
||||||
info "Downloading dsearch binary for amd64..."
|
info "Downloading dsearch binary for amd64..."
|
||||||
if wget -O dsearch-amd64.gz "https://github.com/AvengeMedia/danksearch/releases/download/v${VERSION}/dsearch-linux-amd64.gz"; then
|
if wget -O dsearch-amd64.gz "https://github.com/AvengeMedia/danksearch/releases/download/v${VERSION}/dsearch-linux-amd64.gz"; then
|
||||||
@@ -564,46 +535,26 @@ danksearch)
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
dgop)
|
dgop)
|
||||||
# dgop binary should already be committed in the repo
|
|
||||||
if [ ! -f "dgop" ]; then
|
if [ ! -f "dgop" ]; then
|
||||||
warn "dgop binary not found - should be committed to repo"
|
warn "dgop binary not found - should be committed to repo"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
cd - >/dev/null
|
cd "$WORK_PACKAGE_DIR"
|
||||||
|
|
||||||
# Check if this version already exists on PPA (only in CI environment)
|
|
||||||
if command -v rmadison >/dev/null 2>&1; then
|
|
||||||
info "Checking if version already exists on PPA..."
|
|
||||||
PPA_VERSION_CHECK=$(rmadison -u ppa:avengemedia/dms "$PACKAGE_NAME" 2>/dev/null | grep "$VERSION" || true)
|
|
||||||
if [ -n "$PPA_VERSION_CHECK" ]; then
|
|
||||||
warn "Version $VERSION already exists on PPA:"
|
|
||||||
echo "$PPA_VERSION_CHECK"
|
|
||||||
echo
|
|
||||||
warn "Skipping upload to avoid duplicate. If this is a rebuild, increment the ppa number."
|
|
||||||
cd "$PACKAGE_DIR"
|
|
||||||
# Still clean up extracted sources
|
|
||||||
case "$PACKAGE_NAME" in
|
|
||||||
dms-git)
|
|
||||||
rm -rf DankMaterialShell-*
|
|
||||||
success "Cleaned up DankMaterialShell-*/ directory"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build source package
|
|
||||||
info "Building source package..."
|
info "Building source package..."
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Determine if we need to include orig tarball (-sa) or just debian changes (-sd)
|
SOURCE_FORMAT=$(head -1 "$WORK_PACKAGE_DIR/debian/source/format" 2>/dev/null || echo "3.0 (quilt)")
|
||||||
# Check if .orig.tar.xz already exists in real parent directory (previous build)
|
|
||||||
|
# Native format packages don't use orig tarballs - they include everything in one tarball
|
||||||
|
if [[ "$SOURCE_FORMAT" == *"native"* ]]; then
|
||||||
|
info "Native format detected - including all source files (no orig tarball needed)"
|
||||||
|
DEBUILD_SOURCE_FLAG="-sa"
|
||||||
|
elif [ -f "$PACKAGE_PARENT/${PACKAGE_NAME}_${VERSION%.ppa*}.orig.tar.xz" ]; then
|
||||||
ORIG_TARBALL="${PACKAGE_NAME}_${VERSION%.ppa*}.orig.tar.xz"
|
ORIG_TARBALL="${PACKAGE_NAME}_${VERSION%.ppa*}.orig.tar.xz"
|
||||||
if [ -f "$PACKAGE_PARENT/$ORIG_TARBALL" ]; then
|
|
||||||
info "Found existing orig tarball in $PACKAGE_PARENT, using -sd (debian changes only)"
|
info "Found existing orig tarball in $PACKAGE_PARENT, using -sd (debian changes only)"
|
||||||
# Copy it to temp parent so debuild can find it
|
|
||||||
cp "$PACKAGE_PARENT/$ORIG_TARBALL" "$TEMP_WORK_DIR/"
|
cp "$PACKAGE_PARENT/$ORIG_TARBALL" "$TEMP_WORK_DIR/"
|
||||||
DEBUILD_SOURCE_FLAG="-sd"
|
DEBUILD_SOURCE_FLAG="-sd"
|
||||||
else
|
else
|
||||||
@@ -611,20 +562,20 @@ else
|
|||||||
DEBUILD_SOURCE_FLAG="-sa"
|
DEBUILD_SOURCE_FLAG="-sa"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use -S for source only, -sa/-sd for source inclusion
|
|
||||||
# -d skips dependency checking (we're building on Fedora, not Ubuntu)
|
# -d skips dependency checking (we're building on Fedora, not Ubuntu)
|
||||||
# Pipe yes to automatically answer prompts (e.g., "continue anyway?")
|
|
||||||
if yes | DEBIAN_FRONTEND=noninteractive debuild -S $DEBUILD_SOURCE_FLAG -d; then
|
if yes | DEBIAN_FRONTEND=noninteractive debuild -S $DEBUILD_SOURCE_FLAG -d; then
|
||||||
echo
|
echo
|
||||||
success "Source package built successfully!"
|
success "Source package built successfully!"
|
||||||
|
|
||||||
# Copy build artifacts back to parent directory
|
TEMP_MARKER_FILE="$PACKAGE_PARENT/.ppa_build_temp_${PACKAGE_NAME}"
|
||||||
info "Copying build artifacts to $PACKAGE_PARENT..."
|
echo "PPA_BUILD_TEMP_DIR=$TEMP_WORK_DIR" > "$TEMP_MARKER_FILE"
|
||||||
cp -v "$TEMP_WORK_DIR"/"${SOURCE_NAME}"_"${CHANGELOG_VERSION}"* "$PACKAGE_PARENT/" 2>/dev/null || true
|
|
||||||
|
|
||||||
# List generated files
|
if [[ -z "${PPA_UPLOAD_SCRIPT:-}" ]] && ! pgrep -f "ppa-upload.sh" >/dev/null 2>&1; then
|
||||||
|
info "Copying build artifacts to $PACKAGE_PARENT (standalone build)..."
|
||||||
|
cp -v "$TEMP_WORK_DIR"/"${SOURCE_NAME}"_"${CHANGELOG_VERSION}"* "$PACKAGE_PARENT/" 2>/dev/null || true
|
||||||
info "Generated files in $PACKAGE_PARENT:"
|
info "Generated files in $PACKAGE_PARENT:"
|
||||||
ls -lh "$PACKAGE_PARENT"/"${SOURCE_NAME}"_"${CHANGELOG_VERSION}"* 2>/dev/null || true
|
ls -lh "$PACKAGE_PARENT"/"${SOURCE_NAME}"_"${CHANGELOG_VERSION}"* 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
# Show what to do next
|
# Show what to do next
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -1,204 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Ubuntu PPA uploader for DMS packages
|
|
||||||
# Usage: ./upload-ppa.sh <changes-file> <ppa-name>
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# ./upload-ppa.sh ../dms_0.5.2ppa1_source.changes dms
|
|
||||||
# ./upload-ppa.sh ../dms_0.5.2+git705.fdbb86appa1_source.changes dms-git
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
||||||
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
|
||||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
||||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
||||||
|
|
||||||
if [ $# -lt 2 ]; then
|
|
||||||
error "Usage: $0 <changes-file> <ppa-name>"
|
|
||||||
echo
|
|
||||||
echo "Arguments:"
|
|
||||||
echo " changes-file : Path to .changes file (e.g., ../dms_0.5.2ppa1_source.changes)"
|
|
||||||
echo " ppa-name : PPA to upload to (dms or dms-git)"
|
|
||||||
echo
|
|
||||||
echo "Examples:"
|
|
||||||
echo " $0 ../dms_0.5.2ppa1_source.changes dms"
|
|
||||||
echo " $0 ../dms_0.5.2+git705.fdbb86appa1_source.changes dms-git"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
CHANGES_FILE="$1"
|
|
||||||
PPA_NAME="$2"
|
|
||||||
|
|
||||||
# Validate changes file
|
|
||||||
if [ ! -f "$CHANGES_FILE" ]; then
|
|
||||||
error "Changes file not found: $CHANGES_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! "$CHANGES_FILE" =~ \.changes$ ]]; then
|
|
||||||
error "File must be a .changes file"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate PPA name
|
|
||||||
if [ "$PPA_NAME" != "dms" ] && [ "$PPA_NAME" != "dms-git" ] && [ "$PPA_NAME" != "danklinux" ]; then
|
|
||||||
error "PPA name must be 'dms', 'dms-git', or 'danklinux'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get absolute path
|
|
||||||
CHANGES_FILE=$(realpath "$CHANGES_FILE")
|
|
||||||
|
|
||||||
info "Uploading to PPA: ppa:avengemedia/$PPA_NAME"
|
|
||||||
info "Changes file: $CHANGES_FILE"
|
|
||||||
|
|
||||||
# Check if dput is installed
|
|
||||||
if command -v dput &>/dev/null; then
|
|
||||||
info "dput found"
|
|
||||||
else
|
|
||||||
error "dput not found. Install with:"
|
|
||||||
error " sudo dnf install dput-ng"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if ~/.dput.cf exists
|
|
||||||
if [ ! -f "$HOME/.dput.cf" ]; then
|
|
||||||
error "$HOME/.dput.cf not found!"
|
|
||||||
echo
|
|
||||||
info "Create it from template:"
|
|
||||||
echo " cp $(dirname "$0")/../dput.cf.template ~/.dput.cf"
|
|
||||||
echo
|
|
||||||
info "Or create it manually with:"
|
|
||||||
cat <<'EOF'
|
|
||||||
[ppa:avengemedia/dms]
|
|
||||||
fqdn = ppa.launchpad.net
|
|
||||||
method = ftp
|
|
||||||
incoming = ~avengemedia/ubuntu/dms/
|
|
||||||
login = anonymous
|
|
||||||
allow_unsigned_uploads = 0
|
|
||||||
|
|
||||||
[ppa:avengemedia/dms-git]
|
|
||||||
fqdn = ppa.launchpad.net
|
|
||||||
method = ftp
|
|
||||||
incoming = ~avengemedia/ubuntu/dms-git/
|
|
||||||
login = anonymous
|
|
||||||
allow_unsigned_uploads = 0
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if PPA is configured in dput.cf
|
|
||||||
if ! grep -q "^\[ppa:avengemedia/$PPA_NAME\]" "$HOME/.dput.cf"; then
|
|
||||||
error "PPA 'ppa:avengemedia/$PPA_NAME' not found in ~/.dput.cf"
|
|
||||||
echo
|
|
||||||
info "Add this to ~/.dput.cf:"
|
|
||||||
cat <<EOF
|
|
||||||
[ppa:avengemedia/$PPA_NAME]
|
|
||||||
fqdn = ppa.launchpad.net
|
|
||||||
method = ftp
|
|
||||||
incoming = ~avengemedia/ubuntu/$PPA_NAME/
|
|
||||||
login = anonymous
|
|
||||||
allow_unsigned_uploads = 0
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract package info from changes file
|
|
||||||
PACKAGE_NAME=$(grep "^Source:" "$CHANGES_FILE" | awk '{print $2}')
|
|
||||||
VERSION=$(grep "^Version:" "$CHANGES_FILE" | awk '{print $2}')
|
|
||||||
|
|
||||||
info "Package: $PACKAGE_NAME"
|
|
||||||
info "Version: $VERSION"
|
|
||||||
|
|
||||||
# Show files that will be uploaded
|
|
||||||
echo
|
|
||||||
info "Files to be uploaded:"
|
|
||||||
grep "^ [a-f0-9]" "$CHANGES_FILE" | awk '{print " - " $5}' || true
|
|
||||||
|
|
||||||
# Verify GPG signature
|
|
||||||
info "Verifying GPG signature..."
|
|
||||||
if gpg --verify "$CHANGES_FILE" 2>/dev/null; then
|
|
||||||
success "GPG signature valid"
|
|
||||||
else
|
|
||||||
error "GPG signature verification failed!"
|
|
||||||
error "The .changes file must be signed with your GPG key"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ask for confirmation
|
|
||||||
echo
|
|
||||||
warn "About to upload to: ppa:avengemedia/$PPA_NAME"
|
|
||||||
read -p "Continue? (y/N) " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
info "Upload cancelled"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload to PPA
|
|
||||||
info "Uploading to Launchpad..."
|
|
||||||
echo
|
|
||||||
|
|
||||||
UPLOAD_SUCCESS=false
|
|
||||||
|
|
||||||
if [ "$UPLOAD_METHOD" = "dput" ]; then
|
|
||||||
if dput "ppa:avengemedia/$PPA_NAME" "$CHANGES_FILE"; then
|
|
||||||
UPLOAD_SUCCESS=true
|
|
||||||
fi
|
|
||||||
elif [ "$UPLOAD_METHOD" = "lftp" ]; then
|
|
||||||
# Use lftp to upload to Launchpad PPA
|
|
||||||
CHANGES_DIR=$(dirname "$CHANGES_FILE")
|
|
||||||
CHANGES_BASENAME=$(basename "$CHANGES_FILE")
|
|
||||||
|
|
||||||
# Extract files to upload from .changes file
|
|
||||||
FILES_TO_UPLOAD=("$CHANGES_BASENAME")
|
|
||||||
while IFS= read -r line; do
|
|
||||||
if [[ "$line" =~ ^\ [a-f0-9]+\ [0-9]+\ [^\ ]+\ [^\ ]+\ (.+)$ ]]; then
|
|
||||||
FILES_TO_UPLOAD+=("${BASH_REMATCH[1]}")
|
|
||||||
fi
|
|
||||||
done < "$CHANGES_FILE"
|
|
||||||
|
|
||||||
# Build lftp command to upload all files
|
|
||||||
LFTP_COMMANDS="set ftp:ssl-allow no; open ftp://ppa.launchpad.net; user anonymous ''; cd ~avengemedia/ubuntu/$PPA_NAME/;"
|
|
||||||
for file in "${FILES_TO_UPLOAD[@]}"; do
|
|
||||||
LFTP_COMMANDS="$LFTP_COMMANDS put '$CHANGES_DIR/$file';"
|
|
||||||
done
|
|
||||||
LFTP_COMMANDS="$LFTP_COMMANDS bye"
|
|
||||||
|
|
||||||
if echo "$LFTP_COMMANDS" | lftp; then
|
|
||||||
UPLOAD_SUCCESS=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$UPLOAD_SUCCESS" = true ]; then
|
|
||||||
echo
|
|
||||||
success "Upload successful!"
|
|
||||||
echo
|
|
||||||
info "Monitor build progress at:"
|
|
||||||
echo " https://launchpad.net/~avengemedia/+archive/ubuntu/$PPA_NAME/+packages"
|
|
||||||
echo
|
|
||||||
info "Builds typically take 5-30 minutes depending on:"
|
|
||||||
echo " - Build queue length"
|
|
||||||
echo " - Package complexity"
|
|
||||||
echo " - Number of target Ubuntu series"
|
|
||||||
echo
|
|
||||||
info "Once built, users can install with:"
|
|
||||||
echo " sudo add-apt-repository ppa:avengemedia/$PPA_NAME"
|
|
||||||
echo " sudo apt update"
|
|
||||||
echo " sudo apt install $PACKAGE_NAME"
|
|
||||||
else
|
|
||||||
error "Upload failed!"
|
|
||||||
echo
|
|
||||||
info "Common issues:"
|
|
||||||
echo " - GPG key not verified on Launchpad (check https://launchpad.net/~/+editpgpkeys)"
|
|
||||||
echo " - Version already uploaded (must increment version number)"
|
|
||||||
echo " - Network/firewall blocking FTP (try HTTPS method in dput.cf)"
|
|
||||||
echo " - Email in changelog doesn't match GPG key email"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
Executable
+217
@@ -0,0 +1,217 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Unified PPA status checker for DMS packages
|
||||||
|
# Checks build status for packages across multiple PPAs via Launchpad API
|
||||||
|
# Usage: ./distro/scripts/ppa-status.sh [package-name] [ppa-name]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./distro/scripts/ppa-status.sh # Check all packages in all PPAs
|
||||||
|
# ./distro/scripts/ppa-status.sh dms # Check dms package
|
||||||
|
# ./distro/scripts/ppa-status.sh all dms-git # Check all packages in dms-git PPA
|
||||||
|
|
||||||
|
PPA_OWNER="avengemedia"
|
||||||
|
LAUNCHPAD_API="https://api.launchpad.net/1.0"
|
||||||
|
DISTRO_SERIES="questing"
|
||||||
|
|
||||||
|
# Define packages (sync with ppa-upload.sh)
|
||||||
|
ALL_PACKAGES=(dms dms-git dms-greeter)
|
||||||
|
|
||||||
|
# Function to get PPA name for a package
|
||||||
|
get_ppa_name() {
|
||||||
|
local pkg="$1"
|
||||||
|
case "$pkg" in
|
||||||
|
dms) echo "dms" ;;
|
||||||
|
dms-git) echo "dms-git" ;;
|
||||||
|
dms-greeter) echo "danklinux" ;;
|
||||||
|
*) echo "" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for required tools
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
echo "Error: curl is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
echo "Error: jq is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
PACKAGE_INPUT="${1:-}"
|
||||||
|
PPA_INPUT="${2:-}"
|
||||||
|
|
||||||
|
# Determine packages and PPAs to check
|
||||||
|
if [[ -n "$PACKAGE_INPUT" ]] && [[ "$PACKAGE_INPUT" != "all" ]]; then
|
||||||
|
# Check specific package
|
||||||
|
VALID_PACKAGE=false
|
||||||
|
for pkg in "${ALL_PACKAGES[@]}"; do
|
||||||
|
if [[ "$PACKAGE_INPUT" == "$pkg" ]]; then
|
||||||
|
VALID_PACKAGE=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$VALID_PACKAGE" != "true" ]]; then
|
||||||
|
echo "Error: Unknown package: $PACKAGE_INPUT"
|
||||||
|
echo "Available packages: ${ALL_PACKAGES[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PACKAGES=("$PACKAGE_INPUT")
|
||||||
|
if [[ -n "$PPA_INPUT" ]]; then
|
||||||
|
PPAS=("$PPA_INPUT")
|
||||||
|
else
|
||||||
|
PPAS=("$(get_ppa_name "$PACKAGE_INPUT")")
|
||||||
|
fi
|
||||||
|
elif [[ -n "$PPA_INPUT" ]]; then
|
||||||
|
# Check all packages in specific PPA
|
||||||
|
PACKAGES=("${ALL_PACKAGES[@]}")
|
||||||
|
PPAS=("$PPA_INPUT")
|
||||||
|
else
|
||||||
|
# Check all packages in all PPAs
|
||||||
|
PACKAGES=("${ALL_PACKAGES[@]}")
|
||||||
|
PPAS=("dms" "dms-git" "danklinux")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to get build status color and symbol
|
||||||
|
get_status_display() {
|
||||||
|
local status="$1"
|
||||||
|
case "$status" in
|
||||||
|
"Successfully built")
|
||||||
|
echo -e "✅ \033[0;32m$status\033[0m"
|
||||||
|
;;
|
||||||
|
"Failed to build")
|
||||||
|
echo -e "❌ \033[0;31m$status\033[0m"
|
||||||
|
;;
|
||||||
|
"Needs building"|"Currently building")
|
||||||
|
echo -e "⏳ \033[0;33m$status\033[0m"
|
||||||
|
;;
|
||||||
|
"Dependency wait")
|
||||||
|
echo -e "⚠️ \033[0;33m$status\033[0m"
|
||||||
|
;;
|
||||||
|
"Chroot problem")
|
||||||
|
echo -e "🔧 \033[0;31m$status\033[0m"
|
||||||
|
;;
|
||||||
|
"Uploading build")
|
||||||
|
echo -e "📤 \033[0;36m$status\033[0m"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "❓ \033[0;37m$status\033[0m"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check each PPA
|
||||||
|
for PPA_NAME in "${PPAS[@]}"; do
|
||||||
|
PPA_ARCHIVE="${LAUNCHPAD_API}/~${PPA_OWNER}/+archive/ubuntu/${PPA_NAME}"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "=== PPA: ${PPA_OWNER}/${PPA_NAME} ==="
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Distribution: Ubuntu $DISTRO_SERIES"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for pkg in "${PACKAGES[@]}"; do
|
||||||
|
# Only check packages that belong to this PPA
|
||||||
|
PKG_PPA=$(get_ppa_name "$pkg")
|
||||||
|
if [[ "$PKG_PPA" != "$PPA_NAME" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo "--- $pkg ---"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
# Get published sources for this package
|
||||||
|
SOURCES_URL="${PPA_ARCHIVE}?ws.op=getPublishedSources&source_name=${pkg}&distro_series=${LAUNCHPAD_API}/ubuntu/${DISTRO_SERIES}&status=Published"
|
||||||
|
|
||||||
|
SOURCES=$(curl -s "$SOURCES_URL" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -z "$SOURCES" ]] || [[ "$SOURCES" == "null" ]]; then
|
||||||
|
echo " ⚠️ No published sources found"
|
||||||
|
echo ""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the latest source
|
||||||
|
TOTAL=$(echo "$SOURCES" | jq '.total_size // 0')
|
||||||
|
|
||||||
|
if [[ "$TOTAL" == "0" ]]; then
|
||||||
|
echo " ⚠️ No published sources found for $DISTRO_SERIES"
|
||||||
|
echo ""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get most recent entry
|
||||||
|
ENTRY=$(echo "$SOURCES" | jq '.entries[0]')
|
||||||
|
|
||||||
|
if [[ "$ENTRY" == "null" ]]; then
|
||||||
|
echo " ⚠️ No source entries found"
|
||||||
|
echo ""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract source info
|
||||||
|
VERSION=$(echo "$ENTRY" | jq -r '.source_package_version // "unknown"')
|
||||||
|
STATUS=$(echo "$ENTRY" | jq -r '.status // "unknown"')
|
||||||
|
DATE_PUBLISHED=$(echo "$ENTRY" | jq -r '.date_published // "unknown"')
|
||||||
|
SELF_LINK=$(echo "$ENTRY" | jq -r '.self_link // ""')
|
||||||
|
|
||||||
|
echo " 📦 Version: $VERSION"
|
||||||
|
echo " 📅 Published: ${DATE_PUBLISHED%T*}"
|
||||||
|
echo " 📋 Source Status: $STATUS"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get builds for this source
|
||||||
|
if [[ -n "$SELF_LINK" && "$SELF_LINK" != "null" ]]; then
|
||||||
|
BUILDS_URL="${SELF_LINK}?ws.op=getBuilds"
|
||||||
|
BUILDS=$(curl -s "$BUILDS_URL" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -n "$BUILDS" && "$BUILDS" != "null" ]]; then
|
||||||
|
BUILD_COUNT=$(echo "$BUILDS" | jq '.total_size // 0')
|
||||||
|
|
||||||
|
if [[ "$BUILD_COUNT" -gt 0 ]]; then
|
||||||
|
echo " Builds:"
|
||||||
|
echo "$BUILDS" | jq -r '.entries[] | "\(.arch_tag) \(.buildstate)"' 2>/dev/null | while read -r line; do
|
||||||
|
ARCH=$(echo "$line" | awk '{print $1}')
|
||||||
|
BUILD_STATUS=$(echo "$line" | cut -d' ' -f2-)
|
||||||
|
DISPLAY=$(get_status_display "$BUILD_STATUS")
|
||||||
|
echo " $ARCH: $DISPLAY"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Alternative: Get build records directly from archive
|
||||||
|
BUILD_RECORDS_URL="${PPA_ARCHIVE}?ws.op=getBuildRecords&source_name=${pkg}"
|
||||||
|
BUILD_RECORDS=$(curl -s "$BUILD_RECORDS_URL" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -n "$BUILD_RECORDS" && "$BUILD_RECORDS" != "null" ]]; then
|
||||||
|
RECORD_COUNT=$(echo "$BUILD_RECORDS" | jq '.total_size // 0')
|
||||||
|
|
||||||
|
if [[ "$RECORD_COUNT" -gt 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo " Recent build history:"
|
||||||
|
|
||||||
|
# Get unique version+arch combinations
|
||||||
|
echo "$BUILD_RECORDS" | jq -r '.entries[:6][] | "\(.source_package_version) \(.arch_tag) \(.buildstate)"' 2>/dev/null | while read -r line; do
|
||||||
|
VER=$(echo "$line" | awk '{print $1}')
|
||||||
|
ARCH=$(echo "$line" | awk '{print $2}')
|
||||||
|
BUILD_STATUS=$(echo "$line" | cut -d' ' -f3-)
|
||||||
|
DISPLAY=$(get_status_display "$BUILD_STATUS")
|
||||||
|
echo " $VER ($ARCH): $DISPLAY"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "View full PPA at: https://launchpad.net/~${PPA_OWNER}/+archive/ubuntu/${PPA_NAME}"
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Status check complete!"
|
||||||
|
echo ""
|
||||||
+186
-54
@@ -1,10 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Build and upload PPA package with automatic cleanup
|
# Build and upload PPA package with automatic cleanup
|
||||||
# Usage: ./create-and-upload.sh <package-dir> <ppa-name> [ubuntu-series] [--keep-builds]
|
# Usage: ./ppa-upload.sh [package-name] [ppa-name] [ubuntu-series] [rebuild-number] [--keep-builds] [--rebuild=N]
|
||||||
#
|
#
|
||||||
# Example:
|
# Examples:
|
||||||
# ./create-and-upload.sh ../dms dms questing
|
# ./ppa-upload.sh dms # Single package (auto-detects PPA)
|
||||||
# ./create-and-upload.sh ../danklinux/dgop danklinux questing --keep-builds
|
# ./ppa-upload.sh dms 2 # Rebuild with ppa2 (simple syntax)
|
||||||
|
# ./ppa-upload.sh dms --rebuild=2 # Rebuild with ppa2 (flag syntax)
|
||||||
|
# ./ppa-upload.sh dms-git # Single package
|
||||||
|
# ./ppa-upload.sh all # All packages
|
||||||
|
# ./ppa-upload.sh dms dms questing # Explicit PPA and series
|
||||||
|
# ./ppa-upload.sh dms dms questing 2 # Explicit PPA, series, and rebuild number
|
||||||
|
# ./ppa-upload.sh distro/ubuntu/dms dms # Path-style (backward compatible)
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -19,72 +25,204 @@ success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
|||||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
# Parse arguments
|
AVAILABLE_PACKAGES=(dms dms-git dms-greeter)
|
||||||
|
|
||||||
KEEP_BUILDS=false
|
KEEP_BUILDS=false
|
||||||
ARGS=()
|
REBUILD_RELEASE=""
|
||||||
|
POSITIONAL_ARGS=()
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
if [ "$arg" = "--keep-builds" ]; then
|
case "$arg" in
|
||||||
KEEP_BUILDS=true
|
--keep-builds) KEEP_BUILDS=true ;;
|
||||||
|
--rebuild=*)
|
||||||
|
REBUILD_RELEASE="${arg#*=}"
|
||||||
|
;;
|
||||||
|
-r|--rebuild)
|
||||||
|
REBUILD_NEXT=true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -n "${REBUILD_NEXT:-}" ]]; then
|
||||||
|
REBUILD_RELEASE="$arg"
|
||||||
|
REBUILD_NEXT=false
|
||||||
else
|
else
|
||||||
ARGS+=("$arg")
|
POSITIONAL_ARGS+=("$arg")
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ ${#ARGS[@]} -lt 2 ]; then
|
PACKAGE_INPUT="${POSITIONAL_ARGS[0]:-}"
|
||||||
error "Usage: $0 <package-dir> <ppa-name> [ubuntu-series] [--keep-builds]"
|
PPA_NAME_INPUT="${POSITIONAL_ARGS[1]:-}"
|
||||||
echo
|
UBUNTU_SERIES="${POSITIONAL_ARGS[2]:-questing}"
|
||||||
echo "Arguments:"
|
|
||||||
echo " package-dir : Path to package directory (e.g., ../dms, ../danklinux/dgop)"
|
if [[ ${#POSITIONAL_ARGS[@]} -gt 0 ]]; then
|
||||||
echo " ppa-name : PPA name (danklinux, dms, dms-git)"
|
LAST_INDEX=$((${#POSITIONAL_ARGS[@]} - 1))
|
||||||
echo " ubuntu-series : Ubuntu series (optional, default: questing)"
|
LAST_ARG="${POSITIONAL_ARGS[$LAST_INDEX]}"
|
||||||
echo " Supported: questing (25.10) and newer only"
|
if [[ "$LAST_ARG" =~ ^[0-9]+$ ]] && [[ -z "$REBUILD_RELEASE" ]]; then
|
||||||
echo " Note: Requires Qt 6.6+ (quickshell requirement)"
|
# Last argument is a number and no --rebuild flag was used
|
||||||
echo " --keep-builds : Keep build artifacts after upload (optional)"
|
# Use it as rebuild release and remove from positional args
|
||||||
echo
|
REBUILD_RELEASE="$LAST_ARG"
|
||||||
echo "Examples:"
|
POSITIONAL_ARGS=("${POSITIONAL_ARGS[@]:0:$LAST_INDEX}")
|
||||||
echo " $0 ../dms dms questing"
|
PACKAGE_INPUT="${POSITIONAL_ARGS[0]:-}"
|
||||||
echo " $0 ../danklinux/dgop danklinux questing --keep-builds"
|
PPA_NAME_INPUT="${POSITIONAL_ARGS[1]:-}"
|
||||||
echo " $0 ../dms-git dms-git # Defaults to questing"
|
UBUNTU_SERIES="${POSITIONAL_ARGS[2]:-questing}"
|
||||||
exit 1
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PACKAGE_DIR="${ARGS[0]}"
|
|
||||||
PPA_NAME="${ARGS[1]}"
|
|
||||||
UBUNTU_SERIES="${ARGS[2]:-questing}"
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
BUILD_SCRIPT="$SCRIPT_DIR/ppa-build.sh"
|
BUILD_SCRIPT="$SCRIPT_DIR/ppa-build.sh"
|
||||||
UPLOAD_SCRIPT="$SCRIPT_DIR/ppa-dput.sh"
|
|
||||||
|
|
||||||
# Validate scripts exist
|
|
||||||
if [ ! -f "$BUILD_SCRIPT" ]; then
|
if [ ! -f "$BUILD_SCRIPT" ]; then
|
||||||
error "Build script not found: $BUILD_SCRIPT"
|
error "Build script not found: $BUILD_SCRIPT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get absolute path
|
get_ppa_name() {
|
||||||
PACKAGE_DIR=$(cd "$PACKAGE_DIR" && pwd)
|
local pkg="$1"
|
||||||
|
case "$pkg" in
|
||||||
|
dms) echo "dms" ;;
|
||||||
|
dms-git) echo "dms-git" ;;
|
||||||
|
dms-greeter) echo "danklinux" ;;
|
||||||
|
*) echo "" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Support both path-style and name-style arguments
|
||||||
|
PACKAGE_DIR=""
|
||||||
|
PACKAGE_NAME=""
|
||||||
|
PPA_NAME=""
|
||||||
|
|
||||||
|
if [[ -n "$PACKAGE_INPUT" ]] && [[ "$PACKAGE_INPUT" == *"/"* ]]; then
|
||||||
|
# Path-style argument (backward compatibility)
|
||||||
|
if [[ -d "$PACKAGE_INPUT" ]]; then
|
||||||
|
PACKAGE_DIR="$(cd "$PACKAGE_INPUT" && pwd)"
|
||||||
|
elif [[ -d "$REPO_ROOT/$PACKAGE_INPUT" ]]; then
|
||||||
|
PACKAGE_DIR="$(cd "$REPO_ROOT/$PACKAGE_INPUT" && pwd)"
|
||||||
|
else
|
||||||
|
error "Package directory not found: $PACKAGE_INPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
PACKAGE_NAME=$(basename "$PACKAGE_DIR")
|
PACKAGE_NAME=$(basename "$PACKAGE_DIR")
|
||||||
|
PPA_NAME="${PPA_NAME_INPUT:-$(get_ppa_name "$PACKAGE_NAME")}"
|
||||||
|
if [[ -z "$PPA_NAME" ]]; then
|
||||||
|
error "Could not determine PPA name for package: $PACKAGE_NAME"
|
||||||
|
error "Please specify PPA name as second argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
info "Using path-style argument: $PACKAGE_DIR"
|
||||||
|
elif [[ -n "$PACKAGE_INPUT" ]] && [[ "$PACKAGE_INPUT" == "all" ]]; then
|
||||||
|
echo ""
|
||||||
|
info "Building and uploading all packages..."
|
||||||
|
FAILED_PACKAGES=()
|
||||||
|
for pkg in "${AVAILABLE_PACKAGES[@]}"; do
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
info "Processing $pkg..."
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
BUILD_ARGS=("$pkg" "$PPA_NAME_INPUT" "$UBUNTU_SERIES")
|
||||||
|
[[ "$KEEP_BUILDS" == "true" ]] && BUILD_ARGS+=("--keep-builds")
|
||||||
|
if ! "$0" "${BUILD_ARGS[@]}"; then
|
||||||
|
FAILED_PACKAGES+=("$pkg")
|
||||||
|
error "$pkg failed to upload"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
if [[ ${#FAILED_PACKAGES[@]} -eq 0 ]]; then
|
||||||
|
success "All packages uploaded successfully!"
|
||||||
|
else
|
||||||
|
error "Some packages failed: ${FAILED_PACKAGES[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
elif [[ -n "$PACKAGE_INPUT" ]]; then
|
||||||
|
VALID_PACKAGE=false
|
||||||
|
for pkg in "${AVAILABLE_PACKAGES[@]}"; do
|
||||||
|
if [[ "$PACKAGE_INPUT" == "$pkg" ]]; then
|
||||||
|
VALID_PACKAGE=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$VALID_PACKAGE" != "true" ]]; then
|
||||||
|
error "Unknown package: $PACKAGE_INPUT"
|
||||||
|
echo "Available packages: ${AVAILABLE_PACKAGES[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PACKAGE_NAME="$PACKAGE_INPUT"
|
||||||
|
PACKAGE_DIR="$REPO_ROOT/distro/ubuntu/$PACKAGE_NAME"
|
||||||
|
PPA_NAME="${PPA_NAME_INPUT:-$(get_ppa_name "$PACKAGE_NAME")}"
|
||||||
|
else
|
||||||
|
echo "Available packages:"
|
||||||
|
echo ""
|
||||||
|
for i in "${!AVAILABLE_PACKAGES[@]}"; do
|
||||||
|
echo " $((i+1)). ${AVAILABLE_PACKAGES[$i]}"
|
||||||
|
done
|
||||||
|
echo " a. all"
|
||||||
|
echo ""
|
||||||
|
read -rp "Select package (1-${#AVAILABLE_PACKAGES[@]}, a): " selection
|
||||||
|
|
||||||
|
if [[ "$selection" == "a" ]] || [[ "$selection" == "all" ]]; then
|
||||||
|
PACKAGE_INPUT="all"
|
||||||
|
BUILD_ARGS=("all" "$PPA_NAME_INPUT" "$UBUNTU_SERIES")
|
||||||
|
[[ "$KEEP_BUILDS" == "true" ]] && BUILD_ARGS+=("--keep-builds")
|
||||||
|
exec "$0" "${BUILD_ARGS[@]}"
|
||||||
|
elif [[ "$selection" =~ ^[0-9]+$ ]] && [[ "$selection" -ge 1 ]] && [[ "$selection" -le ${#AVAILABLE_PACKAGES[@]} ]]; then
|
||||||
|
PACKAGE_NAME="${AVAILABLE_PACKAGES[$((selection-1))]}"
|
||||||
|
PACKAGE_DIR="$REPO_ROOT/distro/ubuntu/$PACKAGE_NAME"
|
||||||
|
PPA_NAME="${PPA_NAME_INPUT:-$(get_ppa_name "$PACKAGE_NAME")}"
|
||||||
|
else
|
||||||
|
error "Invalid selection"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$PACKAGE_DIR" ]; then
|
||||||
|
error "Package directory not found: $PACKAGE_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$PACKAGE_DIR/debian" ]; then
|
||||||
|
error "No debian/ directory found in $PACKAGE_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PACKAGE_DIR=$(cd "$PACKAGE_DIR" && pwd)
|
||||||
PARENT_DIR=$(dirname "$PACKAGE_DIR")
|
PARENT_DIR=$(dirname "$PACKAGE_DIR")
|
||||||
|
|
||||||
info "Building and uploading: $PACKAGE_NAME"
|
info "Building and uploading: $PACKAGE_NAME"
|
||||||
info "Package directory: $PACKAGE_DIR"
|
info "Package directory: $PACKAGE_DIR"
|
||||||
info "PPA: ppa:avengemedia/$PPA_NAME"
|
info "PPA: ppa:avengemedia/$PPA_NAME"
|
||||||
info "Ubuntu series: $UBUNTU_SERIES"
|
info "Ubuntu series: $UBUNTU_SERIES"
|
||||||
|
if [[ -n "$REBUILD_RELEASE" ]]; then
|
||||||
|
info "Rebuild release number: ppa$REBUILD_RELEASE"
|
||||||
|
fi
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Step 1: Build source package
|
|
||||||
info "Step 1: Building source package..."
|
info "Step 1: Building source package..."
|
||||||
|
if [[ -n "$REBUILD_RELEASE" ]]; then
|
||||||
|
export REBUILD_RELEASE
|
||||||
|
fi
|
||||||
|
export PPA_UPLOAD_SCRIPT=1
|
||||||
if ! "$BUILD_SCRIPT" "$PACKAGE_DIR" "$UBUNTU_SERIES"; then
|
if ! "$BUILD_SCRIPT" "$PACKAGE_DIR" "$UBUNTU_SERIES"; then
|
||||||
error "Build failed!"
|
error "Build failed!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Find the changes file
|
TEMP_DIR_FILE="$PARENT_DIR/.ppa_build_temp_${PACKAGE_NAME}"
|
||||||
|
if [ -f "$TEMP_DIR_FILE" ]; then
|
||||||
|
BUILD_TEMP_DIR=$(grep -oP 'PPA_BUILD_TEMP_DIR=\K.*' "$TEMP_DIR_FILE")
|
||||||
|
rm -f "$TEMP_DIR_FILE"
|
||||||
|
info "Using build artifacts from temp directory: $BUILD_TEMP_DIR"
|
||||||
|
CHANGES_FILE=$(find "$BUILD_TEMP_DIR" -maxdepth 1 -name "${PACKAGE_NAME}_*_source.changes" -type f 2>/dev/null | sort -V | tail -1)
|
||||||
|
else
|
||||||
|
BUILD_TEMP_DIR="$PARENT_DIR"
|
||||||
CHANGES_FILE=$(find "$PARENT_DIR" -maxdepth 1 -name "${PACKAGE_NAME}_*_source.changes" -type f | sort -V | tail -1)
|
CHANGES_FILE=$(find "$PARENT_DIR" -maxdepth 1 -name "${PACKAGE_NAME}_*_source.changes" -type f | sort -V | tail -1)
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$CHANGES_FILE" ]; then
|
if [ -z "$CHANGES_FILE" ]; then
|
||||||
warn "Changes file not found in $PARENT_DIR"
|
warn "Changes file not found in $BUILD_TEMP_DIR"
|
||||||
warn "Assuming build was skipped (no changes needed) and exiting successfully."
|
warn "Assuming build was skipped (no changes needed) and exiting successfully."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -92,14 +230,11 @@ fi
|
|||||||
info "Found changes file: $CHANGES_FILE"
|
info "Found changes file: $CHANGES_FILE"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Step 2: Upload to PPA
|
|
||||||
info "Step 2: Uploading to PPA..."
|
info "Step 2: Uploading to PPA..."
|
||||||
|
|
||||||
# Check if using lftp (for all PPAs) or dput
|
|
||||||
if [ "$PPA_NAME" = "danklinux" ] || [ "$PPA_NAME" = "dms" ] || [ "$PPA_NAME" = "dms-git" ]; then
|
if [ "$PPA_NAME" = "danklinux" ] || [ "$PPA_NAME" = "dms" ] || [ "$PPA_NAME" = "dms-git" ]; then
|
||||||
warn "Using lftp for upload"
|
warn "Using lftp for upload"
|
||||||
|
|
||||||
# Find all files to upload
|
|
||||||
BUILD_DIR=$(dirname "$CHANGES_FILE")
|
BUILD_DIR=$(dirname "$CHANGES_FILE")
|
||||||
CHANGES_BASENAME=$(basename "$CHANGES_FILE")
|
CHANGES_BASENAME=$(basename "$CHANGES_FILE")
|
||||||
DSC_FILE="${CHANGES_BASENAME/_source.changes/.dsc}"
|
DSC_FILE="${CHANGES_BASENAME/_source.changes/.dsc}"
|
||||||
@@ -127,7 +262,6 @@ if [ "$PPA_NAME" = "danklinux" ] || [ "$PPA_NAME" = "dms" ] || [ "$PPA_NAME" = "
|
|||||||
info " - $BUILDINFO"
|
info " - $BUILDINFO"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# lftp build dir change
|
|
||||||
LFTP_SCRIPT=$(mktemp)
|
LFTP_SCRIPT=$(mktemp)
|
||||||
cat >"$LFTP_SCRIPT" <<EOF
|
cat >"$LFTP_SCRIPT" <<EOF
|
||||||
cd ~avengemedia/ubuntu/$PPA_NAME/
|
cd ~avengemedia/ubuntu/$PPA_NAME/
|
||||||
@@ -148,30 +282,30 @@ EOF
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Use dput for other PPAs
|
# This branch should not be reached for DMS packages
|
||||||
if [ ! -f "$UPLOAD_SCRIPT" ]; then
|
# All DMS packages (dms, dms-git, dms-greeter) use lftp
|
||||||
error "Upload script not found: $UPLOAD_SCRIPT"
|
error "Unknown PPA: $PPA_NAME"
|
||||||
|
error "DMS packages use lftp for upload. Supported PPAs: dms, dms-git, danklinux"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Auto-confirm upload (pipe 'y' to the confirmation prompt)
|
|
||||||
if ! echo "y" | "$UPLOAD_SCRIPT" "$CHANGES_FILE" "$PPA_NAME"; then
|
|
||||||
error "Upload failed!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
success "Package uploaded successfully!"
|
success "Package uploaded successfully!"
|
||||||
info "Monitor build progress at:"
|
info "Monitor build progress at:"
|
||||||
echo " https://launchpad.net/~avengemedia/+archive/ubuntu/$PPA_NAME/+packages"
|
echo " https://launchpad.net/~avengemedia/+archive/ubuntu/$PPA_NAME/+packages"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Step 3: Cleanup (unless --keep-builds is specified)
|
|
||||||
if [ "$KEEP_BUILDS" = "false" ]; then
|
if [ "$KEEP_BUILDS" = "false" ]; then
|
||||||
info "Step 3: Cleaning up build artifacts..."
|
info "Step 3: Cleaning up build artifacts..."
|
||||||
|
|
||||||
# Find all build artifacts in parent directory
|
if [ -n "${BUILD_TEMP_DIR:-}" ] && [ "$BUILD_TEMP_DIR" != "$PARENT_DIR" ]; then
|
||||||
|
if [ -d "$BUILD_TEMP_DIR" ]; then
|
||||||
|
info "Removing temp build directory: $BUILD_TEMP_DIR"
|
||||||
|
rm -rf "$BUILD_TEMP_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$PARENT_DIR/.ppa_build_temp_${PACKAGE_NAME}"
|
||||||
ARTIFACTS=(
|
ARTIFACTS=(
|
||||||
"${PACKAGE_NAME}_*.dsc"
|
"${PACKAGE_NAME}_*.dsc"
|
||||||
"${PACKAGE_NAME}_*.tar.xz"
|
"${PACKAGE_NAME}_*.tar.xz"
|
||||||
@@ -191,7 +325,6 @@ if [ "$KEEP_BUILDS" = "false" ]; then
|
|||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
# Clean up downloaded binaries in package directory
|
|
||||||
case "$PACKAGE_NAME" in
|
case "$PACKAGE_NAME" in
|
||||||
danksearch)
|
danksearch)
|
||||||
if [ -f "$PACKAGE_DIR/dsearch-amd64" ]; then
|
if [ -f "$PACKAGE_DIR/dsearch-amd64" ]; then
|
||||||
@@ -204,7 +337,6 @@ if [ "$KEEP_BUILDS" = "false" ]; then
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
dms)
|
dms)
|
||||||
# Remove downloaded binaries and source
|
|
||||||
if [ -f "$PACKAGE_DIR/dms-distropkg-amd64.gz" ]; then
|
if [ -f "$PACKAGE_DIR/dms-distropkg-amd64.gz" ]; then
|
||||||
rm -f "$PACKAGE_DIR/dms-distropkg-amd64.gz"
|
rm -f "$PACKAGE_DIR/dms-distropkg-amd64.gz"
|
||||||
REMOVED=$((REMOVED + 1))
|
REMOVED=$((REMOVED + 1))
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
danksearch_0.0.7ppa3_source.buildinfo utils optional
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
dgop_0.1.11ppa2_source.buildinfo utils optional
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
dms-git (0.6.2+git2419.993f14a3) questing; urgency=medium
|
dms-git (1.0.2+git2531.208266dfppa1) questing; urgency=medium
|
||||||
|
|
||||||
* widgets: make dank icon picker a popup
|
* Git snapshot (commit 2531: 208266df)
|
||||||
* Previous updates included in build
|
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Mon, 09 Dec 2025 14:00:00 +0000
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sun, 14 Dec 2025 15:20:45 +0000
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
dms-git_0.6.2+git2169.f7f1bbbdppa10_source.buildinfo x11 optional
|
dms-git_1.0.2+git2491.db2f68e3ppa4_source.buildinfo x11 optional
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
dms-greeter (0.6.2ppa3) questing; urgency=medium
|
dms-greeter (1.0.2ppa2) questing; urgency=medium
|
||||||
|
|
||||||
* Rebuild for packaging fixes (ppa3)
|
* Rebuild for packaging fixes (ppa2)
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Thu, 27 Nov 2025 23:38:37 -0500
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:50:44 +0000
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
dms-greeter_0.6.2ppa3_source.buildinfo x11 optional
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
dms (1.0.0ppa4) questing; urgency=medium
|
dms (1.0.2ppa2) questing; urgency=medium
|
||||||
|
|
||||||
* Rebuild for packaging fixes (ppa4)
|
* Rebuild for packaging fixes (ppa2)
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Wed, 10 Dec 2025 12:56:23 -0500
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:46:52 +0000
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
dms_1.0.0ppa4_source.buildinfo x11 optional
|
|
||||||
@@ -44,6 +44,10 @@
|
|||||||
pkgs: qmlPkgs:
|
pkgs: qmlPkgs:
|
||||||
pkgs.lib.concatStringsSep ":" (map (o: "${o}/${pkgs.qt6.qtbase.qtQmlPrefix}") qmlPkgs);
|
pkgs.lib.concatStringsSep ":" (map (o: "${o}/${pkgs.qt6.qtbase.qtQmlPrefix}") qmlPkgs);
|
||||||
|
|
||||||
|
mkQtPluginPath =
|
||||||
|
pkgs: qtPkgs:
|
||||||
|
pkgs.lib.concatStringsSep ":" (map (o: "${o}/${pkgs.qt6.qtbase.qtPluginPrefix}") qtPkgs);
|
||||||
|
|
||||||
qmlPkgs =
|
qmlPkgs =
|
||||||
pkgs: with pkgs.kdePackages; [
|
pkgs: with pkgs.kdePackages; [
|
||||||
kirigami.unwrapped
|
kirigami.unwrapped
|
||||||
@@ -78,7 +82,7 @@
|
|||||||
inherit version;
|
inherit version;
|
||||||
pname = "dms-shell";
|
pname = "dms-shell";
|
||||||
src = ./core;
|
src = ./core;
|
||||||
vendorHash = "sha256-yqV12LssYV0zuUPLjTzJE0e49uUER95dRH4LTcRJeGc=";
|
vendorHash = "sha256-DINaA5LCOWoxBIewuc39Rnwj6NdZoET7Q++B11Qg5rI=";
|
||||||
|
|
||||||
subPackages = [ "cmd/dms" ];
|
subPackages = [ "cmd/dms" ];
|
||||||
|
|
||||||
@@ -108,7 +112,8 @@
|
|||||||
|
|
||||||
wrapProgram $out/bin/dms \
|
wrapProgram $out/bin/dms \
|
||||||
--add-flags "-c $out/share/quickshell/dms" \
|
--add-flags "-c $out/share/quickshell/dms" \
|
||||||
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs (qmlPkgs pkgs)}"
|
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs (qmlPkgs pkgs)}" \
|
||||||
|
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs (qmlPkgs pkgs)}"
|
||||||
|
|
||||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||||
$out/lib/systemd/user/dms.service
|
$out/lib/systemd/user/dms.service
|
||||||
@@ -174,6 +179,10 @@
|
|||||||
|
|
||||||
prek
|
prek
|
||||||
uv # for prek
|
uv # for prek
|
||||||
|
|
||||||
|
# Nix development tools
|
||||||
|
nixd
|
||||||
|
nil
|
||||||
]
|
]
|
||||||
++ devQmlPkgs;
|
++ devQmlPkgs;
|
||||||
|
|
||||||
@@ -183,6 +192,7 @@
|
|||||||
'';
|
'';
|
||||||
|
|
||||||
QML2_IMPORT_PATH = mkQmlImportPath pkgs devQmlPkgs;
|
QML2_IMPORT_PATH = mkQmlImportPath pkgs devQmlPkgs;
|
||||||
|
QT_PLUGIN_PATH = mkQtPluginPath pkgs devQmlPkgs;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property int settingsConfigVersion: 2
|
readonly property int settingsConfigVersion: 3
|
||||||
|
|
||||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||||
|
|
||||||
@@ -352,6 +352,7 @@ Singleton {
|
|||||||
property string customPowerActionReboot: ""
|
property string customPowerActionReboot: ""
|
||||||
property string customPowerActionPowerOff: ""
|
property string customPowerActionPowerOff: ""
|
||||||
|
|
||||||
|
property bool updaterHideWidget: false
|
||||||
property bool updaterUseCustomCommand: false
|
property bool updaterUseCustomCommand: false
|
||||||
property string updaterCustomCommand: ""
|
property string updaterCustomCommand: ""
|
||||||
property string updaterTerminalAdditionalParams: ""
|
property string updaterTerminalAdditionalParams: ""
|
||||||
@@ -396,7 +397,10 @@ Singleton {
|
|||||||
visible: true,
|
visible: true,
|
||||||
popupGapsAuto: true,
|
popupGapsAuto: true,
|
||||||
popupGapsManual: 4,
|
popupGapsManual: 4,
|
||||||
maximizeDetection: true
|
maximizeDetection: true,
|
||||||
|
scrollEnabled: true,
|
||||||
|
scrollXBehavior: "column",
|
||||||
|
scrollYBehavior: "workspace"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -203,10 +203,6 @@ Singleton {
|
|||||||
"value": "scheme-vibrant",
|
"value": "scheme-vibrant",
|
||||||
"label": "Vibrant",
|
"label": "Vibrant",
|
||||||
"description": I18n.tr("Lively palette with saturated accents.")
|
"description": I18n.tr("Lively palette with saturated accents.")
|
||||||
}), ({
|
|
||||||
"value": "scheme-dynamic-contrast",
|
|
||||||
"label": "Dynamic Contrast",
|
|
||||||
"description": I18n.tr("High-contrast palette for strong visual distinction.")
|
|
||||||
}), ({
|
}), ({
|
||||||
"value": "scheme-content",
|
"value": "scheme-content",
|
||||||
"label": "Content",
|
"label": "Content",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -17,40 +16,67 @@ Singleton {
|
|||||||
pciId: "",
|
pciId: "",
|
||||||
mountPath: "/",
|
mountPath: "/",
|
||||||
minimumWidth: true,
|
minimumWidth: true,
|
||||||
showSwap: false
|
showSwap: false,
|
||||||
}
|
mediaSize: 1,
|
||||||
leftModel.append(dummy)
|
showNetworkIcon: true,
|
||||||
centerModel.append(dummy)
|
showBluetoothIcon: true,
|
||||||
rightModel.append(dummy)
|
showAudioIcon: true,
|
||||||
|
showVpnIcon: true,
|
||||||
|
showBrightnessIcon: false,
|
||||||
|
showMicIcon: false,
|
||||||
|
showBatteryIcon: false,
|
||||||
|
showPrinterIcon: false
|
||||||
|
};
|
||||||
|
leftModel.append(dummy);
|
||||||
|
centerModel.append(dummy);
|
||||||
|
rightModel.append(dummy);
|
||||||
|
|
||||||
update(leftModel, left)
|
update(leftModel, left);
|
||||||
update(centerModel, center)
|
update(centerModel, center);
|
||||||
update(rightModel, right)
|
update(rightModel, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update(model, order) {
|
function update(model, order) {
|
||||||
model.clear()
|
model.clear();
|
||||||
for (var i = 0; i < order.length; i++) {
|
for (var i = 0; i < order.length; i++) {
|
||||||
var widgetId = typeof order[i] === "string" ? order[i] : order[i].id
|
var isObj = typeof order[i] !== "string";
|
||||||
var enabled = typeof order[i] === "string" ? true : order[i].enabled
|
var widgetId = isObj ? order[i].id : order[i];
|
||||||
var size = typeof order[i] === "string" ? undefined : order[i].size
|
|
||||||
var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex
|
|
||||||
var pciId = typeof order[i] === "string" ? undefined : order[i].pciId
|
|
||||||
var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath
|
|
||||||
var minimumWidth = typeof order[i] === "string" ? undefined : order[i].minimumWidth
|
|
||||||
var showSwap = typeof order[i] === "string" ? undefined : order[i].showSwap
|
|
||||||
var item = {
|
var item = {
|
||||||
widgetId: widgetId,
|
widgetId: widgetId,
|
||||||
enabled: enabled
|
enabled: isObj ? order[i].enabled : true
|
||||||
}
|
};
|
||||||
if (size !== undefined) item.size = size
|
if (isObj && order[i].size !== undefined)
|
||||||
if (selectedGpuIndex !== undefined) item.selectedGpuIndex = selectedGpuIndex
|
item.size = order[i].size;
|
||||||
if (pciId !== undefined) item.pciId = pciId
|
if (isObj && order[i].selectedGpuIndex !== undefined)
|
||||||
if (mountPath !== undefined) item.mountPath = mountPath
|
item.selectedGpuIndex = order[i].selectedGpuIndex;
|
||||||
if (minimumWidth !== undefined) item.minimumWidth = minimumWidth
|
if (isObj && order[i].pciId !== undefined)
|
||||||
if (showSwap !== undefined) item.showSwap = showSwap
|
item.pciId = order[i].pciId;
|
||||||
|
if (isObj && order[i].mountPath !== undefined)
|
||||||
|
item.mountPath = order[i].mountPath;
|
||||||
|
if (isObj && order[i].minimumWidth !== undefined)
|
||||||
|
item.minimumWidth = order[i].minimumWidth;
|
||||||
|
if (isObj && order[i].showSwap !== undefined)
|
||||||
|
item.showSwap = order[i].showSwap;
|
||||||
|
if (isObj && order[i].mediaSize !== undefined)
|
||||||
|
item.mediaSize = order[i].mediaSize;
|
||||||
|
if (isObj && order[i].showNetworkIcon !== undefined)
|
||||||
|
item.showNetworkIcon = order[i].showNetworkIcon;
|
||||||
|
if (isObj && order[i].showBluetoothIcon !== undefined)
|
||||||
|
item.showBluetoothIcon = order[i].showBluetoothIcon;
|
||||||
|
if (isObj && order[i].showAudioIcon !== undefined)
|
||||||
|
item.showAudioIcon = order[i].showAudioIcon;
|
||||||
|
if (isObj && order[i].showVpnIcon !== undefined)
|
||||||
|
item.showVpnIcon = order[i].showVpnIcon;
|
||||||
|
if (isObj && order[i].showBrightnessIcon !== undefined)
|
||||||
|
item.showBrightnessIcon = order[i].showBrightnessIcon;
|
||||||
|
if (isObj && order[i].showMicIcon !== undefined)
|
||||||
|
item.showMicIcon = order[i].showMicIcon;
|
||||||
|
if (isObj && order[i].showBatteryIcon !== undefined)
|
||||||
|
item.showBatteryIcon = order[i].showBatteryIcon;
|
||||||
|
if (isObj && order[i].showPrinterIcon !== undefined)
|
||||||
|
item.showPrinterIcon = order[i].showPrinterIcon;
|
||||||
|
|
||||||
model.append(item)
|
model.append(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ var SPEC = {
|
|||||||
customPowerActionReboot: { def: "" },
|
customPowerActionReboot: { def: "" },
|
||||||
customPowerActionPowerOff: { def: "" },
|
customPowerActionPowerOff: { def: "" },
|
||||||
|
|
||||||
|
updaterHideWidget: { def: false },
|
||||||
updaterUseCustomCommand: { def: false },
|
updaterUseCustomCommand: { def: false },
|
||||||
updaterCustomCommand: { def: "" },
|
updaterCustomCommand: { def: "" },
|
||||||
updaterTerminalAdditionalParams: { def: "" },
|
updaterTerminalAdditionalParams: { def: "" },
|
||||||
@@ -293,7 +294,10 @@ var SPEC = {
|
|||||||
visible: true,
|
visible: true,
|
||||||
popupGapsAuto: true,
|
popupGapsAuto: true,
|
||||||
popupGapsManual: 4,
|
popupGapsManual: 4,
|
||||||
maximizeDetection: true
|
maximizeDetection: true,
|
||||||
|
scrollEnabled: true,
|
||||||
|
scrollXBehavior: "column",
|
||||||
|
scrollYBehavior: "workspace"
|
||||||
}], onChange: "updateBarConfigs" }
|
}], onChange: "updateBarConfigs" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ function migrateToVersion(obj, targetVersion) {
|
|||||||
settings.configVersion = 2;
|
settings.configVersion = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentVersion < 3) {
|
||||||
|
console.info("Migrating settings from version", currentVersion, "to version 3");
|
||||||
|
console.info("Per-widget controlCenterButton config now supported via widgetData properties");
|
||||||
|
settings.configVersion = 3;
|
||||||
|
}
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,12 +49,14 @@ Item {
|
|||||||
readonly property alias backgroundWindow: backgroundWindow
|
readonly property alias backgroundWindow: backgroundWindow
|
||||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||||
|
|
||||||
|
readonly property bool useSingleWindow: root.useHyprlandFocusGrab
|
||||||
|
|
||||||
signal opened
|
signal opened
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
signal backgroundClicked
|
signal backgroundClicked
|
||||||
|
|
||||||
property bool animationsEnabled: true
|
property bool animationsEnabled: true
|
||||||
readonly property bool useBackgroundWindow: true
|
readonly property bool useBackgroundWindow: !useSingleWindow
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
ModalManager.openModal(root);
|
ModalManager.openModal(root);
|
||||||
@@ -205,7 +207,7 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
|
enabled: root.useBackgroundWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
const clickX = mouse.x;
|
const clickX = mouse.x;
|
||||||
const clickY = mouse.y;
|
const clickY = mouse.y;
|
||||||
@@ -222,7 +224,7 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||||
visible: root.showBackground && SettingsData.modalDarkenBackground
|
visible: root.useBackgroundWindow && root.showBackground && SettingsData.modalDarkenBackground
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled
|
||||||
@@ -271,15 +273,19 @@ Item {
|
|||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
|
right: root.useSingleWindow
|
||||||
|
bottom: root.useSingleWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.margins {
|
WlrLayershell.margins {
|
||||||
left: Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
left: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
||||||
top: Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
top: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
||||||
|
right: 0
|
||||||
|
bottom: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: root.alignedWidth + (shadowBuffer * 2)
|
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
|
||||||
implicitHeight: root.alignedHeight + (shadowBuffer * 2)
|
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
@@ -292,13 +298,48 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
|
z: -2
|
||||||
|
onClicked: root.backgroundClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
color: "black"
|
||||||
|
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||||
|
visible: root.useSingleWindow && root.showBackground && SettingsData.modalDarkenBackground
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animationDuration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: modalContainer
|
||||||
x: shadowBuffer
|
x: root.useSingleWindow ? root.alignedX : shadowBuffer
|
||||||
y: shadowBuffer
|
y: root.useSingleWindow ? root.alignedY : shadowBuffer
|
||||||
|
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.alignedHeight
|
height: root.alignedHeight
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.useSingleWindow
|
||||||
|
hoverEnabled: false
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
onPressed: mouse.accepted = true
|
||||||
|
onClicked: mouse.accepted = true
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
readonly property bool slide: root.animationType === "slide"
|
readonly property bool slide: root.animationType === "slide"
|
||||||
readonly property real offsetX: slide ? 15 : 0
|
readonly property real offsetX: slide ? 15 : 0
|
||||||
readonly property real offsetY: slide ? -30 : root.animationOffset
|
readonly property real offsetY: slide ? -30 : root.animationOffset
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ DankModal {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string outputName: ""
|
property string outputName: ""
|
||||||
property var position: undefined
|
property var changes: []
|
||||||
property var mode: undefined
|
property int countdown: 10
|
||||||
property var vrr: undefined
|
|
||||||
property int countdown: 15
|
signal confirmed
|
||||||
|
signal reverted
|
||||||
|
|
||||||
shouldBeVisible: false
|
shouldBeVisible: false
|
||||||
allowStacking: true
|
allowStacking: true
|
||||||
@@ -23,23 +24,27 @@ DankModal {
|
|||||||
repeat: true
|
repeat: true
|
||||||
running: root.shouldBeVisible
|
running: root.shouldBeVisible
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
countdown--;
|
root.countdown--
|
||||||
if (countdown <= 0) {
|
if (root.countdown <= 0) {
|
||||||
revert();
|
root.reverted()
|
||||||
|
root.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
countdown = 15;
|
countdown = 10
|
||||||
countdownTimer.start();
|
countdownTimer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
onClosed: {
|
onDialogClosed: {
|
||||||
countdownTimer.stop();
|
countdownTimer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackgroundClicked: revert
|
onBackgroundClicked: {
|
||||||
|
root.reverted()
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
FocusScope {
|
FocusScope {
|
||||||
@@ -50,13 +55,15 @@ DankModal {
|
|||||||
implicitHeight: mainColumn.implicitHeight
|
implicitHeight: mainColumn.implicitHeight
|
||||||
|
|
||||||
Keys.onEscapePressed: event => {
|
Keys.onEscapePressed: event => {
|
||||||
revert();
|
root.reverted()
|
||||||
event.accepted = true;
|
root.close()
|
||||||
|
event.accepted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onReturnPressed: event => {
|
Keys.onReturnPressed: event => {
|
||||||
confirm();
|
root.confirmed()
|
||||||
event.accepted = true;
|
root.close()
|
||||||
|
event.accepted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -69,10 +76,6 @@ DankModal {
|
|||||||
anchors.topMargin: Theme.spacingM
|
anchors.topMargin: Theme.spacingM
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("Confirm Display Changes")
|
text: I18n.tr("Confirm Display Changes")
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
@@ -80,70 +83,35 @@ DankModal {
|
|||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Display settings for ") + outputName
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 80
|
height: 70
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceContainerHighest
|
color: Theme.surfaceContainerHighest
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 4
|
text: root.countdown + "s"
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Reverting in:")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: countdown + "s"
|
|
||||||
font.pixelSize: Theme.fontSizeXLarge * 1.5
|
font.pixelSize: Theme.fontSizeXLarge * 1.5
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
visible: root.changes.length > 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.changes
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("Changes:")
|
required property var modelData
|
||||||
|
text: modelData
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceTextMedium
|
color: Theme.surfaceVariantText
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
|
||||||
visible: position !== undefined && position !== null
|
|
||||||
text: I18n.tr("Position: ") + (position ? position.x + ", " + position.y : "")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
visible: mode !== undefined && mode !== null && mode !== ""
|
|
||||||
text: I18n.tr("Mode: ") + (mode || "")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
visible: vrr !== undefined && vrr !== null
|
|
||||||
text: I18n.tr("VRR: ") + (vrr ? I18n.tr("Enabled") : I18n.tr("Disabled"))
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +148,10 @@ DankModal {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: revert
|
onClicked: {
|
||||||
|
root.reverted()
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +177,10 @@ DankModal {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: confirm
|
onClicked: {
|
||||||
|
root.confirmed()
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
@@ -228,18 +202,11 @@ DankModal {
|
|||||||
iconName: "close"
|
iconName: "close"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
onClicked: revert
|
onClicked: {
|
||||||
|
root.reverted()
|
||||||
|
root.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirm() {
|
|
||||||
displaysTab.confirmChanges();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function revert() {
|
|
||||||
displaysTab.revertChanges();
|
|
||||||
close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ DankModal {
|
|||||||
return "NOTIFICATION_MODAL_TOGGLE_SUCCESS";
|
return "NOTIFICATION_MODAL_TOGGLE_SUCCESS";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleDoNotDisturb(): string {
|
||||||
|
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||||
|
|
||||||
|
return "NOTIFICATION_MODAL_TOGGLE_DND_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
target: "notifications"
|
target: "notifications"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,13 +125,45 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: displaysLoader
|
id: displayConfigLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentIndex === 6
|
active: root.currentIndex === 24
|
||||||
visible: active
|
visible: active
|
||||||
focus: active
|
focus: active
|
||||||
|
|
||||||
sourceComponent: DisplaysTab {}
|
sourceComponent: DisplayConfigTab {}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item) {
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: gammaControlLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 25
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: GammaControlTab {}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item) {
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: displayWidgetsLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 26
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: DisplayWidgetsTab {}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active && item) {
|
if (active && item) {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ FloatingWindow {
|
|||||||
objectName: "settingsModal"
|
objectName: "settingsModal"
|
||||||
title: I18n.tr("Settings", "settings window title")
|
title: I18n.tr("Settings", "settings window title")
|
||||||
minimumSize: Qt.size(500, 400)
|
minimumSize: Qt.size(500, 400)
|
||||||
implicitWidth: 800
|
implicitWidth: 900
|
||||||
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
|
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
|
||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|||||||
@@ -144,6 +144,32 @@ Rectangle {
|
|||||||
"tabIndex": 2,
|
"tabIndex": 2,
|
||||||
"shortcutsOnly": true
|
"shortcutsOnly": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "displays",
|
||||||
|
"text": I18n.tr("Displays"),
|
||||||
|
"icon": "monitor",
|
||||||
|
"collapsedByDefault": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "display_config",
|
||||||
|
"text": I18n.tr("Configuration") + " (Beta)",
|
||||||
|
"icon": "display_settings",
|
||||||
|
"tabIndex": 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "display_gamma",
|
||||||
|
"text": I18n.tr("Gamma Control"),
|
||||||
|
"icon": "brightness_6",
|
||||||
|
"tabIndex": 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "display_widgets",
|
||||||
|
"text": I18n.tr("Widgets", "settings_displays"),
|
||||||
|
"icon": "widgets",
|
||||||
|
"tabIndex": 26
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "network",
|
"id": "network",
|
||||||
"text": I18n.tr("Network"),
|
"text": I18n.tr("Network"),
|
||||||
@@ -157,12 +183,6 @@ Rectangle {
|
|||||||
"icon": "computer",
|
"icon": "computer",
|
||||||
"collapsedByDefault": true,
|
"collapsedByDefault": true,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
|
||||||
"id": "displays",
|
|
||||||
"text": I18n.tr("Displays"),
|
|
||||||
"icon": "monitor",
|
|
||||||
"tabIndex": 6
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "printers",
|
"id": "printers",
|
||||||
"text": I18n.tr("Printers"),
|
"text": I18n.tr("Printers"),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ PluginComponent {
|
|||||||
|
|
||||||
ccDetailContent: Component {
|
ccDetailContent: Component {
|
||||||
VpnDetailContent {
|
VpnDetailContent {
|
||||||
listHeight: 180
|
listHeight: 260
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,17 @@ Item {
|
|||||||
|
|
||||||
function getDetailHeight(section) {
|
function getDetailHeight(section) {
|
||||||
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
|
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
|
||||||
if (section === "wifi")
|
switch (true) {
|
||||||
|
case section === "wifi":
|
||||||
|
case section === "bluetooth":
|
||||||
|
case section === "builtin_vpn":
|
||||||
return Math.min(350, maxAvailable);
|
return Math.min(350, maxAvailable);
|
||||||
if (section === "bluetooth")
|
case section.startsWith("brightnessSlider_"):
|
||||||
return Math.min(350, maxAvailable);
|
|
||||||
if (section.startsWith("brightnessSlider_"))
|
|
||||||
return Math.min(400, maxAvailable);
|
return Math.min(400, maxAvailable);
|
||||||
|
default:
|
||||||
return Math.min(250, maxAvailable);
|
return Math.min(250, maxAvailable);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: pluginDetailLoader
|
id: pluginDetailLoader
|
||||||
|
|||||||
@@ -594,7 +594,8 @@ PanelWindow {
|
|||||||
propagateComposedEvents: true
|
propagateComposedEvents: true
|
||||||
z: -1
|
z: -1
|
||||||
|
|
||||||
property real scrollAccumulator: 0
|
property real scrollAccumulatorY: 0
|
||||||
|
property real scrollAccumulatorX: 0
|
||||||
property real touchpadThreshold: 500
|
property real touchpadThreshold: 500
|
||||||
property bool actionInProgress: false
|
property bool actionInProgress: false
|
||||||
|
|
||||||
@@ -604,7 +605,30 @@ PanelWindow {
|
|||||||
onTriggered: parent.actionInProgress = false
|
onTriggered: parent.actionInProgress = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleScrollAction(behavior, direction) {
|
||||||
|
switch (behavior) {
|
||||||
|
case "workspace":
|
||||||
|
topBarContent.switchWorkspace(direction);
|
||||||
|
return true;
|
||||||
|
case "column":
|
||||||
|
if (!CompositorService.isNiri)
|
||||||
|
return false;
|
||||||
|
if (direction > 0)
|
||||||
|
NiriService.moveColumnRight();
|
||||||
|
else
|
||||||
|
NiriService.moveColumnLeft();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onWheel: wheel => {
|
onWheel: wheel => {
|
||||||
|
if (!(barConfig?.scrollEnabled ?? true)) {
|
||||||
|
wheel.accepted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (actionInProgress) {
|
if (actionInProgress) {
|
||||||
wheel.accepted = false;
|
wheel.accepted = false;
|
||||||
return;
|
return;
|
||||||
@@ -612,9 +636,34 @@ PanelWindow {
|
|||||||
|
|
||||||
const deltaY = wheel.angleDelta.y;
|
const deltaY = wheel.angleDelta.y;
|
||||||
const deltaX = wheel.angleDelta.x;
|
const deltaX = wheel.angleDelta.x;
|
||||||
|
const xBehavior = barConfig?.scrollXBehavior ?? "column";
|
||||||
|
const yBehavior = barConfig?.scrollYBehavior ?? "workspace";
|
||||||
|
|
||||||
if (CompositorService.isNiri && Math.abs(deltaX) > Math.abs(deltaY)) {
|
if (CompositorService.isNiri && xBehavior !== "none" && Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||||
topBarContent.switchApp(deltaX);
|
const isMouseWheel = Math.abs(deltaX) >= 120 && (Math.abs(deltaX) % 120) === 0;
|
||||||
|
const direction = deltaX < 0 ? 1 : -1;
|
||||||
|
|
||||||
|
if (isMouseWheel) {
|
||||||
|
if (handleScrollAction(xBehavior, direction)) {
|
||||||
|
actionInProgress = true;
|
||||||
|
cooldownTimer.restart();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scrollAccumulatorX += deltaX;
|
||||||
|
if (Math.abs(scrollAccumulatorX) >= touchpadThreshold) {
|
||||||
|
const touchDirection = scrollAccumulatorX < 0 ? 1 : -1;
|
||||||
|
if (handleScrollAction(xBehavior, touchDirection)) {
|
||||||
|
actionInProgress = true;
|
||||||
|
cooldownTimer.restart();
|
||||||
|
}
|
||||||
|
scrollAccumulatorX = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wheel.accepted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yBehavior === "none") {
|
||||||
wheel.accepted = false;
|
wheel.accepted = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -623,19 +672,20 @@ PanelWindow {
|
|||||||
const direction = deltaY < 0 ? 1 : -1;
|
const direction = deltaY < 0 ? 1 : -1;
|
||||||
|
|
||||||
if (isMouseWheel) {
|
if (isMouseWheel) {
|
||||||
topBarContent.switchWorkspace(direction);
|
if (handleScrollAction(yBehavior, direction)) {
|
||||||
actionInProgress = true;
|
actionInProgress = true;
|
||||||
cooldownTimer.restart();
|
cooldownTimer.restart();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scrollAccumulator += deltaY;
|
scrollAccumulatorY += deltaY;
|
||||||
|
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
|
||||||
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
|
const touchDirection = scrollAccumulatorY < 0 ? 1 : -1;
|
||||||
const touchDirection = scrollAccumulator < 0 ? 1 : -1;
|
if (handleScrollAction(yBehavior, touchDirection)) {
|
||||||
topBarContent.switchWorkspace(touchDirection);
|
|
||||||
scrollAccumulator = 0;
|
|
||||||
actionInProgress = true;
|
actionInProgress = true;
|
||||||
cooldownTimer.restart();
|
cooldownTimer.restart();
|
||||||
}
|
}
|
||||||
|
scrollAccumulatorY = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wheel.accepted = false;
|
wheel.accepted = false;
|
||||||
|
|||||||
@@ -552,7 +552,13 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: profileButtonGroup.height * profileButtonGroup.scale
|
||||||
|
|
||||||
DankButtonGroup {
|
DankButtonGroup {
|
||||||
|
id: profileButtonGroup
|
||||||
|
|
||||||
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||||
property int currentProfileIndex: {
|
property int currentProfileIndex: {
|
||||||
if (typeof PowerProfiles === "undefined")
|
if (typeof PowerProfiles === "undefined")
|
||||||
@@ -560,16 +566,19 @@ DankPopout {
|
|||||||
return profileModel.findIndex(profile => root.isActiveProfile(profile));
|
return profileModel.findIndex(profile => root.isActiveProfile(profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scale: Math.min(1, parent.width / implicitWidth)
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
||||||
currentIndex: currentProfileIndex
|
currentIndex: currentProfileIndex
|
||||||
selectionMode: "single"
|
selectionMode: "single"
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
onSelectionChanged: (index, selected) => {
|
onSelectionChanged: (index, selected) => {
|
||||||
if (!selected)
|
if (!selected)
|
||||||
return;
|
return;
|
||||||
root.setProfile(profileModel[index]);
|
root.setProfile(profileModel[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StyledRect {
|
StyledRect {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ DankPopout {
|
|||||||
triggerY = y;
|
triggerY = y;
|
||||||
triggerWidth = width;
|
triggerWidth = width;
|
||||||
triggerSection = section;
|
triggerSection = section;
|
||||||
|
triggerScreen = screen;
|
||||||
root.screen = screen;
|
root.screen = screen;
|
||||||
|
|
||||||
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
|
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
|
||||||
@@ -102,6 +103,8 @@ DankPopout {
|
|||||||
screen: triggerScreen
|
screen: triggerScreen
|
||||||
shouldBeVisible: false
|
shouldBeVisible: false
|
||||||
|
|
||||||
|
onBackgroundClicked: close()
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: layoutContent
|
id: layoutContent
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ BasePill {
|
|||||||
property var widgetData: null
|
property var widgetData: null
|
||||||
property string screenName: ""
|
property string screenName: ""
|
||||||
property string screenModel: ""
|
property string screenModel: ""
|
||||||
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
|
property bool showNetworkIcon: widgetData?.showNetworkIcon !== undefined ? widgetData.showNetworkIcon : SettingsData.controlCenterShowNetworkIcon
|
||||||
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
|
property bool showBluetoothIcon: widgetData?.showBluetoothIcon !== undefined ? widgetData.showBluetoothIcon : SettingsData.controlCenterShowBluetoothIcon
|
||||||
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
|
property bool showAudioIcon: widgetData?.showAudioIcon !== undefined ? widgetData.showAudioIcon : SettingsData.controlCenterShowAudioIcon
|
||||||
property bool showVpnIcon: SettingsData.controlCenterShowVpnIcon
|
property bool showVpnIcon: widgetData?.showVpnIcon !== undefined ? widgetData.showVpnIcon : SettingsData.controlCenterShowVpnIcon
|
||||||
property bool showBrightnessIcon: SettingsData.controlCenterShowBrightnessIcon
|
property bool showBrightnessIcon: widgetData?.showBrightnessIcon !== undefined ? widgetData.showBrightnessIcon : SettingsData.controlCenterShowBrightnessIcon
|
||||||
property bool showMicIcon: SettingsData.controlCenterShowMicIcon
|
property bool showMicIcon: widgetData?.showMicIcon !== undefined ? widgetData.showMicIcon : SettingsData.controlCenterShowMicIcon
|
||||||
property bool showBatteryIcon: SettingsData.controlCenterShowBatteryIcon
|
property bool showBatteryIcon: widgetData?.showBatteryIcon !== undefined ? widgetData.showBatteryIcon : SettingsData.controlCenterShowBatteryIcon
|
||||||
property bool showPrinterIcon: SettingsData.controlCenterShowPrinterIcon
|
property bool showPrinterIcon: widgetData?.showPrinterIcon !== undefined ? widgetData.showPrinterIcon : SettingsData.controlCenterShowPrinterIcon
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
active: root.showPrinterIcon
|
active: root.showPrinterIcon
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ Item {
|
|||||||
IconImage {
|
IconImage {
|
||||||
id: iconImg
|
id: iconImg
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? (parent.width - Theme.barIconSize(root.barThickness)) / 2 : Theme.spacingXS
|
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: Theme.barIconSize(root.barThickness)
|
width: Theme.barIconSize(root.barThickness)
|
||||||
height: Theme.barIconSize(root.barThickness)
|
height: Theme.barIconSize(root.barThickness)
|
||||||
@@ -385,7 +385,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? (parent.width - Theme.barIconSize(root.barThickness)) / 2 : Theme.spacingXS
|
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
name: "sports_esports"
|
name: "sports_esports"
|
||||||
@@ -607,7 +607,7 @@ Item {
|
|||||||
IconImage {
|
IconImage {
|
||||||
id: iconImg
|
id: iconImg
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? (parent.width - Theme.barIconSize(root.barThickness)) / 2 : Theme.spacingXS
|
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: Theme.barIconSize(root.barThickness)
|
width: Theme.barIconSize(root.barThickness)
|
||||||
height: Theme.barIconSize(root.barThickness)
|
height: Theme.barIconSize(root.barThickness)
|
||||||
@@ -634,7 +634,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? (parent.width - Theme.barIconSize(root.barThickness)) / 2 : Theme.spacingXS
|
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
name: "sports_esports"
|
name: "sports_esports"
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ BasePill {
|
|||||||
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
|
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
|
||||||
readonly property bool isChecking: SystemUpdateService.isChecking
|
readonly property bool isChecking: SystemUpdateService.isChecking
|
||||||
|
|
||||||
|
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
|
||||||
|
width : (SettingsData.updaterHideWidget && !hasUpdates) ? 0 : (18 + horizontalPadding * 2)
|
||||||
|
|
||||||
Ref {
|
Ref {
|
||||||
service: SystemUpdateService
|
service: SystemUpdateService
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -649,6 +649,16 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
|
|
||||||
|
property real scrollAccumulator: 0
|
||||||
|
property real touchpadThreshold: 500
|
||||||
|
property bool scrollInProgress: false
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: scrollCooldown
|
||||||
|
interval: 100
|
||||||
|
onTriggered: parent.scrollInProgress = false
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
@@ -658,6 +668,30 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onWheel: wheel => {
|
||||||
|
if (scrollInProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const delta = wheel.angleDelta.y;
|
||||||
|
const isMouseWheel = Math.abs(delta) >= 120 && (Math.abs(delta) % 120) === 0;
|
||||||
|
const direction = delta < 0 ? 1 : -1;
|
||||||
|
|
||||||
|
if (isMouseWheel) {
|
||||||
|
root.switchWorkspace(direction);
|
||||||
|
scrollInProgress = true;
|
||||||
|
scrollCooldown.restart();
|
||||||
|
} else {
|
||||||
|
scrollAccumulator += delta;
|
||||||
|
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
|
||||||
|
const touchDirection = scrollAccumulator < 0 ? 1 : -1;
|
||||||
|
root.switchWorkspace(touchDirection);
|
||||||
|
scrollInProgress = true;
|
||||||
|
scrollCooldown.restart();
|
||||||
|
scrollAccumulator = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
@@ -808,7 +842,12 @@ Item {
|
|||||||
wsData = modelData;
|
wsData = modelData;
|
||||||
}
|
}
|
||||||
delegateRoot.loadedWorkspaceData = wsData;
|
delegateRoot.loadedWorkspaceData = wsData;
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
const workspaceId = wsData?.id;
|
||||||
|
delegateRoot.loadedIsUrgent = workspaceId ? NiriService.windows.some(w => w.workspace_id === workspaceId && w.is_urgent) : false;
|
||||||
|
} else {
|
||||||
delegateRoot.loadedIsUrgent = wsData?.urgent ?? false;
|
delegateRoot.loadedIsUrgent = wsData?.urgent ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
var icData = null;
|
var icData = null;
|
||||||
if (wsData?.name) {
|
if (wsData?.name) {
|
||||||
@@ -844,8 +883,8 @@ Item {
|
|||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
||||||
|
|
||||||
border.width: isUrgent && !isActive ? 2 : 0
|
border.width: isUrgent ? 2 : 0
|
||||||
border.color: isUrgent && !isActive ? Theme.error : Theme.withAlpha(Theme.error, 0)
|
border.color: isUrgent ? Theme.error : Theme.withAlpha(Theme.error, 0)
|
||||||
|
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.Greetd
|
import Quickshell.Services.Greetd
|
||||||
import Quickshell.Services.Pam
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -22,74 +20,67 @@ Item {
|
|||||||
property string hyprlandCurrentLayout: ""
|
property string hyprlandCurrentLayout: ""
|
||||||
property string hyprlandKeyboard: ""
|
property string hyprlandKeyboard: ""
|
||||||
property int hyprlandLayoutCount: 0
|
property int hyprlandLayoutCount: 0
|
||||||
property bool isPrimaryScreen: {
|
property bool isPrimaryScreen: !Quickshell.screens?.length || screenName === Quickshell.screens[0]?.name
|
||||||
if (!Qt.application.screens || Qt.application.screens.length === 0)
|
|
||||||
return true
|
|
||||||
if (!screenName || screenName === "")
|
|
||||||
return true
|
|
||||||
return screenName === Qt.application.screens[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
signal launchRequested
|
signal launchRequested
|
||||||
|
|
||||||
function pickRandomFact() {
|
function pickRandomFact() {
|
||||||
randomFact = Facts.getRandomFact()
|
randomFact = Facts.getRandomFact();
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool weatherInitialized: false
|
property bool weatherInitialized: false
|
||||||
|
|
||||||
function initWeatherService() {
|
function initWeatherService() {
|
||||||
if (weatherInitialized)
|
if (weatherInitialized)
|
||||||
return
|
return;
|
||||||
if (!GreetdSettings.settingsLoaded)
|
if (!GreetdSettings.settingsLoaded)
|
||||||
return
|
return;
|
||||||
if (!GreetdSettings.weatherEnabled)
|
if (!GreetdSettings.weatherEnabled)
|
||||||
return
|
return;
|
||||||
|
weatherInitialized = true;
|
||||||
weatherInitialized = true
|
WeatherService.addRef();
|
||||||
WeatherService.addRef()
|
WeatherService.forceRefresh();
|
||||||
WeatherService.forceRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: GreetdSettings
|
target: GreetdSettings
|
||||||
function onSettingsLoadedChanged() {
|
function onSettingsLoadedChanged() {
|
||||||
if (GreetdSettings.settingsLoaded)
|
if (GreetdSettings.settingsLoaded)
|
||||||
initWeatherService()
|
initWeatherService();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
pickRandomFact()
|
pickRandomFact();
|
||||||
initWeatherService()
|
initWeatherService();
|
||||||
|
|
||||||
if (isPrimaryScreen) {
|
if (isPrimaryScreen) {
|
||||||
sessionListProc.running = true
|
sessionListProc.running = true;
|
||||||
applyLastSuccessfulUser()
|
applyLastSuccessfulUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
updateHyprlandLayout()
|
updateHyprlandLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyLastSuccessfulUser() {
|
function applyLastSuccessfulUser() {
|
||||||
const lastUser = GreetdMemory.lastSuccessfulUser
|
const lastUser = GreetdMemory.lastSuccessfulUser;
|
||||||
if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) {
|
if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) {
|
||||||
GreeterState.username = lastUser
|
GreeterState.username = lastUser;
|
||||||
GreeterState.usernameInput = lastUser
|
GreeterState.usernameInput = lastUser;
|
||||||
GreeterState.showPasswordInput = true
|
GreeterState.showPasswordInput = true;
|
||||||
PortalService.getGreeterUserProfileImage(lastUser)
|
PortalService.getGreeterUserProfileImage(lastUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
if (weatherInitialized)
|
if (weatherInitialized)
|
||||||
WeatherService.removeRef()
|
WeatherService.removeRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHyprlandLayout() {
|
function updateHyprlandLayout() {
|
||||||
if (CompositorService.isHyprland) {
|
if (CompositorService.isHyprland) {
|
||||||
hyprlandLayoutProcess.running = true
|
hyprlandLayoutProcess.running = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,27 +91,27 @@ Item {
|
|||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(text)
|
const data = JSON.parse(text);
|
||||||
const mainKeyboard = data.keyboards.find(kb => kb.main === true)
|
const mainKeyboard = data.keyboards.find(kb => kb.main === true);
|
||||||
hyprlandKeyboard = mainKeyboard.name
|
hyprlandKeyboard = mainKeyboard.name;
|
||||||
if (mainKeyboard && mainKeyboard.active_keymap) {
|
if (mainKeyboard && mainKeyboard.active_keymap) {
|
||||||
const parts = mainKeyboard.active_keymap.split(" ")
|
const parts = mainKeyboard.active_keymap.split(" ");
|
||||||
if (parts.length > 0) {
|
if (parts.length > 0) {
|
||||||
hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase()
|
hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase();
|
||||||
} else {
|
} else {
|
||||||
hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase()
|
hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hyprlandCurrentLayout = ""
|
hyprlandCurrentLayout = "";
|
||||||
}
|
}
|
||||||
if (mainKeyboard && mainKeyboard.layout_names) {
|
if (mainKeyboard && mainKeyboard.layout_names) {
|
||||||
hyprlandLayoutCount = mainKeyboard.layout_names.length
|
hyprlandLayoutCount = mainKeyboard.layout_names.length;
|
||||||
} else {
|
} else {
|
||||||
hyprlandLayoutCount = 0
|
hyprlandLayoutCount = 0;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
hyprlandCurrentLayout = ""
|
hyprlandCurrentLayout = "";
|
||||||
hyprlandLayoutCount = 0
|
hyprlandLayoutCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +123,7 @@ Item {
|
|||||||
|
|
||||||
function onRawEvent(event) {
|
function onRawEvent(event) {
|
||||||
if (event.name === "activelayout")
|
if (event.name === "activelayout")
|
||||||
updateHyprlandLayout()
|
updateHyprlandLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +131,7 @@ Item {
|
|||||||
target: GreetdMemory
|
target: GreetdMemory
|
||||||
enabled: isPrimaryScreen
|
enabled: isPrimaryScreen
|
||||||
function onLastSuccessfulUserChanged() {
|
function onLastSuccessfulUserChanged() {
|
||||||
applyLastSuccessfulUser()
|
applyLastSuccessfulUser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +139,7 @@ Item {
|
|||||||
target: GreeterState
|
target: GreeterState
|
||||||
function onUsernameChanged() {
|
function onUsernameChanged() {
|
||||||
if (GreeterState.username) {
|
if (GreeterState.username) {
|
||||||
PortalService.getGreeterUserProfileImage(GreeterState.username)
|
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,10 +148,10 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
screenName: root.screenName
|
screenName: root.screenName
|
||||||
visible: {
|
visible: {
|
||||||
var _ = SessionData.perMonitorWallpaper
|
var _ = SessionData.perMonitorWallpaper;
|
||||||
var __ = SessionData.monitorWallpapers
|
var __ = SessionData.monitorWallpapers;
|
||||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||||
return !currentWallpaper || currentWallpaper === "" || (currentWallpaper && currentWallpaper.startsWith("#"))
|
return !currentWallpaper || currentWallpaper === "" || (currentWallpaper && currentWallpaper.startsWith("#"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,10 +160,10 @@ Item {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: {
|
source: {
|
||||||
var _ = SessionData.perMonitorWallpaper
|
var _ = SessionData.perMonitorWallpaper;
|
||||||
var __ = SessionData.monitorWallpapers
|
var __ = SessionData.monitorWallpapers;
|
||||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||||
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""
|
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : "";
|
||||||
}
|
}
|
||||||
fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode)
|
fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode)
|
||||||
smooth: true
|
smooth: true
|
||||||
@@ -213,10 +204,12 @@ Item {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.centerIn: parent
|
id: clockContainer
|
||||||
anchors.verticalCenterOffset: -100
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.verticalCenter
|
||||||
|
anchors.bottomMargin: 60
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 140
|
height: clockText.implicitHeight
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: clockText
|
id: clockText
|
||||||
@@ -225,10 +218,8 @@ Item {
|
|||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
property string fullTimeStr: {
|
property string fullTimeStr: {
|
||||||
const format = GreetdSettings.use24HourClock
|
const format = GreetdSettings.use24HourClock ? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm") : (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP");
|
||||||
? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm")
|
return systemClock.date.toLocaleTimeString(Qt.locale(), format);
|
||||||
: (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP")
|
|
||||||
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
|
|
||||||
}
|
}
|
||||||
property var timeParts: fullTimeStr.split(':')
|
property var timeParts: fullTimeStr.split(':')
|
||||||
property string hours: timeParts[0] || ""
|
property string hours: timeParts[0] || ""
|
||||||
@@ -236,8 +227,8 @@ Item {
|
|||||||
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
|
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
|
||||||
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
|
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
|
||||||
property string ampm: {
|
property string ampm: {
|
||||||
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i)
|
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i);
|
||||||
return match ? match[0].trim() : ""
|
return match ? match[0].trim() : "";
|
||||||
}
|
}
|
||||||
property bool hasSeconds: timeParts.length > 2
|
property bool hasSeconds: timeParts.length > 2
|
||||||
|
|
||||||
@@ -332,13 +323,15 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
id: dateText
|
||||||
anchors.verticalCenterOffset: -10
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: clockContainer.bottom
|
||||||
|
anchors.topMargin: 4
|
||||||
text: {
|
text: {
|
||||||
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
|
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
|
||||||
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat)
|
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat);
|
||||||
}
|
}
|
||||||
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
|
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat);
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeXLarge
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
color: "white"
|
color: "white"
|
||||||
@@ -346,8 +339,9 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.centerIn: parent
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.verticalCenterOffset: 80
|
anchors.top: dateText.bottom
|
||||||
|
anchors.topMargin: Theme.spacingL
|
||||||
width: 380
|
width: 380
|
||||||
height: 140
|
height: 140
|
||||||
|
|
||||||
@@ -364,14 +358,14 @@ Item {
|
|||||||
Layout.preferredHeight: 60
|
Layout.preferredHeight: 60
|
||||||
imageSource: {
|
imageSource: {
|
||||||
if (PortalService.profileImage === "") {
|
if (PortalService.profileImage === "") {
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PortalService.profileImage.startsWith("/")) {
|
if (PortalService.profileImage.startsWith("/")) {
|
||||||
return "file://" + PortalService.profileImage
|
return "file://" + PortalService.profileImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PortalService.profileImage
|
return PortalService.profileImage;
|
||||||
}
|
}
|
||||||
fallbackIcon: "person"
|
fallbackIcon: "person"
|
||||||
}
|
}
|
||||||
@@ -405,57 +399,58 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
|
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
|
||||||
anchors.rightMargin: {
|
anchors.rightMargin: {
|
||||||
let margin = Theme.spacingM
|
let margin = Theme.spacingM;
|
||||||
if (GreeterState.showPasswordInput && revealButton.visible) {
|
if (GreeterState.showPasswordInput && revealButton.visible) {
|
||||||
margin += revealButton.width
|
margin += revealButton.width;
|
||||||
}
|
}
|
||||||
if (virtualKeyboardButton.visible) {
|
if (virtualKeyboardButton.visible) {
|
||||||
margin += virtualKeyboardButton.width
|
margin += virtualKeyboardButton.width;
|
||||||
}
|
}
|
||||||
if (enterButton.visible) {
|
if (enterButton.visible) {
|
||||||
margin += enterButton.width + 2
|
margin += enterButton.width + 2;
|
||||||
}
|
}
|
||||||
return margin
|
return margin;
|
||||||
}
|
}
|
||||||
opacity: 0
|
opacity: 0
|
||||||
focus: true
|
focus: true
|
||||||
echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal
|
echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (syncingFromState) return
|
if (syncingFromState)
|
||||||
|
return;
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
GreeterState.passwordBuffer = text
|
GreeterState.passwordBuffer = text;
|
||||||
} else {
|
} else {
|
||||||
GreeterState.usernameInput = text
|
GreeterState.usernameInput = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
if (Greetd.state === GreetdState.Inactive && GreeterState.username) {
|
if (Greetd.state === GreetdState.Inactive && GreeterState.username) {
|
||||||
Greetd.createSession(GreeterState.username)
|
Greetd.createSession(GreeterState.username);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (text.trim()) {
|
if (text.trim()) {
|
||||||
GreeterState.username = text.trim()
|
GreeterState.username = text.trim();
|
||||||
GreeterState.showPasswordInput = true
|
GreeterState.showPasswordInput = true;
|
||||||
PortalService.getGreeterUserProfileImage(GreeterState.username)
|
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||||
GreeterState.passwordBuffer = ""
|
GreeterState.passwordBuffer = "";
|
||||||
syncingFromState = true
|
syncingFromState = true;
|
||||||
text = ""
|
text = "";
|
||||||
syncingFromState = false
|
syncingFromState = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
syncingFromState = true
|
syncingFromState = true;
|
||||||
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput
|
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput;
|
||||||
syncingFromState = false
|
syncingFromState = false;
|
||||||
if (isPrimaryScreen && !powerMenu.isVisible)
|
if (isPrimaryScreen && !powerMenu.isVisible)
|
||||||
forceActiveFocus()
|
forceActiveFocus();
|
||||||
}
|
}
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible && isPrimaryScreen && !powerMenu.isVisible)
|
if (visible && isPrimaryScreen && !powerMenu.isVisible)
|
||||||
forceActiveFocus()
|
forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,15 +470,15 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: {
|
text: {
|
||||||
if (GreeterState.unlocking) {
|
if (GreeterState.unlocking) {
|
||||||
return "Logging in..."
|
return "Logging in...";
|
||||||
}
|
}
|
||||||
if (Greetd.state !== GreetdState.Inactive) {
|
if (Greetd.state !== GreetdState.Inactive) {
|
||||||
return "Authenticating..."
|
return "Authenticating...";
|
||||||
}
|
}
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
return "Password..."
|
return "Password...";
|
||||||
}
|
}
|
||||||
return "Username..."
|
return "Username...";
|
||||||
}
|
}
|
||||||
color: GreeterState.unlocking ? Theme.primary : (Greetd.state !== GreetdState.Inactive ? Theme.primary : Theme.outline)
|
color: GreeterState.unlocking ? Theme.primary : (Greetd.state !== GreetdState.Inactive ? Theme.primary : Theme.outline)
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
@@ -513,11 +508,11 @@ Item {
|
|||||||
text: {
|
text: {
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
if (parent.showPassword) {
|
if (parent.showPassword) {
|
||||||
return GreeterState.passwordBuffer
|
return GreeterState.passwordBuffer;
|
||||||
}
|
}
|
||||||
return "•".repeat(GreeterState.passwordBuffer.length)
|
return "•".repeat(GreeterState.passwordBuffer.length);
|
||||||
}
|
}
|
||||||
return GreeterState.usernameInput
|
return GreeterState.usernameInput;
|
||||||
}
|
}
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
font.pixelSize: (GreeterState.showPasswordInput && !parent.showPassword) ? Theme.fontSizeLarge : Theme.fontSizeMedium
|
font.pixelSize: (GreeterState.showPasswordInput && !parent.showPassword) ? Theme.fontSizeLarge : Theme.fontSizeMedium
|
||||||
@@ -558,9 +553,9 @@ Item {
|
|||||||
enabled: visible
|
enabled: visible
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (keyboard_controller.isKeyboardActive) {
|
if (keyboard_controller.isKeyboardActive) {
|
||||||
keyboard_controller.hide()
|
keyboard_controller.hide();
|
||||||
} else {
|
} else {
|
||||||
keyboard_controller.show()
|
keyboard_controller.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -578,15 +573,15 @@ Item {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
if (GreeterState.username) {
|
if (GreeterState.username) {
|
||||||
Greetd.createSession(GreeterState.username)
|
Greetd.createSession(GreeterState.username);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (inputField.text.trim()) {
|
if (inputField.text.trim()) {
|
||||||
GreeterState.username = inputField.text.trim()
|
GreeterState.username = inputField.text.trim();
|
||||||
GreeterState.showPasswordInput = true
|
GreeterState.showPasswordInput = true;
|
||||||
PortalService.getGreeterUserProfileImage(GreeterState.username)
|
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||||
GreeterState.passwordBuffer = ""
|
GreeterState.passwordBuffer = "";
|
||||||
inputField.text = ""
|
inputField.text = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -615,10 +610,10 @@ Item {
|
|||||||
Layout.bottomMargin: -Theme.spacingS
|
Layout.bottomMargin: -Theme.spacingS
|
||||||
text: {
|
text: {
|
||||||
if (GreeterState.pamState === "error")
|
if (GreeterState.pamState === "error")
|
||||||
return "Authentication error - try again"
|
return "Authentication error - try again";
|
||||||
if (GreeterState.pamState === "fail")
|
if (GreeterState.pamState === "fail")
|
||||||
return "Incorrect password"
|
return "Incorrect password";
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
color: Theme.error
|
color: Theme.error
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
@@ -675,9 +670,9 @@ Item {
|
|||||||
cornerRadius: parent.radius
|
cornerRadius: parent.radius
|
||||||
enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput
|
enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput
|
||||||
onClicked: {
|
onClicked: {
|
||||||
GreeterState.reset()
|
GreeterState.reset();
|
||||||
inputField.text = ""
|
inputField.text = "";
|
||||||
PortalService.profileImage = ""
|
PortalService.profileImage = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -696,11 +691,11 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: {
|
visible: {
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
return NiriService.keyboardLayoutNames.length > 1
|
return NiriService.keyboardLayoutNames.length > 1;
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
return hyprlandLayoutCount > 1
|
return hyprlandLayoutCount > 1;
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -726,17 +721,18 @@ Item {
|
|||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
const layout = NiriService.getCurrentKeyboardLayoutName()
|
const layout = NiriService.getCurrentKeyboardLayoutName();
|
||||||
if (!layout) return ""
|
if (!layout)
|
||||||
const parts = layout.split(" ")
|
return "";
|
||||||
|
const parts = layout.split(" ");
|
||||||
if (parts.length > 0) {
|
if (parts.length > 0) {
|
||||||
return parts[0].substring(0, 2).toUpperCase()
|
return parts[0].substring(0, 2).toUpperCase();
|
||||||
}
|
}
|
||||||
return layout.substring(0, 2).toUpperCase()
|
return layout.substring(0, 2).toUpperCase();
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
return hyprlandCurrentLayout
|
return hyprlandCurrentLayout;
|
||||||
}
|
}
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Light
|
font.weight: Font.Light
|
||||||
@@ -753,15 +749,10 @@ Item {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
NiriService.cycleKeyboardLayout()
|
NiriService.cycleKeyboardLayout();
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
Quickshell.execDetached([
|
Quickshell.execDetached(["hyprctl", "switchxkblayout", hyprlandKeyboard, "next"]);
|
||||||
"hyprctl",
|
updateHyprlandLayout();
|
||||||
"switchxkblayout",
|
|
||||||
hyprlandKeyboard,
|
|
||||||
"next"
|
|
||||||
])
|
|
||||||
updateHyprlandLayout()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -773,9 +764,8 @@ Item {
|
|||||||
color: Qt.rgba(255, 255, 255, 0.2)
|
color: Qt.rgba(255, 255, 255, 0.2)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: {
|
visible: {
|
||||||
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) ||
|
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) || (CompositorService.isHyprland && hyprlandLayoutCount > 1);
|
||||||
(CompositorService.isHyprland && hyprlandLayoutCount > 1)
|
return keyboardVisible && GreetdSettings.weatherEnabled && WeatherService.weather.available;
|
||||||
return keyboardVisible && GreetdSettings.weatherEnabled && WeatherService.weather.available
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,15 +822,15 @@ Item {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
name: {
|
name: {
|
||||||
if (!AudioService.sink?.audio) {
|
if (!AudioService.sink?.audio) {
|
||||||
return "volume_up"
|
return "volume_up";
|
||||||
}
|
}
|
||||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
||||||
return "volume_off"
|
return "volume_off";
|
||||||
}
|
}
|
||||||
if (AudioService.sink.audio.volume * 100 < 33) {
|
if (AudioService.sink.audio.volume * 100 < 33) {
|
||||||
return "volume_down"
|
return "volume_down";
|
||||||
}
|
}
|
||||||
return "volume_up"
|
return "volume_up";
|
||||||
}
|
}
|
||||||
size: Theme.iconSize - 2
|
size: Theme.iconSize - 2
|
||||||
color: (AudioService.sink && AudioService.sink.audio && (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white"
|
color: (AudioService.sink && AudioService.sink.audio && (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white"
|
||||||
@@ -866,95 +856,95 @@ Item {
|
|||||||
name: {
|
name: {
|
||||||
if (BatteryService.isCharging) {
|
if (BatteryService.isCharging) {
|
||||||
if (BatteryService.batteryLevel >= 90) {
|
if (BatteryService.batteryLevel >= 90) {
|
||||||
return "battery_charging_full"
|
return "battery_charging_full";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 80) {
|
if (BatteryService.batteryLevel >= 80) {
|
||||||
return "battery_charging_90"
|
return "battery_charging_90";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 60) {
|
if (BatteryService.batteryLevel >= 60) {
|
||||||
return "battery_charging_80"
|
return "battery_charging_80";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 50) {
|
if (BatteryService.batteryLevel >= 50) {
|
||||||
return "battery_charging_60"
|
return "battery_charging_60";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 30) {
|
if (BatteryService.batteryLevel >= 30) {
|
||||||
return "battery_charging_50"
|
return "battery_charging_50";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 20) {
|
if (BatteryService.batteryLevel >= 20) {
|
||||||
return "battery_charging_30"
|
return "battery_charging_30";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "battery_charging_20"
|
return "battery_charging_20";
|
||||||
}
|
}
|
||||||
if (BatteryService.isPluggedIn) {
|
if (BatteryService.isPluggedIn) {
|
||||||
if (BatteryService.batteryLevel >= 90) {
|
if (BatteryService.batteryLevel >= 90) {
|
||||||
return "battery_charging_full"
|
return "battery_charging_full";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 80) {
|
if (BatteryService.batteryLevel >= 80) {
|
||||||
return "battery_charging_90"
|
return "battery_charging_90";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 60) {
|
if (BatteryService.batteryLevel >= 60) {
|
||||||
return "battery_charging_80"
|
return "battery_charging_80";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 50) {
|
if (BatteryService.batteryLevel >= 50) {
|
||||||
return "battery_charging_60"
|
return "battery_charging_60";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 30) {
|
if (BatteryService.batteryLevel >= 30) {
|
||||||
return "battery_charging_50"
|
return "battery_charging_50";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 20) {
|
if (BatteryService.batteryLevel >= 20) {
|
||||||
return "battery_charging_30"
|
return "battery_charging_30";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "battery_charging_20"
|
return "battery_charging_20";
|
||||||
}
|
}
|
||||||
if (BatteryService.batteryLevel >= 95) {
|
if (BatteryService.batteryLevel >= 95) {
|
||||||
return "battery_full"
|
return "battery_full";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 85) {
|
if (BatteryService.batteryLevel >= 85) {
|
||||||
return "battery_6_bar"
|
return "battery_6_bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 70) {
|
if (BatteryService.batteryLevel >= 70) {
|
||||||
return "battery_5_bar"
|
return "battery_5_bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 55) {
|
if (BatteryService.batteryLevel >= 55) {
|
||||||
return "battery_4_bar"
|
return "battery_4_bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 40) {
|
if (BatteryService.batteryLevel >= 40) {
|
||||||
return "battery_3_bar"
|
return "battery_3_bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 25) {
|
if (BatteryService.batteryLevel >= 25) {
|
||||||
return "battery_2_bar"
|
return "battery_2_bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "battery_1_bar"
|
return "battery_1_bar";
|
||||||
}
|
}
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: {
|
color: {
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||||
return Theme.error
|
return Theme.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||||
return Theme.primary
|
return Theme.primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "white"
|
return "white";
|
||||||
}
|
}
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
@@ -1007,14 +997,14 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property real longestSessionWidth: {
|
property real longestSessionWidth: {
|
||||||
let maxWidth = 0
|
let maxWidth = 0;
|
||||||
for (var i = 0; i < sessionMetricsRepeater.count; i++) {
|
for (var i = 0; i < sessionMetricsRepeater.count; i++) {
|
||||||
const item = sessionMetricsRepeater.itemAt(i)
|
const item = sessionMetricsRepeater.itemAt(i);
|
||||||
if (item && item.width > maxWidth) {
|
if (item && item.width > maxWidth) {
|
||||||
maxWidth = item.width
|
maxWidth = item.width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return maxWidth
|
return maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
@@ -1038,52 +1028,42 @@ Item {
|
|||||||
openUpwards: true
|
openUpwards: true
|
||||||
alignPopupRight: true
|
alignPopupRight: true
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
const idx = GreeterState.sessionList.indexOf(value)
|
const idx = GreeterState.sessionList.indexOf(value);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
GreeterState.currentSessionIndex = idx
|
GreeterState.currentSessionIndex = idx;
|
||||||
GreeterState.selectedSession = GreeterState.sessionExecs[idx]
|
GreeterState.selectedSession = GreeterState.sessionExecs[idx];
|
||||||
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[idx])
|
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: pamConfigWatcher
|
|
||||||
path: "/etc/pam.d/dankshell"
|
|
||||||
printErrors: false
|
|
||||||
}
|
|
||||||
|
|
||||||
property int sessionCount: 0
|
|
||||||
property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || ""
|
property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || ""
|
||||||
property int pendingParsers: 0
|
property int pendingParsers: 0
|
||||||
|
|
||||||
|
|
||||||
function finalizeSessionSelection() {
|
function finalizeSessionSelection() {
|
||||||
if (GreeterState.sessionList.length === 0) {
|
if (GreeterState.sessionList.length === 0) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.sessionCount = GreeterState.sessionList.length
|
const savedSession = GreetdMemory.lastSessionId;
|
||||||
|
let foundSaved = false;
|
||||||
const savedSession = GreetdMemory.lastSessionId
|
|
||||||
let foundSaved = false
|
|
||||||
if (savedSession) {
|
if (savedSession) {
|
||||||
for (var i = 0; i < GreeterState.sessionPaths.length; i++) {
|
for (var i = 0; i < GreeterState.sessionPaths.length; i++) {
|
||||||
if (GreeterState.sessionPaths[i] === savedSession) {
|
if (GreeterState.sessionPaths[i] === savedSession) {
|
||||||
GreeterState.currentSessionIndex = i
|
GreeterState.currentSessionIndex = i;
|
||||||
foundSaved = true
|
foundSaved = true;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundSaved) {
|
if (!foundSaved) {
|
||||||
GreeterState.currentSessionIndex = 0
|
GreeterState.currentSessionIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
GreeterState.selectedSession = GreeterState.sessionExecs[GreeterState.currentSessionIndex] || GreeterState.sessionExecs[0] || ""
|
GreeterState.selectedSession = GreeterState.sessionExecs[GreeterState.currentSessionIndex] || GreeterState.sessionExecs[0] || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
@@ -1091,39 +1071,34 @@ Item {
|
|||||||
property string homeDir: Quickshell.env("HOME") || ""
|
property string homeDir: Quickshell.env("HOME") || ""
|
||||||
property string xdgDirs: xdgDataDirs || ""
|
property string xdgDirs: xdgDataDirs || ""
|
||||||
command: {
|
command: {
|
||||||
var paths = [
|
var paths = ["/usr/share/wayland-sessions", "/usr/share/xsessions", "/usr/local/share/wayland-sessions", "/usr/local/share/xsessions"];
|
||||||
"/usr/share/wayland-sessions",
|
|
||||||
"/usr/share/xsessions",
|
|
||||||
"/usr/local/share/wayland-sessions",
|
|
||||||
"/usr/local/share/xsessions"
|
|
||||||
]
|
|
||||||
if (homeDir) {
|
if (homeDir) {
|
||||||
paths.push(homeDir + "/.local/share/wayland-sessions")
|
paths.push(homeDir + "/.local/share/wayland-sessions");
|
||||||
paths.push(homeDir + "/.local/share/xsessions")
|
paths.push(homeDir + "/.local/share/xsessions");
|
||||||
}
|
}
|
||||||
// Add XDG_DATA_DIRS paths
|
// Add XDG_DATA_DIRS paths
|
||||||
if (xdgDirs) {
|
if (xdgDirs) {
|
||||||
xdgDirs.split(":").forEach(function (dir) {
|
xdgDirs.split(":").forEach(function (dir) {
|
||||||
if (dir) {
|
if (dir) {
|
||||||
paths.push(dir + "/wayland-sessions")
|
paths.push(dir + "/wayland-sessions");
|
||||||
paths.push(dir + "/xsessions")
|
paths.push(dir + "/xsessions");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
// 1. Explicit system/user paths
|
// 1. Explicit system/user paths
|
||||||
var explicitFind = "find " + paths.join(" ") + " -maxdepth 1 -name '*.desktop' -type f -follow 2>/dev/null"
|
var explicitFind = "find " + paths.join(" ") + " -maxdepth 1 -name '*.desktop' -type f -follow 2>/dev/null";
|
||||||
// 2. Scan all /home user directories for local session files
|
// 2. Scan all /home user directories for local session files
|
||||||
var homeScan = "find /home -maxdepth 5 \\( -path '*/wayland-sessions/*.desktop' -o -path '*/xsessions/*.desktop' \\) -type f -follow 2>/dev/null"
|
var homeScan = "find /home -maxdepth 5 \\( -path '*/wayland-sessions/*.desktop' -o -path '*/xsessions/*.desktop' \\) -type f -follow 2>/dev/null";
|
||||||
var findCmd = "(" + explicitFind + "; " + homeScan + ") | sort -u"
|
var findCmd = "(" + explicitFind + "; " + homeScan + ") | sort -u";
|
||||||
return ["sh", "-c", findCmd]
|
return ["sh", "-c", findCmd];
|
||||||
}
|
}
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
onRead: data => {
|
onRead: data => {
|
||||||
if (data.trim()) {
|
if (data.trim()) {
|
||||||
root.pendingParsers++
|
root.pendingParsers++;
|
||||||
parseDesktopFile(data.trim())
|
parseDesktopFile(data.trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1132,7 +1107,7 @@ Item {
|
|||||||
function parseDesktopFile(path) {
|
function parseDesktopFile(path) {
|
||||||
const parser = desktopParser.createObject(null, {
|
const parser = desktopParser.createObject(null, {
|
||||||
"desktopPath": path
|
"desktopPath": path
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
@@ -1144,41 +1119,40 @@ Item {
|
|||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
const lines = text.split("\n")
|
const lines = text.split("\n");
|
||||||
let name = ""
|
let name = "";
|
||||||
let exec = ""
|
let exec = "";
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.startsWith("Name=")) {
|
if (line.startsWith("Name=")) {
|
||||||
name = line.substring(5).trim()
|
name = line.substring(5).trim();
|
||||||
} else if (line.startsWith("Exec=")) {
|
} else if (line.startsWith("Exec=")) {
|
||||||
exec = line.substring(5).trim()
|
exec = line.substring(5).trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name && exec) {
|
if (name && exec) {
|
||||||
if (!GreeterState.sessionList.includes(name)) {
|
if (!GreeterState.sessionList.includes(name)) {
|
||||||
let newList = GreeterState.sessionList.slice()
|
let newList = GreeterState.sessionList.slice();
|
||||||
let newExecs = GreeterState.sessionExecs.slice()
|
let newExecs = GreeterState.sessionExecs.slice();
|
||||||
let newPaths = GreeterState.sessionPaths.slice()
|
let newPaths = GreeterState.sessionPaths.slice();
|
||||||
newList.push(name)
|
newList.push(name);
|
||||||
newExecs.push(exec)
|
newExecs.push(exec);
|
||||||
newPaths.push(desktopPath)
|
newPaths.push(desktopPath);
|
||||||
GreeterState.sessionList = newList
|
GreeterState.sessionList = newList;
|
||||||
GreeterState.sessionExecs = newExecs
|
GreeterState.sessionExecs = newExecs;
|
||||||
GreeterState.sessionPaths = newPaths
|
GreeterState.sessionPaths = newPaths;
|
||||||
root.sessionCount = GreeterState.sessionList.length
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onExited: code => {
|
onExited: code => {
|
||||||
root.pendingParsers--
|
root.pendingParsers--;
|
||||||
if (root.pendingParsers === 0) {
|
if (root.pendingParsers === 0) {
|
||||||
Qt.callLater(root.finalizeSessionSelection)
|
Qt.callLater(root.finalizeSessionSelection);
|
||||||
}
|
}
|
||||||
destroy()
|
destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1189,34 +1163,34 @@ Item {
|
|||||||
|
|
||||||
function onAuthMessage(message, error, responseRequired, echoResponse) {
|
function onAuthMessage(message, error, responseRequired, echoResponse) {
|
||||||
if (responseRequired) {
|
if (responseRequired) {
|
||||||
Greetd.respond(GreeterState.passwordBuffer)
|
Greetd.respond(GreeterState.passwordBuffer);
|
||||||
GreeterState.passwordBuffer = ""
|
GreeterState.passwordBuffer = "";
|
||||||
inputField.text = ""
|
inputField.text = "";
|
||||||
} else if (!error) {
|
} else if (!error) {
|
||||||
Greetd.respond("")
|
Greetd.respond("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReadyToLaunch() {
|
function onReadyToLaunch() {
|
||||||
GreeterState.unlocking = true
|
GreeterState.unlocking = true;
|
||||||
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
|
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex];
|
||||||
if (sessionCmd) {
|
if (sessionCmd) {
|
||||||
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex])
|
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex]);
|
||||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username)
|
GreetdMemory.setLastSuccessfulUser(GreeterState.username);
|
||||||
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"])
|
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAuthFailure(message) {
|
function onAuthFailure(message) {
|
||||||
GreeterState.pamState = "fail"
|
GreeterState.pamState = "fail";
|
||||||
GreeterState.passwordBuffer = ""
|
GreeterState.passwordBuffer = "";
|
||||||
inputField.text = ""
|
inputField.text = "";
|
||||||
placeholderDelay.restart()
|
placeholderDelay.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError(error) {
|
function onError(error) {
|
||||||
GreeterState.pamState = "error"
|
GreeterState.pamState = "error";
|
||||||
placeholderDelay.restart()
|
placeholderDelay.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1231,7 +1205,7 @@ Item {
|
|||||||
showLogout: false
|
showLogout: false
|
||||||
onClosed: {
|
onClosed: {
|
||||||
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
|
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
|
||||||
Qt.callLater(() => inputField.forceActiveFocus())
|
Qt.callLater(() => inputField.forceActiveFocus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,10 +212,12 @@ Item {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.centerIn: parent
|
id: clockContainer
|
||||||
anchors.verticalCenterOffset: -100
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.verticalCenter
|
||||||
|
anchors.bottomMargin: 60
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 140
|
height: clockText.implicitHeight
|
||||||
visible: SettingsData.lockScreenShowTime
|
visible: SettingsData.lockScreenShowTime
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -330,8 +332,10 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
id: dateText
|
||||||
anchors.verticalCenterOffset: -25
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: clockContainer.bottom
|
||||||
|
anchors.topMargin: 4
|
||||||
visible: SettingsData.lockScreenShowDate
|
visible: SettingsData.lockScreenShowDate
|
||||||
text: {
|
text: {
|
||||||
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
|
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
|
||||||
@@ -346,8 +350,9 @@ Item {
|
|||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: passwordLayout
|
id: passwordLayout
|
||||||
anchors.centerIn: parent
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.verticalCenterOffset: 50
|
anchors.top: dateText.visible ? dateText.bottom : clockContainer.bottom
|
||||||
|
anchors.topMargin: Theme.spacingL
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
width: 380
|
width: 380
|
||||||
|
|
||||||
@@ -384,7 +389,6 @@ Item {
|
|||||||
border.width: passwordField.activeFocus ? 2 : 1
|
border.width: passwordField.activeFocus ? 2 : 1
|
||||||
visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0
|
visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0
|
||||||
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: lockIconContainer
|
id: lockIconContainer
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ DankListView {
|
|||||||
property var keyboardController: null
|
property var keyboardController: null
|
||||||
property bool keyboardActive: false
|
property bool keyboardActive: false
|
||||||
property bool autoScrollDisabled: false
|
property bool autoScrollDisabled: false
|
||||||
|
property bool isAnimatingExpansion: false
|
||||||
property alias count: listView.count
|
property alias count: listView.count
|
||||||
property alias listContentHeight: listView.contentHeight
|
property alias listContentHeight: listView.contentHeight
|
||||||
|
|
||||||
@@ -29,8 +30,19 @@ DankListView {
|
|||||||
Timer {
|
Timer {
|
||||||
id: positionPreservationTimer
|
id: positionPreservationTimer
|
||||||
interval: 200
|
interval: 200
|
||||||
running: keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled
|
running: keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled && !isAnimatingExpansion
|
||||||
repeat: true
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled && !isAnimatingExpansion) {
|
||||||
|
keyboardController.ensureVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: expansionEnsureVisibleTimer
|
||||||
|
interval: Theme.mediumDuration + 50
|
||||||
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
|
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
|
||||||
keyboardController.ensureVisible();
|
keyboardController.ensureVisible();
|
||||||
@@ -68,14 +80,7 @@ DankListView {
|
|||||||
|
|
||||||
width: ListView.view.width
|
width: ListView.view.width
|
||||||
height: isDismissing ? 0 : notificationCard.height
|
height: isDismissing ? 0 : notificationCard.height
|
||||||
clip: true
|
clip: isDismissing
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationCard {
|
NotificationCard {
|
||||||
id: notificationCard
|
id: notificationCard
|
||||||
@@ -84,6 +89,23 @@ DankListView {
|
|||||||
notificationGroup: modelData
|
notificationGroup: modelData
|
||||||
keyboardNavigationActive: listView.keyboardActive
|
keyboardNavigationActive: listView.keyboardActive
|
||||||
opacity: 1 - Math.abs(delegateRoot.swipeOffset) / (delegateRoot.width * 0.5)
|
opacity: 1 - Math.abs(delegateRoot.swipeOffset) / (delegateRoot.width * 0.5)
|
||||||
|
onIsAnimatingChanged: {
|
||||||
|
if (isAnimating) {
|
||||||
|
listView.isAnimatingExpansion = true;
|
||||||
|
} else {
|
||||||
|
Qt.callLater(() => {
|
||||||
|
let anyAnimating = false;
|
||||||
|
for (let i = 0; i < listView.count; i++) {
|
||||||
|
const item = listView.itemAtIndex(i);
|
||||||
|
if (item && item.children[0] && item.children[0].isAnimating) {
|
||||||
|
anyAnimating = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listView.isAnimatingExpansion = anyAnimating;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isGroupSelected: {
|
isGroupSelected: {
|
||||||
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
|
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
|
||||||
@@ -173,23 +195,15 @@ DankListView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onExpandedGroupsChanged() {
|
function onExpandedGroupsChanged() {
|
||||||
if (keyboardController && keyboardController.keyboardNavigationActive) {
|
if (!keyboardController || !keyboardController.keyboardNavigationActive)
|
||||||
Qt.callLater(() => {
|
return;
|
||||||
if (!autoScrollDisabled) {
|
expansionEnsureVisibleTimer.restart();
|
||||||
keyboardController.ensureVisible();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onExpandedMessagesChanged() {
|
function onExpandedMessagesChanged() {
|
||||||
if (keyboardController && keyboardController.keyboardNavigationActive) {
|
if (!keyboardController || !keyboardController.keyboardNavigationActive)
|
||||||
Qt.callLater(() => {
|
return;
|
||||||
if (!autoScrollDisabled) {
|
expansionEnsureVisibleTimer.restart();
|
||||||
keyboardController.ensureVisible();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
|
||||||
import Quickshell.Services.Notifications
|
import Quickshell.Services.Notifications
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -13,9 +10,9 @@ Rectangle {
|
|||||||
|
|
||||||
property var notificationGroup
|
property var notificationGroup
|
||||||
property bool expanded: (NotificationService.expandedGroups[notificationGroup && notificationGroup.key] || false)
|
property bool expanded: (NotificationService.expandedGroups[notificationGroup && notificationGroup.key] || false)
|
||||||
property bool descriptionExpanded: (NotificationService.expandedMessages[(notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification
|
property bool descriptionExpanded: (NotificationService.expandedMessages[(notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""] || false)
|
||||||
&& notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""] || false)
|
|
||||||
property bool userInitiatedExpansion: false
|
property bool userInitiatedExpansion: false
|
||||||
|
property bool isAnimating: false
|
||||||
|
|
||||||
property bool isGroupSelected: false
|
property bool isGroupSelected: false
|
||||||
property int selectedNotificationIndex: -1
|
property int selectedNotificationIndex: -1
|
||||||
@@ -24,13 +21,13 @@ Rectangle {
|
|||||||
width: parent ? parent.width : 400
|
width: parent ? parent.width : 400
|
||||||
height: {
|
height: {
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
return expandedContent.height + 28
|
return expandedContent.height + 28;
|
||||||
}
|
}
|
||||||
const baseHeight = 116
|
const baseHeight = 116;
|
||||||
if (descriptionExpanded) {
|
if (descriptionExpanded) {
|
||||||
return baseHeight + descriptionText.contentHeight - (descriptionText.font.pixelSize * 1.2 * 2)
|
return baseHeight + descriptionText.contentHeight - (descriptionText.font.pixelSize * 1.2 * 2);
|
||||||
}
|
}
|
||||||
return baseHeight
|
return baseHeight;
|
||||||
}
|
}
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
@@ -43,36 +40,36 @@ Rectangle {
|
|||||||
|
|
||||||
color: {
|
color: {
|
||||||
if (isGroupSelected && keyboardNavigationActive) {
|
if (isGroupSelected && keyboardNavigationActive) {
|
||||||
return Theme.primaryPressed
|
return Theme.primaryPressed;
|
||||||
}
|
}
|
||||||
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
||||||
return Theme.primaryHoverLight
|
return Theme.primaryHoverLight;
|
||||||
}
|
}
|
||||||
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
|
||||||
}
|
}
|
||||||
border.color: {
|
border.color: {
|
||||||
if (isGroupSelected && keyboardNavigationActive) {
|
if (isGroupSelected && keyboardNavigationActive) {
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5)
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5);
|
||||||
}
|
}
|
||||||
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
|
||||||
}
|
}
|
||||||
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
|
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
||||||
}
|
}
|
||||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05);
|
||||||
}
|
}
|
||||||
border.width: {
|
border.width: {
|
||||||
if (isGroupSelected && keyboardNavigationActive) {
|
if (isGroupSelected && keyboardNavigationActive) {
|
||||||
return 1.5
|
return 1.5;
|
||||||
}
|
}
|
||||||
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
||||||
return 1
|
return 1;
|
||||||
}
|
}
|
||||||
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
|
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
|
||||||
return 2
|
return 2;
|
||||||
}
|
}
|
||||||
return 1
|
return 1;
|
||||||
}
|
}
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
@@ -121,21 +118,21 @@ Rectangle {
|
|||||||
|
|
||||||
imageSource: {
|
imageSource: {
|
||||||
if (hasNotificationImage)
|
if (hasNotificationImage)
|
||||||
return notificationGroup.latestNotification.cleanImage
|
return notificationGroup.latestNotification.cleanImage;
|
||||||
if (notificationGroup?.latestNotification?.appIcon) {
|
if (notificationGroup?.latestNotification?.appIcon) {
|
||||||
const appIcon = notificationGroup.latestNotification.appIcon
|
const appIcon = notificationGroup.latestNotification.appIcon;
|
||||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||||
return appIcon
|
return appIcon;
|
||||||
return Quickshell.iconPath(appIcon, true)
|
return Quickshell.iconPath(appIcon, true);
|
||||||
}
|
}
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
hasImage: hasNotificationImage
|
hasImage: hasNotificationImage
|
||||||
fallbackIcon: ""
|
fallbackIcon: ""
|
||||||
fallbackText: {
|
fallbackText: {
|
||||||
const appName = notificationGroup?.appName || "?"
|
const appName = notificationGroup?.appName || "?";
|
||||||
return appName.charAt(0).toUpperCase()
|
return appName.charAt(0).toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -195,9 +192,9 @@ Rectangle {
|
|||||||
StyledText {
|
StyledText {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: {
|
text: {
|
||||||
const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || ""
|
const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || "";
|
||||||
const appName = (notificationGroup && notificationGroup.appName) || ""
|
const appName = (notificationGroup && notificationGroup.appName) || "";
|
||||||
return timeStr.length > 0 ? `${appName} • ${timeStr}` : appName
|
return timeStr.length > 0 ? `${appName} • ${timeStr}` : appName;
|
||||||
}
|
}
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
@@ -239,21 +236,20 @@ Rectangle {
|
|||||||
|
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
|
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
|
||||||
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification
|
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "";
|
||||||
&& notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""
|
NotificationService.toggleMessageExpansion(messageId);
|
||||||
NotificationService.toggleMessageExpansion(messageId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
propagateComposedEvents: true
|
propagateComposedEvents: true
|
||||||
onPressed: mouse => {
|
onPressed: mouse => {
|
||||||
if (parent.hoveredLink) {
|
if (parent.hoveredLink) {
|
||||||
mouse.accepted = false
|
mouse.accepted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onReleased: mouse => {
|
onReleased: mouse => {
|
||||||
if (parent.hoveredLink) {
|
if (parent.hoveredLink) {
|
||||||
mouse.accepted = false
|
mouse.accepted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,15 +331,15 @@ Rectangle {
|
|||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: {
|
height: {
|
||||||
const baseHeight = 120
|
const baseHeight = 120;
|
||||||
if (messageExpanded) {
|
if (messageExpanded) {
|
||||||
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2
|
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2;
|
||||||
if (bodyText.implicitHeight > twoLineHeight + 2) {
|
if (bodyText.implicitHeight > twoLineHeight + 2) {
|
||||||
const extraHeight = bodyText.implicitHeight - twoLineHeight
|
const extraHeight = bodyText.implicitHeight - twoLineHeight;
|
||||||
return baseHeight + extraHeight
|
return baseHeight + extraHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return baseHeight
|
return baseHeight;
|
||||||
}
|
}
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
@@ -379,23 +375,23 @@ Rectangle {
|
|||||||
|
|
||||||
imageSource: {
|
imageSource: {
|
||||||
if (hasNotificationImage)
|
if (hasNotificationImage)
|
||||||
return modelData.cleanImage
|
return modelData.cleanImage;
|
||||||
|
|
||||||
if (modelData?.appIcon) {
|
if (modelData?.appIcon) {
|
||||||
const appIcon = modelData.appIcon
|
const appIcon = modelData.appIcon;
|
||||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||||
return appIcon
|
return appIcon;
|
||||||
|
|
||||||
return Quickshell.iconPath(appIcon, true)
|
return Quickshell.iconPath(appIcon, true);
|
||||||
}
|
}
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
fallbackIcon: ""
|
fallbackIcon: ""
|
||||||
|
|
||||||
fallbackText: {
|
fallbackText: {
|
||||||
const appName = modelData?.appName || "?"
|
const appName = modelData?.appName || "?";
|
||||||
return appName.charAt(0).toUpperCase()
|
return appName.charAt(0).toUpperCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,19 +453,19 @@ Rectangle {
|
|||||||
|
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
|
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
|
||||||
NotificationService.toggleMessageExpansion(modelData?.notification?.id || "")
|
NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
propagateComposedEvents: true
|
propagateComposedEvents: true
|
||||||
onPressed: mouse => {
|
onPressed: mouse => {
|
||||||
if (parent.hoveredLink) {
|
if (parent.hoveredLink) {
|
||||||
mouse.accepted = false
|
mouse.accepted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onReleased: mouse => {
|
onReleased: mouse => {
|
||||||
if (parent.hoveredLink) {
|
if (parent.hoveredLink) {
|
||||||
mouse.accepted = false
|
mouse.accepted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,11 +498,11 @@ Rectangle {
|
|||||||
StyledText {
|
StyledText {
|
||||||
id: actionText
|
id: actionText
|
||||||
text: {
|
text: {
|
||||||
const baseText = modelData.text || "View"
|
const baseText = modelData.text || "View";
|
||||||
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0)) {
|
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0)) {
|
||||||
return `${baseText} (${index + 1})`
|
return `${baseText} (${index + 1})`;
|
||||||
}
|
}
|
||||||
return baseText
|
return baseText;
|
||||||
}
|
}
|
||||||
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
|
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
@@ -523,7 +519,7 @@ Rectangle {
|
|||||||
onExited: parent.isHovered = false
|
onExited: parent.isHovered = false
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData && modelData.invoke) {
|
if (modelData && modelData.invoke) {
|
||||||
modelData.invoke()
|
modelData.invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -587,11 +583,11 @@ Rectangle {
|
|||||||
StyledText {
|
StyledText {
|
||||||
id: actionText
|
id: actionText
|
||||||
text: {
|
text: {
|
||||||
const baseText = modelData.text || "View"
|
const baseText = modelData.text || "View";
|
||||||
if (keyboardNavigationActive && isGroupSelected) {
|
if (keyboardNavigationActive && isGroupSelected) {
|
||||||
return `${baseText} (${index + 1})`
|
return `${baseText} (${index + 1})`;
|
||||||
}
|
}
|
||||||
return baseText
|
return baseText;
|
||||||
}
|
}
|
||||||
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
|
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
@@ -608,7 +604,7 @@ Rectangle {
|
|||||||
onExited: parent.isHovered = false
|
onExited: parent.isHovered = false
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData && modelData.invoke) {
|
if (modelData && modelData.invoke) {
|
||||||
modelData.invoke()
|
modelData.invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -654,8 +650,8 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
|
visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.userInitiatedExpansion = true
|
root.userInitiatedExpansion = true;
|
||||||
NotificationService.toggleGroupExpansion(notificationGroup?.key || "")
|
NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
|
||||||
}
|
}
|
||||||
z: -1
|
z: -1
|
||||||
}
|
}
|
||||||
@@ -677,8 +673,8 @@ Rectangle {
|
|||||||
iconSize: 18
|
iconSize: 18
|
||||||
buttonSize: 28
|
buttonSize: 28
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.userInitiatedExpansion = true
|
root.userInitiatedExpansion = true;
|
||||||
NotificationService.toggleGroupExpansion(notificationGroup?.key || "")
|
NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,7 +693,14 @@ Rectangle {
|
|||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.mediumDuration
|
duration: Theme.mediumDuration
|
||||||
easing.type: Theme.emphasizedEasing
|
easing.type: Theme.emphasizedEasing
|
||||||
onFinished: root.userInitiatedExpansion = false
|
onRunningChanged: {
|
||||||
|
if (running) {
|
||||||
|
root.isAnimating = true;
|
||||||
|
} else {
|
||||||
|
root.isAnimating = false;
|
||||||
|
root.userInitiatedExpansion = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
@@ -23,14 +22,14 @@ QtObject {
|
|||||||
property bool isRebuilding: false
|
property bool isRebuilding: false
|
||||||
|
|
||||||
function rebuildFlatNavigation() {
|
function rebuildFlatNavigation() {
|
||||||
isRebuilding = true
|
isRebuilding = true;
|
||||||
|
|
||||||
const nav = []
|
const nav = [];
|
||||||
const groups = NotificationService.groupedNotifications
|
const groups = NotificationService.groupedNotifications;
|
||||||
|
|
||||||
for (var i = 0; i < groups.length; i++) {
|
for (var i = 0; i < groups.length; i++) {
|
||||||
const group = groups[i]
|
const group = groups[i];
|
||||||
const isExpanded = NotificationService.expandedGroups[group.key] || false
|
const isExpanded = NotificationService.expandedGroups[group.key] || false;
|
||||||
|
|
||||||
nav.push({
|
nav.push({
|
||||||
"type": "group",
|
"type": "group",
|
||||||
@@ -38,515 +37,503 @@ QtObject {
|
|||||||
"notificationIndex": -1,
|
"notificationIndex": -1,
|
||||||
"groupKey": group.key,
|
"groupKey": group.key,
|
||||||
"notificationId": ""
|
"notificationId": ""
|
||||||
})
|
});
|
||||||
|
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
const notifications = group.notifications || []
|
const notifications = group.notifications || [];
|
||||||
const maxNotifications = Math.min(notifications.length, 10)
|
const maxNotifications = Math.min(notifications.length, 10);
|
||||||
for (var j = 0; j < maxNotifications; j++) {
|
for (var j = 0; j < maxNotifications; j++) {
|
||||||
const notifId = String(notifications[j] && notifications[j].notification && notifications[j].notification.id ? notifications[j].notification.id : "")
|
const notifId = String(notifications[j] && notifications[j].notification && notifications[j].notification.id ? notifications[j].notification.id : "");
|
||||||
nav.push({
|
nav.push({
|
||||||
"type": "notification",
|
"type": "notification",
|
||||||
"groupIndex": i,
|
"groupIndex": i,
|
||||||
"notificationIndex": j,
|
"notificationIndex": j,
|
||||||
"groupKey": group.key,
|
"groupKey": group.key,
|
||||||
"notificationId": notifId
|
"notificationId": notifId
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flatNavigation = nav
|
flatNavigation = nav;
|
||||||
updateSelectedIndexFromId()
|
updateSelectedIndexFromId();
|
||||||
isRebuilding = false
|
isRebuilding = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedIndexFromId() {
|
function updateSelectedIndexFromId() {
|
||||||
if (!keyboardNavigationActive) {
|
if (!keyboardNavigationActive) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < flatNavigation.length; i++) {
|
for (var i = 0; i < flatNavigation.length; i++) {
|
||||||
const item = flatNavigation[i]
|
const item = flatNavigation[i];
|
||||||
|
|
||||||
if (selectedItemType === "group" && item.type === "group" && item.groupKey === selectedGroupKey) {
|
if (selectedItemType === "group" && item.type === "group" && item.groupKey === selectedGroupKey) {
|
||||||
selectedFlatIndex = i
|
selectedFlatIndex = i;
|
||||||
selectionVersion++ // Trigger UI update
|
selectionVersion++; // Trigger UI update
|
||||||
return
|
return;
|
||||||
} else if (selectedItemType === "notification" && item.type === "notification" && String(item.notificationId) === String(selectedNotificationId)) {
|
} else if (selectedItemType === "notification" && item.type === "notification" && String(item.notificationId) === String(selectedNotificationId)) {
|
||||||
selectedFlatIndex = i
|
selectedFlatIndex = i;
|
||||||
selectionVersion++ // Trigger UI update
|
selectionVersion++; // Trigger UI update
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not found, try to find the same group but select the group header instead
|
// If not found, try to find the same group but select the group header instead
|
||||||
if (selectedItemType === "notification") {
|
if (selectedItemType === "notification") {
|
||||||
for (var j = 0; j < flatNavigation.length; j++) {
|
for (var j = 0; j < flatNavigation.length; j++) {
|
||||||
const groupItem = flatNavigation[j]
|
const groupItem = flatNavigation[j];
|
||||||
if (groupItem.type === "group" && groupItem.groupKey === selectedGroupKey) {
|
if (groupItem.type === "group" && groupItem.groupKey === selectedGroupKey) {
|
||||||
selectedFlatIndex = j
|
selectedFlatIndex = j;
|
||||||
selectedItemType = "group"
|
selectedItemType = "group";
|
||||||
selectedNotificationId = ""
|
selectedNotificationId = "";
|
||||||
selectionVersion++ // Trigger UI update
|
selectionVersion++; // Trigger UI update
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If still not found, clamp to valid range and update
|
// If still not found, clamp to valid range and update
|
||||||
if (flatNavigation.length > 0) {
|
if (flatNavigation.length > 0) {
|
||||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1)
|
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1);
|
||||||
selectedFlatIndex = Math.max(selectedFlatIndex, 0)
|
selectedFlatIndex = Math.max(selectedFlatIndex, 0);
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
selectionVersion++ // Trigger UI update
|
selectionVersion++; // Trigger UI update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedIdFromIndex() {
|
function updateSelectedIdFromIndex() {
|
||||||
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatNavigation.length) {
|
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatNavigation.length) {
|
||||||
const item = flatNavigation[selectedFlatIndex]
|
const item = flatNavigation[selectedFlatIndex];
|
||||||
selectedItemType = item.type
|
selectedItemType = item.type;
|
||||||
selectedGroupKey = item.groupKey
|
selectedGroupKey = item.groupKey;
|
||||||
selectedNotificationId = item.notificationId
|
selectedNotificationId = item.notificationId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
selectedFlatIndex = 0
|
selectedFlatIndex = 0;
|
||||||
keyboardNavigationActive = false
|
keyboardNavigationActive = false;
|
||||||
showKeyboardHints = false
|
showKeyboardHints = false;
|
||||||
// Reset keyboardActive when modal is reset
|
// Reset keyboardActive when modal is reset
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = false
|
listView.keyboardActive = false;
|
||||||
}
|
}
|
||||||
rebuildFlatNavigation()
|
rebuildFlatNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNext() {
|
function selectNext() {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
if (flatNavigation.length === 0)
|
if (flatNavigation.length === 0)
|
||||||
return
|
return;
|
||||||
|
|
||||||
// Re-enable auto-scrolling when arrow keys are used
|
// Re-enable auto-scrolling when arrow keys are used
|
||||||
if (listView && listView.enableAutoScroll) {
|
if (listView && listView.enableAutoScroll) {
|
||||||
listView.enableAutoScroll()
|
listView.enableAutoScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1)
|
selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1);
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNextWrapping() {
|
function selectNextWrapping() {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
if (flatNavigation.length === 0)
|
if (flatNavigation.length === 0)
|
||||||
return
|
return;
|
||||||
|
|
||||||
// Re-enable auto-scrolling when arrow keys are used
|
// Re-enable auto-scrolling when arrow keys are used
|
||||||
if (listView && listView.enableAutoScroll) {
|
if (listView && listView.enableAutoScroll) {
|
||||||
listView.enableAutoScroll()
|
listView.enableAutoScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedFlatIndex = (selectedFlatIndex + 1) % flatNavigation.length
|
selectedFlatIndex = (selectedFlatIndex + 1) % flatNavigation.length;
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectPrevious() {
|
function selectPrevious() {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
if (flatNavigation.length === 0)
|
if (flatNavigation.length === 0)
|
||||||
return
|
return;
|
||||||
|
|
||||||
// Re-enable auto-scrolling when arrow keys are used
|
// Re-enable auto-scrolling when arrow keys are used
|
||||||
if (listView && listView.enableAutoScroll) {
|
if (listView && listView.enableAutoScroll) {
|
||||||
listView.enableAutoScroll()
|
listView.enableAutoScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0)
|
selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0);
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleGroupExpanded() {
|
function toggleGroupExpanded() {
|
||||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||||
return
|
return;
|
||||||
|
const currentItem = flatNavigation[selectedFlatIndex];
|
||||||
const currentItem = flatNavigation[selectedFlatIndex]
|
const groups = NotificationService.groupedNotifications;
|
||||||
const groups = NotificationService.groupedNotifications
|
const group = groups[currentItem.groupIndex];
|
||||||
const group = groups[currentItem.groupIndex]
|
|
||||||
if (!group)
|
if (!group)
|
||||||
return
|
return;
|
||||||
|
|
||||||
// Prevent expanding groups with < 2 notifications
|
// Prevent expanding groups with < 2 notifications
|
||||||
const notificationCount = group.notifications ? group.notifications.length : 0
|
const notificationCount = group.notifications ? group.notifications.length : 0;
|
||||||
if (notificationCount < 2)
|
if (notificationCount < 2)
|
||||||
return
|
return;
|
||||||
|
const wasExpanded = NotificationService.expandedGroups[group.key] || false;
|
||||||
|
const groupIndex = currentItem.groupIndex;
|
||||||
|
|
||||||
const wasExpanded = NotificationService.expandedGroups[group.key] || false
|
isTogglingGroup = true;
|
||||||
const groupIndex = currentItem.groupIndex
|
NotificationService.toggleGroupExpansion(group.key);
|
||||||
|
rebuildFlatNavigation();
|
||||||
isTogglingGroup = true
|
|
||||||
NotificationService.toggleGroupExpansion(group.key)
|
|
||||||
rebuildFlatNavigation()
|
|
||||||
|
|
||||||
// Smart selection after toggle
|
// Smart selection after toggle
|
||||||
if (!wasExpanded) {
|
if (!wasExpanded) {
|
||||||
// Just expanded - move to first notification in the group
|
// Just expanded - move to first notification in the group
|
||||||
for (var i = 0; i < flatNavigation.length; i++) {
|
for (var i = 0; i < flatNavigation.length; i++) {
|
||||||
if (flatNavigation[i].type === "notification" && flatNavigation[i].groupIndex === groupIndex) {
|
if (flatNavigation[i].type === "notification" && flatNavigation[i].groupIndex === groupIndex) {
|
||||||
selectedFlatIndex = i
|
selectedFlatIndex = i;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Just collapsed - stay on the group header
|
// Just collapsed - stay on the group header
|
||||||
for (var i = 0; i < flatNavigation.length; i++) {
|
for (var i = 0; i < flatNavigation.length; i++) {
|
||||||
if (flatNavigation[i].type === "group" && flatNavigation[i].groupIndex === groupIndex) {
|
if (flatNavigation[i].type === "group" && flatNavigation[i].groupIndex === groupIndex) {
|
||||||
selectedFlatIndex = i
|
selectedFlatIndex = i;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isTogglingGroup = false
|
isTogglingGroup = false;
|
||||||
ensureVisible()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEnterKey() {
|
function handleEnterKey() {
|
||||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||||
return
|
return;
|
||||||
|
const currentItem = flatNavigation[selectedFlatIndex];
|
||||||
const currentItem = flatNavigation[selectedFlatIndex]
|
const groups = NotificationService.groupedNotifications;
|
||||||
const groups = NotificationService.groupedNotifications
|
const group = groups[currentItem.groupIndex];
|
||||||
const group = groups[currentItem.groupIndex]
|
|
||||||
if (!group)
|
if (!group)
|
||||||
return
|
return;
|
||||||
|
|
||||||
if (currentItem.type === "group") {
|
if (currentItem.type === "group") {
|
||||||
const notificationCount = group.notifications ? group.notifications.length : 0
|
const notificationCount = group.notifications ? group.notifications.length : 0;
|
||||||
if (notificationCount >= 2) {
|
if (notificationCount >= 2) {
|
||||||
toggleGroupExpanded()
|
toggleGroupExpanded();
|
||||||
} else {
|
} else {
|
||||||
executeAction(0)
|
executeAction(0);
|
||||||
}
|
}
|
||||||
} else if (currentItem.type === "notification") {
|
} else if (currentItem.type === "notification") {
|
||||||
executeAction(0)
|
executeAction(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleTextExpanded() {
|
function toggleTextExpanded() {
|
||||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||||
return
|
return;
|
||||||
|
const currentItem = flatNavigation[selectedFlatIndex];
|
||||||
const currentItem = flatNavigation[selectedFlatIndex]
|
const groups = NotificationService.groupedNotifications;
|
||||||
const groups = NotificationService.groupedNotifications
|
const group = groups[currentItem.groupIndex];
|
||||||
const group = groups[currentItem.groupIndex]
|
|
||||||
if (!group)
|
if (!group)
|
||||||
return
|
return;
|
||||||
|
let messageId = "";
|
||||||
let messageId = ""
|
|
||||||
|
|
||||||
if (currentItem.type === "group") {
|
if (currentItem.type === "group") {
|
||||||
messageId = group.latestNotification?.notification?.id + "_desc"
|
messageId = group.latestNotification?.notification?.id + "_desc";
|
||||||
} else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
|
} else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
|
||||||
messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc"
|
messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
NotificationService.toggleMessageExpansion(messageId)
|
NotificationService.toggleMessageExpansion(messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeAction(actionIndex) {
|
function executeAction(actionIndex) {
|
||||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||||
return
|
return;
|
||||||
|
const currentItem = flatNavigation[selectedFlatIndex];
|
||||||
const currentItem = flatNavigation[selectedFlatIndex]
|
const groups = NotificationService.groupedNotifications;
|
||||||
const groups = NotificationService.groupedNotifications
|
const group = groups[currentItem.groupIndex];
|
||||||
const group = groups[currentItem.groupIndex]
|
|
||||||
if (!group)
|
if (!group)
|
||||||
return
|
return;
|
||||||
|
let actions = [];
|
||||||
let actions = []
|
|
||||||
|
|
||||||
if (currentItem.type === "group") {
|
if (currentItem.type === "group") {
|
||||||
actions = group.latestNotification?.actions || []
|
actions = group.latestNotification?.actions || [];
|
||||||
} else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
|
} else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
|
||||||
actions = group.notifications[currentItem.notificationIndex]?.actions || []
|
actions = group.notifications[currentItem.notificationIndex]?.actions || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actionIndex >= 0 && actionIndex < actions.length) {
|
if (actionIndex >= 0 && actionIndex < actions.length) {
|
||||||
const action = actions[actionIndex]
|
const action = actions[actionIndex];
|
||||||
if (action.invoke) {
|
if (action.invoke) {
|
||||||
action.invoke()
|
action.invoke();
|
||||||
if (onClose)
|
if (onClose)
|
||||||
onClose()
|
onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelected() {
|
function clearSelected() {
|
||||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
|
||||||
return
|
return;
|
||||||
|
const currentItem = flatNavigation[selectedFlatIndex];
|
||||||
const currentItem = flatNavigation[selectedFlatIndex]
|
const groups = NotificationService.groupedNotifications;
|
||||||
const groups = NotificationService.groupedNotifications
|
const group = groups[currentItem.groupIndex];
|
||||||
const group = groups[currentItem.groupIndex]
|
|
||||||
if (!group)
|
if (!group)
|
||||||
return
|
return;
|
||||||
|
|
||||||
if (currentItem.type === "group") {
|
if (currentItem.type === "group") {
|
||||||
NotificationService.dismissGroup(group.key)
|
NotificationService.dismissGroup(group.key);
|
||||||
} else if (currentItem.type === "notification") {
|
} else if (currentItem.type === "notification") {
|
||||||
const notification = group.notifications[currentItem.notificationIndex]
|
const notification = group.notifications[currentItem.notificationIndex];
|
||||||
NotificationService.dismissNotification(notification)
|
NotificationService.dismissNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuildFlatNavigation()
|
rebuildFlatNavigation();
|
||||||
|
|
||||||
if (flatNavigation.length === 0) {
|
if (flatNavigation.length === 0) {
|
||||||
keyboardNavigationActive = false
|
keyboardNavigationActive = false;
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = false
|
listView.keyboardActive = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1)
|
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1);
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findRepeater(parent) {
|
function findRepeater(parent) {
|
||||||
if (!parent || !parent.children) {
|
if (!parent || !parent.children) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < parent.children.length; i++) {
|
for (var i = 0; i < parent.children.length; i++) {
|
||||||
const child = parent.children[i]
|
const child = parent.children[i];
|
||||||
if (child.objectName === "notificationRepeater") {
|
if (child.objectName === "notificationRepeater") {
|
||||||
return child
|
return child;
|
||||||
}
|
}
|
||||||
const found = findRepeater(child)
|
const found = findRepeater(child);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found
|
return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureVisible() {
|
function ensureVisible() {
|
||||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length || !listView)
|
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length || !listView)
|
||||||
return
|
return;
|
||||||
|
const currentItem = flatNavigation[selectedFlatIndex];
|
||||||
const currentItem = flatNavigation[selectedFlatIndex]
|
|
||||||
|
|
||||||
if (keyboardNavigationActive && currentItem && currentItem.groupIndex >= 0) {
|
if (keyboardNavigationActive && currentItem && currentItem.groupIndex >= 0) {
|
||||||
if (currentItem.type === "notification") {
|
if (currentItem.type === "notification") {
|
||||||
const groupDelegate = listView.itemAtIndex(currentItem.groupIndex)
|
const groupDelegate = listView.itemAtIndex(currentItem.groupIndex);
|
||||||
if (groupDelegate && groupDelegate.children && groupDelegate.children.length > 0) {
|
if (groupDelegate && groupDelegate.children && groupDelegate.children.length > 0) {
|
||||||
const notificationCard = groupDelegate.children[0]
|
const notificationCard = groupDelegate.children[0];
|
||||||
const repeater = findRepeater(notificationCard)
|
const repeater = findRepeater(notificationCard);
|
||||||
|
|
||||||
if (repeater && currentItem.notificationIndex < repeater.count) {
|
if (repeater && currentItem.notificationIndex < repeater.count) {
|
||||||
const notificationItem = repeater.itemAt(currentItem.notificationIndex)
|
const notificationItem = repeater.itemAt(currentItem.notificationIndex);
|
||||||
if (notificationItem) {
|
if (notificationItem) {
|
||||||
const itemPos = notificationItem.mapToItem(listView.contentItem, 0, 0)
|
const itemPos = notificationItem.mapToItem(listView.contentItem, 0, 0);
|
||||||
const itemY = itemPos.y
|
const itemY = itemPos.y;
|
||||||
const itemHeight = notificationItem.height
|
const itemHeight = notificationItem.height;
|
||||||
|
|
||||||
const viewportTop = listView.contentY
|
const viewportTop = listView.contentY;
|
||||||
const viewportBottom = listView.contentY + listView.height
|
const viewportBottom = listView.contentY + listView.height;
|
||||||
|
|
||||||
if (itemY < viewportTop) {
|
if (itemY < viewportTop) {
|
||||||
listView.contentY = itemY - 20
|
listView.contentY = itemY - 20;
|
||||||
} else if (itemY + itemHeight > viewportBottom) {
|
} else if (itemY + itemHeight > viewportBottom) {
|
||||||
listView.contentY = itemY + itemHeight - listView.height + 20
|
listView.contentY = itemY + itemHeight - listView.height + 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
listView.positionViewAtIndex(currentItem.groupIndex, ListView.Contain)
|
listView.positionViewAtIndex(currentItem.groupIndex, ListView.Contain);
|
||||||
}
|
}
|
||||||
|
|
||||||
listView.forceLayout()
|
listView.forceLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKey(event) {
|
function handleKey(event) {
|
||||||
if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) {
|
if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) {
|
||||||
NotificationService.clearAllNotifications()
|
NotificationService.clearAllNotifications();
|
||||||
rebuildFlatNavigation()
|
rebuildFlatNavigation();
|
||||||
if (flatNavigation.length === 0) {
|
if (flatNavigation.length === 0) {
|
||||||
keyboardNavigationActive = false
|
keyboardNavigationActive = false;
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = false
|
listView.keyboardActive = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedFlatIndex = 0
|
selectedFlatIndex = 0;
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
if (keyboardNavigationActive) {
|
if (keyboardNavigationActive) {
|
||||||
keyboardNavigationActive = false
|
keyboardNavigationActive = false;
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else {
|
} else {
|
||||||
if (onClose)
|
if (onClose)
|
||||||
onClose()
|
onClose();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
} else if (event.key === Qt.Key_Down || event.key === 16777237) {
|
} else if (event.key === Qt.Key_Down || event.key === 16777237) {
|
||||||
if (!keyboardNavigationActive) {
|
if (!keyboardNavigationActive) {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
rebuildFlatNavigation() // Ensure we have fresh navigation data
|
rebuildFlatNavigation(); // Ensure we have fresh navigation data
|
||||||
selectedFlatIndex = 0
|
selectedFlatIndex = 0;
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
// Set keyboardActive on listView to show highlight
|
// Set keyboardActive on listView to show highlight
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = true
|
listView.keyboardActive = true;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else {
|
} else {
|
||||||
selectNext()
|
selectNext();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
} else if (event.key === Qt.Key_Up || event.key === 16777235) {
|
} else if (event.key === Qt.Key_Up || event.key === 16777235) {
|
||||||
if (!keyboardNavigationActive) {
|
if (!keyboardNavigationActive) {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
rebuildFlatNavigation() // Ensure we have fresh navigation data
|
rebuildFlatNavigation(); // Ensure we have fresh navigation data
|
||||||
selectedFlatIndex = 0
|
selectedFlatIndex = 0;
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
// Set keyboardActive on listView to show highlight
|
// Set keyboardActive on listView to show highlight
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = true
|
listView.keyboardActive = true;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (selectedFlatIndex === 0) {
|
} else if (selectedFlatIndex === 0) {
|
||||||
keyboardNavigationActive = false
|
keyboardNavigationActive = false;
|
||||||
// Reset keyboardActive when navigation is disabled
|
// Reset keyboardActive when navigation is disabled
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = false
|
listView.keyboardActive = false;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
return
|
return;
|
||||||
} else {
|
} else {
|
||||||
selectPrevious()
|
selectPrevious();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
||||||
if (!keyboardNavigationActive) {
|
if (!keyboardNavigationActive) {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
rebuildFlatNavigation()
|
rebuildFlatNavigation();
|
||||||
selectedFlatIndex = 0
|
selectedFlatIndex = 0;
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = true
|
listView.keyboardActive = true;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
} else {
|
} else {
|
||||||
selectNext()
|
selectNext();
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
||||||
if (!keyboardNavigationActive) {
|
if (!keyboardNavigationActive) {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
rebuildFlatNavigation()
|
rebuildFlatNavigation();
|
||||||
selectedFlatIndex = 0
|
selectedFlatIndex = 0;
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = true
|
listView.keyboardActive = true;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
} else if (selectedFlatIndex === 0) {
|
} else if (selectedFlatIndex === 0) {
|
||||||
keyboardNavigationActive = false
|
keyboardNavigationActive = false;
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = false
|
listView.keyboardActive = false;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
} else {
|
} else {
|
||||||
selectPrevious()
|
selectPrevious();
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||||
if (!keyboardNavigationActive) {
|
if (!keyboardNavigationActive) {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
rebuildFlatNavigation()
|
rebuildFlatNavigation();
|
||||||
selectedFlatIndex = 0
|
selectedFlatIndex = 0;
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = true
|
listView.keyboardActive = true;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
} else {
|
} else {
|
||||||
selectNext()
|
selectNext();
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||||
if (!keyboardNavigationActive) {
|
if (!keyboardNavigationActive) {
|
||||||
keyboardNavigationActive = true
|
keyboardNavigationActive = true;
|
||||||
rebuildFlatNavigation()
|
rebuildFlatNavigation();
|
||||||
selectedFlatIndex = 0
|
selectedFlatIndex = 0;
|
||||||
updateSelectedIdFromIndex()
|
updateSelectedIdFromIndex();
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = true
|
listView.keyboardActive = true;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
ensureVisible()
|
ensureVisible();
|
||||||
} else if (selectedFlatIndex === 0) {
|
} else if (selectedFlatIndex === 0) {
|
||||||
keyboardNavigationActive = false
|
keyboardNavigationActive = false;
|
||||||
if (listView) {
|
if (listView) {
|
||||||
listView.keyboardActive = false
|
listView.keyboardActive = false;
|
||||||
}
|
}
|
||||||
selectionVersion++
|
selectionVersion++;
|
||||||
} else {
|
} else {
|
||||||
selectPrevious()
|
selectPrevious();
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (keyboardNavigationActive) {
|
} else if (keyboardNavigationActive) {
|
||||||
if (event.key === Qt.Key_Space) {
|
if (event.key === Qt.Key_Space) {
|
||||||
toggleGroupExpanded()
|
toggleGroupExpanded();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||||
handleEnterKey()
|
handleEnterKey();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_E) {
|
} else if (event.key === Qt.Key_E) {
|
||||||
toggleTextExpanded()
|
toggleTextExpanded();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
|
} else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
|
||||||
clearSelected()
|
clearSelected();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_Tab) {
|
} else if (event.key === Qt.Key_Tab) {
|
||||||
selectNext()
|
selectNext();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_Backtab) {
|
} else if (event.key === Qt.Key_Backtab) {
|
||||||
selectPrevious()
|
selectPrevious();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
|
} else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
|
||||||
const actionIndex = event.key - Qt.Key_1
|
const actionIndex = event.key - Qt.Key_1;
|
||||||
executeAction(actionIndex)
|
executeAction(actionIndex);
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_F10) {
|
if (event.key === Qt.Key_F10) {
|
||||||
showKeyboardHints = !showKeyboardHints
|
showKeyboardHints = !showKeyboardHints;
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,13 +544,13 @@ QtObject {
|
|||||||
"type": "",
|
"type": "",
|
||||||
"groupIndex": -1,
|
"groupIndex": -1,
|
||||||
"notificationIndex": -1
|
"notificationIndex": -1
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
const result = flatNavigation[selectedFlatIndex] || {
|
const result = flatNavigation[selectedFlatIndex] || {
|
||||||
"type": "",
|
"type": "",
|
||||||
"groupIndex": -1,
|
"groupIndex": -1,
|
||||||
"notificationIndex": -1
|
"notificationIndex": -1
|
||||||
}
|
};
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,31 +13,88 @@ Item {
|
|||||||
property bool saving: false
|
property bool saving: false
|
||||||
|
|
||||||
readonly property var maxHistoryOptions: [
|
readonly property var maxHistoryOptions: [
|
||||||
{ text: "25", value: 25 },
|
{
|
||||||
{ text: "50", value: 50 },
|
text: "25",
|
||||||
{ text: "100", value: 100 },
|
value: 25
|
||||||
{ text: "200", value: 200 },
|
},
|
||||||
{ text: "500", value: 500 },
|
{
|
||||||
{ text: "1000", value: 1000 }
|
text: "50",
|
||||||
|
value: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "100",
|
||||||
|
value: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "200",
|
||||||
|
value: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "500",
|
||||||
|
value: 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "1000",
|
||||||
|
value: 1000
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
readonly property var maxEntrySizeOptions: [
|
readonly property var maxEntrySizeOptions: [
|
||||||
{ text: "1 MB", value: 1048576 },
|
{
|
||||||
{ text: "2 MB", value: 2097152 },
|
text: "1 MB",
|
||||||
{ text: "5 MB", value: 5242880 },
|
value: 1048576
|
||||||
{ text: "10 MB", value: 10485760 },
|
},
|
||||||
{ text: "20 MB", value: 20971520 },
|
{
|
||||||
{ text: "50 MB", value: 52428800 }
|
text: "2 MB",
|
||||||
|
value: 2097152
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "5 MB",
|
||||||
|
value: 5242880
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "10 MB",
|
||||||
|
value: 10485760
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "20 MB",
|
||||||
|
value: 20971520
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "50 MB",
|
||||||
|
value: 52428800
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
readonly property var autoClearOptions: [
|
readonly property var autoClearOptions: [
|
||||||
{ text: I18n.tr("Never"), value: 0 },
|
{
|
||||||
{ text: I18n.tr("1 day"), value: 1 },
|
text: I18n.tr("Never"),
|
||||||
{ text: I18n.tr("3 days"), value: 3 },
|
value: 0
|
||||||
{ text: I18n.tr("7 days"), value: 7 },
|
},
|
||||||
{ text: I18n.tr("14 days"), value: 14 },
|
{
|
||||||
{ text: I18n.tr("30 days"), value: 30 },
|
text: I18n.tr("1 day"),
|
||||||
{ text: I18n.tr("90 days"), value: 90 }
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("3 days"),
|
||||||
|
value: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("7 days"),
|
||||||
|
value: 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("14 days"),
|
||||||
|
value: 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("30 days"),
|
||||||
|
value: 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("90 days"),
|
||||||
|
value: 90
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
function getMaxHistoryText(value) {
|
function getMaxHistoryText(value) {
|
||||||
@@ -139,9 +196,7 @@ Item {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
text: !DMSService.isConnected
|
text: !DMSService.isConnected ? I18n.tr("DMS service is not connected. Clipboard settings are unavailable.") : I18n.tr("Failed to load clipboard configuration.")
|
||||||
? I18n.tr("DMS service is not connected. Clipboard settings are unavailable.")
|
|
||||||
: I18n.tr("Failed to load clipboard configuration.")
|
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width - Theme.iconSizeSmall - Theme.spacingM
|
width: parent.width - Theme.iconSizeSmall - Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -264,7 +319,7 @@ Item {
|
|||||||
settingKey: "disablePersist"
|
settingKey: "disablePersist"
|
||||||
text: I18n.tr("Disable Clipboard Ownership")
|
text: I18n.tr("Disable Clipboard Ownership")
|
||||||
description: I18n.tr("Don't preserve clipboard when apps close")
|
description: I18n.tr("Don't preserve clipboard when apps close")
|
||||||
checked: root.config.disablePersist ?? false
|
checked: root.config.disablePersist ?? true
|
||||||
onToggled: checked => root.saveConfig("disablePersist", checked)
|
onToggled: checked => root.saveConfig("disablePersist", checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,10 @@ Item {
|
|||||||
visible: defaultBar.visible ?? true,
|
visible: defaultBar.visible ?? true,
|
||||||
popupGapsAuto: defaultBar.popupGapsAuto ?? true,
|
popupGapsAuto: defaultBar.popupGapsAuto ?? true,
|
||||||
popupGapsManual: defaultBar.popupGapsManual ?? 4,
|
popupGapsManual: defaultBar.popupGapsManual ?? 4,
|
||||||
maximizeDetection: defaultBar.maximizeDetection ?? true
|
maximizeDetection: defaultBar.maximizeDetection ?? true,
|
||||||
|
scrollEnabled: defaultBar.scrollEnabled ?? true,
|
||||||
|
scrollXBehavior: defaultBar.scrollXBehavior ?? "column",
|
||||||
|
scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace"
|
||||||
};
|
};
|
||||||
SettingsData.addBarConfig(newBar);
|
SettingsData.addBarConfig(newBar);
|
||||||
selectedBarId = newId;
|
selectedBarId = newId;
|
||||||
@@ -751,6 +754,90 @@ Item {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleCard {
|
||||||
|
iconName: "mouse"
|
||||||
|
title: I18n.tr("Scroll Wheel")
|
||||||
|
description: I18n.tr("Control workspaces and columns by scrolling on the bar")
|
||||||
|
visible: selectedBarConfig?.enabled
|
||||||
|
checked: selectedBarConfig?.scrollEnabled ?? true
|
||||||
|
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
|
||||||
|
scrollEnabled: checked
|
||||||
|
})
|
||||||
|
|
||||||
|
SettingsButtonGroupRow {
|
||||||
|
text: I18n.tr("Y Axis")
|
||||||
|
model: CompositorService.isNiri ? [I18n.tr("None"), I18n.tr("Workspace"), I18n.tr("Column")] : [I18n.tr("None"), I18n.tr("Workspace")]
|
||||||
|
currentIndex: {
|
||||||
|
switch (selectedBarConfig?.scrollYBehavior || "workspace") {
|
||||||
|
case "none":
|
||||||
|
return 0;
|
||||||
|
case "workspace":
|
||||||
|
return 1;
|
||||||
|
case "column":
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected)
|
||||||
|
return;
|
||||||
|
let behavior = "workspace";
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
behavior = "none";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
behavior = "workspace";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
behavior = "column";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SettingsData.updateBarConfig(selectedBarId, {
|
||||||
|
scrollYBehavior: behavior
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsButtonGroupRow {
|
||||||
|
text: I18n.tr("X Axis")
|
||||||
|
visible: CompositorService.isNiri
|
||||||
|
model: [I18n.tr("None"), I18n.tr("Workspace"), I18n.tr("Column")]
|
||||||
|
currentIndex: {
|
||||||
|
switch (selectedBarConfig?.scrollXBehavior || "column") {
|
||||||
|
case "none":
|
||||||
|
return 0;
|
||||||
|
case "workspace":
|
||||||
|
return 1;
|
||||||
|
case "column":
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected)
|
||||||
|
return;
|
||||||
|
let behavior = "column";
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
behavior = "none";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
behavior = "workspace";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
behavior = "column";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SettingsData.updateBarConfig(selectedBarId, {
|
||||||
|
scrollXBehavior: behavior
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
iconName: "space_bar"
|
iconName: "space_bar"
|
||||||
title: I18n.tr("Spacing")
|
title: I18n.tr("Spacing")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,490 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
function getBarComponentsFromSettings() {
|
||||||
|
const bars = SettingsData.barConfigs || []
|
||||||
|
return bars.map(bar => ({
|
||||||
|
"id": "bar:" + bar.id,
|
||||||
|
"name": bar.name || "Bar",
|
||||||
|
"description": I18n.tr("Individual bar configuration"),
|
||||||
|
"icon": "toolbar",
|
||||||
|
"barId": bar.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
property var variantComponents: getVariantComponentsList()
|
||||||
|
|
||||||
|
function getVariantComponentsList() {
|
||||||
|
return [...getBarComponentsFromSettings(), {
|
||||||
|
"id": "dock",
|
||||||
|
"name": I18n.tr("Application Dock"),
|
||||||
|
"description": I18n.tr("Bottom dock for pinned and running applications"),
|
||||||
|
"icon": "dock"
|
||||||
|
}, {
|
||||||
|
"id": "notifications",
|
||||||
|
"name": I18n.tr("Notification Popups"),
|
||||||
|
"description": I18n.tr("Notification toast popups"),
|
||||||
|
"icon": "notifications"
|
||||||
|
}, {
|
||||||
|
"id": "wallpaper",
|
||||||
|
"name": I18n.tr("Wallpaper"),
|
||||||
|
"description": I18n.tr("Desktop background images"),
|
||||||
|
"icon": "wallpaper"
|
||||||
|
}, {
|
||||||
|
"id": "osd",
|
||||||
|
"name": I18n.tr("On-Screen Displays"),
|
||||||
|
"description": I18n.tr("Volume, brightness, and other system OSDs"),
|
||||||
|
"icon": "picture_in_picture"
|
||||||
|
}, {
|
||||||
|
"id": "toast",
|
||||||
|
"name": I18n.tr("Toast Messages"),
|
||||||
|
"description": I18n.tr("System toast notifications"),
|
||||||
|
"icon": "campaign"
|
||||||
|
}, {
|
||||||
|
"id": "notepad",
|
||||||
|
"name": I18n.tr("Notepad Slideout"),
|
||||||
|
"description": I18n.tr("Quick note-taking slideout panel"),
|
||||||
|
"icon": "sticky_note_2"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onBarConfigsChanged() {
|
||||||
|
variantComponents = getVariantComponentsList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScreenPreferences(componentId) {
|
||||||
|
if (componentId.startsWith("bar:")) {
|
||||||
|
const barId = componentId.substring(4)
|
||||||
|
const barConfig = SettingsData.getBarConfig(barId)
|
||||||
|
return barConfig?.screenPreferences || ["all"]
|
||||||
|
}
|
||||||
|
return SettingsData.screenPreferences && SettingsData.screenPreferences[componentId] || ["all"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScreenPreferences(componentId, screenNames) {
|
||||||
|
if (componentId.startsWith("bar:")) {
|
||||||
|
const barId = componentId.substring(4)
|
||||||
|
SettingsData.updateBarConfig(barId, {
|
||||||
|
"screenPreferences": screenNames
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var prefs = SettingsData.screenPreferences || {}
|
||||||
|
var newPrefs = Object.assign({}, prefs)
|
||||||
|
newPrefs[componentId] = screenNames
|
||||||
|
SettingsData.set("screenPreferences", newPrefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getShowOnLastDisplay(componentId) {
|
||||||
|
if (componentId.startsWith("bar:")) {
|
||||||
|
const barId = componentId.substring(4)
|
||||||
|
const barConfig = SettingsData.getBarConfig(barId)
|
||||||
|
return barConfig?.showOnLastDisplay ?? true
|
||||||
|
}
|
||||||
|
return SettingsData.showOnLastDisplay && SettingsData.showOnLastDisplay[componentId] || false
|
||||||
|
}
|
||||||
|
|
||||||
|
function setShowOnLastDisplay(componentId, enabled) {
|
||||||
|
if (componentId.startsWith("bar:")) {
|
||||||
|
const barId = componentId.substring(4)
|
||||||
|
SettingsData.updateBarConfig(barId, {
|
||||||
|
"showOnLastDisplay": enabled
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var prefs = SettingsData.showOnLastDisplay || {}
|
||||||
|
var newPrefs = Object.assign({}, prefs)
|
||||||
|
newPrefs[componentId] = enabled
|
||||||
|
SettingsData.set("showOnLastDisplay", newPrefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
contentHeight: mainColumn.height + Theme.spacingXL
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
|
||||||
|
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: screensInfoSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: screensInfoSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "monitor"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Connected Displays")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Configure which displays show shell components")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Available Screens (") + Quickshell.screens.length + ")"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Display Name Format")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
id: displayModeGroup
|
||||||
|
model: [I18n.tr("Name"), I18n.tr("Model")]
|
||||||
|
currentIndex: SettingsData.displayNameMode === "model" ? 1 : 0
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected)
|
||||||
|
return
|
||||||
|
SettingsData.displayNameMode = index === 1 ? "model" : "system"
|
||||||
|
SettingsData.saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onDisplayNameModeChanged() {
|
||||||
|
displayModeGroup.currentIndex = SettingsData.displayNameMode === "model" ? 1 : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: screenRow.implicitHeight + Theme.spacingS * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: screenRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "desktop_windows"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM * 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS / 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SettingsData.getScreenDisplayName(modelData)
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
property var wlrOutput: WlrOutputService.wlrOutputAvailable ? WlrOutputService.getOutput(modelData.name) : null
|
||||||
|
property var currentMode: wlrOutput?.currentMode
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (parent.currentMode) {
|
||||||
|
return parent.currentMode.width + "×" + parent.currentMode.height + "@" + Math.round(parent.currentMode.refresh / 1000) + "Hz"
|
||||||
|
}
|
||||||
|
return modelData.width + "×" + modelData.height
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "•"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SettingsData.displayNameMode === "system" ? (modelData.model || "Unknown Model") : modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.variantComponents
|
||||||
|
|
||||||
|
delegate: StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: componentSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: componentSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: modelData.icon
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.description
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Show on screens:")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
property string componentId: modelData.id
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("All displays")
|
||||||
|
description: I18n.tr("Show on all connected displays")
|
||||||
|
checked: {
|
||||||
|
var prefs = root.getScreenPreferences(parent.componentId)
|
||||||
|
return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")
|
||||||
|
}
|
||||||
|
onToggled: checked => {
|
||||||
|
if (checked) {
|
||||||
|
root.setScreenPreferences(parent.componentId, ["all"])
|
||||||
|
} else {
|
||||||
|
root.setScreenPreferences(parent.componentId, [])
|
||||||
|
const cid = parent.componentId
|
||||||
|
if (["dankBar", "dock", "notifications", "osd", "toast"].includes(cid) || cid.startsWith("bar:")) {
|
||||||
|
root.setShowOnLastDisplay(cid, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Show on Last Display")
|
||||||
|
description: I18n.tr("Always show when there's only one connected display")
|
||||||
|
checked: root.getShowOnLastDisplay(parent.componentId)
|
||||||
|
visible: {
|
||||||
|
const prefs = root.getScreenPreferences(parent.componentId)
|
||||||
|
const isAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")
|
||||||
|
const cid = parent.componentId
|
||||||
|
const isRelevantComponent = ["dankBar", "dock", "notifications", "osd", "toast", "notepad"].includes(cid) || cid.startsWith("bar:")
|
||||||
|
return !isAll && isRelevantComponent
|
||||||
|
}
|
||||||
|
onToggled: checked => {
|
||||||
|
root.setShowOnLastDisplay(parent.componentId, checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.2
|
||||||
|
visible: {
|
||||||
|
var prefs = root.getScreenPreferences(parent.componentId)
|
||||||
|
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: {
|
||||||
|
var prefs = root.getScreenPreferences(parent.componentId)
|
||||||
|
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all")
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
delegate: DankToggle {
|
||||||
|
property var screenData: modelData
|
||||||
|
property string componentId: parent.parent.componentId
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
text: SettingsData.getScreenDisplayName(screenData)
|
||||||
|
description: screenData.width + "×" + screenData.height + " • " + (SettingsData.displayNameMode === "system" ? (screenData.model || "Unknown Model") : screenData.name)
|
||||||
|
checked: {
|
||||||
|
var prefs = root.getScreenPreferences(componentId)
|
||||||
|
if (typeof prefs[0] === "string" && prefs[0] === "all")
|
||||||
|
return false
|
||||||
|
return SettingsData.isScreenInPreferences(screenData, prefs)
|
||||||
|
}
|
||||||
|
onToggled: checked => {
|
||||||
|
var currentPrefs = root.getScreenPreferences(componentId)
|
||||||
|
if (typeof currentPrefs[0] === "string" && currentPrefs[0] === "all") {
|
||||||
|
currentPrefs = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const screenModelIndex = SettingsData.getScreenModelIndex(screenData)
|
||||||
|
|
||||||
|
var newPrefs = currentPrefs.filter(pref => {
|
||||||
|
if (typeof pref === "string")
|
||||||
|
return false
|
||||||
|
if (pref.modelIndex !== undefined && screenModelIndex >= 0) {
|
||||||
|
return !(pref.model === screenData.model && pref.modelIndex === screenModelIndex)
|
||||||
|
}
|
||||||
|
return pref.name !== screenData.name || pref.model !== screenData.model
|
||||||
|
})
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
const prefObj = {
|
||||||
|
"name": screenData.name,
|
||||||
|
"model": screenData.model || ""
|
||||||
|
}
|
||||||
|
if (screenModelIndex >= 0) {
|
||||||
|
prefObj.modelIndex = screenModelIndex
|
||||||
|
}
|
||||||
|
newPrefs.push(prefObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setScreenPreferences(componentId, newPrefs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+61
-535
@@ -6,128 +6,21 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: displaysTab
|
id: root
|
||||||
|
|
||||||
function formatGammaTime(isoString) {
|
function formatGammaTime(isoString) {
|
||||||
if (!isoString)
|
if (!isoString)
|
||||||
return "";
|
return ""
|
||||||
try {
|
try {
|
||||||
const date = new Date(isoString);
|
const date = new Date(isoString)
|
||||||
if (isNaN(date.getTime()))
|
if (isNaN(date.getTime()))
|
||||||
return "";
|
return ""
|
||||||
return date.toLocaleTimeString(Qt.locale(), "HH:mm");
|
return date.toLocaleTimeString(Qt.locale(), "HH:mm")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return "";
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBarComponentsFromSettings() {
|
|
||||||
const bars = SettingsData.barConfigs || [];
|
|
||||||
return bars.map(bar => ({
|
|
||||||
"id": "bar:" + bar.id,
|
|
||||||
"name": bar.name || "Bar",
|
|
||||||
"description": I18n.tr("Individual bar configuration"),
|
|
||||||
"icon": "toolbar",
|
|
||||||
"barId": bar.id
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
property var variantComponents: getVariantComponentsList()
|
|
||||||
|
|
||||||
function getVariantComponentsList() {
|
|
||||||
return [...getBarComponentsFromSettings(),
|
|
||||||
{
|
|
||||||
"id": "dock",
|
|
||||||
"name": I18n.tr("Application Dock"),
|
|
||||||
"description": I18n.tr("Bottom dock for pinned and running applications"),
|
|
||||||
"icon": "dock"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "notifications",
|
|
||||||
"name": I18n.tr("Notification Popups"),
|
|
||||||
"description": I18n.tr("Notification toast popups"),
|
|
||||||
"icon": "notifications"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "wallpaper",
|
|
||||||
"name": I18n.tr("Wallpaper"),
|
|
||||||
"description": I18n.tr("Desktop background images"),
|
|
||||||
"icon": "wallpaper"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "osd",
|
|
||||||
"name": I18n.tr("On-Screen Displays"),
|
|
||||||
"description": I18n.tr("Volume, brightness, and other system OSDs"),
|
|
||||||
"icon": "picture_in_picture"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "toast",
|
|
||||||
"name": I18n.tr("Toast Messages"),
|
|
||||||
"description": I18n.tr("System toast notifications"),
|
|
||||||
"icon": "campaign"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "notepad",
|
|
||||||
"name": I18n.tr("Notepad Slideout"),
|
|
||||||
"description": I18n.tr("Quick note-taking slideout panel"),
|
|
||||||
"icon": "sticky_note_2"
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onBarConfigsChanged() {
|
|
||||||
variantComponents = getVariantComponentsList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScreenPreferences(componentId) {
|
|
||||||
if (componentId.startsWith("bar:")) {
|
|
||||||
const barId = componentId.substring(4);
|
|
||||||
const barConfig = SettingsData.getBarConfig(barId);
|
|
||||||
return barConfig?.screenPreferences || ["all"];
|
|
||||||
}
|
|
||||||
return SettingsData.screenPreferences && SettingsData.screenPreferences[componentId] || ["all"];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setScreenPreferences(componentId, screenNames) {
|
|
||||||
if (componentId.startsWith("bar:")) {
|
|
||||||
const barId = componentId.substring(4);
|
|
||||||
SettingsData.updateBarConfig(barId, {
|
|
||||||
screenPreferences: screenNames
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var prefs = SettingsData.screenPreferences || {};
|
|
||||||
var newPrefs = Object.assign({}, prefs);
|
|
||||||
newPrefs[componentId] = screenNames;
|
|
||||||
SettingsData.set("screenPreferences", newPrefs);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getShowOnLastDisplay(componentId) {
|
|
||||||
if (componentId.startsWith("bar:")) {
|
|
||||||
const barId = componentId.substring(4);
|
|
||||||
const barConfig = SettingsData.getBarConfig(barId);
|
|
||||||
return barConfig?.showOnLastDisplay ?? true;
|
|
||||||
}
|
|
||||||
return SettingsData.showOnLastDisplay && SettingsData.showOnLastDisplay[componentId] || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setShowOnLastDisplay(componentId, enabled) {
|
|
||||||
if (componentId.startsWith("bar:")) {
|
|
||||||
const barId = componentId.substring(4);
|
|
||||||
SettingsData.updateBarConfig(barId, {
|
|
||||||
showOnLastDisplay: enabled
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var prefs = SettingsData.showOnLastDisplay || {};
|
|
||||||
var newPrefs = Object.assign({}, prefs);
|
|
||||||
newPrefs[componentId] = enabled;
|
|
||||||
SettingsData.set("showOnLastDisplay", newPrefs);
|
|
||||||
}
|
|
||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
@@ -181,16 +74,17 @@ Item {
|
|||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Night Mode")
|
text: I18n.tr("Night Mode")
|
||||||
description: DisplayService.gammaControlAvailable ? I18n.tr("Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.") : I18n.tr("Gamma control not available. Requires DMS API v6+.")
|
description: DisplayService.gammaControlAvailable ? I18n.tr("Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.") : I18n.tr(
|
||||||
|
"Gamma control not available. Requires DMS API v6+.")
|
||||||
checked: DisplayService.nightModeEnabled
|
checked: DisplayService.nightModeEnabled
|
||||||
enabled: DisplayService.gammaControlAvailable
|
enabled: DisplayService.gammaControlAvailable
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
DisplayService.toggleNightMode();
|
DisplayService.toggleNightMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onNightModeEnabledChanged() {
|
function onNightModeEnabledChanged() {
|
||||||
nightModeToggle.checked = DisplayService.nightModeEnabled;
|
nightModeToggle.checked = DisplayService.nightModeEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
target: DisplayService
|
target: DisplayService
|
||||||
@@ -210,17 +104,17 @@ Item {
|
|||||||
description: SessionData.nightModeAutoEnabled ? I18n.tr("Color temperature for night mode") : I18n.tr("Warm color temperature to apply")
|
description: SessionData.nightModeAutoEnabled ? I18n.tr("Color temperature for night mode") : I18n.tr("Warm color temperature to apply")
|
||||||
currentValue: SessionData.nightModeTemperature + "K"
|
currentValue: SessionData.nightModeTemperature + "K"
|
||||||
options: {
|
options: {
|
||||||
var temps = [];
|
var temps = []
|
||||||
for (var i = 2500; i <= 6000; i += 500) {
|
for (var i = 2500; i <= 6000; i += 500) {
|
||||||
temps.push(i + "K");
|
temps.push(i + "K")
|
||||||
}
|
}
|
||||||
return temps;
|
return temps
|
||||||
}
|
}
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
var temp = parseInt(value.replace("K", ""));
|
var temp = parseInt(value.replace("K", ""))
|
||||||
SessionData.setNightModeTemperature(temp);
|
SessionData.setNightModeTemperature(temp)
|
||||||
if (SessionData.nightModeHighTemperature < temp) {
|
if (SessionData.nightModeHighTemperature < temp) {
|
||||||
SessionData.setNightModeHighTemperature(temp);
|
SessionData.setNightModeHighTemperature(temp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,17 +126,17 @@ Item {
|
|||||||
currentValue: SessionData.nightModeHighTemperature + "K"
|
currentValue: SessionData.nightModeHighTemperature + "K"
|
||||||
visible: SessionData.nightModeAutoEnabled
|
visible: SessionData.nightModeAutoEnabled
|
||||||
options: {
|
options: {
|
||||||
var temps = [];
|
var temps = []
|
||||||
var minTemp = SessionData.nightModeTemperature;
|
var minTemp = SessionData.nightModeTemperature
|
||||||
for (var i = Math.max(2500, minTemp); i <= 10000; i += 500) {
|
for (var i = Math.max(2500, minTemp); i <= 10000; i += 500) {
|
||||||
temps.push(i + "K");
|
temps.push(i + "K")
|
||||||
}
|
}
|
||||||
return temps;
|
return temps
|
||||||
}
|
}
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
var temp = parseInt(value.replace("K", ""));
|
var temp = parseInt(value.replace("K", ""))
|
||||||
if (temp >= SessionData.nightModeTemperature) {
|
if (temp >= SessionData.nightModeTemperature) {
|
||||||
SessionData.setNightModeHighTemperature(temp);
|
SessionData.setNightModeHighTemperature(temp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,17 +151,17 @@ Item {
|
|||||||
visible: DisplayService.gammaControlAvailable
|
visible: DisplayService.gammaControlAvailable
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
if (checked && !DisplayService.nightModeEnabled) {
|
if (checked && !DisplayService.nightModeEnabled) {
|
||||||
DisplayService.toggleNightMode();
|
DisplayService.toggleNightMode()
|
||||||
} else if (!checked && DisplayService.nightModeEnabled) {
|
} else if (!checked && DisplayService.nightModeEnabled) {
|
||||||
DisplayService.toggleNightMode();
|
DisplayService.toggleNightMode()
|
||||||
}
|
}
|
||||||
SessionData.setNightModeAutoEnabled(checked);
|
SessionData.setNightModeAutoEnabled(checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SessionData
|
target: SessionData
|
||||||
function onNightModeAutoEnabledChanged() {
|
function onNightModeAutoEnabledChanged() {
|
||||||
automaticToggle.checked = SessionData.nightModeAutoEnabled;
|
automaticToggle.checked = SessionData.nightModeAutoEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,7 +175,7 @@ Item {
|
|||||||
Connections {
|
Connections {
|
||||||
target: SessionData
|
target: SessionData
|
||||||
function onNightModeAutoEnabledChanged() {
|
function onNightModeAutoEnabledChanged() {
|
||||||
automaticSettings.visible = SessionData.nightModeAutoEnabled;
|
automaticSettings.visible = SessionData.nightModeAutoEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,32 +188,29 @@ Item {
|
|||||||
width: 200
|
width: 200
|
||||||
height: 45
|
height: 45
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
model: [
|
model: [{
|
||||||
{
|
|
||||||
"text": "Time",
|
"text": "Time",
|
||||||
"icon": "access_time"
|
"icon": "access_time"
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"text": "Location",
|
"text": "Location",
|
||||||
"icon": "place"
|
"icon": "place"
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0;
|
currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0
|
||||||
Qt.callLater(updateIndicator);
|
Qt.callLater(updateIndicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
onTabClicked: index => {
|
onTabClicked: index => {
|
||||||
DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time");
|
DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time")
|
||||||
currentIndex = index;
|
currentIndex = index
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SessionData
|
target: SessionData
|
||||||
function onNightModeAutoModeChanged() {
|
function onNightModeAutoModeChanged() {
|
||||||
modeTabBarNight.currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0;
|
modeTabBarNight.currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0
|
||||||
Qt.callLater(modeTabBarNight.updateIndicator);
|
Qt.callLater(modeTabBarNight.updateIndicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,14 +267,14 @@ Item {
|
|||||||
dropdownWidth: 70
|
dropdownWidth: 70
|
||||||
currentValue: SessionData.nightModeStartHour.toString()
|
currentValue: SessionData.nightModeStartHour.toString()
|
||||||
options: {
|
options: {
|
||||||
var hours = [];
|
var hours = []
|
||||||
for (var i = 0; i < 24; i++) {
|
for (var i = 0; i < 24; i++) {
|
||||||
hours.push(i.toString());
|
hours.push(i.toString())
|
||||||
}
|
}
|
||||||
return hours;
|
return hours
|
||||||
}
|
}
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
SessionData.setNightModeStartHour(parseInt(value));
|
SessionData.setNightModeStartHour(parseInt(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,14 +282,14 @@ Item {
|
|||||||
dropdownWidth: 70
|
dropdownWidth: 70
|
||||||
currentValue: SessionData.nightModeStartMinute.toString().padStart(2, '0')
|
currentValue: SessionData.nightModeStartMinute.toString().padStart(2, '0')
|
||||||
options: {
|
options: {
|
||||||
var minutes = [];
|
var minutes = []
|
||||||
for (var i = 0; i < 60; i += 5) {
|
for (var i = 0; i < 60; i += 5) {
|
||||||
minutes.push(i.toString().padStart(2, '0'));
|
minutes.push(i.toString().padStart(2, '0'))
|
||||||
}
|
}
|
||||||
return minutes;
|
return minutes
|
||||||
}
|
}
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
SessionData.setNightModeStartMinute(parseInt(value));
|
SessionData.setNightModeStartMinute(parseInt(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,14 +310,14 @@ Item {
|
|||||||
dropdownWidth: 70
|
dropdownWidth: 70
|
||||||
currentValue: SessionData.nightModeEndHour.toString()
|
currentValue: SessionData.nightModeEndHour.toString()
|
||||||
options: {
|
options: {
|
||||||
var hours = [];
|
var hours = []
|
||||||
for (var i = 0; i < 24; i++) {
|
for (var i = 0; i < 24; i++) {
|
||||||
hours.push(i.toString());
|
hours.push(i.toString())
|
||||||
}
|
}
|
||||||
return hours;
|
return hours
|
||||||
}
|
}
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
SessionData.setNightModeEndHour(parseInt(value));
|
SessionData.setNightModeEndHour(parseInt(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,14 +325,14 @@ Item {
|
|||||||
dropdownWidth: 70
|
dropdownWidth: 70
|
||||||
currentValue: SessionData.nightModeEndMinute.toString().padStart(2, '0')
|
currentValue: SessionData.nightModeEndMinute.toString().padStart(2, '0')
|
||||||
options: {
|
options: {
|
||||||
var minutes = [];
|
var minutes = []
|
||||||
for (var i = 0; i < 60; i += 5) {
|
for (var i = 0; i < 60; i += 5) {
|
||||||
minutes.push(i.toString().padStart(2, '0'));
|
minutes.push(i.toString().padStart(2, '0'))
|
||||||
}
|
}
|
||||||
return minutes;
|
return minutes
|
||||||
}
|
}
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
SessionData.setNightModeEndMinute(parseInt(value));
|
SessionData.setNightModeEndMinute(parseInt(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,13 +352,13 @@ Item {
|
|||||||
description: I18n.tr("Automatically detect location based on IP address")
|
description: I18n.tr("Automatically detect location based on IP address")
|
||||||
checked: SessionData.nightModeUseIPLocation || false
|
checked: SessionData.nightModeUseIPLocation || false
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
SessionData.setNightModeUseIPLocation(checked);
|
SessionData.setNightModeUseIPLocation(checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SessionData
|
target: SessionData
|
||||||
function onNightModeUseIPLocationChanged() {
|
function onNightModeUseIPLocationChanged() {
|
||||||
ipLocationToggle.checked = SessionData.nightModeUseIPLocation;
|
ipLocationToggle.checked = SessionData.nightModeUseIPLocation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,9 +393,9 @@ Item {
|
|||||||
text: SessionData.latitude.toString()
|
text: SessionData.latitude.toString()
|
||||||
placeholderText: "0.0"
|
placeholderText: "0.0"
|
||||||
onEditingFinished: {
|
onEditingFinished: {
|
||||||
const lat = parseFloat(text);
|
const lat = parseFloat(text)
|
||||||
if (!isNaN(lat) && lat >= -90 && lat <= 90 && lat !== SessionData.latitude) {
|
if (!isNaN(lat) && lat >= -90 && lat <= 90 && lat !== SessionData.latitude) {
|
||||||
SessionData.setLatitude(lat);
|
SessionData.setLatitude(lat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,9 +416,9 @@ Item {
|
|||||||
text: SessionData.longitude.toString()
|
text: SessionData.longitude.toString()
|
||||||
placeholderText: "0.0"
|
placeholderText: "0.0"
|
||||||
onEditingFinished: {
|
onEditingFinished: {
|
||||||
const lon = parseFloat(text);
|
const lon = parseFloat(text)
|
||||||
if (!isNaN(lon) && lon >= -180 && lon <= 180 && lon !== SessionData.longitude) {
|
if (!isNaN(lon) && lon >= -180 && lon <= 180 && lon !== SessionData.longitude) {
|
||||||
SessionData.setLongitude(lon);
|
SessionData.setLongitude(lon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -678,7 +569,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: displaysTab.formatGammaTime(DisplayService.gammaSunriseTime)
|
text: root.formatGammaTime(DisplayService.gammaSunriseTime)
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -714,7 +605,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: displaysTab.formatGammaTime(DisplayService.gammaSunsetTime)
|
text: root.formatGammaTime(DisplayService.gammaSunsetTime)
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -761,7 +652,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: displaysTab.formatGammaTime(DisplayService.gammaNextTransition)
|
text: root.formatGammaTime(DisplayService.gammaNextTransition)
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -773,371 +664,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledRect {
|
|
||||||
width: parent.width
|
|
||||||
height: screensInfoSection.implicitHeight + Theme.spacingL * 2
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: screensInfoSection
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "monitor"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Connected Displays")
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Configure which displays show shell components")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Available Screens (") + Quickshell.screens.length + ")"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 1
|
|
||||||
height: 1
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Display Name Format")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
DankButtonGroup {
|
|
||||||
id: displayModeGroup
|
|
||||||
model: [I18n.tr("Name"), I18n.tr("Model")]
|
|
||||||
currentIndex: SettingsData.displayNameMode === "model" ? 1 : 0
|
|
||||||
onSelectionChanged: (index, selected) => {
|
|
||||||
if (!selected)
|
|
||||||
return;
|
|
||||||
SettingsData.displayNameMode = index === 1 ? "model" : "system";
|
|
||||||
SettingsData.saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onDisplayNameModeChanged() {
|
|
||||||
displayModeGroup.currentIndex = SettingsData.displayNameMode === "model" ? 1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: screenRow.implicitHeight + Theme.spacingS * 2
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
|
||||||
border.width: 0
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: screenRow
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "desktop_windows"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingM * 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingXS / 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SettingsData.getScreenDisplayName(modelData)
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
property var wlrOutput: WlrOutputService.wlrOutputAvailable ? WlrOutputService.getOutput(modelData.name) : null
|
|
||||||
property var currentMode: wlrOutput?.currentMode
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (parent.currentMode) {
|
|
||||||
return parent.currentMode.width + "×" + parent.currentMode.height + "@" + Math.round(parent.currentMode.refresh / 1000) + "Hz";
|
|
||||||
}
|
|
||||||
return modelData.width + "×" + modelData.height;
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "•"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SettingsData.displayNameMode === "system" ? (modelData.model || "Unknown Model") : modelData.name
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: displaysTab.variantComponents
|
|
||||||
|
|
||||||
delegate: StyledRect {
|
|
||||||
width: parent.width
|
|
||||||
height: componentSection.implicitHeight + Theme.spacingL * 2
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: componentSection
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: modelData.icon
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.name
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.description
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Show on screens:")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
property string componentId: modelData.id
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: I18n.tr("All displays")
|
|
||||||
description: I18n.tr("Show on all connected displays")
|
|
||||||
checked: {
|
|
||||||
var prefs = displaysTab.getScreenPreferences(parent.componentId);
|
|
||||||
return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
|
||||||
}
|
|
||||||
onToggled: checked => {
|
|
||||||
if (checked) {
|
|
||||||
displaysTab.setScreenPreferences(parent.componentId, ["all"]);
|
|
||||||
} else {
|
|
||||||
displaysTab.setScreenPreferences(parent.componentId, []);
|
|
||||||
const cid = parent.componentId;
|
|
||||||
if (["dankBar", "dock", "notifications", "osd", "toast"].includes(cid) || cid.startsWith("bar:")) {
|
|
||||||
displaysTab.setShowOnLastDisplay(cid, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: I18n.tr("Show on Last Display")
|
|
||||||
description: I18n.tr("Always show when there's only one connected display")
|
|
||||||
checked: displaysTab.getShowOnLastDisplay(parent.componentId)
|
|
||||||
visible: {
|
|
||||||
const prefs = displaysTab.getScreenPreferences(parent.componentId);
|
|
||||||
const isAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
|
||||||
const cid = parent.componentId;
|
|
||||||
const isRelevantComponent = ["dankBar", "dock", "notifications", "osd", "toast", "notepad"].includes(cid) || cid.startsWith("bar:");
|
|
||||||
return !isAll && isRelevantComponent;
|
|
||||||
}
|
|
||||||
onToggled: checked => {
|
|
||||||
displaysTab.setShowOnLastDisplay(parent.componentId, checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Theme.outline
|
|
||||||
opacity: 0.2
|
|
||||||
visible: {
|
|
||||||
var prefs = displaysTab.getScreenPreferences(parent.componentId);
|
|
||||||
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
visible: {
|
|
||||||
var prefs = displaysTab.getScreenPreferences(parent.componentId);
|
|
||||||
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all");
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
delegate: DankToggle {
|
|
||||||
property var screenData: modelData
|
|
||||||
property string componentId: parent.parent.componentId
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
text: SettingsData.getScreenDisplayName(screenData)
|
|
||||||
description: screenData.width + "×" + screenData.height + " • " + (SettingsData.displayNameMode === "system" ? (screenData.model || "Unknown Model") : screenData.name)
|
|
||||||
checked: {
|
|
||||||
var prefs = displaysTab.getScreenPreferences(componentId);
|
|
||||||
if (typeof prefs[0] === "string" && prefs[0] === "all")
|
|
||||||
return false;
|
|
||||||
return SettingsData.isScreenInPreferences(screenData, prefs);
|
|
||||||
}
|
|
||||||
onToggled: checked => {
|
|
||||||
var currentPrefs = displaysTab.getScreenPreferences(componentId);
|
|
||||||
if (typeof currentPrefs[0] === "string" && currentPrefs[0] === "all") {
|
|
||||||
currentPrefs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const screenModelIndex = SettingsData.getScreenModelIndex(screenData);
|
|
||||||
|
|
||||||
var newPrefs = currentPrefs.filter(pref => {
|
|
||||||
if (typeof pref === "string")
|
|
||||||
return false;
|
|
||||||
if (pref.modelIndex !== undefined && screenModelIndex >= 0) {
|
|
||||||
return !(pref.model === screenData.model && pref.modelIndex === screenModelIndex);
|
|
||||||
}
|
|
||||||
return pref.name !== screenData.name || pref.model !== screenData.model;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checked) {
|
|
||||||
const prefObj = {
|
|
||||||
name: screenData.name,
|
|
||||||
model: screenData.model || ""
|
|
||||||
};
|
|
||||||
if (screenModelIndex >= 0) {
|
|
||||||
prefObj.modelIndex = screenModelIndex;
|
|
||||||
}
|
|
||||||
newPrefs.push(prefObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
displaysTab.setScreenPreferences(componentId, newPrefs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ Item {
|
|||||||
DankTextField {
|
DankTextField {
|
||||||
id: searchField
|
id: searchField
|
||||||
width: parent.width - addButton.width - Theme.spacingM
|
width: parent.width - addButton.width - Theme.spacingM
|
||||||
height: 44
|
height: Math.round(Theme.fontSizeMedium * 3)
|
||||||
placeholderText: I18n.tr("Search keybinds...")
|
placeholderText: I18n.tr("Search keybinds...")
|
||||||
leftIconName: "search"
|
leftIconName: "search"
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
@@ -240,8 +240,8 @@ Item {
|
|||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: addButton
|
id: addButton
|
||||||
width: 44
|
width: searchField.height
|
||||||
height: 44
|
height: searchField.height
|
||||||
circular: false
|
circular: false
|
||||||
iconName: "add"
|
iconName: "add"
|
||||||
iconSize: Theme.iconSize
|
iconSize: Theme.iconSize
|
||||||
@@ -328,40 +328,25 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
DankButton {
|
||||||
id: fixButton
|
id: fixButton
|
||||||
width: fixButtonText.implicitWidth + Theme.spacingL * 2
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
visible: warningBox.showError || warningBox.showSetup
|
visible: warningBox.showError || warningBox.showSetup
|
||||||
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: fixButtonText
|
|
||||||
text: {
|
text: {
|
||||||
if (KeybindsService.fixing)
|
if (KeybindsService.fixing)
|
||||||
return I18n.tr("Fixing...");
|
return I18n.tr("Fixing...")
|
||||||
if (warningBox.showSetup)
|
if (warningBox.showSetup)
|
||||||
return I18n.tr("Setup");
|
return I18n.tr("Setup")
|
||||||
return I18n.tr("Fix Now");
|
return I18n.tr("Fix Now")
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
backgroundColor: Theme.primary
|
||||||
font.weight: Font.Medium
|
textColor: Theme.primaryText
|
||||||
color: Theme.surface
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
enabled: !KeybindsService.fixing
|
enabled: !KeybindsService.fixing
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
onClicked: KeybindsService.fixDmsBindsInclude()
|
onClicked: KeybindsService.fixDmsBindsInclude()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
StyledRect {
|
StyledRect {
|
||||||
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
||||||
@@ -382,9 +367,10 @@ Item {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
readonly property real chipHeight: allChip.implicitHeight + Theme.spacingM
|
||||||
width: allChip.implicitWidth + Theme.spacingL
|
width: allChip.implicitWidth + Theme.spacingL
|
||||||
height: 32
|
height: chipHeight
|
||||||
radius: 16
|
radius: chipHeight / 2
|
||||||
color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest
|
color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -412,9 +398,10 @@ Item {
|
|||||||
required property string modelData
|
required property string modelData
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
|
readonly property real chipHeight: catText.implicitHeight + Theme.spacingM
|
||||||
width: catText.implicitWidth + Theme.spacingL
|
width: catText.implicitWidth + Theme.spacingL
|
||||||
height: 32
|
height: chipHeight
|
||||||
radius: 16
|
radius: chipHeight / 2
|
||||||
color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest)
|
color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest)
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|||||||
@@ -23,6 +23,15 @@ Item {
|
|||||||
iconName: "refresh"
|
iconName: "refresh"
|
||||||
title: I18n.tr("System Updater")
|
title: I18n.tr("System Updater")
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Hide Updater Widget", "When updater widget is used, then hide it if no update found")
|
||||||
|
description: I18n.tr("When updater widget is used, then hide it if no update found")
|
||||||
|
checked: SettingsData.updaterHideWidget
|
||||||
|
onToggled: checked => {
|
||||||
|
SettingsData.set("updaterHideWidget", checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
text: I18n.tr("Use Custom Command")
|
text: I18n.tr("Use Custom Command")
|
||||||
description: I18n.tr("Use custom command for update your system")
|
description: I18n.tr("Use custom command for update your system")
|
||||||
|
|||||||
@@ -371,9 +371,14 @@ Item {
|
|||||||
widgetObj.pciId = "";
|
widgetObj.pciId = "";
|
||||||
}
|
}
|
||||||
if (widgetId === "controlCenterButton") {
|
if (widgetId === "controlCenterButton") {
|
||||||
widgetObj.showNetworkIcon = true;
|
widgetObj.showNetworkIcon = SettingsData.controlCenterShowNetworkIcon;
|
||||||
widgetObj.showBluetoothIcon = true;
|
widgetObj.showBluetoothIcon = SettingsData.controlCenterShowBluetoothIcon;
|
||||||
widgetObj.showAudioIcon = true;
|
widgetObj.showAudioIcon = SettingsData.controlCenterShowAudioIcon;
|
||||||
|
widgetObj.showVpnIcon = SettingsData.controlCenterShowVpnIcon;
|
||||||
|
widgetObj.showBrightnessIcon = SettingsData.controlCenterShowBrightnessIcon;
|
||||||
|
widgetObj.showMicIcon = SettingsData.controlCenterShowMicIcon;
|
||||||
|
widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon;
|
||||||
|
widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon;
|
||||||
}
|
}
|
||||||
if (widgetId === "diskUsage")
|
if (widgetId === "diskUsage")
|
||||||
widgetObj.mountPath = "/";
|
widgetObj.mountPath = "/";
|
||||||
@@ -423,9 +428,14 @@ Item {
|
|||||||
else if (widget.id === "gpuTemp")
|
else if (widget.id === "gpuTemp")
|
||||||
newWidget.pciId = "";
|
newWidget.pciId = "";
|
||||||
if (widget.id === "controlCenterButton") {
|
if (widget.id === "controlCenterButton") {
|
||||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
|
||||||
|
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
|
||||||
|
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
|
||||||
|
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
|
||||||
|
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
|
||||||
|
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
|
||||||
}
|
}
|
||||||
widgets[i] = newWidget;
|
widgets[i] = newWidget;
|
||||||
break;
|
break;
|
||||||
@@ -471,9 +481,14 @@ Item {
|
|||||||
if (widget.pciId !== undefined)
|
if (widget.pciId !== undefined)
|
||||||
newWidget.pciId = widget.pciId;
|
newWidget.pciId = widget.pciId;
|
||||||
if (widget.id === "controlCenterButton") {
|
if (widget.id === "controlCenterButton") {
|
||||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
|
||||||
|
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
|
||||||
|
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
|
||||||
|
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
|
||||||
|
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
|
||||||
|
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
|
||||||
}
|
}
|
||||||
widgets[widgetIndex] = newWidget;
|
widgets[widgetIndex] = newWidget;
|
||||||
setWidgetsForSection(sectionId, widgets);
|
setWidgetsForSection(sectionId, widgets);
|
||||||
@@ -541,41 +556,48 @@ Item {
|
|||||||
if (widget.pciId !== undefined)
|
if (widget.pciId !== undefined)
|
||||||
newWidget.pciId = widget.pciId;
|
newWidget.pciId = widget.pciId;
|
||||||
if (widget.id === "controlCenterButton") {
|
if (widget.id === "controlCenterButton") {
|
||||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
|
||||||
|
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
|
||||||
|
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
|
||||||
|
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
|
||||||
|
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
|
||||||
|
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
|
||||||
}
|
}
|
||||||
widgets[widgetIndex] = newWidget;
|
widgets[widgetIndex] = newWidget;
|
||||||
setWidgetsForSection(sectionId, widgets);
|
setWidgetsForSection(sectionId, widgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) {
|
function handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) {
|
||||||
switch (settingName) {
|
var widgets = getWidgetsForSection(sectionId).slice();
|
||||||
case "showNetworkIcon":
|
if (widgetIndex < 0 || widgetIndex >= widgets.length)
|
||||||
SettingsData.set("controlCenterShowNetworkIcon", value);
|
return;
|
||||||
break;
|
|
||||||
case "showBluetoothIcon":
|
var widget = widgets[widgetIndex];
|
||||||
SettingsData.set("controlCenterShowBluetoothIcon", value);
|
if (typeof widget === "string") {
|
||||||
break;
|
widget = {
|
||||||
case "showAudioIcon":
|
"id": widget,
|
||||||
SettingsData.set("controlCenterShowAudioIcon", value);
|
"enabled": true
|
||||||
break;
|
};
|
||||||
case "showVpnIcon":
|
|
||||||
SettingsData.set("controlCenterShowVpnIcon", value);
|
|
||||||
break;
|
|
||||||
case "showBrightnessIcon":
|
|
||||||
SettingsData.set("controlCenterShowBrightnessIcon", value);
|
|
||||||
break;
|
|
||||||
case "showMicIcon":
|
|
||||||
SettingsData.set("controlCenterShowMicIcon", value);
|
|
||||||
break;
|
|
||||||
case "showBatteryIcon":
|
|
||||||
SettingsData.set("controlCenterShowBatteryIcon", value);
|
|
||||||
break;
|
|
||||||
case "showPrinterIcon":
|
|
||||||
SettingsData.set("controlCenterShowPrinterIcon", value);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var newWidget = {
|
||||||
|
"id": widget.id,
|
||||||
|
"enabled": widget.enabled !== undefined ? widget.enabled : true,
|
||||||
|
"showNetworkIcon": widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon,
|
||||||
|
"showBluetoothIcon": widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon,
|
||||||
|
"showAudioIcon": widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon,
|
||||||
|
"showVpnIcon": widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon,
|
||||||
|
"showBrightnessIcon": widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon,
|
||||||
|
"showMicIcon": widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon,
|
||||||
|
"showBatteryIcon": widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon,
|
||||||
|
"showPrinterIcon": widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon
|
||||||
|
};
|
||||||
|
newWidget[settingName] = value;
|
||||||
|
|
||||||
|
widgets[widgetIndex] = newWidget;
|
||||||
|
setWidgetsForSection(sectionId, widgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePrivacySettingChanged(sectionId, widgetIndex, settingName, value) {
|
function handlePrivacySettingChanged(sectionId, widgetIndex, settingName, value) {
|
||||||
@@ -626,9 +648,14 @@ Item {
|
|||||||
if (widget.showSwap !== undefined)
|
if (widget.showSwap !== undefined)
|
||||||
newWidget.showSwap = widget.showSwap;
|
newWidget.showSwap = widget.showSwap;
|
||||||
if (widget.id === "controlCenterButton") {
|
if (widget.id === "controlCenterButton") {
|
||||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
|
||||||
|
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
|
||||||
|
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
|
||||||
|
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
|
||||||
|
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
|
||||||
|
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
|
||||||
}
|
}
|
||||||
widgets[widgetIndex] = newWidget;
|
widgets[widgetIndex] = newWidget;
|
||||||
setWidgetsForSection(sectionId, widgets);
|
setWidgetsForSection(sectionId, widgets);
|
||||||
@@ -678,9 +705,14 @@ Item {
|
|||||||
if (widget.keyboardLayoutNameCompactMode !== undefined)
|
if (widget.keyboardLayoutNameCompactMode !== undefined)
|
||||||
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
|
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
|
||||||
if (widget.id === "controlCenterButton") {
|
if (widget.id === "controlCenterButton") {
|
||||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
|
||||||
|
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
|
||||||
|
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
|
||||||
|
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
|
||||||
|
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
|
||||||
|
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
|
||||||
}
|
}
|
||||||
widgets[widgetIndex] = newWidget;
|
widgets[widgetIndex] = newWidget;
|
||||||
setWidgetsForSection(sectionId, widgets);
|
setWidgetsForSection(sectionId, widgets);
|
||||||
@@ -730,9 +762,14 @@ Item {
|
|||||||
if (widget.keyboardLayoutNameCompactMode !== undefined)
|
if (widget.keyboardLayoutNameCompactMode !== undefined)
|
||||||
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
|
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
|
||||||
if (widget.id === "controlCenterButton") {
|
if (widget.id === "controlCenterButton") {
|
||||||
newWidget.showNetworkIcon = widget.showNetworkIcon ?? true;
|
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||||
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? true;
|
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||||
newWidget.showAudioIcon = widget.showAudioIcon ?? true;
|
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
|
||||||
|
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
|
||||||
|
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
|
||||||
|
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
|
||||||
|
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
|
||||||
|
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
|
||||||
}
|
}
|
||||||
widgets[i] = newWidget;
|
widgets[i] = newWidget;
|
||||||
widget = newWidget;
|
widget = newWidget;
|
||||||
@@ -789,6 +826,16 @@ Item {
|
|||||||
item.showBluetoothIcon = widget.showBluetoothIcon;
|
item.showBluetoothIcon = widget.showBluetoothIcon;
|
||||||
if (widget.showAudioIcon !== undefined)
|
if (widget.showAudioIcon !== undefined)
|
||||||
item.showAudioIcon = widget.showAudioIcon;
|
item.showAudioIcon = widget.showAudioIcon;
|
||||||
|
if (widget.showVpnIcon !== undefined)
|
||||||
|
item.showVpnIcon = widget.showVpnIcon;
|
||||||
|
if (widget.showBrightnessIcon !== undefined)
|
||||||
|
item.showBrightnessIcon = widget.showBrightnessIcon;
|
||||||
|
if (widget.showMicIcon !== undefined)
|
||||||
|
item.showMicIcon = widget.showMicIcon;
|
||||||
|
if (widget.showBatteryIcon !== undefined)
|
||||||
|
item.showBatteryIcon = widget.showBatteryIcon;
|
||||||
|
if (widget.showPrinterIcon !== undefined)
|
||||||
|
item.showPrinterIcon = widget.showPrinterIcon;
|
||||||
if (widget.minimumWidth !== undefined)
|
if (widget.minimumWidth !== undefined)
|
||||||
item.minimumWidth = widget.minimumWidth;
|
item.minimumWidth = widget.minimumWidth;
|
||||||
if (widget.showSwap !== undefined)
|
if (widget.showSwap !== undefined)
|
||||||
|
|||||||
@@ -806,50 +806,42 @@ Column {
|
|||||||
{
|
{
|
||||||
icon: "lan",
|
icon: "lan",
|
||||||
label: I18n.tr("Network"),
|
label: I18n.tr("Network"),
|
||||||
setting: "showNetworkIcon",
|
setting: "showNetworkIcon"
|
||||||
checked: SettingsData.controlCenterShowNetworkIcon
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "vpn_lock",
|
icon: "vpn_lock",
|
||||||
label: I18n.tr("VPN"),
|
label: I18n.tr("VPN"),
|
||||||
setting: "showVpnIcon",
|
setting: "showVpnIcon"
|
||||||
checked: SettingsData.controlCenterShowVpnIcon
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "bluetooth",
|
icon: "bluetooth",
|
||||||
label: I18n.tr("Bluetooth"),
|
label: I18n.tr("Bluetooth"),
|
||||||
setting: "showBluetoothIcon",
|
setting: "showBluetoothIcon"
|
||||||
checked: SettingsData.controlCenterShowBluetoothIcon
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "volume_up",
|
icon: "volume_up",
|
||||||
label: I18n.tr("Audio"),
|
label: I18n.tr("Audio"),
|
||||||
setting: "showAudioIcon",
|
setting: "showAudioIcon"
|
||||||
checked: SettingsData.controlCenterShowAudioIcon
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "mic",
|
icon: "mic",
|
||||||
label: I18n.tr("Microphone"),
|
label: I18n.tr("Microphone"),
|
||||||
setting: "showMicIcon",
|
setting: "showMicIcon"
|
||||||
checked: SettingsData.controlCenterShowMicIcon
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "brightness_high",
|
icon: "brightness_high",
|
||||||
label: I18n.tr("Brightness"),
|
label: I18n.tr("Brightness"),
|
||||||
setting: "showBrightnessIcon",
|
setting: "showBrightnessIcon"
|
||||||
checked: SettingsData.controlCenterShowBrightnessIcon
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "battery_full",
|
icon: "battery_full",
|
||||||
label: I18n.tr("Battery"),
|
label: I18n.tr("Battery"),
|
||||||
setting: "showBatteryIcon",
|
setting: "showBatteryIcon"
|
||||||
checked: SettingsData.controlCenterShowBatteryIcon
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "print",
|
icon: "print",
|
||||||
label: I18n.tr("Printer"),
|
label: I18n.tr("Printer"),
|
||||||
setting: "showPrinterIcon",
|
setting: "showPrinterIcon"
|
||||||
checked: SettingsData.controlCenterShowPrinterIcon
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -857,6 +849,30 @@ Column {
|
|||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
|
function getCheckedState() {
|
||||||
|
var wd = controlCenterContextMenu.widgetData;
|
||||||
|
switch (modelData.setting) {
|
||||||
|
case "showNetworkIcon":
|
||||||
|
return wd?.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||||
|
case "showVpnIcon":
|
||||||
|
return wd?.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
|
||||||
|
case "showBluetoothIcon":
|
||||||
|
return wd?.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
|
||||||
|
case "showAudioIcon":
|
||||||
|
return wd?.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
|
||||||
|
case "showMicIcon":
|
||||||
|
return wd?.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
|
||||||
|
case "showBrightnessIcon":
|
||||||
|
return wd?.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
|
||||||
|
case "showBatteryIcon":
|
||||||
|
return wd?.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
|
||||||
|
case "showPrinterIcon":
|
||||||
|
return wd?.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
width: menuColumn.width
|
width: menuColumn.width
|
||||||
height: 32
|
height: 32
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -891,7 +907,7 @@ Column {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 40
|
width: 40
|
||||||
height: 20
|
height: 20
|
||||||
checked: modelData.checked
|
checked: getCheckedState()
|
||||||
onToggled: {
|
onToggled: {
|
||||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled);
|
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,8 +216,9 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property int maxTextureSize: 8192
|
readonly property int maxTextureSize: 8192
|
||||||
property int textureWidth: Math.min(modelData.width, maxTextureSize)
|
property real screenScale: CompositorService.getScreenScale(modelData)
|
||||||
property int textureHeight: Math.min(modelData.height, maxTextureSize)
|
property int textureWidth: Math.min(Math.round(modelData.width * screenScale), maxTextureSize)
|
||||||
|
property int textureHeight: Math.min(Math.round(modelData.height * screenScale), maxTextureSize)
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: currentWallpaper
|
id: currentWallpaper
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Singleton {
|
|||||||
id: cavaProcess
|
id: cavaProcess
|
||||||
|
|
||||||
running: root.cavaAvailable && root.refCount > 0
|
running: root.cavaAvailable && root.refCount > 0
|
||||||
command: ["sh", "-c", "printf '[general]\\nframerate=25\\nbars=6\\nautosens=0\\nsensitivity=30\\nlower_cutoff_freq=50\\nhigher_cutoff_freq=12000\\n[input]\\nmethod=pipewire\\nsource=auto\\nsample_rate=48000\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nchannels=mono\\nmono_option=average\\n[smoothing]\\nnoise_reduction=35\\nintegral=90\\ngravity=95\\nignore=2\\nmonstercat=1.5' | cava -p /dev/stdin"]
|
command: ["sh", "-c", "printf '[general]\\nframerate=25\\nbars=6\\nautosens=0\\nsensitivity=30\\nlower_cutoff_freq=50\\nhigher_cutoff_freq=12000\\n[input]\\nsample_rate=48000\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nchannels=mono\\nmono_option=average\\n[smoothing]\\nnoise_reduction=35\\nintegral=90\\ngravity=95\\nignore=2\\nmonstercat=1.5' | cava -p /dev/stdin"]
|
||||||
|
|
||||||
onRunningChanged: {
|
onRunningChanged: {
|
||||||
if (!running) {
|
if (!running) {
|
||||||
|
|||||||
@@ -53,10 +53,13 @@ Singleton {
|
|||||||
signal gammaStateUpdate(var data)
|
signal gammaStateUpdate(var data)
|
||||||
signal openUrlRequested(string url)
|
signal openUrlRequested(string url)
|
||||||
signal appPickerRequested(var data)
|
signal appPickerRequested(var data)
|
||||||
|
signal screensaverStateUpdate(var data)
|
||||||
|
|
||||||
property bool capsLockState: false
|
property bool capsLockState: false
|
||||||
|
property bool screensaverInhibited: false
|
||||||
|
property var screensaverInhibitors: []
|
||||||
|
|
||||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser"]
|
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser"]
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
@@ -371,6 +374,10 @@ Singleton {
|
|||||||
} else if (data.url) {
|
} else if (data.url) {
|
||||||
openUrlRequested(data.url);
|
openUrlRequested(data.url);
|
||||||
}
|
}
|
||||||
|
} else if (service === "freedesktop.screensaver") {
|
||||||
|
screensaverInhibited = data.inhibited || false;
|
||||||
|
screensaverInhibitors = data.inhibitors || [];
|
||||||
|
screensaverStateUpdate(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
readonly property string mangoDmsDir: configDir + "/mango/dms"
|
||||||
|
readonly property string outputsPath: mangoDmsDir + "/outputs.conf"
|
||||||
|
|
||||||
property bool dwlAvailable: false
|
property bool dwlAvailable: false
|
||||||
property var outputs: ({})
|
property var outputs: ({})
|
||||||
property var tagCount: 9
|
property var tagCount: 9
|
||||||
@@ -263,4 +269,85 @@ Singleton {
|
|||||||
|
|
||||||
return Array.from(visibleTags).sort((a, b) => a - b);
|
return Array.from(visibleTags).sort((a, b) => a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateOutputsConfig(outputsData) {
|
||||||
|
if (!outputsData || Object.keys(outputsData).length === 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
let lines = ["# Auto-generated by DMS - do not edit manually", "# VRR is global: set adaptive_sync=1 in config.conf", ""]
|
||||||
|
|
||||||
|
for (const outputName in outputsData) {
|
||||||
|
const output = outputsData[outputName]
|
||||||
|
if (!output)
|
||||||
|
continue
|
||||||
|
|
||||||
|
let width = 1920
|
||||||
|
let height = 1080
|
||||||
|
let refreshRate = 60
|
||||||
|
if (output.modes && output.current_mode !== undefined) {
|
||||||
|
const mode = output.modes[output.current_mode]
|
||||||
|
if (mode) {
|
||||||
|
width = mode.width || 1920
|
||||||
|
height = mode.height || 1080
|
||||||
|
refreshRate = Math.round((mode.refresh_rate || 60000) / 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = output.logical?.x ?? 0
|
||||||
|
const y = output.logical?.y ?? 0
|
||||||
|
const scale = output.logical?.scale ?? 1.0
|
||||||
|
const transform = transformToMango(output.logical?.transform ?? "Normal")
|
||||||
|
|
||||||
|
const rule = [
|
||||||
|
outputName,
|
||||||
|
"0.55",
|
||||||
|
"1",
|
||||||
|
"tile",
|
||||||
|
transform,
|
||||||
|
scale,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
refreshRate
|
||||||
|
].join(",")
|
||||||
|
|
||||||
|
lines.push("monitorrule=" + rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("")
|
||||||
|
|
||||||
|
const content = lines.join("\n")
|
||||||
|
|
||||||
|
Proc.runCommand("mango-write-outputs", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.warn("DwlService: Failed to write outputs config:", output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.info("DwlService: Generated outputs config at", outputsPath)
|
||||||
|
if (CompositorService.isDwl)
|
||||||
|
reloadConfig()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadConfig() {
|
||||||
|
Proc.runCommand("mango-reload", ["mmsg", "-d", "reload_config"], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0)
|
||||||
|
console.warn("DwlService: mmsg reload_config failed:", output)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformToMango(transform) {
|
||||||
|
switch (transform) {
|
||||||
|
case "Normal": return 0
|
||||||
|
case "90": return 1
|
||||||
|
case "180": return 2
|
||||||
|
case "270": return 3
|
||||||
|
case "Flipped": return 4
|
||||||
|
case "Flipped90": return 5
|
||||||
|
case "Flipped180": return 6
|
||||||
|
case "Flipped270": return 7
|
||||||
|
default: return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
readonly property string hyprDmsDir: configDir + "/hypr/dms"
|
||||||
|
readonly property string outputsPath: hyprDmsDir + "/outputs.conf"
|
||||||
|
|
||||||
|
function getOutputIdentifier(output, outputName) {
|
||||||
|
if (SettingsData.displayNameMode === "model" && output.make && output.model) {
|
||||||
|
return "desc:" + output.make + " " + output.model
|
||||||
|
}
|
||||||
|
return outputName
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateOutputsConfig(outputsData) {
|
||||||
|
if (!outputsData || Object.keys(outputsData).length === 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
let lines = ["# Auto-generated by DMS - do not edit manually", ""]
|
||||||
|
|
||||||
|
for (const outputName in outputsData) {
|
||||||
|
const output = outputsData[outputName]
|
||||||
|
if (!output)
|
||||||
|
continue
|
||||||
|
|
||||||
|
let resolution = "preferred"
|
||||||
|
if (output.modes && output.current_mode !== undefined) {
|
||||||
|
const mode = output.modes[output.current_mode]
|
||||||
|
if (mode)
|
||||||
|
resolution = mode.width + "x" + mode.height + "@" + (mode.refresh_rate / 1000).toFixed(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = output.logical?.x ?? 0
|
||||||
|
const y = output.logical?.y ?? 0
|
||||||
|
const position = x + "x" + y
|
||||||
|
|
||||||
|
const scale = output.logical?.scale ?? 1.0
|
||||||
|
|
||||||
|
const identifier = getOutputIdentifier(output, outputName)
|
||||||
|
let monitorLine = "monitor = " + identifier + ", " + resolution + ", " + position + ", " + scale
|
||||||
|
|
||||||
|
const transform = transformToHyprland(output.logical?.transform ?? "Normal")
|
||||||
|
if (transform !== 0)
|
||||||
|
monitorLine += ", transform, " + transform
|
||||||
|
|
||||||
|
if (output.vrr_supported && output.vrr_enabled)
|
||||||
|
monitorLine += ", vrr, 1"
|
||||||
|
|
||||||
|
lines.push(monitorLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("")
|
||||||
|
|
||||||
|
const content = lines.join("\n")
|
||||||
|
|
||||||
|
Proc.runCommand("hypr-write-outputs", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.warn("HyprlandService: Failed to write outputs config:", output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.info("HyprlandService: Generated outputs config at", outputsPath)
|
||||||
|
if (CompositorService.isHyprland)
|
||||||
|
reloadConfig()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadConfig() {
|
||||||
|
Proc.runCommand("hyprctl-reload", ["hyprctl", "reload"], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0)
|
||||||
|
console.warn("HyprlandService: hyprctl reload failed:", output)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformToHyprland(transform) {
|
||||||
|
switch (transform) {
|
||||||
|
case "Normal": return 0
|
||||||
|
case "90": return 1
|
||||||
|
case "180": return 2
|
||||||
|
case "270": return 3
|
||||||
|
case "Flipped": return 4
|
||||||
|
case "Flipped90": return 5
|
||||||
|
case "Flipped180": return 6
|
||||||
|
case "Flipped270": return 7
|
||||||
|
default: return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hyprlandToTransform(value) {
|
||||||
|
switch (value) {
|
||||||
|
case 0: return "Normal"
|
||||||
|
case 1: return "90"
|
||||||
|
case 2: return "180"
|
||||||
|
case 3: return "270"
|
||||||
|
case 4: return "Flipped"
|
||||||
|
case 5: return "Flipped90"
|
||||||
|
case 6: return "Flipped180"
|
||||||
|
case 7: return "Flipped270"
|
||||||
|
default: return "Normal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@ Singleton {
|
|||||||
property bool respectInhibitors: true
|
property bool respectInhibitors: true
|
||||||
property bool _enableGate: true
|
property bool _enableGate: true
|
||||||
|
|
||||||
|
readonly property bool externalInhibitActive: DMSService.screensaverInhibited
|
||||||
|
|
||||||
readonly property bool isOnBattery: BatteryService.batteryAvailable && !BatteryService.isPluggedIn
|
readonly property bool isOnBattery: BatteryService.batteryAvailable && !BatteryService.isPluggedIn
|
||||||
readonly property int monitorTimeout: isOnBattery ? SettingsData.batteryMonitorTimeout : SettingsData.acMonitorTimeout
|
readonly property int monitorTimeout: isOnBattery ? SettingsData.batteryMonitorTimeout : SettingsData.acMonitorTimeout
|
||||||
readonly property int lockTimeout: isOnBattery ? SettingsData.batteryLockTimeout : SettingsData.acLockTimeout
|
readonly property int lockTimeout: isOnBattery ? SettingsData.batteryLockTimeout : SettingsData.acLockTimeout
|
||||||
@@ -141,6 +143,19 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onExternalInhibitActiveChanged: {
|
||||||
|
if (externalInhibitActive) {
|
||||||
|
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
|
||||||
|
console.info("IdleService: External idle inhibit active from:", apps || "unknown");
|
||||||
|
SessionService.idleInhibited = true;
|
||||||
|
SessionService.inhibitReason = "External app: " + (apps || "unknown");
|
||||||
|
} else {
|
||||||
|
console.info("IdleService: External idle inhibit released");
|
||||||
|
SessionService.idleInhibited = false;
|
||||||
|
SessionService.inhibitReason = "Keep system awake";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!idleMonitorAvailable) {
|
if (!idleMonitorAvailable) {
|
||||||
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");
|
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");
|
||||||
@@ -148,5 +163,11 @@ Singleton {
|
|||||||
console.info("IdleService: Initialized with idle monitoring support");
|
console.info("IdleService: Initialized with idle monitoring support");
|
||||||
createIdleMonitors();
|
createIdleMonitors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (externalInhibitActive) {
|
||||||
|
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
|
||||||
|
SessionService.idleInhibited = true;
|
||||||
|
SessionService.inhibitReason = "External app: " + (apps || "unknown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
@@ -23,6 +23,8 @@ Singleton {
|
|||||||
property var windows: []
|
property var windows: []
|
||||||
property var displayScales: ({})
|
property var displayScales: ({})
|
||||||
|
|
||||||
|
property var _realOutputs: ({})
|
||||||
|
|
||||||
property bool inOverview: false
|
property bool inOverview: false
|
||||||
|
|
||||||
property int currentKeyboardLayoutIndex: 0
|
property int currentKeyboardLayoutIndex: 0
|
||||||
@@ -66,6 +68,19 @@ Singleton {
|
|||||||
onTriggered: root.doGenerateNiriLayoutConfig()
|
onTriggered: root.doGenerateNiriLayoutConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property int _lastGapValue: -1
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onBarConfigsChanged() {
|
||||||
|
const newGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
|
||||||
|
if (newGaps === root._lastGapValue)
|
||||||
|
return;
|
||||||
|
root._lastGapValue = newGaps;
|
||||||
|
generateNiriLayoutConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: validateProcess
|
id: validateProcess
|
||||||
command: ["niri", "validate"]
|
command: ["niri", "validate"]
|
||||||
@@ -201,12 +216,12 @@ Singleton {
|
|||||||
const ws = workspaces[w.workspace_id];
|
const ws = workspaces[w.workspace_id];
|
||||||
if (!ws) {
|
if (!ws) {
|
||||||
return {
|
return {
|
||||||
window: w,
|
"window": w,
|
||||||
outputX: 999999,
|
"outputX": 999999,
|
||||||
outputY: 999999,
|
"outputY": 999999,
|
||||||
wsIdx: 999999,
|
"wsIdx": 999999,
|
||||||
col: 999999,
|
"col": 999999,
|
||||||
row: 999999
|
"row": 999999
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,12 +234,12 @@ Singleton {
|
|||||||
const row = (pos && pos.length >= 2) ? pos[1] : 999999;
|
const row = (pos && pos.length >= 2) ? pos[1] : 999999;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
window: w,
|
"window": w,
|
||||||
outputX: outputX,
|
"outputX": outputX,
|
||||||
outputY: outputY,
|
"outputY": outputY,
|
||||||
wsIdx: ws.idx,
|
"wsIdx": ws.idx,
|
||||||
col: col,
|
"col": col,
|
||||||
row: row
|
"row": row
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -291,6 +306,9 @@ Singleton {
|
|||||||
case 'WorkspaceUrgencyChanged':
|
case 'WorkspaceUrgencyChanged':
|
||||||
handleWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged);
|
handleWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged);
|
||||||
break;
|
break;
|
||||||
|
case 'WindowUrgencyChanged':
|
||||||
|
handleWindowUrgencyChanged(event.WindowUrgencyChanged);
|
||||||
|
break;
|
||||||
case 'ScreenshotCaptured':
|
case 'ScreenshotCaptured':
|
||||||
handleScreenshotCaptured(event.ScreenshotCaptured);
|
handleScreenshotCaptured(event.ScreenshotCaptured);
|
||||||
break;
|
break;
|
||||||
@@ -558,6 +576,23 @@ Singleton {
|
|||||||
windowUrgentChanged();
|
windowUrgentChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleWindowUrgencyChanged(data) {
|
||||||
|
const windowIndex = windows.findIndex(w => w.id === data.id);
|
||||||
|
if (windowIndex < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const updatedWindows = [...windows];
|
||||||
|
const updatedWindow = {};
|
||||||
|
for (let prop in updatedWindows[windowIndex]) {
|
||||||
|
updatedWindow[prop] = updatedWindows[windowIndex][prop];
|
||||||
|
}
|
||||||
|
updatedWindow.is_urgent = data.urgent;
|
||||||
|
updatedWindows[windowIndex] = updatedWindow;
|
||||||
|
windows = updatedWindows;
|
||||||
|
|
||||||
|
windowUrgentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
function handleScreenshotCaptured(data) {
|
function handleScreenshotCaptured(data) {
|
||||||
if (!data.path)
|
if (!data.path)
|
||||||
return;
|
return;
|
||||||
@@ -565,7 +600,7 @@ Singleton {
|
|||||||
const editor = Quickshell.env("DMS_SCREENSHOT_EDITOR");
|
const editor = Quickshell.env("DMS_SCREENSHOT_EDITOR");
|
||||||
const command = editor === "satty" ? ["satty", "-f", data.path] : ["swappy", "-f", data.path];
|
const command = editor === "satty" ? ["satty", "-f", data.path] : ["swappy", "-f", data.path];
|
||||||
Quickshell.execDetached({
|
Quickshell.execDetached({
|
||||||
command: command
|
"command": command
|
||||||
});
|
});
|
||||||
pendingScreenshotPath = "";
|
pendingScreenshotPath = "";
|
||||||
}
|
}
|
||||||
@@ -959,7 +994,14 @@ Singleton {
|
|||||||
const cornerRadius = typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12;
|
const cornerRadius = typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12;
|
||||||
const gaps = typeof SettingsData !== "undefined" ? Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)) : 4;
|
const gaps = typeof SettingsData !== "undefined" ? Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)) : 4;
|
||||||
|
|
||||||
const configContent = `layout {
|
const dmsWarning = `// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const configContent = dmsWarning + `layout {
|
||||||
gaps ${gaps}
|
gaps ${gaps}
|
||||||
|
|
||||||
border {
|
border {
|
||||||
@@ -977,7 +1019,7 @@ window-rule {
|
|||||||
draw-border-with-background false
|
draw-border-with-background false
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const alttabContent = `recent-windows {
|
const alttabContent = dmsWarning + `recent-windows {
|
||||||
highlight {
|
highlight {
|
||||||
corner-radius ${cornerRadius}
|
corner-radius ${cornerRadius}
|
||||||
}
|
}
|
||||||
@@ -1014,6 +1056,141 @@ window-rule {
|
|||||||
writeBlurruleProcess.running = true;
|
writeBlurruleProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateOutputPosition(outputName, x, y) {
|
||||||
|
if (!outputs || !outputs[outputName])
|
||||||
|
return;
|
||||||
|
const updatedOutputs = {};
|
||||||
|
for (const name in outputs) {
|
||||||
|
const output = outputs[name];
|
||||||
|
if (name === outputName && output.logical) {
|
||||||
|
updatedOutputs[name] = JSON.parse(JSON.stringify(output));
|
||||||
|
updatedOutputs[name].logical.x = x;
|
||||||
|
updatedOutputs[name].logical.y = y;
|
||||||
|
} else {
|
||||||
|
updatedOutputs[name] = output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputs = updatedOutputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyOutputConfig(outputName, config, callback) {
|
||||||
|
if (!CompositorService.isNiri || !outputName) {
|
||||||
|
if (callback)
|
||||||
|
callback(false, "Invalid config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commands = [];
|
||||||
|
|
||||||
|
if (config.position !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" position ${config.position.x} ${config.position.y}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.mode !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" mode ${config.mode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.vrr !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" vrr ${config.vrr ? "on" : "off"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.scale !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" scale ${config.scale}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.transform !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" transform "${config.transform}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commands.length === 0) {
|
||||||
|
if (callback)
|
||||||
|
callback(true, "No changes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullCommand = commands.join(" && ");
|
||||||
|
Proc.runCommand("niri-output-config", ["sh", "-c", fullCommand], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.warn("NiriService: Failed to apply output config:", output);
|
||||||
|
if (callback)
|
||||||
|
callback(false, output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info("NiriService: Applied output config for", outputName);
|
||||||
|
fetchOutputs();
|
||||||
|
if (callback)
|
||||||
|
callback(true, "Success");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputIdentifier(output, outputName) {
|
||||||
|
if (SettingsData.displayNameMode === "model" && output.make && output.model) {
|
||||||
|
const serial = output.serial || "Unknown";
|
||||||
|
return output.make + " " + output.model + " " + serial;
|
||||||
|
}
|
||||||
|
return outputName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateOutputsConfig(outputsData) {
|
||||||
|
const data = outputsData || outputs;
|
||||||
|
if (!data || Object.keys(data).length === 0)
|
||||||
|
return;
|
||||||
|
let kdlContent = `// Auto-generated by DMS - do not edit manually\n\n`;
|
||||||
|
|
||||||
|
for (const outputName in data) {
|
||||||
|
const output = data[outputName];
|
||||||
|
const identifier = getOutputIdentifier(output, outputName);
|
||||||
|
kdlContent += `output "${identifier}" {\n`;
|
||||||
|
|
||||||
|
if (output.current_mode !== undefined && output.modes && output.modes[output.current_mode]) {
|
||||||
|
const mode = output.modes[output.current_mode];
|
||||||
|
kdlContent += ` mode "${mode.width}x${mode.height}@${(mode.refresh_rate / 1000).toFixed(3)}"\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.logical) {
|
||||||
|
if (output.logical.scale && output.logical.scale !== 1.0) {
|
||||||
|
kdlContent += ` scale ${output.logical.scale}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.logical.transform && output.logical.transform !== "Normal") {
|
||||||
|
const transformMap = {
|
||||||
|
"Normal": "normal",
|
||||||
|
"90": "90",
|
||||||
|
"180": "180",
|
||||||
|
"270": "270",
|
||||||
|
"Flipped": "flipped",
|
||||||
|
"Flipped90": "flipped-90",
|
||||||
|
"Flipped180": "flipped-180",
|
||||||
|
"Flipped270": "flipped-270"
|
||||||
|
};
|
||||||
|
kdlContent += ` transform "${transformMap[output.logical.transform] || "normal"}"\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.logical.x !== undefined && output.logical.y !== undefined) {
|
||||||
|
kdlContent += ` position x=${output.logical.x} y=${output.logical.y}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.vrr_enabled) {
|
||||||
|
kdlContent += ` variable-refresh-rate\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
kdlContent += `}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
|
||||||
|
const niriDmsDir = configDir + "/niri/dms";
|
||||||
|
const outputsPath = niriDmsDir + "/outputs.kdl";
|
||||||
|
|
||||||
|
Proc.runCommand("niri-write-outputs", ["sh", "-c", `mkdir -p "${niriDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${kdlContent}EOF`], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.warn("NiriService: Failed to write outputs config:", output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info("NiriService: Generated outputs config at", outputsPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function screenshot(): string {
|
function screenshot(): string {
|
||||||
if (!CompositorService.isNiri) {
|
if (!CompositorService.isNiri) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Quickshell
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -20,6 +21,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
readonly property bool shouldPauseCycling: anyFullscreen || SessionService.locked
|
||||||
property string cachedCyclingTime: SessionData.wallpaperCyclingTime
|
property string cachedCyclingTime: SessionData.wallpaperCyclingTime
|
||||||
property int cachedCyclingInterval: SessionData.wallpaperCyclingInterval
|
property int cachedCyclingInterval: SessionData.wallpaperCyclingInterval
|
||||||
property string lastTimeCheck: ""
|
property string lastTimeCheck: ""
|
||||||
@@ -34,7 +36,7 @@ Singleton {
|
|||||||
running: false
|
running: false
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (typeof WallpaperCyclingService !== "undefined" && targetScreen !== "" && !WallpaperCyclingService.anyFullscreen) {
|
if (typeof WallpaperCyclingService !== "undefined" && targetScreen !== "" && !WallpaperCyclingService.shouldPauseCycling) {
|
||||||
WallpaperCyclingService.cycleNextForMonitor(targetScreen);
|
WallpaperCyclingService.cycleNextForMonitor(targetScreen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +115,16 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionService
|
||||||
|
|
||||||
|
function onSessionUnlocked() {
|
||||||
|
if (SessionData.wallpaperCyclingEnabled || SessionData.perMonitorWallpaper) {
|
||||||
|
updateCyclingState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateCyclingState() {
|
function updateCyclingState() {
|
||||||
if (SessionData.perMonitorWallpaper) {
|
if (SessionData.perMonitorWallpaper) {
|
||||||
stopCycling();
|
stopCycling();
|
||||||
@@ -151,13 +163,18 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startCycling() {
|
function startCycling() {
|
||||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
switch (SessionData.wallpaperCyclingMode) {
|
||||||
|
case "interval":
|
||||||
|
lastTimeCheck = "";
|
||||||
intervalTimer.interval = cachedCyclingInterval * 1000;
|
intervalTimer.interval = cachedCyclingInterval * 1000;
|
||||||
intervalTimer.start();
|
intervalTimer.start();
|
||||||
cyclingActive = true;
|
cyclingActive = true;
|
||||||
} else if (SessionData.wallpaperCyclingMode === "time") {
|
break;
|
||||||
|
case "time":
|
||||||
|
intervalTimer.stop();
|
||||||
cyclingActive = true;
|
cyclingActive = true;
|
||||||
checkTimeBasedCycling();
|
checkTimeBasedCycling();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +184,13 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startMonitorCycling(screenName, settings) {
|
function startMonitorCycling(screenName, settings) {
|
||||||
if (settings.mode === "interval") {
|
switch (settings.mode) {
|
||||||
|
case "interval":
|
||||||
|
{
|
||||||
|
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||||
|
delete newChecks[screenName];
|
||||||
|
monitorLastTimeChecks = newChecks;
|
||||||
|
|
||||||
var timer = monitorTimers[screenName];
|
var timer = monitorTimers[screenName];
|
||||||
if (!timer && monitorTimerComponent && monitorTimerComponent.status === Component.Ready) {
|
if (!timer && monitorTimerComponent && monitorTimerComponent.status === Component.Ready) {
|
||||||
var newTimers = Object.assign({}, monitorTimers);
|
var newTimers = Object.assign({}, monitorTimers);
|
||||||
@@ -180,10 +203,24 @@ Singleton {
|
|||||||
timer.interval = settings.interval * 1000;
|
timer.interval = settings.interval * 1000;
|
||||||
timer.start();
|
timer.start();
|
||||||
}
|
}
|
||||||
} else if (settings.mode === "time") {
|
break;
|
||||||
|
}
|
||||||
|
case "time":
|
||||||
|
{
|
||||||
|
var existingTimer = monitorTimers[screenName];
|
||||||
|
if (existingTimer) {
|
||||||
|
existingTimer.stop();
|
||||||
|
existingTimer.destroy();
|
||||||
|
var newTimers = Object.assign({}, monitorTimers);
|
||||||
|
delete newTimers[screenName];
|
||||||
|
monitorTimers = newTimers;
|
||||||
|
}
|
||||||
|
|
||||||
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||||
newChecks[screenName] = "";
|
newChecks[screenName] = "";
|
||||||
monitorLastTimeChecks = newChecks;
|
monitorLastTimeChecks = newChecks;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +356,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkTimeBasedCycling() {
|
function checkTimeBasedCycling() {
|
||||||
if (anyFullscreen)
|
if (shouldPauseCycling)
|
||||||
return;
|
return;
|
||||||
const currentTime = Qt.formatTime(systemClock.date, "hh:mm");
|
const currentTime = Qt.formatTime(systemClock.date, "hh:mm");
|
||||||
|
|
||||||
@@ -367,7 +404,7 @@ Singleton {
|
|||||||
running: false
|
running: false
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (anyFullscreen)
|
if (shouldPauseCycling)
|
||||||
return;
|
return;
|
||||||
cycleToNextWallpaper();
|
cycleToNextWallpaper();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
layer-rule {
|
layer-rule {
|
||||||
match namespace="dms:blurwallpaper"
|
match namespace="dms:blurwallpaper"
|
||||||
place-within-backdrop true
|
place-within-backdrop true
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ PanelWindow {
|
|||||||
readonly property real dpr: CompositorService.getScreenScale(screen)
|
readonly property real dpr: CompositorService.getScreenScale(screen)
|
||||||
readonly property real screenWidth: screen.width
|
readonly property real screenWidth: screen.width
|
||||||
readonly property real screenHeight: screen.height
|
readonly property real screenHeight: screen.height
|
||||||
readonly property real shadowBuffer: 5
|
readonly property real shadowBuffer: 15
|
||||||
readonly property real alignedWidth: Theme.px(osdWidth, dpr)
|
readonly property real alignedWidth: Theme.px(osdWidth, dpr)
|
||||||
readonly property real alignedHeight: Theme.px(osdHeight, dpr)
|
readonly property real alignedHeight: Theme.px(osdHeight, dpr)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ StyledRect {
|
|||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if (activeFocus) {
|
if (activeFocus) {
|
||||||
textInput.forceActiveFocus()
|
textInput.forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,26 +52,26 @@ StyledRect {
|
|||||||
signal focusStateChanged(bool hasFocus)
|
signal focusStateChanged(bool hasFocus)
|
||||||
|
|
||||||
function getActiveFocus() {
|
function getActiveFocus() {
|
||||||
return textInput.activeFocus
|
return textInput.activeFocus;
|
||||||
}
|
}
|
||||||
function setFocus(value) {
|
function setFocus(value) {
|
||||||
textInput.focus = value
|
textInput.focus = value;
|
||||||
}
|
}
|
||||||
function forceActiveFocus() {
|
function forceActiveFocus() {
|
||||||
textInput.forceActiveFocus()
|
textInput.forceActiveFocus();
|
||||||
}
|
}
|
||||||
function selectAll() {
|
function selectAll() {
|
||||||
textInput.selectAll()
|
textInput.selectAll();
|
||||||
}
|
}
|
||||||
function clear() {
|
function clear() {
|
||||||
textInput.clear()
|
textInput.clear();
|
||||||
}
|
}
|
||||||
function insertText(str) {
|
function insertText(str) {
|
||||||
textInput.insert(textInput.cursorPosition, str)
|
textInput.insert(textInput.cursorPosition, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
width: 200
|
width: 200
|
||||||
height: 48
|
height: Math.round(Theme.fontSizeMedium * 3.4)
|
||||||
radius: cornerRadius
|
radius: cornerRadius
|
||||||
color: backgroundColor
|
color: backgroundColor
|
||||||
border.color: textInput.activeFocus ? focusedBorderColor : normalBorderColor
|
border.color: textInput.activeFocus ? focusedBorderColor : normalBorderColor
|
||||||
@@ -113,25 +112,25 @@ StyledRect {
|
|||||||
Keys.forwardTo: root.keyForwardTargets
|
Keys.forwardTo: root.keyForwardTargets
|
||||||
Keys.onLeftPressed: event => {
|
Keys.onLeftPressed: event => {
|
||||||
if (root.ignoreLeftRightKeys) {
|
if (root.ignoreLeftRightKeys) {
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else {
|
} else {
|
||||||
// Allow normal TextInput cursor movement
|
// Allow normal TextInput cursor movement
|
||||||
event.accepted = false
|
event.accepted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onRightPressed: event => {
|
Keys.onRightPressed: event => {
|
||||||
if (root.ignoreLeftRightKeys) {
|
if (root.ignoreLeftRightKeys) {
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
} else {
|
} else {
|
||||||
event.accepted = false
|
event.accepted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
||||||
event.accepted = false
|
event.accepted = false;
|
||||||
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||||
if (root.keyForwardTargets[i]) {
|
if (root.keyForwardTargets[i]) {
|
||||||
root.keyForwardTargets[i].Keys.pressed(event)
|
root.keyForwardTargets[i].Keys.pressed(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +170,7 @@ StyledRect {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
textInput.text = ""
|
textInput.text = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ Item {
|
|||||||
readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : []
|
readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : []
|
||||||
readonly property bool hasConflict: _conflicts.length > 0
|
readonly property bool hasConflict: _conflicts.length > 0
|
||||||
|
|
||||||
|
readonly property real _inputHeight: Math.round(Theme.fontSizeMedium * 3)
|
||||||
|
readonly property real _chipHeight: Math.round(Theme.fontSizeSmall * 2.3)
|
||||||
|
readonly property real _buttonHeight: Math.round(Theme.fontSizeMedium * 2.3)
|
||||||
|
readonly property real _keysColumnWidth: Math.round(Theme.fontSizeSmall * 12)
|
||||||
|
readonly property real _labelWidth: Math.round(Theme.fontSizeSmall * 5)
|
||||||
|
|
||||||
signal toggleExpand
|
signal toggleExpand
|
||||||
signal saveBind(string originalKey, var newData)
|
signal saveBind(string originalKey, var newData)
|
||||||
signal removeBind(string key)
|
signal removeBind(string key)
|
||||||
@@ -223,7 +229,7 @@ Item {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: collapsedRect
|
id: collapsedRect
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: Math.max(52, keysColumn.implicitHeight + Theme.spacingM * 2)
|
height: Math.max(root._inputHeight + Theme.spacingM, keysColumn.implicitHeight + Theme.spacingM * 2)
|
||||||
radius: root.isExpanded ? 0 : Theme.cornerRadius
|
radius: root.isExpanded ? 0 : Theme.cornerRadius
|
||||||
topLeftRadius: Theme.cornerRadius
|
topLeftRadius: Theme.cornerRadius
|
||||||
topRightRadius: Theme.cornerRadius
|
topRightRadius: Theme.cornerRadius
|
||||||
@@ -240,7 +246,7 @@ Item {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: keysColumn
|
id: keysColumn
|
||||||
Layout.preferredWidth: 140
|
Layout.preferredWidth: root._keysColumnWidth
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
@@ -253,9 +259,9 @@ Item {
|
|||||||
|
|
||||||
property bool isSelected: root.isExpanded && root.editingKeyIndex === index && !root.addingNewKey
|
property bool isSelected: root.isExpanded && root.editingKeyIndex === index && !root.addingNewKey
|
||||||
|
|
||||||
width: 140
|
width: root._keysColumnWidth
|
||||||
height: 28
|
height: root._chipHeight
|
||||||
radius: 6
|
radius: root._chipHeight / 4
|
||||||
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -332,7 +338,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "warning"
|
name: "warning"
|
||||||
size: 14
|
size: Theme.iconSizeSmall
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
visible: root.hasConfigConflict
|
visible: root.hasConfigConflict
|
||||||
}
|
}
|
||||||
@@ -352,7 +358,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: root.isExpanded ? "expand_less" : "expand_more"
|
name: root.isExpanded ? "expand_less" : "expand_more"
|
||||||
size: 20
|
size: Theme.iconSize - 4
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
@@ -360,7 +366,7 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: 140 + Theme.spacingM * 2
|
anchors.leftMargin: root._keysColumnWidth + Theme.spacingM * 2
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: root.toggleExpand()
|
onClicked: root.toggleExpand()
|
||||||
}
|
}
|
||||||
@@ -420,7 +426,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "warning"
|
name: "warning"
|
||||||
size: 16
|
size: Theme.iconSizeSmall
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +467,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
@@ -478,8 +484,8 @@ Item {
|
|||||||
property bool isSelected: root.editingKeyIndex === index && !root.addingNewKey
|
property bool isSelected: root.editingKeyIndex === index && !root.addingNewKey
|
||||||
|
|
||||||
width: editKeyChipText.implicitWidth + Theme.spacingM
|
width: editKeyChipText.implicitWidth + Theme.spacingM
|
||||||
height: 28
|
height: root._chipHeight
|
||||||
radius: 6
|
radius: root._chipHeight / 4
|
||||||
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
color: isSelected ? Theme.primary : Theme.surfaceVariant
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -509,9 +515,9 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 28
|
width: root._chipHeight
|
||||||
height: 28
|
height: root._chipHeight
|
||||||
radius: 6
|
radius: root._chipHeight / 4
|
||||||
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
||||||
visible: !root.isNew
|
visible: !root.isNew
|
||||||
|
|
||||||
@@ -523,7 +529,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "add"
|
name: "add"
|
||||||
size: 16
|
size: Theme.iconSizeSmall
|
||||||
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -548,13 +554,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
id: captureScope
|
id: captureScope
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
focus: root.recording
|
focus: root.recording
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -596,12 +602,12 @@ Item {
|
|||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: recordBtn
|
id: recordBtn
|
||||||
width: 28
|
width: root._chipHeight
|
||||||
height: 28
|
height: root._chipHeight
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
circular: false
|
circular: false
|
||||||
iconName: root.recording ? "close" : "radio_button_checked"
|
iconName: root.recording ? "close" : "radio_button_checked"
|
||||||
iconSize: 16
|
iconSize: Theme.iconSizeSmall
|
||||||
iconColor: root.recording ? Theme.error : Theme.primary
|
iconColor: root.recording ? Theme.error : Theme.primary
|
||||||
onClicked: root.recording ? root.stopRecording() : root.startRecording()
|
onClicked: root.recording ? root.stopRecording() : root.startRecording()
|
||||||
}
|
}
|
||||||
@@ -703,8 +709,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: 40
|
Layout.preferredWidth: root._inputHeight
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
color: root.addingNewKey ? Theme.primary : Theme.surfaceVariant
|
||||||
visible: root.keys.length === 1 && !root.isNew
|
visible: root.keys.length === 1 && !root.isNew
|
||||||
@@ -717,7 +723,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "add"
|
name: "add"
|
||||||
size: 18
|
size: Theme.iconSizeSmall + 2
|
||||||
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
color: root.addingNewKey ? Theme.primaryText : Theme.surfaceVariantText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -736,11 +742,11 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: root.hasConflict
|
visible: root.hasConflict
|
||||||
Layout.leftMargin: 60 + Theme.spacingM
|
Layout.leftMargin: root._labelWidth + Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "warning"
|
name: "warning"
|
||||||
size: 16
|
size: Theme.iconSizeSmall
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,7 +768,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -785,7 +791,7 @@ Item {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 36
|
Layout.preferredHeight: root._buttonHeight
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: root._actionType === modelData.id ? Theme.surfaceContainerHighest : Theme.surfaceContainer
|
color: root._actionType === modelData.id ? Theme.surfaceContainerHighest : Theme.surfaceContainer
|
||||||
border.color: root._actionType === modelData.id ? Theme.outline : (typeArea.containsMouse ? Theme.outlineVariant : "transparent")
|
border.color: root._actionType === modelData.id ? Theme.outline : (typeArea.containsMouse ? Theme.outlineVariant : "transparent")
|
||||||
@@ -797,7 +803,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: typeDelegate.modelData.icon
|
name: typeDelegate.modelData.icon
|
||||||
size: 16
|
size: Theme.iconSizeSmall
|
||||||
color: root._actionType === typeDelegate.modelData.id ? Theme.surfaceText : Theme.surfaceVariantText
|
color: root._actionType === typeDelegate.modelData.id ? Theme.surfaceText : Theme.surfaceVariantText
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,7 +875,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
@@ -913,14 +919,14 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
visible: dmsArgsRow.hasAmountArg
|
visible: dmsArgsRow.hasAmountArg
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: dmsAmountField
|
id: dmsAmountField
|
||||||
Layout.preferredWidth: 80
|
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 5.5)
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
placeholderText: "5"
|
placeholderText: "5"
|
||||||
visible: dmsArgsRow.hasAmountArg
|
visible: dmsArgsRow.hasAmountArg
|
||||||
|
|
||||||
@@ -961,14 +967,14 @@ Item {
|
|||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.leftMargin: dmsArgsRow.hasAmountArg ? Theme.spacingM : 0
|
Layout.leftMargin: dmsArgsRow.hasAmountArg ? Theme.spacingM : 0
|
||||||
Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : 60
|
Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : root._labelWidth
|
||||||
visible: dmsArgsRow.hasDeviceArg
|
visible: dmsArgsRow.hasDeviceArg
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: dmsDeviceField
|
id: dmsDeviceField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
placeholderText: I18n.tr("leave empty for default")
|
placeholderText: I18n.tr("leave empty for default")
|
||||||
visible: dmsArgsRow.hasDeviceArg
|
visible: dmsArgsRow.hasDeviceArg
|
||||||
|
|
||||||
@@ -1006,7 +1012,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
visible: dmsArgsRow.hasTabArg
|
visible: dmsArgsRow.hasTabArg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1064,12 +1070,12 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
id: compositorCatDropdown
|
id: compositorCatDropdown
|
||||||
Layout.preferredWidth: 120
|
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 8.5)
|
||||||
compactMode: true
|
compactMode: true
|
||||||
currentValue: {
|
currentValue: {
|
||||||
const base = root.editAction.split(" ")[0];
|
const base = root.editAction.split(" ")[0];
|
||||||
@@ -1108,8 +1114,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: 40
|
Layout.preferredWidth: root._inputHeight
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariant
|
color: Theme.surfaceVariant
|
||||||
|
|
||||||
@@ -1121,7 +1127,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "edit"
|
name: "edit"
|
||||||
size: 18
|
size: Theme.iconSizeSmall + 2
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -1150,7 +1156,7 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -1160,7 +1166,7 @@ Item {
|
|||||||
DankTextField {
|
DankTextField {
|
||||||
id: argValueField
|
id: argValueField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
visible: {
|
visible: {
|
||||||
const cfg = optionsRow.argConfig;
|
const cfg = optionsRow.argConfig;
|
||||||
if (!cfg?.config?.args)
|
if (!cfg?.config?.args)
|
||||||
@@ -1308,13 +1314,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: customCompositorField
|
id: customCompositorField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10")
|
placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10")
|
||||||
text: root._actionType === "compositor" ? root.editAction : ""
|
text: root._actionType === "compositor" ? root.editAction : ""
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
@@ -1327,8 +1333,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: 40
|
Layout.preferredWidth: root._inputHeight
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariant
|
color: Theme.surfaceVariant
|
||||||
|
|
||||||
@@ -1340,7 +1346,7 @@ Item {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "list"
|
name: "list"
|
||||||
size: 18
|
size: Theme.iconSizeSmall + 2
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -1371,13 +1377,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: spawnTextField
|
id: spawnTextField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
placeholderText: I18n.tr("e.g., firefox, kitty --title foo")
|
placeholderText: I18n.tr("e.g., firefox, kitty --title foo")
|
||||||
readonly property var _parsed: root._actionType === "spawn" ? Actions.parseSpawnCommand(root.editAction) : null
|
readonly property var _parsed: root._actionType === "spawn" ? Actions.parseSpawnCommand(root.editAction) : null
|
||||||
text: _parsed ? (_parsed.command + " " + _parsed.args.join(" ")).trim() : ""
|
text: _parsed ? (_parsed.command + " " + _parsed.args.join(" ")).trim() : ""
|
||||||
@@ -1403,13 +1409,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: shellTextField
|
id: shellTextField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
placeholderText: I18n.tr("e.g., notify-send 'Hello' && sleep 1")
|
placeholderText: I18n.tr("e.g., notify-send 'Hello' && sleep 1")
|
||||||
text: root._actionType === "shell" ? Actions.parseShellCommand(root.editAction) : ""
|
text: root._actionType === "shell" ? Actions.parseShellCommand(root.editAction) : ""
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
@@ -1431,13 +1437,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: titleField
|
id: titleField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
placeholderText: I18n.tr("Hotkey overlay title (optional)")
|
placeholderText: I18n.tr("Hotkey overlay title (optional)")
|
||||||
text: root.editDesc
|
text: root.editDesc
|
||||||
onTextChanged: root.updateEdit({
|
onTextChanged: root.updateEdit({
|
||||||
@@ -1455,13 +1461,13 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: root._labelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: cooldownField
|
id: cooldownField
|
||||||
Layout.preferredWidth: 100
|
Layout.preferredWidth: Math.round(Theme.fontSizeMedium * 7)
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: root._inputHeight
|
||||||
placeholderText: "0"
|
placeholderText: "0"
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -1508,8 +1514,8 @@ Item {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
Layout.preferredWidth: 32
|
Layout.preferredWidth: root._buttonHeight
|
||||||
Layout.preferredHeight: 32
|
Layout.preferredHeight: root._buttonHeight
|
||||||
circular: false
|
circular: false
|
||||||
iconName: "delete"
|
iconName: "delete"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
@@ -1531,7 +1537,7 @@ Item {
|
|||||||
|
|
||||||
DankButton {
|
DankButton {
|
||||||
text: I18n.tr("Cancel")
|
text: I18n.tr("Cancel")
|
||||||
buttonHeight: 32
|
buttonHeight: root._buttonHeight
|
||||||
backgroundColor: Theme.surfaceContainer
|
backgroundColor: Theme.surfaceContainer
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
visible: root.hasChanges || root.isNew
|
visible: root.hasChanges || root.isNew
|
||||||
@@ -1547,7 +1553,7 @@ Item {
|
|||||||
|
|
||||||
DankButton {
|
DankButton {
|
||||||
text: root.isNew ? I18n.tr("Add") : I18n.tr("Save")
|
text: root.isNew ? I18n.tr("Add") : I18n.tr("Save")
|
||||||
buttonHeight: 32
|
buttonHeight: root._buttonHeight
|
||||||
enabled: root.canSave()
|
enabled: root.canSave()
|
||||||
visible: root.hasChanges || root.isNew
|
visible: root.hasChanges || root.isNew
|
||||||
onClicked: root.doSave()
|
onClicked: root.doSave()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Modals.FileBrowser
|
import qs.Modals.FileBrowser
|
||||||
@@ -13,7 +14,7 @@ Rectangle {
|
|||||||
property string expandedUuid: ""
|
property string expandedUuid: ""
|
||||||
property int listHeight: 180
|
property int listHeight: 180
|
||||||
|
|
||||||
implicitHeight: contentColumn.implicitHeight + Theme.spacingM * 2
|
implicitHeight: 32 + 1 + listHeight + Theme.spacingS * 4 + Theme.spacingM * 2
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
|
||||||
@@ -153,25 +154,14 @@ Rectangle {
|
|||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||||
}
|
}
|
||||||
|
|
||||||
DankFlickable {
|
|
||||||
width: parent.width
|
|
||||||
height: root.listHeight
|
|
||||||
contentHeight: listCol.height
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: listCol
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: DMSNetworkService.profiles.length === 0 ? 100 : 0
|
height: root.listHeight
|
||||||
visible: height > 0
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
visible: DMSNetworkService.profiles.length === 0
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "vpn_key_off"
|
name: "vpn_key_off"
|
||||||
@@ -194,292 +184,46 @@ Rectangle {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
DankListView {
|
||||||
model: DMSNetworkService.profiles
|
id: vpnListView
|
||||||
|
anchors.fill: parent
|
||||||
delegate: Rectangle {
|
visible: DMSNetworkService.profiles.length > 0
|
||||||
id: profileRow
|
spacing: 4
|
||||||
required property var modelData
|
cacheBuffer: 200
|
||||||
required property int index
|
|
||||||
|
|
||||||
readonly property bool isActive: DMSNetworkService.isActiveUuid(modelData.uuid)
|
|
||||||
readonly property bool isExpanded: root.expandedUuid === modelData.uuid
|
|
||||||
readonly property bool isHovered: rowArea.containsMouse || expandBtn.containsMouse || deleteBtn.containsMouse
|
|
||||||
readonly property var configData: isExpanded ? VPNService.editConfig : null
|
|
||||||
|
|
||||||
width: listCol.width
|
|
||||||
height: isExpanded ? 46 + expandedContent.height : 46
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: isHovered ? Theme.primaryHoverLight : (isActive ? Theme.primaryPressed : Theme.surfaceLight)
|
|
||||||
border.width: isActive ? 2 : 1
|
|
||||||
border.color: isActive ? Theme.primary : Theme.outlineLight
|
|
||||||
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
|
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Behavior on height {
|
model: ScriptModel {
|
||||||
NumberAnimation {
|
values: DMSNetworkService.profiles
|
||||||
duration: 150
|
objectProp: "uuid"
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
delegate: VpnProfileDelegate {
|
||||||
id: rowArea
|
required property var modelData
|
||||||
anchors.fill: parent
|
width: vpnListView.width
|
||||||
hoverEnabled: true
|
profile: modelData
|
||||||
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
|
isExpanded: root.expandedUuid === modelData.uuid
|
||||||
enabled: !DMSNetworkService.isBusy
|
onToggleExpand: {
|
||||||
onClicked: DMSNetworkService.toggle(modelData.uuid)
|
if (root.expandedUuid === modelData.uuid) {
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: 46 - Theme.spacingS * 2
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: isActive ? "vpn_lock" : "vpn_key_off"
|
|
||||||
size: 20
|
|
||||||
color: isActive ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 1
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: parent.width - 20 - 28 - 28 - Theme.spacingS * 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.name
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: isActive ? Theme.primary : Theme.surfaceText
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: VPNService.getVpnTypeFromProfile(modelData)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: Theme.spacingXS
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: expandBtnRect
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: expandBtn.containsMouse ? Theme.surfacePressed : "transparent"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: isExpanded ? "expand_less" : "expand_more"
|
|
||||||
size: 18
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: expandBtn
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (isExpanded) {
|
|
||||||
root.expandedUuid = "";
|
root.expandedUuid = "";
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
root.expandedUuid = modelData.uuid;
|
root.expandedUuid = modelData.uuid;
|
||||||
VPNService.getConfig(modelData.uuid);
|
VPNService.getConfig(modelData.uuid);
|
||||||
}
|
}
|
||||||
}
|
onDeleteRequested: {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: deleteBtnRect
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: deleteBtn.containsMouse ? Theme.errorHover : "transparent"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "delete"
|
|
||||||
size: 18
|
|
||||||
color: deleteBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: deleteBtn
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
deleteConfirm.showWithOptions({
|
deleteConfirm.showWithOptions({
|
||||||
title: I18n.tr("Delete VPN"),
|
"title": I18n.tr("Delete VPN"),
|
||||||
message: I18n.tr("Delete \"") + modelData.name + "\"?",
|
"message": I18n.tr("Delete \"") + modelData.name + "\"?",
|
||||||
confirmText: I18n.tr("Delete"),
|
"confirmText": I18n.tr("Delete"),
|
||||||
confirmColor: Theme.error,
|
"confirmColor": Theme.error,
|
||||||
onConfirm: () => VPNService.deleteVpn(modelData.uuid)
|
"onConfirm": () => VPNService.deleteVpn(modelData.uuid)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
|
||||||
id: expandedContent
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
visible: isExpanded
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Theme.outlineLight
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: VPNService.configLoading ? 40 : 0
|
|
||||||
visible: VPNService.configLoading
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "sync"
|
|
||||||
size: 16
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Loading...")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
visible: !VPNService.configLoading && configData
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: {
|
|
||||||
if (!configData)
|
|
||||||
return [];
|
|
||||||
const fields = [];
|
|
||||||
const data = configData.data || {};
|
|
||||||
|
|
||||||
if (data.remote)
|
|
||||||
fields.push({
|
|
||||||
label: I18n.tr("Server"),
|
|
||||||
value: data.remote
|
|
||||||
});
|
|
||||||
if (configData.username || data.username)
|
|
||||||
fields.push({
|
|
||||||
label: I18n.tr("Username"),
|
|
||||||
value: configData.username || data.username
|
|
||||||
});
|
|
||||||
if (data.cipher)
|
|
||||||
fields.push({
|
|
||||||
label: I18n.tr("Cipher"),
|
|
||||||
value: data.cipher
|
|
||||||
});
|
|
||||||
if (data.auth)
|
|
||||||
fields.push({
|
|
||||||
label: I18n.tr("Auth"),
|
|
||||||
value: data.auth
|
|
||||||
});
|
|
||||||
if (data["proto-tcp"] === "yes" || data["proto-tcp"] === "no")
|
|
||||||
fields.push({
|
|
||||||
label: I18n.tr("Protocol"),
|
|
||||||
value: data["proto-tcp"] === "yes" ? "TCP" : "UDP"
|
|
||||||
});
|
|
||||||
if (data["tunnel-mtu"])
|
|
||||||
fields.push({
|
|
||||||
label: I18n.tr("MTU"),
|
|
||||||
value: data["tunnel-mtu"]
|
|
||||||
});
|
|
||||||
if (data["connection-type"])
|
|
||||||
fields.push({
|
|
||||||
label: I18n.tr("Auth Type"),
|
|
||||||
value: data["connection-type"]
|
|
||||||
});
|
|
||||||
fields.push({
|
|
||||||
label: I18n.tr("Autoconnect"),
|
|
||||||
value: configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
|
|
||||||
});
|
|
||||||
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
required property var modelData
|
|
||||||
required property int index
|
|
||||||
|
|
||||||
width: fieldContent.width + Theme.spacingM * 2
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius - 2
|
|
||||||
color: Theme.surfaceContainerHigh
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.outlineLight
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: fieldContent
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.label + ":"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.value
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 1
|
|
||||||
height: Theme.spacingXS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 1
|
width: 1
|
||||||
height: Theme.spacingS
|
height: Theme.spacingS
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user