1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-02 02:22:06 -04:00

Compare commits

...

58 Commits

Author SHA1 Message Date
bbedward
03a8e1e0d5 clipboard: fix memory leak from unbounded offer maps and unguarded file reads 2026-02-20 11:42:14 -05:00
bbedward
4d4d3c20a1 keybinds/niri: fix quote preservation 2026-02-20 11:42:14 -05:00
bbedward
cef16d6bc9 dankdash: fix widgets across different bar section fixes #1764s 2026-02-20 11:42:14 -05:00
bbedward
aafaad1791 core/screenshot: light cleanups 2026-02-20 11:42:14 -05:00
Patrick Fischer
7906fdc2b0 screensaver: emit ActiveChanged on lock/unlock (#1761) 2026-02-20 11:42:14 -05:00
Triệu Kha
397650ca52 clipboard: improve image thumbnail (#1759)
- thumbnail image is now bigger
- circular mask has been replaced with rounded rectangular mask
2026-02-20 11:42:14 -05:00
purian23
826207006a template: Default install method 2026-02-20 11:42:14 -05:00
purian23
58c2fcd31c issues: Template fix 2026-02-20 11:42:14 -05:00
purian23
b2a2b425ec templates: Fix GitHub issue labels 2026-02-20 11:42:14 -05:00
shorinkiwata
942c9c9609 feat(distros): allow CatOS to run DMS installer (#1768)
- This PR adds support for **CatOS**
- CatOS is fully compatible with Arch Linux
2026-02-20 11:42:14 -05:00
purian23
46d6e1cff3 templates: Update DMS issue formats 2026-02-20 11:42:14 -05:00
bbedward
a4137c57c1 running apps: fix ordering on niri 2026-02-19 20:46:26 -05:00
bbedward
1ad8b627f1 launcher: fix premature exit of file search fixes #1749 2026-02-19 16:47:34 -05:00
Jonas Bloch
58a02ce290 Search keybinds fixes (#1748)
* fix: close keybind cheatsheet on escape press

* feat: match all space separated words in keybind cheatsheet search
2026-02-19 16:27:14 -05:00
bbedward
8e1ad1a2be audio: fix hide device not working 2026-02-19 16:24:48 -05:00
bbedward
68cd7ab32c i18n: term sync 2026-02-19 14:11:21 -05:00
Youseffo13
f649ce9a8e Added missing i18n strings and changed reset button (#1746)
* Update it.json

* Enhance SettingsSliderRow: add resetText property and update reset button styling

* added i18n strings

* adjust reset button width to be dynamic based on content size

* added i18n strings

* Update template.json

* reverted changes

* Update it.json

* Update template.json
2026-02-19 14:11:21 -05:00
bbedward
c4df242f07 dankbar: remove behaviors from monitoring widgets 2026-02-19 14:11:21 -05:00
bbedward
26846c8d55 dgop: round computed values to match display format 2026-02-19 14:11:21 -05:00
bbedward
31b44a667c flake: fix dev flake for go 1.25 and ashellchheck 2026-02-19 14:11:21 -05:00
bbedward
4f3b73ee21 hyprland: add serial to output model generator 2026-02-19 09:22:56 -05:00
bbedward
4cfae91f02 dock: fix context menu styling fixes #1742 2026-02-19 09:22:56 -05:00
bbedward
8d947a6e95 dock: fix transparency setting fixes #1739 2026-02-19 09:22:56 -05:00
bbedward
1e84d4252c launcher: improve perf of settings search 2026-02-19 09:22:56 -05:00
bbedward
76072e1d4c launcher: always heuristic lookup cached entries 2026-02-19 09:22:56 -05:00
bbedward
6408dce4a9 launcher v2: always heuristicLookup tab actions 2026-02-18 19:07:30 -05:00
bbedward
0b2e1cca38 i18n: term updates 2026-02-18 18:35:29 -05:00
bbedward
c1bfd8c0b7 system tray: fix to take up 0 space when empty 2026-02-18 18:35:29 -05:00
Youseffo13
90ffa5833b Added Missing i18n strings (#1729)
* inverted dock visibility and position option

* added missing I18n strings

* added missing i18n strings

* added i18n strings

* Added missing i18n strings

* updated translations

* Update it.json
2026-02-18 18:35:29 -05:00
bbedward
169c669286 widgets: add openWith/toggleWith modes for dankbar widgets 2026-02-18 16:24:07 -05:00
bbedward
f8350deafc keybinds: fix escape in keybinds modal 2026-02-18 14:57:53 -05:00
bbedward
0286a1b80b launcher v2: remove calc cc: enhancements for plugins to size details 2026-02-18 14:48:44 -05:00
beluch-dev
7c3e6c1f02 fix: correct parameter name in Hyprland windowrule (no_initial_focus) (#1726)
##Description
This PR corrects the parameter name to match new Hyprland standard.

## Changes
-Before: 'noinitialfocus'
-After: 'no_initial_focus'
2026-02-18 14:48:40 -05:00
bbedward
d2d72db3c9 plugins: fix settings focus loss 2026-02-18 13:36:51 -05:00
Evgeny Zemtsov
f81f861408 handle recycled server object IDs for workspace/group handles (#1725)
When switching tabs rapidly or closing multiple tabs, the taskbar shows
"ghost" workspaces — entries with no name, no coordinates, and no active
state. The ghosts appear at positions where workspaces were removed and
then recreated by the compositor.

When a compositor removes a workspace (sends `removed` event) and the
client calls Destroy(), the proxy is marked as zombie but stays in the
Context.objects map. For server-created objects (IDs >= 0xFF000000), the
server never sends `delete_id`, so the zombie proxy persists indefinitely.

When the compositor later creates a new workspace that gets a recycled
server object ID, GetProxy() returns the old zombie proxy. The dispatch
loop in GetDispatch() checks IsZombie() and silently drops ALL events
for zombie proxies — including property events (name, id, coordinates,
state, capabilities) intended for the new workspace. This causes the
ghost workspaces with empty properties in the UI.

Fix: check IsZombie() when handling `workspace` and `workspace_group`
events that carry a `new_id` argument. If the existing proxy is a
zombie, treat it as absent and create a fresh proxy via
registerServerProxy(), which replaces the zombie in the map. Subsequent
property events are then dispatched to the live proxy.
2026-02-18 13:36:51 -05:00
bbedward
af494543f5 1.4.2: staging ground 2026-02-18 13:36:43 -05:00
bbedward
db4de55338 popout: decouple shadow from content layer 2026-02-18 10:46:01 -05:00
bbedward
37ecbbbbde popout: disable layer after animation 2026-02-18 10:34:21 -05:00
purian23
d6a6d2a438 notifications: Maintain shadow during expansion 2026-02-18 10:34:21 -05:00
purian23
bf1c6eec74 notifications: Update initial popup height surfaces 2026-02-18 10:34:21 -05:00
bbedward
0ddae80584 running apps: fix scroll events being propagated fixes #1724 2026-02-18 10:34:21 -05:00
bbedward
5c96c03bfa matugen: make v4 detection more resilient 2026-02-18 09:57:35 -05:00
bbedward
dfe36e47d8 process list: fix scaling with fonts fixes #1721 2026-02-18 09:57:35 -05:00
purian23
63e1b75e57 dankinstall: Fix Debian ARM64 detection 2026-02-18 09:57:35 -05:00
bbedward
29efdd8598 matugen: detect emacs directory fixes #1720 2026-02-18 09:57:35 -05:00
bbedward
34d03cf11b osd: optimize bindings 2026-02-18 09:57:35 -05:00
bbedward
c339389d44 screenshot: adjust cursor CLI option to be more explicit 2026-02-17 22:28:46 -05:00
bbedward
af5f6eb656 settings: workaround crash 2026-02-17 22:20:19 -05:00
purian23
a6d28e2553 notifications: Tweak animation scale & settings 2026-02-17 22:07:36 -05:00
bbedward
6213267908 settings: guard internal writes from watcher 2026-02-17 22:03:57 -05:00
bbedward
d084114149 cc: fix plugin reloading in bar position changes 2026-02-17 17:25:19 -05:00
bbedward
f6d99eca0d popout: anchor height changing popout surfaces to top and bottom 2026-02-17 17:25:19 -05:00
bbedward
722eb3289e workspaces: fix named workspace icons 2026-02-17 17:25:19 -05:00
bbedward
b7f2bdcb2d dankinstall: no_anim on dms layers 2026-02-17 17:25:19 -05:00
bbedward
11c20db6e6 1.4.1 2026-02-17 14:08:15 -05:00
bbedward
8a4e3f8bb1 system updater: fix hide no update option 2026-02-17 14:08:04 -05:00
bbedward
bc8fe97c13 launcher: fix kb navigation not always showing last delegate in view 2026-02-17 14:08:04 -05:00
bbedward
47262155aa doctor: add qt6-imageformats check 2026-02-17 14:08:04 -05:00
116 changed files with 7485 additions and 4147 deletions

View File

@@ -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 -v
description: Output of `dms doctor -v` command
placeholder: Paste the output of `dms doctor -v` here
label: dms doctor -vC
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. ...

View File

@@ -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

View File

@@ -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 -v
description: Output of `dms doctor -v` command
placeholder: Paste the output of `dms doctor -v` here
label: dms doctor -vC
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:**

View File

@@ -649,40 +649,73 @@ func checkI2CAvailability() checkResult {
return checkResult{catOptionalFeatures, "I2C/DDC", statusOK, fmt.Sprintf("%d monitor(s) detected", len(devices)), "External monitor brightness control", doctorDocsURL + "#optional-features"}
}
func checkKImageFormats() checkResult {
func checkImageFormatPlugins() []checkResult {
url := doctorDocsURL + "#optional-features"
desc := "Extra image format support (AVIF, HEIF, JXL)"
pluginDir := findQtPluginDir()
if pluginDir == "" {
return checkResult{catOptionalFeatures, "kimageformats", statusInfo, "Cannot detect (qtpaths not found)", desc, url}
}
imageFormatsDir := filepath.Join(pluginDir, "imageformats")
keyPlugins := []struct{ file, format string }{
{"kimg_avif.so", "AVIF"},
{"kimg_heif.so", "HEIF"},
{"kimg_jxl.so", "JXL"},
{"kimg_exr.so", "EXR"},
}
var found []string
for _, p := range keyPlugins {
if _, err := os.Stat(filepath.Join(imageFormatsDir, p.file)); err == nil {
found = append(found, p.format)
return []checkResult{
{catOptionalFeatures, "qt6-imageformats", statusInfo, "Cannot detect (plugin dir not found)", "WebP, TIFF, JP2 support", url},
{catOptionalFeatures, "kimageformats", statusInfo, "Cannot detect (plugin dir not found)", "AVIF, HEIF, JXL support", url},
}
}
if len(found) == 0 {
return checkResult{catOptionalFeatures, "kimageformats", statusWarn, "Not installed", desc, url}
imageFormatsDir := filepath.Join(pluginDir, "imageformats")
type pluginCheck struct {
name string
desc string
plugins []struct{ file, format string }
}
details := ""
if doctorVerbose {
details = fmt.Sprintf("Formats: %s (%s)", strings.Join(found, ", "), imageFormatsDir)
checks := []pluginCheck{
{
name: "qt6-imageformats",
desc: "WebP, TIFF, GIF, JP2 support",
plugins: []struct{ file, format string }{
{"libqwebp.so", "WebP"},
{"libqtiff.so", "TIFF"},
{"libqgif.so", "GIF"},
{"libqjp2.so", "JP2"},
{"libqicns.so", "ICNS"},
},
},
{
name: "kimageformats",
desc: "AVIF, HEIF, JXL support",
plugins: []struct{ file, format string }{
{"kimg_avif.so", "AVIF"},
{"kimg_heif.so", "HEIF"},
{"kimg_jxl.so", "JXL"},
{"kimg_exr.so", "EXR"},
},
},
}
return checkResult{catOptionalFeatures, "kimageformats", statusOK, fmt.Sprintf("Installed (%d formats)", len(found)), details, url}
var results []checkResult
for _, c := range checks {
var found []string
for _, p := range c.plugins {
if _, err := os.Stat(filepath.Join(imageFormatsDir, p.file)); err == nil {
found = append(found, p.format)
}
}
var result checkResult
switch {
case len(found) == 0:
result = checkResult{catOptionalFeatures, c.name, statusWarn, "Not installed", c.desc, url}
default:
details := ""
if doctorVerbose {
details = fmt.Sprintf("Formats: %s (%s)", strings.Join(found, ", "), imageFormatsDir)
}
result = checkResult{catOptionalFeatures, c.name, statusOK, fmt.Sprintf("Installed (%d formats)", len(found)), details, url}
}
results = append(results, result)
}
return results
}
func findQtPluginDir() string {
@@ -773,7 +806,7 @@ func checkOptionalDependencies() []checkResult {
results = append(results, checkResult{catOptionalFeatures, "cups-pk-helper", cupsPkStatus, cupsPkMsg, "Printer management", optionalFeaturesURL})
results = append(results, checkI2CAvailability())
results = append(results, checkKImageFormats())
results = append(results, checkImageFormatPlugins()...)
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {

View File

@@ -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

View File

@@ -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)$
@@ -111,6 +111,7 @@ windowrule = float on, match:class ^(zoom)$
# windowrule = float on, match:class ^(org.quickshell)$
layerrule = no_anim on, match:namespace ^(quickshell)$
layerrule = no_anim on, match:namespace ^dms:.*
source = ./dms/colors.conf
source = ./dms/outputs.conf

View File

@@ -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)
})

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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{}

View File

@@ -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,
}
}

View File

@@ -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 {

View File

@@ -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,
}
}

View File

@@ -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

View File

@@ -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"
)

View File

@@ -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()
}

View File

@@ -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)
}
}
}

View 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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -142,6 +142,8 @@ Singleton {
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
popout.currentTabIndex = tabIndex;
}
if (popout.updateSurfacePosition)
popout.updateSurfacePosition();
currentPopoutTriggers[screenName] = triggerId;
return;
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -470,9 +470,6 @@ FocusScope {
onTextChanged: {
controller.setSearchQuery(text);
if (text.length === 0) {
controller.restorePreviousMode();
}
if (actionPanel.expanded) {
actionPanel.hide();
}

View File

@@ -81,6 +81,12 @@ function calculateNextIndex(flatModel, selectedFlatIndex, sectionId, viewMode, g
return bounds.start + newPosInSection;
}
var currentRow = Math.floor(posInSection / cols);
var lastRow = Math.floor((bounds.count - 1) / cols);
if (currentRow < lastRow) {
return bounds.start + bounds.count - 1;
}
var nextSection = findNextNonHeaderIndex(flatModel, bounds.end + 1);
return nextSection !== -1 ? nextSection : selectedFlatIndex;
}

View File

@@ -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":

View File

@@ -130,24 +130,17 @@ Item {
if (!entry || entry.isHeader)
return;
var rowIndex = _flatIndexToRowMap[index];
if (rowIndex === undefined || rowIndex >= _cumulativeHeights.length)
return;
var row = _visualRows[rowIndex];
if (!row)
if (rowIndex === undefined)
return;
var rowY = _cumulativeHeights[rowIndex];
var rowHeight = row.height;
var scrollY = mainListView.contentY - mainListView.originY;
var viewHeight = mainListView.height;
var headerH = stickyHeader.height;
mainListView.positionViewAtIndex(rowIndex, ListView.Contain);
if (rowY < scrollY + headerH) {
mainListView.contentY = Math.max(mainListView.originY, rowY - headerH + mainListView.originY);
return;
}
if (rowY + rowHeight > scrollY + viewHeight) {
mainListView.contentY = rowY + rowHeight - viewHeight + mainListView.originY;
if (stickyHeader.visible && rowIndex < _cumulativeHeights.length) {
var rowY = _cumulativeHeights[rowIndex];
var scrollY = mainListView.contentY - mainListView.originY;
if (rowY < scrollY + stickyHeader.height) {
mainListView.contentY = Math.max(mainListView.originY, rowY - stickyHeader.height + mainListView.originY);
}
}
}

View 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"
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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 === "")
@@ -945,22 +961,31 @@ Column {
}
}
Component.onCompleted: {
Qt.callLater(() => {
const pluginComponent = PluginService.pluginWidgetComponents[pluginId];
if (pluginComponent) {
const instance = pluginComponent.createObject(null, {
"pluginId": pluginId,
"pluginService": PluginService,
"visible": false,
"width": 0,
"height": 0
});
if (instance) {
pluginInstance = instance;
}
function tryCreatePluginInstance() {
const pluginComponent = PluginService.pluginWidgetComponents[pluginId];
if (!pluginComponent)
return false;
try {
const instance = pluginComponent.createObject(null, {
"pluginId": pluginId,
"pluginService": PluginService,
"visible": false,
"width": 0,
"height": 0
});
if (instance) {
pluginInstance = instance;
return true;
}
});
} catch (e) {
console.warn("DragDropGrid: stale plugin component for", pluginId, "- reloading");
PluginService.reloadPlugin(pluginId);
}
return false;
}
Component.onCompleted: {
Qt.callLater(() => tryCreatePluginInstance());
}
Connections {
@@ -970,6 +995,11 @@ Column {
pluginInstance.loadPluginData();
}
}
function onPluginLoaded(loadedPluginId) {
if (loadedPluginId !== pluginId || pluginInstance)
return;
Qt.callLater(() => tryCreatePluginInstance());
}
}
Component.onDestruction: {

View File

@@ -13,8 +13,8 @@ Row {
property Item popoutContent: null
signal addWidget(string widgetId)
signal resetToDefault()
signal clearAll()
signal resetToDefault
signal clearAll
height: 48
spacing: Theme.spacingS
@@ -28,7 +28,7 @@ Row {
y: parent ? Math.round((parent.height - height) / 2) : 0
width: 400
height: 300
modal: true
modal: false
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
@@ -133,7 +133,7 @@ Row {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.addWidget(modelData.id)
root.addWidget(modelData.id);
}
}
}

View File

@@ -12,6 +12,7 @@ DankPopout {
id: root
layerNamespace: "dms:control-center"
fullHeightSurface: true
property string expandedSection: ""
property var triggerScreen: null
@@ -115,6 +116,7 @@ DankPopout {
property alias bluetoothCodecSelector: bluetoothCodecSelector
color: "transparent"
clip: true
Rectangle {
anchors.fill: parent
@@ -165,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

View File

@@ -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();

View File

@@ -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();

View File

@@ -216,14 +216,18 @@ QtObject {
}
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id];
if (!pluginComponent || typeof pluginComponent.createObject !== 'function') {
if (!pluginComponent)
continue;
}
const tempInstance = pluginComponent.createObject(null);
if (!tempInstance) {
let tempInstance;
try {
tempInstance = pluginComponent.createObject(null);
} catch (e) {
PluginService.reloadPlugin(plugin.id);
continue;
}
if (!tempInstance)
continue;
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0;
tempInstance.destroy();

View File

@@ -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");
}
}
}

View File

@@ -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: {

View File

@@ -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)

View File

@@ -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)

View File

@@ -193,13 +193,6 @@ BasePill {
}
width: Math.max(diskBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}

View File

@@ -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)

View File

@@ -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
}
}
}
}
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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: {

View File

@@ -10,6 +10,45 @@ BasePill {
property bool isActive: false
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
readonly property bool isChecking: SystemUpdateService.isChecking
readonly property bool shouldHide: SettingsData.updaterHideWidget && !hasUpdates && !isChecking && !SystemUpdateService.hasError
opacity: shouldHide ? 0 : 1
states: [
State {
name: "hidden_horizontal"
when: root.shouldHide && !isVerticalOrientation
PropertyChanges {
target: root
width: 0
}
},
State {
name: "hidden_vertical"
when: root.shouldHide && 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
}
}
Ref {
service: SystemUpdateService

View File

@@ -924,8 +924,13 @@ Item {
return loadedIsUrgent;
return false;
}
property var loadedIconData: null
property bool loadedHasIcon: false
readonly property var loadedIconData: {
if (isPlaceholder) return null;
const name = modelData?.name;
if (!name) return null;
return SettingsData.getWorkspaceNameIcon(name);
}
readonly property bool loadedHasIcon: loadedIconData !== null
property var loadedIcons: []
readonly property int stableIconCount: {
@@ -986,8 +991,8 @@ Item {
readonly property real baseHeight: root.isVertical ? (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7) : (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5)
readonly property bool hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
readonly property bool workspaceNamesEnabled: SettingsData.showWorkspaceName && (CompositorService.isNiri || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
readonly property real contentImplicitWidth: (hasWorkspaceName || loadedHasIcon) ? (appIconsLoader.item?.contentWidth ?? 0) : 0
readonly property real contentImplicitHeight: (workspaceNamesEnabled || loadedHasIcon) ? (appIconsLoader.item?.contentHeight ?? 0) : 0
readonly property real contentImplicitWidth: hasWorkspaceName ? (appIconsLoader.item?.contentWidth ?? 0) : 0
readonly property real contentImplicitHeight: workspaceNamesEnabled ? (appIconsLoader.item?.contentHeight ?? 0) : 0
readonly property real iconsExtraWidth: {
if (!root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
@@ -1222,8 +1227,6 @@ Item {
onTriggered: {
if (isPlaceholder) {
delegateRoot.loadedWorkspaceData = null;
delegateRoot.loadedIconData = null;
delegateRoot.loadedHasIcon = false;
delegateRoot.loadedIcons = [];
delegateRoot.loadedIsUrgent = false;
return;
@@ -1249,13 +1252,6 @@ Item {
delegateRoot.loadedIsUrgent = wsData?.urgent ?? false;
}
var icData = null;
if (wsData?.name) {
icData = SettingsData.getWorkspaceNameIcon(wsData.name);
}
delegateRoot.loadedIconData = icData;
delegateRoot.loadedHasIcon = icData !== null;
if (SettingsData.showWorkspaceApps) {
if (CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
@@ -1420,7 +1416,7 @@ Item {
Item {
visible: loadedHasIcon && loadedIconData?.type === "icon"
width: wsIcon.width + (isActive && loadedIcons.length > 0 ? 4 : 0)
width: wsIcon.width
height: root.appIconSize
DankIcon {
@@ -1435,7 +1431,7 @@ Item {
Item {
visible: loadedHasIcon && loadedIconData?.type === "text"
width: wsText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
width: wsText.implicitWidth
height: root.appIconSize
StyledText {
@@ -1449,14 +1445,14 @@ Item {
}
Item {
visible: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon
width: wsIndexText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
width: wsIndexText.implicitWidth
height: root.appIconSize
StyledText {
id: wsIndexText
anchors.verticalCenter: parent.verticalCenter
text: root.getWorkspaceIndex(modelData, index)
text: loadedHasIcon ? (modelData?.name ?? "") : root.getWorkspaceIndex(modelData, index)
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
@@ -1574,9 +1570,9 @@ Item {
}
StyledText {
visible: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon
visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
anchors.horizontalCenter: parent.horizontalCenter
text: root.getWorkspaceIndex(modelData, index)
text: loadedHasIcon ? (root.isVertical ? (modelData?.name ?? "").charAt(0) : (modelData?.name ?? "")) : root.getWorkspaceIndex(modelData, index)
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
@@ -1670,55 +1666,6 @@ Item {
}
}
// Loader for Custom Name Icon
Loader {
id: customIconLoader
anchors.fill: parent
active: !isPlaceholder && loadedHasIcon && loadedIconData.type === "icon" && !SettingsData.showWorkspaceApps
sourceComponent: Item {
DankIcon {
anchors.centerIn: parent
name: loadedIconData ? loadedIconData.value : "" // NULL CHECK
size: Theme.fontSizeSmall
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : Theme.surfaceTextMedium
weight: isActive && !isPlaceholder ? 500 : 400
}
}
}
// Loader for Custom Name Text
Loader {
id: customTextLoader
anchors.fill: parent
active: !isPlaceholder && loadedHasIcon && loadedIconData.type === "text" && !SettingsData.showWorkspaceApps
sourceComponent: Item {
StyledText {
anchors.centerIn: parent
text: loadedIconData ? loadedIconData.value : "" // NULL CHECK
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
}
}
}
// Loader for Workspace Index
Loader {
id: indexLoader
anchors.fill: parent
active: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon && !SettingsData.showWorkspaceApps
sourceComponent: Item {
StyledText {
anchors.centerIn: parent
text: {
return root.getWorkspaceIndex(modelData, index);
}
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
}
}
}
}
Component.onCompleted: updateAllData()

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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 || "")
}

View File

@@ -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)
}

View File

@@ -7,6 +7,7 @@ DankPopout {
id: root
layerNamespace: "dms:notification-center-popout"
fullHeightSurface: true
property bool notificationHistoryVisible: false
property var triggerScreen: null
@@ -34,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

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}
}
}
}
}

View File

@@ -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));
}
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -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);
}
}
}

View File

@@ -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 => {

View File

@@ -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"));
}
}
}

View File

@@ -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");
});
}
}

