mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-11 14:59:38 -04:00
Compare commits
58 Commits
3d35af2a87
...
v1.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03a8e1e0d5 | ||
|
|
4d4d3c20a1 | ||
|
|
cef16d6bc9 | ||
|
|
aafaad1791 | ||
|
|
7906fdc2b0 | ||
|
|
397650ca52 | ||
|
|
826207006a | ||
|
|
58c2fcd31c | ||
|
|
b2a2b425ec | ||
|
|
942c9c9609 | ||
|
|
46d6e1cff3 | ||
|
|
a4137c57c1 | ||
|
|
1ad8b627f1 | ||
|
|
58a02ce290 | ||
|
|
8e1ad1a2be | ||
|
|
68cd7ab32c | ||
|
|
f649ce9a8e | ||
|
|
c4df242f07 | ||
|
|
26846c8d55 | ||
|
|
31b44a667c | ||
|
|
4f3b73ee21 | ||
|
|
4cfae91f02 | ||
|
|
8d947a6e95 | ||
|
|
1e84d4252c | ||
|
|
76072e1d4c | ||
|
|
6408dce4a9 | ||
|
|
0b2e1cca38 | ||
|
|
c1bfd8c0b7 | ||
|
|
90ffa5833b | ||
|
|
169c669286 | ||
|
|
f8350deafc | ||
|
|
0286a1b80b | ||
|
|
7c3e6c1f02 | ||
|
|
d2d72db3c9 | ||
|
|
f81f861408 | ||
|
|
af494543f5 | ||
|
|
db4de55338 | ||
|
|
37ecbbbbde | ||
|
|
d6a6d2a438 | ||
|
|
bf1c6eec74 | ||
|
|
0ddae80584 | ||
|
|
5c96c03bfa | ||
|
|
dfe36e47d8 | ||
|
|
63e1b75e57 | ||
|
|
29efdd8598 | ||
|
|
34d03cf11b | ||
|
|
c339389d44 | ||
|
|
af5f6eb656 | ||
|
|
a6d28e2553 | ||
|
|
6213267908 | ||
|
|
d084114149 | ||
|
|
f6d99eca0d | ||
|
|
722eb3289e | ||
|
|
b7f2bdcb2d | ||
|
|
11c20db6e6 | ||
|
|
8a4e3f8bb1 | ||
|
|
bc8fe97c13 | ||
|
|
47262155aa |
67
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
67
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -8,31 +8,31 @@ body:
|
||||
value: |
|
||||
## DankMaterialShell Bug Report
|
||||
Limit your report to one issue per submission unless closely related
|
||||
- type: checkboxes
|
||||
- type: dropdown
|
||||
id: compositor
|
||||
attributes:
|
||||
label: Compositor
|
||||
options:
|
||||
- label: Niri
|
||||
- label: Hyprland
|
||||
- label: MangoWC (dwl)
|
||||
- label: Sway
|
||||
- Niri
|
||||
- Hyprland
|
||||
- MangoWC (dwl)
|
||||
- Sway
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
- type: dropdown
|
||||
id: distribution
|
||||
attributes:
|
||||
label: Distribution
|
||||
options:
|
||||
- label: Arch Linux
|
||||
- label: CachyOS
|
||||
- label: Fedora
|
||||
- label: NixOS
|
||||
- label: Debian
|
||||
- label: Ubuntu
|
||||
- label: Gentoo
|
||||
- label: OpenSUSE
|
||||
- label: Other (specify below)
|
||||
- Arch Linux
|
||||
- CachyOS
|
||||
- Fedora
|
||||
- NixOS
|
||||
- Debian
|
||||
- Ubuntu
|
||||
- Gentoo
|
||||
- OpenSUSE
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
@@ -42,12 +42,45 @@ body:
|
||||
placeholder: e.g., PikaOS, Void Linux, etc.
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: installation_method
|
||||
attributes:
|
||||
label: Select your Installation Method
|
||||
options:
|
||||
- DankInstaller
|
||||
- Distro Packaging
|
||||
- Source
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: original_installation_method_different
|
||||
attributes:
|
||||
label: Was your original Installation method different?
|
||||
options:
|
||||
- "Yes"
|
||||
- No (specify below)
|
||||
default: 0
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: original_installation_method_specify
|
||||
attributes:
|
||||
label: If no, specify
|
||||
placeholder: e.g., Distro Packaging, then Source
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: dms_doctor
|
||||
attributes:
|
||||
label: dms doctor -vC
|
||||
description: Output of `dms doctor -vC` command
|
||||
description: Output of `dms doctor -vC` command — paste between the lines below to keep it collapsed in the issue
|
||||
placeholder: Paste the output of `dms doctor -vC` here
|
||||
value: |
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -69,7 +102,7 @@ body:
|
||||
- type: textarea
|
||||
id: steps_to_reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce & Installation Method
|
||||
label: Steps to Reproduce
|
||||
description: Please provide detailed steps to reproduce the issue
|
||||
placeholder: |
|
||||
1. ...
|
||||
|
||||
21
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
21
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -23,18 +23,25 @@ body:
|
||||
placeholder: Why is this feature important?
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
- type: dropdown
|
||||
id: compositor
|
||||
attributes:
|
||||
label: Compositor(s)
|
||||
description: Is this feature specific to one or more compositors?
|
||||
options:
|
||||
- label: All compositors
|
||||
- label: Niri
|
||||
- label: Hyprland
|
||||
- label: MangoWC (dwl)
|
||||
- label: Sway
|
||||
- label: Other (specify below)
|
||||
- All compositors
|
||||
- Niri
|
||||
- Hyprland
|
||||
- MangoWC (dwl)
|
||||
- Sway
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: compositor_other
|
||||
attributes:
|
||||
label: If Other, please specify
|
||||
placeholder: e.g., Wayfire, Mutter, etc.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
|
||||
75
.github/ISSUE_TEMPLATE/support_request.yml
vendored
75
.github/ISSUE_TEMPLATE/support_request.yml
vendored
@@ -7,32 +7,87 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
## DankMaterialShell Support Request
|
||||
- type: checkboxes
|
||||
- type: dropdown
|
||||
id: compositor
|
||||
attributes:
|
||||
label: Compositor
|
||||
options:
|
||||
- label: Niri
|
||||
- label: Hyprland
|
||||
- label: MangoWC (dwl)
|
||||
- label: Sway
|
||||
- label: Other (specify below)
|
||||
- Niri
|
||||
- Hyprland
|
||||
- MangoWC (dwl)
|
||||
- Sway
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: compositor_other
|
||||
attributes:
|
||||
label: If Other, please specify
|
||||
placeholder: e.g., Wayfire, Mutter, etc.
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
- type: dropdown
|
||||
id: distribution
|
||||
attributes:
|
||||
label: Distribution
|
||||
description: Which Linux distribution are you using? (e.g., Arch, Fedora, Debian, etc.)
|
||||
placeholder: Your Linux distribution
|
||||
options:
|
||||
- Arch Linux
|
||||
- CachyOS
|
||||
- Fedora
|
||||
- NixOS
|
||||
- Debian
|
||||
- Ubuntu
|
||||
- Gentoo
|
||||
- OpenSUSE
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: distribution_other
|
||||
attributes:
|
||||
label: If Other, please specify
|
||||
placeholder: e.g., PikaOS, Void Linux, etc.
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: installation_method
|
||||
attributes:
|
||||
label: Select your Installation Method
|
||||
options:
|
||||
- DankInstaller
|
||||
- Distro Packaging
|
||||
- Source
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: original_installation_method_different
|
||||
attributes:
|
||||
label: Was your original Installation method different?
|
||||
options:
|
||||
- "Yes"
|
||||
- No (specify below)
|
||||
default: 0
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: original_installation_method_specify
|
||||
attributes:
|
||||
label: If no, specify
|
||||
placeholder: e.g., Distro Packaging, then Source
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: dms_doctor
|
||||
attributes:
|
||||
label: dms doctor -vC
|
||||
description: Output of `dms doctor -vC` command
|
||||
description: Output of `dms doctor -vC` command — paste between the lines below to keep it collapsed in the issue
|
||||
placeholder: Paste the output of `dms doctor -vC` here
|
||||
value: |
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
|
||||
383
.github/workflows/backup/run-obs.yml.bak
vendored
383
.github/workflows/backup/run-obs.yml.bak
vendored
@@ -1,383 +0,0 @@
|
||||
name: Update OBS Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: "Package to update (dms, dms-git, or all)"
|
||||
required: false
|
||||
default: "all"
|
||||
force_upload:
|
||||
description: "Force upload without version check"
|
||||
required: false
|
||||
default: "false"
|
||||
type: choice
|
||||
options:
|
||||
- "false"
|
||||
- "true"
|
||||
rebuild_release:
|
||||
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
|
||||
required: false
|
||||
default: ""
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
schedule:
|
||||
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
|
||||
|
||||
jobs:
|
||||
check-updates:
|
||||
name: Check for updates
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
has_updates: ${{ steps.check.outputs.has_updates }}
|
||||
packages: ${{ steps.check.outputs.packages }}
|
||||
version: ${{ steps.check.outputs.version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install OSC
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y osc
|
||||
|
||||
mkdir -p ~/.config/osc
|
||||
cat > ~/.config/osc/oscrc << EOF
|
||||
[general]
|
||||
apiurl = https://api.opensuse.org
|
||||
|
||||
[https://api.opensuse.org]
|
||||
user = ${{ secrets.OBS_USERNAME }}
|
||||
pass = ${{ secrets.OBS_PASSWORD }}
|
||||
EOF
|
||||
chmod 600 ~/.config/osc/oscrc
|
||||
|
||||
- name: Check for updates
|
||||
id: check
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by tag: $VERSION (always update)"
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||
echo "Checking if dms-git source has changed..."
|
||||
|
||||
# Get current commit hash (8 chars to match spec format)
|
||||
CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
|
||||
|
||||
# Check OBS for last uploaded commit
|
||||
OBS_BASE="$HOME/.cache/osc-checkouts"
|
||||
mkdir -p "$OBS_BASE"
|
||||
OBS_PROJECT="home:AvengeMedia:dms-git"
|
||||
|
||||
if [[ -d "$OBS_BASE/$OBS_PROJECT/dms-git" ]]; then
|
||||
cd "$OBS_BASE/$OBS_PROJECT/dms-git"
|
||||
osc up -q 2>/dev/null || true
|
||||
|
||||
# Extract commit hash from spec Version line & format like; 0.6.2+git2264.a679be68
|
||||
if [[ -f "dms-git.spec" ]]; then
|
||||
OBS_COMMIT=$(grep "^Version:" "dms-git.spec" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
|
||||
|
||||
if [[ -n "$OBS_COMMIT" ]]; then
|
||||
if [[ "$CURRENT_COMMIT" == "$OBS_COMMIT" ]]; then
|
||||
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||
echo "📋 Commit $CURRENT_COMMIT already uploaded to OBS, skipping"
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 New commit detected: $CURRENT_COMMIT (OBS has $OBS_COMMIT)"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 Could not extract OBS commit, proceeding with update"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 No spec file in OBS, proceeding with update"
|
||||
fi
|
||||
|
||||
cd "${{ github.workspace }}"
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 First upload to OBS, update needed"
|
||||
fi
|
||||
elif [[ "${{ github.event.inputs.force_upload }}" == "true" ]]; then
|
||||
PKG="${{ github.event.inputs.package }}"
|
||||
if [[ -z "$PKG" || "$PKG" == "all" ]]; then
|
||||
echo "packages=all" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "🚀 Force upload: all packages"
|
||||
else
|
||||
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "🚀 Force upload: $PKG"
|
||||
fi
|
||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
||||
else
|
||||
echo "packages=all" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
update-obs:
|
||||
name: Upload to OBS
|
||||
needs: check-updates
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
if: |
|
||||
github.event.inputs.force_upload == 'true' ||
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
needs.check-updates.outputs.has_updates == 'true'
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App Token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
- name: Check if last commit was automated
|
||||
id: check-loop
|
||||
run: |
|
||||
LAST_COMMIT_MSG=$(git log -1 --pretty=%B | head -1)
|
||||
if [[ "$LAST_COMMIT_MSG" == "ci: Auto-update PPA packages"* ]] || [[ "$LAST_COMMIT_MSG" == "ci: Auto-update OBS packages"* ]]; then
|
||||
echo "⏭️ Last commit was automated ($LAST_COMMIT_MSG), skipping to prevent infinite loop"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "✅ Last commit was not automated, proceeding"
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Determine packages to update
|
||||
if: steps.check-loop.outputs.skip != 'true'
|
||||
id: packages
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by tag: $VERSION"
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by schedule: updating git package"
|
||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
||||
else
|
||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Update dms-git spec version
|
||||
if: steps.check-loop.outputs.skip != 'true' && (contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all')
|
||||
run: |
|
||||
# Get commit info for dms-git versioning
|
||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||
|
||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
|
||||
|
||||
# Update version in spec
|
||||
sed -i "s/^Version:.*/Version: $NEW_VERSION/" distro/opensuse/dms-git.spec
|
||||
|
||||
# Add changelog entry
|
||||
DATE_STR=$(date "+%a %b %d %Y")
|
||||
CHANGELOG_ENTRY="* $DATE_STR Avenge Media <AvengeMedia.US@gmail.com> - ${NEW_VERSION}-1\n- Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||
sed -i "/%changelog/a\\$CHANGELOG_ENTRY" distro/opensuse/dms-git.spec
|
||||
|
||||
- name: Update Debian dms-git changelog version
|
||||
if: steps.check-loop.outputs.skip != 'true' && (contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all')
|
||||
run: |
|
||||
# Get commit info for dms-git versioning
|
||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||
|
||||
# Debian version format: 0.6.2+git2256.9162e314
|
||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
|
||||
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
|
||||
CHANGELOG_FILE="distro/debian/dms-git/debian/changelog"
|
||||
|
||||
# Get current version from changelog
|
||||
CURRENT_VERSION=$(head -1 "$CHANGELOG_FILE" | sed 's/.*(\([^)]*\)).*/\1/')
|
||||
|
||||
echo "Current Debian version: $CURRENT_VERSION"
|
||||
echo "New version: $NEW_VERSION"
|
||||
|
||||
# Only update if version changed
|
||||
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
|
||||
# Create new changelog entry at top
|
||||
TEMP_CHANGELOG=$(mktemp)
|
||||
|
||||
cat > "$TEMP_CHANGELOG" << EOF
|
||||
dms-git ($NEW_VERSION) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE
|
||||
|
||||
EOF
|
||||
|
||||
# Prepend to existing changelog
|
||||
cat "$CHANGELOG_FILE" >> "$TEMP_CHANGELOG"
|
||||
mv "$TEMP_CHANGELOG" "$CHANGELOG_FILE"
|
||||
|
||||
echo "✓ Updated Debian changelog: $CURRENT_VERSION → $NEW_VERSION"
|
||||
else
|
||||
echo "✓ Debian changelog already at version $NEW_VERSION"
|
||||
fi
|
||||
|
||||
- name: Update dms stable version
|
||||
if: steps.check-loop.outputs.skip != 'true' && steps.packages.outputs.version != ''
|
||||
run: |
|
||||
VERSION="${{ steps.packages.outputs.version }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
echo "Updating packaging to version $VERSION_NO_V"
|
||||
|
||||
# Update openSUSE dms spec (stable only)
|
||||
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
||||
|
||||
# Update openSUSE spec changelog
|
||||
DATE_STR=$(date "+%a %b %d %Y")
|
||||
CHANGELOG_ENTRY="* $DATE_STR AvengeMedia <maintainer@avengemedia.com> - ${VERSION_NO_V}-1\\n- Update to stable $VERSION release\\n- Bug fixes and improvements"
|
||||
sed -i "/%changelog/a\\$CHANGELOG_ENTRY\\n" distro/opensuse/dms.spec
|
||||
|
||||
# Update Debian _service files (both tar_scm and download_url formats)
|
||||
for service in distro/debian/*/_service; do
|
||||
if [[ -f "$service" ]]; then
|
||||
# Update tar_scm revision parameter (for dms-git)
|
||||
sed -i "s|<param name=\"revision\">v[0-9.]*</param>|<param name=\"revision\">$VERSION</param>|" "$service"
|
||||
|
||||
# Update download_url paths (for dms stable)
|
||||
sed -i "s|/v[0-9.]\+/|/$VERSION/|g" "$service"
|
||||
sed -i "s|/tags/v[0-9.]\+\.tar\.gz|/tags/$VERSION.tar.gz|g" "$service"
|
||||
fi
|
||||
done
|
||||
|
||||
# Update Debian changelog for dms stable
|
||||
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
TEMP_CHANGELOG=$(mktemp)
|
||||
|
||||
cat > "$TEMP_CHANGELOG" << EOF
|
||||
dms ($VERSION_NO_V) stable; urgency=medium
|
||||
|
||||
* Update to $VERSION stable release
|
||||
* Bug fixes and improvements
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE
|
||||
|
||||
EOF
|
||||
|
||||
cat "distro/debian/dms/debian/changelog" >> "$TEMP_CHANGELOG"
|
||||
mv "$TEMP_CHANGELOG" "distro/debian/dms/debian/changelog"
|
||||
|
||||
echo "✓ Updated Debian changelog to $VERSION_NO_V"
|
||||
fi
|
||||
|
||||
- name: Install Go
|
||||
if: steps.check-loop.outputs.skip != 'true'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
|
||||
- name: Install OSC
|
||||
if: steps.check-loop.outputs.skip != 'true'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y osc
|
||||
|
||||
mkdir -p ~/.config/osc
|
||||
cat > ~/.config/osc/oscrc << EOF
|
||||
[general]
|
||||
apiurl = https://api.opensuse.org
|
||||
|
||||
[https://api.opensuse.org]
|
||||
user = ${{ secrets.OBS_USERNAME }}
|
||||
pass = ${{ secrets.OBS_PASSWORD }}
|
||||
EOF
|
||||
chmod 600 ~/.config/osc/oscrc
|
||||
|
||||
- name: Upload to OBS
|
||||
if: steps.check-loop.outputs.skip != 'true'
|
||||
env:
|
||||
FORCE_UPLOAD: ${{ github.event.inputs.force_upload }}
|
||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
||||
run: |
|
||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||
MESSAGE="Automated update from GitHub Actions"
|
||||
|
||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGES" == "all" ]]; then
|
||||
bash distro/scripts/obs-upload.sh dms "$MESSAGE"
|
||||
bash distro/scripts/obs-upload.sh dms-git "Automated git update"
|
||||
else
|
||||
bash distro/scripts/obs-upload.sh "$PACKAGES" "$MESSAGE"
|
||||
fi
|
||||
|
||||
- name: Get changed packages
|
||||
if: steps.check-loop.outputs.skip != 'true'
|
||||
id: changed-packages
|
||||
run: |
|
||||
# Check if there are any changes to commit
|
||||
if git diff --exit-code distro/debian/ distro/opensuse/ >/dev/null 2>&1; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "📋 No changelog or spec changes to commit"
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
# Get list of changed packages for commit message
|
||||
CHANGED_DEB=$(git diff --name-only distro/debian/ 2>/dev/null | grep 'debian/changelog' | xargs dirname 2>/dev/null | xargs dirname 2>/dev/null | xargs basename 2>/dev/null | tr '\n' ', ' | sed 's/, $//' || echo "")
|
||||
CHANGED_SUSE=$(git diff --name-only distro/opensuse/ 2>/dev/null | grep '\.spec$' | sed 's|distro/opensuse/||' | sed 's/\.spec$//' | tr '\n' ', ' | sed 's/, $//' || echo "")
|
||||
|
||||
PKGS=$(echo "$CHANGED_DEB,$CHANGED_SUSE" | tr ',' '\n' | grep -v '^$' | sort -u | tr '\n' ',' | sed 's/,$//')
|
||||
echo "packages=$PKGS" >> $GITHUB_OUTPUT
|
||||
echo "📋 Changed packages: $PKGS"
|
||||
fi
|
||||
|
||||
- name: Commit packaging changes
|
||||
if: steps.check-loop.outputs.skip != 'true' && steps.changed-packages.outputs.has_changes == 'true'
|
||||
run: |
|
||||
git config user.name "dms-ci[bot]"
|
||||
git config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||
git add distro/debian/*/debian/changelog distro/opensuse/*.spec
|
||||
git commit -m "ci: Auto-update OBS packages [${{ steps.changed-packages.outputs.packages }}]" -m "🤖 Automated by GitHub Actions"
|
||||
git pull --rebase origin master
|
||||
git push
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "### OBS Package Update Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Packages**: ${{ steps.packages.outputs.packages }}" >> $GITHUB_STEP_SUMMARY
|
||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||
echo "- **Version**: ${{ steps.packages.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
if [[ "${{ needs.check-updates.outputs.has_updates }}" == "false" ]]; then
|
||||
echo "- **Status**: Skipped (no changes detected)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "- **Project**: https://build.opensuse.org/project/show/home:AvengeMedia" >> $GITHUB_STEP_SUMMARY
|
||||
298
.github/workflows/backup/run-ppa.yml.bak
vendored
298
.github/workflows/backup/run-ppa.yml.bak
vendored
@@ -1,298 +0,0 @@
|
||||
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
|
||||
@@ -5,11 +5,13 @@ repos:
|
||||
- id: trailing-whitespace
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.10.0.1
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
args: [-e, SC2164, -e, SC2001, -e, SC2012, -e, SC2317]
|
||||
name: shellcheck
|
||||
entry: shellcheck -e SC2164 -e SC2001 -e SC2012 -e SC2317
|
||||
language: system
|
||||
types: [shell]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: go-mod-tidy
|
||||
|
||||
@@ -22,7 +22,7 @@ nix develop
|
||||
|
||||
This will provide:
|
||||
|
||||
- Go 1.24 toolchain (go, gopls, delve, go-tools) and GNU Make
|
||||
- Go 1.25+ toolchain (go, gopls, delve, go-tools) and GNU Make
|
||||
- Quickshell and required QML packages
|
||||
- Properly configured QML2_IMPORT_PATH
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ The on-screen preview displays the selected format. JSON output includes hex, RG
|
||||
|
||||
## Building
|
||||
|
||||
Requires Go 1.24+
|
||||
Requires Go 1.25+
|
||||
|
||||
**Development build:**
|
||||
|
||||
|
||||
@@ -13,16 +13,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ssOutputName string
|
||||
ssIncludeCursor bool
|
||||
ssFormat string
|
||||
ssQuality int
|
||||
ssOutputDir string
|
||||
ssFilename string
|
||||
ssNoClipboard bool
|
||||
ssNoFile bool
|
||||
ssNoNotify bool
|
||||
ssStdout bool
|
||||
ssOutputName string
|
||||
ssCursor string
|
||||
ssFormat string
|
||||
ssQuality int
|
||||
ssOutputDir string
|
||||
ssFilename string
|
||||
ssNoClipboard bool
|
||||
ssNoFile bool
|
||||
ssNoNotify bool
|
||||
ssStdout bool
|
||||
)
|
||||
|
||||
var screenshotCmd = &cobra.Command{
|
||||
@@ -52,7 +52,7 @@ Examples:
|
||||
dms screenshot last # Last region (pre-selected)
|
||||
dms screenshot --no-clipboard # Save file only
|
||||
dms screenshot --no-file # Clipboard only
|
||||
dms screenshot --cursor # Include cursor
|
||||
dms screenshot --cursor=on # Include cursor
|
||||
dms screenshot -f jpg -q 85 # JPEG with quality 85`,
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ var notifyActionCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
screenshotCmd.PersistentFlags().StringVarP(&ssOutputName, "output", "o", "", "Output name for 'output' mode")
|
||||
screenshotCmd.PersistentFlags().BoolVar(&ssIncludeCursor, "cursor", false, "Include cursor in screenshot")
|
||||
screenshotCmd.PersistentFlags().StringVar(&ssCursor, "cursor", "off", "Include cursor in screenshot (on/off)")
|
||||
screenshotCmd.PersistentFlags().StringVarP(&ssFormat, "format", "f", "png", "Output format (png, jpg, ppm)")
|
||||
screenshotCmd.PersistentFlags().IntVarP(&ssQuality, "quality", "q", 90, "JPEG quality (1-100)")
|
||||
screenshotCmd.PersistentFlags().StringVarP(&ssOutputDir, "dir", "d", "", "Output directory")
|
||||
@@ -136,7 +136,9 @@ func getScreenshotConfig(mode screenshot.Mode) screenshot.Config {
|
||||
config := screenshot.DefaultConfig()
|
||||
config.Mode = mode
|
||||
config.OutputName = ssOutputName
|
||||
config.IncludeCursor = ssIncludeCursor
|
||||
if strings.EqualFold(ssCursor, "on") {
|
||||
config.Cursor = screenshot.CursorOn
|
||||
}
|
||||
config.Clipboard = !ssNoClipboard
|
||||
config.SaveFile = !ssNoFile
|
||||
config.Notify = !ssNoNotify
|
||||
|
||||
@@ -100,7 +100,7 @@ windowrule = float on, match:class ^(blueman-manager)$
|
||||
windowrule = float on, match:class ^(org\.gnome\.Nautilus)$
|
||||
windowrule = float on, match:class ^(xdg-desktop-portal)$
|
||||
|
||||
windowrule = noinitialfocus on, match:class ^(steam)$, match:title ^(notificationtoasts)
|
||||
windowrule = no_initial_focus on, match:class ^(steam)$, match:title ^(notificationtoasts)
|
||||
windowrule = pin on, match:class ^(steam)$, match:title ^(notificationtoasts)
|
||||
|
||||
windowrule = float on, match:class ^(firefox)$, match:title ^(Picture-in-Picture)$
|
||||
|
||||
@@ -26,6 +26,9 @@ func init() {
|
||||
Register("cachyos", "#08A283", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewArchDistribution(config, logChan)
|
||||
})
|
||||
Register("catos", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewArchDistribution(config, logChan)
|
||||
})
|
||||
Register("endeavouros", "#7F3FBF", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewArchDistribution(config, logChan)
|
||||
})
|
||||
|
||||
@@ -430,7 +430,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
||||
}
|
||||
|
||||
// Add repository
|
||||
repoLine := fmt.Sprintf("deb [signed-by=%s, arch=%s] %s/ /", keyringPath, runtime.GOARCH, baseURL)
|
||||
repoLine := fmt.Sprintf("deb [signed-by=%s arch=%s] %s/ /", keyringPath, runtime.GOARCH, baseURL)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
|
||||
@@ -341,6 +341,8 @@ func (n *NiriProvider) buildActionFromNode(bindNode *document.Node) string {
|
||||
val := arg.ValueString()
|
||||
if val == "" {
|
||||
parts = append(parts, `""`)
|
||||
} else if strings.ContainsAny(val, " \t") {
|
||||
parts = append(parts, `"`+strings.ReplaceAll(val, `"`, `\"`)+`"`)
|
||||
} else {
|
||||
parts = append(parts, val)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
TemplateKindTerminal
|
||||
TemplateKindGTK
|
||||
TemplateKindVSCode
|
||||
TemplateKindEmacs
|
||||
)
|
||||
|
||||
type TemplateDef struct {
|
||||
@@ -65,7 +66,7 @@ var templateRegistry = []TemplateDef{
|
||||
{ID: "dgop", Commands: []string{"dgop"}, ConfigFile: "dgop.toml"},
|
||||
{ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
|
||||
{ID: "vscode", Kind: TemplateKindVSCode},
|
||||
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml"},
|
||||
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
|
||||
}
|
||||
|
||||
func (c *ColorMode) GTKTheme() string {
|
||||
@@ -78,7 +79,8 @@ func (c *ColorMode) GTKTheme() string {
|
||||
}
|
||||
|
||||
var (
|
||||
matugenVersionOnce sync.Once
|
||||
matugenVersionMu sync.Mutex
|
||||
matugenVersionOK bool
|
||||
matugenSupportsCOE bool
|
||||
matugenIsV4 bool
|
||||
)
|
||||
@@ -334,6 +336,10 @@ output_path = '%s'
|
||||
appendVSCodeConfig(cfgFile, "cursor", filepath.Join(homeDir, ".cursor/extensions"), opts.ShellDir)
|
||||
appendVSCodeConfig(cfgFile, "windsurf", filepath.Join(homeDir, ".windsurf/extensions"), opts.ShellDir)
|
||||
appendVSCodeConfig(cfgFile, "vscode-insiders", filepath.Join(homeDir, ".vscode-insiders/extensions"), opts.ShellDir)
|
||||
case TemplateKindEmacs:
|
||||
if utils.EmacsConfigDir() != "" {
|
||||
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
|
||||
}
|
||||
default:
|
||||
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
|
||||
}
|
||||
@@ -491,6 +497,9 @@ func substituteVars(content, shellDir string) string {
|
||||
result = strings.ReplaceAll(result, "'CONFIG_DIR/", "'"+utils.XDGConfigHome()+"/")
|
||||
result = strings.ReplaceAll(result, "'DATA_DIR/", "'"+utils.XDGDataHome()+"/")
|
||||
result = strings.ReplaceAll(result, "'CACHE_DIR/", "'"+utils.XDGCacheHome()+"/")
|
||||
if emacsDir := utils.EmacsConfigDir(); emacsDir != "" {
|
||||
result = strings.ReplaceAll(result, "'EMACS_DIR/", "'"+emacsDir+"/")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -511,79 +520,160 @@ func extractTOMLSection(content, startMarker, endMarker string) string {
|
||||
return content[startIdx : startIdx+endIdx]
|
||||
}
|
||||
|
||||
func checkMatugenVersion() {
|
||||
matugenVersionOnce.Do(func() {
|
||||
cmd := exec.Command("matugen", "--version")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
versionStr := strings.TrimSpace(string(output))
|
||||
versionStr = strings.TrimPrefix(versionStr, "matugen ")
|
||||
|
||||
parts := strings.Split(versionStr, ".")
|
||||
if len(parts) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
matugenSupportsCOE = major > 3 || (major == 3 && minor >= 1)
|
||||
matugenIsV4 = major >= 4
|
||||
if matugenSupportsCOE {
|
||||
log.Infof("Matugen %s supports --continue-on-error", versionStr)
|
||||
}
|
||||
if matugenIsV4 {
|
||||
log.Infof("Matugen %s: using v4 flags", versionStr)
|
||||
}
|
||||
})
|
||||
type matugenFlags struct {
|
||||
supportsCOE bool
|
||||
isV4 bool
|
||||
}
|
||||
|
||||
func runMatugen(args []string) error {
|
||||
checkMatugenVersion()
|
||||
func detectMatugenVersion() (matugenFlags, error) {
|
||||
matugenVersionMu.Lock()
|
||||
defer matugenVersionMu.Unlock()
|
||||
|
||||
if matugenVersionOK {
|
||||
return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil
|
||||
}
|
||||
|
||||
return detectMatugenVersionLocked()
|
||||
}
|
||||
|
||||
func redetectMatugenVersion(old matugenFlags) (matugenFlags, bool) {
|
||||
matugenVersionMu.Lock()
|
||||
defer matugenVersionMu.Unlock()
|
||||
|
||||
matugenVersionOK = false
|
||||
flags, err := detectMatugenVersionLocked()
|
||||
if err != nil {
|
||||
return old, false
|
||||
}
|
||||
changed := flags.supportsCOE != old.supportsCOE || flags.isV4 != old.isV4
|
||||
return flags, changed
|
||||
}
|
||||
|
||||
func detectMatugenVersionLocked() (matugenFlags, error) {
|
||||
cmd := exec.Command("matugen", "--version")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return matugenFlags{}, fmt.Errorf("failed to get matugen version: %w", err)
|
||||
}
|
||||
|
||||
versionStr := strings.TrimSpace(string(output))
|
||||
versionStr = strings.TrimPrefix(versionStr, "matugen ")
|
||||
|
||||
parts := strings.Split(versionStr, ".")
|
||||
if len(parts) < 2 {
|
||||
return matugenFlags{}, fmt.Errorf("unexpected matugen version format: %q", versionStr)
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return matugenFlags{}, fmt.Errorf("failed to parse matugen major version %q: %w", parts[0], err)
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return matugenFlags{}, fmt.Errorf("failed to parse matugen minor version %q: %w", parts[1], err)
|
||||
}
|
||||
|
||||
matugenSupportsCOE = major > 3 || (major == 3 && minor >= 1)
|
||||
matugenIsV4 = major >= 4
|
||||
matugenVersionOK = true
|
||||
|
||||
if matugenSupportsCOE {
|
||||
args = append([]string{"--continue-on-error"}, args...)
|
||||
log.Infof("Matugen %s supports --continue-on-error", versionStr)
|
||||
}
|
||||
if matugenIsV4 {
|
||||
log.Infof("Matugen %s: using v4 flags", versionStr)
|
||||
}
|
||||
return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil
|
||||
}
|
||||
|
||||
func buildMatugenArgs(baseArgs []string, flags matugenFlags) []string {
|
||||
args := make([]string, 0, len(baseArgs)+4)
|
||||
if flags.supportsCOE {
|
||||
args = append(args, "--continue-on-error")
|
||||
}
|
||||
args = append(args, baseArgs...)
|
||||
if flags.isV4 {
|
||||
args = append(args, "--source-color-index", "0")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func runMatugen(baseArgs []string) error {
|
||||
flags, err := detectMatugenVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := buildMatugenArgs(baseArgs, flags)
|
||||
cmd := exec.Command("matugen", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
runErr := cmd.Run()
|
||||
if runErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Warnf("Matugen failed (v4=%v): %v", flags.isV4, runErr)
|
||||
|
||||
newFlags, changed := redetectMatugenVersion(flags)
|
||||
if !changed {
|
||||
return runErr
|
||||
}
|
||||
|
||||
log.Warnf("Matugen version changed (v4: %v -> %v), retrying", flags.isV4, newFlags.isV4)
|
||||
args = buildMatugenArgs(baseArgs, newFlags)
|
||||
retryCmd := exec.Command("matugen", args...)
|
||||
retryCmd.Stdout = os.Stdout
|
||||
retryCmd.Stderr = os.Stderr
|
||||
return retryCmd.Run()
|
||||
}
|
||||
|
||||
func runMatugenDryRun(opts *Options) (string, error) {
|
||||
checkMatugenVersion()
|
||||
|
||||
var args []string
|
||||
switch opts.Kind {
|
||||
case "hex":
|
||||
args = []string{"color", "hex", opts.Value}
|
||||
default:
|
||||
args = []string{opts.Kind, opts.Value}
|
||||
}
|
||||
args = append(args, "-m", "dark", "-t", opts.MatugenType, "--json", "hex", "--dry-run")
|
||||
if matugenIsV4 {
|
||||
args = append(args, "--source-color-index", "0", "--old-json-output")
|
||||
}
|
||||
|
||||
cmd := exec.Command("matugen", args...)
|
||||
output, err := cmd.Output()
|
||||
flags, err := detectMatugenVersion()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, dryErr := execDryRun(opts, flags)
|
||||
if dryErr == nil {
|
||||
return output, nil
|
||||
}
|
||||
|
||||
log.Warnf("Matugen dry-run failed (v4=%v): %v", flags.isV4, dryErr)
|
||||
|
||||
newFlags, changed := redetectMatugenVersion(flags)
|
||||
if !changed {
|
||||
return "", dryErr
|
||||
}
|
||||
|
||||
log.Warnf("Matugen version changed (v4: %v -> %v), retrying dry-run", flags.isV4, newFlags.isV4)
|
||||
return execDryRun(opts, newFlags)
|
||||
}
|
||||
|
||||
func execDryRun(opts *Options, flags matugenFlags) (string, error) {
|
||||
var baseArgs []string
|
||||
switch opts.Kind {
|
||||
case "hex":
|
||||
baseArgs = []string{"color", "hex", opts.Value}
|
||||
default:
|
||||
baseArgs = []string{opts.Kind, opts.Value}
|
||||
}
|
||||
baseArgs = append(baseArgs, "-m", "dark", "-t", opts.MatugenType, "--json", "hex", "--dry-run")
|
||||
if flags.isV4 {
|
||||
baseArgs = append(baseArgs, "--source-color-index", "0", "--old-json-output")
|
||||
}
|
||||
|
||||
cmd := exec.Command("matugen", baseArgs...)
|
||||
var stderr strings.Builder
|
||||
cmd.Stderr = &stderr
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
if stderr.Len() > 0 {
|
||||
return "", fmt.Errorf("matugen %v failed (v4=%v): %s", baseArgs, flags.isV4, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
return "", fmt.Errorf("matugen %v failed (v4=%v): %w", baseArgs, flags.isV4, err)
|
||||
}
|
||||
return strings.ReplaceAll(string(output), "\n", ""), nil
|
||||
}
|
||||
|
||||
@@ -819,6 +909,8 @@ func CheckTemplates(checker utils.AppChecker) []TemplateCheck {
|
||||
detected = true
|
||||
case tmpl.Kind == TemplateKindVSCode:
|
||||
detected = checkVSCodeExtension(homeDir)
|
||||
case tmpl.Kind == TemplateKindEmacs:
|
||||
detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks) && utils.EmacsConfigDir() != ""
|
||||
default:
|
||||
detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks)
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ func (i *ExtWorkspaceManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
l := 0
|
||||
objectID := client.Uint32(data[l : l+4])
|
||||
proxy := i.Context().GetProxy(objectID)
|
||||
if proxy != nil {
|
||||
if proxy != nil && !proxy.IsZombie() {
|
||||
e.WorkspaceGroup = proxy.(*ExtWorkspaceGroupHandleV1)
|
||||
} else {
|
||||
groupHandle := &ExtWorkspaceGroupHandleV1{}
|
||||
@@ -278,7 +278,7 @@ func (i *ExtWorkspaceManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
l := 0
|
||||
objectID := client.Uint32(data[l : l+4])
|
||||
proxy := i.Context().GetProxy(objectID)
|
||||
if proxy != nil {
|
||||
if proxy != nil && !proxy.IsZombie() {
|
||||
e.Workspace = proxy.(*ExtWorkspaceHandleV1)
|
||||
} else {
|
||||
wsHandle := &ExtWorkspaceHandleV1{}
|
||||
|
||||
@@ -108,7 +108,7 @@ func NewRegionSelector(s *Screenshoter) *RegionSelector {
|
||||
screenshoter: s,
|
||||
outputs: make(map[uint32]*WaylandOutput),
|
||||
preCapture: make(map[*WaylandOutput]*PreCapture),
|
||||
showCapturedCursor: true,
|
||||
showCapturedCursor: s.config.Cursor == CursorOn,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -453,10 +453,7 @@ func (s *Screenshoter) blitBuffer(dst, src *ShmBuffer, dstX, dstY int, yInverted
|
||||
}
|
||||
|
||||
func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult, error) {
|
||||
cursor := int32(0)
|
||||
if s.config.IncludeCursor {
|
||||
cursor = 1
|
||||
}
|
||||
cursor := int32(s.config.Cursor)
|
||||
|
||||
frame, err := s.screencopy.CaptureOutput(cursor, output.wlOutput)
|
||||
if err != nil {
|
||||
@@ -624,10 +621,7 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
||||
}
|
||||
}
|
||||
|
||||
cursor := int32(0)
|
||||
if s.config.IncludeCursor {
|
||||
cursor = 1
|
||||
}
|
||||
cursor := int32(s.config.Cursor)
|
||||
|
||||
frame, err := s.screencopy.CaptureOutputRegion(cursor, output.wlOutput, localX, localY, w, h)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,13 @@ const (
|
||||
FormatPPM
|
||||
)
|
||||
|
||||
type CursorMode int
|
||||
|
||||
const (
|
||||
CursorOff CursorMode = iota
|
||||
CursorOn
|
||||
)
|
||||
|
||||
type Region struct {
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
@@ -42,29 +49,29 @@ type Output struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Mode Mode
|
||||
OutputName string
|
||||
IncludeCursor bool
|
||||
Format Format
|
||||
Quality int
|
||||
OutputDir string
|
||||
Filename string
|
||||
Clipboard bool
|
||||
SaveFile bool
|
||||
Notify bool
|
||||
Stdout bool
|
||||
Mode Mode
|
||||
OutputName string
|
||||
Cursor CursorMode
|
||||
Format Format
|
||||
Quality int
|
||||
OutputDir string
|
||||
Filename string
|
||||
Clipboard bool
|
||||
SaveFile bool
|
||||
Notify bool
|
||||
Stdout bool
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Mode: ModeRegion,
|
||||
IncludeCursor: false,
|
||||
Format: FormatPNG,
|
||||
Quality: 90,
|
||||
OutputDir: "",
|
||||
Filename: "",
|
||||
Clipboard: true,
|
||||
SaveFile: true,
|
||||
Notify: true,
|
||||
Mode: ModeRegion,
|
||||
Cursor: CursorOff,
|
||||
Format: FormatPNG,
|
||||
Quality: 90,
|
||||
OutputDir: "",
|
||||
Filename: "",
|
||||
Clipboard: true,
|
||||
SaveFile: true,
|
||||
Notify: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,8 +232,15 @@ func (m *Manager) setupDataDeviceSync() {
|
||||
return
|
||||
}
|
||||
|
||||
prevOffer := m.currentOffer
|
||||
m.currentOffer = offer
|
||||
|
||||
if prevOffer != nil && prevOffer != offer {
|
||||
m.offerMutex.Lock()
|
||||
delete(m.offerMimeTypes, prevOffer)
|
||||
m.offerMutex.Unlock()
|
||||
}
|
||||
|
||||
m.offerMutex.RLock()
|
||||
mimes := m.offerMimeTypes[offer]
|
||||
m.offerMutex.RUnlock()
|
||||
@@ -587,20 +594,26 @@ func (m *Manager) uriListPreview(data []byte) (string, bool) {
|
||||
uris = strings.Split(text, "\n")
|
||||
}
|
||||
|
||||
if len(uris) > 1 {
|
||||
return fmt.Sprintf("[[ %d files ]]", len(uris)), false
|
||||
}
|
||||
|
||||
if len(uris) == 1 && strings.HasPrefix(uris[0], "file://") {
|
||||
filePath := strings.TrimPrefix(uris[0], "file://")
|
||||
if info, err := os.Stat(filePath); err == nil && !info.IsDir() {
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil || info.IsDir() {
|
||||
return m.textPreview(data), false
|
||||
}
|
||||
|
||||
cfg := m.getConfig()
|
||||
if info.Size() <= cfg.MaxEntrySize {
|
||||
if imgData, err := os.ReadFile(filePath); err == nil {
|
||||
if config, imgFmt, err := image.DecodeConfig(bytes.NewReader(imgData)); err == nil {
|
||||
return fmt.Sprintf("[[ file %s %s %dx%d ]]", filepath.Base(filePath), imgFmt, config.Width, config.Height), true
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("[[ file %s ]]", filepath.Base(filePath)), false
|
||||
}
|
||||
}
|
||||
|
||||
if len(uris) > 1 {
|
||||
return fmt.Sprintf("[[ %d files ]]", len(uris)), false
|
||||
return fmt.Sprintf("[[ file %s ]]", filepath.Base(filePath)), false
|
||||
}
|
||||
|
||||
return m.textPreview(data), false
|
||||
@@ -623,6 +636,11 @@ func (m *Manager) tryReadImageFromURI(data []byte) ([]byte, string, bool) {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
cfg := m.getConfig()
|
||||
if info.Size() > cfg.MaxEntrySize {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
imgData, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, "", false
|
||||
|
||||
@@ -16,4 +16,8 @@ const (
|
||||
dbusScreensaverPath = "/ScreenSaver"
|
||||
dbusScreensaverPath2 = "/org/freedesktop/ScreenSaver"
|
||||
dbusScreensaverInterface = "org.freedesktop.ScreenSaver"
|
||||
|
||||
dbusGnomeScreensaverName = "org.gnome.ScreenSaver"
|
||||
dbusGnomeScreensaverPath = "/org/gnome/ScreenSaver"
|
||||
dbusGnomeScreensaverInterface = "org.gnome.ScreenSaver"
|
||||
)
|
||||
|
||||
@@ -191,6 +191,12 @@ func (m *Manager) Close() {
|
||||
return true
|
||||
})
|
||||
|
||||
m.screensaverSubscribers.Range(func(key string, ch chan ScreensaverState) bool {
|
||||
close(ch)
|
||||
m.screensaverSubscribers.Delete(key)
|
||||
return true
|
||||
})
|
||||
|
||||
if m.systemConn != nil {
|
||||
m.systemConn.Close()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package freedesktop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -15,45 +16,9 @@ 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
|
||||
}
|
||||
|
||||
screensaverIface := introspect.Interface{
|
||||
Name: dbusScreensaverInterface,
|
||||
func screensaverIntrospectIface(ifaceName string) introspect.Interface {
|
||||
return introspect.Interface{
|
||||
Name: ifaceName,
|
||||
Methods: []introspect.Method{
|
||||
{
|
||||
Name: "Inhibit",
|
||||
@@ -69,40 +34,106 @@ func (m *Manager) initializeScreensaver() error {
|
||||
{Name: "cookie", Type: "u", Direction: "in"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetActive",
|
||||
Args: []introspect.Arg{
|
||||
{Name: "active", Type: "b", Direction: "out"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "SetActive",
|
||||
Args: []introspect.Arg{
|
||||
{Name: "active", Type: "b", Direction: "in"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Lock",
|
||||
},
|
||||
},
|
||||
Signals: []introspect.Signal{
|
||||
{
|
||||
Name: "ActiveChanged",
|
||||
Args: []introspect.Arg{
|
||||
{Name: "new_value", Type: "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) initializeScreensaver() error {
|
||||
if m.sessionConn == nil {
|
||||
m.stateMutex.Lock()
|
||||
m.state.Screensaver.Available = false
|
||||
m.stateMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
introNode := &introspect.Node{
|
||||
Name: dbusScreensaverPath,
|
||||
Interfaces: []introspect.Interface{
|
||||
introspect.IntrospectData,
|
||||
screensaverIface,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
handler := &screensaverHandler{manager: m}
|
||||
|
||||
introNode2 := &introspect.Node{
|
||||
Name: dbusScreensaverPath2,
|
||||
Interfaces: []introspect.Interface{
|
||||
introspect.IntrospectData,
|
||||
screensaverIface,
|
||||
},
|
||||
}
|
||||
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)
|
||||
m.screensaverFreedesktopClaimed = m.claimScreensaverName(handler,
|
||||
dbusScreensaverName, dbusScreensaverInterface, dbusScreensaverPath, dbusScreensaverPath2)
|
||||
m.screensaverGnomeClaimed = m.claimScreensaverName(handler,
|
||||
dbusGnomeScreensaverName, dbusGnomeScreensaverInterface, dbusGnomeScreensaverPath)
|
||||
|
||||
if !m.screensaverFreedesktopClaimed && !m.screensaverGnomeClaimed {
|
||||
log.Warn("No screensaver interface could be claimed")
|
||||
m.stateMutex.Lock()
|
||||
m.state.Screensaver.Available = false
|
||||
m.stateMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
go m.watchPeerDisconnects()
|
||||
|
||||
m.stateMutex.Lock()
|
||||
m.state.Screensaver.Available = true
|
||||
m.state.Screensaver.Active = false
|
||||
m.state.Screensaver.Inhibited = false
|
||||
m.state.Screensaver.Inhibitors = []ScreensaverInhibitor{}
|
||||
m.stateMutex.Unlock()
|
||||
|
||||
log.Info("Screensaver inhibit listener initialized")
|
||||
log.Info("Screensaver listener initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) claimScreensaverName(handler *screensaverHandler, name, iface string, paths ...dbus.ObjectPath) bool {
|
||||
reply, err := m.sessionConn.RequestName(name, dbus.NameFlagDoNotQueue)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to request screensaver name %s: %v", name, err)
|
||||
return false
|
||||
}
|
||||
if reply != dbus.RequestNameReplyPrimaryOwner {
|
||||
log.Warnf("Screensaver name %s already owned by another process", name)
|
||||
return false
|
||||
}
|
||||
if err := m.exportScreensaverOnPaths(handler, iface, paths...); err != nil {
|
||||
log.Warnf("Failed to export screensaver on %s: %v", name, err)
|
||||
return false
|
||||
}
|
||||
log.Infof("Claimed %s on session bus", name)
|
||||
return true
|
||||
}
|
||||
|
||||
// exportScreensaverOnPaths exports the handler and introspection on the given
|
||||
// paths under the specified interface name.
|
||||
func (m *Manager) exportScreensaverOnPaths(handler *screensaverHandler, ifaceName string, paths ...dbus.ObjectPath) error {
|
||||
iface := screensaverIntrospectIface(ifaceName)
|
||||
for _, path := range paths {
|
||||
if err := m.sessionConn.Export(handler, path, ifaceName); err != nil {
|
||||
return fmt.Errorf("export handler on %s: %w", path, err)
|
||||
}
|
||||
node := &introspect.Node{
|
||||
Name: string(path),
|
||||
Interfaces: []introspect.Interface{
|
||||
introspect.IntrospectData,
|
||||
iface,
|
||||
},
|
||||
}
|
||||
if err := m.sessionConn.Export(introspect.NewIntrospectable(node), path, "org.freedesktop.DBus.Introspectable"); err != nil {
|
||||
log.Warnf("Failed to export introspectable on %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -268,3 +299,51 @@ func (m *Manager) NotifyScreensaverSubscribers() {
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (h *screensaverHandler) GetActive() (bool, *dbus.Error) {
|
||||
h.manager.stateMutex.RLock()
|
||||
active := h.manager.state.Screensaver.Active
|
||||
h.manager.stateMutex.RUnlock()
|
||||
return active, nil
|
||||
}
|
||||
|
||||
func (h *screensaverHandler) SetActive(active bool) *dbus.Error {
|
||||
h.manager.SetScreenLockActive(active)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *screensaverHandler) Lock() *dbus.Error {
|
||||
h.manager.SetScreenLockActive(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) SetScreenLockActive(active bool) {
|
||||
m.stateMutex.Lock()
|
||||
changed := m.state.Screensaver.Active != active
|
||||
m.state.Screensaver.Active = active
|
||||
m.stateMutex.Unlock()
|
||||
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("Screen lock active changed: %v", active)
|
||||
defer m.NotifyScreensaverSubscribers()
|
||||
|
||||
if m.sessionConn == nil {
|
||||
return
|
||||
}
|
||||
if m.screensaverFreedesktopClaimed {
|
||||
if err := m.sessionConn.Emit(dbusScreensaverPath, dbusScreensaverInterface+".ActiveChanged", active); err != nil {
|
||||
log.Warnf("Failed to emit ActiveChanged on %s: %v", dbusScreensaverPath, err)
|
||||
}
|
||||
if err := m.sessionConn.Emit(dbusScreensaverPath2, dbusScreensaverInterface+".ActiveChanged", active); err != nil {
|
||||
log.Warnf("Failed to emit ActiveChanged on %s: %v", dbusScreensaverPath2, err)
|
||||
}
|
||||
}
|
||||
if m.screensaverGnomeClaimed {
|
||||
if err := m.sessionConn.Emit(dbusGnomeScreensaverPath, dbusGnomeScreensaverInterface+".ActiveChanged", active); err != nil {
|
||||
log.Warnf("Failed to emit ActiveChanged on %s: %v", dbusGnomeScreensaverPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
102
core/internal/server/freedesktop/screensaver_test.go
Normal file
102
core/internal/server/freedesktop/screensaver_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package freedesktop
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetScreenLockActive_ChangesState(t *testing.T) {
|
||||
manager := &Manager{
|
||||
state: &FreedeskState{
|
||||
Screensaver: ScreensaverState{Available: true},
|
||||
},
|
||||
stateMutex: sync.RWMutex{},
|
||||
}
|
||||
|
||||
assert.False(t, manager.GetScreensaverState().Active)
|
||||
|
||||
manager.SetScreenLockActive(true)
|
||||
assert.True(t, manager.GetScreensaverState().Active)
|
||||
|
||||
manager.SetScreenLockActive(false)
|
||||
assert.False(t, manager.GetScreensaverState().Active)
|
||||
}
|
||||
|
||||
func TestSetScreenLockActive_NoChangeNoDuplicate(t *testing.T) {
|
||||
ch := make(chan ScreensaverState, 64)
|
||||
manager := &Manager{
|
||||
state: &FreedeskState{
|
||||
Screensaver: ScreensaverState{Available: true, Active: false},
|
||||
},
|
||||
stateMutex: sync.RWMutex{},
|
||||
}
|
||||
manager.screensaverSubscribers.Store("test", ch)
|
||||
defer manager.screensaverSubscribers.Delete("test")
|
||||
|
||||
// Setting to same value should not notify
|
||||
manager.SetScreenLockActive(false)
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
t.Fatal("should not have received notification for no-change")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
// Expected: no notification
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetScreenLockActive_NotifiesSubscribers(t *testing.T) {
|
||||
ch := make(chan ScreensaverState, 64)
|
||||
manager := &Manager{
|
||||
state: &FreedeskState{
|
||||
Screensaver: ScreensaverState{Available: true, Active: false},
|
||||
},
|
||||
stateMutex: sync.RWMutex{},
|
||||
}
|
||||
manager.screensaverSubscribers.Store("test", ch)
|
||||
defer manager.screensaverSubscribers.Delete("test")
|
||||
|
||||
manager.SetScreenLockActive(true)
|
||||
|
||||
select {
|
||||
case state := <-ch:
|
||||
assert.True(t, state.Active)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for subscriber notification")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetScreenLockActive_NilSessionConn(t *testing.T) {
|
||||
manager := &Manager{
|
||||
state: &FreedeskState{
|
||||
Screensaver: ScreensaverState{Available: true},
|
||||
},
|
||||
stateMutex: sync.RWMutex{},
|
||||
}
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
manager.SetScreenLockActive(true)
|
||||
})
|
||||
assert.True(t, manager.GetScreensaverState().Active)
|
||||
}
|
||||
|
||||
func TestGetActive_ReturnsCurrentState(t *testing.T) {
|
||||
manager := &Manager{
|
||||
state: &FreedeskState{
|
||||
Screensaver: ScreensaverState{Available: true, Active: true},
|
||||
},
|
||||
stateMutex: sync.RWMutex{},
|
||||
}
|
||||
|
||||
handler := &screensaverHandler{manager: manager}
|
||||
active, dbusErr := handler.GetActive()
|
||||
assert.Nil(t, dbusErr)
|
||||
assert.True(t, active)
|
||||
}
|
||||
|
||||
func TestScreensaverState_ActiveDefaultsFalse(t *testing.T) {
|
||||
state := ScreensaverState{}
|
||||
assert.False(t, state.Active)
|
||||
}
|
||||
@@ -39,6 +39,7 @@ type ScreensaverInhibitor struct {
|
||||
|
||||
type ScreensaverState struct {
|
||||
Available bool `json:"available"`
|
||||
Active bool `json:"active"`
|
||||
Inhibited bool `json:"inhibited"`
|
||||
Inhibitors []ScreensaverInhibitor `json:"inhibitors"`
|
||||
}
|
||||
@@ -50,14 +51,16 @@ type FreedeskState struct {
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
state *FreedeskState
|
||||
stateMutex sync.RWMutex
|
||||
systemConn *dbus.Conn
|
||||
sessionConn *dbus.Conn
|
||||
accountsObj dbus.BusObject
|
||||
settingsObj dbus.BusObject
|
||||
currentUID uint64
|
||||
subscribers syncmap.Map[string, chan FreedeskState]
|
||||
screensaverSubscribers syncmap.Map[string, chan ScreensaverState]
|
||||
screensaverCookieCounter uint32
|
||||
state *FreedeskState
|
||||
stateMutex sync.RWMutex
|
||||
systemConn *dbus.Conn
|
||||
sessionConn *dbus.Conn
|
||||
accountsObj dbus.BusObject
|
||||
settingsObj dbus.BusObject
|
||||
currentUID uint64
|
||||
subscribers syncmap.Map[string, chan FreedeskState]
|
||||
screensaverSubscribers syncmap.Map[string, chan ScreensaverState]
|
||||
screensaverCookieCounter uint32
|
||||
screensaverFreedesktopClaimed bool
|
||||
screensaverGnomeClaimed bool
|
||||
}
|
||||
|
||||
@@ -1516,7 +1516,11 @@ func Start(printDocs bool) error {
|
||||
}
|
||||
}()
|
||||
|
||||
loginctlReady := make(chan struct{})
|
||||
freedesktopReady := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(loginctlReady)
|
||||
if err := InitializeLoginctlManager(); err != nil {
|
||||
log.Warnf("Loginctl manager unavailable: %v", err)
|
||||
} else {
|
||||
@@ -1525,6 +1529,7 @@ func Start(printDocs bool) error {
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer close(freedesktopReady)
|
||||
if err := InitializeFreedeskManager(); err != nil {
|
||||
log.Warnf("Freedesktop manager unavailable: %v", err)
|
||||
} else if freedesktopManager != nil {
|
||||
@@ -1533,6 +1538,31 @@ func Start(printDocs bool) error {
|
||||
}
|
||||
}()
|
||||
|
||||
// Bridge loginctl lock state to the freedesktop/gnome screensaver
|
||||
// ActiveChanged signal so apps like Bitwarden can detect screen lock.
|
||||
go func() {
|
||||
<-loginctlReady
|
||||
<-freedesktopReady
|
||||
|
||||
if loginctlManager == nil || freedesktopManager == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ch := loginctlManager.Subscribe("dms-lock-bridge")
|
||||
defer loginctlManager.Unsubscribe("dms-lock-bridge")
|
||||
|
||||
initial := loginctlManager.GetState()
|
||||
lastLocked := initial.Locked
|
||||
freedesktopManager.SetScreenLockActive(lastLocked)
|
||||
|
||||
for state := range ch {
|
||||
if state.Locked != lastLocked {
|
||||
lastLocked = state.Locked
|
||||
freedesktopManager.SetScreenLockActive(lastLocked)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := InitializeWaylandManager(); err != nil {
|
||||
log.Warnf("Wayland manager unavailable: %v", err)
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestCleanupStaleSockets(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("XDG_RUNTIME_DIR", tempDir)
|
||||
|
||||
staleSocket := filepath.Join(tempDir, "danklinux-999999.sock")
|
||||
staleSocket := filepath.Join(tempDir, "danklinux-4194305.sock")
|
||||
err := os.WriteFile(staleSocket, []byte{}, 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -38,6 +38,22 @@ func XDGConfigHome() string {
|
||||
return filepath.Join(home, ".config")
|
||||
}
|
||||
|
||||
func EmacsConfigDir() string {
|
||||
home, _ := os.UserHomeDir()
|
||||
|
||||
emacsD := filepath.Join(home, ".emacs.d")
|
||||
if info, err := os.Stat(emacsD); err == nil && info.IsDir() {
|
||||
return emacsD
|
||||
}
|
||||
|
||||
xdgEmacs := filepath.Join(XDGConfigHome(), "emacs")
|
||||
if info, err := os.Stat(xdgEmacs); err == nil && info.IsDir() {
|
||||
return xdgEmacs
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func ExpandPath(path string) (string, error) {
|
||||
expanded := os.ExpandEnv(path)
|
||||
expanded = filepath.Clean(expanded)
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
buildInputs =
|
||||
with pkgs;
|
||||
[
|
||||
go_1_24
|
||||
go_1_25
|
||||
gopls
|
||||
delve
|
||||
go-tools
|
||||
@@ -189,6 +189,7 @@
|
||||
|
||||
prek
|
||||
uv # for prek
|
||||
shellcheck
|
||||
|
||||
# Nix development tools
|
||||
nixd
|
||||
|
||||
@@ -1 +1 @@
|
||||
The Wolverine
|
||||
Saffron Bloom
|
||||
|
||||
@@ -142,6 +142,8 @@ Singleton {
|
||||
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
|
||||
popout.currentTabIndex = tabIndex;
|
||||
}
|
||||
if (popout.updateSurfacePosition)
|
||||
popout.updateSurfacePosition();
|
||||
currentPopoutTriggers[screenName] = triggerId;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ Singleton {
|
||||
property bool _hasLoaded: false
|
||||
property bool _isReadOnly: false
|
||||
property bool _hasUnsavedChanges: false
|
||||
property bool _selfWrite: false
|
||||
property var _loadedSettingsSnapshot: null
|
||||
property var pluginSettings: ({})
|
||||
property var builtInPluginSettings: ({})
|
||||
@@ -1243,6 +1244,7 @@ Singleton {
|
||||
function saveSettings() {
|
||||
if (_loading || _parseError || !_hasLoaded)
|
||||
return;
|
||||
_selfWrite = true;
|
||||
settingsFile.setText(JSON.stringify(Store.toJson(root), null, 2));
|
||||
if (_isReadOnly)
|
||||
_checkSettingsWritable();
|
||||
@@ -2589,7 +2591,13 @@ Singleton {
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
watchChanges: true
|
||||
onFileChanged: settingsFileReloadDebounce.restart()
|
||||
onFileChanged: {
|
||||
if (_selfWrite) {
|
||||
_selfWrite = false;
|
||||
return;
|
||||
}
|
||||
settingsFileReloadDebounce.restart();
|
||||
}
|
||||
onLoaded: {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
|
||||
@@ -859,6 +859,70 @@ Item {
|
||||
return success ? `WIDGET_TOGGLE_SUCCESS: ${widgetId}` : `WIDGET_TOGGLE_FAILED: ${widgetId}`;
|
||||
}
|
||||
|
||||
function openWith(widgetId: string, mode: string): string {
|
||||
if (!widgetId)
|
||||
return "ERROR: No widget ID specified";
|
||||
if (!BarWidgetService.hasWidget(widgetId))
|
||||
return `WIDGET_NOT_FOUND: ${widgetId}`;
|
||||
|
||||
const widget = BarWidgetService.getWidgetOnFocusedScreen(widgetId);
|
||||
if (!widget)
|
||||
return `WIDGET_NOT_AVAILABLE: ${widgetId}`;
|
||||
if (typeof widget.openWithMode !== "function")
|
||||
return `WIDGET_OPEN_WITH_NOT_SUPPORTED: ${widgetId}`;
|
||||
|
||||
widget.openWithMode(mode || "all");
|
||||
return `WIDGET_OPEN_WITH_SUCCESS: ${widgetId} ${mode}`;
|
||||
}
|
||||
|
||||
function toggleWith(widgetId: string, mode: string): string {
|
||||
if (!widgetId)
|
||||
return "ERROR: No widget ID specified";
|
||||
if (!BarWidgetService.hasWidget(widgetId))
|
||||
return `WIDGET_NOT_FOUND: ${widgetId}`;
|
||||
|
||||
const widget = BarWidgetService.getWidgetOnFocusedScreen(widgetId);
|
||||
if (!widget)
|
||||
return `WIDGET_NOT_AVAILABLE: ${widgetId}`;
|
||||
if (typeof widget.toggleWithMode !== "function")
|
||||
return `WIDGET_TOGGLE_WITH_NOT_SUPPORTED: ${widgetId}`;
|
||||
|
||||
widget.toggleWithMode(mode || "all");
|
||||
return `WIDGET_TOGGLE_WITH_SUCCESS: ${widgetId} ${mode}`;
|
||||
}
|
||||
|
||||
function openQuery(widgetId: string, query: string): string {
|
||||
if (!widgetId)
|
||||
return "ERROR: No widget ID specified";
|
||||
if (!BarWidgetService.hasWidget(widgetId))
|
||||
return `WIDGET_NOT_FOUND: ${widgetId}`;
|
||||
|
||||
const widget = BarWidgetService.getWidgetOnFocusedScreen(widgetId);
|
||||
if (!widget)
|
||||
return `WIDGET_NOT_AVAILABLE: ${widgetId}`;
|
||||
if (typeof widget.openWithQuery !== "function")
|
||||
return `WIDGET_OPEN_QUERY_NOT_SUPPORTED: ${widgetId}`;
|
||||
|
||||
widget.openWithQuery(query || "");
|
||||
return `WIDGET_OPEN_QUERY_SUCCESS: ${widgetId}`;
|
||||
}
|
||||
|
||||
function toggleQuery(widgetId: string, query: string): string {
|
||||
if (!widgetId)
|
||||
return "ERROR: No widget ID specified";
|
||||
if (!BarWidgetService.hasWidget(widgetId))
|
||||
return `WIDGET_NOT_FOUND: ${widgetId}`;
|
||||
|
||||
const widget = BarWidgetService.getWidgetOnFocusedScreen(widgetId);
|
||||
if (!widget)
|
||||
return `WIDGET_NOT_AVAILABLE: ${widgetId}`;
|
||||
if (typeof widget.toggleWithQuery !== "function")
|
||||
return `WIDGET_TOGGLE_QUERY_NOT_SUPPORTED: ${widgetId}`;
|
||||
|
||||
widget.toggleWithQuery(query || "");
|
||||
return `WIDGET_TOGGLE_QUERY_SUCCESS: ${widgetId}`;
|
||||
}
|
||||
|
||||
function list(): string {
|
||||
const widgets = BarWidgetService.getRegisteredWidgetIds();
|
||||
if (widgets.length === 0)
|
||||
|
||||
@@ -18,7 +18,7 @@ FloatingWindow {
|
||||
}
|
||||
|
||||
objectName: "changelogModal"
|
||||
title: "What's New"
|
||||
title: i18n("What's New")
|
||||
minimumSize: Qt.size(modalWidth, modalHeight)
|
||||
maximumSize: Qt.size(modalWidth, modalHeight)
|
||||
color: Theme.surfaceContainer
|
||||
@@ -81,7 +81,7 @@ FloatingWindow {
|
||||
onClicked: root.dismiss()
|
||||
|
||||
DankTooltip {
|
||||
text: "Close"
|
||||
text: i18n("Close")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ FloatingWindow {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankButton {
|
||||
text: "Read Full Release Notes"
|
||||
text: i18n("Read Full Release Notes")
|
||||
iconName: "open_in_new"
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
textColor: Theme.surfaceText
|
||||
@@ -133,7 +133,7 @@ FloatingWindow {
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: "Got It"
|
||||
text: i18n("Got It")
|
||||
iconName: "check"
|
||||
backgroundColor: Theme.primary
|
||||
textColor: Theme.primaryText
|
||||
|
||||
@@ -12,7 +12,7 @@ Singleton {
|
||||
readonly property int popoutWidth: 550
|
||||
readonly property int popoutHeight: 500
|
||||
readonly property int itemHeight: 72
|
||||
readonly property int thumbnailSize: 48
|
||||
readonly property int thumbnailSize: 100
|
||||
readonly property int retryInterval: 50
|
||||
readonly property int viewportBuffer: 100
|
||||
readonly property int extendedBuffer: 200
|
||||
|
||||
@@ -84,7 +84,8 @@ Rectangle {
|
||||
anchors.right: actionButtons.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: contentColumn.implicitHeight
|
||||
// height: contentColumn.implicitHeight
|
||||
height: ClipboardConstants.itemHeight
|
||||
clip: true
|
||||
|
||||
ClipboardThumbnail {
|
||||
@@ -92,7 +93,7 @@ Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: entryType === "image" ? ClipboardConstants.thumbnailSize : Theme.iconSize
|
||||
height: entryType === "image" ? ClipboardConstants.thumbnailSize : Theme.iconSize
|
||||
height: entryType === "image" ? ClipboardConstants.itemHeight - 4 : Theme.iconSize // 100 - 4 = 96, 96:72 = 4:3
|
||||
entry: root.entry
|
||||
entryType: root.entryType
|
||||
modal: root.modal
|
||||
|
||||
@@ -137,23 +137,23 @@ Item {
|
||||
anchors.margins: 2
|
||||
source: thumbnailImage
|
||||
maskEnabled: true
|
||||
maskSource: clipboardCircularMask
|
||||
maskSource: clipboardRoundedRectangularMask
|
||||
visible: entryType === "image" && thumbnailImage.status === Image.Ready && thumbnailImage.source != ""
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: clipboardCircularMask
|
||||
width: ClipboardConstants.thumbnailSize - 4
|
||||
height: ClipboardConstants.thumbnailSize - 4
|
||||
id: clipboardRoundedRectangularMask
|
||||
width: ClipboardConstants.thumbnailSize
|
||||
height: ClipboardConstants.itemHeight - 4
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
radius: Theme.cornerRadius / 2 // Thumbnail corner radius is divided by 2 so it doesnt look weird on large corner radius (eg: 32px)
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
@@ -56,11 +56,6 @@ Rectangle {
|
||||
case "app":
|
||||
if (selectedItem?.isCore)
|
||||
break;
|
||||
if (selectedItem?.actions) {
|
||||
for (var i = 0; i < selectedItem.actions.length; i++) {
|
||||
result.push(selectedItem.actions[i]);
|
||||
}
|
||||
}
|
||||
if (SessionService.nvidiaCommand) {
|
||||
result.push({
|
||||
name: I18n.tr("Launch on dGPU"),
|
||||
@@ -68,6 +63,11 @@ Rectangle {
|
||||
action: "launch_dgpu"
|
||||
});
|
||||
}
|
||||
if (selectedItem?.actions) {
|
||||
for (var i = 0; i < selectedItem.actions.length; i++) {
|
||||
result.push(selectedItem.actions[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -125,13 +125,6 @@ Item {
|
||||
}
|
||||
|
||||
readonly property var sectionDefinitions: [
|
||||
{
|
||||
id: "calculator",
|
||||
title: I18n.tr("Calculator"),
|
||||
icon: "calculate",
|
||||
priority: 0,
|
||||
defaultViewMode: "list"
|
||||
},
|
||||
{
|
||||
id: "favorites",
|
||||
title: I18n.tr("Pinned"),
|
||||
@@ -681,12 +674,6 @@ Item {
|
||||
return;
|
||||
}
|
||||
|
||||
var calculatorResult = evaluateCalculator(searchQuery);
|
||||
if (calculatorResult) {
|
||||
calculatorResult._preScored = 12000;
|
||||
allItems.push(calculatorResult);
|
||||
}
|
||||
|
||||
var apps = searchApps(searchQuery);
|
||||
for (var i = 0; i < apps.length; i++) {
|
||||
if (searchQuery)
|
||||
@@ -697,12 +684,15 @@ Item {
|
||||
if (searchMode === "all") {
|
||||
if (searchQuery && searchQuery.length >= 2) {
|
||||
_pluginPhasePending = true;
|
||||
_pluginPhaseForceFirst = shouldResetSelection;
|
||||
_phase1Items = allItems;
|
||||
_phase1Items = allItems.slice();
|
||||
pluginPhaseTimer.restart();
|
||||
isSearching = true;
|
||||
searchCompleted();
|
||||
return;
|
||||
if (allItems.length === 0) {
|
||||
_pluginPhaseForceFirst = shouldResetSelection;
|
||||
isSearching = true;
|
||||
searchCompleted();
|
||||
return;
|
||||
}
|
||||
_pluginPhaseForceFirst = false;
|
||||
} else if (!searchQuery) {
|
||||
var emptyTriggerOrdered = getEmptyTriggerPluginsOrdered();
|
||||
for (var i = 0; i < emptyTriggerOrdered.length; i++) {
|
||||
@@ -931,13 +921,6 @@ Item {
|
||||
return Transform.transformFileResult(file, I18n.tr("Open"), I18n.tr("Open folder"), I18n.tr("Copy path"));
|
||||
}
|
||||
|
||||
function evaluateCalculator(query) {
|
||||
var calc = Utils.evaluateCalculator(query);
|
||||
if (!calc)
|
||||
return null;
|
||||
return Transform.createCalculatorItem(calc, query, I18n.tr("Copy"));
|
||||
}
|
||||
|
||||
function detectTrigger(query) {
|
||||
if (!query || query.length === 0)
|
||||
return {
|
||||
@@ -1270,6 +1253,23 @@ Item {
|
||||
CacheData.saveLauncherCache(serializable);
|
||||
}
|
||||
|
||||
function _actionsFromDesktopEntry(appId) {
|
||||
if (!appId)
|
||||
return [];
|
||||
var entry = DesktopEntries.heuristicLookup(appId);
|
||||
if (!entry || !entry.actions || entry.actions.length === 0)
|
||||
return [];
|
||||
var result = [];
|
||||
for (var i = 0; i < entry.actions.length; i++) {
|
||||
result.push({
|
||||
name: entry.actions[i].name,
|
||||
icon: "play_arrow",
|
||||
actionData: entry.actions[i]
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function _loadDiskCache() {
|
||||
var cached = CacheData.loadLauncherCache();
|
||||
if (!cached || !Array.isArray(cached) || cached.length === 0)
|
||||
@@ -1297,8 +1297,12 @@ Item {
|
||||
data: {
|
||||
id: it.id
|
||||
},
|
||||
actions: [],
|
||||
primaryAction: null,
|
||||
actions: _actionsFromDesktopEntry(it.id),
|
||||
primaryAction: it.type === "app" && !it.isCore ? {
|
||||
name: I18n.tr("Launch"),
|
||||
icon: "open_in_new",
|
||||
action: "launch"
|
||||
} : null,
|
||||
_diskCached: true,
|
||||
_hName: "",
|
||||
_hSub: "",
|
||||
@@ -1559,9 +1563,6 @@ Item {
|
||||
case "file":
|
||||
openFile(item.data?.path);
|
||||
break;
|
||||
case "calculator":
|
||||
copyToClipboard(item.name);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@@ -1616,25 +1617,41 @@ Item {
|
||||
itemExecuted();
|
||||
}
|
||||
|
||||
function launchApp(app) {
|
||||
function _resolveDesktopEntry(app) {
|
||||
if (!app)
|
||||
return null;
|
||||
if (app.command)
|
||||
return app;
|
||||
var id = app.id || app.execString || app.exec || "";
|
||||
if (!id)
|
||||
return null;
|
||||
return DesktopEntries.heuristicLookup(id);
|
||||
}
|
||||
|
||||
function launchApp(app) {
|
||||
var entry = _resolveDesktopEntry(app);
|
||||
if (!entry)
|
||||
return;
|
||||
SessionService.launchDesktopEntry(app);
|
||||
AppUsageHistoryData.addAppUsage(app);
|
||||
SessionService.launchDesktopEntry(entry);
|
||||
AppUsageHistoryData.addAppUsage(entry);
|
||||
}
|
||||
|
||||
function launchAppWithNvidia(app) {
|
||||
if (!app)
|
||||
var entry = _resolveDesktopEntry(app);
|
||||
if (!entry)
|
||||
return;
|
||||
SessionService.launchDesktopEntry(app, true);
|
||||
AppUsageHistoryData.addAppUsage(app);
|
||||
SessionService.launchDesktopEntry(entry, true);
|
||||
AppUsageHistoryData.addAppUsage(entry);
|
||||
}
|
||||
|
||||
function launchAppAction(actionItem) {
|
||||
if (!actionItem || !actionItem.parentApp || !actionItem.actionData)
|
||||
if (!actionItem || !actionItem.actionData)
|
||||
return;
|
||||
SessionService.launchDesktopAction(actionItem.parentApp, actionItem.actionData);
|
||||
AppUsageHistoryData.addAppUsage(actionItem.parentApp);
|
||||
var entry = _resolveDesktopEntry(actionItem.parentApp);
|
||||
if (!entry)
|
||||
return;
|
||||
SessionService.launchDesktopAction(entry, actionItem.actionData);
|
||||
AppUsageHistoryData.addAppUsage(entry);
|
||||
}
|
||||
|
||||
function openFile(path) {
|
||||
|
||||
@@ -101,35 +101,6 @@ function detectIconType(iconName) {
|
||||
return "material";
|
||||
}
|
||||
|
||||
function evaluateCalculator(query) {
|
||||
if (!query || query.length === 0)
|
||||
return null;
|
||||
|
||||
var mathExpr = query.replace(/[^0-9+\-*/().%\s^]/g, "");
|
||||
if (mathExpr.length < 2)
|
||||
return null;
|
||||
|
||||
var hasMath = /[+\-*/^%]/.test(query) && /\d/.test(query);
|
||||
if (!hasMath)
|
||||
return null;
|
||||
|
||||
try {
|
||||
var sanitized = mathExpr.replace(/\^/g, "**");
|
||||
var result = Function('"use strict"; return (' + sanitized + ')')();
|
||||
|
||||
if (typeof result === "number" && isFinite(result)) {
|
||||
var displayResult = Number.isInteger(result) ? result.toString() : result.toFixed(6).replace(/\.?0+$/, "");
|
||||
return {
|
||||
expression: query,
|
||||
result: result,
|
||||
displayResult: displayResult
|
||||
};
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function sortPluginIdsByOrder(pluginIds, order) {
|
||||
if (!order || order.length === 0)
|
||||
return pluginIds;
|
||||
|
||||
@@ -190,32 +190,6 @@ function transformPluginItem(item, pluginId, selectLabel) {
|
||||
};
|
||||
}
|
||||
|
||||
function createCalculatorItem(calc, query, copyLabel) {
|
||||
return {
|
||||
id: "calculator_result",
|
||||
type: "calculator",
|
||||
name: calc.displayResult,
|
||||
subtitle: query + " =",
|
||||
icon: "calculate",
|
||||
iconType: "material",
|
||||
section: "calculator",
|
||||
data: {
|
||||
expression: calc.expression,
|
||||
result: calc.result
|
||||
},
|
||||
actions: [],
|
||||
primaryAction: {
|
||||
name: copyLabel,
|
||||
icon: "content_copy",
|
||||
action: "copy"
|
||||
},
|
||||
_hName: "",
|
||||
_hSub: "",
|
||||
_hRich: false,
|
||||
_preScored: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function createPluginBrowseItem(pluginId, plugin, trigger, isBuiltIn, isAllowed, browseLabel, triggerLabel, noTriggerLabel) {
|
||||
var rawIcon = isBuiltIn ? (plugin.cornerIcon || "extension") : (plugin.icon || "extension");
|
||||
return {
|
||||
|
||||
@@ -470,9 +470,6 @@ FocusScope {
|
||||
|
||||
onTextChanged: {
|
||||
controller.setSearchQuery(text);
|
||||
if (text.length === 0) {
|
||||
controller.restorePreviousMode();
|
||||
}
|
||||
if (actionPanel.expanded) {
|
||||
actionPanel.hide();
|
||||
}
|
||||
|
||||
@@ -178,8 +178,6 @@ Rectangle {
|
||||
if (!root.item)
|
||||
return "";
|
||||
switch (root.item.type) {
|
||||
case "calculator":
|
||||
return I18n.tr("Calc");
|
||||
case "plugin":
|
||||
return I18n.tr("Plugin");
|
||||
case "file":
|
||||
|
||||
@@ -238,37 +238,37 @@ FocusScope {
|
||||
|
||||
property var quickAccessLocations: [
|
||||
{
|
||||
"name": "Home",
|
||||
"name": I18n.tr("Home"),
|
||||
"path": homeDir,
|
||||
"icon": "home"
|
||||
},
|
||||
{
|
||||
"name": "Documents",
|
||||
"name": I18n.tr("Documents"),
|
||||
"path": docsDir,
|
||||
"icon": "description"
|
||||
},
|
||||
{
|
||||
"name": "Downloads",
|
||||
"name": I18n.tr("Downloads"),
|
||||
"path": downloadDir,
|
||||
"icon": "download"
|
||||
},
|
||||
{
|
||||
"name": "Pictures",
|
||||
"name": I18n.tr("Pictures"),
|
||||
"path": picsDir,
|
||||
"icon": "image"
|
||||
},
|
||||
{
|
||||
"name": "Music",
|
||||
"name": I18n.tr("Music"),
|
||||
"path": musicDir,
|
||||
"icon": "music_note"
|
||||
},
|
||||
{
|
||||
"name": "Videos",
|
||||
"name": I18n.tr("Videos"),
|
||||
"path": videosDir,
|
||||
"icon": "movie"
|
||||
},
|
||||
{
|
||||
"name": "Desktop",
|
||||
"name": I18n.tr("Desktop"),
|
||||
"path": desktopDir,
|
||||
"icon": "computer"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ StyledRect {
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: "Quick Access"
|
||||
text: I18n.tr("Quick Access")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -91,7 +91,12 @@ DankModal {
|
||||
id: searchField
|
||||
Layout.alignment: Qt.AlignRight
|
||||
leftIconName: "search"
|
||||
keyForwardTargets: [root.modalFocusScope]
|
||||
onTextEdited: searchDebounce.restart()
|
||||
Keys.onEscapePressed: event => {
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +123,7 @@ DankModal {
|
||||
|
||||
function generateCategories(query) {
|
||||
const lowerQuery = query ? query.toLowerCase().trim() : "";
|
||||
const lowerQueryWords = query.split(/\s+/);
|
||||
const processed = {};
|
||||
|
||||
for (const cat in rawBinds) {
|
||||
@@ -130,10 +136,25 @@ DankModal {
|
||||
const keyLower = bind.key.toLowerCase();
|
||||
const descLower = bind.desc.toLowerCase();
|
||||
const actionLower = bind.action.toLowerCase();
|
||||
if (!(lowerQuery.length === 0 || keyLower.includes(lowerQuery) || descLower.includes(lowerQuery) || catLower.includes(lowerQuery) || actionLower.includes(lowerQuery)))
|
||||
continue;
|
||||
|
||||
if (bind.hideOnOverlay)
|
||||
continue;
|
||||
let shouldContinue = false;
|
||||
for (let j = 0; j < lowerQueryWords.length; j++) {
|
||||
const word = lowerQueryWords[j];
|
||||
if (!(
|
||||
word.length === 0 ||
|
||||
keyLower.includes(word) ||
|
||||
descLower.includes(word) ||
|
||||
catLower.includes(word) ||
|
||||
actionLower.includes(word)
|
||||
)) {
|
||||
shouldContinue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shouldContinue)
|
||||
continue;
|
||||
|
||||
if (bind.subcat) {
|
||||
hasSubcats = true;
|
||||
|
||||
@@ -83,9 +83,9 @@ FloatingWindow {
|
||||
|
||||
objectName: "processListModal"
|
||||
title: I18n.tr("System Monitor", "sysmon window title")
|
||||
minimumSize: Qt.size(750, 550)
|
||||
implicitWidth: 1000
|
||||
implicitHeight: 720
|
||||
minimumSize: Qt.size(Math.min(Math.round(Theme.fontSizeMedium * 48), Screen.width), Math.min(Math.round(Theme.fontSizeMedium * 34), Screen.height))
|
||||
implicitWidth: Math.round(Theme.fontSizeMedium * 71)
|
||||
implicitHeight: Math.round(Theme.fontSizeMedium * 51)
|
||||
color: Theme.surfaceContainer
|
||||
visible: false
|
||||
|
||||
@@ -236,7 +236,7 @@ FloatingWindow {
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 48
|
||||
Layout.preferredHeight: Math.round(Theme.fontSizeMedium * 3.4)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -293,10 +293,10 @@ FloatingWindow {
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 52
|
||||
Layout.preferredHeight: Math.round(Theme.fontSizeMedium * 3.7)
|
||||
Layout.leftMargin: Theme.spacingL
|
||||
Layout.rightMargin: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
spacing: 2
|
||||
@@ -322,14 +322,15 @@ FloatingWindow {
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
width: 120
|
||||
height: 44
|
||||
width: tabRowContent.implicitWidth + Theme.spacingM * 2
|
||||
height: Math.round(Theme.fontSizeMedium * 3.1)
|
||||
radius: Theme.cornerRadius
|
||||
color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent")
|
||||
border.color: currentTab === index ? Theme.primary : "transparent"
|
||||
border.width: currentTab === index ? 1 : 0
|
||||
|
||||
Row {
|
||||
id: tabRowContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
@@ -373,11 +374,13 @@ FloatingWindow {
|
||||
|
||||
DankButtonGroup {
|
||||
id: processFilterGroup
|
||||
Layout.minimumWidth: implicitWidth + 8
|
||||
model: [I18n.tr("All"), I18n.tr("User"), I18n.tr("System")]
|
||||
currentIndex: 0
|
||||
checkEnabled: false
|
||||
buttonHeight: 36
|
||||
buttonHeight: Math.round(Theme.fontSizeSmall * 2.6)
|
||||
minButtonWidth: 0
|
||||
buttonPadding: Theme.spacingS
|
||||
textSize: Theme.fontSizeSmall
|
||||
visible: currentTab === 0
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
@@ -400,9 +403,9 @@ FloatingWindow {
|
||||
DankTextField {
|
||||
id: searchField
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 250
|
||||
Layout.minimumWidth: 120
|
||||
Layout.preferredHeight: 40
|
||||
Layout.maximumWidth: Math.round(Theme.fontSizeMedium * 18)
|
||||
Layout.minimumWidth: Theme.fontSizeMedium * 4
|
||||
Layout.preferredHeight: Math.round(Theme.fontSizeMedium * 2.8)
|
||||
placeholderText: I18n.tr("Search processes...", "process search placeholder")
|
||||
leftIconName: "search"
|
||||
showClearButton: true
|
||||
@@ -470,7 +473,7 @@ FloatingWindow {
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 32
|
||||
Layout.preferredHeight: Math.round(Theme.fontSizeSmall * 2.7)
|
||||
Layout.leftMargin: Theme.spacingL
|
||||
Layout.rightMargin: Theme.spacingL
|
||||
Layout.bottomMargin: Theme.spacingM
|
||||
|
||||
@@ -8,10 +8,39 @@ DankPopout {
|
||||
|
||||
layerNamespace: "dms:app-launcher"
|
||||
|
||||
property string _pendingMode: ""
|
||||
property string _pendingQuery: ""
|
||||
|
||||
function show() {
|
||||
open();
|
||||
}
|
||||
|
||||
function openWithMode(mode) {
|
||||
_pendingMode = mode || "";
|
||||
open();
|
||||
}
|
||||
|
||||
function toggleWithMode(mode) {
|
||||
if (shouldBeVisible) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
openWithMode(mode);
|
||||
}
|
||||
|
||||
function openWithQuery(query) {
|
||||
_pendingQuery = query || "";
|
||||
open();
|
||||
}
|
||||
|
||||
function toggleWithQuery(query) {
|
||||
if (shouldBeVisible) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
openWithQuery(query);
|
||||
}
|
||||
|
||||
popupWidth: 560
|
||||
popupHeight: 640
|
||||
triggerWidth: 40
|
||||
@@ -30,15 +59,25 @@ DankPopout {
|
||||
var lc = contentLoader.item?.launcherContent;
|
||||
if (!lc)
|
||||
return;
|
||||
|
||||
const query = _pendingQuery;
|
||||
const mode = _pendingMode || "apps";
|
||||
_pendingMode = "";
|
||||
_pendingQuery = "";
|
||||
|
||||
if (lc.searchField) {
|
||||
lc.searchField.text = "";
|
||||
lc.searchField.text = query;
|
||||
lc.searchField.forceActiveFocus();
|
||||
}
|
||||
if (lc.controller) {
|
||||
lc.controller.searchMode = "apps";
|
||||
lc.controller.searchMode = mode;
|
||||
lc.controller.pluginFilter = "";
|
||||
lc.controller.searchQuery = "";
|
||||
lc.controller.performSearch();
|
||||
if (query) {
|
||||
lc.controller.setSearchQuery(query);
|
||||
} else {
|
||||
lc.controller.performSearch();
|
||||
}
|
||||
}
|
||||
lc.resetScroll?.();
|
||||
lc.actionPanel?.hide();
|
||||
|
||||
@@ -15,18 +15,22 @@ Item {
|
||||
property var pluginDetailInstance: null
|
||||
property var widgetModel: null
|
||||
property var collapseCallback: null
|
||||
property real maxAvailableHeight: 9999
|
||||
|
||||
function getDetailHeight(section) {
|
||||
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
|
||||
switch (true) {
|
||||
case section === "wifi":
|
||||
case section === "bluetooth":
|
||||
case section === "builtin_vpn":
|
||||
return Math.min(350, maxAvailable);
|
||||
return Math.min(350, maxAvailableHeight);
|
||||
case section.startsWith("brightnessSlider_"):
|
||||
return Math.min(400, maxAvailable);
|
||||
return Math.min(400, maxAvailableHeight);
|
||||
case section.startsWith("plugin_"):
|
||||
if (pluginDetailInstance?.ccDetailHeight)
|
||||
return Math.min(pluginDetailInstance.ccDetailHeight, maxAvailableHeight);
|
||||
return Math.min(250, maxAvailableHeight);
|
||||
default:
|
||||
return Math.min(250, maxAvailable);
|
||||
return Math.min(250, maxAvailableHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,26 @@ Column {
|
||||
|
||||
spacing: editMode ? Theme.spacingL : Theme.spacingS
|
||||
|
||||
property real maxPopoutHeight: 9999
|
||||
property var currentRowWidgets: []
|
||||
property real currentRowWidth: 0
|
||||
property int expandedRowIndex: -1
|
||||
property var colorPickerModal: null
|
||||
|
||||
readonly property real _maxDetailHeight: {
|
||||
const rows = layoutResult.rows;
|
||||
let totalRowHeight = 0;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const sliderOnly = rows[i].every(w => {
|
||||
const id = w.id || "";
|
||||
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider";
|
||||
});
|
||||
totalRowHeight += sliderOnly ? 36 : 60;
|
||||
}
|
||||
const rowSpacing = Math.max(0, rows.length - 1) * spacing;
|
||||
return Math.max(100, maxPopoutHeight - totalRowHeight - rowSpacing);
|
||||
}
|
||||
|
||||
function calculateRowsAndWidgets() {
|
||||
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex);
|
||||
}
|
||||
@@ -163,6 +178,7 @@ Column {
|
||||
DetailHost {
|
||||
id: detailHost
|
||||
width: parent.width
|
||||
maxAvailableHeight: root._maxDetailHeight
|
||||
height: active ? (getDetailHeight(root.expandedSection) + Theme.spacingS) : 0
|
||||
property bool active: {
|
||||
if (root.expandedSection === "")
|
||||
|
||||
@@ -116,6 +116,7 @@ DankPopout {
|
||||
property alias bluetoothCodecSelector: bluetoothCodecSelector
|
||||
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -166,6 +167,10 @@ DankPopout {
|
||||
id: widgetGrid
|
||||
width: parent.width
|
||||
editMode: root.editMode
|
||||
maxPopoutHeight: {
|
||||
const screenHeight = (root.triggerScreen?.height ?? 1080);
|
||||
return screenHeight - 100 - Theme.spacingL - headerPane.height - Theme.spacingS;
|
||||
}
|
||||
expandedSection: root.expandedSection
|
||||
expandedWidgetIndex: root.expandedWidgetIndex
|
||||
expandedWidgetData: root.expandedWidgetData
|
||||
|
||||
@@ -40,6 +40,24 @@ Rectangle {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
height: 1
|
||||
width: parent.width - headerText.width - settingsButton.width
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: settingsButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "settings"
|
||||
buttonSize: 28
|
||||
iconSize: 16
|
||||
iconColor: Theme.surfaceVariantText
|
||||
onClicked: {
|
||||
PopoutService.closeControlCenter();
|
||||
PopoutService.openSettingsWithTab("audio");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -151,8 +169,11 @@ Rectangle {
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const hidden = SessionData.hiddenInputDeviceNames ?? [];
|
||||
const nodes = Pipewire.nodes.values.filter(node => {
|
||||
return node.audio && !node.isSink && !node.isStream;
|
||||
if (!node.audio || node.isSink || node.isStream)
|
||||
return false;
|
||||
return !hidden.includes(node.name);
|
||||
});
|
||||
const pinnedList = audioContent.getPinnedInputs();
|
||||
|
||||
|
||||
@@ -40,6 +40,24 @@ Rectangle {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
height: 1
|
||||
width: parent.width - headerText.width - settingsButton.width
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: settingsButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "settings"
|
||||
buttonSize: 28
|
||||
iconSize: 16
|
||||
iconColor: Theme.surfaceVariantText
|
||||
onClicked: {
|
||||
PopoutService.closeControlCenter();
|
||||
PopoutService.openSettingsWithTab("audio");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -161,8 +179,11 @@ Rectangle {
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const hidden = SessionData.hiddenOutputDeviceNames ?? [];
|
||||
const nodes = Pipewire.nodes.values.filter(node => {
|
||||
return node.audio && node.isSink && !node.isStream;
|
||||
if (!node.audio || !node.isSink || node.isStream)
|
||||
return false;
|
||||
return !hidden.includes(node.name);
|
||||
});
|
||||
const pinnedList = audioContent.getPinnedOutputs();
|
||||
|
||||
|
||||
@@ -642,24 +642,52 @@ Item {
|
||||
popoutTarget: appDrawerLoader.item
|
||||
parentScreen: barWindow.screen
|
||||
hyprlandOverviewLoader: barWindow ? barWindow.hyprlandOverviewLoader : null
|
||||
onClicked: {
|
||||
|
||||
function _preparePopout() {
|
||||
appDrawerLoader.active = true;
|
||||
// Use topBarContent.barConfig directly since widget barConfig binding doesn't work in Components
|
||||
if (!appDrawerLoader.item)
|
||||
return false;
|
||||
const effectiveBarConfig = topBarContent.barConfig;
|
||||
// Calculate barPosition from axis.edge
|
||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
||||
if (appDrawerLoader.item && appDrawerLoader.item.setBarContext) {
|
||||
if (appDrawerLoader.item.setBarContext)
|
||||
appDrawerLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
||||
}
|
||||
if (appDrawerLoader.item && appDrawerLoader.item.setTriggerPosition) {
|
||||
if (appDrawerLoader.item.setTriggerPosition) {
|
||||
const globalPos = launcherButton.visualContent.mapToItem(null, 0, 0);
|
||||
const currentScreen = barWindow.screen;
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barWindow.effectiveBarThickness, launcherButton.visualWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
||||
appDrawerLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, launcherButton.section, currentScreen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
||||
}
|
||||
if (appDrawerLoader.item) {
|
||||
PopoutManager.requestPopout(appDrawerLoader.item, undefined, "appDrawer");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function openWithMode(mode) {
|
||||
if (!_preparePopout())
|
||||
return;
|
||||
appDrawerLoader.item.openWithMode(mode);
|
||||
}
|
||||
|
||||
function toggleWithMode(mode) {
|
||||
if (!_preparePopout())
|
||||
return;
|
||||
appDrawerLoader.item.toggleWithMode(mode);
|
||||
}
|
||||
|
||||
function openWithQuery(query) {
|
||||
if (!_preparePopout())
|
||||
return;
|
||||
appDrawerLoader.item.openWithQuery(query);
|
||||
}
|
||||
|
||||
function toggleWithQuery(query) {
|
||||
if (!_preparePopout())
|
||||
return;
|
||||
appDrawerLoader.item.toggleWithQuery(query);
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (!_preparePopout())
|
||||
return;
|
||||
PopoutManager.requestPopout(appDrawerLoader.item, undefined, "appDrawer");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -783,7 +811,7 @@ Item {
|
||||
} else {
|
||||
dankDashPopoutLoader.item.triggerScreen = barWindow.screen;
|
||||
}
|
||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 0, (effectiveBarConfig?.id ?? "default") + "-0");
|
||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 0, (effectiveBarConfig?.id ?? "default") + "-" + section + "-0");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -839,7 +867,7 @@ Item {
|
||||
} else {
|
||||
dankDashPopoutLoader.item.triggerScreen = barWindow.screen;
|
||||
}
|
||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 1, (effectiveBarConfig?.id ?? "default") + "-1");
|
||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 1, (effectiveBarConfig?.id ?? "default") + "-" + section + "-1");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -898,7 +926,7 @@ Item {
|
||||
} else {
|
||||
dankDashPopoutLoader.item.triggerScreen = barWindow.screen;
|
||||
}
|
||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 3, (effectiveBarConfig?.id ?? "default") + "-3");
|
||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 3, (effectiveBarConfig?.id ?? "default") + "-" + section + "-3");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ PanelWindow {
|
||||
dankDashPopoutLoader.item.triggerScreen = barWindow.screen;
|
||||
}
|
||||
|
||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 2, (barConfig?.id ?? "default") + "-2");
|
||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 2, (barConfig?.id ?? "default") + "-" + section + "-2");
|
||||
}
|
||||
|
||||
readonly property var dBarLayer: {
|
||||
|
||||
@@ -99,13 +99,6 @@ BasePill {
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
StyledTextMetrics {
|
||||
id: cpuBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
||||
|
||||
@@ -99,13 +99,6 @@ BasePill {
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
StyledTextMetrics {
|
||||
id: tempBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
||||
|
||||
@@ -193,13 +193,6 @@ BasePill {
|
||||
}
|
||||
|
||||
width: Math.max(diskBaseline.width, paintedWidth)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,13 +167,6 @@ BasePill {
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
StyledTextMetrics {
|
||||
id: gpuTempBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
||||
|
||||
@@ -110,13 +110,6 @@ BasePill {
|
||||
}
|
||||
|
||||
width: Math.max(rxBaseline.width, paintedWidth)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,13 +139,6 @@ BasePill {
|
||||
}
|
||||
|
||||
width: Math.max(txBaseline.width, paintedWidth)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,13 +109,6 @@ BasePill {
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
StyledTextMetrics {
|
||||
id: ramBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
||||
|
||||
@@ -138,15 +138,13 @@ BasePill {
|
||||
readonly property real iconCellSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground) + 6
|
||||
|
||||
readonly property string focusedAppId: {
|
||||
const toplevels = CompositorService.sortedToplevels;
|
||||
if (!toplevels)
|
||||
if (!sortedToplevels || sortedToplevels.length === 0)
|
||||
return "";
|
||||
let result = "";
|
||||
for (let i = 0; i < toplevels.length; i++) {
|
||||
if (toplevels[i].activated)
|
||||
result = toplevels[i].appId || "";
|
||||
for (let i = 0; i < sortedToplevels.length; i++) {
|
||||
if (sortedToplevels[i].activated)
|
||||
return sortedToplevels[i].appId || "";
|
||||
}
|
||||
return result;
|
||||
return "";
|
||||
}
|
||||
|
||||
visible: windowCount > 0
|
||||
@@ -155,6 +153,7 @@ BasePill {
|
||||
property real touchpadThreshold: 500
|
||||
|
||||
onWheel: function (wheelEvent) {
|
||||
wheelEvent.accepted = true;
|
||||
const deltaY = wheelEvent.angleDelta.y;
|
||||
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
|
||||
|
||||
|
||||
@@ -99,6 +99,44 @@ BasePill {
|
||||
property bool suppressShiftAnimation: false
|
||||
readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length
|
||||
visible: allTrayItems.length > 0
|
||||
opacity: allTrayItems.length > 0 ? 1 : 0
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "hidden_horizontal"
|
||||
when: allTrayItems.length === 0 && !isVerticalOrientation
|
||||
PropertyChanges {
|
||||
target: root
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hidden_vertical"
|
||||
when: allTrayItems.length === 0 && isVerticalOrientation
|
||||
PropertyChanges {
|
||||
target: root
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
NumberAnimation {
|
||||
properties: "width,height"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real trayItemSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground) + 6
|
||||
|
||||
readonly property real minTooltipY: {
|
||||
|
||||
@@ -41,7 +41,7 @@ Card {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: UserInfoService.username || "brandon"
|
||||
text: UserInfoService.username || I18n.tr("brandon")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
@@ -64,18 +64,18 @@ Card {
|
||||
StyledText {
|
||||
text: {
|
||||
if (CompositorService.isNiri)
|
||||
return "on niri";
|
||||
return I18n.tr("on Niri");
|
||||
if (CompositorService.isHyprland)
|
||||
return "on Hyprland";
|
||||
return I18n.tr("on Hyprland");
|
||||
// technically they might not be on mangowc, but its what we support in the docs
|
||||
if (CompositorService.isDwl)
|
||||
return "on MangoWC";
|
||||
return I18n.tr("on MangoWC");
|
||||
if (CompositorService.isSway)
|
||||
return "on Sway";
|
||||
return I18n.tr("on Sway");
|
||||
if (CompositorService.isScroll)
|
||||
return "on Scroll";
|
||||
return I18n.tr("on Scroll");
|
||||
if (CompositorService.isMiracle)
|
||||
return "on Miracle WM";
|
||||
return I18n.tr("on Miracle WM");
|
||||
return "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -99,7 +99,7 @@ Card {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.shortUptime || "up"
|
||||
text: DgopService.shortUptime || I18n.tr("up")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -553,15 +553,7 @@ Variants {
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, backgroundTransparency)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
width: Math.min(400, Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
@@ -388,18 +388,31 @@ PanelWindow {
|
||||
radius: Theme.cornerRadius
|
||||
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
StyledText {
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.appData && root.appData.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: root.appData && root.appData.isPinned ? "keep_off" : "push_pin"
|
||||
size: 14
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.appData && root.appData.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
@@ -448,18 +461,31 @@ PanelWindow {
|
||||
radius: Theme.cornerRadius
|
||||
color: nvidiaArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
StyledText {
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Launch on dGPU")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "memory"
|
||||
size: 14
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Launch on dGPU")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
@@ -490,23 +516,31 @@ PanelWindow {
|
||||
radius: Theme.cornerRadius
|
||||
color: closeArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
|
||||
StyledText {
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (root.appData && root.appData.type === "grouped") {
|
||||
return "Close All Windows";
|
||||
}
|
||||
return "Close Window";
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "close"
|
||||
size: 14
|
||||
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.appData && root.appData.type === "grouped" ? I18n.tr("Close All Windows") : I18n.tr("Close Window")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
|
||||
@@ -1065,7 +1065,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Caps Lock is on"
|
||||
text: I18n.tr("Caps Lock is on")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -278,7 +278,7 @@ Item {
|
||||
Behavior on x {
|
||||
enabled: !swipeDragHandler.active && delegateRoot.__delegateInitialized
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
duration: Theme.notificationExitDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@ Item {
|
||||
Behavior on opacity {
|
||||
enabled: delegateRoot.__delegateInitialized
|
||||
NumberAnimation {
|
||||
duration: delegateRoot.__delegateInitialized ? Theme.shortDuration : 0
|
||||
duration: delegateRoot.__delegateInitialized ? Theme.notificationExitDuration : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ DankListView {
|
||||
target: delegateRoot
|
||||
property: "swipeOffset"
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
duration: Theme.notificationExitDuration
|
||||
easing.type: Easing.OutCubic
|
||||
onStopped: NotificationService.dismissGroup(delegateRoot.modelData?.key || "")
|
||||
}
|
||||
|
||||
@@ -764,7 +764,7 @@ Rectangle {
|
||||
target: expandedDelegateWrapper
|
||||
property: "swipeOffset"
|
||||
to: expandedDelegateWrapper.swipeOffset > 0 ? expandedDelegateWrapper.width : -expandedDelegateWrapper.width
|
||||
duration: Theme.shortDuration
|
||||
duration: Theme.notificationExitDuration
|
||||
easing.type: Easing.OutCubic
|
||||
onStopped: NotificationService.dismissNotification(modelData)
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ DankPopout {
|
||||
popupWidth: triggerScreen ? Math.min(500, Math.max(380, triggerScreen.width - 48)) : 400
|
||||
popupHeight: stablePopupHeight
|
||||
positioning: ""
|
||||
animationScaleCollapsed: 1.0
|
||||
animationScaleCollapsed: 0.94
|
||||
animationOffset: 0
|
||||
suspendShadowWhileResizing: true
|
||||
suspendShadowWhileResizing: false
|
||||
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: notificationHistoryVisible
|
||||
|
||||
@@ -23,7 +23,7 @@ Rectangle {
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: "↑/↓: Nav • Space: Expand • Enter: Action/Expand • E: Text"
|
||||
text: I18n.tr("↑/↓: Nav • Space: Expand • Enter: Action/Expand • E: Text")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
|
||||
@@ -22,6 +22,8 @@ PanelWindow {
|
||||
property bool _isDestroying: false
|
||||
property bool _finalized: false
|
||||
property real _lastReportedAlignedHeight: -1
|
||||
property real _storedTopMargin: 0
|
||||
property real _storedBottomMargin: 0
|
||||
readonly property string clearText: I18n.tr("Dismiss")
|
||||
property bool descriptionExpanded: false
|
||||
readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
|
||||
@@ -146,6 +148,8 @@ PanelWindow {
|
||||
}
|
||||
Component.onCompleted: {
|
||||
_lastReportedAlignedHeight = Theme.px(implicitHeight, dpr);
|
||||
_storedTopMargin = getTopMargin();
|
||||
_storedBottomMargin = getBottomMargin();
|
||||
if (SettingsData.notificationPopupPrivacyMode)
|
||||
descriptionExpanded = false;
|
||||
if (hasValidData) {
|
||||
@@ -179,14 +183,30 @@ PanelWindow {
|
||||
property bool isBottomCenter: SettingsData.notificationPopupPosition === SettingsData.Position.BottomCenter
|
||||
property bool isCenterPosition: isTopCenter || isBottomCenter
|
||||
|
||||
anchors.top: isTopCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Left
|
||||
anchors.bottom: isBottomCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom || SettingsData.notificationPopupPosition === SettingsData.Position.Right
|
||||
anchors.top: true
|
||||
anchors.bottom: true
|
||||
anchors.left: SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom
|
||||
anchors.right: SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Right
|
||||
|
||||
mask: contentInputMask
|
||||
|
||||
Region {
|
||||
id: contentInputMask
|
||||
item: contentMaskRect
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentMaskRect
|
||||
visible: false
|
||||
x: content.x
|
||||
y: content.y
|
||||
width: alignedWidth
|
||||
height: alignedHeight
|
||||
}
|
||||
|
||||
margins {
|
||||
top: getTopMargin()
|
||||
bottom: getBottomMargin()
|
||||
top: _storedTopMargin
|
||||
bottom: _storedBottomMargin
|
||||
left: getLeftMargin()
|
||||
right: getRightMargin()
|
||||
}
|
||||
@@ -263,7 +283,14 @@ PanelWindow {
|
||||
id: content
|
||||
|
||||
x: Theme.snap((win.width - alignedWidth) / 2, dpr)
|
||||
y: Theme.snap((win.height - alignedHeight) / 2, dpr)
|
||||
y: {
|
||||
const isTop = isTopCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Left;
|
||||
if (isTop) {
|
||||
return Theme.snap(screenY, dpr);
|
||||
} else {
|
||||
return Theme.snap(win.height - alignedHeight - screenY, dpr);
|
||||
}
|
||||
}
|
||||
width: alignedWidth
|
||||
height: alignedHeight
|
||||
visible: !win._finalized
|
||||
@@ -311,7 +338,7 @@ PanelWindow {
|
||||
id: bgShadowLayer
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.snap(4, win.dpr)
|
||||
layer.enabled: !win._isDestroying && win.screenValid && !implicitHeightAnim.running
|
||||
layer.enabled: !win._isDestroying && win.screenValid
|
||||
layer.smooth: false
|
||||
layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
|
||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||
@@ -814,7 +841,7 @@ PanelWindow {
|
||||
Behavior on swipeOffset {
|
||||
enabled: !content.swipeActive && !content.swipeDismissing
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
duration: Theme.notificationExitDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
@@ -824,7 +851,7 @@ PanelWindow {
|
||||
target: content
|
||||
property: "swipeOffset"
|
||||
to: isTopCenter ? -content.height : isBottomCenter ? content.height : (SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom ? -content.width : content.width)
|
||||
duration: Theme.shortDuration
|
||||
duration: Theme.notificationExitDuration
|
||||
easing.type: Easing.OutCubic
|
||||
onStopped: {
|
||||
NotificationService.dismissNotification(notificationData);
|
||||
|
||||
@@ -7,9 +7,10 @@ DankOSD {
|
||||
id: root
|
||||
|
||||
readonly property bool useVertical: isVerticalLayout
|
||||
property int targetBrightness: {
|
||||
DisplayService.brightnessVersion;
|
||||
return DisplayService.brightnessLevel;
|
||||
property int _displayBrightness: 0
|
||||
|
||||
function _syncBrightness() {
|
||||
_displayBrightness = DisplayService.brightnessLevel;
|
||||
}
|
||||
|
||||
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
|
||||
@@ -20,9 +21,9 @@ DankOSD {
|
||||
Connections {
|
||||
target: DisplayService
|
||||
function onBrightnessChanged(showOsd) {
|
||||
if (showOsd && SettingsData.osdBrightnessEnabled) {
|
||||
root._syncBrightness();
|
||||
if (showOsd && SettingsData.osdBrightnessEnabled)
|
||||
root.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,13 +54,11 @@ DankOSD {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc")
|
||||
return "brightness_medium";
|
||||
} else if (deviceInfo.name.includes("kbd")) {
|
||||
if (deviceInfo.name.includes("kbd"))
|
||||
return "keyboard";
|
||||
} else {
|
||||
return "lightbulb";
|
||||
}
|
||||
return "lightbulb";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
@@ -77,20 +76,16 @@ DankOSD {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return 1;
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential) {
|
||||
if (SessionData.getBrightnessExponential(deviceInfo.id))
|
||||
return 1;
|
||||
}
|
||||
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0;
|
||||
}
|
||||
maximum: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return 100;
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential) {
|
||||
if (SessionData.getBrightnessExponential(deviceInfo.id))
|
||||
return 100;
|
||||
}
|
||||
return deviceInfo.displayMax || 100;
|
||||
}
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
@@ -99,28 +94,24 @@ DankOSD {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return "%";
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential) {
|
||||
if (SessionData.getBrightnessExponential(deviceInfo.id))
|
||||
return "%";
|
||||
}
|
||||
return deviceInfo.class === "ddc" ? "" : "%";
|
||||
}
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
alwaysShowValue: SettingsData.osdAlwaysShowValue
|
||||
|
||||
onSliderValueChanged: newValue => {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true);
|
||||
resetHideTimer();
|
||||
}
|
||||
if (!DisplayService.brightnessAvailable)
|
||||
return;
|
||||
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true);
|
||||
resetHideTimer();
|
||||
}
|
||||
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse);
|
||||
}
|
||||
onContainsMouseChanged: setChildHovered(containsMouse)
|
||||
|
||||
Binding on value {
|
||||
value: root.targetBrightness
|
||||
value: root._displayBrightness
|
||||
when: !brightnessSlider.isDragging
|
||||
}
|
||||
}
|
||||
@@ -146,13 +137,11 @@ DankOSD {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc")
|
||||
return "brightness_medium";
|
||||
} else if (deviceInfo.name.includes("kbd")) {
|
||||
if (deviceInfo.name.includes("kbd"))
|
||||
return "keyboard";
|
||||
} else {
|
||||
return "lightbulb";
|
||||
}
|
||||
return "lightbulb";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
@@ -170,7 +159,7 @@ DankOSD {
|
||||
property int value: 50
|
||||
|
||||
Binding on value {
|
||||
value: root.targetBrightness
|
||||
value: root._displayBrightness
|
||||
when: !vertSlider.dragging
|
||||
}
|
||||
|
||||
@@ -178,8 +167,7 @@ DankOSD {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return 1;
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential)
|
||||
if (SessionData.getBrightnessExponential(deviceInfo.id))
|
||||
return 1;
|
||||
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0;
|
||||
}
|
||||
@@ -188,8 +176,7 @@ DankOSD {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return 100;
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential)
|
||||
if (SessionData.getBrightnessExponential(deviceInfo.id))
|
||||
return 100;
|
||||
return deviceInfo.displayMax || 100;
|
||||
}
|
||||
@@ -240,33 +227,25 @@ DankOSD {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse);
|
||||
}
|
||||
onContainsMouseChanged: setChildHovered(containsMouse)
|
||||
|
||||
onPressed: mouse => {
|
||||
vertSlider.dragging = true;
|
||||
updateBrightness(mouse);
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
vertSlider.dragging = false;
|
||||
}
|
||||
onReleased: vertSlider.dragging = false
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
if (pressed)
|
||||
updateBrightness(mouse);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
updateBrightness(mouse);
|
||||
}
|
||||
onClicked: mouse => updateBrightness(mouse)
|
||||
|
||||
function updateBrightness(mouse) {
|
||||
if (!DisplayService.brightnessAvailable) {
|
||||
if (!DisplayService.brightnessAvailable)
|
||||
return;
|
||||
}
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum));
|
||||
vertSlider.value = newValue;
|
||||
|
||||
@@ -8,13 +8,20 @@ DankOSD {
|
||||
|
||||
readonly property bool useVertical: isVerticalLayout
|
||||
readonly property var player: MprisController.activePlayer
|
||||
readonly property int currentVolume: player ? Math.min(100, Math.round(player.volume * 100)) : 0
|
||||
readonly property bool volumeSupported: player?.volumeSupported ?? false
|
||||
property bool _suppressNewPlayer: false
|
||||
property int _displayVolume: 0
|
||||
|
||||
function _syncVolume() {
|
||||
if (!player)
|
||||
return;
|
||||
_displayVolume = Math.min(100, Math.round(player.volume * 100));
|
||||
}
|
||||
|
||||
onPlayerChanged: {
|
||||
_suppressNewPlayer = true;
|
||||
_suppressTimer.restart();
|
||||
_syncVolume();
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -37,25 +44,25 @@ DankOSD {
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
if (player) {
|
||||
player.volume = player.volume > 0 ? 0 : 1;
|
||||
}
|
||||
if (!player)
|
||||
return;
|
||||
player.volume = player.volume > 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
function setVolume(volumePercent) {
|
||||
if (player) {
|
||||
player.volume = volumePercent / 100;
|
||||
resetHideTimer();
|
||||
}
|
||||
if (!player)
|
||||
return;
|
||||
player.volume = volumePercent / 100;
|
||||
resetHideTimer();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (SettingsData.osdMediaVolumeEnabled && volumeSupported && !_suppressNewPlayer) {
|
||||
root._syncVolume();
|
||||
if (SettingsData.osdMediaVolumeEnabled && volumeSupported && !_suppressNewPlayer)
|
||||
root.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,9 +103,7 @@ DankOSD {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: toggleMute()
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || volumeSlider.containsMouse);
|
||||
}
|
||||
onContainsMouseChanged: setChildHovered(containsMouse || volumeSlider.containsMouse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,29 +120,21 @@ DankOSD {
|
||||
showValue: true
|
||||
unit: "%"
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
valueOverride: currentVolume
|
||||
valueOverride: root._displayVolume
|
||||
alwaysShowValue: SettingsData.osdAlwaysShowValue
|
||||
|
||||
Component.onCompleted: {
|
||||
value = currentVolume;
|
||||
root._syncVolume();
|
||||
value = root._displayVolume;
|
||||
}
|
||||
|
||||
onSliderValueChanged: newValue => {
|
||||
setVolume(newValue);
|
||||
}
|
||||
onSliderValueChanged: newValue => setVolume(newValue)
|
||||
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || muteButton.containsMouse);
|
||||
}
|
||||
onContainsMouseChanged: setChildHovered(containsMouse || muteButton.containsMouse)
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (volumeSlider && !volumeSlider.pressed) {
|
||||
volumeSlider.value = currentVolume;
|
||||
}
|
||||
}
|
||||
Binding on value {
|
||||
value: root._displayVolume
|
||||
when: !volumeSlider.pressed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,9 +169,7 @@ DankOSD {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: toggleMute()
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || vertSliderArea.containsMouse);
|
||||
}
|
||||
onContainsMouseChanged: setChildHovered(containsMouse || vertSliderArea.containsMouse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +181,7 @@ DankOSD {
|
||||
y: gap * 2 + Theme.iconSize
|
||||
|
||||
property bool dragging: false
|
||||
property int value: currentVolume
|
||||
property int value: root._displayVolume
|
||||
|
||||
Rectangle {
|
||||
id: vertTrack
|
||||
@@ -231,28 +226,21 @@ DankOSD {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || muteButtonVert.containsMouse);
|
||||
}
|
||||
onContainsMouseChanged: setChildHovered(containsMouse || muteButtonVert.containsMouse)
|
||||
|
||||
onPressed: mouse => {
|
||||
vertSlider.dragging = true;
|
||||
updateVolume(mouse);
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
vertSlider.dragging = false;
|
||||
}
|
||||
onReleased: vertSlider.dragging = false
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
if (pressed)
|
||||
updateVolume(mouse);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
updateVolume(mouse);
|
||||
}
|
||||
onClicked: mouse => updateVolume(mouse)
|
||||
|
||||
function updateVolume(mouse) {
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
@@ -260,16 +248,6 @@ DankOSD {
|
||||
setVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (!vertSlider.dragging) {
|
||||
vertSlider.value = currentVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -283,15 +261,4 @@ DankOSD {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOsdShown: {
|
||||
if (player && contentLoader.item && contentLoader.item.item) {
|
||||
if (!useVertical) {
|
||||
const slider = contentLoader.item.item.children[0].children[1];
|
||||
if (slider && slider.value !== undefined) {
|
||||
slider.value = currentVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,13 @@ DankOSD {
|
||||
id: root
|
||||
|
||||
readonly property bool useVertical: isVerticalLayout
|
||||
property int _displayVolume: 0
|
||||
|
||||
function _syncVolume() {
|
||||
if (!AudioService.sink?.audio)
|
||||
return;
|
||||
_displayVolume = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
|
||||
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
|
||||
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
|
||||
@@ -14,18 +21,17 @@ DankOSD {
|
||||
enableMouseInteraction: true
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
|
||||
target: AudioService.sink?.audio ?? null
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (SettingsData.osdVolumeEnabled) {
|
||||
root._syncVolume();
|
||||
if (SettingsData.osdVolumeEnabled)
|
||||
root.show();
|
||||
}
|
||||
}
|
||||
|
||||
function onMutedChanged() {
|
||||
if (SettingsData.osdVolumeEnabled) {
|
||||
if (SettingsData.osdVolumeEnabled)
|
||||
root.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +39,9 @@ DankOSD {
|
||||
target: AudioService
|
||||
|
||||
function onSinkChanged() {
|
||||
if (root.shouldBeVisible && SettingsData.osdVolumeEnabled) {
|
||||
root._syncVolume();
|
||||
if (root.shouldBeVisible && SettingsData.osdVolumeEnabled)
|
||||
root.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +70,7 @@ DankOSD {
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted ? "volume_off" : "volume_up"
|
||||
name: AudioService.sink?.audio?.muted ? "volume_off" : "volume_up"
|
||||
size: Theme.iconSize
|
||||
color: muteButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
@@ -75,60 +81,45 @@ DankOSD {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
AudioService.toggleMute();
|
||||
}
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || volumeSlider.containsMouse);
|
||||
}
|
||||
onClicked: AudioService.toggleMute()
|
||||
onContainsMouseChanged: setChildHovered(containsMouse || volumeSlider.containsMouse)
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: volumeSlider
|
||||
|
||||
readonly property real actualVolumePercent: AudioService.sink && AudioService.sink.audio ? Math.round(AudioService.sink.audio.volume * 100) : 0
|
||||
readonly property real displayPercent: actualVolumePercent
|
||||
|
||||
width: parent.width - Theme.iconSize - parent.gap * 3
|
||||
height: 40
|
||||
x: parent.gap * 2 + Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
minimum: 0
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
enabled: AudioService.sink?.audio
|
||||
showValue: true
|
||||
unit: "%"
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
valueOverride: displayPercent
|
||||
valueOverride: root._displayVolume
|
||||
alwaysShowValue: SettingsData.osdAlwaysShowValue
|
||||
|
||||
Component.onCompleted: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
root._syncVolume();
|
||||
value = root._displayVolume;
|
||||
}
|
||||
|
||||
onSliderValueChanged: newValue => {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.sink.audio.volume = newValue / 100;
|
||||
resetHideTimer();
|
||||
}
|
||||
if (!AudioService.sink?.audio)
|
||||
return;
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.sink.audio.volume = newValue / 100;
|
||||
resetHideTimer();
|
||||
}
|
||||
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || muteButton.containsMouse);
|
||||
}
|
||||
onContainsMouseChanged: setChildHovered(containsMouse || muteButton.containsMouse)
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (volumeSlider && !volumeSlider.pressed) {
|
||||
volumeSlider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
Binding on value {
|
||||
value: root._displayVolume
|
||||
when: !volumeSlider.pressed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,7 +142,7 @@ DankOSD {
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted ? "volume_off" : "volume_up"
|
||||
name: AudioService.sink?.audio?.muted ? "volume_off" : "volume_up"
|
||||
size: Theme.iconSize
|
||||
color: muteButtonVert.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
@@ -162,12 +153,8 @@ DankOSD {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
AudioService.toggleMute();
|
||||
}
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || vertSliderArea.containsMouse);
|
||||
}
|
||||
onClicked: AudioService.toggleMute()
|
||||
onContainsMouseChanged: setChildHovered(containsMouse || vertSliderArea.containsMouse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +166,7 @@ DankOSD {
|
||||
y: gap * 2 + Theme.iconSize
|
||||
|
||||
property bool dragging: false
|
||||
property int value: AudioService.sink && AudioService.sink.audio ? Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
property int value: root._displayVolume
|
||||
|
||||
Rectangle {
|
||||
id: vertTrack
|
||||
@@ -220,50 +207,35 @@ DankOSD {
|
||||
id: vertSliderArea
|
||||
anchors.fill: parent
|
||||
anchors.margins: -12
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
enabled: AudioService.sink?.audio
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || muteButtonVert.containsMouse);
|
||||
}
|
||||
onContainsMouseChanged: setChildHovered(containsMouse || muteButtonVert.containsMouse)
|
||||
|
||||
onPressed: mouse => {
|
||||
vertSlider.dragging = true;
|
||||
updateVolume(mouse);
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
vertSlider.dragging = false;
|
||||
}
|
||||
onReleased: vertSlider.dragging = false
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
if (pressed)
|
||||
updateVolume(mouse);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
updateVolume(mouse);
|
||||
}
|
||||
onClicked: mouse => updateVolume(mouse)
|
||||
|
||||
function updateVolume(mouse) {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
const maxVol = AudioService.sinkMaxVolume;
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const volume = Math.max(0, Math.min(maxVol, Math.round(ratio * maxVol)));
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.sink.audio.volume = volume / 100;
|
||||
resetHideTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
|
||||
|
||||
function onVolumeChanged() {
|
||||
vertSlider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
if (!AudioService.sink?.audio)
|
||||
return;
|
||||
const maxVol = AudioService.sinkMaxVolume;
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const volume = Math.max(0, Math.min(maxVol, Math.round(ratio * maxVol)));
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.sink.audio.volume = volume / 100;
|
||||
resetHideTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,15 +251,4 @@ DankOSD {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOsdShown: {
|
||||
if (AudioService.sink && AudioService.sink.audio && contentLoader.item && contentLoader.item.item) {
|
||||
if (!useVertical) {
|
||||
const slider = contentLoader.item.item.children[0].children[1];
|
||||
if (slider && slider.value !== undefined) {
|
||||
slider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,36 +18,41 @@ Column {
|
||||
property bool isInitialized: false
|
||||
|
||||
function loadValue() {
|
||||
const settings = findSettings()
|
||||
const settings = findSettings();
|
||||
if (settings && settings.pluginService) {
|
||||
const loadedValue = settings.loadValue(settingKey, defaultValue)
|
||||
value = loadedValue
|
||||
textField.text = loadedValue
|
||||
isInitialized = true
|
||||
const loadedValue = settings.loadValue(settingKey, defaultValue);
|
||||
if (textField.activeFocus && isInitialized)
|
||||
return;
|
||||
value = loadedValue;
|
||||
textField.text = loadedValue;
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(loadValue)
|
||||
Qt.callLater(loadValue);
|
||||
}
|
||||
|
||||
onValueChanged: {
|
||||
if (!isInitialized) return
|
||||
const settings = findSettings()
|
||||
if (settings) {
|
||||
settings.saveValue(settingKey, value)
|
||||
}
|
||||
function commit() {
|
||||
if (!isInitialized)
|
||||
return;
|
||||
if (textField.text === value)
|
||||
return;
|
||||
value = textField.text;
|
||||
const settings = findSettings();
|
||||
if (settings)
|
||||
settings.saveValue(settingKey, value);
|
||||
}
|
||||
|
||||
function findSettings() {
|
||||
let item = parent
|
||||
let item = parent;
|
||||
while (item) {
|
||||
if (item.saveValue !== undefined && item.loadValue !== undefined) {
|
||||
return item
|
||||
return item;
|
||||
}
|
||||
item = item.parent
|
||||
item = item.parent;
|
||||
}
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -70,16 +75,10 @@ Column {
|
||||
id: textField
|
||||
width: parent.width
|
||||
placeholderText: root.placeholder
|
||||
onTextEdited: {
|
||||
root.value = text
|
||||
}
|
||||
onEditingFinished: {
|
||||
root.value = text
|
||||
}
|
||||
onEditingFinished: root.commit()
|
||||
onActiveFocusChanged: {
|
||||
if (!activeFocus) {
|
||||
root.value = text
|
||||
}
|
||||
if (!activeFocus)
|
||||
root.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ DankPopout {
|
||||
open();
|
||||
}
|
||||
|
||||
popupWidth: 650
|
||||
popupHeight: 550
|
||||
popupWidth: Math.round(Theme.fontSizeMedium * 46)
|
||||
popupHeight: Math.round(Theme.fontSizeMedium * 39)
|
||||
triggerWidth: 55
|
||||
positioning: ""
|
||||
screen: triggerScreen
|
||||
@@ -151,11 +151,13 @@ DankPopout {
|
||||
|
||||
DankButtonGroup {
|
||||
id: processFilterGroup
|
||||
Layout.minimumWidth: implicitWidth + 8
|
||||
Layout.minimumWidth: implicitWidth
|
||||
model: [I18n.tr("All"), I18n.tr("User"), I18n.tr("System")]
|
||||
currentIndex: 0
|
||||
checkEnabled: false
|
||||
buttonHeight: Math.round(Theme.fontSizeMedium * 2.2)
|
||||
buttonHeight: Math.round(Theme.fontSizeSmall * 2.4)
|
||||
minButtonWidth: 0
|
||||
buttonPadding: Theme.spacingM
|
||||
textSize: Theme.fontSizeSmall
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
@@ -177,7 +179,8 @@ DankPopout {
|
||||
|
||||
DankTextField {
|
||||
id: searchField
|
||||
Layout.preferredWidth: Theme.fontSizeMedium * 14
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: Theme.fontSizeMedium * 8
|
||||
Layout.preferredHeight: Theme.fontSizeMedium * 2.5
|
||||
placeholderText: I18n.tr("Search...")
|
||||
leftIconName: "search"
|
||||
|
||||
@@ -1061,7 +1061,7 @@ Singleton {
|
||||
|
||||
function getHyprlandOutputIdentifier(output, outputName) {
|
||||
if (SettingsData.displayNameMode === "model" && output?.make && output?.model)
|
||||
return "desc:" + output.make + " " + output.model;
|
||||
return "desc:" + output.make + " " + output.model + " " + (output?.serial || "Unknown");
|
||||
return outputName;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,56 +30,6 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "swap_vert"
|
||||
title: I18n.tr("Position")
|
||||
settingKey: "dockPosition"
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: dockPositionButtonGroup.height
|
||||
|
||||
DankButtonGroup {
|
||||
id: dockPositionButtonGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
model: [I18n.tr("Top"), I18n.tr("Bottom"), I18n.tr("Left"), I18n.tr("Right")]
|
||||
currentIndex: {
|
||||
switch (SettingsData.dockPosition) {
|
||||
case SettingsData.Position.Top:
|
||||
return 0;
|
||||
case SettingsData.Position.Bottom:
|
||||
return 1;
|
||||
case SettingsData.Position.Left:
|
||||
return 2;
|
||||
case SettingsData.Position.Right:
|
||||
return 3;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
switch (index) {
|
||||
case 0:
|
||||
SettingsData.setDockPosition(SettingsData.Position.Top);
|
||||
break;
|
||||
case 1:
|
||||
SettingsData.setDockPosition(SettingsData.Position.Bottom);
|
||||
break;
|
||||
case 2:
|
||||
SettingsData.setDockPosition(SettingsData.Position.Left);
|
||||
break;
|
||||
case 3:
|
||||
SettingsData.setDockPosition(SettingsData.Position.Right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "dock_to_bottom"
|
||||
@@ -136,6 +86,56 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "swap_vert"
|
||||
title: I18n.tr("Position")
|
||||
settingKey: "dockPosition"
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: dockPositionButtonGroup.height
|
||||
|
||||
DankButtonGroup {
|
||||
id: dockPositionButtonGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
model: [I18n.tr("Top"), I18n.tr("Bottom"), I18n.tr("Left"), I18n.tr("Right")]
|
||||
currentIndex: {
|
||||
switch (SettingsData.dockPosition) {
|
||||
case SettingsData.Position.Top:
|
||||
return 0;
|
||||
case SettingsData.Position.Bottom:
|
||||
return 1;
|
||||
case SettingsData.Position.Left:
|
||||
return 2;
|
||||
case SettingsData.Position.Right:
|
||||
return 3;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
switch (index) {
|
||||
case 0:
|
||||
SettingsData.setDockPosition(SettingsData.Position.Top);
|
||||
break;
|
||||
case 1:
|
||||
SettingsData.setDockPosition(SettingsData.Position.Bottom);
|
||||
break;
|
||||
case 2:
|
||||
SettingsData.setDockPosition(SettingsData.Position.Left);
|
||||
break;
|
||||
case 3:
|
||||
SettingsData.setDockPosition(SettingsData.Position.Right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "apps"
|
||||
|
||||
@@ -375,8 +375,10 @@ FocusScope {
|
||||
if (!plugin || !PluginService.isPluginLoaded(pluginId))
|
||||
return;
|
||||
var isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"));
|
||||
if (isLauncher)
|
||||
if (isLauncher) {
|
||||
pluginsTab.isReloading = true;
|
||||
PluginService.reloadPlugin(pluginId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import qs.Modules.Settings.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
readonly property var timeoutOptions: [I18n.tr("Never"), I18n.tr("1 minute"), I18n.tr("2 minutes"), I18n.tr("3 minutes"), I18n.tr("5 minutes"), I18n.tr("10 minutes"), I18n.tr("15 minutes"), I18n.tr("20 minutes"), I18n.tr("30 minutes"), I18n.tr("1 hour"), I18n.tr("1 hour 30 minutes"), I18n.tr("2 hours"), I18n.tr("3 hours")]
|
||||
readonly property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
function getTimeoutIndex(timeout) {
|
||||
@@ -56,7 +56,7 @@ Item {
|
||||
id: powerCategory
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: BatteryService.batteryAvailable
|
||||
model: ["AC Power", "Battery"]
|
||||
model: [I18n.tr("AC Power"), I18n.tr("Battery")]
|
||||
currentIndex: 0
|
||||
selectionMode: "single"
|
||||
checkEnabled: false
|
||||
@@ -100,7 +100,7 @@ Item {
|
||||
id: fadeGracePeriodDropdown
|
||||
settingKey: "fadeToLockGracePeriod"
|
||||
tags: ["fade", "grace", "period", "timeout", "lock"]
|
||||
property var periodOptions: ["1 second", "2 seconds", "3 seconds", "4 seconds", "5 seconds", "10 seconds", "15 seconds", "20 seconds", "30 seconds"]
|
||||
property var periodOptions: [I18n.tr("1 second"), I18n.tr("2 seconds"), I18n.tr("3 seconds"), I18n.tr("4 seconds"), I18n.tr("5 seconds"), I18n.tr("10 seconds"), I18n.tr("15 seconds"), I18n.tr("20 seconds"), I18n.tr("30 seconds")]
|
||||
property var periodValues: [1, 2, 3, 4, 5, 10, 15, 20, 30]
|
||||
|
||||
text: I18n.tr("Lock fade grace period")
|
||||
@@ -111,7 +111,7 @@ Item {
|
||||
Component.onCompleted: {
|
||||
const currentPeriod = SettingsData.fadeToLockGracePeriod;
|
||||
const index = periodValues.indexOf(currentPeriod);
|
||||
currentValue = index >= 0 ? periodOptions[index] : "5 seconds";
|
||||
currentValue = index >= 0 ? periodOptions[index] : I18n.tr("5 seconds");
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
@@ -126,7 +126,7 @@ Item {
|
||||
id: fadeDpmsGracePeriodDropdown
|
||||
settingKey: "fadeToDpmsGracePeriod"
|
||||
tags: ["fade", "grace", "period", "timeout", "dpms", "monitor"]
|
||||
property var periodOptions: ["1 second", "2 seconds", "3 seconds", "4 seconds", "5 seconds", "10 seconds", "15 seconds", "20 seconds", "30 seconds"]
|
||||
property var periodOptions: [I18n.tr("1 second"), I18n.tr("2 seconds"), I18n.tr("3 seconds"), I18n.tr("4 seconds"), I18n.tr("5 seconds"), I18n.tr("10 seconds"), I18n.tr("15 seconds"), I18n.tr("20 seconds"), I18n.tr("30 seconds")]
|
||||
property var periodValues: [1, 2, 3, 4, 5, 10, 15, 20, 30]
|
||||
|
||||
text: I18n.tr("Monitor fade grace period")
|
||||
@@ -137,7 +137,7 @@ Item {
|
||||
Component.onCompleted: {
|
||||
const currentPeriod = SettingsData.fadeToDpmsGracePeriod;
|
||||
const index = periodValues.indexOf(currentPeriod);
|
||||
currentValue = index >= 0 ? periodOptions[index] : "5 seconds";
|
||||
currentValue = index >= 0 ? periodOptions[index] : I18n.tr("5 seconds");
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
@@ -308,7 +308,7 @@ Item {
|
||||
DankButtonGroup {
|
||||
id: suspendBehaviorSelector
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
model: ["Suspend", "Hibernate", "Suspend then Hibernate"]
|
||||
model: [I18n.tr("Suspend"), I18n.tr("Hibernate"), I18n.tr("Suspend then Hibernate")]
|
||||
selectionMode: "single"
|
||||
checkEnabled: false
|
||||
|
||||
@@ -375,13 +375,13 @@ Item {
|
||||
settingKey: "powerMenuDefaultAction"
|
||||
tags: ["power", "menu", "default", "action", "reboot", "logout", "shutdown"]
|
||||
text: I18n.tr("Default selected action")
|
||||
options: ["Reboot", "Log Out", "Power Off", "Lock", "Suspend", "Restart DMS", "Hibernate"]
|
||||
options: [I18n.tr("Reboot"), I18n.tr("Log Out"), I18n.tr("Power Off"), I18n.tr("Lock"), I18n.tr("Suspend"), I18n.tr("Restart DMS"), I18n.tr("Hibernate")]
|
||||
property var actionValues: ["reboot", "logout", "poweroff", "lock", "suspend", "restart", "hibernate"]
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentAction = SettingsData.powerMenuDefaultAction || "logout";
|
||||
const index = actionValues.indexOf(currentAction);
|
||||
currentValue = index >= 0 ? options[index] : "Log Out";
|
||||
currentValue = index >= 0 ? options[index] : I18n.tr("Log Out");
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
@@ -479,7 +479,7 @@ Item {
|
||||
id: holdDurationDropdown
|
||||
settingKey: "powerActionHoldDuration"
|
||||
tags: ["power", "hold", "duration", "confirm", "time"]
|
||||
property var durationOptions: ["250 ms", "500 ms", "750 ms", "1 second", "2 seconds", "3 seconds", "5 seconds", "10 seconds"]
|
||||
property var durationOptions: [I18n.tr("250 ms"), I18n.tr("500 ms"), I18n.tr("750 ms"), I18n.tr("1 second"), I18n.tr("2 seconds"), I18n.tr("3 seconds"), I18n.tr("5 seconds"), I18n.tr("10 seconds")]
|
||||
property var durationValues: [0.25, 0.5, 0.75, 1, 2, 3, 5, 10]
|
||||
|
||||
text: I18n.tr("Hold Duration")
|
||||
@@ -489,7 +489,7 @@ Item {
|
||||
Component.onCompleted: {
|
||||
const currentDuration = SettingsData.powerActionHoldDuration;
|
||||
const index = durationValues.indexOf(currentDuration);
|
||||
currentValue = index >= 0 ? durationOptions[index] : "500 ms";
|
||||
currentValue = index >= 0 ? durationOptions[index] : I18n.tr("500 ms");
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
|
||||
@@ -16,7 +16,7 @@ Item {
|
||||
property var cachedCursorThemes: SettingsData.availableCursorThemes
|
||||
property var cachedMatugenSchemes: Theme.availableMatugenSchemes.map(option => option.label)
|
||||
property var installedRegistryThemes: []
|
||||
property var templateDetection: ({})
|
||||
property var templateDetection: []
|
||||
|
||||
property var cursorIncludeStatus: ({
|
||||
"exists": false,
|
||||
@@ -106,9 +106,10 @@ Item {
|
||||
}
|
||||
|
||||
function isTemplateDetected(templateId) {
|
||||
if (!templateDetection || Object.keys(templateDetection).length === 0)
|
||||
if (!templateDetection || templateDetection.length === 0)
|
||||
return true;
|
||||
return templateDetection[templateId] !== false;
|
||||
var item = templateDetection.find(i => i.id === templateId);
|
||||
return !item || item.detected !== false;
|
||||
}
|
||||
|
||||
function getTemplateDescription(templateId, baseDescription) {
|
||||
@@ -145,30 +146,17 @@ Item {
|
||||
DMSService.listInstalledThemes();
|
||||
if (PopoutService.pendingThemeInstall)
|
||||
Qt.callLater(() => showThemeBrowser());
|
||||
templateCheckProcess.running = true;
|
||||
Proc.runCommand("template-check", ["dms", "matugen", "check"], (output, exitCode) => {
|
||||
if (exitCode !== 0)
|
||||
return;
|
||||
try {
|
||||
themeColorsTab.templateDetection = JSON.parse(output.trim());
|
||||
} catch (e) {}
|
||||
});
|
||||
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl)
|
||||
checkCursorIncludeStatus();
|
||||
}
|
||||
|
||||
Process {
|
||||
id: templateCheckProcess
|
||||
command: ["dms", "matugen", "check"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const results = JSON.parse(text);
|
||||
const detection = {};
|
||||
for (const item of results) {
|
||||
detection[item.id] = item.detected;
|
||||
}
|
||||
themeColorsTab.templateDetection = detection;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DMSService
|
||||
function onInstalledThemesReceived(themes) {
|
||||
@@ -1045,11 +1033,11 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
model: [
|
||||
{
|
||||
"text": "Time",
|
||||
"text": I18n.tr("Time", "theme auto mode tab"),
|
||||
"icon": "access_time"
|
||||
},
|
||||
{
|
||||
"text": "Location",
|
||||
"text": I18n.tr("Location", "theme auto mode tab"),
|
||||
"icon": "place"
|
||||
}
|
||||
]
|
||||
@@ -2340,7 +2328,7 @@ Item {
|
||||
tags: ["matugen", "neovim", "terminal", "template"]
|
||||
settingKey: "matugenTemplateNeovim"
|
||||
text: "neovim"
|
||||
description: getTemplateDescription("nvim", "Requires lazy plugin manager")
|
||||
description: getTemplateDescription("nvim", I18n.tr("Requires lazy plugin manager", "neovim template description"))
|
||||
descriptionColor: getTemplateDescriptionColor("nvim")
|
||||
visible: SettingsData.runDmsMatugenTemplates
|
||||
checked: SettingsData.matugenTemplateNeovim
|
||||
@@ -2469,7 +2457,7 @@ Item {
|
||||
onValueChanged: value => {
|
||||
SettingsData.setIconTheme(value);
|
||||
if (Quickshell.env("QT_QPA_PLATFORMTHEME") != "gtk3" && Quickshell.env("QT_QPA_PLATFORMTHEME") != "qt6ct" && Quickshell.env("QT_QPA_PLATFORMTHEME_QT6") != "qt6ct") {
|
||||
ToastService.showError("Missing Environment Variables", "You need to set either:\nQT_QPA_PLATFORMTHEME=gtk3 OR\nQT_QPA_PLATFORMTHEME=qt6ct\nas environment variables, and then restart the shell.\n\nqt6ct requires qt6ct-kde to be installed.");
|
||||
ToastService.showError(I18n.tr("Missing Environment Variables", "qt theme env error title"), I18n.tr("You need to set either:\nQT_QPA_PLATFORMTHEME=gtk3 OR\nQT_QPA_PLATFORMTHEME=qt6ct\nas environment variables, and then restart the shell.\n\nqt6ct requires qt6ct-kde to be installed.", "qt theme env error body"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ Item {
|
||||
if (!PopoutService.colorPickerModal)
|
||||
return;
|
||||
PopoutService.colorPickerModal.selectedColor = root.currentWallpaper.startsWith("#") ? root.currentWallpaper : Theme.primary;
|
||||
PopoutService.colorPickerModal.pickerTitle = "Choose Wallpaper Color";
|
||||
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Choose Wallpaper Color", "wallpaper color picker title");
|
||||
PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
SessionData.setMonitorWallpaper(selectedMonitorName, selectedColor);
|
||||
@@ -237,7 +237,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: root.currentWallpaper ? root.currentWallpaper.split('/').pop() : "No wallpaper selected"
|
||||
text: root.currentWallpaper ? root.currentWallpaper.split('/').pop() : I18n.tr("No wallpaper selected")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideMiddle
|
||||
@@ -507,7 +507,7 @@ Item {
|
||||
return;
|
||||
var lightWallpaper = SessionData.wallpaperPathLight;
|
||||
PopoutService.colorPickerModal.selectedColor = lightWallpaper.startsWith("#") ? lightWallpaper : Theme.primary;
|
||||
PopoutService.colorPickerModal.pickerTitle = "Choose Light Mode Color";
|
||||
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Choose Light Mode Color", "light mode wallpaper color picker title");
|
||||
PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
|
||||
SessionData.wallpaperPathLight = selectedColor;
|
||||
SessionData.syncWallpaperForCurrentMode();
|
||||
@@ -558,7 +558,7 @@ Item {
|
||||
StyledText {
|
||||
text: {
|
||||
var lightWallpaper = SessionData.wallpaperPathLight;
|
||||
return lightWallpaper ? lightWallpaper.split('/').pop() : "Not set";
|
||||
return lightWallpaper ? lightWallpaper.split('/').pop() : I18n.tr("Not set", "wallpaper not set label");
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
@@ -691,7 +691,7 @@ Item {
|
||||
return;
|
||||
var darkWallpaper = SessionData.wallpaperPathDark;
|
||||
PopoutService.colorPickerModal.selectedColor = darkWallpaper.startsWith("#") ? darkWallpaper : Theme.primary;
|
||||
PopoutService.colorPickerModal.pickerTitle = "Choose Dark Mode Color";
|
||||
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Choose Dark Mode Color", "dark mode wallpaper color picker title");
|
||||
PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
|
||||
SessionData.wallpaperPathDark = selectedColor;
|
||||
SessionData.syncWallpaperForCurrentMode();
|
||||
@@ -742,7 +742,7 @@ Item {
|
||||
StyledText {
|
||||
text: {
|
||||
var darkWallpaper = SessionData.wallpaperPathDark;
|
||||
return darkWallpaper ? darkWallpaper.split('/').pop() : "Not set";
|
||||
return darkWallpaper ? darkWallpaper.split('/').pop() : I18n.tr("Not set", "wallpaper not set label");
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
@@ -813,7 +813,7 @@ Item {
|
||||
return SettingsData.getScreenDisplayName(screens[i]);
|
||||
}
|
||||
}
|
||||
return "No monitors";
|
||||
return I18n.tr("No monitors", "no monitors available label");
|
||||
}
|
||||
options: {
|
||||
var screenNames = [];
|
||||
@@ -844,7 +844,7 @@ Item {
|
||||
currentValue: {
|
||||
var screens = Quickshell.screens;
|
||||
if (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "") {
|
||||
return screens.length > 0 ? SettingsData.getScreenDisplayName(screens[0]) + " (Default)" : "No monitors";
|
||||
return screens.length > 0 ? SettingsData.getScreenDisplayName(screens[0]) + " " + I18n.tr("(Default)", "default monitor label suffix") : I18n.tr("No monitors", "no monitors available label");
|
||||
}
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
if (screens[i].name === SettingsData.matugenTargetMonitor) {
|
||||
@@ -859,14 +859,14 @@ Item {
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
var label = SettingsData.getScreenDisplayName(screens[i]);
|
||||
if (i === 0 && (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "")) {
|
||||
label += " (Default)";
|
||||
label += " " + I18n.tr("(Default)", "default monitor label suffix");
|
||||
}
|
||||
screenNames.push(label);
|
||||
}
|
||||
return screenNames;
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var cleanValue = value.replace(" (Default)", "");
|
||||
var cleanValue = value.replace(" " + I18n.tr("(Default)", "default monitor label suffix"), "");
|
||||
var screens = Quickshell.screens;
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
if (SettingsData.getScreenDisplayName(screens[i]) === cleanValue) {
|
||||
@@ -941,11 +941,11 @@ Item {
|
||||
height: 45
|
||||
model: [
|
||||
{
|
||||
"text": "Interval",
|
||||
"text": I18n.tr("Interval", "wallpaper cycling mode tab"),
|
||||
"icon": "schedule"
|
||||
},
|
||||
{
|
||||
"text": "Time",
|
||||
"text": I18n.tr("Time", "wallpaper cycling mode tab"),
|
||||
"icon": "access_time"
|
||||
}
|
||||
]
|
||||
@@ -981,7 +981,7 @@ Item {
|
||||
|
||||
SettingsDropdownRow {
|
||||
id: intervalDropdown
|
||||
property var intervalOptions: ["5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "35 seconds", "40 seconds", "45 seconds", "50 seconds", "55 seconds", "1 minute", "5 minutes", "15 minutes", "30 minutes", "1 hour", "1.5 hours", "2 hours", "3 hours", "4 hours", "6 hours", "8 hours", "12 hours"]
|
||||
property var intervalOptions: [I18n.tr("5 seconds", "wallpaper interval"), I18n.tr("10 seconds", "wallpaper interval"), I18n.tr("15 seconds", "wallpaper interval"), I18n.tr("20 seconds", "wallpaper interval"), I18n.tr("25 seconds", "wallpaper interval"), I18n.tr("30 seconds", "wallpaper interval"), I18n.tr("35 seconds", "wallpaper interval"), I18n.tr("40 seconds", "wallpaper interval"), I18n.tr("45 seconds", "wallpaper interval"), I18n.tr("50 seconds", "wallpaper interval"), I18n.tr("55 seconds", "wallpaper interval"), I18n.tr("1 minute", "wallpaper interval"), I18n.tr("5 minutes", "wallpaper interval"), I18n.tr("15 minutes", "wallpaper interval"), I18n.tr("30 minutes", "wallpaper interval"), I18n.tr("1 hour", "wallpaper interval"), I18n.tr("1 hour 30 minutes", "wallpaper interval"), I18n.tr("2 hours", "wallpaper interval"), I18n.tr("3 hours", "wallpaper interval"), I18n.tr("4 hours", "wallpaper interval"), I18n.tr("6 hours", "wallpaper interval"), I18n.tr("8 hours", "wallpaper interval"), I18n.tr("12 hours", "wallpaper interval")]
|
||||
|
||||
property var intervalValues: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200]
|
||||
tab: "wallpaper"
|
||||
@@ -1005,7 +1005,7 @@ Item {
|
||||
currentSeconds = SessionData.wallpaperCyclingInterval;
|
||||
}
|
||||
const index = intervalValues.indexOf(currentSeconds);
|
||||
return index >= 0 ? intervalOptions[index] : "5 minutes";
|
||||
return index >= 0 ? intervalOptions[index] : I18n.tr("5 minutes", "wallpaper interval");
|
||||
}
|
||||
onValueChanged: value => {
|
||||
const index = intervalOptions.indexOf(value);
|
||||
@@ -1029,7 +1029,7 @@ Item {
|
||||
currentSeconds = SessionData.wallpaperCyclingInterval;
|
||||
}
|
||||
const index = intervalDropdown.intervalValues.indexOf(currentSeconds);
|
||||
intervalDropdown.currentValue = index >= 0 ? intervalDropdown.intervalOptions[index] : "5 minutes";
|
||||
intervalDropdown.currentValue = index >= 0 ? intervalDropdown.intervalOptions[index] : I18n.tr("5 minutes", "wallpaper interval");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,7 +813,7 @@ Item {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80
|
||||
width: resetContentRow.implicitWidth + Theme.spacingM * 2
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: resetArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariant
|
||||
@@ -821,6 +821,7 @@ Item {
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
id: resetContentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ Variants {
|
||||
Image {
|
||||
id: nextWallpaper
|
||||
anchors.fill: parent
|
||||
visible: true
|
||||
visible: source !== ""
|
||||
opacity: 0
|
||||
layer.enabled: false
|
||||
asynchronous: true
|
||||
@@ -512,14 +512,18 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
source: effectLoader.active ? effectLoader.item : currentWallpaper
|
||||
visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
|
||||
blurEnabled: true
|
||||
blur: 0.8
|
||||
blurMax: 75
|
||||
autoPaddingEnabled: false
|
||||
active: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
|
||||
|
||||
sourceComponent: MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: effectLoader.active ? effectLoader.item : currentWallpaper
|
||||
blurEnabled: true
|
||||
blur: 0.8
|
||||
blurMax: 75
|
||||
autoPaddingEnabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string currentVersion: "1.4"
|
||||
readonly property bool changelogEnabled: false
|
||||
readonly property bool changelogEnabled: true
|
||||
|
||||
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell"
|
||||
readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion
|
||||
|
||||
@@ -288,9 +288,9 @@ Singleton {
|
||||
const cpu = data.cpu;
|
||||
cpuSampleCount++;
|
||||
|
||||
cpuUsage = cpu.usage || 0;
|
||||
cpuFrequency = cpu.frequency || 0;
|
||||
cpuTemperature = cpu.temperature || 0;
|
||||
cpuUsage = Math.round((cpu.usage || 0) * 10) / 10;
|
||||
cpuFrequency = Math.round(cpu.frequency || 0);
|
||||
cpuTemperature = Math.round(cpu.temperature || 0);
|
||||
cpuCores = cpu.count || 1;
|
||||
cpuModel = cpu.model || "";
|
||||
perCoreCpuUsage = cpu.coreUsage || [];
|
||||
@@ -308,11 +308,12 @@ Singleton {
|
||||
const freeKB = mem.free || 0;
|
||||
const usedKB = mem.used !== undefined ? mem.used : (totalKB - availableKB);
|
||||
|
||||
totalMemoryMB = totalKB / 1024;
|
||||
availableMemoryMB = availableKB / 1024;
|
||||
freeMemoryMB = freeKB / 1024;
|
||||
usedMemoryMB = usedKB / 1024;
|
||||
memoryUsage = mem.usedPercent !== undefined ? mem.usedPercent : (totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0);
|
||||
totalMemoryMB = Math.round(totalKB / 1024);
|
||||
availableMemoryMB = Math.round(availableKB / 1024);
|
||||
freeMemoryMB = Math.round(freeKB / 1024);
|
||||
usedMemoryMB = Math.round(usedKB / 1024);
|
||||
const rawMemUsage = mem.usedPercent !== undefined ? mem.usedPercent : (totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0);
|
||||
memoryUsage = Math.round(rawMemUsage * 10) / 10;
|
||||
|
||||
totalMemoryKB = totalKB;
|
||||
usedMemoryKB = usedKB;
|
||||
|
||||
@@ -55,7 +55,7 @@ Singleton {
|
||||
|
||||
function getOutputIdentifier(output, outputName) {
|
||||
if (SettingsData.displayNameMode === "model" && output.make && output.model)
|
||||
return "desc:" + output.make + " " + output.model;
|
||||
return "desc:" + output.make + " " + output.model + " " + (output.serial || "Unknown");
|
||||
return outputName;
|
||||
}
|
||||
|
||||
|
||||
@@ -963,48 +963,34 @@ Singleton {
|
||||
return enrichedToplevels;
|
||||
}
|
||||
|
||||
function filterCurrentWorkspace(toplevels, screenName) {
|
||||
let currentWorkspaceId = null;
|
||||
|
||||
for (var i = 0; i < allWorkspaces.length; i++) {
|
||||
const ws = allWorkspaces[i];
|
||||
if (ws.output === screenName && ws.is_active) {
|
||||
currentWorkspaceId = ws.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentWorkspaceId === null)
|
||||
return toplevels;
|
||||
|
||||
const workspaceWindows = windows.filter(niriWindow => niriWindow.workspace_id === currentWorkspaceId);
|
||||
function _matchAndEnrichToplevels(toplevels, niriWindows) {
|
||||
const usedToplevels = new Set();
|
||||
const result = [];
|
||||
|
||||
for (const niriWindow of workspaceWindows) {
|
||||
for (const niriWindow of niriWindows) {
|
||||
let bestMatch = null;
|
||||
let bestScore = -1;
|
||||
|
||||
for (const toplevel of toplevels) {
|
||||
if (usedToplevels.has(toplevel))
|
||||
continue;
|
||||
if (toplevel.appId === niriWindow.app_id) {
|
||||
let score = 1;
|
||||
if (toplevel.appId !== niriWindow.app_id)
|
||||
continue;
|
||||
|
||||
if (niriWindow.title && toplevel.title) {
|
||||
if (toplevel.title === niriWindow.title) {
|
||||
score = 3;
|
||||
} else if (toplevel.title.includes(niriWindow.title) || niriWindow.title.includes(toplevel.title)) {
|
||||
score = 2;
|
||||
}
|
||||
let score = 1;
|
||||
if (niriWindow.title && toplevel.title) {
|
||||
if (toplevel.title === niriWindow.title) {
|
||||
score = 3;
|
||||
} else if (toplevel.title.includes(niriWindow.title) || niriWindow.title.includes(toplevel.title)) {
|
||||
score = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMatch = toplevel;
|
||||
if (score === 3)
|
||||
break;
|
||||
}
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMatch = toplevel;
|
||||
if (score === 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,17 +1011,15 @@ Singleton {
|
||||
return NiriService.focusWindow(niriWindow.id);
|
||||
},
|
||||
"close": function () {
|
||||
if (bestMatch.close) {
|
||||
if (bestMatch.close)
|
||||
return bestMatch.close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
for (let prop in bestMatch) {
|
||||
if (!(prop in enrichedToplevel)) {
|
||||
if (!(prop in enrichedToplevel))
|
||||
enrichedToplevel[prop] = bestMatch[prop];
|
||||
}
|
||||
}
|
||||
|
||||
result.push(enrichedToplevel);
|
||||
@@ -1044,6 +1028,26 @@ Singleton {
|
||||
return result;
|
||||
}
|
||||
|
||||
function filterCurrentWorkspace(toplevels, screenName) {
|
||||
let currentWorkspaceId = null;
|
||||
|
||||
for (var i = 0; i < allWorkspaces.length; i++) {
|
||||
const ws = allWorkspaces[i];
|
||||
if (ws.output === screenName && ws.is_active) {
|
||||
currentWorkspaceId = ws.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentWorkspaceId === null)
|
||||
return toplevels;
|
||||
|
||||
if (toplevels.length > 0 && toplevels[0].niriWorkspaceId !== undefined)
|
||||
return toplevels.filter(t => t.niriWorkspaceId === currentWorkspaceId);
|
||||
|
||||
return _matchAndEnrichToplevels(toplevels, windows.filter(nw => nw.workspace_id === currentWorkspaceId));
|
||||
}
|
||||
|
||||
function filterCurrentDisplay(toplevels, screenName) {
|
||||
if (!toplevels || toplevels.length === 0 || !screenName)
|
||||
return toplevels;
|
||||
@@ -1058,71 +1062,10 @@ Singleton {
|
||||
if (outputWorkspaceIds.size === 0)
|
||||
return toplevels;
|
||||
|
||||
const displayWindows = windows.filter(niriWindow => outputWorkspaceIds.has(niriWindow.workspace_id));
|
||||
const usedToplevels = new Set();
|
||||
const result = [];
|
||||
if (toplevels.length > 0 && toplevels[0].niriWorkspaceId !== undefined)
|
||||
return toplevels.filter(t => outputWorkspaceIds.has(t.niriWorkspaceId));
|
||||
|
||||
for (const niriWindow of displayWindows) {
|
||||
let bestMatch = null;
|
||||
let bestScore = -1;
|
||||
|
||||
for (const toplevel of toplevels) {
|
||||
if (usedToplevels.has(toplevel))
|
||||
continue;
|
||||
if (toplevel.appId === niriWindow.app_id) {
|
||||
let score = 1;
|
||||
|
||||
if (niriWindow.title && toplevel.title) {
|
||||
if (toplevel.title === niriWindow.title) {
|
||||
score = 3;
|
||||
} else if (toplevel.title.includes(niriWindow.title) || niriWindow.title.includes(toplevel.title)) {
|
||||
score = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMatch = toplevel;
|
||||
if (score === 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bestMatch)
|
||||
continue;
|
||||
usedToplevels.add(bestMatch);
|
||||
|
||||
const workspace = workspaces[niriWindow.workspace_id];
|
||||
const isFocused = niriWindow.is_focused ?? (workspace && workspace.active_window_id === niriWindow.id) ?? false;
|
||||
|
||||
const enrichedToplevel = {
|
||||
"appId": bestMatch.appId,
|
||||
"title": bestMatch.title,
|
||||
"activated": isFocused,
|
||||
"niriWindowId": niriWindow.id,
|
||||
"niriWorkspaceId": niriWindow.workspace_id,
|
||||
"activate": function () {
|
||||
return NiriService.focusWindow(niriWindow.id);
|
||||
},
|
||||
"close": function () {
|
||||
if (bestMatch.close) {
|
||||
return bestMatch.close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
for (let prop in bestMatch) {
|
||||
if (!(prop in enrichedToplevel)) {
|
||||
enrichedToplevel[prop] = bestMatch[prop];
|
||||
}
|
||||
}
|
||||
|
||||
result.push(enrichedToplevel);
|
||||
}
|
||||
|
||||
return result;
|
||||
return _matchAndEnrichToplevels(toplevels, windows.filter(nw => outputWorkspaceIds.has(nw.workspace_id)));
|
||||
}
|
||||
|
||||
function generateNiriLayoutConfig() {
|
||||
|
||||
@@ -16,6 +16,7 @@ Singleton {
|
||||
property var registeredCards: ({})
|
||||
property var settingsIndex: []
|
||||
property bool indexLoaded: false
|
||||
property var _translatedCache: []
|
||||
|
||||
readonly property var conditionMap: ({
|
||||
"isNiri": () => CompositorService.isNiri,
|
||||
@@ -38,9 +39,11 @@ Singleton {
|
||||
try {
|
||||
root.settingsIndex = JSON.parse(text());
|
||||
root.indexLoaded = true;
|
||||
root._rebuildTranslationCache();
|
||||
} catch (e) {
|
||||
console.warn("SettingsSearchService: Failed to parse index:", e);
|
||||
root.settingsIndex = [];
|
||||
root._translatedCache = [];
|
||||
}
|
||||
}
|
||||
onLoadFailed: error => console.warn("SettingsSearchService: Failed to load index:", error)
|
||||
@@ -131,6 +134,27 @@ Singleton {
|
||||
};
|
||||
}
|
||||
|
||||
function _rebuildTranslationCache() {
|
||||
var cache = [];
|
||||
for (var i = 0; i < settingsIndex.length; i++) {
|
||||
var item = settingsIndex[i];
|
||||
var t = translateItem(item);
|
||||
cache.push({
|
||||
section: t.section,
|
||||
label: t.label,
|
||||
tabIndex: t.tabIndex,
|
||||
category: t.category,
|
||||
keywords: t.keywords,
|
||||
icon: t.icon,
|
||||
description: t.description,
|
||||
conditionKey: t.conditionKey,
|
||||
labelLower: t.label.toLowerCase(),
|
||||
categoryLower: t.category.toLowerCase()
|
||||
});
|
||||
}
|
||||
_translatedCache = cache;
|
||||
}
|
||||
|
||||
function search(text) {
|
||||
query = text;
|
||||
if (!text) {
|
||||
@@ -138,18 +162,19 @@ Singleton {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryLower = text.toLowerCase().trim();
|
||||
const queryWords = queryLower.split(/\s+/).filter(w => w.length > 0);
|
||||
const scored = [];
|
||||
var queryLower = text.toLowerCase().trim();
|
||||
var queryWords = queryLower.split(/\s+/).filter(w => w.length > 0);
|
||||
var scored = [];
|
||||
var cache = _translatedCache;
|
||||
|
||||
for (const item of settingsIndex) {
|
||||
if (!checkCondition(item))
|
||||
for (var i = 0; i < cache.length; i++) {
|
||||
var entry = cache[i];
|
||||
if (!checkCondition(entry))
|
||||
continue;
|
||||
|
||||
const translated = translateItem(item);
|
||||
const labelLower = translated.label.toLowerCase();
|
||||
const categoryLower = translated.category.toLowerCase();
|
||||
let score = 0;
|
||||
var labelLower = entry.labelLower;
|
||||
var categoryLower = entry.categoryLower;
|
||||
var score = 0;
|
||||
|
||||
if (labelLower === queryLower) {
|
||||
score = 10000;
|
||||
@@ -162,24 +187,32 @@ Singleton {
|
||||
}
|
||||
|
||||
if (score === 0) {
|
||||
for (const keyword of item.keywords) {
|
||||
if (keyword.startsWith(queryLower)) {
|
||||
score = Math.max(score, 800);
|
||||
var keywords = entry.keywords;
|
||||
for (var k = 0; k < keywords.length; k++) {
|
||||
if (keywords[k].startsWith(queryLower)) {
|
||||
score = 800;
|
||||
break;
|
||||
}
|
||||
if (keyword.includes(queryLower)) {
|
||||
score = Math.max(score, 400);
|
||||
if (keywords[k].includes(queryLower) && score < 400) {
|
||||
score = 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (score === 0 && queryWords.length > 1) {
|
||||
let allMatch = true;
|
||||
for (const word of queryWords) {
|
||||
const inLabel = labelLower.includes(word);
|
||||
const inKeywords = item.keywords.some(k => k.includes(word));
|
||||
const inCategory = categoryLower.includes(word);
|
||||
if (!inLabel && !inKeywords && !inCategory) {
|
||||
var allMatch = true;
|
||||
for (var w = 0; w < queryWords.length; w++) {
|
||||
var word = queryWords[w];
|
||||
if (labelLower.includes(word))
|
||||
continue;
|
||||
var inKeywords = false;
|
||||
for (var k = 0; k < entry.keywords.length; k++) {
|
||||
if (entry.keywords[k].includes(word)) {
|
||||
inKeywords = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inKeywords && !categoryLower.includes(word)) {
|
||||
allMatch = false;
|
||||
break;
|
||||
}
|
||||
@@ -190,7 +223,7 @@ Singleton {
|
||||
|
||||
if (score > 0) {
|
||||
scored.push({
|
||||
item: translated,
|
||||
item: entry,
|
||||
score: score
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,36 +119,36 @@ Singleton {
|
||||
|
||||
function getWeatherCondition(code) {
|
||||
const conditions = {
|
||||
"0": "Clear",
|
||||
"1": "Clear",
|
||||
"2": "Partly cloudy",
|
||||
"3": "Overcast",
|
||||
"45": "Fog",
|
||||
"48": "Fog",
|
||||
"51": "Drizzle",
|
||||
"53": "Drizzle",
|
||||
"55": "Drizzle",
|
||||
"56": "Freezing drizzle",
|
||||
"57": "Freezing drizzle",
|
||||
"61": "Light rain",
|
||||
"63": "Rain",
|
||||
"65": "Heavy rain",
|
||||
"66": "Light rain",
|
||||
"67": "Heavy rain",
|
||||
"71": "Light snow",
|
||||
"73": "Snow",
|
||||
"75": "Heavy snow",
|
||||
"77": "Snow",
|
||||
"80": "Light rain",
|
||||
"81": "Rain",
|
||||
"82": "Heavy rain",
|
||||
"85": "Light snow showers",
|
||||
"86": "Heavy snow showers",
|
||||
"95": "Thunderstorm",
|
||||
"96": "Thunderstorm with hail",
|
||||
"99": "Thunderstorm with hail"
|
||||
"0": I18n.tr("Clear Sky"),
|
||||
"1": I18n.tr("Clear Sky"),
|
||||
"2": I18n.tr("Partly Cloudy"),
|
||||
"3": I18n.tr("Overcast"),
|
||||
"45": I18n.tr("Fog"),
|
||||
"48": I18n.tr("Fog"),
|
||||
"51": I18n.tr("Drizzle"),
|
||||
"53": I18n.tr("Drizzle"),
|
||||
"55": I18n.tr("Drizzle"),
|
||||
"56": I18n.tr("Freezing Drizzle"),
|
||||
"57": I18n.tr("Freezing Drizzle"),
|
||||
"61": I18n.tr("Light Rain"),
|
||||
"63": I18n.tr("Rain"),
|
||||
"65": I18n.tr("Heavy Rain"),
|
||||
"66": I18n.tr("Light Rain"),
|
||||
"67": I18n.tr("Heavy Rain"),
|
||||
"71": I18n.tr("Light Snow"),
|
||||
"73": I18n.tr("Snow"),
|
||||
"75": I18n.tr("Heavy Snow"),
|
||||
"77": I18n.tr("Snow"),
|
||||
"80": I18n.tr("Light Rain"),
|
||||
"81": I18n.tr("Rain"),
|
||||
"82": I18n.tr("Heavy Rain"),
|
||||
"85": I18n.tr("Light Snow Showers"),
|
||||
"86": I18n.tr("Heavy Snow Showers"),
|
||||
"95": I18n.tr("Thunderstorm"),
|
||||
"96": I18n.tr("Thunderstorm with Hail"),
|
||||
"99": I18n.tr("Thunderstorm with Hail")
|
||||
};
|
||||
return conditions[String(code)] || "Unknown";
|
||||
return conditions[String(code)] || I18n.tr("Unknown");
|
||||
}
|
||||
|
||||
property var moonPhaseNames: ["moon_new", "moon_waxing_crescent", "moon_first_quarter", "moon_waxing_gibbous", "moon_full", "moon_waning_gibbous", "moon_last_quarter", "moon_waning_crescent"]
|
||||
@@ -647,8 +647,8 @@ Singleton {
|
||||
const address = data.address || {};
|
||||
|
||||
root.location = {
|
||||
city: address.hamlet || address.city || address.town || address.village || "Unknown",
|
||||
country: address.country || "Unknown",
|
||||
city: address.hamlet || address.city || address.town || address.village || I18n.tr("Unknown"),
|
||||
country: address.country || I18n.tr("Unknown"),
|
||||
latitude: parseFloat(data.lat),
|
||||
longitude: parseFloat(data.lon)
|
||||
};
|
||||
@@ -807,8 +807,8 @@ Singleton {
|
||||
"tempF": Math.round(tempF),
|
||||
"feelsLike": Math.round(feelsLikeC),
|
||||
"feelsLikeF": Math.round(feelsLikeF),
|
||||
"city": root.location?.city || "Unknown",
|
||||
"country": root.location?.country || "Unknown",
|
||||
"city": root.location?.city || I18n.tr("Unknown"),
|
||||
"country": root.location?.country || I18n.tr("Unknown"),
|
||||
"wCode": current.weather_code || 0,
|
||||
"humidity": Math.round(current.relative_humidity_2m || 0),
|
||||
"wind": Math.round(current.wind_speed_10m || 0),
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.5-beta
|
||||
v1.4.2
|
||||
|
||||
@@ -28,6 +28,10 @@ PanelWindow {
|
||||
function show() {
|
||||
if (SessionData.suppressOSD)
|
||||
return;
|
||||
if (shouldBeVisible) {
|
||||
hideTimer.restart();
|
||||
return;
|
||||
}
|
||||
OSDManager.showOSD(root);
|
||||
closeTimer.stop();
|
||||
shouldBeVisible = true;
|
||||
@@ -257,7 +261,7 @@ PanelWindow {
|
||||
property real shadowSpreadPx: 0
|
||||
property real shadowBaseAlpha: 0.60
|
||||
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
|
||||
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha * osdContainer.opacity))
|
||||
readonly property real effectiveShadowAlpha: shouldBeVisible ? Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha)) : 0
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
|
||||
@@ -109,6 +109,13 @@ Item {
|
||||
|
||||
readonly property bool useBackgroundWindow: !CompositorService.isHyprland || CompositorService.useHyprlandFocusGrab
|
||||
|
||||
function updateSurfacePosition() {
|
||||
if (useBackgroundWindow && shouldBeVisible) {
|
||||
_surfaceMarginLeft = alignedX - shadowBuffer;
|
||||
_surfaceW = alignedWidth + shadowBuffer * 2;
|
||||
}
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (!screen)
|
||||
return;
|
||||
@@ -469,6 +476,44 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: shadowSource
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: "black"
|
||||
visible: false
|
||||
opacity: contentWrapper.opacity
|
||||
scale: contentWrapper.scale
|
||||
x: contentWrapper.x
|
||||
y: contentWrapper.y
|
||||
|
||||
property real shadowBlurPx: 10
|
||||
property real shadowSpreadPx: 0
|
||||
property real shadowBaseAlpha: 0.60
|
||||
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
|
||||
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
|
||||
readonly property int blurMax: 64
|
||||
|
||||
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
|
||||
layer.smooth: false
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
id: shadowFx
|
||||
autoPaddingEnabled: true
|
||||
shadowEnabled: true
|
||||
blurEnabled: false
|
||||
maskEnabled: false
|
||||
shadowBlur: Math.max(0, Math.min(1, shadowSource.shadowBlurPx / shadowSource.blurMax))
|
||||
shadowScale: 1 + (2 * shadowSource.shadowSpreadPx) / Math.max(1, Math.min(shadowSource.width, shadowSource.height))
|
||||
shadowColor: {
|
||||
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
|
||||
return Theme.withAlpha(baseColor, shadowSource.effectiveShadowAlpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentWrapper
|
||||
anchors.centerIn: parent
|
||||
@@ -480,31 +525,10 @@ Item {
|
||||
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
|
||||
property real shadowBlurPx: 10
|
||||
property real shadowSpreadPx: 0
|
||||
property real shadowBaseAlpha: 0.60
|
||||
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
|
||||
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
|
||||
readonly property int blurMax: 64
|
||||
|
||||
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
|
||||
layer.enabled: contentWrapper.opacity < 1
|
||||
layer.smooth: false
|
||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
id: shadowFx
|
||||
autoPaddingEnabled: true
|
||||
shadowEnabled: true
|
||||
blurEnabled: false
|
||||
maskEnabled: false
|
||||
shadowBlur: Math.max(0, Math.min(1, contentWrapper.shadowBlurPx / contentWrapper.blurMax))
|
||||
shadowScale: 1 + (2 * contentWrapper.shadowSpreadPx) / Math.max(1, Math.min(contentWrapper.width, contentWrapper.height))
|
||||
shadowColor: {
|
||||
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
|
||||
return Theme.withAlpha(baseColor, contentWrapper.effectiveShadowAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: animationDuration
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[templates.dmsemacs]
|
||||
input_path = 'SHELL_DIR/matugen/templates/dank-emacs.el'
|
||||
output_path = "~/.emacs.d/themes/dank-emacs-theme.el"
|
||||
output_path = 'EMACS_DIR/themes/dank-emacs-theme.el'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -104,18 +104,30 @@
|
||||
"2 minutes": {
|
||||
"2 minutes": "2 minutos"
|
||||
},
|
||||
"2 seconds": {
|
||||
"2 seconds": ""
|
||||
},
|
||||
"20 minutes": {
|
||||
"20 minutes": ""
|
||||
},
|
||||
"24-Hour Format": {
|
||||
"24-Hour Format": "Formato de 24 horas"
|
||||
},
|
||||
"24-hour format": {
|
||||
"24-hour format": "Formato de 24 horas"
|
||||
},
|
||||
"250 ms": {
|
||||
"250 ms": ""
|
||||
},
|
||||
"270°": {
|
||||
"270°": "270º"
|
||||
},
|
||||
"3 days": {
|
||||
"3 days": "3 dias"
|
||||
},
|
||||
"3 minutes": {
|
||||
"3 minutes": ""
|
||||
},
|
||||
"3 seconds": {
|
||||
"3 seconds": "3 segundos"
|
||||
},
|
||||
@@ -128,15 +140,24 @@
|
||||
"3rd party": {
|
||||
"3rd party": "De terceros"
|
||||
},
|
||||
"4 seconds": {
|
||||
"4 seconds": ""
|
||||
},
|
||||
"5 minutes": {
|
||||
"5 minutes": "5 minutos"
|
||||
},
|
||||
"5 seconds": {
|
||||
"5 seconds": "5 segundos"
|
||||
},
|
||||
"500 ms": {
|
||||
"500 ms": ""
|
||||
},
|
||||
"7 days": {
|
||||
"7 days": "7 días"
|
||||
},
|
||||
"750 ms": {
|
||||
"750 ms": ""
|
||||
},
|
||||
"8 seconds": {
|
||||
"8 seconds": "8 segundos"
|
||||
},
|
||||
@@ -149,6 +170,9 @@
|
||||
"A file with this name already exists. Do you want to overwrite it?": {
|
||||
"A file with this name already exists. Do you want to overwrite it?": "Un archivo con este nombre ya existe. ¿Quieres reemplazarlo?"
|
||||
},
|
||||
"AC Power": {
|
||||
"AC Power": ""
|
||||
},
|
||||
"API": {
|
||||
"API": "API"
|
||||
},
|
||||
@@ -745,6 +769,9 @@
|
||||
"Caps Lock Indicator": {
|
||||
"Caps Lock Indicator": "Indicador de Bloq Mayús"
|
||||
},
|
||||
"Caps Lock is on": {
|
||||
"Caps Lock is on": ""
|
||||
},
|
||||
"Center Section": {
|
||||
"Center Section": "Sección central"
|
||||
},
|
||||
@@ -835,6 +862,9 @@
|
||||
"Clear History?": {
|
||||
"Clear History?": ""
|
||||
},
|
||||
"Clear Sky": {
|
||||
"Clear Sky": ""
|
||||
},
|
||||
"Clear all history when server starts": {
|
||||
"Clear all history when server starts": "Limpiar todo el historial cuando el servidor inicia"
|
||||
},
|
||||
@@ -1387,6 +1417,9 @@
|
||||
"Description": {
|
||||
"Description": "Descripción"
|
||||
},
|
||||
"Desktop": {
|
||||
"Desktop": ""
|
||||
},
|
||||
"Desktop Clock": {
|
||||
"Desktop Clock": "Reloj del escritorio"
|
||||
},
|
||||
@@ -1558,6 +1591,9 @@
|
||||
"Docs": {
|
||||
"Docs": "Docs"
|
||||
},
|
||||
"Documents": {
|
||||
"Documents": ""
|
||||
},
|
||||
"Domain (optional)": {
|
||||
"Domain (optional)": "Dominio (opcional)"
|
||||
},
|
||||
@@ -1570,6 +1606,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "Puera abierta"
|
||||
},
|
||||
"Downloads": {
|
||||
"Downloads": ""
|
||||
},
|
||||
"Drag to Reorder": {
|
||||
"Drag to Reorder": ""
|
||||
},
|
||||
@@ -1582,6 +1621,9 @@
|
||||
"Driver": {
|
||||
"Driver": "Controlador"
|
||||
},
|
||||
"Drizzle": {
|
||||
"Drizzle": ""
|
||||
},
|
||||
"Duplicate": {
|
||||
"Duplicate": "Duplicado"
|
||||
},
|
||||
@@ -2044,6 +2086,9 @@
|
||||
"Focused Window": {
|
||||
"Focused Window": "Ventana enfocada"
|
||||
},
|
||||
"Fog": {
|
||||
"Fog": ""
|
||||
},
|
||||
"Follow Monitor Focus": {
|
||||
"Follow Monitor Focus": ""
|
||||
},
|
||||
@@ -2107,6 +2152,9 @@
|
||||
"Free VRAM/memory when the launcher is closed. May cause a slight delay when reopening.": {
|
||||
"Free VRAM/memory when the launcher is closed. May cause a slight delay when reopening.": ""
|
||||
},
|
||||
"Freezing Drizzle": {
|
||||
"Freezing Drizzle": ""
|
||||
},
|
||||
"Frequency": {
|
||||
"Frequency": "Frecuencia"
|
||||
},
|
||||
@@ -2212,6 +2260,15 @@
|
||||
"Health": {
|
||||
"Health": "Salud"
|
||||
},
|
||||
"Heavy Rain": {
|
||||
"Heavy Rain": ""
|
||||
},
|
||||
"Heavy Snow": {
|
||||
"Heavy Snow": ""
|
||||
},
|
||||
"Heavy Snow Showers": {
|
||||
"Heavy Snow Showers": ""
|
||||
},
|
||||
"Height": {
|
||||
"Height": ""
|
||||
},
|
||||
@@ -2311,6 +2368,9 @@
|
||||
"Hold to confirm (%1s)": {
|
||||
"Hold to confirm (%1s)": "Mantener pulsado para confirmar (%1s)"
|
||||
},
|
||||
"Home": {
|
||||
"Home": ""
|
||||
},
|
||||
"Hot Corners": {
|
||||
"Hot Corners": "Esquinas calientes"
|
||||
},
|
||||
@@ -2758,6 +2818,15 @@
|
||||
"Light Mode": {
|
||||
"Light Mode": "Modo claro"
|
||||
},
|
||||
"Light Rain": {
|
||||
"Light Rain": ""
|
||||
},
|
||||
"Light Snow": {
|
||||
"Light Snow": ""
|
||||
},
|
||||
"Light Snow Showers": {
|
||||
"Light Snow Showers": ""
|
||||
},
|
||||
"Linear": {
|
||||
"Linear": "Lineal"
|
||||
},
|
||||
@@ -3103,6 +3172,9 @@
|
||||
"Moving to Paused": {
|
||||
"Moving to Paused": "Pausando"
|
||||
},
|
||||
"Music": {
|
||||
"Music": ""
|
||||
},
|
||||
"Mute popups for %1": {
|
||||
"Mute popups for %1": ""
|
||||
},
|
||||
@@ -3520,6 +3592,9 @@
|
||||
"Outputs Include Missing": {
|
||||
"Outputs Include Missing": "Los resultados incluyen"
|
||||
},
|
||||
"Overcast": {
|
||||
"Overcast": ""
|
||||
},
|
||||
"Overflow": {
|
||||
"Overflow": ""
|
||||
},
|
||||
@@ -3577,6 +3652,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "Vinculando..."
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": ""
|
||||
},
|
||||
"Passkey:": {
|
||||
"Passkey:": "Clave de acceso:"
|
||||
},
|
||||
@@ -3684,6 +3762,9 @@
|
||||
"Phone Connect unavailable status": {
|
||||
"Unavailable": ""
|
||||
},
|
||||
"Pictures": {
|
||||
"Pictures": ""
|
||||
},
|
||||
"Pin": {
|
||||
"Pin": "Fijar"
|
||||
},
|
||||
@@ -3909,6 +3990,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "Protocolo"
|
||||
},
|
||||
"Quick Access": {
|
||||
"Quick Access": ""
|
||||
},
|
||||
"Quick access to application launcher": {
|
||||
"Quick access to application launcher": "Acceso rápido al lanzador de aplicaciones"
|
||||
},
|
||||
@@ -3927,6 +4011,9 @@
|
||||
"Radius": {
|
||||
"Radius": ""
|
||||
},
|
||||
"Rain": {
|
||||
"Rain": ""
|
||||
},
|
||||
"Rain Chance": {
|
||||
"Rain Chance": "Lluvia"
|
||||
},
|
||||
@@ -4626,6 +4713,9 @@
|
||||
"Snap": {
|
||||
"Snap": ""
|
||||
},
|
||||
"Snow": {
|
||||
"Snow": ""
|
||||
},
|
||||
"Some plugins require a newer version of DMS:": {
|
||||
"Some plugins require a newer version of DMS:": "Algunos plugins requieren una version más nueva de DMS:"
|
||||
},
|
||||
@@ -4722,6 +4812,9 @@
|
||||
"Suspend system after": {
|
||||
"Suspend system after": "Suspender sistema después de"
|
||||
},
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": ""
|
||||
},
|
||||
"Swap": {
|
||||
"Swap": "Intercambio"
|
||||
},
|
||||
@@ -4860,6 +4953,12 @@
|
||||
"This will permanently remove this saved clipboard item. This action cannot be undone.": {
|
||||
"This will permanently remove this saved clipboard item. This action cannot be undone.": ""
|
||||
},
|
||||
"Thunderstorm": {
|
||||
"Thunderstorm": ""
|
||||
},
|
||||
"Thunderstorm with Hail": {
|
||||
"Thunderstorm with Hail": ""
|
||||
},
|
||||
"Tiled": {
|
||||
"Tiled": ""
|
||||
},
|
||||
@@ -5250,6 +5349,9 @@
|
||||
"Vibrant palette with playful saturation.": {
|
||||
"Vibrant palette with playful saturation.": "Paleta vibrante y juguetona."
|
||||
},
|
||||
"Videos": {
|
||||
"Videos": ""
|
||||
},
|
||||
"View Mode": {
|
||||
"View Mode": ""
|
||||
},
|
||||
@@ -5519,6 +5621,9 @@
|
||||
"border thickness": {
|
||||
"Thickness": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": ""
|
||||
},
|
||||
"browse themes button | theme browser header | theme browser window title": {
|
||||
"Browse Themes": "Busqueda de temas"
|
||||
},
|
||||
@@ -5562,6 +5667,9 @@
|
||||
"custom theme file hint": {
|
||||
"Click to select a custom theme JSON file": "Haz clic para seleccionar un archivo JSON de tema personalizado"
|
||||
},
|
||||
"dark mode wallpaper color picker title": {
|
||||
"Choose Dark Mode Color": ""
|
||||
},
|
||||
"dark mode wallpaper file browser title | light mode wallpaper file browser title | wallpaper file browser title": {
|
||||
"Select Wallpaper": "Seleccionar fondo de pantalla"
|
||||
},
|
||||
@@ -5580,6 +5688,9 @@
|
||||
"days": {
|
||||
"days": "dias"
|
||||
},
|
||||
"default monitor label suffix": {
|
||||
"(Default)": ""
|
||||
},
|
||||
"dgop not available": {
|
||||
"dgop not available": "dgop no disponible"
|
||||
},
|
||||
@@ -5845,6 +5956,9 @@
|
||||
"leave empty for default": {
|
||||
"leave empty for default": "deja vacio para valor por defecto"
|
||||
},
|
||||
"light mode wallpaper color picker title": {
|
||||
"Choose Light Mode Color": ""
|
||||
},
|
||||
"loading indicator": {
|
||||
"Loading...": "Cargando..."
|
||||
},
|
||||
@@ -5904,6 +6018,9 @@
|
||||
"nav": {
|
||||
"nav": ""
|
||||
},
|
||||
"neovim template description": {
|
||||
"Requires lazy plugin manager": ""
|
||||
},
|
||||
"network status": {
|
||||
"Connected": "",
|
||||
"Disabling WiFi...": "",
|
||||
@@ -5917,6 +6034,9 @@
|
||||
"no custom theme file status": {
|
||||
"No custom theme file": "Ningún archivo de tema personalizado"
|
||||
},
|
||||
"no monitors available label": {
|
||||
"No monitors": ""
|
||||
},
|
||||
"no registry themes installed hint": {
|
||||
"No themes installed. Browse themes to install from the registry.": "No hay temas instalados. Busca temas para instalar en el repositorio/tienda de complementos."
|
||||
},
|
||||
@@ -5997,6 +6117,24 @@
|
||||
"official": {
|
||||
"official": "oficial"
|
||||
},
|
||||
"on Hyprland": {
|
||||
"on Hyprland": ""
|
||||
},
|
||||
"on MangoWC": {
|
||||
"on MangoWC": ""
|
||||
},
|
||||
"on Miracle WM": {
|
||||
"on Miracle WM": ""
|
||||
},
|
||||
"on Niri": {
|
||||
"on Niri": ""
|
||||
},
|
||||
"on Scroll": {
|
||||
"on Scroll": ""
|
||||
},
|
||||
"on Sway": {
|
||||
"on Sway": ""
|
||||
},
|
||||
"open": {
|
||||
"open": ""
|
||||
},
|
||||
@@ -6044,6 +6182,12 @@
|
||||
"profile image file browser title": {
|
||||
"Select Profile Image": "Elegir foto de perfil"
|
||||
},
|
||||
"qt theme env error body": {
|
||||
"You need to set either:\nQT_QPA_PLATFORMTHEME=gtk3 OR\nQT_QPA_PLATFORMTHEME=qt6ct\nas environment variables, and then restart the shell.\n\nqt6ct requires qt6ct-kde to be installed.": ""
|
||||
},
|
||||
"qt theme env error title": {
|
||||
"Missing Environment Variables": ""
|
||||
},
|
||||
"read-only settings warning for NixOS home-manager users": {
|
||||
"Settings are read-only. Changes will not persist.": "Los ajustes son de solo lectura. Los cambios no persistirán."
|
||||
},
|
||||
@@ -6101,6 +6245,12 @@
|
||||
"Kernel": "",
|
||||
"Load Average": ""
|
||||
},
|
||||
"theme auto mode tab": {
|
||||
"Location": ""
|
||||
},
|
||||
"theme auto mode tab | wallpaper cycling mode tab": {
|
||||
"Time": ""
|
||||
},
|
||||
"theme browser description": {
|
||||
"Install color themes from the DMS theme registry": "Instalar tema de colores desde el repositorio de temas de DMS"
|
||||
},
|
||||
@@ -6141,6 +6291,9 @@
|
||||
"unknown author": {
|
||||
"Unknown": "Desconocido"
|
||||
},
|
||||
"up": {
|
||||
"up": ""
|
||||
},
|
||||
"update dms for NM integration.": {
|
||||
"update dms for NM integration.": "Actualizar dms para integración con NM."
|
||||
},
|
||||
@@ -6153,6 +6306,12 @@
|
||||
"version requirement": {
|
||||
"Requires %1": ""
|
||||
},
|
||||
"wallpaper color picker title": {
|
||||
"Choose Wallpaper Color": ""
|
||||
},
|
||||
"wallpaper cycling mode tab": {
|
||||
"Interval": ""
|
||||
},
|
||||
"wallpaper directory file browser title": {
|
||||
"Select Wallpaper Directory": "Elegir carpeta de fondos de pantalla"
|
||||
},
|
||||
@@ -6171,6 +6330,34 @@
|
||||
"Tile H": "",
|
||||
"Tile V": ""
|
||||
},
|
||||
"wallpaper interval": {
|
||||
"1 hour": "",
|
||||
"1 hour 30 minutes": "",
|
||||
"1 minute": "",
|
||||
"10 seconds": "",
|
||||
"12 hours": "",
|
||||
"15 minutes": "",
|
||||
"15 seconds": "",
|
||||
"2 hours": "",
|
||||
"20 seconds": "",
|
||||
"25 seconds": "",
|
||||
"3 hours": "",
|
||||
"30 minutes": "",
|
||||
"30 seconds": "",
|
||||
"35 seconds": "",
|
||||
"4 hours": "",
|
||||
"40 seconds": "",
|
||||
"45 seconds": "",
|
||||
"5 minutes": "",
|
||||
"5 seconds": "",
|
||||
"50 seconds": "",
|
||||
"55 seconds": "",
|
||||
"6 hours": "",
|
||||
"8 hours": ""
|
||||
},
|
||||
"wallpaper not set label": {
|
||||
"Not set": ""
|
||||
},
|
||||
"wallpaper processing error": {
|
||||
"Wallpaper processing failed": "Fallo en el procesamiento del fondo de pantalla"
|
||||
},
|
||||
@@ -6245,5 +6432,8 @@
|
||||
},
|
||||
"• yyyy - Year (2024)": {
|
||||
"• yyyy - Year (2024)": "yyyy - Año (2024)"
|
||||
},
|
||||
"↑/↓: Nav • Space: Expand • Enter: Action/Expand • E: Text": {
|
||||
"↑/↓: Nav • Space: Expand • Enter: Action/Expand • E: Text": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,18 +104,30 @@
|
||||
"2 minutes": {
|
||||
"2 minutes": "۲ دقیقه"
|
||||
},
|
||||
"2 seconds": {
|
||||
"2 seconds": "۲ ثانیه"
|
||||
},
|
||||
"20 minutes": {
|
||||
"20 minutes": "۲۰ دقیقه"
|
||||
},
|
||||
"24-Hour Format": {
|
||||
"24-Hour Format": "قالب ۲۴ ساعته"
|
||||
},
|
||||
"24-hour format": {
|
||||
"24-hour format": "قالب ۲۴ ساعته"
|
||||
},
|
||||
"250 ms": {
|
||||
"250 ms": "۲۵۰ ms"
|
||||
},
|
||||
"270°": {
|
||||
"270°": "۲۷۰°"
|
||||
},
|
||||
"3 days": {
|
||||
"3 days": "۳ روز"
|
||||
},
|
||||
"3 minutes": {
|
||||
"3 minutes": "۳ دقیقه"
|
||||
},
|
||||
"3 seconds": {
|
||||
"3 seconds": "۳ ثانیه"
|
||||
},
|
||||
@@ -128,15 +140,24 @@
|
||||
"3rd party": {
|
||||
"3rd party": "شخص ثالث"
|
||||
},
|
||||
"4 seconds": {
|
||||
"4 seconds": "۴ ثانیه"
|
||||
},
|
||||
"5 minutes": {
|
||||
"5 minutes": "۵ دقیقه"
|
||||
},
|
||||
"5 seconds": {
|
||||
"5 seconds": "۵ ثانیه"
|
||||
},
|
||||
"500 ms": {
|
||||
"500 ms": "۵۰۰ ms"
|
||||
},
|
||||
"7 days": {
|
||||
"7 days": "۷ روز"
|
||||
},
|
||||
"750 ms": {
|
||||
"750 ms": "۷۵۰ ms"
|
||||
},
|
||||
"8 seconds": {
|
||||
"8 seconds": "۸ ثانیه"
|
||||
},
|
||||
@@ -149,6 +170,9 @@
|
||||
"A file with this name already exists. Do you want to overwrite it?": {
|
||||
"A file with this name already exists. Do you want to overwrite it?": "فایلی با این نام از قبل وجود دارد. آیا میخواهید آن را بازنویسی کنید؟"
|
||||
},
|
||||
"AC Power": {
|
||||
"AC Power": "برق AC"
|
||||
},
|
||||
"API": {
|
||||
"API": "API"
|
||||
},
|
||||
@@ -357,7 +381,7 @@
|
||||
"Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.": "برنامهها با نام نمایشی، آیکون و گزینههای اجرای سفارشی. روی یک برنامه راستکلیک کرده و «ویرایش برنامه» را برای سفارشیسازی انتخاب کنید."
|
||||
},
|
||||
"Apps with notification popups muted. Unmute or delete to remove.": {
|
||||
"Apps with notification popups muted. Unmute or delete to remove.": ""
|
||||
"Apps with notification popups muted. Unmute or delete to remove.": "برنامههایی که پاپآپ اعلان آنها بیصدا شده است. برای حذف، بیصدا را بردارید یا برنامه را حذف کنید."
|
||||
},
|
||||
"Arrange displays and configure resolution, refresh rate, and VRR": {
|
||||
"Arrange displays and configure resolution, refresh rate, and VRR": "چیدمان نمایشگرها و پیکربندی وضوح، نرخ تازهسازی و VRR"
|
||||
@@ -500,7 +524,7 @@
|
||||
"Automatically cycle through wallpapers in the same folder": "تصاویر پسزمینه را در همان پوشه به صورت خودکار تغییر بده"
|
||||
},
|
||||
"Automatically delete entries older than this": {
|
||||
"Automatically delete entries older than this": "ورودیهای قدیمیتر از این را به صورت خودکار پاک کن"
|
||||
"Automatically delete entries older than this": "مدخلهای قدیمیتر از این را به صورت خودکار پاک کن"
|
||||
},
|
||||
"Automatically detect location based on IP address": {
|
||||
"Automatically detect location based on IP address": "تشخیص خودکار موقعیت مکانی بر اساس آدرس IP"
|
||||
@@ -563,7 +587,7 @@
|
||||
"Bar Transparency": "شفافیت نوار"
|
||||
},
|
||||
"Base duration for animations (drag to use Custom)": {
|
||||
"Base duration for animations (drag to use Custom)": ""
|
||||
"Base duration for animations (drag to use Custom)": "مدت زمان پایه برای انیمیشنها (برای استفاده سفارشی بکشید)"
|
||||
},
|
||||
"Battery": {
|
||||
"Battery": "باتری"
|
||||
@@ -593,10 +617,10 @@
|
||||
"Bit Depth": "عمق بیت"
|
||||
},
|
||||
"Block Out": {
|
||||
"Block Out": ""
|
||||
"Block Out": "مسدود کردن خروج"
|
||||
},
|
||||
"Block Out From": {
|
||||
"Block Out From": ""
|
||||
"Block Out From": "مسدود کردن خروج از"
|
||||
},
|
||||
"Block notifications": {
|
||||
"Block notifications": "مسدودکردن اعلانها"
|
||||
@@ -641,7 +665,7 @@
|
||||
"Border Thickness": "ضخامت حاشیه"
|
||||
},
|
||||
"Border with BG": {
|
||||
"Border with BG": ""
|
||||
"Border with BG": "حاشیه با پسزمینه"
|
||||
},
|
||||
"Bottom": {
|
||||
"Bottom": "پایین"
|
||||
@@ -713,7 +737,7 @@
|
||||
"CUPS not available": "CUPS در دسترس نیست"
|
||||
},
|
||||
"Calc": {
|
||||
"Calc": ""
|
||||
"Calc": "ماشین حساب"
|
||||
},
|
||||
"Calculator": {
|
||||
"Calculator": "ماشین حساب"
|
||||
@@ -745,6 +769,9 @@
|
||||
"Caps Lock Indicator": {
|
||||
"Caps Lock Indicator": "نشانگر Caps Lock"
|
||||
},
|
||||
"Caps Lock is on": {
|
||||
"Caps Lock is on": ""
|
||||
},
|
||||
"Center Section": {
|
||||
"Center Section": "بخش مرکزی"
|
||||
},
|
||||
@@ -812,7 +839,7 @@
|
||||
"Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.": "انتخاب کنید که کدام مانیتور رابط صفحه قفل را نشان دهد. مانیتورهای دیگر برای محافظت در برابر سوختگی OLED، رنگ ثابتی را نمایش میدهند."
|
||||
},
|
||||
"Chroma Style": {
|
||||
"Chroma Style": ""
|
||||
"Chroma Style": "استایل رنگی"
|
||||
},
|
||||
"Cipher": {
|
||||
"Cipher": "رمزگذار"
|
||||
@@ -835,6 +862,9 @@
|
||||
"Clear History?": {
|
||||
"Clear History?": "تاریخچه پاک شود؟"
|
||||
},
|
||||
"Clear Sky": {
|
||||
"Clear Sky": "آسمان پاک"
|
||||
},
|
||||
"Clear all history when server starts": {
|
||||
"Clear all history when server starts": "هنگام راهاندازی سرور تمام تاریخچه را پاک کن"
|
||||
},
|
||||
@@ -890,7 +920,7 @@
|
||||
"Clipboard Manager": "مدیریت کلیپبورد"
|
||||
},
|
||||
"Clipboard behavior setting description": {
|
||||
"Press Enter to paste, Shift+Enter to copy": ""
|
||||
"Press Enter to paste, Shift+Enter to copy": "برای الصاق Enter، برای کپی Shift+Enter را فشار دهید"
|
||||
},
|
||||
"Clipboard service not available": {
|
||||
"Clipboard service not available": "سرویس کلیپبورد در دسترس نیست"
|
||||
@@ -950,16 +980,16 @@
|
||||
"Color temperature for night mode": "دمای رنگ برای حالت شب"
|
||||
},
|
||||
"Color theme for syntax highlighting.": {
|
||||
"Color theme for syntax highlighting.": ""
|
||||
"Color theme for syntax highlighting.": "تم رنگی برای برجستهسازی کد."
|
||||
},
|
||||
"Color theme for syntax highlighting. %1 themes available.": {
|
||||
"Color theme for syntax highlighting. %1 themes available.": ""
|
||||
"Color theme for syntax highlighting. %1 themes available.": "تم رنگی برای برجستهسازی کد. %1 تم دردسترس است."
|
||||
},
|
||||
"Colorful mix of bright contrasting accents.": {
|
||||
"Colorful mix of bright contrasting accents.": "ترکیب رنگارنگ از رنگهای تأکیدی متضاد."
|
||||
},
|
||||
"Colorize Active": {
|
||||
"Colorize Active": ""
|
||||
"Colorize Active": "رنگآمیزی حالت فعال"
|
||||
},
|
||||
"Column": {
|
||||
"Column": "ستون"
|
||||
@@ -1022,7 +1052,7 @@
|
||||
"Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": "آیکونها را برای محیطکارهای نامدار تنظیم کنید. آیکونها بر شمارهها هنگامی که هر دو فعال باشند اولویت دارند."
|
||||
},
|
||||
"Configure match criteria and actions": {
|
||||
"Configure match criteria and actions": "پیکربندی معیارهای انطباق و اقدامات"
|
||||
"Configure match criteria and actions": "پیکربندی انطباق معیار و اقدامات"
|
||||
},
|
||||
"Configure which displays show \"%1": {
|
||||
"Configure which displays show \"%1\"": "تنظیم کنید که کدام نمایشگرها «%1» را نشان دهند"
|
||||
@@ -1082,7 +1112,7 @@
|
||||
"Control Center Tile Color": "رنگ کاشی مرکز کنترل"
|
||||
},
|
||||
"Control animation duration for notification popups and history": {
|
||||
"Control animation duration for notification popups and history": ""
|
||||
"Control animation duration for notification popups and history": "مدت زمان انیمیشن را برای پاپآپ اعلانها و تاریخچه مشخص کنید"
|
||||
},
|
||||
"Control currently playing media": {
|
||||
"Control currently playing media": "کنترل رسانه درحال پخش"
|
||||
@@ -1175,7 +1205,7 @@
|
||||
"Create rules to mute, ignore, hide from history, or override notification priority.": "برای بیصدا کردن، نادیده گرفتن، پنهانسازی از تاریخچه و یا تغییر اولویت اعلان قاعده ایجاد کنید."
|
||||
},
|
||||
"Create rules to mute, ignore, hide from history, or override notification priority. Default only overrides priority; notifications still show normally.": {
|
||||
"Create rules to mute, ignore, hide from history, or override notification priority. Default only overrides priority; notifications still show normally.": ""
|
||||
"Create rules to mute, ignore, hide from history, or override notification priority. Default only overrides priority; notifications still show normally.": "برای بیصدا کردن، نادیده گرفتن، مخفیسازی از تاریخچه، یا تغییر اولویت اعلانها قواعدی ایجاد کنید. در حالت پیشفرض، تنها اولویت تغییر میکند؛ اعلانها همچنان بهصورت عادی نمایش داده میشوند."
|
||||
},
|
||||
"Creating...": {
|
||||
"Creating...": "درحال ایجاد..."
|
||||
@@ -1211,7 +1241,7 @@
|
||||
"Cursor Config Not Configured": "پیکربندی اشارهگر موس تنظیم نشده"
|
||||
},
|
||||
"Cursor Include Missing": {
|
||||
"Cursor Include Missing": ""
|
||||
"Cursor Include Missing": "دستور include اشارهگر موس یافت نشد"
|
||||
},
|
||||
"Cursor Size": {
|
||||
"Cursor Size": "اندازه اشارهگر موس"
|
||||
@@ -1292,7 +1322,7 @@
|
||||
"DWL service not available": "سرویس DWL در دسترس نیست"
|
||||
},
|
||||
"Daily": {
|
||||
"Daily": ""
|
||||
"Daily": "روزانه"
|
||||
},
|
||||
"Daily Forecast": {
|
||||
"Daily Forecast": "پیشبینی روزانه"
|
||||
@@ -1387,6 +1417,9 @@
|
||||
"Description": {
|
||||
"Description": "توضیحات"
|
||||
},
|
||||
"Desktop": {
|
||||
"Desktop": "دسکتاپ"
|
||||
},
|
||||
"Desktop Clock": {
|
||||
"Desktop Clock": "ساعت دسکتاپ"
|
||||
},
|
||||
@@ -1558,6 +1591,9 @@
|
||||
"Docs": {
|
||||
"Docs": "مستندات"
|
||||
},
|
||||
"Documents": {
|
||||
"Documents": "اسناد"
|
||||
},
|
||||
"Domain (optional)": {
|
||||
"Domain (optional)": "دامنه (اختیاری)"
|
||||
},
|
||||
@@ -1570,6 +1606,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "درب باز"
|
||||
},
|
||||
"Downloads": {
|
||||
"Downloads": "بارگیریها"
|
||||
},
|
||||
"Drag to Reorder": {
|
||||
"Drag to Reorder": "نگهداشتن برای بازآرایی"
|
||||
},
|
||||
@@ -1582,6 +1621,9 @@
|
||||
"Driver": {
|
||||
"Driver": "درایور"
|
||||
},
|
||||
"Drizzle": {
|
||||
"Drizzle": "بارانریزه"
|
||||
},
|
||||
"Duplicate": {
|
||||
"Duplicate": "تکثیر"
|
||||
},
|
||||
@@ -1589,7 +1631,7 @@
|
||||
"Duplicate Wallpaper with Blur": "تصویر پسزمینه تکراری با تاری"
|
||||
},
|
||||
"Duration": {
|
||||
"Duration": ""
|
||||
"Duration": "مدت زمان"
|
||||
},
|
||||
"Dusk (Astronomical Twilight)": {
|
||||
"Dusk (Astronomical Twilight)": "غروب (گرگ و میش نجومی)"
|
||||
@@ -1742,16 +1784,16 @@
|
||||
"Enter this passkey on ": "ورود این کلید عبور در "
|
||||
},
|
||||
"Enter to Paste": {
|
||||
"Enter to Paste": ""
|
||||
"Enter to Paste": "Enter برای الصاق"
|
||||
},
|
||||
"Enterprise": {
|
||||
"Enterprise": "شرکت"
|
||||
},
|
||||
"Entry pinned": {
|
||||
"Entry pinned": "ورودی سنجاق شد"
|
||||
"Entry pinned": "مدخل سنجاق شد"
|
||||
},
|
||||
"Entry unpinned": {
|
||||
"Entry unpinned": "سنجاق ورودی برداشته شد"
|
||||
"Entry unpinned": "سنجاق مدخل برداشته شد"
|
||||
},
|
||||
"Environment Variables": {
|
||||
"Environment Variables": "متغیرهای محیطی"
|
||||
@@ -1772,7 +1814,7 @@
|
||||
"Exponential": "نمایی"
|
||||
},
|
||||
"Extra Arguments": {
|
||||
"Extra Arguments": ""
|
||||
"Extra Arguments": "آرگومانهای اضافی"
|
||||
},
|
||||
"F1/I: Toggle • F10: Help": {
|
||||
"F1/I: Toggle • F10: Help": "F1/I: تغییر حالت • F10: راهنما"
|
||||
@@ -1814,7 +1856,7 @@
|
||||
"Failed to connect to %1": "اتصال به %1 ناموفق بود"
|
||||
},
|
||||
"Failed to copy entry": {
|
||||
"Failed to copy entry": "کپی ورودی ناموفق بود"
|
||||
"Failed to copy entry": "کپی مدخل ناموفق بود"
|
||||
},
|
||||
"Failed to create printer": {
|
||||
"Failed to create printer": "ایجاد چاپگر ناموفق بود"
|
||||
@@ -1886,7 +1928,7 @@
|
||||
"Failed to pause printer": "توقف چاپگر ناموفق بود"
|
||||
},
|
||||
"Failed to pin entry": {
|
||||
"Failed to pin entry": "سنجاق کردن ورودی ناموفق بود"
|
||||
"Failed to pin entry": "سنجاق کردن مدخل ناموفق بود"
|
||||
},
|
||||
"Failed to print test page": {
|
||||
"Failed to print test page": "چاپ صفحه تست ناموفق بود"
|
||||
@@ -1943,7 +1985,7 @@
|
||||
"Failed to start connection to %1": "شروع اتصال به %1 ناموفق بود"
|
||||
},
|
||||
"Failed to unpin entry": {
|
||||
"Failed to unpin entry": "برداشتن سنجاق ورودی ناموفق بود"
|
||||
"Failed to unpin entry": "برداشتن سنجاق مدخل ناموفق بود"
|
||||
},
|
||||
"Failed to update VPN": {
|
||||
"Failed to update VPN": "بروزرسانی VPN ناموفق بود"
|
||||
@@ -2044,6 +2086,9 @@
|
||||
"Focused Window": {
|
||||
"Focused Window": "پنجره فوکوسشده"
|
||||
},
|
||||
"Fog": {
|
||||
"Fog": "مه"
|
||||
},
|
||||
"Follow Monitor Focus": {
|
||||
"Follow Monitor Focus": "فوکوس مانیتور را دنبال کن"
|
||||
},
|
||||
@@ -2102,10 +2147,13 @@
|
||||
"Forgot network %1": "فراموشی شبکه %1"
|
||||
},
|
||||
"Format Legend": {
|
||||
"Format Legend": "قالب فهرست علائم"
|
||||
"Format Legend": "راهنمای قالببندی"
|
||||
},
|
||||
"Free VRAM/memory when the launcher is closed. May cause a slight delay when reopening.": {
|
||||
"Free VRAM/memory when the launcher is closed. May cause a slight delay when reopening.": ""
|
||||
"Free VRAM/memory when the launcher is closed. May cause a slight delay when reopening.": "هنگامی که لانچر بسته شد VRAM/حافظه را آزاد کن. ممکن است باعث اندکی تأخیر هنگام باز کردن مجدد شود."
|
||||
},
|
||||
"Freezing Drizzle": {
|
||||
"Freezing Drizzle": "بارانریزه یخزن"
|
||||
},
|
||||
"Frequency": {
|
||||
"Frequency": "فرکانس"
|
||||
@@ -2212,6 +2260,15 @@
|
||||
"Health": {
|
||||
"Health": "سلامت"
|
||||
},
|
||||
"Heavy Rain": {
|
||||
"Heavy Rain": "باران سنگین"
|
||||
},
|
||||
"Heavy Snow": {
|
||||
"Heavy Snow": "برف سنگین"
|
||||
},
|
||||
"Heavy Snow Showers": {
|
||||
"Heavy Snow Showers": "برف سنگین پراکنده"
|
||||
},
|
||||
"Height": {
|
||||
"Height": "ارتفاع"
|
||||
},
|
||||
@@ -2231,7 +2288,7 @@
|
||||
"Hidden": "پنهان"
|
||||
},
|
||||
"Hidden (%1)": {
|
||||
"Hidden (%1)": ""
|
||||
"Hidden (%1)": "پنهان (%1)"
|
||||
},
|
||||
"Hidden Apps": {
|
||||
"Hidden Apps": "برنامههای پنهان"
|
||||
@@ -2273,13 +2330,13 @@
|
||||
"Hide cursor when using touch input": "اشارهگر موس را هنگام استفاده از ورودی لمسی پنهان کن"
|
||||
},
|
||||
"Hide device": {
|
||||
"Hide device": ""
|
||||
"Hide device": "پنهان کردن دستگاه"
|
||||
},
|
||||
"Hide notification content until expanded": {
|
||||
"Hide notification content until expanded": ""
|
||||
"Hide notification content until expanded": "پنهان کردن محتوای اعلان را تا هنگام گسترش"
|
||||
},
|
||||
"Hide notification content until expanded; popups show collapsed by default": {
|
||||
"Hide notification content until expanded; popups show collapsed by default": ""
|
||||
"Hide notification content until expanded; popups show collapsed by default": "محتوای اعلان را تا هنگام گسترش پنهان کن؛ پاپآپها به طور پیشفرض گسترشیافته نمایش داده میشوند"
|
||||
},
|
||||
"Hide on Touch": {
|
||||
"Hide on Touch": "هنگام لمس پنهان کن"
|
||||
@@ -2294,7 +2351,7 @@
|
||||
"History Settings": "تنظیمات تاریخچه"
|
||||
},
|
||||
"History cleared. %1 pinned entries kept.": {
|
||||
"History cleared. %1 pinned entries kept.": "تاریخچه پاک شد. %1 ورودی سنجاق شده نگه داشته شد."
|
||||
"History cleared. %1 pinned entries kept.": "تاریخچه پاک شد. %1 مدخل سنجاق شده نگه داشته شد."
|
||||
},
|
||||
"Hold Duration": {
|
||||
"Hold Duration": "مدت زمان نگهداشتن"
|
||||
@@ -2311,6 +2368,9 @@
|
||||
"Hold to confirm (%1s)": {
|
||||
"Hold to confirm (%1s)": "برای تأیید نگهدارید (%1s)"
|
||||
},
|
||||
"Home": {
|
||||
"Home": "خانه"
|
||||
},
|
||||
"Hot Corners": {
|
||||
"Hot Corners": "گوشههای فعال"
|
||||
},
|
||||
@@ -2671,13 +2731,13 @@
|
||||
"Key": "کلید"
|
||||
},
|
||||
"Keybind Sources": {
|
||||
"Keybind Sources": ""
|
||||
"Keybind Sources": "منابع نگاشتکلیدها"
|
||||
},
|
||||
"Keybinds Search Settings": {
|
||||
"Keybinds Search Settings": "تنظیمات نگاشتکلیدهای جستجو"
|
||||
},
|
||||
"Keybinds shown alongside regular search results": {
|
||||
"Keybinds shown alongside regular search results": ""
|
||||
"Keybinds shown alongside regular search results": "نگاشتکلیدها همراه نتایج عادی جستجو نمایش داده میشوند"
|
||||
},
|
||||
"Keyboard Layout Name": {
|
||||
"Keyboard Layout Name": "نام چیدمان صفحهکلید"
|
||||
@@ -2686,8 +2746,8 @@
|
||||
"Keyboard Shortcuts": "میانبرهای صفحهکلید"
|
||||
},
|
||||
"Keyboard hints when enter-to-paste is enabled": {
|
||||
"Shift+Enter: Copy • Shift+Del: Clear All • Esc: Close": "",
|
||||
"↑/↓: Navigate • Enter: Paste • Del: Delete • F10: Help": ""
|
||||
"Shift+Enter: Copy • Shift+Del: Clear All • Esc: Close": "Shift+Enter: کپی • Shift+Del: پاککردن همه • Esc: بستن",
|
||||
"↑/↓: Navigate • Enter: Paste • Del: Delete • F10: Help": "↑/↓: پیمایش • Enter: الصاق • Del: حذف • F10: راهنما"
|
||||
},
|
||||
"Keys": {
|
||||
"Keys": "کلیدها"
|
||||
@@ -2758,6 +2818,15 @@
|
||||
"Light Mode": {
|
||||
"Light Mode": "حالت روشن"
|
||||
},
|
||||
"Light Rain": {
|
||||
"Light Rain": "باران سبک"
|
||||
},
|
||||
"Light Snow": {
|
||||
"Light Snow": "برف سبک"
|
||||
},
|
||||
"Light Snow Showers": {
|
||||
"Light Snow Showers": "برف پراکنده"
|
||||
},
|
||||
"Linear": {
|
||||
"Linear": "خطی"
|
||||
},
|
||||
@@ -2894,10 +2963,10 @@
|
||||
"Marker Waste Full": "ضایعات مارکر پر است"
|
||||
},
|
||||
"Match Criteria": {
|
||||
"Match Criteria": ""
|
||||
"Match Criteria": "انطباق معیار"
|
||||
},
|
||||
"Matches profile: %1": {
|
||||
"Matches profile: %1": ""
|
||||
"Matches profile: %1": "منطبق با پروفایل: %1"
|
||||
},
|
||||
"Material Colors": {
|
||||
"Material Colors": "رنگهای Material"
|
||||
@@ -2939,31 +3008,31 @@
|
||||
"Max to Edges": "بیشینه تا لبهها"
|
||||
},
|
||||
"Maximize": {
|
||||
"Maximize": ""
|
||||
"Maximize": "بزرگکردن پنجره"
|
||||
},
|
||||
"Maximize Detection": {
|
||||
"Maximize Detection": "تشخیص بزرگشدن پنجره"
|
||||
},
|
||||
"Maximum Entry Size": {
|
||||
"Maximum Entry Size": "حداکثر اندازه ورودی"
|
||||
"Maximum Entry Size": "حداکثر اندازه مدخل"
|
||||
},
|
||||
"Maximum History": {
|
||||
"Maximum History": "حداکثر تاریخچه"
|
||||
},
|
||||
"Maximum Pinned Entries": {
|
||||
"Maximum Pinned Entries": "بیشینه ورودیهای سنجاق شده"
|
||||
"Maximum Pinned Entries": "بیشینه مدخلهای سنجاق شده"
|
||||
},
|
||||
"Maximum number of clipboard entries to keep": {
|
||||
"Maximum number of clipboard entries to keep": "بیشینه تعداد ورودیهای کلیپبورد برای نگهداری"
|
||||
"Maximum number of clipboard entries to keep": "بیشینه تعداد مدخلهای کلیپبورد برای نگهداری"
|
||||
},
|
||||
"Maximum number of entries that can be saved": {
|
||||
"Maximum number of entries that can be saved": "بیشینه تعداد ورودیهایی که میتوانند ذخیره شوند"
|
||||
"Maximum number of entries that can be saved": "بیشینه تعداد مدخلهایی که میتوانند ذخیره شوند"
|
||||
},
|
||||
"Maximum pinned entries reached": {
|
||||
"Maximum pinned entries reached": "به بیشینه ورودیهای سنجاق شده رسیدید"
|
||||
"Maximum pinned entries reached": "به بیشینه مدخلهای سنجاق شده رسیدید"
|
||||
},
|
||||
"Maximum size per clipboard entry": {
|
||||
"Maximum size per clipboard entry": "بیشینه اندازه برای هر ورودی کلیپبورد"
|
||||
"Maximum size per clipboard entry": "بیشینه اندازه برای هر مدخل کلیپبورد"
|
||||
},
|
||||
"Media": {
|
||||
"Media": "رسانه"
|
||||
@@ -2984,7 +3053,7 @@
|
||||
"Media Needed": "مدیا مورد نیاز است"
|
||||
},
|
||||
"Media Playback": {
|
||||
"Media Playback": ""
|
||||
"Media Playback": "پخش رسانه"
|
||||
},
|
||||
"Media Player": {
|
||||
"Media Player": "پخشکننده رسانه"
|
||||
@@ -3103,11 +3172,14 @@
|
||||
"Moving to Paused": {
|
||||
"Moving to Paused": "انتقال به حالت متوقف شده"
|
||||
},
|
||||
"Music": {
|
||||
"Music": "موسیقی"
|
||||
},
|
||||
"Mute popups for %1": {
|
||||
"Mute popups for %1": ""
|
||||
"Mute popups for %1": "بیصدا کردن پاپآپها را برای %1"
|
||||
},
|
||||
"Muted Apps": {
|
||||
"Muted Apps": ""
|
||||
"Muted Apps": "برنامههای بیصدا"
|
||||
},
|
||||
"Muted palette with subdued, calming tones.": {
|
||||
"Muted palette with subdued, calming tones.": "پالت رنگی ساکت با تنهای ملایم و آرامشبخش."
|
||||
@@ -3260,7 +3332,7 @@
|
||||
"No apps have been launched yet.": "هیچ برنامهای هنوز اجرا نشده است."
|
||||
},
|
||||
"No apps muted. Right-click a notification and choose \"Mute popups\" to add one here.": {
|
||||
"No apps muted. Right-click a notification and choose \"Mute popups\" to add one here.": ""
|
||||
"No apps muted. Right-click a notification and choose \"Mute popups\" to add one here.": "هیچ برنامهای بیصدا نشده است. برای افزودن یکی به اینجا روی اعلان راستکلیک کرده و «بیصدا کردن پاپآپها» را انتخاب کنید."
|
||||
},
|
||||
"No battery": {
|
||||
"No battery": "بدون باتری"
|
||||
@@ -3272,7 +3344,7 @@
|
||||
"No changes": "بدون تغییرات"
|
||||
},
|
||||
"No clipboard entries found": {
|
||||
"No clipboard entries found": "هیچ ورودی کلیپبوردی پیدا نشد"
|
||||
"No clipboard entries found": "هیچ مدخل کلیپبوردی پیدا نشد"
|
||||
},
|
||||
"No devices found": {
|
||||
"No devices found": "دستگاهی یافت نشد"
|
||||
@@ -3305,7 +3377,7 @@
|
||||
"No launcher plugins installed.": "هیچ افزونه لانچری نصب نشده است."
|
||||
},
|
||||
"No match criteria": {
|
||||
"No match criteria": ""
|
||||
"No match criteria": "هیچ انطباق معیاری یافت نشد"
|
||||
},
|
||||
"No matches": {
|
||||
"No matches": "موردی پیدا نشد"
|
||||
@@ -3332,13 +3404,13 @@
|
||||
"No profiles": "هیچ پروفایلی یافت نشد"
|
||||
},
|
||||
"No recent clipboard entries found": {
|
||||
"No recent clipboard entries found": "هیچ ورودی کلیپبورد اخیری یافت نشد"
|
||||
"No recent clipboard entries found": "هیچ مدخل کلیپبورد اخیری یافت نشد"
|
||||
},
|
||||
"No results found": {
|
||||
"No results found": "هیچ نتیجهای یافت نشد"
|
||||
},
|
||||
"No saved clipboard entries": {
|
||||
"No saved clipboard entries": "هیچ ورودی کلیپبوردی ذخیره نشده است"
|
||||
"No saved clipboard entries": "هیچ مدخل کلیپبوردی ذخیره نشده است"
|
||||
},
|
||||
"No trigger": {
|
||||
"No trigger": "بدون راهانداز"
|
||||
@@ -3467,7 +3539,7 @@
|
||||
"Opacity": "شفافیت"
|
||||
},
|
||||
"Opaque": {
|
||||
"Opaque": ""
|
||||
"Opaque": "کدر"
|
||||
},
|
||||
"Open": {
|
||||
"Open": "بازکردن"
|
||||
@@ -3520,6 +3592,9 @@
|
||||
"Outputs Include Missing": {
|
||||
"Outputs Include Missing": "Include خروجیها یافت نشد"
|
||||
},
|
||||
"Overcast": {
|
||||
"Overcast": "کاملاْ ابری"
|
||||
},
|
||||
"Overflow": {
|
||||
"Overflow": "سرریزی"
|
||||
},
|
||||
@@ -3557,7 +3632,7 @@
|
||||
"PIN": "PIN"
|
||||
},
|
||||
"Pad Hours": {
|
||||
"Pad Hours": ""
|
||||
"Pad Hours": "حاشیهگذاری ساعات"
|
||||
},
|
||||
"Padding": {
|
||||
"Padding": "فاصله درونی"
|
||||
@@ -3577,6 +3652,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "درحال جفت شدن..."
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "نیمه ابری"
|
||||
},
|
||||
"Passkey:": {
|
||||
"Passkey:": "کلید عبور:"
|
||||
},
|
||||
@@ -3639,7 +3717,7 @@
|
||||
"Failed to send ping": "ارسال پینگ ناموفق بود",
|
||||
"Failed to share": "اشتراکگذاری ناموفق بود",
|
||||
"Pairing failed": "جفتسازی ناموفق بود",
|
||||
"Unpair failed": ""
|
||||
"Unpair failed": "جداسازی ناموفق بود"
|
||||
},
|
||||
"Phone Connect file send": {
|
||||
"Sending": "درحال ارسال"
|
||||
@@ -3684,6 +3762,9 @@
|
||||
"Phone Connect unavailable status": {
|
||||
"Unavailable": "دردسترس نیست"
|
||||
},
|
||||
"Pictures": {
|
||||
"Pictures": "تصاویر"
|
||||
},
|
||||
"Pin": {
|
||||
"Pin": "سنجاق"
|
||||
},
|
||||
@@ -3754,7 +3835,7 @@
|
||||
"Popup Position": "مکان پاپآپ"
|
||||
},
|
||||
"Popup Shadow": {
|
||||
"Popup Shadow": ""
|
||||
"Popup Shadow": "سایه پاپآپ"
|
||||
},
|
||||
"Popup Transparency": {
|
||||
"Popup Transparency": "شفافیت پاپآپ"
|
||||
@@ -3865,7 +3946,7 @@
|
||||
"Privacy Indicator": "نشانگر حریم خصوصی"
|
||||
},
|
||||
"Privacy Mode": {
|
||||
"Privacy Mode": ""
|
||||
"Privacy Mode": "حالت حریم خصوصی"
|
||||
},
|
||||
"Private Key Password": {
|
||||
"Private Key Password": "کلید خصوصی گذرواژه"
|
||||
@@ -3909,6 +3990,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "پروتکل"
|
||||
},
|
||||
"Quick Access": {
|
||||
"Quick Access": "دسترسی سریع"
|
||||
},
|
||||
"Quick access to application launcher": {
|
||||
"Quick access to application launcher": "دسترسی سریع به لانچر برنامه"
|
||||
},
|
||||
@@ -3927,6 +4011,9 @@
|
||||
"Radius": {
|
||||
"Radius": "شعاع"
|
||||
},
|
||||
"Rain": {
|
||||
"Rain": "باران"
|
||||
},
|
||||
"Rain Chance": {
|
||||
"Rain Chance": "احتمال بارش"
|
||||
},
|
||||
@@ -4195,10 +4282,10 @@
|
||||
"Search Options": "گزینههای جستجو"
|
||||
},
|
||||
"Search by key combo, description, or action name.\n\nDefault action copies the keybind to clipboard.\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": {
|
||||
"Search by key combo, description, or action name.\n\nDefault action copies the keybind to clipboard.\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": ""
|
||||
"Search by key combo, description, or action name.\n\nDefault action copies the keybind to clipboard.\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": "با ترکیب کلیدها، توضیحات یا نام اقدام جستجو کنید.\n\nاقدام پیشفرض نگاشتکلید را در کلیپبورد کپی میکند.\nبرای سنجاق کردن نگاشتکلیدهای پر استفاده راستکلیک کنید یا پیکان راست را فشار دهید - آنها هنگام جستجو نکردن در بالای لیست ظاهر خواهند شد."
|
||||
},
|
||||
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": {
|
||||
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": "با ترکیب کلیدها، توضیحات یا نام اقدام جستجو کنید.\\n\\اقدام پیشفرض نگاشتکلید را در کلیپبورد کپی میکند.\\nبرای سنجاق کردن نگاشتکلیدهای پر استفاده راستکلیک کنید یا پیکان راست را فشار دهید - آنها هنگام جستجو نکردن در بالا ظاهر خواهند شد."
|
||||
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": "با ترکیب کلیدها، توضیحات یا نام اقدام جستجو کنید.\\n\\اقدام پیشفرض نگاشتکلید را در کلیپبورد کپی میکند.\\nبرای سنجاق کردن نگاشتکلیدهای پر استفاده راستکلیک کنید یا پیکان راست را فشار دهید - آنها هنگام جستجو نکردن در بالای لیست ظاهر خواهند شد."
|
||||
},
|
||||
"Search file contents": {
|
||||
"Search file contents": "جستجو در محتوای فایل"
|
||||
@@ -4246,7 +4333,7 @@
|
||||
"Select Bar": "انتخاب نوار"
|
||||
},
|
||||
"Select Dock Launcher Logo": {
|
||||
"Select Dock Launcher Logo": ""
|
||||
"Select Dock Launcher Logo": "لوگوی لانچر داک را انتخاب کنید"
|
||||
},
|
||||
"Select Launcher Logo": {
|
||||
"Select Launcher Logo": "انتخاب لوگوی لانچر"
|
||||
@@ -4294,7 +4381,7 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": "انتخاب الگوریتم پالت رنگی استفاده شده برای رنگهای بر اساس تصویر پسزمینه"
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": ""
|
||||
"Select which keybind providers to include": "انتخاب کنید که کدام ارائه دهنده نگاشتکلیدها include شود"
|
||||
},
|
||||
"Select which transitions to include in randomization": {
|
||||
"Select which transitions to include in randomization": "انتخاب کنید کدام گذارها در تصادفیسازی باشند"
|
||||
@@ -4324,13 +4411,13 @@
|
||||
"Set key and action to save": "کلید و اقدام را برای ذخیره تنظیم کنید"
|
||||
},
|
||||
"Set notification rules": {
|
||||
"Set notification rules": ""
|
||||
"Set notification rules": "تنظیم قوانین اعلانها"
|
||||
},
|
||||
"Setup": {
|
||||
"Setup": "راهاندازی"
|
||||
},
|
||||
"Share Gamma Control Settings": {
|
||||
"Share Gamma Control Settings": ""
|
||||
"Share Gamma Control Settings": "اشتراکگذاری تنظیمات کنترل گاما"
|
||||
},
|
||||
"Shell": {
|
||||
"Shell": "شِل"
|
||||
@@ -4498,13 +4585,13 @@
|
||||
"Show darkened overlay behind modal dialogs": "لایه overlay تیره پشت پنجره مودال نمایش بده"
|
||||
},
|
||||
"Show device": {
|
||||
"Show device": ""
|
||||
"Show device": "نمایش دستگاه"
|
||||
},
|
||||
"Show dock when floating windows don't overlap its area": {
|
||||
"Show dock when floating windows don't overlap its area": "داک را هنگامی که پنجرههای شناور با محیط آن همپوشانی ندارند نمایش بده"
|
||||
},
|
||||
"Show drop shadow on notification popups": {
|
||||
"Show drop shadow on notification popups": ""
|
||||
"Show drop shadow on notification popups": "سایهافتاده را برای پاپآپ اعلانها نمایش بده"
|
||||
},
|
||||
"Show launcher overlay when typing in Niri overview. Disable to use another launcher.": {
|
||||
"Show launcher overlay when typing in Niri overview. Disable to use another launcher.": "لایه overlay لانچر را هنگام تایپ در نمای کلی نیری نمایش بده. برای استفاده از لانچر دیگری غیرفعال کنید."
|
||||
@@ -4603,7 +4690,7 @@
|
||||
"Size": "اندازه"
|
||||
},
|
||||
"Size Constraints": {
|
||||
"Size Constraints": ""
|
||||
"Size Constraints": "محدودیتهای اندازه"
|
||||
},
|
||||
"Size Offset": {
|
||||
"Size Offset": "آفست اندازه"
|
||||
@@ -4626,6 +4713,9 @@
|
||||
"Snap": {
|
||||
"Snap": "چسباندن"
|
||||
},
|
||||
"Snow": {
|
||||
"Snow": "برف"
|
||||
},
|
||||
"Some plugins require a newer version of DMS:": {
|
||||
"Some plugins require a newer version of DMS:": "برخی افزونهها نیازمند نسخه جدیدتر DMS هستند:"
|
||||
},
|
||||
@@ -4722,6 +4812,9 @@
|
||||
"Suspend system after": {
|
||||
"Suspend system after": "تعلیق پس از"
|
||||
},
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "تعلیق سپس هایبرنیت"
|
||||
},
|
||||
"Swap": {
|
||||
"Swap": "سواپ"
|
||||
},
|
||||
@@ -4852,7 +4945,7 @@
|
||||
"This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics.": "این ابزارک از حالتهای خاموش شدن GPU جلوگیری میکند، که میتواند به طور قابل توجهی بر عمر باتری لپتاپها تأثیر بگذارد. استفاده از این ابزارک در لپتاپهایی با گرافیک هیبریدی توصیه نمیشود."
|
||||
},
|
||||
"This will delete all unpinned entries. %1 pinned entries will be kept.": {
|
||||
"This will delete all unpinned entries. %1 pinned entries will be kept.": "این همه ورودیهای سنجاق نشده را پاک میکند. %1 ورودی سنجاق شده نگه داشته میشوند."
|
||||
"This will delete all unpinned entries. %1 pinned entries will be kept.": "این همه مدخلهای سنجاق نشده را پاک میکند. %1 مدخل سنجاق شده نگه داشته میشوند."
|
||||
},
|
||||
"This will permanently delete all clipboard history.": {
|
||||
"This will permanently delete all clipboard history.": "این کار تمام تاریخچه کلیپبورد را برای همیشه حذف میکند."
|
||||
@@ -4860,11 +4953,17 @@
|
||||
"This will permanently remove this saved clipboard item. This action cannot be undone.": {
|
||||
"This will permanently remove this saved clipboard item. This action cannot be undone.": "این به طور دائمی این مورد کلیپبورد را حذف خواهد کرد. این عمل برگشتناپذیر است."
|
||||
},
|
||||
"Thunderstorm": {
|
||||
"Thunderstorm": "رعد و برق"
|
||||
},
|
||||
"Thunderstorm with Hail": {
|
||||
"Thunderstorm with Hail": "رعد و برق با تگرگ"
|
||||
},
|
||||
"Tiled": {
|
||||
"Tiled": ""
|
||||
"Tiled": "کاشیشده"
|
||||
},
|
||||
"Tiled State": {
|
||||
"Tiled State": ""
|
||||
"Tiled State": "حالت کاشیشده"
|
||||
},
|
||||
"Tiling": {
|
||||
"Tiling": "تایلینگ"
|
||||
@@ -5044,13 +5143,13 @@
|
||||
"Unknown Title": "عنوان ناشناس"
|
||||
},
|
||||
"Unload on Close": {
|
||||
"Unload on Close": ""
|
||||
"Unload on Close": "خالیکردن هنگام بستن"
|
||||
},
|
||||
"Unmute": {
|
||||
"Unmute": ""
|
||||
"Unmute": "صدادار کردن"
|
||||
},
|
||||
"Unmute popups for %1": {
|
||||
"Unmute popups for %1": ""
|
||||
"Unmute popups for %1": "صدادار کردن پاپآپها را برای %1"
|
||||
},
|
||||
"Unnamed Rule": {
|
||||
"Unnamed Rule": "قاعده بینام"
|
||||
@@ -5250,6 +5349,9 @@
|
||||
"Vibrant palette with playful saturation.": {
|
||||
"Vibrant palette with playful saturation.": "پالتر رنگی پر جنب و جوش با اشباع رنگی سرزنده."
|
||||
},
|
||||
"Videos": {
|
||||
"Videos": "ویدئوها"
|
||||
},
|
||||
"View Mode": {
|
||||
"View Mode": "حالت نمایش"
|
||||
},
|
||||
@@ -5519,6 +5621,9 @@
|
||||
"border thickness": {
|
||||
"Thickness": "ضخامت"
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "براندون"
|
||||
},
|
||||
"browse themes button | theme browser header | theme browser window title": {
|
||||
"Browse Themes": "مرور تمها"
|
||||
},
|
||||
@@ -5548,7 +5653,7 @@
|
||||
"Active tile background and icon color": "رنگ کاشی پسزمینه فعال و آیکون"
|
||||
},
|
||||
"count of hidden audio devices": {
|
||||
"Hidden (%1)": ""
|
||||
"Hidden (%1)": "پنهان (%1)"
|
||||
},
|
||||
"current theme label": {
|
||||
"Current Theme: %1": "تم کنونی: %1"
|
||||
@@ -5562,6 +5667,9 @@
|
||||
"custom theme file hint": {
|
||||
"Click to select a custom theme JSON file": "برای انتخاب یک فایل JSON قالب سفارشی کلیک کنید"
|
||||
},
|
||||
"dark mode wallpaper color picker title": {
|
||||
"Choose Dark Mode Color": "انتخاب رنگ حالت تاریک"
|
||||
},
|
||||
"dark mode wallpaper file browser title | light mode wallpaper file browser title | wallpaper file browser title": {
|
||||
"Select Wallpaper": "انتخاب تصویر پسزمینه"
|
||||
},
|
||||
@@ -5580,6 +5688,9 @@
|
||||
"days": {
|
||||
"days": "روز"
|
||||
},
|
||||
"default monitor label suffix": {
|
||||
"(Default)": "(پیشفرض)"
|
||||
},
|
||||
"dgop not available": {
|
||||
"dgop not available": "dgop در دسترس نیست"
|
||||
},
|
||||
@@ -5646,7 +5757,7 @@
|
||||
"No GPUs detected": "هیچ GPUای شناسایی نشد"
|
||||
},
|
||||
"empty state in process list": {
|
||||
"No matching processes": ""
|
||||
"No matching processes": "فرآیند موردنظر یافت نشد"
|
||||
},
|
||||
"empty theme list": {
|
||||
"No themes found": "هیچ تمی یافت نشد"
|
||||
@@ -5845,6 +5956,9 @@
|
||||
"leave empty for default": {
|
||||
"leave empty for default": "برای پیشفرض خالی رها کنید"
|
||||
},
|
||||
"light mode wallpaper color picker title": {
|
||||
"Choose Light Mode Color": "انتخاب رنگ حالت روشن"
|
||||
},
|
||||
"loading indicator": {
|
||||
"Loading...": "درحال بارگذاری..."
|
||||
},
|
||||
@@ -5904,6 +6018,9 @@
|
||||
"nav": {
|
||||
"nav": "جهت"
|
||||
},
|
||||
"neovim template description": {
|
||||
"Requires lazy plugin manager": "به مدیریت افزونه lazy نیاز دارد"
|
||||
},
|
||||
"network status": {
|
||||
"Connected": "متصل",
|
||||
"Disabling WiFi...": "غیرفعالسازی وایفای...",
|
||||
@@ -5917,6 +6034,9 @@
|
||||
"no custom theme file status": {
|
||||
"No custom theme file": "هیچ تم سفارشی یافت نشد"
|
||||
},
|
||||
"no monitors available label": {
|
||||
"No monitors": "بدون مانیتور"
|
||||
},
|
||||
"no registry themes installed hint": {
|
||||
"No themes installed. Browse themes to install from the registry.": "هیچ تمی نصب نشده. تمها را برای نصب از مخزن مرور کنید."
|
||||
},
|
||||
@@ -5965,7 +6085,7 @@
|
||||
"Enable History": "فعالکردن تاریخچه"
|
||||
},
|
||||
"notification privacy mode placeholder": {
|
||||
"Message Content": ""
|
||||
"Message Content": "محتوای پیام"
|
||||
},
|
||||
"notification rule action option": {
|
||||
"Ignore Completely": "کلاً نادیده بگیر",
|
||||
@@ -5978,7 +6098,7 @@
|
||||
},
|
||||
"notification rule match field option": {
|
||||
"Body": "بدنه",
|
||||
"Desktop Entry": "",
|
||||
"Desktop Entry": "مدخل دسکتاپ",
|
||||
"Summary": "خلاصه"
|
||||
},
|
||||
"notification rule match type option": {
|
||||
@@ -5997,6 +6117,24 @@
|
||||
"official": {
|
||||
"official": "رسمی"
|
||||
},
|
||||
"on Hyprland": {
|
||||
"on Hyprland": "در Hyprland"
|
||||
},
|
||||
"on MangoWC": {
|
||||
"on MangoWC": "در MangoWC"
|
||||
},
|
||||
"on Miracle WM": {
|
||||
"on Miracle WM": "در Miracle WM"
|
||||
},
|
||||
"on Niri": {
|
||||
"on Niri": "در Niri"
|
||||
},
|
||||
"on Scroll": {
|
||||
"on Scroll": "در Scroll"
|
||||
},
|
||||
"on Sway": {
|
||||
"on Sway": "در Sway"
|
||||
},
|
||||
"open": {
|
||||
"open": "باز کردن"
|
||||
},
|
||||
@@ -6044,6 +6182,12 @@
|
||||
"profile image file browser title": {
|
||||
"Select Profile Image": "انتخاب تصویر نمایه"
|
||||
},
|
||||
"qt theme env error body": {
|
||||
"You need to set either:\nQT_QPA_PLATFORMTHEME=gtk3 OR\nQT_QPA_PLATFORMTHEME=qt6ct\nas environment variables, and then restart the shell.\n\nqt6ct requires qt6ct-kde to be installed.": "لازم است یکی از مقادیر زیر را به عنوان متغیر محیطی تنظیم نمایید:\nQT_QPA_PLATFORMTHEME=gtk3 یا\nQT_QPA_PLATFORMTHEME=qt6ct\nو پس از آن، شل را مجدداً راهاندازی کنید.\n\nبسته qt6ct نیازمند نصب qt6ct-kde است."
|
||||
},
|
||||
"qt theme env error title": {
|
||||
"Missing Environment Variables": "متغیرهای محیطی یافت نشد"
|
||||
},
|
||||
"read-only settings warning for NixOS home-manager users": {
|
||||
"Settings are read-only. Changes will not persist.": "تنظیمات فقط قابل خواندن هستند. تغییرات حفظ نخواهند شد."
|
||||
},
|
||||
@@ -6101,6 +6245,12 @@
|
||||
"Kernel": "کرنل",
|
||||
"Load Average": "متوسط بارگذاری"
|
||||
},
|
||||
"theme auto mode tab": {
|
||||
"Location": "مکان"
|
||||
},
|
||||
"theme auto mode tab | wallpaper cycling mode tab": {
|
||||
"Time": "زمان"
|
||||
},
|
||||
"theme browser description": {
|
||||
"Install color themes from the DMS theme registry": "نصب تم رنگها از مخزن تم DMS"
|
||||
},
|
||||
@@ -6120,7 +6270,7 @@
|
||||
"Search themes...": "جستجوی تمها..."
|
||||
},
|
||||
"this app": {
|
||||
"this app": ""
|
||||
"this app": "این برنامه"
|
||||
},
|
||||
"tile color option": {
|
||||
"Primary Container": "زمینه اصلی",
|
||||
@@ -6141,6 +6291,9 @@
|
||||
"unknown author": {
|
||||
"Unknown": "ناشناخته"
|
||||
},
|
||||
"up": {
|
||||
"up": "روشن"
|
||||
},
|
||||
"update dms for NM integration.": {
|
||||
"update dms for NM integration.": "DMS را برای یکپارچهسازی NM بروز کنید."
|
||||
},
|
||||
@@ -6153,6 +6306,12 @@
|
||||
"version requirement": {
|
||||
"Requires %1": "به %1 نیاز دارد"
|
||||
},
|
||||
"wallpaper color picker title": {
|
||||
"Choose Wallpaper Color": "انتخاب رنگ تصویر پسزمینه"
|
||||
},
|
||||
"wallpaper cycling mode tab": {
|
||||
"Interval": "وقفه"
|
||||
},
|
||||
"wallpaper directory file browser title": {
|
||||
"Select Wallpaper Directory": "انتخاب دایرکتوری تصاویر پسزمینه"
|
||||
},
|
||||
@@ -6171,6 +6330,34 @@
|
||||
"Tile H": "کاشی افقی",
|
||||
"Tile V": "کاشی عمودی"
|
||||
},
|
||||
"wallpaper interval": {
|
||||
"1 hour": "۱ ساعت",
|
||||
"1 hour 30 minutes": "۱ ساعت و نیم",
|
||||
"1 minute": "۱ دقیقه",
|
||||
"10 seconds": "۱۰ ثانیه",
|
||||
"12 hours": "۱۲ ساعت",
|
||||
"15 minutes": "۱۵ دقیقه",
|
||||
"15 seconds": "۱۵ ثانیه",
|
||||
"2 hours": "۲ ساعت",
|
||||
"20 seconds": "۲۰ ثانیه",
|
||||
"25 seconds": "۲۵ ثانیه",
|
||||
"3 hours": "۳ ساعت",
|
||||
"30 minutes": "۳۰ دقیقه",
|
||||
"30 seconds": "۳۰ ثانیه",
|
||||
"35 seconds": "۳۵ ثانیه",
|
||||
"4 hours": "۴ ساعت",
|
||||
"40 seconds": "۴۰ ثانیه",
|
||||
"45 seconds": "۴۵ ثانیه",
|
||||
"5 minutes": "۵ دقیقه",
|
||||
"5 seconds": "۵ ثانیه",
|
||||
"50 seconds": "۵۰ ثانیه",
|
||||
"55 seconds": "۵۵ ثانیه",
|
||||
"6 hours": "۶ ساعت",
|
||||
"8 hours": "۸ ساعت"
|
||||
},
|
||||
"wallpaper not set label": {
|
||||
"Not set": "تنظیم نشده"
|
||||
},
|
||||
"wallpaper processing error": {
|
||||
"Wallpaper processing failed": "پردازش تصویر پسزمینه ناموفق بود"
|
||||
},
|
||||
@@ -6245,5 +6432,8 @@
|
||||
},
|
||||
"• yyyy - Year (2024)": {
|
||||
"• yyyy - Year (2024)": "• yyyy - سال (2024)"
|
||||
},
|
||||
"↑/↓: Nav • Space: Expand • Enter: Action/Expand • E: Text": {
|
||||
"↑/↓: Nav • Space: Expand • Enter: Action/Expand • E: Text": ""
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user