1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-07 13:02:09 -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: | value: |
## DankMaterialShell Bug Report ## DankMaterialShell Bug Report
Limit your report to one issue per submission unless closely related Limit your report to one issue per submission unless closely related
- type: checkboxes - type: dropdown
id: compositor id: compositor
attributes: attributes:
label: Compositor label: Compositor
options: options:
- label: Niri - Niri
- label: Hyprland - Hyprland
- label: MangoWC (dwl) - MangoWC (dwl)
- label: Sway - Sway
validations: validations:
required: true required: true
- type: checkboxes - type: dropdown
id: distribution id: distribution
attributes: attributes:
label: Distribution label: Distribution
options: options:
- label: Arch Linux - Arch Linux
- label: CachyOS - CachyOS
- label: Fedora - Fedora
- label: NixOS - NixOS
- label: Debian - Debian
- label: Ubuntu - Ubuntu
- label: Gentoo - Gentoo
- label: OpenSUSE - OpenSUSE
- label: Other (specify below) - Other (specify below)
validations: validations:
required: true required: true
- type: input - type: input
@@ -42,12 +42,45 @@ body:
placeholder: e.g., PikaOS, Void Linux, etc. placeholder: e.g., PikaOS, Void Linux, etc.
validations: validations:
required: false 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 - type: textarea
id: dms_doctor id: dms_doctor
attributes: attributes:
label: dms doctor -v label: dms doctor -vC
description: Output of `dms doctor -v` command description: Output of `dms doctor -vC` command — paste between the lines below to keep it collapsed in the issue
placeholder: Paste the output of `dms doctor -v` here placeholder: Paste the output of `dms doctor -vC` here
value: |
<details>
<summary>Click to expand</summary>
</details>
validations: validations:
required: true required: true
- type: textarea - type: textarea
@@ -69,7 +102,7 @@ body:
- type: textarea - type: textarea
id: steps_to_reproduce id: steps_to_reproduce
attributes: attributes:
label: Steps to Reproduce & Installation Method label: Steps to Reproduce
description: Please provide detailed steps to reproduce the issue description: Please provide detailed steps to reproduce the issue
placeholder: | placeholder: |
1. ... 1. ...

View File

@@ -23,18 +23,25 @@ body:
placeholder: Why is this feature important? placeholder: Why is this feature important?
validations: validations:
required: false required: false
- type: checkboxes - type: dropdown
id: compositor id: compositor
attributes: attributes:
label: Compositor(s) label: Compositor(s)
description: Is this feature specific to one or more compositors? description: Is this feature specific to one or more compositors?
options: options:
- label: All compositors - All compositors
- label: Niri - Niri
- label: Hyprland - Hyprland
- label: MangoWC (dwl) - MangoWC (dwl)
- label: Sway - Sway
- label: Other (specify below) - Other (specify below)
validations:
required: true
- type: input
id: compositor_other
attributes:
label: If Other, please specify
placeholder: e.g., Wayfire, Mutter, etc.
validations: validations:
required: false required: false
- type: textarea - type: textarea

View File

@@ -7,32 +7,87 @@ body:
attributes: attributes:
value: | value: |
## DankMaterialShell Support Request ## DankMaterialShell Support Request
- type: checkboxes - type: dropdown
id: compositor id: compositor
attributes: attributes:
label: Compositor label: Compositor
options: options:
- label: Niri - Niri
- label: Hyprland - Hyprland
- label: MangoWC (dwl) - MangoWC (dwl)
- label: Sway - Sway
- label: Other (specify below) - Other (specify below)
validations:
required: true
- type: input
id: compositor_other
attributes:
label: If Other, please specify
placeholder: e.g., Wayfire, Mutter, etc.
validations: validations:
required: false required: false
- type: input - type: dropdown
id: distribution id: distribution
attributes: attributes:
label: Distribution label: Distribution
description: Which Linux distribution are you using? (e.g., Arch, Fedora, Debian, etc.) options:
placeholder: Your Linux distribution - 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: validations:
required: false required: false
- type: textarea - type: textarea
id: dms_doctor id: dms_doctor
attributes: attributes:
label: dms doctor -v label: dms doctor -vC
description: Output of `dms doctor -v` command description: Output of `dms doctor -vC` command — paste between the lines below to keep it collapsed in the issue
placeholder: Paste the output of `dms doctor -v` here placeholder: Paste the output of `dms doctor -vC` here
value: |
<details>
<summary>Click to expand</summary>
</details>
validations: validations:
required: false required: false
- type: textarea - 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: trailing-whitespace
- id: check-yaml - id: check-yaml
- id: end-of-file-fixer - id: end-of-file-fixer
- repo: https://github.com/shellcheck-py/shellcheck-py - repo: local
rev: v0.10.0.1
hooks: hooks:
- id: shellcheck - 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 - repo: local
hooks: hooks:
- id: go-mod-tidy - id: go-mod-tidy

View File