View File

@@ -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

View File

@@ -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
}
}
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -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
});
}

View File

@@ -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),

View File

@@ -1 +1 @@
v1.4.0
v1.4.2

View File

@@ -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

View File

@@ -30,7 +30,10 @@ Item {
property var customKeyboardFocus: null
property bool backgroundInteractive: true
property bool contentHandlesKeys: false
property bool fullHeightSurface: false
property bool _resizeActive: false
property real _surfaceMarginLeft: 0
property real _surfaceW: 0
property real storedBarThickness: Theme.barHeight - 4
property real storedBarSpacing: 4
@@ -106,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;
@@ -125,6 +135,10 @@ Item {
_lastOpenedScreen = screen;
shouldBeVisible = true;
if (useBackgroundWindow) {
_surfaceMarginLeft = alignedX - shadowBuffer;
_surfaceW = alignedWidth + shadowBuffer * 2;
}
Qt.callLater(() => {
if (shouldBeVisible && screen) {
if (useBackgroundWindow)
@@ -360,20 +374,38 @@ Item {
return WlrKeyboardFocus.Exclusive;
}
readonly property bool _fullHeight: useBackgroundWindow && root.fullHeightSurface
anchors {
left: true
top: true
right: !useBackgroundWindow
bottom: !useBackgroundWindow
bottom: _fullHeight || !useBackgroundWindow
}
WlrLayershell.margins {
left: useBackgroundWindow ? (root.alignedX - shadowBuffer) : 0
top: useBackgroundWindow ? (root.alignedY - shadowBuffer) : 0
left: useBackgroundWindow ? root._surfaceMarginLeft : 0
top: (useBackgroundWindow && !_fullHeight) ? (root.alignedY - shadowBuffer) : 0
}
implicitWidth: useBackgroundWindow ? (root.alignedWidth + (shadowBuffer * 2)) : 0
implicitHeight: useBackgroundWindow ? (root.alignedHeight + (shadowBuffer * 2)) : 0
implicitWidth: useBackgroundWindow ? root._surfaceW : 0
implicitHeight: (useBackgroundWindow && !_fullHeight) ? (root.alignedHeight + shadowBuffer * 2) : 0
mask: (useBackgroundWindow && _fullHeight) ? contentInputMask : null
Region {
id: contentInputMask
item: contentMaskRect
}
Item {
id: contentMaskRect
visible: false
x: contentContainer.x - root.shadowBuffer
y: contentContainer.y - root.shadowBuffer
width: root.alignedWidth + root.shadowBuffer * 2
height: root.alignedHeight + root.shadowBuffer * 2
}
MouseArea {
anchors.fill: parent
@@ -393,7 +425,7 @@ Item {
Item {
id: contentContainer
x: useBackgroundWindow ? shadowBuffer : root.alignedX
y: useBackgroundWindow ? shadowBuffer : root.alignedY
y: (useBackgroundWindow && !contentWindow._fullHeight) ? shadowBuffer : root.alignedY
width: root.alignedWidth
height: root.alignedHeight
@@ -444,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
@@ -455,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

View File

@@ -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'

Some files were not shown because too many files have changed in this diff Show More