@@ -22,7 +22,7 @@ nix develop
This will provide: 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 - Quickshell and required QML packages
- Properly configured QML2_IMPORT_PATH - 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 ## Building
Requires Go 1.24+ Requires Go 1.25+
**Development build:** **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"} 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" url := doctorDocsURL + "#optional-features"
desc := "Extra image format support (AVIF, HEIF, JXL)"
pluginDir := findQtPluginDir() pluginDir := findQtPluginDir()
if pluginDir == "" { if pluginDir == "" {
return checkResult{catOptionalFeatures, "kimageformats", statusInfo, "Cannot detect (qtpaths not found)", desc, url} 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},
}
} }
imageFormatsDir := filepath.Join(pluginDir, "imageformats") imageFormatsDir := filepath.Join(pluginDir, "imageformats")
keyPlugins := []struct{ file, format string }{
type pluginCheck struct {
name string
desc string
plugins []struct{ file, format string }
}
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_avif.so", "AVIF"},
{"kimg_heif.so", "HEIF"}, {"kimg_heif.so", "HEIF"},
{"kimg_jxl.so", "JXL"}, {"kimg_jxl.so", "JXL"},
{"kimg_exr.so", "EXR"}, {"kimg_exr.so", "EXR"},
},
},
} }
var results []checkResult
for _, c := range checks {
var found []string var found []string
for _, p := range keyPlugins { for _, p := range c.plugins {
if _, err := os.Stat(filepath.Join(imageFormatsDir, p.file)); err == nil { if _, err := os.Stat(filepath.Join(imageFormatsDir, p.file)); err == nil {
found = append(found, p.format) found = append(found, p.format)
} }
} }
if len(found) == 0 { var result checkResult
return checkResult{catOptionalFeatures, "kimageformats", statusWarn, "Not installed", desc, url} switch {
} case len(found) == 0:
result = checkResult{catOptionalFeatures, c.name, statusWarn, "Not installed", c.desc, url}
default:
details := "" details := ""
if doctorVerbose { if doctorVerbose {
details = fmt.Sprintf("Formats: %s (%s)", strings.Join(found, ", "), imageFormatsDir) 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 checkResult{catOptionalFeatures, "kimageformats", statusOK, fmt.Sprintf("Installed (%d formats)", len(found)), details, url} return results
} }
func findQtPluginDir() string { 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, checkResult{catOptionalFeatures, "cups-pk-helper", cupsPkStatus, cupsPkMsg, "Printer management", optionalFeaturesURL})
results = append(results, checkI2CAvailability()) results = append(results, checkI2CAvailability())
results = append(results, checkKImageFormats()) results = append(results, checkImageFormatPlugins()...)
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"} terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 { if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {

View File

@@ -14,7 +14,7 @@ import (
var ( var (
ssOutputName string ssOutputName string
ssIncludeCursor bool ssCursor string
ssFormat string ssFormat string
ssQuality int ssQuality int
ssOutputDir string ssOutputDir string
@@ -52,7 +52,7 @@ Examples:
dms screenshot last # Last region (pre-selected) dms screenshot last # Last region (pre-selected)
dms screenshot --no-clipboard # Save file only dms screenshot --no-clipboard # Save file only
dms screenshot --no-file # Clipboard 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`, dms screenshot -f jpg -q 85 # JPEG with quality 85`,
} }
@@ -111,7 +111,7 @@ var notifyActionCmd = &cobra.Command{
func init() { func init() {
screenshotCmd.PersistentFlags().StringVarP(&ssOutputName, "output", "o", "", "Output name for 'output' mode") 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().StringVarP(&ssFormat, "format", "f", "png", "Output format (png, jpg, ppm)")
screenshotCmd.PersistentFlags().IntVarP(&ssQuality, "quality", "q", 90, "JPEG quality (1-100)") screenshotCmd.PersistentFlags().IntVarP(&ssQuality, "quality", "q", 90, "JPEG quality (1-100)")
screenshotCmd.PersistentFlags().StringVarP(&ssOutputDir, "dir", "d", "", "Output directory") screenshotCmd.PersistentFlags().StringVarP(&ssOutputDir, "dir", "d", "", "Output directory")
@@ -136,7 +136,9 @@ func getScreenshotConfig(mode screenshot.Mode) screenshot.Config {
config := screenshot.DefaultConfig() config := screenshot.DefaultConfig()
config.Mode = mode config.Mode = mode
config.OutputName = ssOutputName config.OutputName = ssOutputName
config.IncludeCursor = ssIncludeCursor if strings.EqualFold(ssCursor, "on") {
config.Cursor = screenshot.CursorOn
}
config.Clipboard = !ssNoClipboard config.Clipboard = !ssNoClipboard
config.SaveFile = !ssNoFile config.SaveFile = !ssNoFile
config.Notify = !ssNoNotify 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 ^(org\.gnome\.Nautilus)$
windowrule = float on, match:class ^(xdg-desktop-portal)$ 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 = pin on, match:class ^(steam)$, match:title ^(notificationtoasts)
windowrule = float on, match:class ^(firefox)$, match:title ^(Picture-in-Picture)$ 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)$ # windowrule = float on, match:class ^(org.quickshell)$
layerrule = no_anim on, match:namespace ^(quickshell)$ layerrule = no_anim on, match:namespace ^(quickshell)$
layerrule = no_anim on, match:namespace ^dms:.*
source = ./dms/colors.conf source = ./dms/colors.conf
source = ./dms/outputs.conf source = ./dms/outputs.conf

View File

@@ -26,6 +26,9 @@ func init() {
Register("cachyos", "#08A283", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution { Register("cachyos", "#08A283", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
return NewArchDistribution(config, logChan) 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 { Register("endeavouros", "#7F3FBF", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
return NewArchDistribution(config, logChan) return NewArchDistribution(config, logChan)
}) })

View File

@@ -430,7 +430,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
} }
// Add repository // 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{ progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages, Phase: PhaseSystemPackages,

View File

@@ -341,6 +341,8 @@ func (n *NiriProvider) buildActionFromNode(bindNode *document.Node) string {
val := arg.ValueString() val := arg.ValueString()
if val == "" { if val == "" {
parts = append(parts, `""`) parts = append(parts, `""`)
} else if strings.ContainsAny(val, " \t") {
parts = append(parts, `"`+strings.ReplaceAll(val, `"`, `\"`)+`"`)
} else { } else {
parts = append(parts, val) parts = append(parts, val)
} }

View File

@@ -33,6 +33,7 @@ const (
TemplateKindTerminal TemplateKindTerminal
TemplateKindGTK TemplateKindGTK
TemplateKindVSCode TemplateKindVSCode
TemplateKindEmacs
) )
type TemplateDef struct { type TemplateDef struct {
@@ -65,7 +66,7 @@ var templateRegistry = []TemplateDef{
{ID: "dgop", Commands: []string{"dgop"}, ConfigFile: "dgop.toml"}, {ID: "dgop", Commands: []string{"dgop"}, ConfigFile: "dgop.toml"},
{ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true}, {ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
{ID: "vscode", Kind: TemplateKindVSCode}, {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 { func (c *ColorMode) GTKTheme() string {
@@ -78,7 +79,8 @@ func (c *ColorMode) GTKTheme() string {
} }
var ( var (
matugenVersionOnce sync.Once matugenVersionMu sync.Mutex
matugenVersionOK bool
matugenSupportsCOE bool matugenSupportsCOE bool
matugenIsV4 bool matugenIsV4 bool
) )
@@ -334,6 +336,10 @@ output_path = '%s'
appendVSCodeConfig(cfgFile, "cursor", filepath.Join(homeDir, ".cursor/extensions"), opts.ShellDir) appendVSCodeConfig(cfgFile, "cursor", filepath.Join(homeDir, ".cursor/extensions"), opts.ShellDir)
appendVSCodeConfig(cfgFile, "windsurf", filepath.Join(homeDir, ".windsurf/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) 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: default:
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile) 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, "'CONFIG_DIR/", "'"+utils.XDGConfigHome()+"/")
result = strings.ReplaceAll(result, "'DATA_DIR/", "'"+utils.XDGDataHome()+"/") result = strings.ReplaceAll(result, "'DATA_DIR/", "'"+utils.XDGDataHome()+"/")
result = strings.ReplaceAll(result, "'CACHE_DIR/", "'"+utils.XDGCacheHome()+"/") result = strings.ReplaceAll(result, "'CACHE_DIR/", "'"+utils.XDGCacheHome()+"/")
if emacsDir := utils.EmacsConfigDir(); emacsDir != "" {
result = strings.ReplaceAll(result, "'EMACS_DIR/", "'"+emacsDir+"/")
}
return result return result
} }
@@ -511,12 +520,40 @@ func extractTOMLSection(content, startMarker, endMarker string) string {
return content[startIdx : startIdx+endIdx] return content[startIdx : startIdx+endIdx]
} }
func checkMatugenVersion() { type matugenFlags struct {
matugenVersionOnce.Do(func() { supportsCOE bool
isV4 bool
}
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") cmd := exec.Command("matugen", "--version")
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
return return matugenFlags{}, fmt.Errorf("failed to get matugen version: %w", err)
} }
versionStr := strings.TrimSpace(string(output)) versionStr := strings.TrimSpace(string(output))
@@ -524,66 +561,119 @@ func checkMatugenVersion() {
parts := strings.Split(versionStr, ".") parts := strings.Split(versionStr, ".")
if len(parts) < 2 { if len(parts) < 2 {
return return matugenFlags{}, fmt.Errorf("unexpected matugen version format: %q", versionStr)
} }
major, err := strconv.Atoi(parts[0]) major, err := strconv.Atoi(parts[0])
if err != nil { if err != nil {
return return matugenFlags{}, fmt.Errorf("failed to parse matugen major version %q: %w", parts[0], err)
} }
minor, err := strconv.Atoi(parts[1]) minor, err := strconv.Atoi(parts[1])
if err != nil { if err != nil {
return return matugenFlags{}, fmt.Errorf("failed to parse matugen minor version %q: %w", parts[1], err)
} }
matugenSupportsCOE = major > 3 || (major == 3 && minor >= 1) matugenSupportsCOE = major > 3 || (major == 3 && minor >= 1)
matugenIsV4 = major >= 4 matugenIsV4 = major >= 4
matugenVersionOK = true
if matugenSupportsCOE { if matugenSupportsCOE {
log.Infof("Matugen %s supports --continue-on-error", versionStr) log.Infof("Matugen %s supports --continue-on-error", versionStr)
} }
if matugenIsV4 { if matugenIsV4 {
log.Infof("Matugen %s: using v4 flags", versionStr) log.Infof("Matugen %s: using v4 flags", versionStr)
} }
}) return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil
} }
func runMatugen(args []string) error { func buildMatugenArgs(baseArgs []string, flags matugenFlags) []string {
checkMatugenVersion() args := make([]string, 0, len(baseArgs)+4)
if flags.supportsCOE {
if matugenSupportsCOE { args = append(args, "--continue-on-error")
args = append([]string{"--continue-on-error"}, args...)
} }
if matugenIsV4 { args = append(args, baseArgs...)
if flags.isV4 {
args = append(args, "--source-color-index", "0") 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 := exec.Command("matugen", args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr 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) { func runMatugenDryRun(opts *Options) (string, error) {
checkMatugenVersion() flags, err := detectMatugenVersion()
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()
if err != nil { if err != nil {
return "", err 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 return strings.ReplaceAll(string(output), "\n", ""), nil
} }
@@ -819,6 +909,8 @@ func CheckTemplates(checker utils.AppChecker) []TemplateCheck {
detected = true detected = true
case tmpl.Kind == TemplateKindVSCode: case tmpl.Kind == TemplateKindVSCode:
detected = checkVSCodeExtension(homeDir) detected = checkVSCodeExtension(homeDir)
case tmpl.Kind == TemplateKindEmacs:
detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks) && utils.EmacsConfigDir() != ""
default: default:
detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks) 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 l := 0
objectID := client.Uint32(data[l : l+4]) objectID := client.Uint32(data[l : l+4])
proxy := i.Context().GetProxy(objectID) proxy := i.Context().GetProxy(objectID)
if proxy != nil { if proxy != nil && !proxy.IsZombie() {
e.WorkspaceGroup = proxy.(*ExtWorkspaceGroupHandleV1) e.WorkspaceGroup = proxy.(*ExtWorkspaceGroupHandleV1)
} else { } else {
groupHandle := &ExtWorkspaceGroupHandleV1{} groupHandle := &ExtWorkspaceGroupHandleV1{}
@@ -278,7 +278,7 @@ func (i *ExtWorkspaceManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
l := 0 l := 0
objectID := client.Uint32(data[l : l+4]) objectID := client.Uint32(data[l : l+4])
proxy := i.Context().GetProxy(objectID) proxy := i.Context().GetProxy(objectID)
if proxy != nil { if proxy != nil && !proxy.IsZombie() {
e.Workspace = proxy.(*ExtWorkspaceHandleV1) e.Workspace = proxy.(*ExtWorkspaceHandleV1)
} else { } else {
wsHandle := &ExtWorkspaceHandleV1{} wsHandle := &ExtWorkspaceHandleV1{}

View File

@@ -108,7 +108,7 @@ func NewRegionSelector(s *Screenshoter) *RegionSelector {
screenshoter: s, screenshoter: s,
outputs: make(map[uint32]*WaylandOutput), outputs: make(map[uint32]*WaylandOutput),
preCapture: make(map[*WaylandOutput]*PreCapture), 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) { func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult, error) {
cursor := int32(0) cursor := int32(s.config.Cursor)
if s.config.IncludeCursor {
cursor = 1
}
frame, err := s.screencopy.CaptureOutput(cursor, output.wlOutput) frame, err := s.screencopy.CaptureOutput(cursor, output.wlOutput)
if err != nil { if err != nil {
@@ -624,10 +621,7 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
} }
} }
cursor := int32(0) cursor := int32(s.config.Cursor)
if s.config.IncludeCursor {
cursor = 1
}
frame, err := s.screencopy.CaptureOutputRegion(cursor, output.wlOutput, localX, localY, w, h) frame, err := s.screencopy.CaptureOutputRegion(cursor, output.wlOutput, localX, localY, w, h)
if err != nil { if err != nil {

View File

@@ -19,6 +19,13 @@ const (
FormatPPM FormatPPM
) )
type CursorMode int
const (
CursorOff CursorMode = iota
CursorOn
)
type Region struct { type Region struct {
X int32 `json:"x"` X int32 `json:"x"`
Y int32 `json:"y"` Y int32 `json:"y"`
@@ -44,7 +51,7 @@ type Output struct {
type Config struct { type Config struct {
Mode Mode Mode Mode
OutputName string OutputName string
IncludeCursor bool Cursor CursorMode
Format Format Format Format
Quality int Quality int
OutputDir string OutputDir string
@@ -58,7 +65,7 @@ type Config struct {
func DefaultConfig() Config { func DefaultConfig() Config {
return Config{ return Config{
Mode: ModeRegion, Mode: ModeRegion,
IncludeCursor: false, Cursor: CursorOff,
Format: FormatPNG, Format: FormatPNG,
Quality: 90, Quality: 90,
OutputDir: "", OutputDir: "",

View File

@@ -232,8 +232,15 @@ func (m *Manager) setupDataDeviceSync() {
return return
} }
prevOffer := m.currentOffer
m.currentOffer = offer m.currentOffer = offer
if prevOffer != nil && prevOffer != offer {
m.offerMutex.Lock()
delete(m.offerMimeTypes, prevOffer)
m.offerMutex.Unlock()
}
m.offerMutex.RLock() m.offerMutex.RLock()
mimes := m.offerMimeTypes[offer] mimes := m.offerMimeTypes[offer]
m.offerMutex.RUnlock() m.offerMutex.RUnlock()
@@ -587,21 +594,27 @@ func (m *Manager) uriListPreview(data []byte) (string, bool) {
uris = strings.Split(text, "\n") 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://") { if len(uris) == 1 && strings.HasPrefix(uris[0], "file://") {
filePath := strings.TrimPrefix(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 imgData, err := os.ReadFile(filePath); err == nil {
if config, imgFmt, err := image.DecodeConfig(bytes.NewReader(imgData)); 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 %s %dx%d ]]", filepath.Base(filePath), imgFmt, config.Width, config.Height), true
} }
} }
}
return fmt.Sprintf("[[ file %s ]]", filepath.Base(filePath)), false return fmt.Sprintf("[[ file %s ]]", filepath.Base(filePath)), false
} }
}
if len(uris) > 1 {
return fmt.Sprintf("[[ %d files ]]", len(uris)), false
}
return m.textPreview(data), false return m.textPreview(data), false
} }
@@ -623,6 +636,11 @@ func (m *Manager) tryReadImageFromURI(data []byte) ([]byte, string, bool) {
return nil, "", false return nil, "", false
} }
cfg := m.getConfig()
if info.Size() > cfg.MaxEntrySize {
return nil, "", false
}
imgData, err := os.ReadFile(filePath) imgData, err := os.ReadFile(filePath)
if err != nil { if err != nil {
return nil, "", false return nil, "", false

View File

@@ -16,4 +16,8 @@ const (
dbusScreensaverPath = "/ScreenSaver" dbusScreensaverPath = "/ScreenSaver"
dbusScreensaverPath2 = "/org/freedesktop/ScreenSaver" dbusScreensaverPath2 = "/org/freedesktop/ScreenSaver"
dbusScreensaverInterface = "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 return true
}) })
m.screensaverSubscribers.Range(func(key string, ch chan ScreensaverState) bool {
close(ch)
m.screensaverSubscribers.Delete(key)
return true
})
if m.systemConn != nil { if m.systemConn != nil {
m.systemConn.Close() m.systemConn.Close()
} }

View File

@@ -1,6 +1,7 @@
package freedesktop package freedesktop
import ( import (
"fmt"
"path/filepath" "path/filepath"
"strings" "strings"
"sync/atomic" "sync/atomic"
@@ -15,45 +16,9 @@ type screensaverHandler struct {
manager *Manager manager *Manager
} }
func (m *Manager) initializeScreensaver() error { func screensaverIntrospectIface(ifaceName string) introspect.Interface {
if m.sessionConn == nil { return introspect.Interface{
m.stateMutex.Lock() Name: ifaceName,
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,
Methods: []introspect.Method{ Methods: []introspect.Method{
{ {
Name: "Inhibit", Name: "Inhibit",
@@ -69,40 +34,106 @@ func (m *Manager) initializeScreensaver() error {
{Name: "cookie", Type: "u", Direction: "in"}, {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{ handler := &screensaverHandler{manager: m}
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)
}
introNode2 := &introspect.Node{ m.screensaverFreedesktopClaimed = m.claimScreensaverName(handler,
Name: dbusScreensaverPath2, dbusScreensaverName, dbusScreensaverInterface, dbusScreensaverPath, dbusScreensaverPath2)
Interfaces: []introspect.Interface{ m.screensaverGnomeClaimed = m.claimScreensaverName(handler,
introspect.IntrospectData, dbusGnomeScreensaverName, dbusGnomeScreensaverInterface, dbusGnomeScreensaverPath)
screensaverIface,
}, if !m.screensaverFreedesktopClaimed && !m.screensaverGnomeClaimed {
} log.Warn("No screensaver interface could be claimed")
if err := m.sessionConn.Export(introspect.NewIntrospectable(introNode2), dbusScreensaverPath2, "org.freedesktop.DBus.Introspectable"); err != nil { m.stateMutex.Lock()
log.Warnf("Failed to export introspectable on %s: %v", dbusScreensaverPath2, err) m.state.Screensaver.Available = false
m.stateMutex.Unlock()
return nil
} }
go m.watchPeerDisconnects() go m.watchPeerDisconnects()
m.stateMutex.Lock() m.stateMutex.Lock()
m.state.Screensaver.Available = true m.state.Screensaver.Available = true
m.state.Screensaver.Active = false
m.state.Screensaver.Inhibited = false m.state.Screensaver.Inhibited = false
m.state.Screensaver.Inhibitors = []ScreensaverInhibitor{} m.state.Screensaver.Inhibitors = []ScreensaverInhibitor{}
m.stateMutex.Unlock() 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 return nil
} }
@@ -268,3 +299,51 @@ func (m *Manager) NotifyScreensaverSubscribers() {
return true 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 { type ScreensaverState struct {
Available bool `json:"available"` Available bool `json:"available"`
Active bool `json:"active"`
Inhibited bool `json:"inhibited"` Inhibited bool `json:"inhibited"`
Inhibitors []ScreensaverInhibitor `json:"inhibitors"` Inhibitors []ScreensaverInhibitor `json:"inhibitors"`
} }
@@ -60,4 +61,6 @@ type Manager struct {
subscribers syncmap.Map[string, chan FreedeskState] subscribers syncmap.Map[string, chan FreedeskState]
screensaverSubscribers syncmap.Map[string, chan ScreensaverState] screensaverSubscribers syncmap.Map[string, chan ScreensaverState]
screensaverCookieCounter uint32 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() { go func() {
defer close(loginctlReady)
if err := InitializeLoginctlManager(); err != nil { if err := InitializeLoginctlManager(); err != nil {
log.Warnf("Loginctl manager unavailable: %v", err) log.Warnf("Loginctl manager unavailable: %v", err)
} else { } else {
@@ -1525,6 +1529,7 @@ func Start(printDocs bool) error {
}() }()
go func() { go func() {
defer close(freedesktopReady)
if err := InitializeFreedeskManager(); err != nil { if err := InitializeFreedeskManager(); err != nil {
log.Warnf("Freedesktop manager unavailable: %v", err) log.Warnf("Freedesktop manager unavailable: %v", err)
} else if freedesktopManager != nil { } 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 { if err := InitializeWaylandManager(); err != nil {
log.Warnf("Wayland manager unavailable: %v", err) log.Warnf("Wayland manager unavailable: %v", err)
} }

View File

@@ -162,7 +162,7 @@ func TestCleanupStaleSockets(t *testing.T) {
tempDir := t.TempDir() tempDir := t.TempDir()
t.Setenv("XDG_RUNTIME_DIR", 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) err := os.WriteFile(staleSocket, []byte{}, 0o600)
require.NoError(t, err) require.NoError(t, err)

View File

@@ -38,6 +38,22 @@ func XDGConfigHome() string {
return filepath.Join(home, ".config") 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) { func ExpandPath(path string) (string, error) {
expanded := os.ExpandEnv(path) expanded := os.ExpandEnv(path)
expanded = filepath.Clean(expanded) expanded = filepath.Clean(expanded)

View File

@@ -181,7 +181,7 @@
buildInputs = buildInputs =
with pkgs; with pkgs;
[ [
go_1_24 go_1_25
gopls gopls
delve delve
go-tools go-tools
@@ -189,6 +189,7 @@
prek prek
uv # for prek uv # for prek
shellcheck
# Nix development tools # Nix development tools
nixd nixd

View File

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

View File

@@ -60,6 +60,7 @@ Singleton {
property bool _hasLoaded: false property bool _hasLoaded: false
property bool _isReadOnly: false property bool _isReadOnly: false
property bool _hasUnsavedChanges: false property bool _hasUnsavedChanges: false
property bool _selfWrite: false
property var _loadedSettingsSnapshot: null property var _loadedSettingsSnapshot: null
property var pluginSettings: ({}) property var pluginSettings: ({})
property var builtInPluginSettings: ({}) property var builtInPluginSettings: ({})
@@ -1243,6 +1244,7 @@ Singleton {
function saveSettings() { function saveSettings() {
if (_loading || _parseError || !_hasLoaded) if (_loading || _parseError || !_hasLoaded)
return; return;
_selfWrite = true;
settingsFile.setText(JSON.stringify(Store.toJson(root), null, 2)); settingsFile.setText(JSON.stringify(Store.toJson(root), null, 2));
if (_isReadOnly) if (_isReadOnly)
_checkSettingsWritable(); _checkSettingsWritable();
@@ -2589,7 +2591,13 @@ Singleton {
blockWrites: true blockWrites: true
atomicWrites: true atomicWrites: true
watchChanges: true watchChanges: true
onFileChanged: settingsFileReloadDebounce.restart() onFileChanged: {
if (_selfWrite) {
_selfWrite = false;
return;
}
settingsFileReloadDebounce.restart();
}
onLoaded: { onLoaded: {
if (isGreeterMode) if (isGreeterMode)
return; return;

View File

@@ -859,6 +859,70 @@ Item {
return success ? `WIDGET_TOGGLE_SUCCESS: ${widgetId}` : `WIDGET_TOGGLE_FAILED: ${widgetId}`; 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 { function list(): string {
const widgets = BarWidgetService.getRegisteredWidgetIds(); const widgets = BarWidgetService.getRegisteredWidgetIds();
if (widgets.length === 0) if (widgets.length === 0)

View File

@@ -18,7 +18,7 @@ FloatingWindow {
} }
objectName: "changelogModal" objectName: "changelogModal"
title: "What's New" title: i18n("What's New")
minimumSize: Qt.size(modalWidth, modalHeight) minimumSize: Qt.size(modalWidth, modalHeight)
maximumSize: Qt.size(modalWidth, modalHeight) maximumSize: Qt.size(modalWidth, modalHeight)
color: Theme.surfaceContainer color: Theme.surfaceContainer
@@ -81,7 +81,7 @@ FloatingWindow {
onClicked: root.dismiss() onClicked: root.dismiss()
DankTooltip { DankTooltip {
text: "Close" text: i18n("Close")
} }
} }
} }
@@ -125,7 +125,7 @@ FloatingWindow {
spacing: Theme.spacingM spacing: Theme.spacingM
DankButton { DankButton {
text: "Read Full Release Notes" text: i18n("Read Full Release Notes")
iconName: "open_in_new" iconName: "open_in_new"
backgroundColor: Theme.surfaceContainerHighest backgroundColor: Theme.surfaceContainerHighest
textColor: Theme.surfaceText textColor: Theme.surfaceText
@@ -133,7 +133,7 @@ FloatingWindow {
} }
DankButton { DankButton {
text: "Got It" text: i18n("Got It")
iconName: "check" iconName: "check"
backgroundColor: Theme.primary backgroundColor: Theme.primary
textColor: Theme.primaryText textColor: Theme.primaryText

View File

@@ -12,7 +12,7 @@ Singleton {
readonly property int popoutWidth: 550 readonly property int popoutWidth: 550
readonly property int popoutHeight: 500 readonly property int popoutHeight: 500
readonly property int itemHeight: 72 readonly property int itemHeight: 72
readonly property int thumbnailSize: 48 readonly property int thumbnailSize: 100
readonly property int retryInterval: 50 readonly property int retryInterval: 50
readonly property int viewportBuffer: 100 readonly property int viewportBuffer: 100
readonly property int extendedBuffer: 200 readonly property int extendedBuffer: 200

View File

@@ -84,7 +84,8 @@ Rectangle {
anchors.right: actionButtons.left anchors.right: actionButtons.left
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: contentColumn.implicitHeight // height: contentColumn.implicitHeight
height: ClipboardConstants.itemHeight
clip: true clip: true
ClipboardThumbnail { ClipboardThumbnail {
@@ -92,7 +93,7 @@ Rectangle {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: entryType === "image" ? ClipboardConstants.thumbnailSize : Theme.iconSize 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 entry: root.entry
entryType: root.entryType entryType: root.entryType
modal: root.modal modal: root.modal

View File

@@ -137,23 +137,23 @@ Item {
anchors.margins: 2 anchors.margins: 2
source: thumbnailImage source: thumbnailImage
maskEnabled: true maskEnabled: true
maskSource: clipboardCircularMask maskSource: clipboardRoundedRectangularMask
visible: entryType === "image" && thumbnailImage.status === Image.Ready && thumbnailImage.source != "" visible: entryType === "image" && thumbnailImage.status === Image.Ready && thumbnailImage.source != ""
maskThresholdMin: 0.5 maskThresholdMin: 0.5
maskSpreadAtMin: 1 maskSpreadAtMin: 1
} }
Item { Item {
id: clipboardCircularMask id: clipboardRoundedRectangularMask
width: ClipboardConstants.thumbnailSize - 4 width: ClipboardConstants.thumbnailSize
height: ClipboardConstants.thumbnailSize - 4 height: ClipboardConstants.itemHeight - 4
layer.enabled: true layer.enabled: true
layer.smooth: true layer.smooth: true
visible: false visible: false
Rectangle { Rectangle {
anchors.fill: parent 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" color: "black"
antialiasing: true antialiasing: true
} }

View File

@@ -56,11 +56,6 @@ Rectangle {
case "app": case "app":
if (selectedItem?.isCore) if (selectedItem?.isCore)
break; break;
if (selectedItem?.actions) {
for (var i = 0; i < selectedItem.actions.length; i++) {
result.push(selectedItem.actions[i]);
}
}
if (SessionService.nvidiaCommand) { if (SessionService.nvidiaCommand) {
result.push({ result.push({
name: I18n.tr("Launch on dGPU"), name: I18n.tr("Launch on dGPU"),
@@ -68,6 +63,11 @@ Rectangle {
action: "launch_dgpu" action: "launch_dgpu"
}); });
} }
if (selectedItem?.actions) {
for (var i = 0; i < selectedItem.actions.length; i++) {
result.push(selectedItem.actions[i]);
}
}
break; break;
} }
return result; return result;

View File

@@ -125,13 +125,6 @@ Item {
} }
readonly property var sectionDefinitions: [ readonly property var sectionDefinitions: [
{
id: "calculator",
title: I18n.tr("Calculator"),
icon: "calculate",
priority: 0,
defaultViewMode: "list"
},
{ {
id: "favorites", id: "favorites",
title: I18n.tr("Pinned"), title: I18n.tr("Pinned"),
@@ -681,12 +674,6 @@ Item {
return; return;
} }
var calculatorResult = evaluateCalculator(searchQuery);
if (calculatorResult) {
calculatorResult._preScored = 12000;
allItems.push(calculatorResult);
}
var apps = searchApps(searchQuery); var apps = searchApps(searchQuery);
for (var i = 0; i < apps.length; i++) { for (var i = 0; i < apps.length; i++) {
if (searchQuery) if (searchQuery)
@@ -697,12 +684,15 @@ Item {
if (searchMode === "all") { if (searchMode === "all") {
if (searchQuery && searchQuery.length >= 2) { if (searchQuery && searchQuery.length >= 2) {
_pluginPhasePending = true; _pluginPhasePending = true;
_pluginPhaseForceFirst = shouldResetSelection; _phase1Items = allItems.slice();
_phase1Items = allItems;
pluginPhaseTimer.restart(); pluginPhaseTimer.restart();
if (allItems.length === 0) {
_pluginPhaseForceFirst = shouldResetSelection;
isSearching = true; isSearching = true;
searchCompleted(); searchCompleted();
return; return;
}
_pluginPhaseForceFirst = false;
} else if (!searchQuery) { } else if (!searchQuery) {
var emptyTriggerOrdered = getEmptyTriggerPluginsOrdered(); var emptyTriggerOrdered = getEmptyTriggerPluginsOrdered();
for (var i = 0; i < emptyTriggerOrdered.length; i++) { 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")); 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) { function detectTrigger(query) {
if (!query || query.length === 0) if (!query || query.length === 0)
return { return {
@@ -1270,6 +1253,23 @@ Item {
CacheData.saveLauncherCache(serializable); 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() { function _loadDiskCache() {
var cached = CacheData.loadLauncherCache(); var cached = CacheData.loadLauncherCache();
if (!cached || !Array.isArray(cached) || cached.length === 0) if (!cached || !Array.isArray(cached) || cached.length === 0)
@@ -1297,8 +1297,12 @@ Item {
data: { data: {
id: it.id id: it.id
}, },
actions: [], actions: _actionsFromDesktopEntry(it.id),
primaryAction: null, primaryAction: it.type === "app" && !it.isCore ? {
name: I18n.tr("Launch"),
icon: "open_in_new",
action: "launch"
} : null,
_diskCached: true, _diskCached: true,
_hName: "", _hName: "",
_hSub: "", _hSub: "",
@@ -1559,9 +1563,6 @@ Item {
case "file": case "file":
openFile(item.data?.path); openFile(item.data?.path);
break; break;
case "calculator":
copyToClipboard(item.name);
break;
default: default:
return; return;
} }
@@ -1616,25 +1617,41 @@ Item {
itemExecuted(); itemExecuted();
} }
function launchApp(app) { function _resolveDesktopEntry(app) {
if (!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; return;
SessionService.launchDesktopEntry(app); SessionService.launchDesktopEntry(entry);
AppUsageHistoryData.addAppUsage(app); AppUsageHistoryData.addAppUsage(entry);
} }
function launchAppWithNvidia(app) { function launchAppWithNvidia(app) {
if (!app) var entry = _resolveDesktopEntry(app);
if (!entry)
return; return;
SessionService.launchDesktopEntry(app, true); SessionService.launchDesktopEntry(entry, true);
AppUsageHistoryData.addAppUsage(app); AppUsageHistoryData.addAppUsage(entry);
} }
function launchAppAction(actionItem) { function launchAppAction(actionItem) {
if (!actionItem || !actionItem.parentApp || !actionItem.actionData) if (!actionItem || !actionItem.actionData)
return; return;
SessionService.launchDesktopAction(actionItem.parentApp, actionItem.actionData); var entry = _resolveDesktopEntry(actionItem.parentApp);
AppUsageHistoryData.addAppUsage(actionItem.parentApp); if (!entry)
return;
SessionService.launchDesktopAction(entry, actionItem.actionData);
AppUsageHistoryData.addAppUsage(entry);
} }
function openFile(path) { function openFile(path) {

View File

@@ -101,35 +101,6 @@ function detectIconType(iconName) {
return "material"; 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) { function sortPluginIdsByOrder(pluginIds, order) {
if (!order || order.length === 0) if (!order || order.length === 0)
return pluginIds; 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) { function createPluginBrowseItem(pluginId, plugin, trigger, isBuiltIn, isAllowed, browseLabel, triggerLabel, noTriggerLabel) {
var rawIcon = isBuiltIn ? (plugin.cornerIcon || "extension") : (plugin.icon || "extension"); var rawIcon = isBuiltIn ? (plugin.cornerIcon || "extension") : (plugin.icon || "extension");
return { return {

View File

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

View File

@@ -81,6 +81,12 @@ function calculateNextIndex(flatModel, selectedFlatIndex, sectionId, viewMode, g
return bounds.start + newPosInSection; 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); var nextSection = findNextNonHeaderIndex(flatModel, bounds.end + 1);
return nextSection !== -1 ? nextSection : selectedFlatIndex; return nextSection !== -1 ? nextSection : selectedFlatIndex;
} }

View File

@@ -178,8 +178,6 @@ Rectangle {
if (!root.item) if (!root.item)
return ""; return "";
switch (root.item.type) { switch (root.item.type) {
case "calculator":
return I18n.tr("Calc");
case "plugin": case "plugin":
return I18n.tr("Plugin"); return I18n.tr("Plugin");
case "file": case "file":

View File

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

View File

@@ -238,37 +238,37 @@ FocusScope {
property var quickAccessLocations: [ property var quickAccessLocations: [
{ {
"name": "Home", "name": I18n.tr("Home"),
"path": homeDir, "path": homeDir,
"icon": "home" "icon": "home"
}, },
{ {
"name": "Documents", "name": I18n.tr("Documents"),
"path": docsDir, "path": docsDir,
"icon": "description" "icon": "description"
}, },
{ {
"name": "Downloads", "name": I18n.tr("Downloads"),
"path": downloadDir, "path": downloadDir,
"icon": "download" "icon": "download"
}, },
{ {
"name": "Pictures", "name": I18n.tr("Pictures"),
"path": picsDir, "path": picsDir,
"icon": "image" "icon": "image"
}, },
{ {
"name": "Music", "name": I18n.tr("Music"),
"path": musicDir, "path": musicDir,
"icon": "music_note" "icon": "music_note"
}, },
{ {
"name": "Videos", "name": I18n.tr("Videos"),
"path": videosDir, "path": videosDir,
"icon": "movie" "icon": "movie"
}, },
{ {
"name": "Desktop", "name": I18n.tr("Desktop"),
"path": desktopDir, "path": desktopDir,
"icon": "computer" "icon": "computer"
} }

View File

@@ -19,7 +19,7 @@ StyledRect {
spacing: 4 spacing: 4
StyledText { StyledText {
text: "Quick Access" text: I18n.tr("Quick Access")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -91,7 +91,12 @@ DankModal {
id: searchField id: searchField
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
leftIconName: "search" leftIconName: "search"
keyForwardTargets: [root.modalFocusScope]
onTextEdited: searchDebounce.restart() onTextEdited: searchDebounce.restart()
Keys.onEscapePressed: event => {
root.close();
event.accepted = true;
}
} }
} }
@@ -118,6 +123,7 @@ DankModal {
function generateCategories(query) { function generateCategories(query) {
const lowerQuery = query ? query.toLowerCase().trim() : ""; const lowerQuery = query ? query.toLowerCase().trim() : "";
const lowerQueryWords = query.split(/\s+/);
const processed = {}; const processed = {};
for (const cat in rawBinds) { for (const cat in rawBinds) {
@@ -130,10 +136,25 @@ DankModal {
const keyLower = bind.key.toLowerCase(); const keyLower = bind.key.toLowerCase();
const descLower = bind.desc.toLowerCase(); const descLower = bind.desc.toLowerCase();
const actionLower = bind.action.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) if (bind.hideOnOverlay)
continue; 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) { if (bind.subcat) {
hasSubcats = true; hasSubcats = true;

View File

@@ -83,9 +83,9 @@ FloatingWindow {
objectName: "processListModal" objectName: "processListModal"
title: I18n.tr("System Monitor", "sysmon window title") title: I18n.tr("System Monitor", "sysmon window title")
minimumSize: Qt.size(750, 550) minimumSize: Qt.size(Math.min(Math.round(Theme.fontSizeMedium * 48), Screen.width), Math.min(Math.round(Theme.fontSizeMedium * 34), Screen.height))
implicitWidth: 1000 implicitWidth: Math.round(Theme.fontSizeMedium * 71)
implicitHeight: 720 implicitHeight: Math.round(Theme.fontSizeMedium * 51)
color: Theme.surfaceContainer color: Theme.surfaceContainer
visible: false visible: false
@@ -236,7 +236,7 @@ FloatingWindow {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 48 Layout.preferredHeight: Math.round(Theme.fontSizeMedium * 3.4)
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@@ -293,10 +293,10 @@ FloatingWindow {
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 52 Layout.preferredHeight: Math.round(Theme.fontSizeMedium * 3.7)
Layout.leftMargin: Theme.spacingL Layout.leftMargin: Theme.spacingL
Layout.rightMargin: Theme.spacingL Layout.rightMargin: Theme.spacingL
spacing: Theme.spacingL spacing: Theme.spacingM
Row { Row {
spacing: 2 spacing: 2
@@ -322,14 +322,15 @@ FloatingWindow {
] ]
Rectangle { Rectangle {
width: 120 width: tabRowContent.implicitWidth + Theme.spacingM * 2
height: 44 height: Math.round(Theme.fontSizeMedium * 3.1)
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent") color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent")
border.color: currentTab === index ? Theme.primary : "transparent" border.color: currentTab === index ? Theme.primary : "transparent"
border.width: currentTab === index ? 1 : 0 border.width: currentTab === index ? 1 : 0
Row { Row {
id: tabRowContent
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
@@ -373,11 +374,13 @@ FloatingWindow {
DankButtonGroup { DankButtonGroup {
id: processFilterGroup id: processFilterGroup
Layout.minimumWidth: implicitWidth + 8
model: [I18n.tr("All"), I18n.tr("User"), I18n.tr("System")] model: [I18n.tr("All"), I18n.tr("User"), I18n.tr("System")]
currentIndex: 0 currentIndex: 0
checkEnabled: false checkEnabled: false
buttonHeight: 36 buttonHeight: Math.round(Theme.fontSizeSmall * 2.6)
minButtonWidth: 0
buttonPadding: Theme.spacingS
textSize: Theme.fontSizeSmall
visible: currentTab === 0 visible: currentTab === 0
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (!selected) if (!selected)
@@ -400,9 +403,9 @@ FloatingWindow {
DankTextField { DankTextField {
id: searchField id: searchField
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: 250 Layout.maximumWidth: Math.round(Theme.fontSizeMedium * 18)
Layout.minimumWidth: 120 Layout.minimumWidth: Theme.fontSizeMedium * 4
Layout.preferredHeight: 40 Layout.preferredHeight: Math.round(Theme.fontSizeMedium * 2.8)
placeholderText: I18n.tr("Search processes...", "process search placeholder") placeholderText: I18n.tr("Search processes...", "process search placeholder")
leftIconName: "search" leftIconName: "search"
showClearButton: true showClearButton: true
@@ -470,7 +473,7 @@ FloatingWindow {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 32 Layout.preferredHeight: Math.round(Theme.fontSizeSmall * 2.7)
Layout.leftMargin: Theme.spacingL Layout.leftMargin: Theme.spacingL
Layout.rightMargin: Theme.spacingL Layout.rightMargin: Theme.spacingL
Layout.bottomMargin: Theme.spacingM Layout.bottomMargin: Theme.spacingM

View File

@@ -8,10 +8,39 @@ DankPopout {
layerNamespace: "dms:app-launcher" layerNamespace: "dms:app-launcher"
property string _pendingMode: ""
property string _pendingQuery: ""
function show() { function show() {
open(); 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 popupWidth: 560
popupHeight: 640 popupHeight: 640
triggerWidth: 40 triggerWidth: 40
@@ -30,16 +59,26 @@ DankPopout {
var lc = contentLoader.item?.launcherContent; var lc = contentLoader.item?.launcherContent;
if (!lc) if (!lc)
return; return;
const query = _pendingQuery;
const mode = _pendingMode || "apps";
_pendingMode = "";
_pendingQuery = "";
if (lc.searchField) { if (lc.searchField) {
lc.searchField.text = ""; lc.searchField.text = query;
lc.searchField.forceActiveFocus(); lc.searchField.forceActiveFocus();
} }
if (lc.controller) { if (lc.controller) {
lc.controller.searchMode = "apps"; lc.controller.searchMode = mode;
lc.controller.pluginFilter = ""; lc.controller.pluginFilter = "";
lc.controller.searchQuery = ""; lc.controller.searchQuery = "";
if (query) {
lc.controller.setSearchQuery(query);
} else {
lc.controller.performSearch(); lc.controller.performSearch();
} }
}
lc.resetScroll?.(); lc.resetScroll?.();
lc.actionPanel?.hide(); lc.actionPanel?.hide();
} }

View File

@@ -15,18 +15,22 @@ Item {
property var pluginDetailInstance: null property var pluginDetailInstance: null
property var widgetModel: null property var widgetModel: null
property var collapseCallback: null property var collapseCallback: null
property real maxAvailableHeight: 9999
function getDetailHeight(section) { function getDetailHeight(section) {
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
switch (true) { switch (true) {
case section === "wifi": case section === "wifi":
case section === "bluetooth": case section === "bluetooth":
case section === "builtin_vpn": case section === "builtin_vpn":
return Math.min(350, maxAvailable); return Math.min(350, maxAvailableHeight);
case section.startsWith("brightnessSlider_"): 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: default:
return Math.min(250, maxAvailable); return Math.min(250, maxAvailableHeight);
} }
} }

View File

@@ -31,11 +31,26 @@ Column {
spacing: editMode ? Theme.spacingL : Theme.spacingS spacing: editMode ? Theme.spacingL : Theme.spacingS
property real maxPopoutHeight: 9999
property var currentRowWidgets: [] property var currentRowWidgets: []
property real currentRowWidth: 0 property real currentRowWidth: 0
property int expandedRowIndex: -1 property int expandedRowIndex: -1
property var colorPickerModal: null 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() { function calculateRowsAndWidgets() {
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex); return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex);
} }
@@ -163,6 +178,7 @@ Column {
DetailHost { DetailHost {
id: detailHost id: detailHost
width: parent.width width: parent.width
maxAvailableHeight: root._maxDetailHeight
height: active ? (getDetailHeight(root.expandedSection) + Theme.spacingS) : 0 height: active ? (getDetailHeight(root.expandedSection) + Theme.spacingS) : 0
property bool active: { property bool active: {
if (root.expandedSection === "") if (root.expandedSection === "")
@@ -945,10 +961,11 @@ Column {
} }
} }
Component.onCompleted: { function tryCreatePluginInstance() {
Qt.callLater(() => {
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]; const pluginComponent = PluginService.pluginWidgetComponents[pluginId];
if (pluginComponent) { if (!pluginComponent)
return false;
try {
const instance = pluginComponent.createObject(null, { const instance = pluginComponent.createObject(null, {
"pluginId": pluginId, "pluginId": pluginId,
"pluginService": PluginService, "pluginService": PluginService,
@@ -958,9 +975,17 @@ Column {
}); });
if (instance) { if (instance) {
pluginInstance = 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 { Connections {
@@ -970,6 +995,11 @@ Column {
pluginInstance.loadPluginData(); pluginInstance.loadPluginData();
} }
} }
function onPluginLoaded(loadedPluginId) {
if (loadedPluginId !== pluginId || pluginInstance)
return;
Qt.callLater(() => tryCreatePluginInstance());
}
} }
Component.onDestruction: { Component.onDestruction: {

View File

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

View File

@@ -12,6 +12,7 @@ DankPopout {
id: root id: root
layerNamespace: "dms:control-center" layerNamespace: "dms:control-center"
fullHeightSurface: true
property string expandedSection: "" property string expandedSection: ""
property var triggerScreen: null property var triggerScreen: null
@@ -115,6 +116,7 @@ DankPopout {
property alias bluetoothCodecSelector: bluetoothCodecSelector property alias bluetoothCodecSelector: bluetoothCodecSelector
color: "transparent" color: "transparent"
clip: true
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -165,6 +167,10 @@ DankPopout {
id: widgetGrid id: widgetGrid
width: parent.width width: parent.width
editMode: root.editMode editMode: root.editMode
maxPopoutHeight: {
const screenHeight = (root.triggerScreen?.height ?? 1080);
return screenHeight - 100 - Theme.spacingL - headerPane.height - Theme.spacingS;
}
expandedSection: root.expandedSection expandedSection: root.expandedSection
expandedWidgetIndex: root.expandedWidgetIndex expandedWidgetIndex: root.expandedWidgetIndex
expandedWidgetData: root.expandedWidgetData expandedWidgetData: root.expandedWidgetData

View File

@@ -40,6 +40,24 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter 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 { Row {
@@ -151,8 +169,11 @@ Rectangle {
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
values: { values: {
const hidden = SessionData.hiddenInputDeviceNames ?? [];
const nodes = Pipewire.nodes.values.filter(node => { 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(); const pinnedList = audioContent.getPinnedInputs();

View File

@@ -40,6 +40,24 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter 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 { Row {
@@ -161,8 +179,11 @@ Rectangle {
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
values: { values: {
const hidden = SessionData.hiddenOutputDeviceNames ?? [];
const nodes = Pipewire.nodes.values.filter(node => { 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(); const pinnedList = audioContent.getPinnedOutputs();

View File

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

View File

@@ -642,24 +642,52 @@ Item {
popoutTarget: appDrawerLoader.item popoutTarget: appDrawerLoader.item
parentScreen: barWindow.screen parentScreen: barWindow.screen
hyprlandOverviewLoader: barWindow ? barWindow.hyprlandOverviewLoader : null hyprlandOverviewLoader: barWindow ? barWindow.hyprlandOverviewLoader : null
onClicked: {
function _preparePopout() {
appDrawerLoader.active = true; 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; 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)); 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); appDrawerLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
} if (appDrawerLoader.item.setTriggerPosition) {
if (appDrawerLoader.item && appDrawerLoader.item.setTriggerPosition) {
const globalPos = launcherButton.visualContent.mapToItem(null, 0, 0); const globalPos = launcherButton.visualContent.mapToItem(null, 0, 0);
const currentScreen = barWindow.screen; const currentScreen = barWindow.screen;
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barWindow.effectiveBarThickness, launcherButton.visualWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig); 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); appDrawerLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, launcherButton.section, currentScreen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
} }
if (appDrawerLoader.item) { return true;
PopoutManager.requestPopout(appDrawerLoader.item, undefined, "appDrawer");
} }
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 { } else {
dankDashPopoutLoader.item.triggerScreen = barWindow.screen; 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 { } else {
dankDashPopoutLoader.item.triggerScreen = barWindow.screen; 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 { } else {
dankDashPopoutLoader.item.triggerScreen = barWindow.screen; 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; 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: { readonly property var dBarLayer: {

View File

@@ -99,13 +99,6 @@ BasePill {
width: implicitWidth width: implicitWidth
height: implicitHeight height: implicitHeight
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
StyledTextMetrics { StyledTextMetrics {
id: cpuBaseline id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)

View File

@@ -99,13 +99,6 @@ BasePill {
width: implicitWidth width: implicitWidth
height: implicitHeight height: implicitHeight
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
StyledTextMetrics { StyledTextMetrics {
id: tempBaseline id: tempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)

View File

@@ -193,13 +193,6 @@ BasePill {
} }
width: Math.max(diskBaseline.width, paintedWidth) 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 width: implicitWidth
height: implicitHeight height: implicitHeight
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
StyledTextMetrics { StyledTextMetrics {
id: gpuTempBaseline id: gpuTempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)

View File

@@ -110,13 +110,6 @@ BasePill {
} }
width: Math.max(rxBaseline.width, paintedWidth) 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) 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 width: implicitWidth
height: implicitHeight height: implicitHeight
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
StyledTextMetrics { StyledTextMetrics {
id: ramBaseline id: ramBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale) 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 real iconCellSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground) + 6
readonly property string focusedAppId: { readonly property string focusedAppId: {
const toplevels = CompositorService.sortedToplevels; if (!sortedToplevels || sortedToplevels.length === 0)
if (!toplevels)
return ""; return "";
let result = ""; for (let i = 0; i < sortedToplevels.length; i++) {
for (let i = 0; i < toplevels.length; i++) { if (sortedToplevels[i].activated)
if (toplevels[i].activated) return sortedToplevels[i].appId || "";
result = toplevels[i].appId || "";
} }
return result; return "";
} }
visible: windowCount > 0 visible: windowCount > 0
@@ -155,6 +153,7 @@ BasePill {
property real touchpadThreshold: 500 property real touchpadThreshold: 500
onWheel: function (wheelEvent) { onWheel: function (wheelEvent) {
wheelEvent.accepted = true;
const deltaY = wheelEvent.angleDelta.y; const deltaY = wheelEvent.angleDelta.y;
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0; const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;

View File

@@ -99,6 +99,44 @@ BasePill {
property bool suppressShiftAnimation: false property bool suppressShiftAnimation: false
readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length
visible: allTrayItems.length > 0 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 trayItemSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground) + 6
readonly property real minTooltipY: { readonly property real minTooltipY: {

View File

@@ -10,6 +10,45 @@ BasePill {
property bool isActive: false property bool isActive: false
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0 readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
readonly property bool isChecking: SystemUpdateService.isChecking readonly property bool isChecking: SystemUpdateService.isChecking
readonly property 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 { Ref {
service: SystemUpdateService service: SystemUpdateService

View File

@@ -924,8 +924,13 @@ Item {
return loadedIsUrgent; return loadedIsUrgent;
return false; return false;
} }
property var loadedIconData: null readonly property var loadedIconData: {
property bool loadedHasIcon: false 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: [] property var loadedIcons: []
readonly property int stableIconCount: { 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 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 hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
readonly property bool workspaceNamesEnabled: SettingsData.showWorkspaceName && (CompositorService.isNiri || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) 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 contentImplicitWidth: hasWorkspaceName ? (appIconsLoader.item?.contentWidth ?? 0) : 0
readonly property real contentImplicitHeight: (workspaceNamesEnabled || loadedHasIcon) ? (appIconsLoader.item?.contentHeight ?? 0) : 0 readonly property real contentImplicitHeight: workspaceNamesEnabled ? (appIconsLoader.item?.contentHeight ?? 0) : 0
readonly property real iconsExtraWidth: { readonly property real iconsExtraWidth: {
if (!root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) { if (!root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
@@ -1222,8 +1227,6 @@ Item {
onTriggered: { onTriggered: {
if (isPlaceholder) { if (isPlaceholder) {
delegateRoot.loadedWorkspaceData = null; delegateRoot.loadedWorkspaceData = null;
delegateRoot.loadedIconData = null;
delegateRoot.loadedHasIcon = false;
delegateRoot.loadedIcons = []; delegateRoot.loadedIcons = [];
delegateRoot.loadedIsUrgent = false; delegateRoot.loadedIsUrgent = false;
return; return;
@@ -1249,13 +1252,6 @@ Item {
delegateRoot.loadedIsUrgent = wsData?.urgent ?? false; 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 (SettingsData.showWorkspaceApps) {
if (CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { if (CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData); delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
@@ -1420,7 +1416,7 @@ Item {
Item { Item {
visible: loadedHasIcon && loadedIconData?.type === "icon" visible: loadedHasIcon && loadedIconData?.type === "icon"
width: wsIcon.width + (isActive && loadedIcons.length > 0 ? 4 : 0) width: wsIcon.width
height: root.appIconSize height: root.appIconSize
DankIcon { DankIcon {
@@ -1435,7 +1431,7 @@ Item {
Item { Item {
visible: loadedHasIcon && loadedIconData?.type === "text" visible: loadedHasIcon && loadedIconData?.type === "text"
width: wsText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0) width: wsText.implicitWidth
height: root.appIconSize height: root.appIconSize
StyledText { StyledText {
@@ -1449,14 +1445,14 @@ Item {
} }
Item { Item {
visible: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
width: wsIndexText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0) width: wsIndexText.implicitWidth
height: root.appIconSize height: root.appIconSize
StyledText { StyledText {
id: wsIndexText id: wsIndexText
anchors.verticalCenter: parent.verticalCenter 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 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.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
@@ -1574,9 +1570,9 @@ Item {
} }
StyledText { StyledText {
visible: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
anchors.horizontalCenter: parent.horizontalCenter 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 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.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal 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() Component.onCompleted: updateAllData()

View File

@@ -41,7 +41,7 @@ Card {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
text: UserInfoService.username || "brandon" text: UserInfoService.username || I18n.tr("brandon")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -64,18 +64,18 @@ Card {
StyledText { StyledText {
text: { text: {
if (CompositorService.isNiri) if (CompositorService.isNiri)
return "on niri"; return I18n.tr("on Niri");
if (CompositorService.isHyprland) 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 // technically they might not be on mangowc, but its what we support in the docs
if (CompositorService.isDwl) if (CompositorService.isDwl)
return "on MangoWC"; return I18n.tr("on MangoWC");
if (CompositorService.isSway) if (CompositorService.isSway)
return "on Sway"; return I18n.tr("on Sway");
if (CompositorService.isScroll) if (CompositorService.isScroll)
return "on Scroll"; return I18n.tr("on Scroll");
if (CompositorService.isMiracle) if (CompositorService.isMiracle)
return "on Miracle WM"; return I18n.tr("on Miracle WM");
return ""; return "";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -99,7 +99,7 @@ Card {
} }
StyledText { StyledText {
text: DgopService.shortUptime || "up" text: DgopService.shortUptime || I18n.tr("up")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -553,15 +553,7 @@ Variants {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, backgroundTransparency) color: Theme.withAlpha(Theme.surfaceContainer, 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)
radius: Theme.cornerRadius radius: Theme.cornerRadius
} }
} }

View File

@@ -165,7 +165,7 @@ PanelWindow {
} }
width: Math.min(400, Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)) 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) color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
@@ -388,11 +388,23 @@ PanelWindow {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
StyledText { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
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 anchors.verticalCenter: parent.verticalCenter
text: root.appData && root.appData.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock") text: root.appData && root.appData.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -401,6 +413,7 @@ PanelWindow {
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
}
DankRipple { DankRipple {
id: pinRipple id: pinRipple
@@ -448,11 +461,23 @@ PanelWindow {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: nvidiaArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: nvidiaArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
StyledText { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: "memory"
size: 14
color: Theme.surfaceText
opacity: 0.7
}
StyledText {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Launch on dGPU") text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -461,6 +486,7 @@ PanelWindow {
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
}
DankRipple { DankRipple {
id: nvidiaRipple id: nvidiaRipple
@@ -490,24 +516,32 @@ PanelWindow {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" color: closeArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
StyledText { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { spacing: Theme.spacingXS
if (root.appData && root.appData.type === "grouped") {
return "Close All Windows"; DankIcon {
} anchors.verticalCenter: parent.verticalCenter
return "Close Window"; 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 font.pixelSize: Theme.fontSizeSmall
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
}
DankRipple { DankRipple {
id: closeRipple id: closeRipple

View File

@@ -1065,7 +1065,7 @@ Item {
} }
StyledText { StyledText {
text: "Caps Lock is on" text: I18n.tr("Caps Lock is on")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.error color: Theme.error
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -278,7 +278,7 @@ Item {
Behavior on x { Behavior on x {
enabled: !swipeDragHandler.active && delegateRoot.__delegateInitialized enabled: !swipeDragHandler.active && delegateRoot.__delegateInitialized
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.notificationExitDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
@@ -286,7 +286,7 @@ Item {
Behavior on opacity { Behavior on opacity {
enabled: delegateRoot.__delegateInitialized enabled: delegateRoot.__delegateInitialized
NumberAnimation { NumberAnimation {
duration: delegateRoot.__delegateInitialized ? Theme.shortDuration : 0 duration: delegateRoot.__delegateInitialized ? Theme.notificationExitDuration : 0
} }
} }
} }

View File

@@ -257,7 +257,7 @@ DankListView {
target: delegateRoot target: delegateRoot
property: "swipeOffset" property: "swipeOffset"
to: 0 to: 0
duration: Theme.shortDuration duration: Theme.notificationExitDuration
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
onStopped: NotificationService.dismissGroup(delegateRoot.modelData?.key || "") onStopped: NotificationService.dismissGroup(delegateRoot.modelData?.key || "")
} }

View File

@@ -764,7 +764,7 @@ Rectangle {
target: expandedDelegateWrapper target: expandedDelegateWrapper
property: "swipeOffset" property: "swipeOffset"
to: expandedDelegateWrapper.swipeOffset > 0 ? expandedDelegateWrapper.width : -expandedDelegateWrapper.width to: expandedDelegateWrapper.swipeOffset > 0 ? expandedDelegateWrapper.width : -expandedDelegateWrapper.width
duration: Theme.shortDuration duration: Theme.notificationExitDuration
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
onStopped: NotificationService.dismissNotification(modelData) onStopped: NotificationService.dismissNotification(modelData)
} }

View File

@@ -7,6 +7,7 @@ DankPopout {
id: root id: root
layerNamespace: "dms:notification-center-popout" layerNamespace: "dms:notification-center-popout"
fullHeightSurface: true
property bool notificationHistoryVisible: false property bool notificationHistoryVisible: false
property var triggerScreen: null property var triggerScreen: null
@@ -34,9 +35,9 @@ DankPopout {
popupWidth: triggerScreen ? Math.min(500, Math.max(380, triggerScreen.width - 48)) : 400 popupWidth: triggerScreen ? Math.min(500, Math.max(380, triggerScreen.width - 48)) : 400
popupHeight: stablePopupHeight popupHeight: stablePopupHeight
positioning: "" positioning: ""
animationScaleCollapsed: 1.0 animationScaleCollapsed: 0.94
animationOffset: 0 animationOffset: 0
suspendShadowWhileResizing: true suspendShadowWhileResizing: false
screen: triggerScreen screen: triggerScreen
shouldBeVisible: notificationHistoryVisible shouldBeVisible: notificationHistoryVisible

View File

@@ -23,7 +23,7 @@ Rectangle {
spacing: 2 spacing: 2
StyledText { 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 font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width

View File

@@ -22,6 +22,8 @@ PanelWindow {
property bool _isDestroying: false property bool _isDestroying: false
property bool _finalized: false property bool _finalized: false
property real _lastReportedAlignedHeight: -1 property real _lastReportedAlignedHeight: -1
property real _storedTopMargin: 0
property real _storedBottomMargin: 0
readonly property string clearText: I18n.tr("Dismiss") readonly property string clearText: I18n.tr("Dismiss")
property bool descriptionExpanded: false property bool descriptionExpanded: false
readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0 readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
@@ -146,6 +148,8 @@ PanelWindow {
} }
Component.onCompleted: { Component.onCompleted: {
_lastReportedAlignedHeight = Theme.px(implicitHeight, dpr); _lastReportedAlignedHeight = Theme.px(implicitHeight, dpr);
_storedTopMargin = getTopMargin();
_storedBottomMargin = getBottomMargin();
if (SettingsData.notificationPopupPrivacyMode) if (SettingsData.notificationPopupPrivacyMode)
descriptionExpanded = false; descriptionExpanded = false;
if (hasValidData) { if (hasValidData) {
@@ -179,14 +183,30 @@ PanelWindow {
property bool isBottomCenter: SettingsData.notificationPopupPosition === SettingsData.Position.BottomCenter property bool isBottomCenter: SettingsData.notificationPopupPosition === SettingsData.Position.BottomCenter
property bool isCenterPosition: isTopCenter || isBottomCenter property bool isCenterPosition: isTopCenter || isBottomCenter
anchors.top: isTopCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Left anchors.top: true
anchors.bottom: isBottomCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom || SettingsData.notificationPopupPosition === SettingsData.Position.Right anchors.bottom: true
anchors.left: SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom anchors.left: SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom
anchors.right: SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Right 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 { margins {
top: getTopMargin() top: _storedTopMargin
bottom: getBottomMargin() bottom: _storedBottomMargin
left: getLeftMargin() left: getLeftMargin()
right: getRightMargin() right: getRightMargin()
} }
@@ -263,7 +283,14 @@ PanelWindow {
id: content id: content
x: Theme.snap((win.width - alignedWidth) / 2, dpr) 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 width: alignedWidth
height: alignedHeight height: alignedHeight
visible: !win._finalized visible: !win._finalized
@@ -311,7 +338,7 @@ PanelWindow {
id: bgShadowLayer id: bgShadowLayer
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.snap(4, win.dpr) 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.smooth: false
layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr)) layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically
@@ -814,7 +841,7 @@ PanelWindow {
Behavior on swipeOffset { Behavior on swipeOffset {
enabled: !content.swipeActive && !content.swipeDismissing enabled: !content.swipeActive && !content.swipeDismissing
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.notificationExitDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
@@ -824,7 +851,7 @@ PanelWindow {
target: content target: content
property: "swipeOffset" property: "swipeOffset"
to: isTopCenter ? -content.height : isBottomCenter ? content.height : (SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom ? -content.width : content.width) 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 easing.type: Easing.OutCubic
onStopped: { onStopped: {
NotificationService.dismissNotification(notificationData); NotificationService.dismissNotification(notificationData);

View File

@@ -7,9 +7,10 @@ DankOSD {
id: root id: root
readonly property bool useVertical: isVerticalLayout readonly property bool useVertical: isVerticalLayout
property int targetBrightness: { property int _displayBrightness: 0
DisplayService.brightnessVersion;
return DisplayService.brightnessLevel; function _syncBrightness() {
_displayBrightness = DisplayService.brightnessLevel;
} }
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2) osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
@@ -20,11 +21,11 @@ DankOSD {
Connections { Connections {
target: DisplayService target: DisplayService
function onBrightnessChanged(showOsd) { function onBrightnessChanged(showOsd) {
if (showOsd && SettingsData.osdBrightnessEnabled) { root._syncBrightness();
if (showOsd && SettingsData.osdBrightnessEnabled)
root.show(); root.show();
} }
} }
}
content: Loader { content: Loader {
anchors.fill: parent anchors.fill: parent
@@ -53,14 +54,12 @@ DankOSD {
anchors.centerIn: parent anchors.centerIn: parent
name: { name: {
const deviceInfo = DisplayService.getCurrentDeviceInfo(); const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") { if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc")
return "brightness_medium"; return "brightness_medium";
} else if (deviceInfo.name.includes("kbd")) { if (deviceInfo.name.includes("kbd"))
return "keyboard"; return "keyboard";
} else {
return "lightbulb"; return "lightbulb";
} }
}
size: Theme.iconSize size: Theme.iconSize
color: Theme.primary color: Theme.primary
} }
@@ -77,20 +76,16 @@ DankOSD {
const deviceInfo = DisplayService.getCurrentDeviceInfo(); const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo) if (!deviceInfo)
return 1; return 1;
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id); if (SessionData.getBrightnessExponential(deviceInfo.id))
if (isExponential) {
return 1; return 1;
}
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0; return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0;
} }
maximum: { maximum: {
const deviceInfo = DisplayService.getCurrentDeviceInfo(); const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo) if (!deviceInfo)
return 100; return 100;
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id); if (SessionData.getBrightnessExponential(deviceInfo.id))
if (isExponential) {
return 100; return 100;
}
return deviceInfo.displayMax || 100; return deviceInfo.displayMax || 100;
} }
enabled: DisplayService.brightnessAvailable enabled: DisplayService.brightnessAvailable
@@ -99,28 +94,24 @@ DankOSD {
const deviceInfo = DisplayService.getCurrentDeviceInfo(); const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo) if (!deviceInfo)
return "%"; return "%";
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id); if (SessionData.getBrightnessExponential(deviceInfo.id))
if (isExponential) {
return "%"; return "%";
}
return deviceInfo.class === "ddc" ? "" : "%"; return deviceInfo.class === "ddc" ? "" : "%";
} }
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
alwaysShowValue: SettingsData.osdAlwaysShowValue alwaysShowValue: SettingsData.osdAlwaysShowValue
onSliderValueChanged: newValue => { onSliderValueChanged: newValue => {
if (DisplayService.brightnessAvailable) { if (!DisplayService.brightnessAvailable)
return;
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true); DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true);
resetHideTimer(); resetHideTimer();
} }
}
onContainsMouseChanged: { onContainsMouseChanged: setChildHovered(containsMouse)
setChildHovered(containsMouse);
}
Binding on value { Binding on value {
value: root.targetBrightness value: root._displayBrightness
when: !brightnessSlider.isDragging when: !brightnessSlider.isDragging
} }
} }
@@ -146,14 +137,12 @@ DankOSD {
anchors.centerIn: parent anchors.centerIn: parent
name: { name: {
const deviceInfo = DisplayService.getCurrentDeviceInfo(); const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") { if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc")
return "brightness_medium"; return "brightness_medium";
} else if (deviceInfo.name.includes("kbd")) { if (deviceInfo.name.includes("kbd"))
return "keyboard"; return "keyboard";
} else {
return "lightbulb"; return "lightbulb";
} }
}
size: Theme.iconSize size: Theme.iconSize
color: Theme.primary color: Theme.primary
} }
@@ -170,7 +159,7 @@ DankOSD {
property int value: 50 property int value: 50
Binding on value { Binding on value {
value: root.targetBrightness value: root._displayBrightness
when: !vertSlider.dragging when: !vertSlider.dragging
} }
@@ -178,8 +167,7 @@ DankOSD {
const deviceInfo = DisplayService.getCurrentDeviceInfo(); const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo) if (!deviceInfo)
return 1; return 1;
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id); if (SessionData.getBrightnessExponential(deviceInfo.id))
if (isExponential)
return 1; return 1;
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0; return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0;
} }
@@ -188,8 +176,7 @@ DankOSD {
const deviceInfo = DisplayService.getCurrentDeviceInfo(); const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo) if (!deviceInfo)
return 100; return 100;
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id); if (SessionData.getBrightnessExponential(deviceInfo.id))
if (isExponential)
return 100; return 100;
return deviceInfo.displayMax || 100; return deviceInfo.displayMax || 100;
} }
@@ -240,33 +227,25 @@ DankOSD {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: { onContainsMouseChanged: setChildHovered(containsMouse)
setChildHovered(containsMouse);
}
onPressed: mouse => { onPressed: mouse => {
vertSlider.dragging = true; vertSlider.dragging = true;
updateBrightness(mouse); updateBrightness(mouse);
} }
onReleased: { onReleased: vertSlider.dragging = false
vertSlider.dragging = false;
}
onPositionChanged: mouse => { onPositionChanged: mouse => {
if (pressed) { if (pressed)
updateBrightness(mouse); updateBrightness(mouse);
} }
}
onClicked: mouse => { onClicked: mouse => updateBrightness(mouse)
updateBrightness(mouse);
}
function updateBrightness(mouse) { function updateBrightness(mouse) {
if (!DisplayService.brightnessAvailable) { if (!DisplayService.brightnessAvailable)
return; return;
}
const ratio = 1.0 - (mouse.y / height); const ratio = 1.0 - (mouse.y / height);
const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum)); const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum));
vertSlider.value = newValue; vertSlider.value = newValue;

View File

@@ -8,13 +8,20 @@ DankOSD {
readonly property bool useVertical: isVerticalLayout readonly property bool useVertical: isVerticalLayout
readonly property var player: MprisController.activePlayer 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 readonly property bool volumeSupported: player?.volumeSupported ?? false
property bool _suppressNewPlayer: false property bool _suppressNewPlayer: false
property int _displayVolume: 0
function _syncVolume() {
if (!player)
return;
_displayVolume = Math.min(100, Math.round(player.volume * 100));
}
onPlayerChanged: { onPlayerChanged: {
_suppressNewPlayer = true; _suppressNewPlayer = true;
_suppressTimer.restart(); _suppressTimer.restart();
_syncVolume();
} }
Timer { Timer {
@@ -37,27 +44,27 @@ DankOSD {
} }
function toggleMute() { function toggleMute() {
if (player) { if (!player)
return;
player.volume = player.volume > 0 ? 0 : 1; player.volume = player.volume > 0 ? 0 : 1;
} }
}
function setVolume(volumePercent) { function setVolume(volumePercent) {
if (player) { if (!player)
return;
player.volume = volumePercent / 100; player.volume = volumePercent / 100;
resetHideTimer(); resetHideTimer();
} }
}
Connections { Connections {
target: player target: player
function onVolumeChanged() { function onVolumeChanged() {
if (SettingsData.osdMediaVolumeEnabled && volumeSupported && !_suppressNewPlayer) { root._syncVolume();
if (SettingsData.osdMediaVolumeEnabled && volumeSupported && !_suppressNewPlayer)
root.show(); root.show();
} }
} }
}
content: Loader { content: Loader {
anchors.fill: parent anchors.fill: parent
@@ -96,9 +103,7 @@ DankOSD {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: toggleMute() onClicked: toggleMute()
onContainsMouseChanged: { onContainsMouseChanged: setChildHovered(containsMouse || volumeSlider.containsMouse)
setChildHovered(containsMouse || volumeSlider.containsMouse);
}
} }
} }
@@ -115,29 +120,21 @@ DankOSD {
showValue: true showValue: true
unit: "%" unit: "%"
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
valueOverride: currentVolume valueOverride: root._displayVolume
alwaysShowValue: SettingsData.osdAlwaysShowValue alwaysShowValue: SettingsData.osdAlwaysShowValue
Component.onCompleted: { Component.onCompleted: {
value = currentVolume; root._syncVolume();
value = root._displayVolume;
} }
onSliderValueChanged: newValue => { onSliderValueChanged: newValue => setVolume(newValue)
setVolume(newValue);
}
onContainsMouseChanged: { onContainsMouseChanged: setChildHovered(containsMouse || muteButton.containsMouse)
setChildHovered(containsMouse || muteButton.containsMouse);
}
Connections { Binding on value {
target: player value: root._displayVolume
when: !volumeSlider.pressed
function onVolumeChanged() {
if (volumeSlider && !volumeSlider.pressed) {
volumeSlider.value = currentVolume;
}
}
} }
} }
} }
@@ -172,9 +169,7 @@ DankOSD {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: toggleMute() onClicked: toggleMute()
onContainsMouseChanged: { onContainsMouseChanged: setChildHovered(containsMouse || vertSliderArea.containsMouse)
setChildHovered(containsMouse || vertSliderArea.containsMouse);
}
} }
} }
@@ -186,7 +181,7 @@ DankOSD {
y: gap * 2 + Theme.iconSize y: gap * 2 + Theme.iconSize
property bool dragging: false property bool dragging: false
property int value: currentVolume property int value: root._displayVolume
Rectangle { Rectangle {
id: vertTrack id: vertTrack
@@ -231,28 +226,21 @@ DankOSD {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: { onContainsMouseChanged: setChildHovered(containsMouse || muteButtonVert.containsMouse)
setChildHovered(containsMouse || muteButtonVert.containsMouse);
}
onPressed: mouse => { onPressed: mouse => {
vertSlider.dragging = true; vertSlider.dragging = true;
updateVolume(mouse); updateVolume(mouse);
} }
onReleased: { onReleased: vertSlider.dragging = false
vertSlider.dragging = false;
}
onPositionChanged: mouse => { onPositionChanged: mouse => {
if (pressed) { if (pressed)
updateVolume(mouse); updateVolume(mouse);
} }
}
onClicked: mouse => { onClicked: mouse => updateVolume(mouse)
updateVolume(mouse);
}
function updateVolume(mouse) { function updateVolume(mouse) {
const ratio = 1.0 - (mouse.y / height); const ratio = 1.0 - (mouse.y / height);
@@ -260,16 +248,6 @@ DankOSD {
setVolume(volume); setVolume(volume);
} }
} }
Connections {
target: player
function onVolumeChanged() {
if (!vertSlider.dragging) {
vertSlider.value = currentVolume;
}
}
}
} }
StyledText { 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 id: root
readonly property bool useVertical: isVerticalLayout 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) 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) osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
@@ -14,30 +21,29 @@ DankOSD {
enableMouseInteraction: true enableMouseInteraction: true
Connections { Connections {
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null target: AudioService.sink?.audio ?? null
function onVolumeChanged() { function onVolumeChanged() {
if (SettingsData.osdVolumeEnabled) { root._syncVolume();
if (SettingsData.osdVolumeEnabled)
root.show(); root.show();
} }
}
function onMutedChanged() { function onMutedChanged() {
if (SettingsData.osdVolumeEnabled) { if (SettingsData.osdVolumeEnabled)
root.show(); root.show();
} }
} }
}
Connections { Connections {
target: AudioService target: AudioService
function onSinkChanged() { function onSinkChanged() {
if (root.shouldBeVisible && SettingsData.osdVolumeEnabled) { root._syncVolume();
if (root.shouldBeVisible && SettingsData.osdVolumeEnabled)
root.show(); root.show();
} }
} }
}
content: Loader { content: Loader {
anchors.fill: parent anchors.fill: parent
@@ -64,7 +70,7 @@ DankOSD {
DankIcon { DankIcon {
anchors.centerIn: parent 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 size: Theme.iconSize
color: muteButton.containsMouse ? Theme.primary : Theme.surfaceText color: muteButton.containsMouse ? Theme.primary : Theme.surfaceText
} }
@@ -75,60 +81,45 @@ DankOSD {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: AudioService.toggleMute()
AudioService.toggleMute(); onContainsMouseChanged: setChildHovered(containsMouse || volumeSlider.containsMouse)
}
onContainsMouseChanged: {
setChildHovered(containsMouse || volumeSlider.containsMouse);
}
} }
} }
DankSlider { DankSlider {
id: volumeSlider 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 width: parent.width - Theme.iconSize - parent.gap * 3
height: 40 height: 40
x: parent.gap * 2 + Theme.iconSize x: parent.gap * 2 + Theme.iconSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
minimum: 0 minimum: 0
maximum: AudioService.sinkMaxVolume maximum: AudioService.sinkMaxVolume
enabled: AudioService.sink && AudioService.sink.audio enabled: AudioService.sink?.audio
showValue: true showValue: true
unit: "%" unit: "%"
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
valueOverride: displayPercent valueOverride: root._displayVolume
alwaysShowValue: SettingsData.osdAlwaysShowValue alwaysShowValue: SettingsData.osdAlwaysShowValue
Component.onCompleted: { Component.onCompleted: {
if (AudioService.sink && AudioService.sink.audio) { root._syncVolume();
value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100)); value = root._displayVolume;
}
} }
onSliderValueChanged: newValue => { onSliderValueChanged: newValue => {
if (AudioService.sink && AudioService.sink.audio) { if (!AudioService.sink?.audio)
return;
SessionData.suppressOSDTemporarily(); SessionData.suppressOSDTemporarily();
AudioService.sink.audio.volume = newValue / 100; AudioService.sink.audio.volume = newValue / 100;
resetHideTimer(); resetHideTimer();
} }
}
onContainsMouseChanged: { onContainsMouseChanged: setChildHovered(containsMouse || muteButton.containsMouse)
setChildHovered(containsMouse || muteButton.containsMouse);
}
Connections { Binding on value {
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null value: root._displayVolume
when: !volumeSlider.pressed
function onVolumeChanged() {
if (volumeSlider && !volumeSlider.pressed) {
volumeSlider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
}
}
} }
} }
} }
@@ -151,7 +142,7 @@ DankOSD {
DankIcon { DankIcon {
anchors.centerIn: parent 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 size: Theme.iconSize
color: muteButtonVert.containsMouse ? Theme.primary : Theme.surfaceText color: muteButtonVert.containsMouse ? Theme.primary : Theme.surfaceText
} }
@@ -162,12 +153,8 @@ DankOSD {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: AudioService.toggleMute()
AudioService.toggleMute(); onContainsMouseChanged: setChildHovered(containsMouse || vertSliderArea.containsMouse)
}
onContainsMouseChanged: {
setChildHovered(containsMouse || vertSliderArea.containsMouse);
}
} }
} }
@@ -179,7 +166,7 @@ DankOSD {
y: gap * 2 + Theme.iconSize y: gap * 2 + Theme.iconSize
property bool dragging: false 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 { Rectangle {
id: vertTrack id: vertTrack
@@ -220,35 +207,29 @@ DankOSD {
id: vertSliderArea id: vertSliderArea
anchors.fill: parent anchors.fill: parent
anchors.margins: -12 anchors.margins: -12
enabled: AudioService.sink && AudioService.sink.audio enabled: AudioService.sink?.audio
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: { onContainsMouseChanged: setChildHovered(containsMouse || muteButtonVert.containsMouse)
setChildHovered(containsMouse || muteButtonVert.containsMouse);
}
onPressed: mouse => { onPressed: mouse => {
vertSlider.dragging = true; vertSlider.dragging = true;
updateVolume(mouse); updateVolume(mouse);
} }
onReleased: { onReleased: vertSlider.dragging = false
vertSlider.dragging = false;
}
onPositionChanged: mouse => { onPositionChanged: mouse => {
if (pressed) { if (pressed)
updateVolume(mouse); updateVolume(mouse);
} }
}
onClicked: mouse => { onClicked: mouse => updateVolume(mouse)
updateVolume(mouse);
}
function updateVolume(mouse) { function updateVolume(mouse) {
if (AudioService.sink && AudioService.sink.audio) { if (!AudioService.sink?.audio)
return;
const maxVol = AudioService.sinkMaxVolume; const maxVol = AudioService.sinkMaxVolume;
const ratio = 1.0 - (mouse.y / height); const ratio = 1.0 - (mouse.y / height);
const volume = Math.max(0, Math.min(maxVol, Math.round(ratio * maxVol))); const volume = Math.max(0, Math.min(maxVol, Math.round(ratio * maxVol)));
@@ -259,15 +240,6 @@ DankOSD {
} }
} }
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));
}
}
}
StyledText { StyledText {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -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 property bool isInitialized: false
function loadValue() { function loadValue() {
const settings = findSettings() const settings = findSettings();
if (settings && settings.pluginService) { if (settings && settings.pluginService) {
const loadedValue = settings.loadValue(settingKey, defaultValue) const loadedValue = settings.loadValue(settingKey, defaultValue);
value = loadedValue if (textField.activeFocus && isInitialized)
textField.text = loadedValue return;
isInitialized = true value = loadedValue;
textField.text = loadedValue;
isInitialized = true;
} }
} }
Component.onCompleted: { Component.onCompleted: {
Qt.callLater(loadValue) Qt.callLater(loadValue);
} }
onValueChanged: { function commit() {
if (!isInitialized) return if (!isInitialized)
const settings = findSettings() return;
if (settings) { if (textField.text === value)
settings.saveValue(settingKey, value) return;
} value = textField.text;
const settings = findSettings();
if (settings)
settings.saveValue(settingKey, value);
} }
function findSettings() { function findSettings() {
let item = parent let item = parent;
while (item) { while (item) {
if (item.saveValue !== undefined && item.loadValue !== undefined) { if (item.saveValue !== undefined && item.loadValue !== undefined) {
return item return item;
} }
item = item.parent item = item.parent;
} }
return null return null;
} }
StyledText { StyledText {
@@ -70,16 +75,10 @@ Column {
id: textField id: textField
width: parent.width width: parent.width
placeholderText: root.placeholder placeholderText: root.placeholder
onTextEdited: { onEditingFinished: root.commit()
root.value = text
}
onEditingFinished: {
root.value = text
}
onActiveFocusChanged: { onActiveFocusChanged: {
if (!activeFocus) { if (!activeFocus)
root.value = text root.commit();
}
} }
} }
} }

View File

@@ -26,8 +26,8 @@ DankPopout {
open(); open();
} }
popupWidth: 650 popupWidth: Math.round(Theme.fontSizeMedium * 46)
popupHeight: 550 popupHeight: Math.round(Theme.fontSizeMedium * 39)
triggerWidth: 55 triggerWidth: 55
positioning: "" positioning: ""
screen: triggerScreen screen: triggerScreen
@@ -151,11 +151,13 @@ DankPopout {
DankButtonGroup { DankButtonGroup {
id: processFilterGroup id: processFilterGroup
Layout.minimumWidth: implicitWidth + 8 Layout.minimumWidth: implicitWidth
model: [I18n.tr("All"), I18n.tr("User"), I18n.tr("System")] model: [I18n.tr("All"), I18n.tr("User"), I18n.tr("System")]
currentIndex: 0 currentIndex: 0
checkEnabled: false checkEnabled: false
buttonHeight: Math.round(Theme.fontSizeMedium * 2.2) buttonHeight: Math.round(Theme.fontSizeSmall * 2.4)
minButtonWidth: 0
buttonPadding: Theme.spacingM
textSize: Theme.fontSizeSmall textSize: Theme.fontSizeSmall
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (!selected) if (!selected)
@@ -177,7 +179,8 @@ DankPopout {
DankTextField { DankTextField {
id: searchField id: searchField
Layout.preferredWidth: Theme.fontSizeMedium * 14 Layout.fillWidth: true
Layout.minimumWidth: Theme.fontSizeMedium * 8
Layout.preferredHeight: Theme.fontSizeMedium * 2.5 Layout.preferredHeight: Theme.fontSizeMedium * 2.5
placeholderText: I18n.tr("Search...") placeholderText: I18n.tr("Search...")
leftIconName: "search" leftIconName: "search"

View File

@@ -1061,7 +1061,7 @@ Singleton {
function getHyprlandOutputIdentifier(output, outputName) { function getHyprlandOutputIdentifier(output, outputName) {
if (SettingsData.displayNameMode === "model" && output?.make && output?.model) 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; return outputName;
} }

View File

@@ -30,56 +30,6 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXL 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 { SettingsCard {
width: parent.width width: parent.width
iconName: "dock_to_bottom" 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 { SettingsCard {
width: parent.width width: parent.width
iconName: "apps" iconName: "apps"

View File

@@ -375,10 +375,12 @@ FocusScope {
if (!plugin || !PluginService.isPluginLoaded(pluginId)) if (!plugin || !PluginService.isPluginLoaded(pluginId))
return; return;
var isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher")); var isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"));
if (isLauncher) if (isLauncher) {
pluginsTab.isReloading = true;
PluginService.reloadPlugin(pluginId); PluginService.reloadPlugin(pluginId);
} }
} }
}
Connections { Connections {
target: DMSService target: DMSService

View File

@@ -7,7 +7,7 @@ import qs.Modules.Settings.Widgets
Item { Item {
id: root 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] readonly property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
function getTimeoutIndex(timeout) { function getTimeoutIndex(timeout) {
@@ -56,7 +56,7 @@ Item {
id: powerCategory id: powerCategory
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable visible: BatteryService.batteryAvailable
model: ["AC Power", "Battery"] model: [I18n.tr("AC Power"), I18n.tr("Battery")]
currentIndex: 0 currentIndex: 0
selectionMode: "single" selectionMode: "single"
checkEnabled: false checkEnabled: false
@@ -100,7 +100,7 @@ Item {
id: fadeGracePeriodDropdown id: fadeGracePeriodDropdown
settingKey: "fadeToLockGracePeriod" settingKey: "fadeToLockGracePeriod"
tags: ["fade", "grace", "period", "timeout", "lock"] 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] property var periodValues: [1, 2, 3, 4, 5, 10, 15, 20, 30]
text: I18n.tr("Lock fade grace period") text: I18n.tr("Lock fade grace period")
@@ -111,7 +111,7 @@ Item {
Component.onCompleted: { Component.onCompleted: {
const currentPeriod = SettingsData.fadeToLockGracePeriod; const currentPeriod = SettingsData.fadeToLockGracePeriod;
const index = periodValues.indexOf(currentPeriod); const index = periodValues.indexOf(currentPeriod);
currentValue = index >= 0 ? periodOptions[index] : "5 seconds"; currentValue = index >= 0 ? periodOptions[index] : I18n.tr("5 seconds");
} }
onValueChanged: value => { onValueChanged: value => {
@@ -126,7 +126,7 @@ Item {
id: fadeDpmsGracePeriodDropdown id: fadeDpmsGracePeriodDropdown
settingKey: "fadeToDpmsGracePeriod" settingKey: "fadeToDpmsGracePeriod"
tags: ["fade", "grace", "period", "timeout", "dpms", "monitor"] 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] property var periodValues: [1, 2, 3, 4, 5, 10, 15, 20, 30]
text: I18n.tr("Monitor fade grace period") text: I18n.tr("Monitor fade grace period")
@@ -137,7 +137,7 @@ Item {
Component.onCompleted: { Component.onCompleted: {
const currentPeriod = SettingsData.fadeToDpmsGracePeriod; const currentPeriod = SettingsData.fadeToDpmsGracePeriod;
const index = periodValues.indexOf(currentPeriod); const index = periodValues.indexOf(currentPeriod);
currentValue = index >= 0 ? periodOptions[index] : "5 seconds"; currentValue = index >= 0 ? periodOptions[index] : I18n.tr("5 seconds");
} }
onValueChanged: value => { onValueChanged: value => {
@@ -308,7 +308,7 @@ Item {
DankButtonGroup { DankButtonGroup {
id: suspendBehaviorSelector id: suspendBehaviorSelector
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
model: ["Suspend", "Hibernate", "Suspend then Hibernate"] model: [I18n.tr("Suspend"), I18n.tr("Hibernate"), I18n.tr("Suspend then Hibernate")]
selectionMode: "single" selectionMode: "single"
checkEnabled: false checkEnabled: false
@@ -375,13 +375,13 @@ Item {
settingKey: "powerMenuDefaultAction" settingKey: "powerMenuDefaultAction"
tags: ["power", "menu", "default", "action", "reboot", "logout", "shutdown"] tags: ["power", "menu", "default", "action", "reboot", "logout", "shutdown"]
text: I18n.tr("Default selected action") 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"] property var actionValues: ["reboot", "logout", "poweroff", "lock", "suspend", "restart", "hibernate"]
Component.onCompleted: { Component.onCompleted: {
const currentAction = SettingsData.powerMenuDefaultAction || "logout"; const currentAction = SettingsData.powerMenuDefaultAction || "logout";
const index = actionValues.indexOf(currentAction); const index = actionValues.indexOf(currentAction);
currentValue = index >= 0 ? options[index] : "Log Out"; currentValue = index >= 0 ? options[index] : I18n.tr("Log Out");
} }
onValueChanged: value => { onValueChanged: value => {
@@ -479,7 +479,7 @@ Item {
id: holdDurationDropdown id: holdDurationDropdown
settingKey: "powerActionHoldDuration" settingKey: "powerActionHoldDuration"
tags: ["power", "hold", "duration", "confirm", "time"] 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] property var durationValues: [0.25, 0.5, 0.75, 1, 2, 3, 5, 10]
text: I18n.tr("Hold Duration") text: I18n.tr("Hold Duration")
@@ -489,7 +489,7 @@ Item {
Component.onCompleted: { Component.onCompleted: {
const currentDuration = SettingsData.powerActionHoldDuration; const currentDuration = SettingsData.powerActionHoldDuration;
const index = durationValues.indexOf(currentDuration); const index = durationValues.indexOf(currentDuration);
currentValue = index >= 0 ? durationOptions[index] : "500 ms"; currentValue = index >= 0 ? durationOptions[index] : I18n.tr("500 ms");
} }
onValueChanged: value => { onValueChanged: value => {

View File

@@ -16,7 +16,7 @@ Item {
property var cachedCursorThemes: SettingsData.availableCursorThemes property var cachedCursorThemes: SettingsData.availableCursorThemes
property var cachedMatugenSchemes: Theme.availableMatugenSchemes.map(option => option.label) property var cachedMatugenSchemes: Theme.availableMatugenSchemes.map(option => option.label)
property var installedRegistryThemes: [] property var installedRegistryThemes: []
property var templateDetection: ({}) property var templateDetection: []
property var cursorIncludeStatus: ({ property var cursorIncludeStatus: ({
"exists": false, "exists": false,
@@ -106,9 +106,10 @@ Item {
} }
function isTemplateDetected(templateId) { function isTemplateDetected(templateId) {
if (!templateDetection || Object.keys(templateDetection).length === 0) if (!templateDetection || templateDetection.length === 0)
return true; return true;
return templateDetection[templateId] !== false; var item = templateDetection.find(i => i.id === templateId);
return !item || item.detected !== false;
} }
function getTemplateDescription(templateId, baseDescription) { function getTemplateDescription(templateId, baseDescription) {
@@ -145,30 +146,17 @@ Item {
DMSService.listInstalledThemes(); DMSService.listInstalledThemes();
if (PopoutService.pendingThemeInstall) if (PopoutService.pendingThemeInstall)
Qt.callLater(() => showThemeBrowser()); 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) if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl)
checkCursorIncludeStatus(); 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 { Connections {
target: DMSService target: DMSService
function onInstalledThemesReceived(themes) { function onInstalledThemesReceived(themes) {
@@ -1045,11 +1033,11 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
model: [ model: [
{ {
"text": "Time", "text": I18n.tr("Time", "theme auto mode tab"),
"icon": "access_time" "icon": "access_time"
}, },
{ {
"text": "Location", "text": I18n.tr("Location", "theme auto mode tab"),
"icon": "place" "icon": "place"
} }
] ]
@@ -2340,7 +2328,7 @@ Item {
tags: ["matugen", "neovim", "terminal", "template"] tags: ["matugen", "neovim", "terminal", "template"]
settingKey: "matugenTemplateNeovim" settingKey: "matugenTemplateNeovim"
text: "neovim" text: "neovim"
description: getTemplateDescription("nvim", "Requires lazy plugin manager") description: getTemplateDescription("nvim", I18n.tr("Requires lazy plugin manager", "neovim template description"))
descriptionColor: getTemplateDescriptionColor("nvim") descriptionColor: getTemplateDescriptionColor("nvim")
visible: SettingsData.runDmsMatugenTemplates visible: SettingsData.runDmsMatugenTemplates
checked: SettingsData.matugenTemplateNeovim checked: SettingsData.matugenTemplateNeovim
@@ -2469,7 +2457,7 @@ Item {
onValueChanged: value => { onValueChanged: value => {
SettingsData.setIconTheme(value); SettingsData.setIconTheme(value);
if (Quickshell.env("QT_QPA_PLATFORMTHEME") != "gtk3" && Quickshell.env("QT_QPA_PLATFORMTHEME") != "qt6ct" && Quickshell.env("QT_QPA_PLATFORMTHEME_QT6") != "qt6ct") { 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) if (!PopoutService.colorPickerModal)
return; return;
PopoutService.colorPickerModal.selectedColor = root.currentWallpaper.startsWith("#") ? root.currentWallpaper : Theme.primary; 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) { PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
if (SessionData.perMonitorWallpaper) { if (SessionData.perMonitorWallpaper) {
SessionData.setMonitorWallpaper(selectedMonitorName, selectedColor); SessionData.setMonitorWallpaper(selectedMonitorName, selectedColor);
@@ -237,7 +237,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { 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 font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
elide: Text.ElideMiddle elide: Text.ElideMiddle
@@ -507,7 +507,7 @@ Item {
return; return;
var lightWallpaper = SessionData.wallpaperPathLight; var lightWallpaper = SessionData.wallpaperPathLight;
PopoutService.colorPickerModal.selectedColor = lightWallpaper.startsWith("#") ? lightWallpaper : Theme.primary; 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) { PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
SessionData.wallpaperPathLight = selectedColor; SessionData.wallpaperPathLight = selectedColor;
SessionData.syncWallpaperForCurrentMode(); SessionData.syncWallpaperForCurrentMode();
@@ -558,7 +558,7 @@ Item {
StyledText { StyledText {
text: { text: {
var lightWallpaper = SessionData.wallpaperPathLight; 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 font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
@@ -691,7 +691,7 @@ Item {
return; return;
var darkWallpaper = SessionData.wallpaperPathDark; var darkWallpaper = SessionData.wallpaperPathDark;
PopoutService.colorPickerModal.selectedColor = darkWallpaper.startsWith("#") ? darkWallpaper : Theme.primary; 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) { PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
SessionData.wallpaperPathDark = selectedColor; SessionData.wallpaperPathDark = selectedColor;
SessionData.syncWallpaperForCurrentMode(); SessionData.syncWallpaperForCurrentMode();
@@ -742,7 +742,7 @@ Item {
StyledText { StyledText {
text: { text: {
var darkWallpaper = SessionData.wallpaperPathDark; 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 font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
@@ -813,7 +813,7 @@ Item {
return SettingsData.getScreenDisplayName(screens[i]); return SettingsData.getScreenDisplayName(screens[i]);
} }
} }
return "No monitors"; return I18n.tr("No monitors", "no monitors available label");
} }
options: { options: {
var screenNames = []; var screenNames = [];
@@ -844,7 +844,7 @@ Item {
currentValue: { currentValue: {
var screens = Quickshell.screens; var screens = Quickshell.screens;
if (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "") { 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++) { for (var i = 0; i < screens.length; i++) {
if (screens[i].name === SettingsData.matugenTargetMonitor) { if (screens[i].name === SettingsData.matugenTargetMonitor) {
@@ -859,14 +859,14 @@ Item {
for (var i = 0; i < screens.length; i++) { for (var i = 0; i < screens.length; i++) {
var label = SettingsData.getScreenDisplayName(screens[i]); var label = SettingsData.getScreenDisplayName(screens[i]);
if (i === 0 && (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "")) { if (i === 0 && (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "")) {
label += " (Default)"; label += " " + I18n.tr("(Default)", "default monitor label suffix");
} }
screenNames.push(label); screenNames.push(label);
} }
return screenNames; return screenNames;
} }
onValueChanged: value => { onValueChanged: value => {
var cleanValue = value.replace(" (Default)", ""); var cleanValue = value.replace(" " + I18n.tr("(Default)", "default monitor label suffix"), "");
var screens = Quickshell.screens; var screens = Quickshell.screens;
for (var i = 0; i < screens.length; i++) { for (var i = 0; i < screens.length; i++) {
if (SettingsData.getScreenDisplayName(screens[i]) === cleanValue) { if (SettingsData.getScreenDisplayName(screens[i]) === cleanValue) {
@@ -941,11 +941,11 @@ Item {
height: 45 height: 45
model: [ model: [
{ {
"text": "Interval", "text": I18n.tr("Interval", "wallpaper cycling mode tab"),
"icon": "schedule" "icon": "schedule"
}, },
{ {
"text": "Time", "text": I18n.tr("Time", "wallpaper cycling mode tab"),
"icon": "access_time" "icon": "access_time"
} }
] ]
@@ -981,7 +981,7 @@ Item {
SettingsDropdownRow { SettingsDropdownRow {
id: intervalDropdown 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] 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" tab: "wallpaper"
@@ -1005,7 +1005,7 @@ Item {
currentSeconds = SessionData.wallpaperCyclingInterval; currentSeconds = SessionData.wallpaperCyclingInterval;
} }
const index = intervalValues.indexOf(currentSeconds); 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 => { onValueChanged: value => {
const index = intervalOptions.indexOf(value); const index = intervalOptions.indexOf(value);
@@ -1029,7 +1029,7 @@ Item {
currentSeconds = SessionData.wallpaperCyclingInterval; currentSeconds = SessionData.wallpaperCyclingInterval;
} }
const index = intervalDropdown.intervalValues.indexOf(currentSeconds); 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 { Rectangle {
width: 80 width: resetContentRow.implicitWidth + Theme.spacingM * 2
height: 28 height: 28
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: resetArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariant color: resetArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariant
@@ -821,6 +821,7 @@ Item {
border.width: 0 border.width: 0
Row { Row {
id: resetContentRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS

View File

@@ -242,7 +242,7 @@ Variants {
Image { Image {
id: nextWallpaper id: nextWallpaper
anchors.fill: parent anchors.fill: parent
visible: true visible: source !== ""
opacity: 0 opacity: 0
layer.enabled: false layer.enabled: false
asynchronous: true asynchronous: true
@@ -512,10 +512,13 @@ Variants {
} }
} }
MultiEffect { Loader {
anchors.fill: parent
active: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
sourceComponent: MultiEffect {
anchors.fill: parent anchors.fill: parent
source: effectLoader.active ? effectLoader.item : currentWallpaper source: effectLoader.active ? effectLoader.item : currentWallpaper
visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
blurEnabled: true blurEnabled: true
blur: 0.8 blur: 0.8
blurMax: 75 blurMax: 75
@@ -523,4 +526,5 @@ Variants {
} }
} }
} }
}
} }

View File

@@ -288,9 +288,9 @@ Singleton {
const cpu = data.cpu; const cpu = data.cpu;
cpuSampleCount++; cpuSampleCount++;
cpuUsage = cpu.usage || 0; cpuUsage = Math.round((cpu.usage || 0) * 10) / 10;
cpuFrequency = cpu.frequency || 0; cpuFrequency = Math.round(cpu.frequency || 0);
cpuTemperature = cpu.temperature || 0; cpuTemperature = Math.round(cpu.temperature || 0);
cpuCores = cpu.count || 1; cpuCores = cpu.count || 1;
cpuModel = cpu.model || ""; cpuModel = cpu.model || "";
perCoreCpuUsage = cpu.coreUsage || []; perCoreCpuUsage = cpu.coreUsage || [];
@@ -308,11 +308,12 @@ Singleton {
const freeKB = mem.free || 0; const freeKB = mem.free || 0;
const usedKB = mem.used !== undefined ? mem.used : (totalKB - availableKB); const usedKB = mem.used !== undefined ? mem.used : (totalKB - availableKB);
totalMemoryMB = totalKB / 1024; totalMemoryMB = Math.round(totalKB / 1024);
availableMemoryMB = availableKB / 1024; availableMemoryMB = Math.round(availableKB / 1024);
freeMemoryMB = freeKB / 1024; freeMemoryMB = Math.round(freeKB / 1024);
usedMemoryMB = usedKB / 1024; usedMemoryMB = Math.round(usedKB / 1024);
memoryUsage = mem.usedPercent !== undefined ? mem.usedPercent : (totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0); const rawMemUsage = mem.usedPercent !== undefined ? mem.usedPercent : (totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0);
memoryUsage = Math.round(rawMemUsage * 10) / 10;
totalMemoryKB = totalKB; totalMemoryKB = totalKB;
usedMemoryKB = usedKB; usedMemoryKB = usedKB;

View File

@@ -55,7 +55,7 @@ Singleton {
function getOutputIdentifier(output, outputName) { function getOutputIdentifier(output, outputName) {
if (SettingsData.displayNameMode === "model" && output.make && output.model) 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; return outputName;
} }

View File

@@ -963,34 +963,21 @@ Singleton {
return enrichedToplevels; return enrichedToplevels;
} }
function filterCurrentWorkspace(toplevels, screenName) { function _matchAndEnrichToplevels(toplevels, niriWindows) {
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);
const usedToplevels = new Set(); const usedToplevels = new Set();
const result = []; const result = [];
for (const niriWindow of workspaceWindows) { for (const niriWindow of niriWindows) {
let bestMatch = null; let bestMatch = null;
let bestScore = -1; let bestScore = -1;
for (const toplevel of toplevels) { for (const toplevel of toplevels) {
if (usedToplevels.has(toplevel)) if (usedToplevels.has(toplevel))
continue; continue;
if (toplevel.appId === niriWindow.app_id) { if (toplevel.appId !== niriWindow.app_id)
let score = 1; continue;
let score = 1;
if (niriWindow.title && toplevel.title) { if (niriWindow.title && toplevel.title) {
if (toplevel.title === niriWindow.title) { if (toplevel.title === niriWindow.title) {
score = 3; score = 3;
@@ -1006,7 +993,6 @@ Singleton {
break; break;
} }
} }
}
if (!bestMatch) if (!bestMatch)
continue; continue;
@@ -1025,18 +1011,16 @@ Singleton {
return NiriService.focusWindow(niriWindow.id); return NiriService.focusWindow(niriWindow.id);
}, },
"close": function () { "close": function () {
if (bestMatch.close) { if (bestMatch.close)
return bestMatch.close(); return bestMatch.close();
}
return false; return false;
} }
}; };
for (let prop in bestMatch) { for (let prop in bestMatch) {
if (!(prop in enrichedToplevel)) { if (!(prop in enrichedToplevel))
enrichedToplevel[prop] = bestMatch[prop]; enrichedToplevel[prop] = bestMatch[prop];
} }
}
result.push(enrichedToplevel); result.push(enrichedToplevel);
} }
@@ -1044,6 +1028,26 @@ Singleton {
return result; 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) { function filterCurrentDisplay(toplevels, screenName) {
if (!toplevels || toplevels.length === 0 || !screenName) if (!toplevels || toplevels.length === 0 || !screenName)
return toplevels; return toplevels;
@@ -1058,71 +1062,10 @@ Singleton {
if (outputWorkspaceIds.size === 0) if (outputWorkspaceIds.size === 0)
return toplevels; return toplevels;
const displayWindows = windows.filter(niriWindow => outputWorkspaceIds.has(niriWindow.workspace_id)); if (toplevels.length > 0 && toplevels[0].niriWorkspaceId !== undefined)
const usedToplevels = new Set(); return toplevels.filter(t => outputWorkspaceIds.has(t.niriWorkspaceId));
const result = [];
for (const niriWindow of displayWindows) { return _matchAndEnrichToplevels(toplevels, windows.filter(nw => outputWorkspaceIds.has(nw.workspace_id)));
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;
} }
function generateNiriLayoutConfig() { function generateNiriLayoutConfig() {

View File

@@ -16,6 +16,7 @@ Singleton {
property var registeredCards: ({}) property var registeredCards: ({})
property var settingsIndex: [] property var settingsIndex: []
property bool indexLoaded: false property bool indexLoaded: false
property var _translatedCache: []
readonly property var conditionMap: ({ readonly property var conditionMap: ({
"isNiri": () => CompositorService.isNiri, "isNiri": () => CompositorService.isNiri,
@@ -38,9 +39,11 @@ Singleton {
try { try {
root.settingsIndex = JSON.parse(text()); root.settingsIndex = JSON.parse(text());
root.indexLoaded = true; root.indexLoaded = true;
root._rebuildTranslationCache();
} catch (e) { } catch (e) {
console.warn("SettingsSearchService: Failed to parse index:", e); console.warn("SettingsSearchService: Failed to parse index:", e);
root.settingsIndex = []; root.settingsIndex = [];
root._translatedCache = [];
} }
} }
onLoadFailed: error => console.warn("SettingsSearchService: Failed to load index:", error) 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) { function search(text) {
query = text; query = text;
if (!text) { if (!text) {
@@ -138,18 +162,19 @@ Singleton {
return; return;
} }
const queryLower = text.toLowerCase().trim(); var queryLower = text.toLowerCase().trim();
const queryWords = queryLower.split(/\s+/).filter(w => w.length > 0); var queryWords = queryLower.split(/\s+/).filter(w => w.length > 0);
const scored = []; var scored = [];
var cache = _translatedCache;
for (const item of settingsIndex) { for (var i = 0; i < cache.length; i++) {
if (!checkCondition(item)) var entry = cache[i];
if (!checkCondition(entry))
continue; continue;
const translated = translateItem(item); var labelLower = entry.labelLower;
const labelLower = translated.label.toLowerCase(); var categoryLower = entry.categoryLower;
const categoryLower = translated.category.toLowerCase(); var score = 0;
let score = 0;
if (labelLower === queryLower) { if (labelLower === queryLower) {
score = 10000; score = 10000;
@@ -162,24 +187,32 @@ Singleton {
} }
if (score === 0) { if (score === 0) {
for (const keyword of item.keywords) { var keywords = entry.keywords;
if (keyword.startsWith(queryLower)) { for (var k = 0; k < keywords.length; k++) {
score = Math.max(score, 800); if (keywords[k].startsWith(queryLower)) {
score = 800;
break; break;
} }
if (keyword.includes(queryLower)) { if (keywords[k].includes(queryLower) && score < 400) {
score = Math.max(score, 400); score = 400;
} }
} }
} }
if (score === 0 && queryWords.length > 1) { if (score === 0 && queryWords.length > 1) {
let allMatch = true; var allMatch = true;
for (const word of queryWords) { for (var w = 0; w < queryWords.length; w++) {
const inLabel = labelLower.includes(word); var word = queryWords[w];
const inKeywords = item.keywords.some(k => k.includes(word)); if (labelLower.includes(word))
const inCategory = categoryLower.includes(word); continue;
if (!inLabel && !inKeywords && !inCategory) { 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; allMatch = false;
break; break;
} }
@@ -190,7 +223,7 @@ Singleton {
if (score > 0) { if (score > 0) {
scored.push({ scored.push({
item: translated, item: entry,
score: score score: score
}); });
} }

View File

@@ -119,36 +119,36 @@ Singleton {
function getWeatherCondition(code) { function getWeatherCondition(code) {
const conditions = { const conditions = {
"0": "Clear", "0": I18n.tr("Clear Sky"),
"1": "Clear", "1": I18n.tr("Clear Sky"),
"2": "Partly cloudy", "2": I18n.tr("Partly Cloudy"),
"3": "Overcast", "3": I18n.tr("Overcast"),
"45": "Fog", "45": I18n.tr("Fog"),
"48": "Fog", "48": I18n.tr("Fog"),
"51": "Drizzle", "51": I18n.tr("Drizzle"),
"53": "Drizzle", "53": I18n.tr("Drizzle"),
"55": "Drizzle", "55": I18n.tr("Drizzle"),
"56": "Freezing drizzle", "56": I18n.tr("Freezing Drizzle"),
"57": "Freezing drizzle", "57": I18n.tr("Freezing Drizzle"),
"61": "Light rain", "61": I18n.tr("Light Rain"),
"63": "Rain", "63": I18n.tr("Rain"),
"65": "Heavy rain", "65": I18n.tr("Heavy Rain"),
"66": "Light rain", "66": I18n.tr("Light Rain"),
"67": "Heavy rain", "67": I18n.tr("Heavy Rain"),
"71": "Light snow", "71": I18n.tr("Light Snow"),
"73": "Snow", "73": I18n.tr("Snow"),
"75": "Heavy snow", "75": I18n.tr("Heavy Snow"),
"77": "Snow", "77": I18n.tr("Snow"),
"80": "Light rain", "80": I18n.tr("Light Rain"),
"81": "Rain", "81": I18n.tr("Rain"),
"82": "Heavy rain", "82": I18n.tr("Heavy Rain"),
"85": "Light snow showers", "85": I18n.tr("Light Snow Showers"),
"86": "Heavy snow showers", "86": I18n.tr("Heavy Snow Showers"),
"95": "Thunderstorm", "95": I18n.tr("Thunderstorm"),
"96": "Thunderstorm with hail", "96": I18n.tr("Thunderstorm with Hail"),
"99": "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"] 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 || {}; const address = data.address || {};
root.location = { root.location = {
city: address.hamlet || address.city || address.town || address.village || "Unknown", city: address.hamlet || address.city || address.town || address.village || I18n.tr("Unknown"),
country: address.country || "Unknown", country: address.country || I18n.tr("Unknown"),
latitude: parseFloat(data.lat), latitude: parseFloat(data.lat),
longitude: parseFloat(data.lon) longitude: parseFloat(data.lon)
}; };
@@ -807,8 +807,8 @@ Singleton {
"tempF": Math.round(tempF), "tempF": Math.round(tempF),
"feelsLike": Math.round(feelsLikeC), "feelsLike": Math.round(feelsLikeC),
"feelsLikeF": Math.round(feelsLikeF), "feelsLikeF": Math.round(feelsLikeF),
"city": root.location?.city || "Unknown", "city": root.location?.city || I18n.tr("Unknown"),
"country": root.location?.country || "Unknown", "country": root.location?.country || I18n.tr("Unknown"),
"wCode": current.weather_code || 0, "wCode": current.weather_code || 0,
"humidity": Math.round(current.relative_humidity_2m || 0), "humidity": Math.round(current.relative_humidity_2m || 0),
"wind": Math.round(current.wind_speed_10m || 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() { function show() {
if (SessionData.suppressOSD) if (SessionData.suppressOSD)
return; return;
if (shouldBeVisible) {
hideTimer.restart();
return;
}
OSDManager.showOSD(root); OSDManager.showOSD(root);
closeTimer.stop(); closeTimer.stop();
shouldBeVisible = true; shouldBeVisible = true;
@@ -257,7 +261,7 @@ PanelWindow {
property real shadowSpreadPx: 0 property real shadowSpreadPx: 0
property real shadowBaseAlpha: 0.60 property real shadowBaseAlpha: 0.60
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency 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 { Rectangle {
id: background id: background

View File

@@ -30,7 +30,10 @@ Item {
property var customKeyboardFocus: null property var customKeyboardFocus: null
property bool backgroundInteractive: true property bool backgroundInteractive: true
property bool contentHandlesKeys: false property bool contentHandlesKeys: false
property bool fullHeightSurface: false
property bool _resizeActive: false property bool _resizeActive: false
property real _surfaceMarginLeft: 0
property real _surfaceW: 0
property real storedBarThickness: Theme.barHeight - 4 property real storedBarThickness: Theme.barHeight - 4
property real storedBarSpacing: 4 property real storedBarSpacing: 4
@@ -106,6 +109,13 @@ Item {
readonly property bool useBackgroundWindow: !CompositorService.isHyprland || CompositorService.useHyprlandFocusGrab readonly property bool useBackgroundWindow: !CompositorService.isHyprland || CompositorService.useHyprlandFocusGrab
function updateSurfacePosition() {
if (useBackgroundWindow && shouldBeVisible) {
_surfaceMarginLeft = alignedX - shadowBuffer;
_surfaceW = alignedWidth + shadowBuffer * 2;
}
}
function open() { function open() {
if (!screen) if (!screen)
return; return;
@@ -125,6 +135,10 @@ Item {
_lastOpenedScreen = screen; _lastOpenedScreen = screen;
shouldBeVisible = true; shouldBeVisible = true;
if (useBackgroundWindow) {
_surfaceMarginLeft = alignedX - shadowBuffer;
_surfaceW = alignedWidth + shadowBuffer * 2;
}
Qt.callLater(() => { Qt.callLater(() => {
if (shouldBeVisible && screen) { if (shouldBeVisible && screen) {
if (useBackgroundWindow) if (useBackgroundWindow)
@@ -360,20 +374,38 @@ Item {
return WlrKeyboardFocus.Exclusive; return WlrKeyboardFocus.Exclusive;
} }
readonly property bool _fullHeight: useBackgroundWindow && root.fullHeightSurface
anchors { anchors {
left: true left: true
top: true top: true
right: !useBackgroundWindow right: !useBackgroundWindow
bottom: !useBackgroundWindow bottom: _fullHeight || !useBackgroundWindow
} }
WlrLayershell.margins { WlrLayershell.margins {
left: useBackgroundWindow ? (root.alignedX - shadowBuffer) : 0 left: useBackgroundWindow ? root._surfaceMarginLeft : 0
top: useBackgroundWindow ? (root.alignedY - shadowBuffer) : 0 top: (useBackgroundWindow && !_fullHeight) ? (root.alignedY - shadowBuffer) : 0
} }
implicitWidth: useBackgroundWindow ? (root.alignedWidth + (shadowBuffer * 2)) : 0 implicitWidth: useBackgroundWindow ? root._surfaceW : 0
implicitHeight: useBackgroundWindow ? (root.alignedHeight + (shadowBuffer * 2)) : 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 { MouseArea {
anchors.fill: parent anchors.fill: parent
@@ -393,7 +425,7 @@ Item {
Item { Item {
id: contentContainer id: contentContainer
x: useBackgroundWindow ? shadowBuffer : root.alignedX x: useBackgroundWindow ? shadowBuffer : root.alignedX
y: useBackgroundWindow ? shadowBuffer : root.alignedY y: (useBackgroundWindow && !contentWindow._fullHeight) ? shadowBuffer : root.alignedY
width: root.alignedWidth width: root.alignedWidth
height: root.alignedHeight 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 { Item {
id: contentWrapper id: contentWrapper
anchors.centerIn: parent anchors.centerIn: parent
@@ -455,31 +525,10 @@ Item {
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr) 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) y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
property real shadowBlurPx: 10 layer.enabled: contentWrapper.opacity < 1
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.smooth: false
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0) 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 { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: animationDuration duration: animationDuration

View File

@@ -1,3 +1,3 @@
[templates.dmsemacs] [templates.dmsemacs]
input_path = 'SHELL_DIR/matugen/templates/dank-emacs.el' 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