1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 22:12:50 -05:00

Compare commits

..

3 Commits

Author SHA1 Message Date
purian23
0a038fa3c9 Revert "wip binds"
This reverts commit 841e55d37f.
2025-10-01 11:49:29 -04:00
bbedward
841e55d37f wip binds 2025-09-30 17:31:17 -04:00
bbedward
287dda5675 niri: generate colors, layout, binds 2025-09-30 17:31:17 -04:00
349 changed files with 22758 additions and 65608 deletions

View File

@@ -26,14 +26,6 @@ assignees: ""
- [ ] Hyprland - [ ] Hyprland
- [ ] Other (specify) - [ ] Other (specify)
## Distribution
<!-- Arch, Fedora, Debian, etc. -->
## dms version
<!-- Output of dms version command -->
## Description ## Description
<!-- Brief description of the issue --> <!-- Brief description of the issue -->
@@ -53,14 +45,6 @@ assignees: ""
## Error Messages/Logs ## Error Messages/Logs
<!-- Please include any error messages, stack traces, or relevant logs --> <!-- Please include any error messages, stack traces, or relevant logs -->
<!-- you can get a log file with the following steps:
dms kill
mkdir ~/dms_logs
nohup dms run > ~/dms_logs/dms-$(date +%s).txt 2>&1 &
Then trigger your issue, and share the contents of ~/dms_logs/dms-<timestamp>.txt
-->
``` ```
Paste error messages or logs here Paste error messages or logs here

View File

@@ -12,14 +12,6 @@ assignees: ""
- [ ] Hyprland - [ ] Hyprland
- [ ] other - [ ] other
## Distribution
<!-- Arch, Fedora, Debian, etc. -->
## dms version
<!-- Output of dms version command -->
## Description ## Description
<!-- Brief description of the support needed --> <!-- Brief description of the support needed -->

View File

@@ -1,325 +0,0 @@
name: DMS Copr Stable Release
on:
workflow_run:
workflows: ["Create Release from DMS"]
types: [completed]
branches: [master]
workflow_dispatch:
inputs:
version:
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
required: false
default: ''
jobs:
build-and-upload:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine version
id: version
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
echo "Using manual version: $VERSION"
elif [ "${{ github.event_name }}" = "workflow_run" ]; then
VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Using latest release version from workflow_run: $VERSION"
else
VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Using latest release version: $VERSION"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "✅ Building DMS stable version: $VERSION"
- name: Setup build environment
run: |
sudo apt-get update
sudo apt-get install -y rpm wget curl jq gzip
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
echo "✅ RPM build environment ready"
- name: Download release assets
run: |
VERSION="${{ steps.version.outputs.version }}"
cd ~/rpmbuild/SOURCES
echo "📦 Downloading DMS QML source for v${VERSION}..."
# Download DMS QML source
wget "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
echo "❌ Failed to download dms-qml.tar.gz for v${VERSION}"
exit 1
}
echo "✅ Source downloaded"
echo "Note: dms-cli and dgop binaries will be downloaded during build based on target architecture"
ls -lh
- name: Generate stable spec file
run: |
VERSION="${{ steps.version.outputs.version }}"
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
# Spec for DMS stable releases - Generated by GitHub Actions
%global debug_package %{nil}
%global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
Name: dms
Version: %{version}
Release: 1%{?dist}
Summary: %{pkg_summary}
License: GPL-3.0-only
URL: https://github.com/AvengeMedia/DankMaterialShell
Source0: dms-qml.tar.gz
BuildRequires: gzip
BuildRequires: wget
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli
Requires: dgop
Requires: fira-code-fonts
Requires: material-symbols-fonts
Requires: rsms-inter-fonts
Recommends: brightnessctl
Recommends: cava
Recommends: cliphist
Recommends: hyprpicker
Recommends: matugen
Recommends: wl-clipboard
Recommends: NetworkManager
Recommends: qt6-qtmultimedia
Suggests: qt6ct
%description
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
and optimized for the niri and hyprland compositors. Features notifications,
app launcher, wallpaper customization, and fully customizable with plugins.
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
process monitoring, notification center, clipboard history, dock, control center,
lock screen, and comprehensive plugin system.
%package -n dms-cli
Summary: DankMaterialShell CLI tool
License: GPL-3.0-only
URL: https://github.com/AvengeMedia/danklinux
%description -n dms-cli
Command-line interface for DankMaterialShell configuration and management.
Provides native DBus bindings, NetworkManager integration, and system utilities.
%package -n dgop
Summary: Stateless CPU/GPU monitor for DankMaterialShell
License: MIT
URL: https://github.com/AvengeMedia/dgop
Provides: dgop
%description -n dgop
DGOP is a stateless system monitoring tool that provides CPU, GPU, memory, and
network statistics. Designed for integration with DankMaterialShell but can be
used standalone. This package always includes the latest stable dgop release.
%prep
%setup -q -c -n dms-qml
# Download architecture-specific binaries during build
# This ensures the correct architecture is used for each build target
case "%{_arch}" in
x86_64)
ARCH_SUFFIX="amd64"
;;
aarch64)
ARCH_SUFFIX="arm64"
;;
*)
echo "Unsupported architecture: %{_arch}"
exit 1
;;
esac
# Download dms-cli for target architecture
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/danklinux/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
echo "Failed to download dms-cli for architecture %{_arch}"
exit 1
}
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
chmod +x %{_builddir}/dms-cli
# Download dgop for target architecture
wget -O %{_builddir}/dgop.gz "https://github.com/AvengeMedia/dgop/releases/latest/download/dgop-linux-${ARCH_SUFFIX}.gz" || {
echo "Failed to download dgop for architecture %{_arch}"
exit 1
}
gunzip -c %{_builddir}/dgop.gz > %{_builddir}/dgop
chmod +x %{_builddir}/dgop
%build
%install
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -f %{buildroot}%{_datadir}/quickshell/dms/*.spec
%posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
# Restart DMS for active users after upgrade
if [ "$1" -ge 2 ]; then
# Find all quickshell DMS processes (PID and username)
while read pid cmd; do
username=$(ps -o user= -p "$pid" 2>/dev/null)
[ "$username" = "root" ] && continue
[ -z "$username" ] && continue
# Get user's UID and validate session
user_uid=$(id -u "$username" 2>/dev/null)
[ -z "$user_uid" ] && continue
[ ! -d "/run/user/$user_uid" ] && continue
wayland_display=$(tr '\0' '\n' < /proc/$pid/environ 2>/dev/null | grep '^WAYLAND_DISPLAY=' | cut -d= -f2)
[ -z "$wayland_display" ] && continue
echo "Restarting DMS for user: $username"
# Run as user with full Wayland session environment
runuser -u "$username" -- /bin/sh -c "
export XDG_RUNTIME_DIR=/run/user/$user_uid
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$user_uid/bus
export WAYLAND_DISPLAY=$wayland_display
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:\$PATH
dms restart >/dev/null 2>&1
" 2>/dev/null || true
break
done < <(pgrep -a -f 'quickshell.*dms' 2>/dev/null)
fi
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_datadir}/quickshell/dms/
%files -n dms-cli
%{_bindir}/dms
%files -n dgop
%{_bindir}/dgop
%changelog
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
- Stable release VERSION_PLACEHOLDER
- Built from GitHub release
- Includes latest dms-cli and dgop binaries
SPECEOF
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
echo "✅ Spec file generated for v${VERSION}"
echo ""
echo "=== Spec file preview ==="
head -40 ~/rpmbuild/SPECS/dms.spec
- name: Build SRPM
id: build
run: |
cd ~/rpmbuild/SPECS
echo "🔨 Building SRPM..."
rpmbuild -bs dms.spec
SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
SRPM_NAME=$(basename "$SRPM")
echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
echo "srpm_name=$SRPM_NAME" >> $GITHUB_OUTPUT
echo "✅ SRPM built: $SRPM_NAME"
echo ""
echo "=== SRPM Info ==="
rpm -qpi "$SRPM"
- name: Upload SRPM artifact
uses: actions/upload-artifact@v4
with:
name: dms-stable-srpm-${{ steps.version.outputs.version }}
path: ${{ steps.build.outputs.srpm_path }}
retention-days: 90
- name: Install Copr CLI
run: |
sudo apt-get install -y python3-pip
pip3 install copr-cli
mkdir -p ~/.config
cat > ~/.config/copr << EOF
[copr-cli]
login = ${{ secrets.COPR_LOGIN }}
username = avengemedia
token = ${{ secrets.COPR_TOKEN }}
copr_url = https://copr.fedorainfracloud.org
EOF
chmod 600 ~/.config/copr
echo "✅ Copr CLI configured"
- name: Upload to Copr
run: |
SRPM="${{ steps.build.outputs.srpm_path }}"
VERSION="${{ steps.version.outputs.version }}"
echo "🚀 Uploading SRPM to avengemedia/dms..."
echo " SRPM: $(basename $SRPM)"
echo " Version: $VERSION"
BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
echo "$BUILD_OUTPUT"
BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
if [ "$BUILD_ID" != "unknown" ]; then
echo "✅ Build submitted successfully!"
echo "🔗 https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
else
echo "⚠️ Could not extract build ID, but upload may have succeeded"
fi
- name: Build summary
if: always()
run: |
echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Stable release has been built and uploaded to Copr!" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,190 +0,0 @@
name: POEditor Diff & Sync
on:
push:
branches: [ master ]
workflow_dispatch: {}
concurrency:
group: poeditor-sync
cancel-in-progress: false
jobs:
sync-translations:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Extract source strings from codebase
env:
API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }}
PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
run: |
set -euo pipefail
echo "::group::Extracting strings from QML files"
python3 translations/extract_translations.py
echo "::endgroup::"
echo "::group::Checking for changes in en.json"
if [[ -f "translations/en.json" ]]; then
jq -S . "translations/en.json" > /tmp/en_new.json
if [[ -f "translations/en.json.orig" ]]; then
jq -S . "translations/en.json.orig" > /tmp/en_old.json
else
git show HEAD:translations/en.json > /tmp/en_old.json 2>/dev/null || echo "[]" > /tmp/en_old.json
jq -S . /tmp/en_old.json > /tmp/en_old.json.tmp && mv /tmp/en_old.json.tmp /tmp/en_old.json
fi
if diff -q /tmp/en_new.json /tmp/en_old.json >/dev/null 2>&1; then
echo "No changes in source strings"
echo "source_changed=false" >> "$GITHUB_OUTPUT"
else
echo "Detected changes in source strings"
echo "source_changed=true" >> "$GITHUB_OUTPUT"
echo "::group::Uploading source strings to POEditor"
RESP=$(curl -sS -X POST https://api.poeditor.com/v2/projects/upload \
-F api_token="$API_TOKEN" \
-F id="$PROJECT_ID" \
-F updating="terms" \
-F file=@"translations/en.json")
STATUS=$(echo "$RESP" | jq -r '.response.status')
if [[ "$STATUS" != "success" ]]; then
echo "::warning::POEditor upload failed: $RESP"
else
TERMS_ADDED=$(echo "$RESP" | jq -r '.result.terms.added // 0')
TERMS_UPDATED=$(echo "$RESP" | jq -r '.result.terms.updated // 0')
TERMS_DELETED=$(echo "$RESP" | jq -r '.result.terms.deleted // 0')
echo "Terms added: $TERMS_ADDED, updated: $TERMS_UPDATED, deleted: $TERMS_DELETED"
fi
echo "::endgroup::"
fi
else
echo "::warning::translations/en.json not found"
echo "source_changed=false" >> "$GITHUB_OUTPUT"
fi
echo "::endgroup::"
id: extract
- name: Commit and push source strings
if: steps.extract.outputs.source_changed == 'true'
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add translations/en.json translations/template.json
git commit -m "i18n: update source strings from codebase"
for attempt in 1 2 3; do
if git push; then
echo "Successfully pushed source string updates"
exit 0
fi
echo "Push attempt $attempt failed, pulling and retrying..."
git pull --rebase
sleep $((attempt*2))
done
echo "Failed to push after retries" >&2
exit 1
- name: Export and update translations from POEditor
env:
API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }}
PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
run: |
set -euo pipefail
LANGUAGES=(
"ja:translations/poexports/ja.json"
"zh-Hans:translations/poexports/zh_CN.json"
"pt-br:translations/poexports/pt.json"
"tr:translations/poexports/tr.json"
)
ANY_CHANGED=false
for lang_pair in "${LANGUAGES[@]}"; do
IFS=':' read -r PO_LANG REPO_FILE <<< "$lang_pair"
echo "::group::Processing $PO_LANG"
RESP=$(curl -sS -X POST https://api.poeditor.com/v2/projects/export \
-d api_token="$API_TOKEN" \
-d id="$PROJECT_ID" \
-d language="$PO_LANG" \
-d type="key_value_json")
STATUS=$(echo "$RESP" | jq -r '.response.status')
if [[ "$STATUS" != "success" ]]; then
echo "POEditor export request failed for $PO_LANG: $RESP" >&2
continue
fi
URL=$(echo "$RESP" | jq -r '.result.url')
if [[ -z "$URL" || "$URL" == "null" ]]; then
echo "No export URL returned for $PO_LANG" >&2
continue
fi
curl -sS -L "$URL" -o "/tmp/po_export_${PO_LANG}.json"
jq -S . "/tmp/po_export_${PO_LANG}.json" > "/tmp/po_export_${PO_LANG}.norm.json"
if [[ -f "$REPO_FILE" ]]; then
jq -S . "$REPO_FILE" > "/tmp/repo_${PO_LANG}.norm.json" || echo "{}" > "/tmp/repo_${PO_LANG}.norm.json"
else
echo "{}" > "/tmp/repo_${PO_LANG}.norm.json"
fi
if diff -q "/tmp/po_export_${PO_LANG}.norm.json" "/tmp/repo_${PO_LANG}.norm.json" >/dev/null; then
echo "No changes for $PO_LANG"
else
echo "Detected changes for $PO_LANG"
mkdir -p "$(dirname "$REPO_FILE")"
cp "/tmp/po_export_${PO_LANG}.norm.json" "$REPO_FILE"
ANY_CHANGED=true
fi
echo "::endgroup::"
done
echo "any_changed=$ANY_CHANGED" >> "$GITHUB_OUTPUT"
id: export
- name: Commit and push translation updates
if: steps.export.outputs.any_changed == 'true'
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add translations/poexports/*.json
git commit -m "i18n: update translations"
for attempt in 1 2 3; do
if git push; then
echo "Successfully pushed translation updates"
exit 0
fi
echo "Push attempt $attempt failed, pulling and retrying..."
git pull --rebase
sleep $((attempt*2))
done
echo "Failed to push after retries" >&2
exit 1

View File

@@ -1,221 +1,59 @@
# Release from a dispatch event from the danklinux repo name: Create Release
name: Create Release from DMS
on: on:
repository_dispatch: push:
types: [dms_release] tags:
- 'v*'
permissions:
contents: write
actions: write
concurrency: concurrency:
group: release-${{ github.event.client_payload.tag }} group: release-${{ github.ref_name }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
create_release_from_dms: create_release:
name: 📦 Create GitHub Release
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
env:
TAG: ${{ github.event.client_payload.tag }}
DMS_REPO: ${{ github.event.client_payload.dms_repo }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0 # Fetch full history for changelog generation
- name: Ensure VERSION and tag
run: |
set -euxo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
echo "${TAG}" > VERSION
git add -A VERSION
if ! git diff --cached --quiet; then
git commit -m "Update VERSION to ${TAG} (from DMS)"
fi
git tag -f "${TAG}"
git push origin HEAD
git push -f origin "${TAG}"
# Generate changelog
- name: Generate Changelog - name: Generate Changelog
id: changelog id: changelog
run: | run: |
set -e # Get the previous tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "") PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then if [ -z "$PREVIOUS_TAG" ]; then
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" --author='^(?!github-actions\[bot\])' | head -50) echo "No previous tag found, using all commits"
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50)
else else
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" --author='^(?!github-actions\[bot\])' "${PREVIOUS_TAG}..${TAG}") echo "Generating changelog from $PREVIOUS_TAG to HEAD"
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" $PREVIOUS_TAG..HEAD)
fi fi
cat > RELEASE_BODY.md << 'EOF' # Create the changelog with proper formatting
## Assets cat > CHANGELOG.md << EOF
### Complete Packages
- **`dms-full-amd64.tar.gz`** - Complete package for x86_64 systems (CLI binaries + QML source + installation guide)
- **`dms-full-arm64.tar.gz`** - Complete package for ARM64 systems (CLI binaries + QML source + installation guide)
### Individual Components
- **`dms-cli-amd64.gz`** - DMS CLI binary for x86_64 systems
- **`dms-cli-arm64.gz`** - DMS CLI binary for ARM64 systems
- **`dms-distropkg-amd64.gz`** - DMS CLI binary built with distro_package tag for AMD64 systems
- **`dms-distropkg-arm64.gz`** - DMS CLI binary built with distro_package tag for ARM64 systems
- **`dms-qml.tar.gz`** - QML source code only
### Checksums
- **`*.sha256`** - SHA256 checksums for verifying download integrity
**Installation:** Extract the `dms-full-*.tar.gz` package for your architecture and follow the `INSTALL.md` instructions inside.
---
EOF
cat >> RELEASE_BODY.md << EOF
## What's Changed ## What's Changed
$CHANGELOG $CHANGELOG
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${TAG} **Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ github.ref_name }}
EOF EOF
# Set output for use in release step
echo "changelog<<EOF" >> $GITHUB_OUTPUT echo "changelog<<EOF" >> $GITHUB_OUTPUT
cat RELEASE_BODY.md >> $GITHUB_OUTPUT cat CHANGELOG.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
- name: Create/Update DankMaterialShell Release # Create GitHub Release
uses: softprops/action-gh-release@v2 - name: Create GitHub Release
uses: comnoco/create-release-action@v2.0.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ env.TAG }} tag_name: ${{ github.ref_name }}
name: Release ${{ env.TAG }} release_name: Release ${{ github.ref_name }}
body: ${{ steps.changelog.outputs.changelog }} body: ${{ steps.changelog.outputs.changelog }}
draft: false draft: false
prerelease: ${{ contains(env.TAG, '-') }} prerelease: ${{ contains(github.ref_name, '-') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download and prepare release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euxo pipefail
mkdir -p _release_assets
# Download DMS CLI binaries from the danklinux repo
gh release download "${TAG}" -R "${DMS_REPO}" --dir ./_dms_assets
# Rename CLI binaries to dms-cli-* format and copy distropkg binaries
for file in _dms_assets/dms-*.gz*; do
if [ -f "$file" ]; then
basename=$(basename "$file")
if [[ "$basename" == dms-distropkg-* ]]; then
cp "$file" "_release_assets/$basename"
else
newname=$(echo "$basename" | sed 's/^dms-/dms-cli-/')
cp "$file" "_release_assets/$newname"
fi
fi
done
# Create QML source package (exclude .git, .github, build artifacts)
tar --exclude='.git' \
--exclude='.github' \
--exclude='_dms_assets' \
--exclude='_release_assets' \
--exclude='*.tar.gz' \
-czf _release_assets/dms-qml.tar.gz .
# Generate checksum for QML package
(cd _release_assets && sha256sum dms-qml.tar.gz > dms-qml.tar.gz.sha256)
# Create full packages for each architecture
for arch in amd64 arm64; do
mkdir -p _temp_full/dms
mkdir -p _temp_full/bin
# Extract QML source to temp directory
tar -xzf _release_assets/dms-qml.tar.gz -C _temp_full/dms
# Copy CLI binary if it exists
if [ -f "_dms_assets/dms-${arch}.gz" ]; then
gunzip -c "_dms_assets/dms-${arch}.gz" > _temp_full/bin/dms
chmod +x _temp_full/bin/dms
fi
# Copy distropkg binary if it exists
if [ -f "_dms_assets/dms-distropkg-${arch}.gz" ]; then
gunzip -c "_dms_assets/dms-distropkg-${arch}.gz" > _temp_full/bin/dms-distropkg
chmod +x _temp_full/bin/dms-distropkg
fi
# Create INSTALL.md
cat > _temp_full/INSTALL.md << 'EOF'
# DankMaterialShell Installation
## Requirements
- Wayland compositor (niri or Hyprland recommended)
- Quickshell framework
- Qt6
## Installation Steps
1. **Install quickshell assets:**
```bash
mkdir -p ~/.config/quickshell
cp -r dms ~/.config/quickshell/
```
2. **Install the DMS CLI binaries:**
```bash
sudo install -m 755 bin/dms /usr/local/bin/dms
# or install to a local directory:
mkdir -p ~/.local/bin
install -m 755 bin/dms ~/.local/bin/dms
```
3. **Start the shell:**
```bash
dms run
# or directly with quickshell (will lack some dbus integrations & plugin management):
quickshell -p ~/.config/quickshell/dms
```
## Configuration
- Settings are stored in `~/.config/DankMaterialShell/settings.json`
- Plugins go in `~/.config/DankMaterialShell/plugins/`
- See the documentation in the `dms/` directory for more details
## Troubleshooting
- Run with verbose output: `quickshell -v -p ~/.config/quickshell/dms`
- Check logs in `~/.local/state/DankMaterialShell/`
- Ensure all dependencies are installed
EOF
# Create the full package
(cd _temp_full && tar -czf "../_release_assets/dms-full-${arch}.tar.gz" .)
# Generate checksum
(cd _release_assets && sha256sum "dms-full-${arch}.tar.gz" > "dms-full-${arch}.tar.gz.sha256")
# Cleanup
rm -rf _temp_full
done
- name: Attach all assets to release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG }}
files: _release_assets/**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

255
CLAUDE.md
View File

@@ -63,9 +63,6 @@ quickshell -p shell.qml
# Or use the shorthand # Or use the shorthand
qs -p . qs -p .
# Run with verbose output for debugging
qs -v -p shell.qml
# Code formatting and linting # Code formatting and linting
qmlfmt -t 4 -i 4 -b 250 -w /path/to/file.qml # Format a QML file (requires qmlfmt, do not use qmlformat) qmlfmt -t 4 -i 4 -b 250 -w /path/to/file.qml # Format a QML file (requires qmlfmt, do not use qmlformat)
qmllint **/*.qml # Lint all QML files for syntax errors qmllint **/*.qml # Lint all QML files for syntax errors
@@ -92,7 +89,6 @@ shell.qml # Main entry point (minimal orchestration)
│ ├── DisplayService.qml │ ├── DisplayService.qml
│ ├── NotificationService.qml │ ├── NotificationService.qml
│ ├── WeatherService.qml │ ├── WeatherService.qml
│ ├── PluginService.qml
│ └── [14 more services] │ └── [14 more services]
├── Modules/ # UI components (93 files) ├── Modules/ # UI components (93 files)
│ ├── TopBar/ # Panel components (13 files) │ ├── TopBar/ # Panel components (13 files)
@@ -108,21 +104,15 @@ shell.qml # Main entry point (minimal orchestration)
│ ├── SettingsModal.qml │ ├── SettingsModal.qml
│ ├── ClipboardHistoryModal.qml │ ├── ClipboardHistoryModal.qml
│ ├── ProcessListModal.qml │ ├── ProcessListModal.qml
│ ├── PluginSettingsModal.qml
│ └── [7 more modals] │ └── [7 more modals]
── Widgets/ # Reusable UI controls (19 files) ── Widgets/ # Reusable UI controls (19 files)
├── DankIcon.qml ├── DankIcon.qml
├── DankSlider.qml ├── DankSlider.qml
├── DankToggle.qml ├── DankToggle.qml
├── DankTabBar.qml ├── DankTabBar.qml
├── DankGridView.qml ├── DankGridView.qml
├── DankListView.qml ├── DankListView.qml
└── [13 more widgets] └── [13 more widgets]
└── plugins/ # External plugins directory ($CONFIGPATH/DankMaterialShell/plugins/)
└── PluginName/ # Example Plugin structure
├── plugin.json # Plugin manifest
├── PluginNameWidget.qml # Widget component
└── PluginNameSettings.qml # Settings UI
``` ```
### Component Organization ### Component Organization
@@ -173,12 +163,6 @@ shell.qml # Main entry point (minimal orchestration)
- **DankLocationSearch**: Location picker with search - **DankLocationSearch**: Location picker with search
- **SystemLogo**: Animated system branding component - **SystemLogo**: Animated system branding component
7. **Plugins/** - External plugin system (`$CONFIGPATH/DankMaterialShell/plugins/`)
- **PluginService**: Discovers, loads, and manages plugin lifecycle
- **Dynamic Loading**: Plugins loaded at runtime from external directory
- **DankBar Integration**: Plugin widgets rendered alongside built-in widgets
- **Settings System**: Per-plugin settings with persistence
### Key Architectural Patterns ### Key Architectural Patterns
1. **Singleton Services Pattern**: 1. **Singleton Services Pattern**:
@@ -191,9 +175,9 @@ shell.qml # Main entry point (minimal orchestration)
Singleton { Singleton {
id: root id: root
property type value: defaultValue property type value: defaultValue
function performAction() { /* implementation */ } function performAction() { /* implementation */ }
} }
``` ```
@@ -251,23 +235,23 @@ shell.qml # Main entry point (minimal orchestration)
// For regular components // For regular components
Item { Item {
id: root id: root
property type name: value property type name: value
signal customSignal(type param) signal customSignal(type param)
onSignal: { /* handler */ } onSignal: { /* handler */ }
Component { /* children */ } Component { /* children */ }
} }
// For services (singletons) // For services (singletons)
Singleton { Singleton {
id: root id: root
property bool featureAvailable: false property bool featureAvailable: false
property type currentValue: defaultValue property type currentValue: defaultValue
function performAction(param) { /* implementation */ } function performAction(param) { /* implementation */ }
} }
``` ```
@@ -305,7 +289,7 @@ shell.qml # Main entry point (minimal orchestration)
```qml ```qml
// In services - detect capabilities // In services - detect capabilities
property bool brightnessAvailable: false property bool brightnessAvailable: false
// In modules - adapt UI accordingly // In modules - adapt UI accordingly
DankSlider { DankSlider {
visible: DisplayService.brightnessAvailable visible: DisplayService.brightnessAvailable
@@ -335,7 +319,7 @@ shell.qml # Main entry point (minimal orchestration)
console.log("Info message") // General info console.log("Info message") // General info
console.warn("Warning message") // Warnings console.warn("Warning message") // Warnings
console.error("Error message") // Errors console.error("Error message") // Errors
// Include context in service operations // Include context in service operations
onExited: (exitCode) => { onExited: (exitCode) => {
if (exitCode !== 0) { if (exitCode !== 0) {
@@ -424,10 +408,10 @@ When modifying the shell:
Singleton { Singleton {
id: root id: root
property bool featureAvailable: false property bool featureAvailable: false
property type currentValue: defaultValue property type currentValue: defaultValue
function performAction(param) { function performAction(param) {
// Implementation // Implementation
} }
@@ -438,7 +422,7 @@ When modifying the shell:
```qml ```qml
// In module files // In module files
property alias serviceValue: NewService.currentValue property alias serviceValue: NewService.currentValue
SomeControl { SomeControl {
visible: NewService.featureAvailable visible: NewService.featureAvailable
enabled: NewService.featureAvailable enabled: NewService.featureAvailable
@@ -446,200 +430,6 @@ When modifying the shell:
} }
``` ```
### Creating Plugins
Plugins are external, dynamically-loaded components that extend DankMaterialShell functionality. Plugins are stored in `~/.config/DankMaterialShell/plugins/` and have their settings isolated from core DMS settings.
**Plugin Types:**
- **Widget plugins** (`"type": "widget"` or omit type field): Display UI components in DankBar
- **Daemon plugins** (`"type": "daemon"`): Run invisibly in the background without UI
#### Widget Plugins
1. **Create plugin directory**:
```bash
mkdir -p ~/.config/DankMaterialShell/plugins/YourPlugin
```
2. **Create manifest** (`plugin.json`):
```json
{
"id": "yourPlugin",
"name": "Your Plugin",
"description": "Widget description",
"version": "1.0.0",
"author": "Your Name",
"icon": "extension",
"type": "widget",
"component": "./YourWidget.qml",
"settings": "./YourSettings.qml",
"permissions": ["settings_read", "settings_write"]
}
```
3. **Create widget component** (`YourWidget.qml`):
```qml
import QtQuick
import qs.Services
Rectangle {
id: root
property bool compactMode: false
property string section: "center"
property real widgetHeight: 30
property var pluginService: null
width: content.implicitWidth + 16
height: widgetHeight
radius: 8
color: "#20FFFFFF"
Component.onCompleted: {
if (pluginService) {
var data = pluginService.loadPluginData("yourPlugin", "key", defaultValue)
}
}
}
```
4. **Create settings component** (`YourSettings.qml`):
```qml
import QtQuick
import QtQuick.Controls
FocusScope {
id: root
property var pluginService: null
implicitHeight: settingsColumn.implicitHeight
height: implicitHeight
Column {
id: settingsColumn
anchors.fill: parent
anchors.margins: 16
spacing: 12
Text {
text: "Your Plugin Settings"
font.pixelSize: 18
font.weight: Font.Bold
}
// Your settings UI here
}
function saveSettings(key, value) {
if (pluginService) {
pluginService.savePluginData("yourPlugin", key, value)
}
}
function loadSettings(key, defaultValue) {
if (pluginService) {
return pluginService.loadPluginData("yourPlugin", key, defaultValue)
}
return defaultValue
}
}
```
5. **Enable plugin**:
- Open Settings → Plugins
- Click "Scan for Plugins"
- Toggle plugin to enable
- Add plugin ID to DankBar widget list
#### Daemon Plugins
Daemon plugins run invisibly in the background without any UI components. They're useful for monitoring system events, background tasks, or data synchronization.
1. **Create plugin directory**:
```bash
mkdir -p ~/.config/DankMaterialShell/plugins/YourDaemon
```
2. **Create manifest** (`plugin.json`):
```json
{
"id": "yourDaemon",
"name": "Your Daemon",
"description": "Background daemon description",
"version": "1.0.0",
"author": "Your Name",
"icon": "settings_applications",
"type": "daemon",
"component": "./YourDaemon.qml",
"permissions": ["settings_read", "settings_write"]
}
```
3. **Create daemon component** (`YourDaemon.qml`):
```qml
import QtQuick
import qs.Common
import qs.Services
Item {
id: root
property var pluginService: null
Connections {
target: SessionData
function onWallpaperPathChanged() {
console.log("Wallpaper changed:", SessionData.wallpaperPath)
if (pluginService) {
pluginService.savePluginData("yourDaemon", "lastEvent", Date.now())
}
}
}
Component.onCompleted: {
console.log("Daemon started")
}
}
```
4. **Enable daemon**:
- Open Settings → Plugins
- Click "Scan for Plugins"
- Toggle daemon to enable
- Daemon runs automatically in background
**Example**: See `PLUGINS/WallpaperWatcherDaemon/` for a complete daemon plugin that monitors wallpaper changes
**Plugin Directory Structure:**
```
~/.config/DankMaterialShell/
├── settings.json # Core DMS settings + plugin settings
│ └── pluginSettings: {
│ └── yourPlugin: {
│ ├── enabled: true,
│ └── customData: {...}
│ }
│ }
└── plugins/ # Plugin files directory
└── YourPlugin/ # Plugin directory (matches manifest ID)
├── plugin.json # Plugin manifest
├── YourWidget.qml # Widget component
└── YourSettings.qml # Settings UI (optional)
```
**Key Plugin APIs:**
- `pluginService.loadPluginData(pluginId, key, default)` - Load persistent data
- `pluginService.savePluginData(pluginId, key, value)` - Save persistent data
- `PluginService.enablePlugin(pluginId)` - Load plugin
- `PluginService.disablePlugin(pluginId)` - Unload plugin
**Important Notes:**
- Plugin settings are automatically injected by the PluginService via `item.pluginService = PluginService`
- Settings are stored in the main settings.json but namespaced under `pluginSettings.{pluginId}`
- Plugin directories must match the plugin ID in the manifest
- Use the injected `pluginService` property in both widget and settings components
### Debugging Common Issues ### Debugging Common Issues
1. **Import errors**: Check import paths 1. **Import errors**: Check import paths
@@ -664,7 +454,6 @@ Daemon plugins run invisibly in the background without any UI components. They'r
- **Function Discovery**: Use grep/search tools to find existing utility functions before implementing new ones - **Function Discovery**: Use grep/search tools to find existing utility functions before implementing new ones
- **Modern QML Patterns**: Leverage new widgets like DankTextField, DankDropdown, CachingImage - **Modern QML Patterns**: Leverage new widgets like DankTextField, DankDropdown, CachingImage
- **Structured Organization**: Follow the established Services/Modules/Widgets/Modals separation - **Structured Organization**: Follow the established Services/Modules/Widgets/Modals separation
- **Plugin System**: For user extensions, create plugins instead of modifying core modules - see docs/PLUGINS.md
### Common Widget Patterns ### Common Widget Patterns

View File

@@ -72,6 +72,10 @@ Singleton {
saveSettings() saveSettings()
} }
function getAppUsageRanking() {
return appUsageRanking
}
function getRankedApps() { function getRankedApps() {
var apps = [] var apps = []
for (var appId in appUsageRanking) { for (var appId in appUsageRanking) {

View File

@@ -1,122 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
readonly property int cacheConfigVersion: 1
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
readonly property string _stateDir: Paths.strip(_stateUrl)
property bool _loading: false
property string wallpaperLastPath: ""
property string profileLastPath: ""
Component.onCompleted: {
if (!isGreeterMode) {
loadCache()
}
}
function loadCache() {
_loading = true
parseCache(cacheFile.text())
_loading = false
}
function parseCache(content) {
_loading = true
try {
if (content && content.trim()) {
const cache = JSON.parse(content)
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : ""
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : ""
if (cache.configVersion === undefined) {
migrateFromUndefinedToV1(cache)
cleanupUnusedKeys()
saveCache()
}
}
} catch (e) {
console.warn("CacheData: Failed to parse cache:", e.message)
} finally {
_loading = false
}
}
function saveCache() {
if (_loading)
return
cacheFile.setText(JSON.stringify({
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath,
"configVersion": cacheConfigVersion
}, null, 2))
}
function migrateFromUndefinedToV1(cache) {
console.info("CacheData: Migrating configuration from undefined to version 1")
}
function cleanupUnusedKeys() {
const validKeys = [
"wallpaperLastPath",
"profileLastPath",
"configVersion"
]
try {
const content = cacheFile.text()
if (!content || !content.trim()) return
const cache = JSON.parse(content)
let needsSave = false
for (const key in cache) {
if (!validKeys.includes(key)) {
console.log("CacheData: Removing unused key:", key)
delete cache[key]
needsSave = true
}
}
if (needsSave) {
cacheFile.setText(JSON.stringify(cache, null, 2))
}
} catch (e) {
console.warn("CacheData: Failed to cleanup unused keys:", e.message)
}
}
FileView {
id: cacheFile
path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/cache.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
onLoaded: {
if (!isGreeterMode) {
parseCache(cacheFile.text())
}
}
onLoadFailed: error => {
if (!isGreeterMode) {
console.info("CacheData: No cache file found, starting fresh")
}
}
}
}

View File

@@ -1,62 +0,0 @@
import QtQuick
import Quickshell.Io
Item {
id: root
property alias path: socket.path
property alias parser: socket.parser
property bool connected: false
property int reconnectBaseMs: 400
property int reconnectMaxMs: 15000
property int _reconnectAttempt: 0
signal connectionStateChanged()
onConnectedChanged: {
socket.connected = connected
}
Socket {
id: socket
onConnectionStateChanged: {
root.connectionStateChanged()
if (connected) {
root._reconnectAttempt = 0
return
}
if (root.connected) {
root._scheduleReconnect()
}
}
}
Timer {
id: reconnectTimer
interval: 0
repeat: false
onTriggered: {
socket.connected = false
Qt.callLater(() => socket.connected = true)
}
}
function send(data) {
const json = typeof data === "string" ? data : JSON.stringify(data)
const message = json.endsWith("\n") ? json : json + "\n"
socket.write(message)
socket.flush()
}
function _scheduleReconnect() {
const pow = Math.min(_reconnectAttempt, 10)
const base = Math.min(reconnectBaseMs * Math.pow(2, pow), reconnectMaxMs)
const jitter = Math.floor(Math.random() * Math.floor(base / 4))
reconnectTimer.interval = base + jitter
reconnectTimer.restart()
_reconnectAttempt++
}
}

View File

@@ -1,53 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
readonly property var facts: [
"A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.",
"A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.",
"Right now, 100 trillion solar neutrinos are passing through your body every second.",
"The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.",
"The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.",
"There's a nebula out there that's actually colder than empty space itself.",
"We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.",
"Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.",
"Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.",
"Distant galaxies can move away from us faster than light because space itself is stretching.",
"The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.",
"The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.",
"A day on Venus lasts longer than its entire year around the Sun.",
"On Mercury, the time between sunrises is 176 Earth days long.",
"In about 4.5 billion years, our galaxy will smash into Andromeda.",
"Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.",
"PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.",
"Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.",
"Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.",
"Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.",
"Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.",
"Counting to a billion at one number per second would take over 31 years.",
"Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.",
"Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.",
"Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.",
"Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.",
"Even at light-speed, you'd never catch up to most galaxies—space expands faster.",
"Only around 5% of galaxies are ever reachable—even at light-speed.",
"If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.",
"If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.",
"Our oldest radio signals will reach the Milky Way's center in 26,000 years.",
"Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.",
"The Moon moves 3.8 centimeters farther from Earth every year.",
"The universe creates 275 million new stars every single day.",
"Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.",
"If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.",
"The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have."
]
function getRandomFact() {
return facts[Math.floor(Math.random() * facts.length)]
}
}

View File

@@ -1,113 +0,0 @@
import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
readonly property string _rawLocale: Qt.locale().name
readonly property string _lang: _rawLocale.split(/[_-]/)[0]
readonly property var _candidates: {
const fullUnderscore = _rawLocale;
const fullHyphen = _rawLocale.replace("_", "-");
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
}
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
property string currentLocale: "en"
property var translations: ({})
property bool translationsLoaded: false
property url _selectedPath: ""
FolderListModel {
id: dir
folder: root.translationsFolder
nameFilters: ["*.json"]
showDirs: false
showDotAndDotDot: false
onStatusChanged: if (status === FolderListModel.Ready) root._pickTranslation()
}
FileView {
id: translationLoader
path: root._selectedPath
onLoaded: {
try {
root.translations = JSON.parse(text())
root.translationsLoaded = true
console.info(`I18n: Loaded translations for '${root.currentLocale}' ` +
`(${Object.keys(root.translations).length} contexts)`)
} catch (e) {
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
"- falling back to English")
root._fallbackToEnglish()
}
}
onLoadFailed: (error) => {
console.warn(`I18n: Failed to load '${root.currentLocale}' (${error}), ` +
"falling back to English")
root._fallbackToEnglish()
}
}
function _pickTranslation() {
const present = new Set()
for (let i = 0; i < dir.count; i++) {
const name = dir.get(i, "fileName") // e.g. "zh_CN.json"
if (name && name.endsWith(".json")) {
present.add(name.slice(0, -5))
}
}
for (let i = 0; i < _candidates.length; i++) {
const cand = _candidates[i]
if (present.has(cand)) {
_useLocale(cand, dir.folder + "/" + cand + ".json")
return
}
}
_fallbackToEnglish()
}
function _useLocale(localeTag, fileUrl) {
currentLocale = localeTag
_selectedPath = fileUrl
translationsLoaded = false
translations = ({})
console.info(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
}
function _fallbackToEnglish() {
currentLocale = "en"
_selectedPath = ""
translationsLoaded = false
translations = ({})
console.warn("I18n: Falling back to built-in English strings")
}
function tr(term, context) {
if (!translationsLoaded || !translations) return term
const ctx = context || term
if (translations[ctx] && translations[ctx][term]) return translations[ctx][term]
for (const c in translations) {
if (translations[c] && translations[c][term]) return translations[c][term]
}
return term
}
function trContext(context, term) {
if (!translationsLoaded || !translations) return term
if (translations[context] && translations[context][term]) return translations[context][term]
return term
}
}

View File

@@ -38,10 +38,6 @@ Singleton {
return stringify(path).replace("file://", "") return stringify(path).replace("file://", "")
} }
function toFileUrl(path: string): string {
return path.startsWith("file://") ? path : "file://" + path
}
function mkdir(path: url): void { function mkdir(path: url): void {
Quickshell.execDetached(["mkdir", "-p", strip(path)]) Quickshell.execDetached(["mkdir", "-p", strip(path)])
} }

View File

@@ -1,70 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property int defaultDebounceMs: 50
property var _procDebouncers: ({}) // id -> { timer, command, callback, waitMs }
function runCommand(id, command, callback, debounceMs) {
const wait = (typeof debounceMs === "number" && debounceMs >= 0) ? debounceMs : defaultDebounceMs
let procId = id ? id : Math.random()
if (!_procDebouncers[procId]) {
const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root)
t.triggered.connect(function() { _launchProc(procId) })
_procDebouncers[procId] = { timer: t, command: command, callback: callback, waitMs: wait }
} else {
_procDebouncers[procId].command = command
_procDebouncers[procId].callback = callback
_procDebouncers[procId].waitMs = wait
}
const entry = _procDebouncers[procId]
entry.timer.interval = entry.waitMs
entry.timer.restart()
}
function _launchProc(id) {
const entry = _procDebouncers[id]
if (!entry) return
const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root)
const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc)
const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc)
proc.stdout = out
proc.stderr = err
proc.command = entry.command
let capturedOut = ""
let exitSeen = false
let exitCodeValue = -1
out.streamFinished.connect(function() {
capturedOut = out.text || ""
maybeComplete()
})
proc.exited.connect(function(code) {
exitSeen = true
exitCodeValue = code
maybeComplete()
})
function maybeComplete() {
if (!exitSeen) return
if (typeof entry.callback === "function") {
try { entry.callback(capturedOut, exitCodeValue) } catch (e) { console.warn("runCommand callback error:", e) }
}
try { proc.destroy() } catch (_) {}
}
proc.running = true
}
}

View File

@@ -9,19 +9,13 @@ import qs.Common
import qs.Services import qs.Services
Singleton { Singleton {
id: root id: root
readonly property int sessionConfigVersion: 1
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
property bool hasTriedDefaultSession: false
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
readonly property string _stateDir: Paths.strip(_stateUrl)
property bool isLightMode: false property bool isLightMode: false
property bool doNotDisturb: false
property string wallpaperPath: "" property string wallpaperPath: ""
property string wallpaperLastPath: ""
property string profileLastPath: ""
property bool perMonitorWallpaper: false property bool perMonitorWallpaper: false
property var monitorWallpapers: ({}) property var monitorWallpapers: ({})
property bool perModeWallpaper: false property bool perModeWallpaper: false
@@ -29,52 +23,59 @@ Singleton {
property string wallpaperPathDark: "" property string wallpaperPathDark: ""
property var monitorWallpapersLight: ({}) property var monitorWallpapersLight: ({})
property var monitorWallpapersDark: ({}) property var monitorWallpapersDark: ({})
property string wallpaperTransition: "fade" property bool doNotDisturb: false
readonly property var availableWallpaperTransitions: ["none", "fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"]
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
property bool wallpaperCyclingEnabled: false
property string wallpaperCyclingMode: "interval"
property int wallpaperCyclingInterval: 300
property string wallpaperCyclingTime: "06:00"
property var monitorCyclingSettings: ({})
property bool nightModeEnabled: false property bool nightModeEnabled: false
property int nightModeTemperature: 4500 property int nightModeTemperature: 4500
property bool nightModeAutoEnabled: false property bool nightModeAutoEnabled: false
property string nightModeAutoMode: "time" property string nightModeAutoMode: "time"
property bool hasTriedDefaultSession: false
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
readonly property string _stateDir: Paths.strip(_stateUrl)
property int nightModeStartHour: 18 property int nightModeStartHour: 18
property int nightModeStartMinute: 0 property int nightModeStartMinute: 0
property int nightModeEndHour: 6 property int nightModeEndHour: 6
property int nightModeEndMinute: 0 property int nightModeEndMinute: 0
property real latitude: 0.0 property real latitude: 0.0
property real longitude: 0.0 property real longitude: 0.0
property bool nightModeUseIPLocation: false
property string nightModeLocationProvider: "" property string nightModeLocationProvider: ""
property var pinnedApps: [] property var pinnedApps: []
property var recentColors: []
property bool showThirdPartyPlugins: false
property string launchPrefix: ""
property string lastBrightnessDevice: ""
property int selectedGpuIndex: 0 property int selectedGpuIndex: 0
property bool nvidiaGpuTempEnabled: false property bool nvidiaGpuTempEnabled: false
property bool nonNvidiaGpuTempEnabled: false property bool nonNvidiaGpuTempEnabled: false
property var enabledGpuPciIds: [] property var enabledGpuPciIds: []
property bool wallpaperCyclingEnabled: false
property string wallpaperCyclingMode: "interval" // "interval" or "time"
property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
property string wallpaperCyclingTime: "06:00" // HH:mm format
property var monitorCyclingSettings: ({})
property string lastBrightnessDevice: ""
property string launchPrefix: ""
property string wallpaperTransition: "fade"
readonly property var availableWallpaperTransitions: ["none", "fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"]
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
// Power management settings - AC Power
property int acMonitorTimeout: 0 // Never
property int acLockTimeout: 0 // Never
property int acSuspendTimeout: 0 // Never
property int acHibernateTimeout: 0 // Never
// Power management settings - Battery
property int batteryMonitorTimeout: 0 // Never
property int batteryLockTimeout: 0 // Never
property int batterySuspendTimeout: 0 // Never
property int batteryHibernateTimeout: 0 // Never
property bool lockBeforeSuspend: false
Component.onCompleted: { Component.onCompleted: {
if (!isGreeterMode) { loadSettings()
loadSettings()
}
} }
function loadSettings() { function loadSettings() {
if (isGreeterMode) { parseSettings(settingsFile.text())
parseSettings(greeterSessionFile.text())
} else {
parseSettings(settingsFile.text())
}
} }
function parseSettings(content) { function parseSettings(content) {
@@ -83,6 +84,8 @@ Singleton {
var settings = JSON.parse(content) var settings = JSON.parse(content)
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false
wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : "" wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : ""
wallpaperLastPath = settings.wallpaperLastPath !== undefined ? settings.wallpaperLastPath : ""
profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : ""
perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false
monitorWallpapers = settings.monitorWallpapers !== undefined ? settings.monitorWallpapers : {} monitorWallpapers = settings.monitorWallpapers !== undefined ? settings.monitorWallpapers : {}
perModeWallpaper = settings.perModeWallpaper !== undefined ? settings.perModeWallpaper : false perModeWallpaper = settings.perModeWallpaper !== undefined ? settings.perModeWallpaper : false
@@ -95,6 +98,7 @@ Singleton {
nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500 nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500
nightModeAutoEnabled = settings.nightModeAutoEnabled !== undefined ? settings.nightModeAutoEnabled : false nightModeAutoEnabled = settings.nightModeAutoEnabled !== undefined ? settings.nightModeAutoEnabled : false
nightModeAutoMode = settings.nightModeAutoMode !== undefined ? settings.nightModeAutoMode : "time" nightModeAutoMode = settings.nightModeAutoMode !== undefined ? settings.nightModeAutoMode : "time"
// Handle legacy time format
if (settings.nightModeStartTime !== undefined) { if (settings.nightModeStartTime !== undefined) {
const parts = settings.nightModeStartTime.split(":") const parts = settings.nightModeStartTime.split(":")
nightModeStartHour = parseInt(parts[0]) || 18 nightModeStartHour = parseInt(parts[0]) || 18
@@ -113,7 +117,6 @@ Singleton {
} }
latitude = settings.latitude !== undefined ? settings.latitude : 0.0 latitude = settings.latitude !== undefined ? settings.latitude : 0.0
longitude = settings.longitude !== undefined ? settings.longitude : 0.0 longitude = settings.longitude !== undefined ? settings.longitude : 0.0
nightModeUseIPLocation = settings.nightModeUseIPLocation !== undefined ? settings.nightModeUseIPLocation : false
nightModeLocationProvider = settings.nightModeLocationProvider !== undefined ? settings.nightModeLocationProvider : "" nightModeLocationProvider = settings.nightModeLocationProvider !== undefined ? settings.nightModeLocationProvider : ""
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : [] pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0 selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0
@@ -129,20 +132,20 @@ Singleton {
launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : "" launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : ""
wallpaperTransition = settings.wallpaperTransition !== undefined ? settings.wallpaperTransition : "fade" wallpaperTransition = settings.wallpaperTransition !== undefined ? settings.wallpaperTransition : "fade"
includedTransitions = settings.includedTransitions !== undefined ? settings.includedTransitions : availableWallpaperTransitions.filter(t => t !== "none") includedTransitions = settings.includedTransitions !== undefined ? settings.includedTransitions : availableWallpaperTransitions.filter(t => t !== "none")
recentColors = settings.recentColors !== undefined ? settings.recentColors : []
showThirdPartyPlugins = settings.showThirdPartyPlugins !== undefined ? settings.showThirdPartyPlugins : false
if (settings.configVersion === undefined) { acMonitorTimeout = settings.acMonitorTimeout !== undefined ? settings.acMonitorTimeout : 0
migrateFromUndefinedToV1(settings) acLockTimeout = settings.acLockTimeout !== undefined ? settings.acLockTimeout : 0
saveSettings() acSuspendTimeout = settings.acSuspendTimeout !== undefined ? settings.acSuspendTimeout : 0
} else if (settings.configVersion === sessionConfigVersion) { acHibernateTimeout = settings.acHibernateTimeout !== undefined ? settings.acHibernateTimeout : 0
cleanupUnusedKeys() batteryMonitorTimeout = settings.batteryMonitorTimeout !== undefined ? settings.batteryMonitorTimeout : 0
} batteryLockTimeout = settings.batteryLockTimeout !== undefined ? settings.batteryLockTimeout : 0
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
if (!isGreeterMode) { batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
if (typeof Theme !== "undefined") { lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
Theme.generateSystemThemesFromCurrentTheme()
} // Generate system themes but don't override user's theme choice
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
} }
} }
} catch (e) { } catch (e) {
@@ -151,10 +154,11 @@ Singleton {
} }
function saveSettings() { function saveSettings() {
if (isGreeterMode) return
settingsFile.setText(JSON.stringify({ settingsFile.setText(JSON.stringify({
"isLightMode": isLightMode, "isLightMode": isLightMode,
"wallpaperPath": wallpaperPath, "wallpaperPath": wallpaperPath,
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath,
"perMonitorWallpaper": perMonitorWallpaper, "perMonitorWallpaper": perMonitorWallpaper,
"monitorWallpapers": monitorWallpapers, "monitorWallpapers": monitorWallpapers,
"perModeWallpaper": perModeWallpaper, "perModeWallpaper": perModeWallpaper,
@@ -173,7 +177,6 @@ Singleton {
"nightModeEndMinute": nightModeEndMinute, "nightModeEndMinute": nightModeEndMinute,
"latitude": latitude, "latitude": latitude,
"longitude": longitude, "longitude": longitude,
"nightModeUseIPLocation": nightModeUseIPLocation,
"nightModeLocationProvider": nightModeLocationProvider, "nightModeLocationProvider": nightModeLocationProvider,
"pinnedApps": pinnedApps, "pinnedApps": pinnedApps,
"selectedGpuIndex": selectedGpuIndex, "selectedGpuIndex": selectedGpuIndex,
@@ -189,109 +192,98 @@ Singleton {
"launchPrefix": launchPrefix, "launchPrefix": launchPrefix,
"wallpaperTransition": wallpaperTransition, "wallpaperTransition": wallpaperTransition,
"includedTransitions": includedTransitions, "includedTransitions": includedTransitions,
"recentColors": recentColors, "acMonitorTimeout": acMonitorTimeout,
"showThirdPartyPlugins": showThirdPartyPlugins, "acLockTimeout": acLockTimeout,
"configVersion": sessionConfigVersion "acSuspendTimeout": acSuspendTimeout,
"acHibernateTimeout": acHibernateTimeout,
"batteryMonitorTimeout": batteryMonitorTimeout,
"batteryLockTimeout": batteryLockTimeout,
"batterySuspendTimeout": batterySuspendTimeout,
"batteryHibernateTimeout": batteryHibernateTimeout,
"lockBeforeSuspend": lockBeforeSuspend
}, null, 2)) }, null, 2))
} }
function migrateFromUndefinedToV1(settings) {
console.info("SessionData: Migrating configuration from undefined to version 1")
if (typeof SettingsData !== "undefined") {
if (settings.acMonitorTimeout !== undefined) {
SettingsData.setAcMonitorTimeout(settings.acMonitorTimeout)
}
if (settings.acLockTimeout !== undefined) {
SettingsData.setAcLockTimeout(settings.acLockTimeout)
}
if (settings.acSuspendTimeout !== undefined) {
SettingsData.setAcSuspendTimeout(settings.acSuspendTimeout)
}
if (settings.acHibernateTimeout !== undefined) {
SettingsData.setAcHibernateTimeout(settings.acHibernateTimeout)
}
if (settings.batteryMonitorTimeout !== undefined) {
SettingsData.setBatteryMonitorTimeout(settings.batteryMonitorTimeout)
}
if (settings.batteryLockTimeout !== undefined) {
SettingsData.setBatteryLockTimeout(settings.batteryLockTimeout)
}
if (settings.batterySuspendTimeout !== undefined) {
SettingsData.setBatterySuspendTimeout(settings.batterySuspendTimeout)
}
if (settings.batteryHibernateTimeout !== undefined) {
SettingsData.setBatteryHibernateTimeout(settings.batteryHibernateTimeout)
}
if (settings.lockBeforeSuspend !== undefined) {
SettingsData.setLockBeforeSuspend(settings.lockBeforeSuspend)
}
if (settings.loginctlLockIntegration !== undefined) {
SettingsData.setLoginctlLockIntegration(settings.loginctlLockIntegration)
}
if (settings.launchPrefix !== undefined) {
SettingsData.setLaunchPrefix(settings.launchPrefix)
}
}
if (typeof CacheData !== "undefined") {
if (settings.wallpaperLastPath !== undefined) {
CacheData.wallpaperLastPath = settings.wallpaperLastPath
}
if (settings.profileLastPath !== undefined) {
CacheData.profileLastPath = settings.profileLastPath
}
CacheData.saveCache()
}
}
function cleanupUnusedKeys() {
const validKeys = [
"isLightMode", "wallpaperPath", "perMonitorWallpaper", "monitorWallpapers", "perModeWallpaper",
"wallpaperPathLight", "wallpaperPathDark", "monitorWallpapersLight",
"monitorWallpapersDark", "doNotDisturb", "nightModeEnabled",
"nightModeTemperature", "nightModeAutoEnabled", "nightModeAutoMode",
"nightModeStartHour", "nightModeStartMinute", "nightModeEndHour",
"nightModeEndMinute", "latitude", "longitude", "nightModeUseIPLocation", "nightModeLocationProvider",
"pinnedApps", "selectedGpuIndex", "nvidiaGpuTempEnabled",
"nonNvidiaGpuTempEnabled", "enabledGpuPciIds", "wallpaperCyclingEnabled",
"wallpaperCyclingMode", "wallpaperCyclingInterval", "wallpaperCyclingTime",
"monitorCyclingSettings", "lastBrightnessDevice", "launchPrefix", "wallpaperTransition",
"includedTransitions", "recentColors", "showThirdPartyPlugins", "configVersion"
]
try {
const content = settingsFile.text()
if (!content || !content.trim()) return
const settings = JSON.parse(content)
let needsSave = false
for (const key in settings) {
if (!validKeys.includes(key)) {
console.log("SessionData: Removing unused key:", key)
delete settings[key]
needsSave = true
}
}
if (needsSave) {
settingsFile.setText(JSON.stringify(settings, null, 2))
}
} catch (e) {
console.warn("SessionData: Failed to cleanup unused keys:", e.message)
}
}
function setLightMode(lightMode) { function setLightMode(lightMode) {
isLightMode = lightMode isLightMode = lightMode
syncWallpaperForCurrentMode() syncWallpaperForCurrentMode()
saveSettings() saveSettings()
} }
function syncWallpaperForCurrentMode() {
if (!perModeWallpaper) return
if (perMonitorWallpaper) {
monitorWallpapers = isLightMode ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark)
return
}
wallpaperPath = isLightMode ? wallpaperPathLight : wallpaperPathDark
}
function setDoNotDisturb(enabled) { function setDoNotDisturb(enabled) {
doNotDisturb = enabled doNotDisturb = enabled
saveSettings() saveSettings()
} }
function setNightModeEnabled(enabled) {
nightModeEnabled = enabled
saveSettings()
}
function setNightModeTemperature(temperature) {
nightModeTemperature = temperature
saveSettings()
}
function setNightModeAutoEnabled(enabled) {
console.log("SessionData: Setting nightModeAutoEnabled to", enabled)
nightModeAutoEnabled = enabled
saveSettings()
}
function setNightModeAutoMode(mode) {
nightModeAutoMode = mode
saveSettings()
}
function setNightModeStartHour(hour) {
nightModeStartHour = hour
saveSettings()
}
function setNightModeStartMinute(minute) {
nightModeStartMinute = minute
saveSettings()
}
function setNightModeEndHour(hour) {
nightModeEndHour = hour
saveSettings()
}
function setNightModeEndMinute(minute) {
nightModeEndMinute = minute
saveSettings()
}
function setLatitude(lat) {
console.log("SessionData: Setting latitude to", lat)
latitude = lat
saveSettings()
}
function setLongitude(lng) {
console.log("SessionData: Setting longitude to", lng)
longitude = lng
saveSettings()
}
function setNightModeLocationProvider(provider) {
nightModeLocationProvider = provider
saveSettings()
}
function setWallpaperPath(path) { function setWallpaperPath(path) {
wallpaperPath = path wallpaperPath = path
saveSettings() saveSettings()
@@ -342,6 +334,131 @@ Singleton {
} }
} }
function setWallpaperLastPath(path) {
wallpaperLastPath = path
saveSettings()
}
function setProfileLastPath(path) {
profileLastPath = path
saveSettings()
}
function setPinnedApps(apps) {
pinnedApps = apps
saveSettings()
}
function addPinnedApp(appId) {
if (!appId)
return
var currentPinned = [...pinnedApps]
if (currentPinned.indexOf(appId) === -1) {
currentPinned.push(appId)
setPinnedApps(currentPinned)
}
}
function removePinnedApp(appId) {
if (!appId)
return
var currentPinned = pinnedApps.filter(id => id !== appId)
setPinnedApps(currentPinned)
}
function isPinnedApp(appId) {
return appId && pinnedApps.indexOf(appId) !== -1
}
function setSelectedGpuIndex(index) {
selectedGpuIndex = index
saveSettings()
}
function setNvidiaGpuTempEnabled(enabled) {
nvidiaGpuTempEnabled = enabled
saveSettings()
}
function setNonNvidiaGpuTempEnabled(enabled) {
nonNvidiaGpuTempEnabled = enabled
saveSettings()
}
function setEnabledGpuPciIds(pciIds) {
enabledGpuPciIds = pciIds
saveSettings()
}
function setWallpaperCyclingEnabled(enabled) {
wallpaperCyclingEnabled = enabled
saveSettings()
}
function setWallpaperCyclingMode(mode) {
wallpaperCyclingMode = mode
saveSettings()
}
function setWallpaperCyclingInterval(interval) {
wallpaperCyclingInterval = interval
saveSettings()
}
function setWallpaperCyclingTime(time) {
wallpaperCyclingTime = time
saveSettings()
}
function getMonitorCyclingSettings(screenName) {
return monitorCyclingSettings[screenName] || {
enabled: false,
mode: "interval",
interval: 300,
time: "06:00"
}
}
function setMonitorCyclingEnabled(screenName, enabled) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].enabled = enabled
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingMode(screenName, mode) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].mode = mode
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingInterval(screenName, interval) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].interval = interval
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingTime(screenName, time) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].time = time
monitorCyclingSettings = newSettings
saveSettings()
}
function setPerMonitorWallpaper(enabled) { function setPerMonitorWallpaper(enabled) {
perMonitorWallpaper = enabled perMonitorWallpaper = enabled
if (enabled && perModeWallpaper) { if (enabled && perModeWallpaper) {
@@ -433,172 +550,15 @@ Singleton {
} }
} }
function setWallpaperTransition(transition) { function getMonitorWallpaper(screenName) {
wallpaperTransition = transition if (!perMonitorWallpaper) {
saveSettings() return wallpaperPath
}
function setWallpaperCyclingEnabled(enabled) {
wallpaperCyclingEnabled = enabled
saveSettings()
}
function setWallpaperCyclingMode(mode) {
wallpaperCyclingMode = mode
saveSettings()
}
function setWallpaperCyclingInterval(interval) {
wallpaperCyclingInterval = interval
saveSettings()
}
function setWallpaperCyclingTime(time) {
wallpaperCyclingTime = time
saveSettings()
}
function setMonitorCyclingEnabled(screenName, enabled) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
} }
newSettings[screenName].enabled = enabled return monitorWallpapers[screenName] || wallpaperPath
monitorCyclingSettings = newSettings
saveSettings()
} }
function setMonitorCyclingMode(screenName, mode) { function setLastBrightnessDevice(device) {
var newSettings = Object.assign({}, monitorCyclingSettings) lastBrightnessDevice = device
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].mode = mode
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingInterval(screenName, interval) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].interval = interval
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingTime(screenName, time) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].time = time
monitorCyclingSettings = newSettings
saveSettings()
}
function setNightModeEnabled(enabled) {
nightModeEnabled = enabled
saveSettings()
}
function setNightModeTemperature(temperature) {
nightModeTemperature = temperature
saveSettings()
}
function setNightModeAutoEnabled(enabled) {
console.log("SessionData: Setting nightModeAutoEnabled to", enabled)
nightModeAutoEnabled = enabled
saveSettings()
}
function setNightModeAutoMode(mode) {
nightModeAutoMode = mode
saveSettings()
}
function setNightModeStartHour(hour) {
nightModeStartHour = hour
saveSettings()
}
function setNightModeStartMinute(minute) {
nightModeStartMinute = minute
saveSettings()
}
function setNightModeEndHour(hour) {
nightModeEndHour = hour
saveSettings()
}
function setNightModeEndMinute(minute) {
nightModeEndMinute = minute
saveSettings()
}
function setNightModeUseIPLocation(use) {
nightModeUseIPLocation = use
saveSettings()
}
function setLatitude(lat) {
console.log("SessionData: Setting latitude to", lat)
latitude = lat
saveSettings()
}
function setLongitude(lng) {
console.log("SessionData: Setting longitude to", lng)
longitude = lng
saveSettings()
}
function setNightModeLocationProvider(provider) {
nightModeLocationProvider = provider
saveSettings()
}
function setPinnedApps(apps) {
pinnedApps = apps
saveSettings()
}
function addPinnedApp(appId) {
if (!appId)
return
var currentPinned = [...pinnedApps]
if (currentPinned.indexOf(appId) === -1) {
currentPinned.push(appId)
setPinnedApps(currentPinned)
}
}
function removePinnedApp(appId) {
if (!appId)
return
var currentPinned = pinnedApps.filter(id => id !== appId)
setPinnedApps(currentPinned)
}
function isPinnedApp(appId) {
return appId && pinnedApps.indexOf(appId) !== -1
}
function addRecentColor(color) {
const colorStr = color.toString()
let recent = recentColors.slice()
recent = recent.filter(c => c !== colorStr)
recent.unshift(colorStr)
if (recent.length > 5) recent = recent.slice(0, 5)
recentColors = recent
saveSettings()
}
function setShowThirdPartyPlugins(enabled) {
showThirdPartyPlugins = enabled
saveSettings() saveSettings()
} }
@@ -607,107 +567,84 @@ Singleton {
saveSettings() saveSettings()
} }
function setLastBrightnessDevice(device) { function setWallpaperTransition(transition) {
lastBrightnessDevice = device wallpaperTransition = transition
saveSettings() saveSettings()
} }
function setSelectedGpuIndex(index) { function setAcMonitorTimeout(timeout) {
selectedGpuIndex = index acMonitorTimeout = timeout
saveSettings() saveSettings()
} }
function setNvidiaGpuTempEnabled(enabled) { function setAcLockTimeout(timeout) {
nvidiaGpuTempEnabled = enabled acLockTimeout = timeout
saveSettings() saveSettings()
} }
function setNonNvidiaGpuTempEnabled(enabled) { function setAcSuspendTimeout(timeout) {
nonNvidiaGpuTempEnabled = enabled acSuspendTimeout = timeout
saveSettings() saveSettings()
} }
function setEnabledGpuPciIds(pciIds) { function setBatteryMonitorTimeout(timeout) {
enabledGpuPciIds = pciIds batteryMonitorTimeout = timeout
saveSettings() saveSettings()
} }
function syncWallpaperForCurrentMode() { function setBatteryLockTimeout(timeout) {
if (!perModeWallpaper) return batteryLockTimeout = timeout
saveSettings()
if (perMonitorWallpaper) {
monitorWallpapers = isLightMode ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark)
return
}
wallpaperPath = isLightMode ? wallpaperPathLight : wallpaperPathDark
} }
function getMonitorWallpaper(screenName) { function setBatterySuspendTimeout(timeout) {
if (!perMonitorWallpaper) { batterySuspendTimeout = timeout
return wallpaperPath saveSettings()
}
return monitorWallpapers[screenName] || wallpaperPath
} }
function getMonitorCyclingSettings(screenName) { function setAcHibernateTimeout(timeout) {
return monitorCyclingSettings[screenName] || { acHibernateTimeout = timeout
enabled: false, saveSettings()
mode: "interval", }
interval: 300,
time: "06:00" function setBatteryHibernateTimeout(timeout) {
} batteryHibernateTimeout = timeout
saveSettings()
}
function setLockBeforeSuspend(enabled) {
lockBeforeSuspend = enabled
saveSettings()
} }
FileView { FileView {
id: settingsFile id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json" path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
blockLoading: isGreeterMode blockLoading: true
blockWrites: true blockWrites: true
watchChanges: !isGreeterMode watchChanges: true
onLoaded: { onLoaded: {
if (!isGreeterMode) { parseSettings(settingsFile.text())
parseSettings(settingsFile.text()) hasTriedDefaultSession = false
hasTriedDefaultSession = false
}
} }
onLoadFailed: error => { onLoadFailed: error => {
if (!isGreeterMode && !hasTriedDefaultSession) { if (!hasTriedDefaultSession) {
hasTriedDefaultSession = true hasTriedDefaultSession = true
defaultSessionCheckProcess.running = true defaultSessionCheckProcess.running = true
} }
} }
} }
FileView {
id: greeterSessionFile
path: {
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
return greetCfgDir + "/session.json"
}
preload: isGreeterMode
blockLoading: false
blockWrites: true
watchChanges: false
printErrors: true
onLoaded: {
if (isGreeterMode) {
parseSettings(greeterSessionFile.text())
}
}
}
Process { Process {
id: defaultSessionCheckProcess id: defaultSessionCheckProcess
command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir
+ "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"] + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"]
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {
console.info("Copied default-session.json to session.json") console.log("Copied default-session.json to session.json")
settingsFile.reload() settingsFile.reload()
} }
} }
@@ -849,4 +786,4 @@ Singleton {
} }
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -14,19 +14,14 @@ import "StockThemes.js" as StockThemes
Singleton { Singleton {
id: root id: root
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericCacheLocation).toString()) + "/DankMaterialShell"
readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true" readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
readonly property real popupDistance: { // ! TODO - Synchronize with niri/hyprland gaps?
if (typeof SettingsData === "undefined") return 4 readonly property real popupDistance: 2
return SettingsData.popupGapsAuto ? Math.max(4, SettingsData.dankBarSpacing) : SettingsData.popupGapsManual
}
property string currentTheme: "blue" property string currentTheme: "blue"
property string currentThemeCategory: "generic" property string currentThemeCategory: "generic"
property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false
property bool colorsFileLoadFailed: false
readonly property string dynamic: "dynamic" readonly property string dynamic: "dynamic"
readonly property string custom : "custom" readonly property string custom : "custom"
@@ -36,8 +31,9 @@ Singleton {
readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "") readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "")
readonly property string wallpaperPath: { readonly property string wallpaperPath: {
if (typeof SessionData === "undefined") return "" if (typeof SessionData === "undefined") return ""
if (SessionData.perMonitorWallpaper) { if (SessionData.perMonitorWallpaper) {
// Use first monitor's wallpaper for dynamic theming
var screens = Quickshell.screens var screens = Quickshell.screens
if (screens.length > 0) { if (screens.length > 0) {
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name) var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
@@ -61,7 +57,7 @@ Singleton {
} }
readonly property string rawWallpaperPath: { readonly property string rawWallpaperPath: {
if (typeof SessionData === "undefined") return "" if (typeof SessionData === "undefined") return ""
if (SessionData.perMonitorWallpaper) { if (SessionData.perMonitorWallpaper) {
// Use first monitor's wallpaper for dynamic theming // Use first monitor's wallpaper for dynamic theming
var screens = Quickshell.screens var screens = Quickshell.screens
@@ -79,66 +75,17 @@ Singleton {
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
property var workerRunning: false property var workerRunning: false
property var matugenColors: ({}) property var matugenColors: ({})
property int colorUpdateTrigger: 0
property var customThemeData: null property var customThemeData: null
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell"
Component.onCompleted: { Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", stateDir]) Quickshell.execDetached(["mkdir", "-p", stateDir])
Proc.runCommand("matugenCheck", ["which", "matugen"], (output, code) => { matugenCheck.running = true
matugenAvailable = (code === 0) && !envDisableMatugen
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!matugenAvailable || isGreeterMode) {
return
}
if (colorsFileLoadFailed && currentTheme === dynamic && wallpaperPath) {
console.info("Theme: Matugen now available, regenerating colors for dynamic theme")
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
}
return
}
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
if (currentTheme === dynamic) {
if (wallpaperPath) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
}
}
} else {
let primaryColor
let matugenType
if (currentTheme === "custom") {
if (customThemeData && customThemeData.primary) {
primaryColor = customThemeData.primary
matugenType = customThemeData.matugen_type
}
} else {
primaryColor = currentThemeData.primary
matugenType = currentThemeData.matugen_type
}
if (primaryColor) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
}
}
}, 0)
if (typeof SessionData !== "undefined") { if (typeof SessionData !== "undefined") {
SessionData.isLightModeChanged.connect(root.onLightModeChanged) SessionData.isLightModeChanged.connect(root.onLightModeChanged)
isLightMode = SessionData.isLightMode
} }
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) { if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
@@ -146,14 +93,8 @@ Singleton {
} }
} }
function applyGreeterTheme(themeName) {
switchTheme(themeName, false, false)
if (themeName === dynamic && dynamicColorsFileView.path) {
dynamicColorsFileView.reload()
}
}
function getMatugenColor(path, fallback) { function getMatugenColor(path, fallback) {
colorUpdateTrigger
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode] let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode]
for (const part of path.split(".")) { for (const part of path.split(".")) {
@@ -195,16 +136,14 @@ Singleton {
} }
readonly property var availableMatugenSchemes: [ readonly property var availableMatugenSchemes: [
({ "value": "scheme-tonal-spot", "label": "Tonal Spot", "description": I18n.tr("Balanced palette with focused accents (default).") }), ({ "value": "scheme-tonal-spot", "label": "Tonal Spot", "description": "Balanced palette with focused accents (default)." }),
({ "value": "scheme-vibrant-spot", "label": "Vibrant Spot", "description": I18n.tr("Lively palette with saturated accents.") }), ({ "value": "scheme-content", "label": "Content", "description": "Derives colors that closely match the underlying image." }),
({ "value": "scheme-dynamic-contrast", "label": "Dynamic Contrast", "description": I18n.tr("High-contrast palette for strong visual distinction.") }), ({ "value": "scheme-expressive", "label": "Expressive", "description": "Vibrant palette with playful saturation." }),
({ "value": "scheme-content", "label": "Content", "description": I18n.tr("Derives colors that closely match the underlying image.") }), ({ "value": "scheme-fidelity", "label": "Fidelity", "description": "High-fidelity palette that preserves source hues." }),
({ "value": "scheme-expressive", "label": "Expressive", "description": I18n.tr("Vibrant palette with playful saturation.") }), ({ "value": "scheme-fruit-salad", "label": "Fruit Salad", "description": "Colorful mix of bright contrasting accents." }),
({ "value": "scheme-fidelity", "label": "Fidelity", "description": I18n.tr("High-fidelity palette that preserves source hues.") }), ({ "value": "scheme-monochrome", "label": "Monochrome", "description": "Minimal palette built around a single hue." }),
({ "value": "scheme-fruit-salad", "label": "Fruit Salad", "description": I18n.tr("Colorful mix of bright contrasting accents.") }), ({ "value": "scheme-neutral", "label": "Neutral", "description": "Muted palette with subdued, calming tones." }),
({ "value": "scheme-monochrome", "label": "Monochrome", "description": I18n.tr("Minimal palette built around a single hue.") }), ({ "value": "scheme-rainbow", "label": "Rainbow", "description": "Diverse palette spanning the full spectrum." })
({ "value": "scheme-neutral", "label": "Neutral", "description": I18n.tr("Muted palette with subdued, calming tones.") }),
({ "value": "scheme-rainbow", "label": "Rainbow", "description": I18n.tr("Diverse palette spanning the full spectrum.") })
] ]
function getMatugenScheme(value) { function getMatugenScheme(value) {
@@ -315,61 +254,6 @@ Singleton {
property int standardEasing: Easing.OutCubic property int standardEasing: Easing.OutCubic
property int emphasizedEasing: Easing.OutQuart property int emphasizedEasing: Easing.OutQuart
readonly property var expressiveCurves: {
"emphasized": [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1],
"emphasizedAccel": [0.3, 0, 0.8, 0.15, 1, 1],
"emphasizedDecel": [0.05, 0.7, 0.1, 1, 1, 1],
"standard": [0.2, 0, 0, 1, 1, 1],
"standardAccel": [0.3, 0, 1, 1, 1, 1],
"standardDecel": [0, 0, 0, 1, 1, 1],
"expressiveFastSpatial": [0.42, 1.67, 0.21, 0.9, 1, 1],
"expressiveDefaultSpatial": [0.38, 1.21, 0.22, 1, 1, 1],
"expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1]
}
readonly property var animationPresetDurations: {
"none": 0,
"short": 250,
"medium": 500,
"long": 750
}
readonly property int currentAnimationBaseDuration: {
if (typeof SettingsData === "undefined") return 500
if (SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) {
return SettingsData.customAnimationDuration
}
const presetMap = [0, 250, 500, 750]
return presetMap[SettingsData.animationSpeed] !== undefined ? presetMap[SettingsData.animationSpeed] : 500
}
readonly property var expressiveDurations: {
if (typeof SettingsData === "undefined") {
return {
"fast": 200,
"normal": 400,
"large": 600,
"extraLarge": 1000,
"expressiveFastSpatial": 350,
"expressiveDefaultSpatial": 500,
"expressiveEffects": 200
}
}
const baseDuration = currentAnimationBaseDuration
return {
"fast": baseDuration * 0.4,
"normal": baseDuration * 0.8,
"large": baseDuration * 1.2,
"extraLarge": baseDuration * 2.0,
"expressiveFastSpatial": baseDuration * 0.7,
"expressiveDefaultSpatial": baseDuration,
"expressiveEffects": baseDuration * 0.4
}
}
property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12 property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
property real spacingXS: 4 property real spacingXS: 4
property real spacingS: 8 property real spacingS: 8
@@ -419,13 +303,10 @@ Singleton {
currentThemeCategory = "generic" currentThemeCategory = "generic"
} }
} }
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode) if (savePrefs && typeof SettingsData !== "undefined")
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
SettingsData.setTheme(currentTheme) SettingsData.setTheme(currentTheme)
if (!isGreeterMode) { generateSystemThemesFromCurrentTheme()
generateSystemThemesFromCurrentTheme()
}
} }
function setLightMode(light, savePrefs = true, enableTransition = false) { function setLightMode(light, savePrefs = true, enableTransition = false) {
@@ -437,16 +318,11 @@ Singleton {
return return
} }
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode) isLightMode = light
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) if (savePrefs && typeof SessionData !== "undefined")
SessionData.setLightMode(light) SessionData.setLightMode(isLightMode)
if (!isGreeterMode) { PortalService.setLightMode(isLightMode)
// Skip with matugen becuase, our script runner will do it. generateSystemThemesFromCurrentTheme()
if (!matugenAvailable) {
PortalService.setLightMode(light)
}
generateSystemThemesFromCurrentTheme()
}
} }
function toggleLightMode(savePrefs = true) { function toggleLightMode(savePrefs = true) {
@@ -585,19 +461,6 @@ Singleton {
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5 return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
} }
function barIconSize(barThickness, offset) {
const defaultOffset = offset !== undefined ? offset : -6
return Math.round((barThickness / 48) * (iconSize + defaultOffset))
}
function barTextSize(barThickness) {
const scale = barThickness / 48
const dankBarScale = (typeof SettingsData !== "undefined" ? SettingsData.dankBarFontScale : 1.0)
if (scale <= 0.75) return fontSizeSmall * 0.9 * dankBarScale
if (scale >= 1.25) return fontSizeMedium * dankBarScale
return fontSizeSmall * dankBarScale
}
function getBatteryIcon(level, isCharging, batteryAvailable) { function getBatteryIcon(level, isCharging, batteryAvailable) {
if (!batteryAvailable) if (!batteryAvailable)
return _getBatteryPowerProfileIcon() return _getBatteryPowerProfileIcon()
@@ -690,6 +553,10 @@ Singleton {
function onLightModeChanged() { function onLightModeChanged() {
if (matugenColors && Object.keys(matugenColors).length > 0) {
colorUpdateTrigger++
}
if (currentTheme === "custom" && customThemeFileView.path) { if (currentTheme === "custom" && customThemeFileView.path) {
customThemeFileView.reload() customThemeFileView.reload()
} }
@@ -697,12 +564,10 @@ Singleton {
function setDesiredTheme(kind, value, isLight, iconTheme, matugenType) { function setDesiredTheme(kind, value, isLight, iconTheme, matugenType) {
if (!matugenAvailable) { if (!matugenAvailable) {
console.warn("Theme: matugen not available or disabled - cannot set system theme") console.warn("matugen not available or disabled - cannot set system theme")
return return
} }
console.info("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", "type:", matugenType)
if (typeof NiriService !== "undefined" && CompositorService.isNiri) { if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
NiriService.suppressNextToast() NiriService.suppressNextToast()
} }
@@ -713,8 +578,7 @@ Singleton {
"mode": isLight ? "light" : "dark", "mode": isLight ? "light" : "dark",
"iconTheme": iconTheme || "System Default", "iconTheme": iconTheme || "System Default",
"matugenType": matugenType || "scheme-tonal-spot", "matugenType": matugenType || "scheme-tonal-spot",
"surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc", "surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc"
"runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
} }
const json = JSON.stringify(desired) const json = JSON.stringify(desired)
@@ -722,23 +586,20 @@ Singleton {
Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`]) Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`])
workerRunning = true workerRunning = true
const syncModeWithPortal = (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) ? "true" : "false"
if (rawWallpaperPath.startsWith("we:")) { if (rawWallpaperPath.startsWith("we:")) {
console.log("Theme: Starting matugen worker (WE wallpaper)") console.log("calling matugen worker")
systemThemeGenerator.command = [ systemThemeGenerator.command = [
"sh", "-c", "sh", "-c",
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' '${syncModeWithPortal}' --run` `sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' --run`
] ]
} else { } else {
console.log("Theme: Starting matugen worker") systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, "--run"]
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, "--run"]
} }
systemThemeGenerator.running = true systemThemeGenerator.running = true
} }
function generateSystemThemesFromCurrentTheme() { function generateSystemThemesFromCurrentTheme() {
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode) if (!matugenAvailable)
if (!matugenAvailable || isGreeterMode)
return return
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
@@ -786,17 +647,8 @@ Singleton {
} }
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false" const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false"
Proc.runCommand("gtkApplier", [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir], (output, exitCode) => { gtkApplier.command = [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir]
if (exitCode === 0) { gtkApplier.running = true
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
ToastService.showInfo("GTK colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply GTK colors")
}
}
})
} }
function applyQtColors() { function applyQtColors() {
@@ -807,99 +659,62 @@ Singleton {
return return
} }
Proc.runCommand("qtApplier", [shellDir + "/scripts/qt.sh", configDir], (output, exitCode) => { qtApplier.command = [shellDir + "/scripts/qt.sh", configDir]
if (exitCode === 0) { qtApplier.running = true
if (typeof ToastService !== "undefined") {
ToastService.showInfo("Qt colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply Qt colors")
}
}
})
} }
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); } function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
function getFillMode(modeName) { Process {
switch(modeName) { id: matugenCheck
case "Stretch": return Image.Stretch command: ["which", "matugen"]
case "Fit": onExited: code => {
case "PreserveAspectFit": return Image.PreserveAspectFit matugenAvailable = (code === 0) && !envDisableMatugen
case "Fill": if (!matugenAvailable) {
case "PreserveAspectCrop": return Image.PreserveAspectCrop console.log("matugen not not available in path or disabled via DMS_DISABLE_MATUGEN")
case "Tile": return Image.Tile return
case "TileVertically": return Image.TileVertically }
case "TileHorizontally": return Image.TileHorizontally
case "Pad": return Image.Pad const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
default: return Image.PreserveAspectCrop const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
if (currentTheme === dynamic) {
if (wallpaperPath) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
}
}
} else {
let primaryColor
let matugenType
if (currentTheme === "custom") {
if (customThemeData && customThemeData.primary) {
primaryColor = customThemeData.primary
matugenType = customThemeData.matugen_type
}
} else {
primaryColor = currentThemeData.primary
matugenType = currentThemeData.matugen_type
}
if (primaryColor) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
}
}
} }
} }
function snap(value, dpr) {
const s = dpr || 1
return Math.round(value * s) / s Process {
id: ensureStateDir
} }
function px(value, dpr) {
const s = dpr || 1
return Math.round(value * s) / s
}
function hairline(dpr) {
return 1 / (dpr || 1)
}
function invertHex(hex) {
hex = hex.replace('#', '');
if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
return hex;
}
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
const invR = (255 - r).toString(16).padStart(2, '0');
const invG = (255 - g).toString(16).padStart(2, '0');
const invB = (255 - b).toString(16).padStart(2, '0');
return `#${invR}${invG}${invB}`;
}
property string baseLogoColor: {
if (typeof SettingsData === "undefined") return ""
const colorOverride = SettingsData.launcherLogoColorOverride
if (!colorOverride || colorOverride === "") return ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
return colorOverride
}
property string effectiveLogoColor: {
if (typeof SettingsData === "undefined") return ""
const colorOverride = SettingsData.launcherLogoColorOverride
if (!colorOverride || colorOverride === "") return ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
if (!SettingsData.launcherLogoColorInvertOnMode) {
return colorOverride
}
if (isLightMode) {
return invertHex(colorOverride)
}
return colorOverride
}
Process { Process {
id: systemThemeGenerator id: systemThemeGenerator
running: false running: false
@@ -907,19 +722,64 @@ Singleton {
onExited: exitCode => { onExited: exitCode => {
workerRunning = false workerRunning = false
if (exitCode === 0) { if (exitCode === 2) {
console.info("Theme: Matugen worker completed successfully") // Exit code 2 means wallpaper/color not found - this is expected on first run
if (currentTheme === dynamic) { console.log("Theme worker: wallpaper/color not found, skipping theme generation")
console.log("Theme: Reloading dynamic colors file") } else if (exitCode !== 0) {
dynamicColorsFileView.reload()
}
} else if (exitCode === 2) {
console.log("Theme: Matugen worker completed with code 2 (no changes needed)")
} else {
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.showError("Theme worker failed (" + exitCode + ")") ToastService.showError("Theme worker failed (" + exitCode + ")")
} }
console.warn("Theme: Matugen worker failed with exit code:", exitCode) console.warn("Theme worker failed with exit code:", exitCode)
}
}
}
Process {
id: gtkApplier
running: false
stdout: StdioCollector {
id: gtkStdout
}
stderr: StdioCollector {
id: gtkStderr
}
onExited: exitCode => {
if (exitCode === 0) {
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
ToastService.showInfo("GTK colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply GTK colors: " + gtkStderr.text)
}
}
}
}
Process {
id: qtApplier
running: false
stdout: StdioCollector {
id: qtStdout
}
stderr: StdioCollector {
id: qtStderr
}
onExited: exitCode => {
if (exitCode === 0) {
if (typeof ToastService !== "undefined") {
ToastService.showInfo("Qt colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply Qt colors: " + qtStderr.text)
}
} }
} }
} }
@@ -954,26 +814,20 @@ Singleton {
FileView { FileView {
id: dynamicColorsFileView id: dynamicColorsFileView
path: { path: stateDir + "/dms-colors.json"
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms" watchChanges: currentTheme === dynamic
const colorsPath = SessionData.isGreeterMode
? greetCfgDir + "/colors.json"
: stateDir + "/dms-colors.json"
return colorsPath
}
watchChanges: currentTheme === dynamic && !SessionData.isGreeterMode
function parseAndLoadColors() { function parseAndLoadColors() {
try { try {
const colorsText = dynamicColorsFileView.text() const colorsText = dynamicColorsFileView.text()
if (colorsText) { if (colorsText) {
root.matugenColors = JSON.parse(colorsText) root.matugenColors = JSON.parse(colorsText)
root.colorUpdateTrigger++
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.clearWallpaperError() ToastService.clearWallpaperError()
} }
} }
} catch (e) { } catch (e) {
console.error("Theme: Failed to parse dynamic colors:", e)
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error" ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Dynamic colors parse error: " + e.message) ToastService.showError("Dynamic colors parse error: " + e.message)
@@ -983,8 +837,6 @@ Singleton {
onLoaded: { onLoaded: {
if (currentTheme === dynamic) { if (currentTheme === dynamic) {
console.info("Theme: Dynamic colors file loaded successfully")
colorsFileLoadFailed = false
parseAndLoadColors() parseAndLoadColors()
} }
} }
@@ -996,20 +848,10 @@ Singleton {
} }
onLoadFailed: function (error) { onLoadFailed: function (error) {
if (currentTheme === dynamic) { if (currentTheme === dynamic && typeof ToastService !== "undefined") {
console.warn("Theme: Dynamic colors file load failed, marking for regeneration") ToastService.showError("Failed to read dynamic colors: " + error)
colorsFileLoadFailed = true
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!isGreeterMode && matugenAvailable && wallpaperPath) {
console.log("Theme: Matugen available, triggering immediate regeneration")
generateSystemThemesFromCurrentTheme()
}
} }
} }
onPathChanged: {
colorsFileLoadFailed = false
}
} }
IpcHandler { IpcHandler {
@@ -1017,7 +859,7 @@ Singleton {
function toggle(): string { function toggle(): string {
root.toggleLightMode() root.toggleLightMode()
return root.isLightMode ? "dark" : "light" return root.isLightMode ? "light" : "dark"
} }
function light(): string { function light(): string {

View File

@@ -1,11 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Services.Greetd
import qs.Common
import qs.Modules.Greetd
Scope {
id: root
GreeterSurface {}
}

View File

@@ -1,564 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals
import qs.Modals.Clipboard
import qs.Modals.Common
import qs.Modals.Settings
import qs.Modals.Spotlight
import qs.Modules
import qs.Modules.AppDrawer
import qs.Modules.DankDash
import qs.Modules.ControlCenter
import qs.Modules.Dock
import qs.Modules.Lock
import qs.Modules.Notepad
import qs.Modules.Notifications.Center
import qs.Widgets
import qs.Modules.Notifications.Popup
import qs.Modules.OSD
import qs.Modules.ProcessList
import qs.Modules.Settings
import qs.Modules.DankBar
import qs.Modules.DankBar.Popouts
import qs.Modules.HyprWorkspaces
import qs.Modules.Plugins
import qs.Services
Item {
id: root
Instantiator {
id: daemonPluginInstantiator
asynchronous: true
model: Object.keys(PluginService.pluginDaemonComponents)
delegate: Loader {
id: daemonLoader
property string pluginId: modelData
sourceComponent: PluginService.pluginDaemonComponents[pluginId]
onLoaded: {
if (item) {
item.pluginService = PluginService
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
item.pluginId = pluginId
console.info("Daemon plugin loaded:", pluginId)
}
}
}
}
Loader {
id: blurredWallpaperBackgroundLoader
active: SettingsData.blurredWallpaperLayer
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
WallpaperBackground {}
Lock {
id: lock
}
Loader {
id: dankBarLoader
asynchronous: false
property var currentPosition: SettingsData.dankBarPosition
property bool initialized: false
property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader
sourceComponent: DankBar {
hyprlandOverviewLoader: dankBarLoader.hyprlandOverviewLoaderRef
onColorPickerRequested: {
if (colorPickerModal.shouldBeVisible) {
colorPickerModal.close()
} else {
colorPickerModal.show()
}
}
}
Component.onCompleted: {
initialized = true
}
onCurrentPositionChanged: {
if (!initialized)
return
const component = sourceComponent
sourceComponent = null
sourceComponent = component
}
}
Loader {
id: dockLoader
active: true
asynchronous: false
property var currentPosition: SettingsData.dockPosition
property bool initialized: false
sourceComponent: Dock {
contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null
}
onLoaded: {
if (item) {
dockContextMenuLoader.active = true
}
}
Component.onCompleted: {
initialized = true
}
onCurrentPositionChanged: {
if (!initialized)
return
console.log("DEBUG: Dock position changed to:", currentPosition, "- recreating dock")
const comp = sourceComponent
sourceComponent = null
sourceComponent = comp
}
}
Loader {
id: dankDashPopoutLoader
active: false
asynchronous: true
sourceComponent: Component {
DankDashPopout {
id: dankDashPopout
Component.onCompleted: {
PopoutService.dankDashPopout = dankDashPopout
}
}
}
}
LazyLoader {
id: dockContextMenuLoader
active: false
DockContextMenu {
id: dockContextMenu
}
}
LazyLoader {
id: notificationCenterLoader
active: false
NotificationCenterPopout {
id: notificationCenter
Component.onCompleted: {
PopoutService.notificationCenterPopout = notificationCenter
}
}
}
Variants {
model: SettingsData.getFilteredScreens("notifications")
delegate: NotificationPopupManager {
modelData: item
}
}
LazyLoader {
id: controlCenterLoader
active: false
property var modalRef: colorPickerModal
property LazyLoader powerModalLoaderRef: powerMenuModalLoader
ControlCenterPopout {
id: controlCenterPopout
colorPickerModal: controlCenterLoader.modalRef
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
onLockRequested: {
lock.activate()
}
Component.onCompleted: {
PopoutService.controlCenterPopout = controlCenterPopout
}
}
}
WifiPasswordModal {
id: wifiPasswordModal
Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModal
}
}
Connections {
target: NetworkService
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService)
}
}
LazyLoader {
id: networkInfoModalLoader
active: false
NetworkInfoModal {
id: networkInfoModal
Component.onCompleted: {
PopoutService.networkInfoModal = networkInfoModal
}
}
}
LazyLoader {
id: batteryPopoutLoader
active: false
BatteryPopout {
id: batteryPopout
Component.onCompleted: {
PopoutService.batteryPopout = batteryPopout
}
}
}
LazyLoader {
id: vpnPopoutLoader
active: false
VpnPopout {
id: vpnPopout
Component.onCompleted: {
PopoutService.vpnPopout = vpnPopout
}
}
}
LazyLoader {
id: powerMenuLoader
active: false
PowerMenu {
id: powerMenu
onPowerActionRequested: (action, title, message) => {
if (SettingsData.powerActionConfirm) {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {})
}
} else {
actionApply(action)
}
}
function actionApply(action) {
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}
}
}
LazyLoader {
id: powerConfirmModalLoader
active: false
ConfirmModal {
id: powerConfirmModal
}
}
LazyLoader {
id: processListPopoutLoader
active: false
ProcessListPopout {
id: processListPopout
Component.onCompleted: {
PopoutService.processListPopout = processListPopout
}
}
}
SettingsModal {
id: settingsModal
Component.onCompleted: {
PopoutService.settingsModal = settingsModal
}
}
LazyLoader {
id: appDrawerLoader
active: false
AppDrawerPopout {
id: appDrawerPopout
Component.onCompleted: {
PopoutService.appDrawerPopout = appDrawerPopout
}
}
}
SpotlightModal {
id: spotlightModal
Component.onCompleted: {
PopoutService.spotlightModal = spotlightModal
}
}
ClipboardHistoryModal {
id: clipboardHistoryModalPopup
Component.onCompleted: {
PopoutService.clipboardHistoryModal = clipboardHistoryModalPopup
}
}
NotificationModal {
id: notificationModal
Component.onCompleted: {
PopoutService.notificationModal = notificationModal
}
}
DankColorPickerModal {
id: colorPickerModal
Component.onCompleted: {
PopoutService.colorPickerModal = colorPickerModal
}
}
LazyLoader {
id: processListModalLoader
active: false
ProcessListModal {
id: processListModal
Component.onCompleted: {
PopoutService.processListModal = processListModal
}
}
}
LazyLoader {
id: systemUpdateLoader
active: false
SystemUpdatePopout {
id: systemUpdatePopout
Component.onCompleted: {
PopoutService.systemUpdatePopout = systemUpdatePopout
}
}
}
Variants {
id: notepadSlideoutVariants
model: SettingsData.getFilteredScreens("notepad")
delegate: DankSlideout {
id: notepadSlideout
modelData: item
title: I18n.tr("Notepad")
slideoutWidth: 480
expandable: true
expandedWidthValue: 960
customTransparency: SettingsData.notepadTransparencyOverride
content: Component {
Notepad {
onHideRequested: {
notepadSlideout.hide()
}
}
}
function toggle() {
if (isVisible) {
hide()
} else {
show()
}
}
}
}
LazyLoader {
id: powerMenuModalLoader
active: false
PowerMenuModal {
id: powerMenuModal
onPowerActionRequested: (action, title, message) => {
if (SettingsData.powerActionConfirm) {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {})
}
} else {
actionApply(action)
}
}
function actionApply(action) {
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}
Component.onCompleted: {
PopoutService.powerMenuModal = powerMenuModal
}
}
}
LazyLoader {
id: hyprKeybindsModalLoader
active: false
HyprKeybindsModal {
id: hyprKeybindsModal
Component.onCompleted: {
PopoutService.hyprKeybindsModal = hyprKeybindsModal
}
}
}
DMSShellIPC {
powerMenuModalLoader: powerMenuModalLoader
processListModalLoader: processListModalLoader
controlCenterLoader: controlCenterLoader
dankDashPopoutLoader: dankDashPopoutLoader
notepadSlideoutVariants: notepadSlideoutVariants
hyprKeybindsModalLoader: hyprKeybindsModalLoader
dankBarLoader: dankBarLoader
hyprlandOverviewLoader: hyprlandOverviewLoader
}
Variants {
model: SettingsData.getFilteredScreens("toast")
delegate: Toast {
modelData: item
visible: ToastService.toastVisible
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: VolumeOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: MicMuteOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: BrightnessOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: IdleInhibitorOSD {
modelData: item
}
}
LazyLoader {
id: hyprlandOverviewLoader
active: CompositorService.isHyprland
component: HyprlandOverview {
id: hyprlandOverview
}
}
}

View File

@@ -1,388 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import qs.Common
import qs.Services
Item {
id: root
required property var powerMenuModalLoader
required property var processListModalLoader
required property var controlCenterLoader
required property var dankDashPopoutLoader
required property var notepadSlideoutVariants
required property var hyprKeybindsModalLoader
required property var dankBarLoader
required property var hyprlandOverviewLoader
IpcHandler {
function open() {
root.powerMenuModalLoader.active = true
if (root.powerMenuModalLoader.item)
root.powerMenuModalLoader.item.openCentered()
return "POWERMENU_OPEN_SUCCESS"
}
function close() {
if (root.powerMenuModalLoader.item)
root.powerMenuModalLoader.item.close()
return "POWERMENU_CLOSE_SUCCESS"
}
function toggle() {
root.powerMenuModalLoader.active = true
if (root.powerMenuModalLoader.item) {
if (root.powerMenuModalLoader.item.shouldBeVisible) {
root.powerMenuModalLoader.item.close()
} else {
root.powerMenuModalLoader.item.openCentered()
}
}
return "POWERMENU_TOGGLE_SUCCESS"
}
target: "powermenu"
}
IpcHandler {
function open(): string {
root.processListModalLoader.active = true
if (root.processListModalLoader.item)
root.processListModalLoader.item.show()
return "PROCESSLIST_OPEN_SUCCESS"
}
function close(): string {
if (root.processListModalLoader.item)
root.processListModalLoader.item.hide()
return "PROCESSLIST_CLOSE_SUCCESS"
}
function toggle(): string {
root.processListModalLoader.active = true
if (root.processListModalLoader.item)
root.processListModalLoader.item.toggle()
return "PROCESSLIST_TOGGLE_SUCCESS"
}
target: "processlist"
}
IpcHandler {
function open(): string {
if (root.dankBarLoader.item) {
root.dankBarLoader.item.triggerControlCenterOnFocusedScreen()
return "CONTROL_CENTER_OPEN_SUCCESS"
}
return "CONTROL_CENTER_OPEN_FAILED"
}
function close(): string {
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.close()
return "CONTROL_CENTER_CLOSE_SUCCESS"
}
return "CONTROL_CENTER_CLOSE_FAILED"
}
function toggle(): string {
if (root.dankBarLoader.item) {
root.dankBarLoader.item.triggerControlCenterOnFocusedScreen()
return "CONTROL_CENTER_TOGGLE_SUCCESS"
}
return "CONTROL_CENTER_TOGGLE_FAILED"
}
target: "control-center"
}
IpcHandler {
function open(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.dankDashPopoutLoader.item.dashVisible = true
return "DASH_OPEN_SUCCESS"
}
return "DASH_OPEN_FAILED"
}
function close(): string {
if (root.dankDashPopoutLoader.item) {
root.dankDashPopoutLoader.item.dashVisible = false
return "DASH_CLOSE_SUCCESS"
}
return "DASH_CLOSE_FAILED"
}
function toggle(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
if (root.dankDashPopoutLoader.item.dashVisible) {
root.dankDashPopoutLoader.item.dashVisible = false
} else {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.dankDashPopoutLoader.item.dashVisible = true
}
return "DASH_TOGGLE_SUCCESS"
}
return "DASH_TOGGLE_FAILED"
}
target: "dash"
}
IpcHandler {
function getFocusedScreenName() {
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
return Hyprland.focusedWorkspace.monitor.name
}
if (CompositorService.isNiri && NiriService.currentOutput) {
return NiriService.currentOutput
}
return ""
}
function getActiveNotepadInstance() {
if (root.notepadSlideoutVariants.instances.length === 0) {
return null
}
if (root.notepadSlideoutVariants.instances.length === 1) {
return root.notepadSlideoutVariants.instances[0]
}
var focusedScreen = getFocusedScreenName()
if (focusedScreen && root.notepadSlideoutVariants.instances.length > 0) {
for (var i = 0; i < root.notepadSlideoutVariants.instances.length; i++) {
var slideout = root.notepadSlideoutVariants.instances[i]
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
return slideout
}
}
}
for (var i = 0; i < root.notepadSlideoutVariants.instances.length; i++) {
var slideout = root.notepadSlideoutVariants.instances[i]
if (slideout.isVisible) {
return slideout
}
}
return root.notepadSlideoutVariants.instances[0]
}
function open(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.show()
return "NOTEPAD_OPEN_SUCCESS"
}
return "NOTEPAD_OPEN_FAILED"
}
function close(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.hide()
return "NOTEPAD_CLOSE_SUCCESS"
}
return "NOTEPAD_CLOSE_FAILED"
}
function toggle(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.toggle()
return "NOTEPAD_TOGGLE_SUCCESS"
}
return "NOTEPAD_TOGGLE_FAILED"
}
target: "notepad"
}
IpcHandler {
function toggle(): string {
SessionService.toggleIdleInhibit()
return SessionService.idleInhibited ? "Idle inhibit enabled" : "Idle inhibit disabled"
}
function enable(): string {
SessionService.enableIdleInhibit()
return "Idle inhibit enabled"
}
function disable(): string {
SessionService.disableIdleInhibit()
return "Idle inhibit disabled"
}
function status(): string {
return SessionService.idleInhibited ? "Idle inhibit is enabled" : "Idle inhibit is disabled"
}
function reason(newReason: string): string {
if (!newReason) {
return `Current reason: ${SessionService.inhibitReason}`
}
SessionService.setInhibitReason(newReason)
return `Inhibit reason set to: ${newReason}`
}
target: "inhibit"
}
IpcHandler {
function list(): string {
return MprisController.availablePlayers.map(p => p.identity).join("\n")
}
function play(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canPlay) {
MprisController.activePlayer.play()
}
}
function pause(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canPause) {
MprisController.activePlayer.pause()
}
}
function playPause(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canTogglePlaying) {
MprisController.activePlayer.togglePlaying()
}
}
function previous(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canGoPrevious) {
MprisController.activePlayer.previous()
}
}
function next(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canGoNext) {
MprisController.activePlayer.next()
}
}
function stop(): void {
if (MprisController.activePlayer) {
MprisController.activePlayer.stop()
}
}
target: "mpris"
}
IpcHandler {
function openBinds(): string {
if (!CompositorService.isHyprland) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprKeybindsModalLoader.active = true
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.open()
return "HYPR_KEYBINDS_OPEN_SUCCESS"
}
return "HYPR_KEYBINDS_OPEN_FAILED"
}
function closeBinds(): string {
if (!CompositorService.isHyprland) {
return "HYPR_NOT_AVAILABLE"
}
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.close()
return "HYPR_KEYBINDS_CLOSE_SUCCESS"
}
return "HYPR_KEYBINDS_CLOSE_FAILED"
}
function toggleBinds(): string {
if (!CompositorService.isHyprland) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprKeybindsModalLoader.active = true
if (root.hyprKeybindsModalLoader.item) {
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
root.hyprKeybindsModalLoader.item.close()
} else {
root.hyprKeybindsModalLoader.item.open()
}
return "HYPR_KEYBINDS_TOGGLE_SUCCESS"
}
return "HYPR_KEYBINDS_TOGGLE_FAILED"
}
function toggleOverview(): string {
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen
return root.hyprlandOverviewLoader.item.overviewOpen ? "OVERVIEW_OPEN_SUCCESS" : "OVERVIEW_CLOSE_SUCCESS"
}
function closeOverview(): string {
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprlandOverviewLoader.item.overviewOpen = false
return "OVERVIEW_CLOSE_SUCCESS"
}
function openOverview(): string {
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprlandOverviewLoader.item.overviewOpen = true
return "OVERVIEW_OPEN_SUCCESS"
}
target: "hypr"
}
IpcHandler {
function wallpaper(): string {
if (root.dankBarLoader.item && root.dankBarLoader.item.triggerWallpaperBrowserOnFocusedScreen()) {
return "SUCCESS: Toggled wallpaper browser"
}
return "ERROR: Failed to toggle wallpaper browser"
}
target: "dankdash"
}
}

View File

@@ -1,362 +0,0 @@
import QtQuick
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property string deviceName: ""
property string deviceAddress: ""
property string requestType: ""
property string token: ""
property int passkey: 0
property string pinInput: ""
property string passkeyInput: ""
function show(pairingData) {
token = pairingData.token || ""
deviceName = pairingData.deviceName || ""
deviceAddress = pairingData.deviceAddr || ""
requestType = pairingData.requestType || ""
passkey = pairingData.passkey || 0
pinInput = ""
passkeyInput = ""
open()
Qt.callLater(() => {
if (contentLoader.item) {
if (requestType === "pin" && contentLoader.item.pinInputField) {
contentLoader.item.pinInputField.forceActiveFocus()
} else if (requestType === "passkey" && contentLoader.item.passkeyInputField) {
contentLoader.item.passkeyInputField.forceActiveFocus()
}
}
})
}
shouldBeVisible: false
width: 420
height: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 240
onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) {
pinInput = ""
passkeyInput = ""
}
}
onOpened: {
Qt.callLater(() => {
if (contentLoader.item) {
if (requestType === "pin" && contentLoader.item.pinInputField) {
contentLoader.item.pinInputField.forceActiveFocus()
} else if (requestType === "passkey" && contentLoader.item.passkeyInputField) {
contentLoader.item.passkeyInputField.forceActiveFocus()
}
}
})
}
onBackgroundClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
content: Component {
FocusScope {
id: pairingContent
property alias pinInputField: pinInputField
property alias passkeyInputField: passkeyInputField
anchors.fill: parent
focus: true
implicitHeight: mainColumn.implicitHeight
Keys.onEscapePressed: event => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
event.accepted = true
}
Column {
id: mainColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingM
spacing: requestType === "pin" || requestType === "passkey" ? Theme.spacingM : Theme.spacingS
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Pair Bluetooth Device")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: {
if (requestType === "confirm")
return I18n.tr("Confirm passkey for ") + deviceName
if (requestType === "authorize")
return I18n.tr("Authorize pairing with ") + deviceName
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize service for ") + deviceName
if (requestType === "pin")
return I18n.tr("Enter PIN for ") + deviceName
if (requestType === "passkey")
return I18n.tr("Enter passkey for ") + deviceName
return deviceName
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width - 40
elide: Text.ElideRight
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: pinInputField.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: pinInputField.activeFocus ? 2 : 1
visible: requestType === "pin"
MouseArea {
anchors.fill: parent
onClicked: () => {
pinInputField.forceActiveFocus()
}
}
DankTextField {
id: pinInputField
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: pinInput
placeholderText: I18n.tr("Enter PIN")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
pinInput = text
}
onAccepted: () => {
submitPairing()
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: passkeyInputField.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: passkeyInputField.activeFocus ? 2 : 1
visible: requestType === "passkey"
MouseArea {
anchors.fill: parent
onClicked: () => {
passkeyInputField.forceActiveFocus()
}
}
DankTextField {
id: passkeyInputField
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: passkeyInput
placeholderText: I18n.tr("Enter 6-digit passkey")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
passkeyInput = text
}
onAccepted: () => {
submitPairing()
}
}
}
Rectangle {
width: parent.width
height: 56
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
visible: requestType === "confirm"
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: I18n.tr("Passkey:")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: String(passkey).padStart(6, "0")
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Item {
width: parent.width
height: 36
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
StyledText {
id: cancelText
anchors.centerIn: parent
text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
}
}
Rectangle {
width: Math.max(80, pairText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: pairArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: {
if (requestType === "pin")
return pinInput.length > 0
if (requestType === "passkey")
return passkeyInput.length === 6
return true
}
opacity: enabled ? 1 : 0.5
StyledText {
id: pairText
anchors.centerIn: parent
text: {
if (requestType === "confirm")
return I18n.tr("Confirm")
if (requestType === "authorize" || requestType.startsWith("authorize-service"))
return I18n.tr("Authorize")
return I18n.tr("Pair")
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: pairArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
submitPairing()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
DankActionButton {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
}
}
}
function submitPairing() {
const secrets = {}
if (requestType === "pin") {
secrets["pin"] = pinInput
} else if (requestType === "passkey") {
secrets["passkey"] = passkeyInput
} else if (requestType === "confirm" || requestType === "authorize" || requestType.startsWith("authorize-service")) {
secrets["decision"] = "yes"
}
DMSService.bluetoothSubmitPairing(token, secrets, true, response => {
if (response.error) {
ToastService.showError(I18n.tr("Pairing failed"), response.error)
}
})
close()
pinInput = ""
passkeyInput = ""
}
}

View File

@@ -30,7 +30,7 @@ Item {
showKeyboardHints: modal.showKeyboardHints showKeyboardHints: modal.showKeyboardHints
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
onClearAllClicked: { onClearAllClicked: {
clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () { clearConfirmDialog.show("Clear All History?", "This will permanently delete all clipboard history.", function () {
modal.clearAll() modal.clearAll()
modal.hide() modal.hide()
}, function () {}) }, function () {})
@@ -46,7 +46,7 @@ Item {
leftIconName: "search" leftIconName: "search"
showClearButton: true showClearButton: true
focus: true focus: true
ignoreTabKeys: true ignoreLeftRightKeys: true
keyForwardTargets: [modal.modalFocusScope] keyForwardTargets: [modal.modalFocusScope]
onTextChanged: { onTextChanged: {
modal.searchText = text modal.searchText = text
@@ -84,7 +84,7 @@ Item {
id: clipboardListView id: clipboardListView
anchors.fill: parent anchors.fill: parent
model: filteredModel model: filteredModel
currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0 currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0
spacing: Theme.spacingXS spacing: Theme.spacingXS
interactive: true interactive: true
@@ -94,7 +94,7 @@ Item {
boundsMovement: Flickable.FollowBoundsBehavior boundsMovement: Flickable.FollowBoundsBehavior
pressDelay: 0 pressDelay: 0
flickableDirection: Flickable.VerticalFlick flickableDirection: Flickable.VerticalFlick
function ensureVisible(index) { function ensureVisible(index) {
if (index < 0 || index >= count) { if (index < 0 || index >= count) {
return return
@@ -108,25 +108,25 @@ Item {
contentY = itemBottom - height contentY = itemBottom - height
} }
} }
onCurrentIndexChanged: { onCurrentIndexChanged: {
if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) { if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) {
ensureVisible(currentIndex) ensureVisible(currentIndex)
} }
} }
StyledText { StyledText {
text: I18n.tr("No clipboard entries found") text: "No clipboard entries found"
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: filteredModel.count === 0 visible: filteredModel.count === 0
} }
delegate: ClipboardEntry { delegate: ClipboardEntry {
required property int index required property int index
required property var model required property var model
width: clipboardListView.width width: clipboardListView.width
height: ClipboardConstants.itemHeight height: ClipboardConstants.itemHeight
entryData: model.entry entryData: model.entry

View File

@@ -80,11 +80,11 @@ Rectangle {
text: { text: {
switch (entryType) { switch (entryType) {
case "image": case "image":
return I18n.tr("Image") + " • " + entryPreview return "Image • " + entryPreview
case "long_text": case "long_text":
return I18n.tr("Long Text") return "Long Text"
default: default:
return I18n.tr("Text") return "Text"
} }
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall

View File

@@ -28,7 +28,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("Clipboard History") + ` (${totalCount})` text: `Clipboard History (${totalCount})`
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -60,7 +60,6 @@ DankModal {
open() open()
clipboardHistoryModal.searchText = "" clipboardHistoryModal.searchText = ""
clipboardHistoryModal.activeImageLoads = 0 clipboardHistoryModal.activeImageLoads = 0
clipboardHistoryModal.shouldHaveFocus = true
refreshClipboard() refreshClipboard()
keyboardController.reset() keyboardController.reset()
@@ -92,7 +91,7 @@ DankModal {
function copyEntry(entry) { function copyEntry(entry) {
const entryId = entry.split('\t')[0] const entryId = entry.split('\t')[0]
Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`]) Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`])
ToastService.showInfo(I18n.tr("Copied to clipboard")) ToastService.showInfo("Copied to clipboard")
hide() hide()
} }
@@ -154,7 +153,7 @@ DankModal {
ConfirmModal { ConfirmModal {
id: clearConfirmDialog id: clearConfirmDialog
confirmButtonText: I18n.tr("Clear All") confirmButtonText: "Clear All"
confirmButtonColor: Theme.primary confirmButtonColor: Theme.primary
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {

View File

@@ -6,8 +6,6 @@ import qs.Modals.Clipboard
Rectangle { Rectangle {
id: keyboardHints id: keyboardHints
readonly property string hintsText: I18n.tr("Shift+Del: Clear All • Esc: Close")
height: ClipboardConstants.keyboardHintsHeight height: ClipboardConstants.keyboardHintsHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
@@ -28,7 +26,7 @@ Rectangle {
} }
StyledText { StyledText {
text: keyboardHints.hintsText text: "Shift+Del: Clear All • Esc: Close"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -0,0 +1,37 @@
import QtQuick
import Qt.labs.platform
import Quickshell
import qs.Common
import qs.Services
Item {
id: colorPickerModal
signal colorSelected(color selectedColor)
function show() {
colorDialog.open()
}
function hide() {
colorDialog.close()
}
function copyColorToClipboard(colorValue) {
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
ToastService.showInfo(`Color ${colorValue} copied to clipboard`)
console.log("Copied color to clipboard:", colorValue)
}
ColorDialog {
id: colorDialog
title: "Color Picker - Select and copy color"
color: Theme.primary
onAccepted: {
const colorString = color.toString()
copyColorToClipboard(colorString)
colorSelected(color)
}
}
}

View File

@@ -68,11 +68,9 @@ DankModal {
} }
} }
onOpened: { onOpened: {
Qt.callLater(function () { modalFocusScope.forceActiveFocus()
modalFocusScope.forceActiveFocus() modalFocusScope.focus = true
modalFocusScope.focus = true shouldHaveFocus = true
shouldHaveFocus = true
})
} }
modalFocusScope.Keys.onPressed: function (event) { modalFocusScope.Keys.onPressed: function (event) {
switch (event.key) { switch (event.key) {

View File

@@ -1,9 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services
PanelWindow { PanelWindow {
id: root id: root
@@ -12,22 +11,10 @@ PanelWindow {
property alias content: contentLoader.sourceComponent property alias content: contentLoader.sourceComponent
property alias contentLoader: contentLoader property alias contentLoader: contentLoader
property Item directContent: null
property real width: 400 property real width: 400
property real height: 300 property real height: 300
readonly property real screenWidth: screen ? screen.width : 1920 readonly property real screenWidth: screen ? screen.width : 1920
readonly property real screenHeight: screen ? screen.height : 1080 readonly property real screenHeight: screen ? screen.height : 1080
readonly property real dpr: {
if (CompositorService.isNiri && screen) {
const niriScale = NiriService.displayScales[screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return (screen?.devicePixelRatio) || 1
}
property bool showBackground: true property bool showBackground: true
property real backgroundOpacity: 0.5 property real backgroundOpacity: 0.5
property string positioning: "center" property string positioning: "center"
@@ -35,11 +22,8 @@ PanelWindow {
property bool closeOnEscapeKey: true property bool closeOnEscapeKey: true
property bool closeOnBackgroundClick: true property bool closeOnBackgroundClick: true
property string animationType: "scale" property string animationType: "scale"
property int animationDuration: Theme.expressiveDurations.expressiveDefaultSpatial property int animationDuration: Theme.shortDuration
property real animationScaleCollapsed: 0.96 property var animationEasing: Theme.emphasizedEasing
property real animationOffset: Theme.spacingL
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property color backgroundColor: Theme.surfaceContainer property color backgroundColor: Theme.surfaceContainer
property color borderColor: Theme.outlineMedium property color borderColor: Theme.outlineMedium
property real borderWidth: 1 property real borderWidth: 1
@@ -107,7 +91,7 @@ PanelWindow {
Timer { Timer {
id: closeTimer id: closeTimer
interval: animationDuration + 120 interval: animationDuration + 100
onTriggered: { onTriggered: {
visible = false visible = false
} }
@@ -142,8 +126,7 @@ PanelWindow {
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: root.animationDuration duration: root.animationDuration
easing.type: Easing.BezierSpline easing.type: root.animationEasing
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
} }
} }
} }
@@ -151,26 +134,22 @@ PanelWindow {
Rectangle { Rectangle {
id: contentContainer id: contentContainer
width: Theme.px(root.width, dpr) width: root.width
height: Theme.px(root.height, dpr) height: root.height
anchors.centerIn: undefined anchors.centerIn: positioning === "center" ? parent : undefined
x: { x: {
if (positioning === "center") { if (positioning === "top-right") {
return Theme.snap((root.screenWidth - width) / 2, dpr) return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL)
} else if (positioning === "top-right") {
return Theme.px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL), dpr)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return Theme.snap(root.customPosition.x, dpr) return root.customPosition.x
} }
return 0 return 0
} }
y: { y: {
if (positioning === "center") { if (positioning === "top-right") {
return Theme.snap((root.screenHeight - height) / 2, dpr) return Theme.barHeight + Theme.spacingXS
} else if (positioning === "top-right") {
return Theme.px(Theme.barHeight + Theme.spacingXS, dpr)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return Theme.snap(root.customPosition.y, dpr) return root.customPosition.y
} }
return 0 return 0
} }
@@ -178,122 +157,39 @@ PanelWindow {
radius: root.cornerRadius radius: root.cornerRadius
border.color: root.borderColor border.color: root.borderColor
border.width: root.borderWidth border.width: root.borderWidth
clip: false layer.enabled: root.enableShadow
layer.enabled: true
layer.smooth: true
opacity: root.shouldBeVisible ? 1 : 0 opacity: root.shouldBeVisible ? 1 : 0
transform: [scaleTransform, motionTransform] transform: root.animationType === "slide" ? slideTransform : null
Scale {
id: scaleTransform
origin.x: contentContainer.width / 2
origin.y: contentContainer.height / 2
xScale: root.shouldBeVisible ? 1 : root.animationScaleCollapsed
yScale: root.shouldBeVisible ? 1 : root.animationScaleCollapsed
Behavior on xScale {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on yScale {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
Translate { Translate {
id: motionTransform id: slideTransform
readonly property bool slide: root.animationType === "slide" x: root.shouldBeVisible ? 0 : 15
readonly property real hiddenX: slide ? 15 : 0 y: root.shouldBeVisible ? 0 : -30
readonly property real hiddenY: slide ? -30 : root.animationOffset }
x: Theme.snap(root.shouldBeVisible ? 0 : hiddenX, root.dpr) Loader {
y: Theme.snap(root.shouldBeVisible ? 0 : hiddenY, root.dpr) id: contentLoader
Behavior on x { anchors.fill: parent
NumberAnimation { active: root.keepContentLoaded || root.shouldBeVisible || root.visible
duration: root.animationDuration asynchronous: false
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on y {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: animationDuration duration: root.animationDuration
easing.type: Easing.BezierSpline easing.type: root.animationEasing
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
} }
} }
FocusScope { layer.effect: MultiEffect {
anchors.fill: parent shadowEnabled: true
focus: root.shouldBeVisible shadowHorizontalOffset: 0
clip: false shadowVerticalOffset: 8
shadowBlur: 1
Item { shadowColor: Theme.shadowStrong
id: directContentWrapper shadowOpacity: 0.3
anchors.fill: parent
visible: root.directContent !== null
focus: true
clip: false
Component.onCompleted: {
if (root.directContent) {
root.directContent.parent = directContentWrapper
root.directContent.anchors.fill = directContentWrapper
Qt.callLater(() => root.directContent.forceActiveFocus())
}
}
Connections {
function onDirectContentChanged() {
if (root.directContent) {
root.directContent.parent = directContentWrapper
root.directContent.anchors.fill = directContentWrapper
Qt.callLater(() => root.directContent.forceActiveFocus())
}
}
target: root
}
}
Loader {
id: contentLoader
anchors.fill: parent
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || root.visible)
asynchronous: false
focus: true
clip: false
visible: root.directContent === null
onLoaded: {
if (item) {
Qt.callLater(() => item.forceActiveFocus())
}
}
}
} }
} }

View File

@@ -1,542 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property string pickerTitle: "Choose Color"
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
property var onColorSelectedCallback: null
signal colorSelected(color selectedColor)
property color currentColor: Theme.primary
property real hue: 0
property real saturation: 1
property real value: 1
property real alpha: 1
property real gradientX: 0
property real gradientY: 0
readonly property var standardColors: [
"#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4",
"#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722",
"#d32f2f", "#c2185b", "#7b1fa2", "#512da8", "#303f9f", "#1976d2", "#0288d1", "#0097a7",
"#00796b", "#388e3c", "#689f38", "#afb42b", "#fbc02d", "#ffa000", "#f57c00", "#e64a19",
"#c62828", "#ad1457", "#6a1b9a", "#4527a0", "#283593", "#1565c0", "#0277bd", "#00838f",
"#00695c", "#2e7d32", "#558b2f", "#9e9d24", "#f9a825", "#ff8f00", "#ef6c00", "#d84315",
"#ffffff", "#9e9e9e", "#212121"
]
function show() {
currentColor = selectedColor
updateFromColor(currentColor)
open()
}
function hide() {
onColorSelectedCallback = null
close()
}
onColorSelected: (color) => {
if (onColorSelectedCallback) {
onColorSelectedCallback(color)
}
}
function copyColorToClipboard(colorValue) {
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
ToastService.showInfo(`Color ${colorValue} copied`)
SessionData.addRecentColor(currentColor)
}
function updateFromColor(color) {
hue = color.hsvHue
saturation = color.hsvSaturation
value = color.hsvValue
alpha = color.a
gradientX = saturation
gradientY = 1 - value
}
function updateColor() {
currentColor = Qt.hsva(hue, saturation, value, alpha)
}
function updateColorFromGradient(x, y) {
saturation = Math.max(0, Math.min(1, x))
value = Math.max(0, Math.min(1, 1 - y))
updateColor()
selectedColor = currentColor
}
function pickColorFromScreen() {
hide()
Proc.runCommand("hyprpicker", ["hyprpicker", "--format=hex"], (output, errorCode) => {
if (errorCode !== 0) {
console.warn("hyprpicker exited with code:", errorCode)
root.show()
return
}
const colorStr = output.trim()
if (colorStr.length >= 7 && colorStr.startsWith('#')) {
const pickedColor = Qt.color(colorStr)
root.selectedColor = pickedColor
root.currentColor = pickedColor
root.updateFromColor(pickedColor)
copyColorToClipboard(colorStr)
root.show()
}
})
}
width: 680
height: 680
backgroundColor: Theme.surfaceContainer
cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium
borderWidth: 1
keepContentLoaded: true
onBackgroundClicked: hide()
content: Component {
FocusScope {
id: colorContent
property alias hexInput: hexInput
anchors.fill: parent
focus: true
Keys.onEscapePressed: event => {
root.hide()
event.accepted = true
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingS
Column {
width: parent.width - 90
spacing: Theme.spacingXS
StyledText {
text: root.pickerTitle
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: I18n.tr("Select a color from the palette or use custom sliders")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
}
}
DankActionButton {
iconName: "colorize"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
root.pickColorFromScreen()
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
root.hide()
}
}
}
Row {
width: parent.width
spacing: Theme.spacingM
Rectangle {
id: gradientPicker
width: parent.width - 70
height: 280
radius: Theme.cornerRadius
border.color: Theme.outlineStrong
border.width: 1
clip: true
Rectangle {
anchors.fill: parent
color: Qt.hsva(root.hue, 1, 1, 1)
Rectangle {
anchors.fill: parent
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "#ffffff" }
GradientStop { position: 1.0; color: "transparent" }
}
}
Rectangle {
anchors.fill: parent
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: "#000000" }
}
}
}
Rectangle {
id: pickerCircle
width: 16
height: 16
radius: 8
border.color: "white"
border.width: 2
color: "transparent"
x: root.gradientX * parent.width - width / 2
y: root.gradientY * parent.height - height / 2
Rectangle {
anchors.centerIn: parent
width: parent.width - 4
height: parent.height - 4
radius: width / 2
border.color: "black"
border.width: 1
color: "transparent"
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.CrossCursor
onPressed: mouse => {
const x = Math.max(0, Math.min(1, mouse.x / width))
const y = Math.max(0, Math.min(1, mouse.y / height))
root.gradientX = x
root.gradientY = y
root.updateColorFromGradient(x, y)
}
onPositionChanged: mouse => {
if (pressed) {
const x = Math.max(0, Math.min(1, mouse.x / width))
const y = Math.max(0, Math.min(1, mouse.y / height))
root.gradientX = x
root.gradientY = y
root.updateColorFromGradient(x, y)
}
}
}
}
Rectangle {
id: hueSlider
width: 50
height: 280
radius: Theme.cornerRadius
border.color: Theme.outlineStrong
border.width: 1
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.00; color: "#ff0000" }
GradientStop { position: 0.17; color: "#ffff00" }
GradientStop { position: 0.33; color: "#00ff00" }
GradientStop { position: 0.50; color: "#00ffff" }
GradientStop { position: 0.67; color: "#0000ff" }
GradientStop { position: 0.83; color: "#ff00ff" }
GradientStop { position: 1.00; color: "#ff0000" }
}
Rectangle {
id: hueIndicator
width: parent.width
height: 4
color: "white"
border.color: "black"
border.width: 1
y: root.hue * parent.height - height / 2
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeVerCursor
onPressed: mouse => {
const h = Math.max(0, Math.min(1, mouse.y / height))
root.hue = h
root.updateColor()
root.selectedColor = root.currentColor
}
onPositionChanged: mouse => {
if (pressed) {
const h = Math.max(0, Math.min(1, mouse.y / height))
root.hue = h
root.updateColor()
root.selectedColor = root.currentColor
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Material Colors")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
GridView {
width: parent.width
height: 140
cellWidth: 38
cellHeight: 38
clip: true
interactive: false
model: root.standardColors
delegate: Rectangle {
width: 36
height: 36
color: modelData
radius: 4
border.color: Theme.outlineStrong
border.width: 1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: () => {
const pickedColor = Qt.color(modelData)
root.selectedColor = pickedColor
root.currentColor = pickedColor
root.updateFromColor(pickedColor)
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
Column {
width: 210
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Recent Colors")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Row {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: 5
Rectangle {
width: 36
height: 36
radius: 4
border.color: Theme.outlineStrong
border.width: 1
color: {
if (index < SessionData.recentColors.length) {
return SessionData.recentColors[index]
}
return Theme.surfaceContainerHigh
}
opacity: index < SessionData.recentColors.length ? 1.0 : 0.3
MouseArea {
anchors.fill: parent
cursorShape: index < SessionData.recentColors.length ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: index < SessionData.recentColors.length
onClicked: () => {
if (index < SessionData.recentColors.length) {
const pickedColor = SessionData.recentColors[index]
root.selectedColor = pickedColor
root.currentColor = pickedColor
root.updateFromColor(pickedColor)
}
}
}
}
}
}
}
Column {
width: parent.width - 330
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Opacity")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
DankSlider {
width: parent.width
value: Math.round(root.alpha * 100)
minimum: 0
maximum: 100
showValue: false
onSliderValueChanged: (newValue) => {
root.alpha = newValue / 100
root.updateColor()
root.selectedColor = root.currentColor
}
}
}
Rectangle {
width: 100
height: 50
radius: Theme.cornerRadius
color: root.currentColor
border.color: Theme.outlineStrong
border.width: 2
anchors.verticalCenter: parent.verticalCenter
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Hex:")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
id: hexInput
width: 120
height: 38
text: root.currentColor.toString()
font.pixelSize: Theme.fontSizeMedium
textColor: {
if (text.length === 0) return Theme.surfaceText
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
return hexPattern.test(text) ? Theme.surfaceText : Theme.error
}
placeholderText: "#000000"
backgroundColor: Theme.surfaceHover
borderWidth: 1
focusedBorderWidth: 2
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
onAccepted: () => {
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
if (!hexPattern.test(text)) return
const color = Qt.color(text)
if (color) {
root.selectedColor = color
root.currentColor = color
root.updateFromColor(color)
}
}
}
DankButton {
width: 80
buttonHeight: 36
text: I18n.tr("Apply")
backgroundColor: Theme.primary
textColor: Theme.background
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
if (!hexPattern.test(hexInput.text)) return
const color = Qt.color(hexInput.text)
if (color) {
root.currentColor = color
root.updateFromColor(color)
root.selectedColor = root.currentColor
root.colorSelected(root.currentColor)
SessionData.addRecentColor(root.currentColor)
root.hide()
}
}
}
Item {
width: parent.width - 460
height: 1
}
DankButton {
width: 70
buttonHeight: 36
text: I18n.tr("Cancel")
backgroundColor: "transparent"
textColor: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
onClicked: root.hide()
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
z: -1
}
}
DankButton {
width: 70
buttonHeight: 36
text: I18n.tr("Copy")
backgroundColor: Theme.primary
textColor: Theme.background
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const colorString = root.currentColor.toString()
root.copyColorToClipboard(colorString)
}
}
}
}
}
}
}

View File

@@ -35,7 +35,6 @@ DankModal {
property bool weAvailable: false property bool weAvailable: false
property string wePath: "" property string wePath: ""
property bool weMode: false property bool weMode: false
property var parentModal: null
signal fileSelected(string path) signal fileSelected(string path)
@@ -48,17 +47,15 @@ DankModal {
} }
function getLastPath() { function getLastPath() {
const lastPath = browserType === "wallpaper" ? CacheData.wallpaperLastPath : browserType === "profile" ? CacheData.profileLastPath : "" const lastPath = browserType === "wallpaper" ? SessionData.wallpaperLastPath : browserType === "profile" ? SessionData.profileLastPath : ""
return (lastPath && lastPath !== "") ? lastPath : homeDir return (lastPath && lastPath !== "") ? lastPath : homeDir
} }
function saveLastPath(path) { function saveLastPath(path) {
if (browserType === "wallpaper") { if (browserType === "wallpaper") {
CacheData.wallpaperLastPath = path SessionData.setWallpaperLastPath(path)
CacheData.saveCache()
} else if (browserType === "profile") { } else if (browserType === "profile") {
CacheData.profileLastPath = path SessionData.setProfileLastPath(path)
CacheData.saveCache()
} }
} }
@@ -111,18 +108,18 @@ DankModal {
if (!normalizedPath.startsWith("file://")) { if (!normalizedPath.startsWith("file://")) {
normalizedPath = "file://" + filePath normalizedPath = "file://" + filePath
} }
// Check if file exists by looking through the folder model // Check if file exists by looking through the folder model
var exists = false var exists = false
var fileName = filePath.split('/').pop() var fileName = filePath.split('/').pop()
for (var i = 0; i < folderModel.count; i++) { for (var i = 0; i < folderModel.count; i++) {
if (folderModel.get(i, "fileName") === fileName && !folderModel.get(i, "fileIsDir")) { if (folderModel.get(i, "fileName") === fileName && !folderModel.get(i, "fileIsDir")) {
exists = true exists = true
break break
} }
} }
if (exists) { if (exists) {
pendingFilePath = normalizedPath pendingFilePath = normalizedPath
showOverwriteConfirmation = true showOverwriteConfirmation = true
@@ -134,12 +131,10 @@ DankModal {
objectName: "fileBrowserModal" objectName: "fileBrowserModal"
allowStacking: true allowStacking: true
closeOnEscapeKey: false
shouldHaveFocus: shouldBeVisible
Component.onCompleted: { Component.onCompleted: {
currentPath = getLastPath() currentPath = getLastPath()
} }
property var steamPaths: [ property var steamPaths: [
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.steam/steam/steamapps/workshop/content/431960", StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.steam/steam/steamapps/workshop/content/431960",
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.local/share/Steam/steamapps/workshop/content/431960", StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.local/share/Steam/steamapps/workshop/content/431960",
@@ -147,17 +142,17 @@ DankModal {
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/snap/steam/common/.local/share/Steam/steamapps/workshop/content/431960" StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/snap/steam/common/.local/share/Steam/steamapps/workshop/content/431960"
] ]
property int currentPathIndex: 0 property int currentPathIndex: 0
function discoverWallpaperEngine() { function discoverWallpaperEngine() {
currentPathIndex = 0 currentPathIndex = 0
checkNextPath() checkNextPath()
} }
function checkNextPath() { function checkNextPath() {
if (currentPathIndex >= steamPaths.length) { if (currentPathIndex >= steamPaths.length) {
return return
} }
const wePath = steamPaths[currentPathIndex] const wePath = steamPaths[currentPathIndex]
const cleanPath = wePath.replace(/^file:\/\//, '') const cleanPath = wePath.replace(/^file:\/\//, '')
weDiscoveryProcess.command = ["test", "-d", cleanPath] weDiscoveryProcess.command = ["test", "-d", cleanPath]
@@ -170,23 +165,10 @@ DankModal {
visible: false visible: false
onBackgroundClicked: close() onBackgroundClicked: close()
onOpened: { onOpened: {
if (parentModal) { modalFocusScope.forceActiveFocus()
parentModal.shouldHaveFocus = false
parentModal.allowFocusOverride = true
}
Qt.callLater(() => {
if (contentLoader && contentLoader.item) {
contentLoader.item.forceActiveFocus()
}
})
} }
onDialogClosed: { modalFocusScope.Keys.onPressed: function (event) {
if (parentModal) { keyboardController.handleKey(event)
parentModal.allowFocusOverride = false
parentModal.shouldHaveFocus = Qt.binding(() => {
return parentModal.shouldBeVisible
})
}
} }
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
@@ -451,13 +433,13 @@ DankModal {
executeKeyboardSelection(targetIndex) executeKeyboardSelection(targetIndex)
} }
} }
Process { Process {
id: weDiscoveryProcess id: weDiscoveryProcess
property string wePath: "" property string wePath: ""
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {
fileBrowserModal.weAvailable = true fileBrowserModal.weAvailable = true
@@ -473,16 +455,6 @@ DankModal {
Item { Item {
anchors.fill: parent anchors.fill: parent
Keys.onPressed: event => {
keyboardController.handleKey(event)
}
onVisibleChanged: {
if (visible) {
forceActiveFocus()
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
@@ -532,7 +504,7 @@ DankModal {
} }
} }
} }
DankActionButton { DankActionButton {
circular: false circular: false
iconName: "info" iconName: "info"
@@ -623,6 +595,7 @@ DankModal {
required property bool fileIsDir required property bool fileIsDir
required property string filePath required property string filePath
required property string fileName required property string fileName
required property url fileURL
required property int index required property int index
width: weMode ? 245 : 140 width: weMode ? 245 : 140
@@ -782,7 +755,7 @@ DankModal {
width: parent.width - saveButton.width - Theme.spacingM width: parent.width - saveButton.width - Theme.spacingM
height: 40 height: 40
text: defaultFileName text: defaultFileName
placeholderText: I18n.tr("Enter filename...") placeholderText: "Enter filename..."
ignoreLeftRightKeys: false ignoreLeftRightKeys: false
focus: saveMode focus: saveMode
topPadding: Theme.spacingS topPadding: Theme.spacingS
@@ -815,7 +788,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Save") text: "Save"
color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
} }
@@ -875,26 +848,26 @@ DankModal {
id: overwriteDialog id: overwriteDialog
anchors.fill: parent anchors.fill: parent
visible: showOverwriteConfirmation visible: showOverwriteConfirmation
Keys.onEscapePressed: { Keys.onEscapePressed: {
showOverwriteConfirmation = false showOverwriteConfirmation = false
pendingFilePath = "" pendingFilePath = ""
} }
Keys.onReturnPressed: { Keys.onReturnPressed: {
showOverwriteConfirmation = false showOverwriteConfirmation = false
fileSelected(pendingFilePath) fileSelected(pendingFilePath)
pendingFilePath = "" pendingFilePath = ""
Qt.callLater(() => fileBrowserModal.close()) Qt.callLater(() => fileBrowserModal.close())
} }
focus: showOverwriteConfirmation focus: showOverwriteConfirmation
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Theme.shadowStrong color: Theme.shadowStrong
opacity: 0.8 opacity: 0.8
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
@@ -903,7 +876,7 @@ DankModal {
} }
} }
} }
StyledRect { StyledRect {
anchors.centerIn: parent anchors.centerIn: parent
width: 400 width: 400
@@ -912,33 +885,33 @@ DankModal {
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Theme.outlineMedium border.color: Theme.outlineMedium
border.width: 1 border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2 width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingM spacing: Theme.spacingM
StyledText { StyledText {
text: I18n.tr("File Already Exists") text: qsTr("File Already Exists")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: I18n.tr("A file with this name already exists. Do you want to overwrite it?") text: qsTr("A file with this name already exists. Do you want to overwrite it?")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
Row { Row {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
StyledRect { StyledRect {
width: 80 width: 80
height: 36 height: 36
@@ -946,15 +919,15 @@ DankModal {
color: cancelArea.containsMouse ? Theme.surfaceVariantHover : Theme.surfaceVariant color: cancelArea.containsMouse ? Theme.surfaceVariantHover : Theme.surfaceVariant
border.color: Theme.outline border.color: Theme.outline
border.width: 1 border.width: 1
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Cancel") text: qsTr("Cancel")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: cancelArea id: cancelArea
anchors.fill: parent anchors.fill: parent
@@ -966,21 +939,21 @@ DankModal {
} }
} }
} }
StyledRect { StyledRect {
width: 90 width: 90
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: overwriteArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary color: overwriteArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Overwrite") text: qsTr("Overwrite")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: overwriteArea id: overwriteArea
anchors.fill: parent anchors.fill: parent

View File

@@ -134,7 +134,7 @@ Rectangle {
} }
StyledText { StyledText {
text: I18n.tr("File Information") text: "File Information"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -197,7 +197,7 @@ Rectangle {
} }
StyledText { StyledText {
text: I18n.tr("F1/I: Toggle • F10: Help") text: "F1/I: Toggle • F10: Help"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
anchors.bottom: parent.bottom anchors.bottom: parent.bottom

View File

@@ -23,7 +23,7 @@ Rectangle {
spacing: 2 spacing: 2
StyledText { StyledText {
text: I18n.tr("Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select") text: "Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width
@@ -32,7 +32,7 @@ Rectangle {
} }
StyledText { StyledText {
text: I18n.tr("Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close") text: "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width

View File

@@ -1,266 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
width: 1400
height: 900
onBackgroundClicked: close()
function categorizeKeybinds() {
const categories = {
"Workspace": [],
"Window": [],
"Monitor": [],
"Execute": [],
"System": [],
"Other": []
}
function addKeybind(keybind) {
const dispatcher = keybind.dispatcher || ""
if (dispatcher.includes("workspace")) {
categories["Workspace"].push(keybind)
} else if (dispatcher.includes("monitor")) {
categories["Monitor"].push(keybind)
} else if (dispatcher.includes("window") || dispatcher.includes("focus") || dispatcher.includes("move") || dispatcher.includes("swap") || dispatcher.includes("resize") || dispatcher === "killactive" || dispatcher === "fullscreen" || dispatcher === "togglefloating") {
categories["Window"].push(keybind)
} else if (dispatcher === "exec") {
categories["Execute"].push(keybind)
} else if (dispatcher === "exit" || dispatcher.includes("dpms")) {
categories["System"].push(keybind)
} else {
categories["Other"].push(keybind)
}
}
const allKeybinds = HyprKeybindsService.keybinds.keybinds || []
for (let i = 0; i < allKeybinds.length; i++) {
addKeybind(allKeybinds[i])
}
const children = HyprKeybindsService.keybinds.children || []
for (let i = 0; i < children.length; i++) {
const child = children[i]
const childKeybinds = child.keybinds || []
for (let j = 0; j < childKeybinds.length; j++) {
addKeybind(childKeybinds[j])
}
}
categories["Workspace"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Window"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Monitor"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Execute"].sort((a, b) => {
const modsA = a.mods || []
const keyA = a.key || ""
const bindA = [...modsA, keyA].join("+")
const modsB = b.mods || []
const keyB = b.key || ""
const bindB = [...modsB, keyB].join("+")
return bindA.localeCompare(bindB)
})
return categories
}
content: Component {
Item {
anchors.fill: parent
DankFlickable {
id: mainFlickable
anchors.fill: parent
anchors.margins: Theme.spacingL
contentWidth: rowLayout.implicitWidth
contentHeight: rowLayout.implicitHeight
clip: true
Row {
id: rowLayout
spacing: Theme.spacingM
property var categories: root.categorizeKeybinds()
property real columnWidth: (mainFlickable.width - spacing * 2) / 3
Column {
width: rowLayout.columnWidth
spacing: Theme.spacingXS
StyledText {
text: "Window / Monitor"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.primary
}
Rectangle {
width: parent.width
height: 1
color: Theme.primary
opacity: 0.3
}
Item { width: 1; height: Theme.spacingXS }
Column {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: [...(rowLayout.categories["Window"] || []), ...(rowLayout.categories["Monitor"] || [])]
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: Math.min(140, parent.width * 0.42)
height: 22
radius: 4
opacity: 0.3
StyledText {
anchors.centerIn: parent
anchors.margins: 2
width: parent.width - 4
text: {
const mods = modelData.mods || []
const key = modelData.key || ""
const parts = [...mods, key]
return parts.join("+")
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
isMonospace: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
width: parent.width - 150
text: {
const comment = modelData.comment || ""
if (comment) return comment
const dispatcher = modelData.dispatcher || ""
const params = modelData.params || ""
return params ? `${dispatcher} ${params}` : dispatcher
}
font.pixelSize: Theme.fontSizeSmall
opacity: 0.9
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
Repeater {
model: ["Workspace", "Execute"]
Column {
width: rowLayout.columnWidth
spacing: Theme.spacingXS
StyledText {
text: modelData
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.primary
}
Rectangle {
width: parent.width
height: 1
color: Theme.primary
opacity: 0.3
}
Item { width: 1; height: Theme.spacingXS }
Column {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: rowLayout.categories[modelData] || []
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: Math.min(140, parent.width * 0.42)
height: 22
radius: 4
opacity: 0.3
StyledText {
anchors.centerIn: parent
anchors.margins: 2
width: parent.width - 4
text: {
const mods = modelData.mods || []
const key = modelData.key || ""
const parts = [...mods, key]
return parts.join("+")
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
isMonospace: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
width: parent.width - 150
text: {
const comment = modelData.comment || ""
if (comment) return comment
const dispatcher = modelData.dispatcher || ""
const params = modelData.params || ""
return params ? `${dispatcher} ${params}` : dispatcher
}
font.pixelSize: Theme.fontSizeSmall
opacity: 0.9
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}
}
}
}
}

View File

@@ -56,7 +56,7 @@ DankModal {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Network Information") text: "Network Information"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -126,7 +126,7 @@ DankModal {
id: closeText id: closeText
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Close") text: "Close"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -1,162 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property bool networkWiredInfoModalVisible: false
property string networkID: ""
property var networkData: null
function showNetworkInfo(id, data) {
networkID = id
networkData = data
networkWiredInfoModalVisible = true
open()
NetworkService.fetchWiredNetworkInfo(data.uuid)
}
function hideDialog() {
networkWiredInfoModalVisible = false
close()
networkID = ""
networkData = null
}
visible: networkWiredInfoModalVisible
width: 600
height: 500
enableShadow: true
onBackgroundClicked: hideDialog()
onVisibleChanged: {
if (!visible) {
networkID = ""
networkData = null
}
}
content: Component {
Item {
anchors.fill: parent
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
Row {
width: parent.width
Column {
width: parent.width - 40
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Network Information")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: `Details for "${networkID}"`
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
elide: Text.ElideRight
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: root.hideDialog()
}
}
Rectangle {
id: detailsRect
width: parent.width
height: parent.height - 140
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: Theme.outlineStrong
border.width: 1
clip: true
DankFlickable {
anchors.fill: parent
anchors.margins: Theme.spacingM
contentHeight: detailsText.contentHeight
StyledText {
id: detailsText
width: parent.width
text: NetworkService.networkWiredInfoDetails && NetworkService.networkWiredInfoDetails.replace(/\\n/g, '\n') || "No information available"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
wrapMode: Text.WordWrap
}
}
}
Item {
width: parent.width
height: 40
Rectangle {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
StyledText {
id: closeText
anchors.centerIn: parent
text: I18n.tr("Close")
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.hideDialog()
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
}

View File

@@ -20,7 +20,7 @@ DankModal {
if (modalKeyboardController && notificationListRef) { if (modalKeyboardController && notificationListRef) {
modalKeyboardController.listView = notificationListRef modalKeyboardController.listView = notificationListRef
modalKeyboardController.rebuildFlatNavigation() modalKeyboardController.rebuildFlatNavigation()
Qt.callLater(() => { Qt.callLater(() => {
modalKeyboardController.keyboardNavigationActive = true modalKeyboardController.keyboardNavigationActive = true
modalKeyboardController.selectedFlatIndex = 0 modalKeyboardController.selectedFlatIndex = 0
@@ -54,9 +54,6 @@ DankModal {
height: 700 height: 700
visible: false visible: false
onBackgroundClicked: hide() onBackgroundClicked: hide()
onOpened: () => {
Qt.callLater(() => modalFocusScope.forceActiveFocus());
}
onShouldBeVisibleChanged: (shouldBeVisible) => { onShouldBeVisibleChanged: (shouldBeVisible) => {
if (!shouldBeVisible) { if (!shouldBeVisible) {
notificationModalOpen = false notificationModalOpen = false

View File

@@ -9,47 +9,31 @@ DankModal {
property int selectedIndex: 0 property int selectedIndex: 0
property int optionCount: SessionService.hibernateSupported ? 5 : 4 property int optionCount: SessionService.hibernateSupported ? 5 : 4
property rect parentBounds: Qt.rect(0, 0, 0, 0)
property var parentScreen: null
signal powerActionRequested(string action, string title, string message) signal powerActionRequested(string action, string title, string message)
function openCentered() {
parentBounds = Qt.rect(0, 0, 0, 0)
parentScreen = null
backgroundOpacity = 0.5
open()
}
function openFromControlCenter(bounds, targetScreen) {
parentBounds = bounds
parentScreen = targetScreen
backgroundOpacity = 0
open()
}
function selectOption(action) { function selectOption(action) {
close(); close();
const actions = { const actions = {
"logout": { "logout": {
"title": I18n.tr("Log Out"), "title": "Log Out",
"message": I18n.tr("Are you sure you want to log out?") "message": "Are you sure you want to log out?"
}, },
"suspend": { "suspend": {
"title": I18n.tr("Suspend"), "title": "Suspend",
"message": I18n.tr("Are you sure you want to suspend the system?") "message": "Are you sure you want to suspend the system?"
}, },
"hibernate": { "hibernate": {
"title": I18n.tr("Hibernate"), "title": "Hibernate",
"message": I18n.tr("Are you sure you want to hibernate the system?") "message": "Are you sure you want to hibernate the system?"
}, },
"reboot": { "reboot": {
"title": I18n.tr("Reboot"), "title": "Reboot",
"message": I18n.tr("Are you sure you want to reboot the system?") "message": "Are you sure you want to reboot the system?"
}, },
"poweroff": { "poweroff": {
"title": I18n.tr("Power Off"), "title": "Power Off",
"message": I18n.tr("Are you sure you want to power off the system?") "message": "Are you sure you want to power off the system?"
} }
} }
const selected = actions[action] const selected = actions[action]
@@ -63,22 +47,12 @@ DankModal {
width: 320 width: 320
height: contentLoader.item ? contentLoader.item.implicitHeight : 300 height: contentLoader.item ? contentLoader.item.implicitHeight : 300
enableShadow: true enableShadow: true
screen: parentScreen
positioning: parentBounds.width > 0 ? "custom" : "center"
customPosition: {
if (parentBounds.width > 0) {
const centerX = parentBounds.x + (parentBounds.width - width) / 2
const centerY = parentBounds.y + (parentBounds.height - height) / 2
return Qt.point(centerX, centerY)
}
return Qt.point(0, 0)
}
onBackgroundClicked: () => { onBackgroundClicked: () => {
return close(); return close();
} }
onOpened: () => { onOpened: () => {
selectedIndex = 0; selectedIndex = 0;
Qt.callLater(() => modalFocusScope.forceActiveFocus()); modalFocusScope.forceActiveFocus();
} }
modalFocusScope.Keys.onPressed: (event) => { modalFocusScope.Keys.onPressed: (event) => {
switch (event.key) { switch (event.key) {
@@ -144,7 +118,7 @@ DankModal {
width: parent.width width: parent.width
StyledText { StyledText {
text: I18n.tr("Power Options") text: "Power Options"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -201,7 +175,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Log Out") text: "Log Out"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -254,7 +228,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Suspend") text: "Suspend"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -308,7 +282,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Hibernate") text: "Hibernate"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -362,7 +336,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Reboot") text: "Reboot"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -416,7 +390,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Power Off") text: "Power Off"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -123,7 +123,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("System Monitor Unavailable") text: "System Monitor Unavailable"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.error color: Theme.error
@@ -131,7 +131,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature.") text: "The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature."
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -154,7 +154,7 @@ DankModal {
height: 40 height: 40
StyledText { StyledText {
text: I18n.tr("System Monitor") text: "System Monitor"
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.surfaceText color: Theme.surfaceText

View File

@@ -19,96 +19,12 @@ Item {
spacing: Theme.spacingXL spacing: Theme.spacingXL
StyledText { StyledText {
text: I18n.tr("Battery not detected - only AC power settings available") text: "Battery not detected - only AC power settings available"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: !BatteryService.batteryAvailable visible: !BatteryService.batteryAvailable
} }
StyledRect {
width: parent.width
height: lockScreenSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
Column {
id: lockScreenSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "lock"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Lock Screen")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
width: parent.width
text: I18n.tr("Show Power Actions")
description: I18n.tr("Show power, restart, and logout buttons on the lock screen")
checked: SettingsData.lockScreenShowPowerActions
onToggled: checked => SettingsData.setLockScreenShowPowerActions(checked)
}
StyledText {
text: I18n.tr("loginctl not available - lock integration requires DMS socket connection")
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
visible: !SessionService.loginctlAvailable
width: parent.width
wrapMode: Text.Wrap
}
DankToggle {
width: parent.width
text: I18n.tr("Enable loginctl lock integration")
description: I18n.tr("Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen")
checked: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
enabled: SessionService.loginctlAvailable
onToggled: checked => {
if (SessionService.loginctlAvailable) {
SettingsData.setLoginctlLockIntegration(checked)
}
}
}
DankToggle {
width: parent.width
text: I18n.tr("Lock before suspend")
description: I18n.tr("Automatically lock the screen when the system prepares to suspend")
checked: SettingsData.lockBeforeSuspend
visible: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
onToggled: checked => SettingsData.setLockBeforeSuspend(checked)
}
DankToggle {
width: parent.width
text: I18n.tr("Enable fingerprint authentication")
description: I18n.tr("Use fingerprint reader for lock screen authentication (requires enrolled fingerprints)")
checked: SettingsData.enableFprint
visible: SettingsData.fprintdAvailable
onToggled: checked => SettingsData.setEnableFprint(checked)
}
}
}
StyledRect { StyledRect {
width: parent.width width: parent.width
height: timeoutSection.implicitHeight + Theme.spacingL * 2 height: timeoutSection.implicitHeight + Theme.spacingL * 2
@@ -135,7 +51,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("Idle Settings") text: "Idle Settings"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -163,20 +79,21 @@ Item {
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"] 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"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Automatically lock after") width: parent.width
text: "Automatically lock after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acLockTimeout : SessionData.batteryLockTimeout
const index = lockDropdown.timeoutValues.indexOf(currentTimeout) const index = lockDropdown.timeoutValues.indexOf(currentTimeout)
lockDropdown.currentValue = index >= 0 ? lockDropdown.timeoutOptions[index] : "Never" lockDropdown.currentValue = index >= 0 ? lockDropdown.timeoutOptions[index] : "Never"
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acLockTimeout : SessionData.batteryLockTimeout
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never"
} }
@@ -186,9 +103,9 @@ Item {
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SettingsData.setAcLockTimeout(timeout) SessionData.setAcLockTimeout(timeout)
} else { } else {
SettingsData.setBatteryLockTimeout(timeout) SessionData.setBatteryLockTimeout(timeout)
} }
} }
} }
@@ -199,20 +116,21 @@ Item {
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"] 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"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Turn off monitors after") width: parent.width
text: "Turn off monitors after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acMonitorTimeout : SessionData.batteryMonitorTimeout
const index = monitorDropdown.timeoutValues.indexOf(currentTimeout) const index = monitorDropdown.timeoutValues.indexOf(currentTimeout)
monitorDropdown.currentValue = index >= 0 ? monitorDropdown.timeoutOptions[index] : "Never" monitorDropdown.currentValue = index >= 0 ? monitorDropdown.timeoutOptions[index] : "Never"
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acMonitorTimeout : SessionData.batteryMonitorTimeout
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never"
} }
@@ -222,9 +140,9 @@ Item {
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SettingsData.setAcMonitorTimeout(timeout) SessionData.setAcMonitorTimeout(timeout)
} else { } else {
SettingsData.setBatteryMonitorTimeout(timeout) SessionData.setBatteryMonitorTimeout(timeout)
} }
} }
} }
@@ -235,20 +153,21 @@ Item {
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"] 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"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Suspend system after") width: parent.width
text: "Suspend system after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acSuspendTimeout : SessionData.batterySuspendTimeout
const index = suspendDropdown.timeoutValues.indexOf(currentTimeout) const index = suspendDropdown.timeoutValues.indexOf(currentTimeout)
suspendDropdown.currentValue = index >= 0 ? suspendDropdown.timeoutOptions[index] : "Never" suspendDropdown.currentValue = index >= 0 ? suspendDropdown.timeoutOptions[index] : "Never"
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acSuspendTimeout : SessionData.batterySuspendTimeout
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never"
} }
@@ -258,9 +177,9 @@ Item {
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SettingsData.setAcSuspendTimeout(timeout) SessionData.setAcSuspendTimeout(timeout)
} else { } else {
SettingsData.setBatterySuspendTimeout(timeout) SessionData.setBatterySuspendTimeout(timeout)
} }
} }
} }
@@ -271,21 +190,22 @@ Item {
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"] 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"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Hibernate system after") width: parent.width
text: "Hibernate system after"
options: timeoutOptions options: timeoutOptions
visible: SessionService.hibernateSupported visible: SessionService.hibernateSupported
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acHibernateTimeout : SettingsData.batteryHibernateTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acHibernateTimeout : SessionData.batteryHibernateTimeout
const index = hibernateDropdown.timeoutValues.indexOf(currentTimeout) const index = hibernateDropdown.timeoutValues.indexOf(currentTimeout)
hibernateDropdown.currentValue = index >= 0 ? hibernateDropdown.timeoutOptions[index] : "Never" hibernateDropdown.currentValue = index >= 0 ? hibernateDropdown.timeoutOptions[index] : "Never"
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acHibernateTimeout : SettingsData.batteryHibernateTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acHibernateTimeout : SessionData.batteryHibernateTimeout
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never"
} }
@@ -295,16 +215,24 @@ Item {
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SettingsData.setAcHibernateTimeout(timeout) SessionData.setAcHibernateTimeout(timeout)
} else { } else {
SettingsData.setBatteryHibernateTimeout(timeout) SessionData.setBatteryHibernateTimeout(timeout)
} }
} }
} }
} }
DankToggle {
width: parent.width
text: "Lock before suspend"
description: "Automatically lock the screen when the system prepares to suspend"
checked: SessionData.lockBeforeSuspend
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
}
StyledText { StyledText {
text: I18n.tr("Idle monitoring not supported - requires newer Quickshell version") text: "Idle monitoring not supported - requires newer Quickshell version"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.error color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -313,277 +241,6 @@ Item {
} }
} }
StyledRect {
width: parent.width
height: powerCommandConfirmSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
Column {
id: powerCommandConfirmSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "check_circle"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Power Action Confirmation")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
width: parent.width
text: I18n.tr("Show Confirmation on Power Actions")
description: I18n.tr("Request confirmation on power off, restart, suspend, hibernate and logout actions")
checked: SettingsData.powerActionConfirm
onToggled: checked => SettingsData.setPowerActionConfirm(checked)
}
}
}
StyledRect {
width: parent.width
height: powerCommandCustomization.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
Column {
id: powerCommandCustomization
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "developer_mode"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Custom Power Actions")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard lock procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customLockCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myLock.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionLock) {
text = SettingsData.customPowerActionLock;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionLock(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard logout procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customLogoutCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myLogout.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionLogout) {
text = SettingsData.customPowerActionLogout;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionLogout(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard suspend procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customSuspendCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/mySuspend.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionSuspend) {
text = SettingsData.customPowerActionSuspend;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionSuspend(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard hibernate procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customHibernateCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myHibernate.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionHibernate) {
text = SettingsData.customPowerActionHibernate;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionHibernate(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard reboot procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customRebootCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myReboot.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionReboot) {
text = SettingsData.customPowerActionReboot;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionReboot(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard power off procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customPowerOffCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myPowerOff.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionPowerOff) {
text = SettingsData.customPowerActionPowerOff;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionPowerOff(text.trim());
}
}
}
}
}
} }
} }
} }

View File

@@ -22,6 +22,7 @@ Item {
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 0 active: root.currentIndex === 0
visible: active visible: active
asynchronous: true
sourceComponent: Component { sourceComponent: Component {
PersonalizationTab { PersonalizationTab {
@@ -33,13 +34,27 @@ Item {
} }
Loader { Loader {
id: timeWeatherLoader id: timeLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 1 active: root.currentIndex === 1
visible: active visible: active
asynchronous: true
sourceComponent: TimeWeatherTab { sourceComponent: TimeTab {
}
}
Loader {
id: weatherLoader
anchors.fill: parent
active: root.currentIndex === 2
visible: active
asynchronous: true
sourceComponent: WeatherTab {
} }
} }
@@ -48,11 +63,11 @@ Item {
id: topBarLoader id: topBarLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 2 active: root.currentIndex === 3
visible: active visible: active
asynchronous: true
sourceComponent: DankBarTab { sourceComponent: DankBarTab {
parentModal: root.parentModal
} }
} }
@@ -61,8 +76,9 @@ Item {
id: widgetsLoader id: widgetsLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 3 active: root.currentIndex === 4
visible: active visible: active
asynchronous: true
sourceComponent: WidgetTweaksTab { sourceComponent: WidgetTweaksTab {
} }
@@ -73,8 +89,9 @@ Item {
id: dockLoader id: dockLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 4 active: root.currentIndex === 5
visible: active visible: active
asynchronous: true
sourceComponent: Component { sourceComponent: Component {
DockTab { DockTab {
@@ -88,8 +105,9 @@ Item {
id: displaysLoader id: displaysLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 5 active: root.currentIndex === 6
visible: active visible: active
asynchronous: true
sourceComponent: DisplaysTab { sourceComponent: DisplaysTab {
} }
@@ -100,8 +118,9 @@ Item {
id: launcherLoader id: launcherLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 6 active: root.currentIndex === 7
visible: active visible: active
asynchronous: true
sourceComponent: LauncherTab { sourceComponent: LauncherTab {
} }
@@ -112,8 +131,9 @@ Item {
id: themeColorsLoader id: themeColorsLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 7 active: root.currentIndex === 8
visible: active visible: active
asynchronous: true
sourceComponent: ThemeColorsTab { sourceComponent: ThemeColorsTab {
} }
@@ -123,24 +143,12 @@ Item {
Loader { Loader {
id: powerLoader id: powerLoader
anchors.fill: parent
active: root.currentIndex === 8
visible: active
sourceComponent: PowerSettings {
}
}
Loader {
id: pluginsLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 9 active: root.currentIndex === 9
visible: active visible: active
asynchronous: true
sourceComponent: PluginsTab { sourceComponent: PowerSettings {
parentModal: root.parentModal
} }
} }
@@ -151,6 +159,7 @@ Item {
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 10 active: root.currentIndex === 10
visible: active visible: active
asynchronous: true
sourceComponent: AboutTab { sourceComponent: AboutTab {
} }

View File

@@ -13,7 +13,6 @@ DankModal {
property Component settingsContent property Component settingsContent
property alias profileBrowser: profileBrowser property alias profileBrowser: profileBrowser
property int currentTabIndex: 0
signal closingModal() signal closingModal()
@@ -35,31 +34,12 @@ DankModal {
objectName: "settingsModal" objectName: "settingsModal"
width: 800 width: 800
height: 800 height: 750
visible: false visible: false
onBackgroundClicked: () => { onBackgroundClicked: () => {
return hide(); return hide();
} }
content: settingsContent content: settingsContent
onOpened: () => {
Qt.callLater(() => modalFocusScope.forceActiveFocus())
}
modalFocusScope.Keys.onPressed: event => {
const tabCount = 11
if (event.key === Qt.Key_Down) {
currentTabIndex = (currentTabIndex + 1) % tabCount
event.accepted = true
} else if (event.key === Qt.Key_Up) {
currentTabIndex = (currentTabIndex - 1 + tabCount) % tabCount
event.accepted = true
} else if (event.key === Qt.Key_Tab && !event.modifiers) {
currentTabIndex = (currentTabIndex + 1) % tabCount
event.accepted = true
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && event.modifiers & Qt.ShiftModifier)) {
currentTabIndex = (currentTabIndex - 1 + tabCount) % tabCount
event.accepted = true
}
}
IpcHandler { IpcHandler {
function open(): string { function open(): string {
@@ -98,17 +78,21 @@ DankModal {
id: profileBrowser id: profileBrowser
allowStacking: true allowStacking: true
parentModal: settingsModal
browserTitle: "Select Profile Image" browserTitle: "Select Profile Image"
browserIcon: "person" browserIcon: "person"
browserType: "profile" browserType: "profile"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: (path) => { onFileSelected: (path) => {
PortalService.setProfileImage(path); PortalService.setProfileImage(path);
close(); close();
} }
onDialogClosed: () => { onDialogClosed: () => {
if (settingsModal) {
settingsModal.allowFocusOverride = false;
settingsModal.shouldHaveFocus = Qt.binding(() => {
return settingsModal.shouldBeVisible;
});
}
allowStacking = true; allowStacking = true;
} }
} }
@@ -117,11 +101,9 @@ DankModal {
id: wallpaperBrowser id: wallpaperBrowser
allowStacking: true allowStacking: true
parentModal: settingsModal
browserTitle: "Select Wallpaper" browserTitle: "Select Wallpaper"
browserIcon: "wallpaper" browserIcon: "wallpaper"
browserType: "wallpaper" browserType: "wallpaper"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: (path) => { onFileSelected: (path) => {
SessionData.setWallpaper(path); SessionData.setWallpaper(path);
@@ -134,8 +116,8 @@ DankModal {
settingsContent: Component { settingsContent: Component {
Item { Item {
id: rootScope
anchors.fill: parent anchors.fill: parent
focus: true
Column { Column {
anchors.fill: parent anchors.fill: parent
@@ -162,7 +144,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Settings") text: "Settings"
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -194,10 +176,7 @@ DankModal {
id: sidebar id: sidebar
parentModal: settingsModal parentModal: settingsModal
currentIndex: settingsModal.currentTabIndex onCurrentIndexChanged: content.currentIndex = currentIndex
onCurrentIndexChanged: {
settingsModal.currentTabIndex = currentIndex
}
} }
SettingsContent { SettingsContent {
@@ -206,7 +185,7 @@ DankModal {
width: parent.width - sidebar.width width: parent.width - sidebar.width
height: parent.height height: parent.height
parentModal: settingsModal parentModal: settingsModal
currentIndex: settingsModal.currentTabIndex currentIndex: sidebar.currentIndex
} }
} }

View File

@@ -1,5 +1,3 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Modals.Settings import qs.Modals.Settings
@@ -11,48 +9,40 @@ Rectangle {
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
readonly property var sidebarItems: [{ readonly property var sidebarItems: [{
"text": I18n.tr("Personalization"), "text": "Personalization",
"icon": "person" "icon": "person"
}, { }, {
"text": I18n.tr("Time & Weather"), "text": "Time & Date",
"icon": "schedule" "icon": "schedule"
}, { }, {
"text": I18n.tr("Dank Bar"), "text": "Weather",
"icon": "cloud"
}, {
"text": "Dank Bar",
"icon": "toolbar" "icon": "toolbar"
}, { }, {
"text": I18n.tr("Widgets"), "text": "Widgets",
"icon": "widgets" "icon": "widgets"
}, { }, {
"text": I18n.tr("Dock"), "text": "Dock",
"icon": "dock_to_bottom" "icon": "dock_to_bottom"
}, { }, {
"text": I18n.tr("Displays"), "text": "Displays",
"icon": "monitor" "icon": "monitor"
}, { }, {
"text": I18n.tr("Launcher"), "text": "Launcher",
"icon": "apps" "icon": "apps"
}, { }, {
"text": I18n.tr("Theme & Colors"), "text": "Theme & Colors",
"icon": "palette" "icon": "palette"
}, { }, {
"text": I18n.tr("Power & Security"), "text": "Power",
"icon": "power" "icon": "power_settings_new"
}, { }, {
"text": I18n.tr("Plugins"), "text": "About",
"icon": "extension"
}, {
"text": I18n.tr("About"),
"icon": "info" "icon": "info"
}] }]
function navigateNext() {
currentIndex = (currentIndex + 1) % sidebarItems.length
}
function navigatePrevious() {
currentIndex = (currentIndex - 1 + sidebarItems.length) % sidebarItems.length
}
width: 270 width: 270
height: parent.height height: parent.height
color: Theme.surfaceContainer color: Theme.surfaceContainer
@@ -87,16 +77,13 @@ Rectangle {
model: sidebarContainer.sidebarItems model: sidebarContainer.sidebarItems
delegate: Rectangle { Rectangle {
required property int index
required property var modelData
property bool isActive: sidebarContainer.currentIndex === index property bool isActive: sidebarContainer.currentIndex === index
width: parent.width - Theme.spacingS * 2 width: parent.width - Theme.spacingS * 2
height: 44 height: 44
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: isActive ? Theme.primary : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent" color: isActive ? Theme.primaryContainer : tabMouseArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.primaryContainer, 0)
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -107,14 +94,14 @@ Rectangle {
DankIcon { DankIcon {
name: modelData.icon || "" name: modelData.icon || ""
size: Theme.iconSize - 2 size: Theme.iconSize - 2
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: modelData.text || "" text: modelData.text || ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText
font.weight: parent.parent.isActive ? Font.Medium : Font.Normal font.weight: parent.parent.isActive ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }

View File

@@ -13,13 +13,8 @@ Item {
property alias searchField: searchField property alias searchField: searchField
property var parentModal: null property var parentModal: null
function resetScroll() {
resultsView.resetScroll()
}
anchors.fill: parent anchors.fill: parent
focus: true focus: true
clip: false
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
if (parentModal) if (parentModal)
@@ -81,6 +76,10 @@ Item {
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
appLauncher.launchSelected() appLauncher.launchSelected()
event.accepted = true event.accepted = true
} else if (!searchField.activeFocus && event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
searchField.forceActiveFocus()
searchField.insertText(event.text)
event.accepted = true
} }
} }
@@ -102,7 +101,27 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
spacing: Theme.spacingM spacing: Theme.spacingM
clip: false
Rectangle {
width: parent.width
height: categorySelector.height + Theme.spacingS * 2
radius: Theme.cornerRadius
color: "transparent"
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
CategorySelector {
id: categorySelector
anchors.centerIn: parent
width: parent.width - Theme.spacingS * 2
categories: appLauncher.categories
selectedCategory: appLauncher.selectedCategory
compact: false
onCategorySelected: category => {
appLauncher.setCategory(category)
}
}
}
Row { Row {
width: parent.width width: parent.width
@@ -127,8 +146,7 @@ Item {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: parentModal ? parentModal.spotlightOpen : true enabled: parentModal ? parentModal.spotlightOpen : true
placeholderText: "" placeholderText: ""
ignoreLeftRightKeys: appLauncher.viewMode !== "list" ignoreLeftRightKeys: true
ignoreTabKeys: true
keyForwardTargets: [spotlightKeyHandler] keyForwardTargets: [spotlightKeyHandler]
text: appLauncher.searchQuery text: appLauncher.searchQuery
onTextEdited: () => { onTextEdited: () => {
@@ -210,7 +228,6 @@ Item {
} }
SpotlightResults { SpotlightResults {
id: resultsView
appLauncher: spotlightKeyHandler.appLauncher appLauncher: spotlightKeyHandler.appLauncher
contextMenu: contextMenu contextMenu: contextMenu
} }
@@ -228,7 +245,7 @@ Item {
visible: contextMenu.visible visible: contextMenu.visible
z: 999 z: 999
onClicked: () => { onClicked: () => {
contextMenu.hide() contextMenu.close()
} }
MouseArea { MouseArea {

View File

@@ -1,73 +1,65 @@
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Popup { Rectangle {
id: contextMenu id: contextMenu
property var currentApp: null property var currentApp: null
property bool menuVisible: false
property var appLauncher: null property var appLauncher: null
property var parentHandler: null property var parentHandler: null
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
contextMenu.x = x + 4 const menuWidth = 180
contextMenu.y = y + 4 const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
contextMenu.open() let finalX = x + 8
let finalY = y + 8
if (parentHandler) {
if (finalX + menuWidth > parentHandler.width)
finalX = x - menuWidth - 8
if (finalY + menuHeight > parentHandler.height)
finalY = y - menuHeight - 8
finalX = Math.max(8, Math.min(finalX, parentHandler.width - menuWidth - 8))
finalY = Math.max(8, Math.min(finalY, parentHandler.height - menuHeight - 8))
}
contextMenu.x = finalX
contextMenu.y = finalY
contextMenu.visible = true
contextMenu.menuVisible = true
} }
function hide() { function close() {
contextMenu.close() contextMenu.menuVisible = false
Qt.callLater(() => {
contextMenu.visible = false
})
} }
width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2) visible: false
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
padding: 0 radius: Theme.cornerRadius
closePolicy: Popup.CloseOnPressOutside color: Theme.popupBackground()
modal: false border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
dim: false border.width: 1
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
background: Rectangle { Rectangle {
radius: Theme.cornerRadius anchors.fill: parent
color: Theme.popupBackground() anchors.topMargin: 4
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) anchors.leftMargin: 2
border.width: 1 anchors.rightMargin: -2
anchors.bottomMargin: -4
Rectangle { radius: parent.radius
anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.15)
anchors.topMargin: 4 z: parent.z - 1
anchors.leftMargin: 2
anchors.rightMargin: -2
anchors.bottomMargin: -4
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15)
z: -1
}
}
enter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
exit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
} }
Column { Column {
@@ -84,7 +76,6 @@ Popup {
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row { Row {
id: pinRow
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -92,10 +83,10 @@ Popup {
DankIcon { DankIcon {
name: { name: {
if (!desktopEntry) if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
return "push_pin" return "push_pin"
const appId = desktopEntry.id || desktopEntry.execString || "" const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
return SessionData.isPinnedApp(appId) ? "keep_off" : "push_pin" return SessionData.isPinnedApp(appId) ? "keep_off" : "push_pin"
} }
size: Theme.iconSize - 2 size: Theme.iconSize - 2
@@ -106,11 +97,11 @@ Popup {
StyledText { StyledText {
text: { text: {
if (!desktopEntry) if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
return I18n.tr("Pin to Dock") return "Pin to Dock"
const appId = desktopEntry.id || desktopEntry.execString || "" const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
return SessionData.isPinnedApp(appId) ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock") return SessionData.isPinnedApp(appId) ? "Unpin from Dock" : "Pin to Dock"
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
@@ -126,15 +117,15 @@ Popup {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: () => { onClicked: () => {
if (!desktopEntry) if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
return return
const appId = desktopEntry.id || desktopEntry.execString || "" const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
if (SessionData.isPinnedApp(appId)) if (SessionData.isPinnedApp(appId))
SessionData.removePinnedApp(appId) SessionData.removePinnedApp(appId)
else else
SessionData.addPinnedApp(appId) SessionData.addPinnedApp(appId)
contextMenu.hide() contextMenu.close()
} }
} }
} }
@@ -153,79 +144,6 @@ Popup {
} }
} }
Repeater {
model: desktopEntry && desktopEntry.actions ? desktopEntry.actions : []
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: actionRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Item {
anchors.verticalCenter: parent.verticalCenter
width: Theme.iconSize - 2
height: Theme.iconSize - 2
visible: modelData.icon && modelData.icon !== ""
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
}
StyledText {
text: modelData.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: actionMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && desktopEntry) {
SessionService.launchDesktopAction(desktopEntry, modelData)
if (appLauncher && contextMenu.currentApp) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
}
}
Rectangle {
visible: desktopEntry && desktopEntry.actions && desktopEntry.actions.length > 0
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
@@ -233,7 +151,6 @@ Popup {
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row { Row {
id: launchRow
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -248,7 +165,7 @@ Popup {
} }
StyledText { StyledText {
text: I18n.tr("Launch") text: "Launch"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -266,73 +183,23 @@ Popup {
if (contextMenu.currentApp && appLauncher) if (contextMenu.currentApp && appLauncher)
appLauncher.launchApp(contextMenu.currentApp) appLauncher.launchApp(contextMenu.currentApp)
contextMenu.hide() contextMenu.close()
}
}
}
Rectangle {
visible: SessionService.hasPrimeRun
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle {
visible: SessionService.hasPrimeRun
width: parent.width
height: 32
radius: Theme.cornerRadius
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: primeRunRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "memory"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: primeRunMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
if (desktopEntry) {
SessionService.launchDesktopEntry(desktopEntry, true)
if (appLauncher && contextMenu.currentApp) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
} }
} }
} }
} }
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
} }

View File

@@ -13,58 +13,26 @@ DankModal {
id: spotlightModal id: spotlightModal
property bool spotlightOpen: false property bool spotlightOpen: false
property alias spotlightContent: spotlightContentInstance property Component spotlightContent
function show() { function show() {
spotlightOpen = true spotlightOpen = true
open() open()
if (contentLoader.item && contentLoader.item.appLauncher) {
Qt.callLater(() => { contentLoader.item.appLauncher.searchQuery = ""
if (spotlightContent && spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus()
}
})
}
function showWithQuery(query) {
if (spotlightContent) {
if (spotlightContent.appLauncher) {
spotlightContent.appLauncher.searchQuery = query
}
if (spotlightContent.searchField) {
spotlightContent.searchField.text = query
}
} }
spotlightOpen = true
open()
Qt.callLater(() => { Qt.callLater(() => {
if (spotlightContent && spotlightContent.searchField) { if (contentLoader.item && contentLoader.item.searchField) {
spotlightContent.searchField.forceActiveFocus() contentLoader.item.searchField.forceActiveFocus()
} }
}) })
} }
function hide() { function hide() {
spotlightOpen = false spotlightOpen = false
close() close()
} cleanupTimer.restart()
onDialogClosed: {
if (spotlightContent) {
if (spotlightContent.appLauncher) {
spotlightContent.appLauncher.searchQuery = ""
spotlightContent.appLauncher.selectedIndex = 0
spotlightContent.appLauncher.setCategory(I18n.tr("All"))
}
if (spotlightContent.resetScroll) {
spotlightContent.resetScroll()
}
if (spotlightContent.searchField) {
spotlightContent.searchField.text = ""
}
}
} }
function toggle() { function toggle() {
@@ -77,7 +45,7 @@ DankModal {
shouldBeVisible: spotlightOpen shouldBeVisible: spotlightOpen
width: 550 width: 550
height: 700 height: 600
backgroundColor: Theme.popupBackground() backgroundColor: Theme.popupBackground()
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
@@ -88,10 +56,10 @@ DankModal {
if (visible && !spotlightOpen) { if (visible && !spotlightOpen) {
show() show()
} }
if (visible && spotlightContent) { if (visible && contentLoader.item) {
Qt.callLater(() => { Qt.callLater(() => {
if (spotlightContent.searchField) { if (contentLoader.item.searchField) {
spotlightContent.searchField.forceActiveFocus() contentLoader.item.searchField.forceActiveFocus()
} }
}) })
} }
@@ -99,6 +67,20 @@ DankModal {
onBackgroundClicked: () => { onBackgroundClicked: () => {
return hide() return hide()
} }
content: spotlightContent
Timer {
id: cleanupTimer
interval: animationDuration + 50
onTriggered: {
if (contentLoader.item && contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
contentLoader.item.appLauncher.selectedIndex = 0
contentLoader.item.appLauncher.setCategory("All")
}
}
}
Connections { Connections {
function onCloseAllModalsExcept(excludedModal) { function onCloseAllModalsExcept(excludedModal) {
@@ -126,28 +108,12 @@ DankModal {
return "SPOTLIGHT_TOGGLE_SUCCESS" return "SPOTLIGHT_TOGGLE_SUCCESS"
} }
function openQuery(query: string): string {
spotlightModal.showWithQuery(query)
return "SPOTLIGHT_OPEN_QUERY_SUCCESS"
}
function toggleQuery(query: string): string {
if (spotlightModal.spotlightOpen) {
spotlightModal.hide()
} else {
spotlightModal.showWithQuery(query)
}
return "SPOTLIGHT_TOGGLE_QUERY_SUCCESS"
}
target: "spotlight" target: "spotlight"
} }
SpotlightContent { spotlightContent: Component {
id: spotlightContentInstance SpotlightContent {
parentModal: spotlightModal
parentModal: spotlightModal }
} }
directContent: spotlightContentInstance
} }

View File

@@ -7,23 +7,13 @@ import qs.Widgets
Rectangle { Rectangle {
id: resultsContainer id: resultsContainer
// DEVELOPER NOTE: This component renders the Spotlight launcher (accessed via Mod+Space).
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
// likely require corresponding updates in Modules/AppDrawer/AppLauncher.qml and vice versa.
property var appLauncher: null property var appLauncher: null
property var contextMenu: null property var contextMenu: null
function resetScroll() {
resultsList.contentY = 0
resultsGrid.contentY = 0
}
width: parent.width width: parent.width
height: parent.height - y height: parent.height - y
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: "transparent" color: "transparent"
clip: true
DankListView { DankListView {
id: resultsList id: resultsList
@@ -94,32 +84,19 @@ Rectangle {
width: resultsList.iconSize width: resultsList.iconSize
height: resultsList.iconSize height: resultsList.iconSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: resultsList.iconSize
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage { IconImage {
id: listIconImg id: listIconImg
anchors.fill: parent anchors.fill: parent
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) source: Quickshell.iconPath(model.icon, true)
asynchronous: true asynchronous: true
visible: !parent.isMaterial && status === Image.Ready visible: status === Image.Ready
} }
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
visible: !parent.isMaterial && !listIconImg.visible visible: !listIconImg.visible
color: Theme.surfaceLight color: Theme.surfaceLight
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 1
@@ -137,7 +114,7 @@ Rectangle {
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - resultsList.iconSize - Theme.spacingL) : parent.width width: parent.width - resultsList.iconSize - Theme.spacingL
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
@@ -147,8 +124,6 @@ Rectangle {
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
} }
StyledText { StyledText {
@@ -181,9 +156,8 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
resultsList.itemClicked(index, model) resultsList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton && !model.isPlugin) { } else if (mouse.button === Qt.RightButton) {
const globalPos = mapToItem(null, mouse.x, mouse.y) const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y) resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y)
} }
} }
@@ -272,33 +246,20 @@ Rectangle {
width: iconSize width: iconSize
height: iconSize height: iconSize
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: parent.iconSize
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage { IconImage {
id: gridIconImg id: gridIconImg
anchors.fill: parent anchors.fill: parent
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) source: Quickshell.iconPath(model.icon, true)
smooth: true smooth: true
asynchronous: true asynchronous: true
visible: !parent.isMaterial && status === Image.Ready visible: status === Image.Ready
} }
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
visible: !parent.isMaterial && !gridIconImg.visible visible: !gridIconImg.visible
color: Theme.surfaceLight color: Theme.surfaceLight
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 1
@@ -323,8 +284,8 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
maximumLineCount: 1 maximumLineCount: 2
wrapMode: Text.NoWrap wrapMode: Text.WordWrap
} }
} }
@@ -346,9 +307,8 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
resultsGrid.itemClicked(index, model) resultsGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton && !model.isPlugin) { } else if (mouse.button === Qt.RightButton) {
const globalPos = mapToItem(null, mouse.x, mouse.y) const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y) resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y)
} }
} }

View File

@@ -9,125 +9,33 @@ DankModal {
property string wifiPasswordSSID: "" property string wifiPasswordSSID: ""
property string wifiPasswordInput: "" property string wifiPasswordInput: ""
property string wifiUsernameInput: ""
property bool requiresEnterprise: false
property string wifiAnonymousIdentityInput: ""
property string wifiDomainInput: ""
property bool isPromptMode: false
property string promptToken: ""
property string promptReason: ""
property var promptFields: []
property string promptSetting: ""
property bool isVpnPrompt: false
property string connectionName: ""
property string vpnServiceType: ""
property string connectionType: ""
function show(ssid) { function show(ssid) {
wifiPasswordSSID = ssid wifiPasswordSSID = ssid
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
isPromptMode = false
promptToken = ""
promptReason = ""
promptFields = []
promptSetting = ""
isVpnPrompt = false
connectionName = ""
vpnServiceType = ""
connectionType = ""
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
requiresEnterprise = network?.enterprise || false
open() open()
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item) { if (contentLoader.item && contentLoader.item.passwordInput)
if (requiresEnterprise && contentLoader.item.usernameInput) { contentLoader.item.passwordInput.forceActiveFocus()
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus()
}
}
})
}
function showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
isPromptMode = true
promptToken = token
promptReason = reason
promptFields = fields || []
promptSetting = setting || "802-11-wireless-security"
connectionType = connType || "802-11-wireless"
connectionName = connName || ssid || ""
vpnServiceType = vpnService || ""
isVpnPrompt = (connectionType === "vpn" || connectionType === "wireguard")
wifiPasswordSSID = isVpnPrompt ? connectionName : ssid
requiresEnterprise = setting === "802-1x"
if (reason === "wrong-password") {
wifiPasswordInput = ""
wifiUsernameInput = ""
} else {
wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
}
open()
Qt.callLater(() => {
if (contentLoader.item) {
if (reason === "wrong-password" && contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.text = ""
contentLoader.item.passwordInput.forceActiveFocus()
} else if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus()
}
}
}) })
} }
shouldBeVisible: false shouldBeVisible: false
width: 420 width: 420
height: requiresEnterprise ? 430 : 230 height: 230
onShouldBeVisibleChanged: () => { onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) { if (!shouldBeVisible)
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
}
} }
onOpened: { onOpened: {
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item) { if (contentLoader.item && contentLoader.item.passwordInput)
if (requiresEnterprise && contentLoader.item.usernameInput) { contentLoader.item.passwordInput.forceActiveFocus()
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus()
}
}
}) })
} }
onBackgroundClicked: () => { onBackgroundClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
} }
Connections { Connections {
@@ -147,20 +55,13 @@ DankModal {
FocusScope { FocusScope {
id: wifiContent id: wifiContent
property alias usernameInput: usernameInput
property alias passwordInput: passwordInput property alias passwordInput: passwordInput
anchors.fill: parent anchors.fill: parent
focus: true focus: true
Keys.onEscapePressed: event => { Keys.onEscapePressed: event => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
event.accepted = true event.accepted = true
} }
@@ -177,42 +78,18 @@ DankModal {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: { text: "Connect to Wi-Fi"
if (isVpnPrompt) {
return I18n.tr("Connect to VPN")
}
return I18n.tr("Connect to Wi-Fi")
}
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Column { StyledText {
text: `Enter password for "${wifiPasswordSSID}"`
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width width: parent.width
spacing: Theme.spacingXS elide: Text.ElideRight
StyledText {
text: {
if (isVpnPrompt) {
return I18n.tr("Enter password for ") + wifiPasswordSSID
}
const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ")
return prefix + wifiPasswordSSID
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
elide: Text.ElideRight
}
StyledText {
visible: isPromptMode && promptReason === "wrong-password"
text: I18n.tr("Incorrect password")
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
width: parent.width
}
} }
} }
@@ -221,55 +98,12 @@ DankModal {
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
onClicked: () => { onClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
} }
} }
} }
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: usernameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: usernameInput.activeFocus ? 2 : 1
visible: requiresEnterprise && !isVpnPrompt
MouseArea {
anchors.fill: parent
onClicked: () => {
usernameInput.forceActiveFocus()
}
}
DankTextField {
id: usernameInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiUsernameInput
placeholderText: I18n.tr("Username")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiUsernameInput = text
}
onAccepted: () => {
if (passwordInput) {
passwordInput.forceActiveFocus()
}
}
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
@@ -293,46 +127,21 @@ DankModal {
textColor: Theme.surfaceText textColor: Theme.surfaceText
text: wifiPasswordInput text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
placeholderText: (requiresEnterprise && !isVpnPrompt) ? I18n.tr("Password") : "" placeholderText: ""
backgroundColor: "transparent" backgroundColor: "transparent"
focus: !requiresEnterprise focus: true
enabled: root.shouldBeVisible enabled: root.shouldBeVisible
onTextEdited: () => { onTextEdited: () => {
wifiPasswordInput = text wifiPasswordInput = text
} }
onAccepted: () => { onAccepted: () => {
if (isPromptMode) { NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text)
const secrets = {}
if (isVpnPrompt) {
if (passwordInput.text) secrets["password"] = passwordInput.text
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text
} else if (promptSetting === "802-1x") {
if (usernameInput.text) secrets["identity"] = usernameInput.text
if (passwordInput.text) secrets["password"] = passwordInput.text
if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput
}
NetworkService.submitCredentials(promptToken, secrets, true)
} else {
const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
}
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
passwordInput.text = "" passwordInput.text = ""
if (requiresEnterprise) usernameInput.text = ""
} }
Component.onCompleted: () => { Component.onCompleted: () => {
if (root.shouldBeVisible && !requiresEnterprise) if (root.shouldBeVisible)
focusDelayTimer.start() focusDelayTimer.start()
} }
@@ -342,13 +151,8 @@ DankModal {
interval: 100 interval: 100
repeat: false repeat: false
onTriggered: () => { onTriggered: () => {
if (root.shouldBeVisible) { if (root.shouldBeVisible)
if (requiresEnterprise && usernameInput) { passwordInput.forceActiveFocus()
usernameInput.forceActiveFocus()
} else {
passwordInput.forceActiveFocus()
}
}
} }
} }
@@ -363,70 +167,6 @@ DankModal {
} }
} }
Rectangle {
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: anonInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: anonInput.activeFocus ? 2 : 1
MouseArea {
anchors.fill: parent
onClicked: () => {
anonInput.forceActiveFocus()
}
}
DankTextField {
id: anonInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiAnonymousIdentityInput
placeholderText: I18n.tr("Anonymous Identity (optional)")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiAnonymousIdentityInput = text
}
}
}
Rectangle {
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: domainMatchInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: domainMatchInput.activeFocus ? 2 : 1
MouseArea {
anchors.fill: parent
onClicked: () => {
domainMatchInput.forceActiveFocus()
}
}
DankTextField {
id: domainMatchInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiDomainInput
placeholderText: I18n.tr("Domain (optional)")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiDomainInput = text
}
}
}
Row { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -461,7 +201,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Show password") text: "Show password"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -489,7 +229,7 @@ DankModal {
id: cancelText id: cancelText
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Cancel") text: "Cancel"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -502,14 +242,8 @@ DankModal {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: () => { onClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
} }
} }
} }
@@ -519,19 +253,14 @@ DankModal {
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: { enabled: passwordInput.text.length > 0
if (isVpnPrompt) {
return passwordInput.text.length > 0
}
return requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
}
opacity: enabled ? 1 : 0.5 opacity: enabled ? 1 : 0.5
StyledText { StyledText {
id: connectText id: connectText
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Connect") text: "Connect"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium
@@ -545,35 +274,10 @@ DankModal {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: () => { onClicked: () => {
if (isPromptMode) { NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text)
const secrets = {}
if (isVpnPrompt) {
if (passwordInput.text) secrets["password"] = passwordInput.text
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text
} else if (promptSetting === "802-1x") {
if (usernameInput.text) secrets["identity"] = usernameInput.text
if (passwordInput.text) secrets["password"] = passwordInput.text
if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput
}
NetworkService.submitCredentials(promptToken, secrets, true)
} else {
const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
}
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
passwordInput.text = "" passwordInput.text = ""
if (requiresEnterprise) usernameInput.text = ""
} }
} }

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -42,7 +41,7 @@ DankPopout {
if (shouldBeVisible) { if (shouldBeVisible) {
appLauncher.searchQuery = "" appLauncher.searchQuery = ""
appLauncher.selectedIndex = 0 appLauncher.selectedIndex = 0
appLauncher.setCategory(I18n.tr("All")) appLauncher.setCategory("All")
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) { if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.text = "" contentLoader.item.searchField.text = ""
@@ -112,8 +111,6 @@ DankPopout {
mappings[Qt.Key_Up] = () => appLauncher.selectPrevious() mappings[Qt.Key_Up] = () => appLauncher.selectPrevious()
mappings[Qt.Key_Return] = () => appLauncher.launchSelected() mappings[Qt.Key_Return] = () => appLauncher.launchSelected()
mappings[Qt.Key_Enter] = () => appLauncher.launchSelected() mappings[Qt.Key_Enter] = () => appLauncher.launchSelected()
mappings[Qt.Key_Tab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : appLauncher.selectNext()
mappings[Qt.Key_Backtab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : appLauncher.selectPrevious()
if (appLauncher.viewMode === "grid") { if (appLauncher.viewMode === "grid") {
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow() mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow()
@@ -168,6 +165,11 @@ DankPopout {
} }
} }
if (!searchField.activeFocus && event.text && /[a-zA-Z0-9\s]/.test(event.text)) {
searchField.forceActiveFocus()
searchField.insertText(event.text)
event.accepted = true
}
} }
Column { Column {
@@ -177,23 +179,25 @@ DankPopout {
y: Theme.spacingS y: Theme.spacingS
spacing: Theme.spacingS spacing: Theme.spacingS
Item { Row {
width: parent.width width: parent.width
height: 40 height: 40
leftPadding: Theme.spacingS
StyledText { StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Applications") text: "Applications"
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.surfaceText color: Theme.surfaceText
} }
Item {
width: parent.width - 200
height: 1
}
StyledText { StyledText {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: appLauncher.model.count + " apps" text: appLauncher.model.count + " apps"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -218,8 +222,7 @@ DankPopout {
showClearButton: true showClearButton: true
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: appDrawerPopout.shouldBeVisible enabled: appDrawerPopout.shouldBeVisible
ignoreLeftRightKeys: appLauncher.viewMode !== "list" ignoreLeftRightKeys: true
ignoreTabKeys: true
keyForwardTargets: [keyHandler] keyForwardTargets: [keyHandler]
onTextEdited: { onTextEdited: {
appLauncher.searchQuery = text appLauncher.searchQuery = text
@@ -244,7 +247,7 @@ DankPopout {
return return
} }
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right, Qt.Key_Tab, Qt.Key_Backtab] const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right]
const isNavigationKey = navigationKeys.includes(event.key) const isNavigationKey = navigationKeys.includes(event.key)
const isEmptyEnter = isEnterKey && !hasText const isEmptyEnter = isEnterKey && !hasText
@@ -268,17 +271,18 @@ DankPopout {
spacing: Theme.spacingM spacing: Theme.spacingM
visible: searchField.text.length === 0 visible: searchField.text.length === 0
leftPadding: Theme.spacingS leftPadding: Theme.spacingS
topPadding: Theme.spacingXS
Rectangle { Item {
width: 180 width: 200
height: 40 height: 36
radius: Theme.cornerRadius
color: "transparent"
DankDropdown { DankDropdown {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
text: "" text: ""
dropdownWidth: 180
currentValue: appLauncher.selectedCategory currentValue: appLauncher.selectedCategory
options: appLauncher.categories options: appLauncher.categories
optionIcons: appLauncher.categoryIcons optionIcons: appLauncher.categoryIcons
@@ -289,7 +293,7 @@ DankPopout {
} }
Item { Item {
width: parent.width - 290 width: parent.width - 310
height: 1 height: 1
} }
@@ -404,29 +408,16 @@ DankPopout {
width: appList.iconSize width: appList.iconSize
height: appList.iconSize height: appList.iconSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: appList.iconSize - Theme.spacingM
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage { IconImage {
id: listIconImg id: listIconImg
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingXS anchors.margins: Theme.spacingXS
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) source: Quickshell.iconPath(model.icon, true)
smooth: true smooth: true
asynchronous: true asynchronous: true
visible: !parent.isMaterial && status === Image.Ready visible: status === Image.Ready
} }
Rectangle { Rectangle {
@@ -434,7 +425,7 @@ DankPopout {
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingM anchors.bottomMargin: Theme.spacingM
visible: !parent.isMaterial && listIconImg.status !== Image.Ready visible: !listIconImg.visible
color: Theme.surfaceLight color: Theme.surfaceLight
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 0 border.width: 0
@@ -448,12 +439,11 @@ DankPopout {
font.weight: Font.Bold font.weight: Font.Bold
} }
} }
} }
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - appList.iconSize - Theme.spacingL) : parent.width width: parent.width - appList.iconSize - Theme.spacingL
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
@@ -463,8 +453,6 @@ DankPopout {
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
} }
StyledText { StyledText {
@@ -501,8 +489,7 @@ DankPopout {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appList.itemClicked(index, model) appList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var globalPos = mapToItem(null, mouse.x, mouse.y) var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appList.itemRightClicked(index, model, panelPos.x, panelPos.y) appList.itemRightClicked(index, model, panelPos.x, panelPos.y)
} }
} }
@@ -527,7 +514,6 @@ DankPopout {
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20 property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth) property int remainingSpace: width - (actualColumns * cellWidth)
signal keyboardNavigationReset signal keyboardNavigationReset
@@ -593,19 +579,6 @@ DankPopout {
width: iconSize width: iconSize
height: iconSize height: iconSize
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: parent.iconSize - Theme.spacingL
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage { IconImage {
id: gridIconImg id: gridIconImg
@@ -614,10 +587,10 @@ DankPopout {
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS anchors.bottomMargin: Theme.spacingS
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) source: Quickshell.iconPath(model.icon, true)
smooth: true smooth: true
asynchronous: true asynchronous: true
visible: !parent.isMaterial && status === Image.Ready visible: status === Image.Ready
} }
Rectangle { Rectangle {
@@ -625,7 +598,7 @@ DankPopout {
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS anchors.bottomMargin: Theme.spacingS
visible: !parent.isMaterial && gridIconImg.status !== Image.Ready visible: !gridIconImg.visible
color: Theme.surfaceLight color: Theme.surfaceLight
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 0 border.width: 0
@@ -650,8 +623,8 @@ DankPopout {
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
maximumLineCount: 1 maximumLineCount: 2
wrapMode: Text.NoWrap wrapMode: Text.WordWrap
} }
} }
@@ -677,8 +650,7 @@ DankPopout {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appGrid.itemClicked(index, model) appGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var globalPos = mapToItem(null, mouse.x, mouse.y) var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y) appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y)
} }
} }
@@ -691,68 +663,68 @@ DankPopout {
} }
} }
Popup { Rectangle {
id: contextMenu id: contextMenu
property var currentApp: null property var currentApp: null
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null property bool menuVisible: false
readonly property string appId: desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : ""
readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : ""
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId) readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
contextMenu.x = x + 4
contextMenu.y = y + 4 const menuWidth = 180
contextMenu.open() const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
let finalX = x + 8
let finalY = y + 8
if (finalX + menuWidth > appDrawerPopout.popupWidth) {
finalX = x - menuWidth - 8
}
if (finalY + menuHeight > appDrawerPopout.popupHeight) {
finalY = y - menuHeight - 8
}
finalX = Math.max(8, Math.min(finalX, appDrawerPopout.popupWidth - menuWidth - 8))
finalY = Math.max(8, Math.min(finalY, appDrawerPopout.popupHeight - menuHeight - 8))
contextMenu.x = finalX
contextMenu.y = finalY
contextMenu.visible = true
contextMenu.menuVisible = true
} }
function hide() { function close() {
contextMenu.close() contextMenu.menuVisible = false
Qt.callLater(() => {
contextMenu.visible = false
})
} }
width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2) visible: false
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
padding: 0 radius: Theme.cornerRadius
closePolicy: Popup.CloseOnPressOutside color: Theme.popupBackground()
modal: false border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
dim: false border.width: 0
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
background: Rectangle { Rectangle {
radius: Theme.cornerRadius anchors.fill: parent
color: Theme.popupBackground() anchors.topMargin: 4
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) anchors.leftMargin: 2
border.width: 1 anchors.rightMargin: -2
anchors.bottomMargin: -4
Rectangle { radius: parent.radius
anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.15)
anchors.topMargin: 4 z: parent.z - 1
anchors.leftMargin: 2
anchors.rightMargin: -2
anchors.bottomMargin: -4
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15)
z: -1
}
}
enter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
exit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
} }
Column { Column {
@@ -783,7 +755,7 @@ DankPopout {
} }
StyledText { StyledText {
text: contextMenu.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock") text: contextMenu.isPinned ? "Unpin from Dock" : "Pin to Dock"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -798,7 +770,7 @@ DankPopout {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (!contextMenu.desktopEntry) { if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) {
return return
} }
@@ -807,7 +779,7 @@ DankPopout {
} else { } else {
SessionData.addPinnedApp(contextMenu.appId) SessionData.addPinnedApp(contextMenu.appId)
} }
contextMenu.hide() contextMenu.close()
} }
} }
} }
@@ -826,79 +798,6 @@ DankPopout {
} }
} }
Repeater {
model: contextMenu.desktopEntry && contextMenu.desktopEntry.actions ? contextMenu.desktopEntry.actions : []
Rectangle {
width: Math.max(parent.width, actionRow.implicitWidth + Theme.spacingS * 2)
height: 32
radius: Theme.cornerRadius
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: actionRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Item {
anchors.verticalCenter: parent.verticalCenter
width: Theme.iconSize - 2
height: Theme.iconSize - 2
visible: modelData.icon && modelData.icon !== ""
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
}
StyledText {
text: modelData.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: actionMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && contextMenu.desktopEntry) {
SessionService.launchDesktopAction(contextMenu.desktopEntry, modelData)
if (contextMenu.currentApp) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
}
}
Rectangle {
visible: contextMenu.desktopEntry && contextMenu.desktopEntry.actions && contextMenu.desktopEntry.actions.length > 0
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
@@ -920,7 +819,7 @@ DankPopout {
} }
StyledText { StyledText {
text: I18n.tr("Launch") text: "Launch"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -938,72 +837,23 @@ DankPopout {
if (contextMenu.currentApp) if (contextMenu.currentApp)
appLauncher.launchApp(contextMenu.currentApp) appLauncher.launchApp(contextMenu.currentApp)
contextMenu.hide() contextMenu.close()
} }
} }
} }
}
Rectangle { Behavior on opacity {
visible: SessionService.hasPrimeRun NumberAnimation {
width: parent.width - Theme.spacingS * 2 duration: Theme.mediumDuration
height: 5 easing.type: Theme.emphasizedEasing
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
} }
}
Rectangle { Behavior on scale {
visible: SessionService.hasPrimeRun NumberAnimation {
width: parent.width duration: Theme.mediumDuration
height: 32 easing.type: Theme.emphasizedEasing
radius: Theme.cornerRadius
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "memory"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: primeRunMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (contextMenu.desktopEntry) {
SessionService.launchDesktopEntry(contextMenu.desktopEntry, true)
if (contextMenu.currentApp) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
} }
} }
} }
@@ -1013,7 +863,7 @@ DankPopout {
visible: contextMenu.visible visible: contextMenu.visible
z: 999 z: 999
onClicked: { onClicked: {
contextMenu.hide() contextMenu.close()
} }
MouseArea { MouseArea {

View File

@@ -8,12 +8,8 @@ import qs.Widgets
Item { Item {
id: root id: root
// DEVELOPER NOTE: This component manages the AppDrawer launcher (accessed via DankBar icon).
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
// likely require corresponding updates in Modals/Spotlight/SpotlightResults.qml and vice versa.
property string searchQuery: "" property string searchQuery: ""
property string selectedCategory: I18n.tr("All") property string selectedCategory: "All"
property string viewMode: "list" // "list" or "grid" property string viewMode: "list" // "list" or "grid"
property int selectedIndex: 0 property int selectedIndex: 0
property int maxResults: 50 property int maxResults: 50
@@ -22,46 +18,20 @@ Item {
property int debounceInterval: 50 property int debounceInterval: 50
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
property bool suppressUpdatesWhileLaunching: false property bool suppressUpdatesWhileLaunching: false
property var categories: { readonly property var categories: {
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science") const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
const result = [I18n.tr("All")] const result = ["All"]
return result.concat(allCategories.filter(cat => cat !== I18n.tr("All"))) return result.concat(allCategories.filter(cat => cat !== "All"))
} }
readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category)) readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {} property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
property alias model: filteredModel property alias model: filteredModel
property var _watchApplications: AppSearchService.applications property var _watchApplications: AppSearchService.applications
property var _uniqueApps: []
property bool _isTriggered: false
property string _triggeredCategory: ""
property bool _updatingFromTrigger: false
signal appLaunched(var app) signal appLaunched(var app)
signal categorySelected(string category) signal categorySelected(string category)
signal viewModeSelected(string mode) signal viewModeSelected(string mode)
function updateCategories() {
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
const result = [I18n.tr("All")]
categories = result.concat(allCategories.filter(cat => cat !== I18n.tr("All")))
}
Connections {
target: PluginService
function onPluginLoaded() { updateCategories() }
function onPluginUnloaded() { updateCategories() }
function onPluginListUpdated() { updateCategories() }
}
Connections {
target: SettingsData
function onSortAppsAlphabeticallyChanged() {
updateFilteredModel()
}
}
function updateFilteredModel() { function updateFilteredModel() {
if (suppressUpdatesWhileLaunching) { if (suppressUpdatesWhileLaunching) {
suppressUpdatesWhileLaunching = false suppressUpdatesWhileLaunching = false
@@ -71,112 +41,49 @@ Item {
selectedIndex = 0 selectedIndex = 0
keyboardNavigationActive = false keyboardNavigationActive = false
const triggerResult = checkPluginTriggers(searchQuery)
if (triggerResult.triggered) {
console.log("AppLauncher: Plugin trigger detected:", triggerResult.trigger, "for plugin:", triggerResult.pluginId)
}
let apps = [] let apps = []
const allCategory = I18n.tr("All") if (searchQuery.length === 0) {
const emptyTriggerPlugins = typeof PluginService !== "undefined" ? PluginService.getPluginsWithEmptyTrigger() : [] apps = selectedCategory === "All" ? AppSearchService.getAppsInCategory("All") : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
if (triggerResult.triggered) {
_isTriggered = true
_triggeredCategory = triggerResult.pluginCategory
_updatingFromTrigger = true
selectedCategory = triggerResult.pluginCategory
_updatingFromTrigger = false
apps = AppSearchService.getPluginItems(triggerResult.pluginCategory, triggerResult.query)
} else { } else {
if (_isTriggered) { if (selectedCategory === "All") {
_updatingFromTrigger = true apps = AppSearchService.searchApplications(searchQuery)
selectedCategory = allCategory
_updatingFromTrigger = false
_isTriggered = false
_triggeredCategory = ""
}
if (searchQuery.length === 0) {
if (selectedCategory === allCategory) {
let emptyTriggerItems = []
emptyTriggerPlugins.forEach(pluginId => {
const plugin = PluginService.getLauncherPlugin(pluginId)
const pluginCategory = plugin.name || pluginId
const items = AppSearchService.getPluginItems(pluginCategory, "")
emptyTriggerItems = emptyTriggerItems.concat(items)
})
apps = AppSearchService.applications.concat(emptyTriggerItems)
} else {
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
}
} else { } else {
if (selectedCategory === allCategory) { const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
apps = AppSearchService.searchApplications(searchQuery) if (categoryApps.length > 0) {
const allSearchResults = AppSearchService.searchApplications(searchQuery)
let emptyTriggerItems = [] const categoryNames = new Set(categoryApps.map(app => app.name))
emptyTriggerPlugins.forEach(pluginId => { apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults)
const plugin = PluginService.getLauncherPlugin(pluginId)
const pluginCategory = plugin.name || pluginId
const items = AppSearchService.getPluginItems(pluginCategory, searchQuery)
emptyTriggerItems = emptyTriggerItems.concat(items)
})
apps = apps.concat(emptyTriggerItems)
} else { } else {
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory) apps = []
if (categoryApps.length > 0) {
const allSearchResults = AppSearchService.searchApplications(searchQuery)
const categoryNames = new Set(categoryApps.map(app => app.name))
apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults)
} else {
apps = []
}
} }
} }
} }
if (searchQuery.length === 0) { if (searchQuery.length === 0) {
if (SettingsData.sortAppsAlphabetically) { apps = apps.sort((a, b) => {
apps = apps.sort((a, b) => { const aId = a.id || a.execString || a.exec || ""
return (a.name || "").localeCompare(b.name || "") const bId = b.id || b.execString || b.exec || ""
}) const aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0
} else { const bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0
apps = apps.sort((a, b) => { if (aUsage !== bUsage) {
const aId = a.id || a.execString || a.exec || "" return bUsage - aUsage
const bId = b.id || b.execString || b.exec || "" }
const aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0 return (a.name || "").localeCompare(b.name || "")
const bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0 })
if (aUsage !== bUsage) {
return bUsage - aUsage
}
return (a.name || "").localeCompare(b.name || "")
})
}
} }
const seenNames = new Set()
const uniqueApps = []
apps.forEach(app => { apps.forEach(app => {
if (app) { if (app) {
const itemKey = app.name + "|" + (app.execString || app.exec || app.action || "")
if (seenNames.has(itemKey)) {
return
}
seenNames.add(itemKey)
uniqueApps.push(app)
const isPluginItem = app.action !== undefined
filteredModel.append({ filteredModel.append({
"name": app.name || "", "name": app.name || "",
"exec": app.execString || app.exec || app.action || "", "exec": app.execString || "",
"icon": app.icon !== undefined ? app.icon : (isPluginItem ? "" : "application-x-executable"), "icon": app.icon || "application-x-executable",
"comment": app.comment || "", "comment": app.comment || "",
"categories": app.categories || [], "categories": app.categories || [],
"isPlugin": isPluginItem, "desktopEntry": app
"appIndex": uniqueApps.length - 1
}) })
} }
}) })
root._uniqueApps = uniqueApps
} }
function selectNext() { function selectNext() {
@@ -220,25 +127,13 @@ Item {
} }
function launchApp(appData) { function launchApp(appData) {
if (!appData || typeof appData.appIndex === "undefined" || appData.appIndex < 0 || appData.appIndex >= _uniqueApps.length) { if (!appData) {
return return
} }
suppressUpdatesWhileLaunching = true suppressUpdatesWhileLaunching = true
SessionService.launchDesktopEntry(appData.desktopEntry)
const actualApp = _uniqueApps[appData.appIndex] appLaunched(appData)
AppUsageHistoryData.addAppUsage(appData.desktopEntry)
if (appData.isPlugin) {
const pluginId = getPluginIdForItem(actualApp)
if (pluginId) {
AppSearchService.executePluginItem(actualApp, pluginId)
appLaunched(appData)
return
}
} else {
SessionService.launchDesktopEntry(actualApp)
appLaunched(appData)
AppUsageHistoryData.addAppUsage(actualApp)
}
} }
function setCategory(category) { function setCategory(category) {
@@ -258,12 +153,7 @@ Item {
updateFilteredModel() updateFilteredModel()
} }
} }
onSelectedCategoryChanged: { onSelectedCategoryChanged: updateFilteredModel()
if (_updatingFromTrigger) {
return
}
updateFilteredModel()
}
onAppUsageRankingChanged: updateFilteredModel() onAppUsageRankingChanged: updateFilteredModel()
on_WatchApplicationsChanged: updateFilteredModel() on_WatchApplicationsChanged: updateFilteredModel()
Component.onCompleted: { Component.onCompleted: {
@@ -281,63 +171,4 @@ Item {
repeat: false repeat: false
onTriggered: updateFilteredModel() onTriggered: updateFilteredModel()
} }
// Plugin trigger system functions
function checkPluginTriggers(query) {
if (!query || typeof PluginService === "undefined") {
return { triggered: false, pluginCategory: "", query: "" }
}
const triggers = PluginService.getAllPluginTriggers()
for (const trigger in triggers) {
if (query.startsWith(trigger)) {
const pluginId = triggers[trigger]
const plugin = PluginService.getLauncherPlugin(pluginId)
if (plugin) {
const remainingQuery = query.substring(trigger.length).trim()
const result = {
triggered: true,
pluginId: pluginId,
pluginCategory: plugin.name || pluginId,
query: remainingQuery,
trigger: trigger
}
return result
}
}
}
return { triggered: false, pluginCategory: "", query: "" }
}
function getPluginIdForItem(item) {
if (!item || !item.categories || typeof PluginService === "undefined") {
return null
}
const launchers = PluginService.getLauncherPlugins()
for (const pluginId in launchers) {
const plugin = launchers[pluginId]
const pluginCategory = plugin.name || pluginId
let hasCategory = false
if (Array.isArray(item.categories)) {
hasCategory = item.categories.includes(pluginCategory)
} else if (item.categories && typeof item.categories.count !== "undefined") {
for (let i = 0; i < item.categories.count; i++) {
if (item.categories.get(i) === pluginCategory) {
hasCategory = true
break
}
}
}
if (hasCategory) {
return pluginId
}
}
return null
}
} }

View File

@@ -1,14 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Services
import qs.Widgets import qs.Widgets
Item { Item {
id: root id: root
property var categories: [] property var categories: []
property string selectedCategory: I18n.tr("All") property string selectedCategory: "All"
property bool compact: false property bool compact: false
signal categorySelected(string category) signal categorySelected(string category)
@@ -19,6 +18,7 @@ Item {
readonly property color unselectedBorderColor: "transparent" readonly property color unselectedBorderColor: "transparent"
function handleCategoryClick(category) { function handleCategoryClick(category) {
selectedCategory = category
categorySelected(category) categorySelected(category)
} }

View File

@@ -1,135 +0,0 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules
Variants {
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens
}
return SettingsData.getFilteredScreens("wallpaper")
}
PanelWindow {
id: blurWallpaperWindow
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.namespace: "dms:blurwallpaper"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
anchors.top: true
anchors.bottom: true
anchors.left: true
anchors.right: true
color: "transparent"
Item {
id: root
anchors.fill: parent
property string source: SessionData.getMonitorWallpaper(modelData.name) || ""
property bool isColorSource: source.startsWith("#")
Connections {
target: SessionData
function onIsLightModeChanged() {
if (SessionData.perModeWallpaper) {
var newSource = SessionData.getMonitorWallpaper(modelData.name) || ""
if (newSource !== root.source) {
root.source = newSource
}
}
}
}
function getFillMode(modeName) {
switch(modeName) {
case "Stretch": return Image.Stretch
case "Fit":
case "PreserveAspectFit": return Image.PreserveAspectFit
case "Fill":
case "PreserveAspectCrop": return Image.PreserveAspectCrop
case "Tile": return Image.Tile
case "TileVertically": return Image.TileVertically
case "TileHorizontally": return Image.TileHorizontally
case "Pad": return Image.Pad
default: return Image.PreserveAspectCrop
}
}
WallpaperEngineProc {
id: weProc
monitor: modelData.name
}
Component.onCompleted: {
if (source) {
const formattedSource = source.startsWith("file://") ? source : "file://" + source
wallpaperImage.source = formattedSource
}
}
Component.onDestruction: {
weProc.stop()
}
onSourceChanged: {
const isWE = source.startsWith("we:")
const isColor = source.startsWith("#")
if (isWE) {
wallpaperImage.source = ""
weProc.start(source.substring(3))
} else {
weProc.stop()
if (!source) {
wallpaperImage.source = ""
} else if (isColor) {
wallpaperImage.source = ""
} else {
wallpaperImage.source = source.startsWith("file://") ? source : "file://" + source
}
}
}
Loader {
anchors.fill: parent
active: !root.source || root.isColorSource
asynchronous: true
sourceComponent: DankBackdrop {
screenName: modelData.name
}
}
Image {
id: wallpaperImage
anchors.fill: parent
visible: false
asynchronous: true
smooth: true
cache: true
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
}
MultiEffect {
anchors.fill: parent
source: wallpaperImage
blurEnabled: true
blur: 0.8
blurMax: 48
}
}
}
}

View File

@@ -1,246 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
Ref {
service: DMSNetworkService
}
ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetSecondaryText: {
if (!DMSNetworkService.connected)
return "Disconnected"
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return names[0] || "Connected"
return names[0] + " +" + (names.length - 1)
}
ccWidgetIsActive: DMSNetworkService.connected
onCcWidgetToggled: {
if (DMSNetworkService.connected) {
DMSNetworkService.disconnectAllActive()
} else if (DMSNetworkService.profiles.length > 0) {
DMSNetworkService.connect(DMSNetworkService.profiles[0].uuid)
}
}
ccDetailContent: Component {
Rectangle {
id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: detailColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
RowLayout {
spacing: Theme.spacingS
width: parent.width
StyledText {
text: {
if (!DMSNetworkService.connected)
return "Active: None"
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return "Active: " + (names[0] || "VPN")
return "Active: " + names[0] + " +" + (names.length - 1)
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Item {
Layout.fillWidth: true
}
Rectangle {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: DMSNetworkService.connected
width: 110
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "link_off"
size: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Disconnect")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: discAllArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: DMSNetworkService.disconnectAllActive()
}
}
}
Rectangle {
height: 1
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
DankFlickable {
width: parent.width
height: 160
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: Theme.spacingXS
Item {
width: parent.width
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "playlist_remove"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No VPN profiles found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("Add a VPN in NetworkManager")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Repeater {
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
text: {
if (modelData.type === "wireguard")
return "WireGuard"
const svc = modelData.serviceType || ""
if (svc.indexOf("openvpn") !== -1)
return "OpenVPN"
if (svc.indexOf("wireguard") !== -1)
return "WireGuard (plugin)"
if (svc.indexOf("openconnect") !== -1)
return "OpenConnect"
if (svc.indexOf("fortissl") !== -1 || svc.indexOf("forti") !== -1)
return "Fortinet"
if (svc.indexOf("strongswan") !== -1)
return "IPsec (strongSwan)"
if (svc.indexOf("libreswan") !== -1)
return "IPsec (Libreswan)"
if (svc.indexOf("l2tp") !== -1)
return "L2TP/IPsec"
if (svc.indexOf("pptp") !== -1)
return "PPTP"
if (svc.indexOf("vpnc") !== -1)
return "Cisco (vpnc)"
if (svc.indexOf("sstp") !== -1)
return "SSTP"
if (svc)
return svc.split('.').pop()
return "VPN"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}
}
}
}
}
}
}
}

View File

@@ -59,7 +59,7 @@ Rectangle {
DankIcon { DankIcon {
name: root.iconName name: root.iconName
size: Theme.iconSize size: Theme.iconSize
color: isActive ? Theme.primaryText : Theme.primary color: isActive ? Theme.primaryContainer : Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -77,7 +77,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.text text: root.text
style: Typography.Style.Body style: Typography.Style.Body
color: isActive ? Theme.primaryText : Theme.surfaceText color: isActive ? Theme.primaryContainer : Theme.surfaceText
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
@@ -86,7 +86,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.secondaryText text: root.secondaryText
style: Typography.Style.Caption style: Typography.Style.Caption
color: isActive ? Theme.primaryText : Theme.surfaceVariantText color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
visible: text.length > 0 visible: text.length > 0
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap

View File

@@ -1,8 +1,6 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services
import qs.Modules.ControlCenter.Details import qs.Modules.ControlCenter.Details
import qs.Modules.ControlCenter.Models
Item { Item {
id: root id: root
@@ -10,156 +8,34 @@ Item {
property string expandedSection: "" property string expandedSection: ""
property var expandedWidgetData: null property var expandedWidgetData: null
property var bluetoothCodecSelector: null property var bluetoothCodecSelector: null
property string screenName: ""
property var pluginDetailInstance: null
property var widgetModel: null
property var collapseCallback: null
Loader { Loader {
id: pluginDetailLoader
width: parent.width width: parent.width
height: 250 height: 250
y: Theme.spacingS y: Theme.spacingS
active: false active: parent.height > 0
sourceComponent: null property string sectionKey: root.expandedSection
} sourceComponent: {
switch (root.expandedSection) {
Loader { case "network":
id: coreDetailLoader case "wifi": return networkDetailComponent
width: parent.width case "bluetooth": return bluetoothDetailComponent
height: 250 case "audioOutput": return audioOutputDetailComponent
y: Theme.spacingS case "audioInput": return audioInputDetailComponent
active: false case "battery": return batteryDetailComponent
sourceComponent: null default:
} if (root.expandedSection.startsWith("diskUsage_")) {
return diskUsageDetailComponent
Connections {
target: coreDetailLoader.item
enabled: root.expandedSection.startsWith("brightnessSlider_")
ignoreUnknownSignals: true
function onDeviceNameChanged(newDeviceName) {
if (root.expandedWidgetData && root.expandedWidgetData.id === "brightnessSlider") {
const widgets = SettingsData.controlCenterWidgets || []
const newWidgets = widgets.map(w => {
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.deviceName = newDeviceName
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
if (root.collapseCallback) {
root.collapseCallback()
} }
return null
} }
} }
} onSectionKeyChanged: {
active = false
Connections { active = true
target: coreDetailLoader.item
enabled: root.expandedSection.startsWith("diskUsage_")
ignoreUnknownSignals: true
function onMountPathChanged(newMountPath) {
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
const widgets = SettingsData.controlCenterWidgets || []
const newWidgets = widgets.map(w => {
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.mountPath = newMountPath
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
if (root.collapseCallback) {
root.collapseCallback()
}
}
} }
} }
onExpandedSectionChanged: {
if (pluginDetailInstance) {
pluginDetailInstance.destroy()
pluginDetailInstance = null
}
pluginDetailLoader.active = false
coreDetailLoader.active = false
if (!root.expandedSection) {
return
}
if (root.expandedSection.startsWith("builtin_")) {
const builtinId = root.expandedSection
let builtinInstance = null
if (builtinId === "builtin_vpn") {
if (widgetModel?.vpnLoader) {
widgetModel.vpnLoader.active = true
}
builtinInstance = widgetModel.vpnBuiltinInstance
}
if (!builtinInstance || !builtinInstance.ccDetailContent) {
return
}
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent
pluginDetailLoader.active = parent.height > 0
return
}
if (root.expandedSection.startsWith("plugin_")) {
const pluginId = root.expandedSection.replace("plugin_", "")
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
if (!pluginComponent) {
return
}
pluginDetailInstance = pluginComponent.createObject(null)
if (!pluginDetailInstance || !pluginDetailInstance.ccDetailContent) {
if (pluginDetailInstance) {
pluginDetailInstance.destroy()
pluginDetailInstance = null
}
return
}
pluginDetailLoader.sourceComponent = pluginDetailInstance.ccDetailContent
pluginDetailLoader.active = parent.height > 0
return
}
if (root.expandedSection.startsWith("diskUsage_")) {
coreDetailLoader.sourceComponent = diskUsageDetailComponent
coreDetailLoader.active = parent.height > 0
return
}
if (root.expandedSection.startsWith("brightnessSlider_")) {
coreDetailLoader.sourceComponent = brightnessDetailComponent
coreDetailLoader.active = parent.height > 0
return
}
switch (root.expandedSection) {
case "network":
case "wifi": coreDetailLoader.sourceComponent = networkDetailComponent; break
case "bluetooth": coreDetailLoader.sourceComponent = bluetoothDetailComponent; break
case "audioOutput": coreDetailLoader.sourceComponent = audioOutputDetailComponent; break
case "audioInput": coreDetailLoader.sourceComponent = audioInputDetailComponent; break
case "battery": coreDetailLoader.sourceComponent = batteryDetailComponent; break
default: return
}
coreDetailLoader.active = parent.height > 0
}
Component { Component {
id: networkDetailComponent id: networkDetailComponent
NetworkDetail {} NetworkDetail {}
@@ -200,15 +76,22 @@ Item {
DiskUsageDetail { DiskUsageDetail {
currentMountPath: root.expandedWidgetData?.mountPath || "/" currentMountPath: root.expandedWidgetData?.mountPath || "/"
instanceId: root.expandedWidgetData?.instanceId || "" instanceId: root.expandedWidgetData?.instanceId || ""
}
}
Component {
id: brightnessDetailComponent onMountPathChanged: (newMountPath) => {
BrightnessDetail { if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
initialDeviceName: root.expandedWidgetData?.deviceName || "" const widgets = SettingsData.controlCenterWidgets || []
instanceId: root.expandedWidgetData?.instanceId || "" const newWidgets = widgets.map(w => {
screenName: root.screenName if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.mountPath = newMountPath
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
}
}
} }
} }
} }

View File

@@ -15,18 +15,11 @@ Column {
property var expandedWidgetData: null property var expandedWidgetData: null
property var bluetoothCodecSelector: null property var bluetoothCodecSelector: null
property bool darkModeTransitionPending: false property bool darkModeTransitionPending: false
property string screenName: ""
property var parentScreen: null
signal expandClicked(var widgetData, int globalIndex) signal expandClicked(var widgetData, int globalIndex)
signal removeWidget(int index) signal removeWidget(int index)
signal moveWidget(int fromIndex, int toIndex) signal moveWidget(int fromIndex, int toIndex)
signal toggleWidgetSize(int index) signal toggleWidgetSize(int index)
signal collapseRequested()
function requestCollapse() {
collapseRequested()
}
spacing: editMode ? Theme.spacingL : Theme.spacingS spacing: editMode ? Theme.spacingL : Theme.spacingS
@@ -69,8 +62,7 @@ Column {
property var rowWidgets: modelData property var rowWidgets: modelData
property bool isSliderOnlyRow: { property bool isSliderOnlyRow: {
const widgets = rowWidgets || [] const widgets = rowWidgets || []
if (widgets.length === 0) if (widgets.length === 0) return false
return false
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider") return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
} }
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0 topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
@@ -89,7 +81,7 @@ Column {
const widgets = SettingsData.controlCenterWidgets || [] const widgets = SettingsData.controlCenterWidgets || []
for (var i = 0; i < widgets.length; i++) { for (var i = 0; i < widgets.length; i++) {
if (widgets[i].id === modelData.id) { if (widgets[i].id === modelData.id) {
if (modelData.id === "diskUsage" || modelData.id === "brightnessSlider") { if (modelData.id === "diskUsage") {
if (widgets[i].instanceId === modelData.instanceId) { if (widgets[i].instanceId === modelData.instanceId) {
return i return i
} }
@@ -129,11 +121,7 @@ Column {
widgetComponent: { widgetComponent: {
const id = modelData.id || "" const id = modelData.id || ""
if (id.startsWith("builtin_")) { if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
return builtinPluginWidgetComponent
} else if (id.startsWith("plugin_")) {
return pluginWidgetComponent
} else if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
return compoundPillComponent return compoundPillComponent
} else if (id === "volumeSlider") { } else if (id === "volumeSlider") {
return audioSliderComponent return audioSliderComponent
@@ -163,48 +151,23 @@ Column {
width: parent.width width: parent.width
height: active ? (250 + Theme.spacingS) : 0 height: active ? (250 + Theme.spacingS) : 0
property bool active: { property bool active: {
if (root.expandedSection === "") if (root.expandedSection === "") return false
return false
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) { if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
const expandedInstanceId = root.expandedWidgetData.instanceId const expandedInstanceId = root.expandedWidgetData.instanceId
return rowWidgets.some(w => w.id === "diskUsage" && w.instanceId === expandedInstanceId) return rowWidgets.some(w => w.id === "diskUsage" && w.instanceId === expandedInstanceId)
} }
if (root.expandedSection.startsWith("brightnessSlider_") && root.expandedWidgetData) {
const expandedInstanceId = root.expandedWidgetData.instanceId
return rowWidgets.some(w => w.id === "brightnessSlider" && w.instanceId === expandedInstanceId)
}
return rowIndex === root.expandedRowIndex return rowIndex === root.expandedRowIndex
} }
visible: active visible: active
expandedSection: root.expandedSection expandedSection: root.expandedSection
expandedWidgetData: root.expandedWidgetData expandedWidgetData: root.expandedWidgetData
bluetoothCodecSelector: root.bluetoothCodecSelector bluetoothCodecSelector: root.bluetoothCodecSelector
widgetModel: root.model
collapseCallback: root.requestCollapse
screenName: root.screenName
} }
} }
} }
Component {
id: errorPillComponent
ErrorPill {
property var widgetData: parent.widgetData || {}
width: parent.width
height: 60
primaryMessage: {
if (!DMSService.dmsAvailable) {
return I18n.tr("DMS_SOCKET not available")
}
return I18n.tr("NM not supported")
}
secondaryMessage: I18n.tr("update dms for NM integration.")
}
}
Component { Component {
id: compoundPillComponent id: compoundPillComponent
CompoundPill { CompoundPill {
@@ -233,6 +196,18 @@ Column {
return "bluetooth_disabled" return "bluetooth_disabled"
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
return "bluetooth_disabled" return "bluetooth_disabled"
const primaryDevice = (() => {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
return null
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
for (let device of devices) {
if (device && device.connected)
return device
}
return null
})()
if (primaryDevice)
return BluetoothService.getDeviceIcon(primaryDevice)
return "bluetooth" return "bluetooth"
} }
case "audioOutput": case "audioOutput":
@@ -332,10 +307,7 @@ Column {
return "Select device" return "Select device"
if (AudioService.sink.audio.muted) if (AudioService.sink.audio.muted)
return "Muted" return "Muted"
const volume = AudioService.sink.audio.volume return Math.round(AudioService.sink.audio.volume * 100) + "%"
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
} }
case "audioInput": case "audioInput":
{ {
@@ -343,10 +315,7 @@ Column {
return "Select device" return "Select device"
if (AudioService.source.audio.muted) if (AudioService.source.audio.muted)
return "Muted" return "Muted"
const volume = AudioService.source.audio.volume return Math.round(AudioService.source.audio.volume * 100) + "%"
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
} }
default: default:
return widgetDef?.description || "" return widgetDef?.description || ""
@@ -376,8 +345,7 @@ Column {
} }
enabled: widgetDef?.enabled ?? true enabled: widgetDef?.enabled ?? true
onToggled: { onToggled: {
if (root.editMode) if (root.editMode) return
return
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": case "wifi":
{ {
@@ -410,13 +378,11 @@ Column {
} }
} }
onExpandClicked: { onExpandClicked: {
if (root.editMode) if (root.editMode) return
return
root.expandClicked(widgetData, widgetIndex) root.expandClicked(widgetData, widgetIndex)
} }
onWheelEvent: function (wheelEvent) { onWheelEvent: function (wheelEvent) {
if (root.editMode) if (root.editMode) return
return
const id = widgetData.id || "" const id = widgetData.id || ""
if (id === "audioOutput") { if (id === "audioOutput") {
if (!AudioService.sink || !AudioService.sink.audio) if (!AudioService.sink || !AudioService.sink.audio)
@@ -475,21 +441,10 @@ Column {
height: 16 height: 16
BrightnessSliderRow { BrightnessSliderRow {
id: brightnessSliderRow
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width width: parent.width
height: 14 height: 14
deviceName: widgetData.deviceName || ""
instanceId: widgetData.instanceId || ""
screenName: root.screenName
parentScreen: root.parentScreen
property color sliderTrackColor: Theme.surfaceContainerHigh property color sliderTrackColor: Theme.surfaceContainerHigh
onIconClicked: {
if (!root.editMode && DisplayService.devices && DisplayService.devices.length > 1) {
root.expandClicked(widgetData, widgetIndex)
}
}
} }
} }
} }
@@ -582,10 +537,9 @@ Column {
} }
iconRotation: { iconRotation: {
if (widgetData.id !== "darkMode") if (widgetData.id !== "darkMode") return 0
return 0
if (darkModeTransitionPending) { if (darkModeTransitionPending) {
return SessionData.isLightMode ? 180 : 0 return SessionData.isLightMode ? 0 : 180
} }
return SessionData.isLightMode ? 180 : 0 return SessionData.isLightMode ? 180 : 0
} }
@@ -595,7 +549,7 @@ Column {
case "nightMode": case "nightMode":
return DisplayService.nightModeEnabled || false return DisplayService.nightModeEnabled || false
case "darkMode": case "darkMode":
return SessionData.isLightMode return !SessionData.isLightMode
case "doNotDisturb": case "doNotDisturb":
return SessionData.doNotDisturb || false return SessionData.doNotDisturb || false
case "idleInhibitor": case "idleInhibitor":
@@ -605,7 +559,15 @@ Column {
} }
} }
enabled: !root.editMode enabled: !root.editMode
onIconRotationCompleted: {
if (root.darkModeTransitionPending && widgetData.id === "darkMode") {
root.darkModeTransitionPending = false
Theme.screenTransition()
Theme.toggleLightMode()
}
}
onClicked: { onClicked: {
if (root.editMode) if (root.editMode)
@@ -619,9 +581,7 @@ Column {
} }
case "darkMode": case "darkMode":
{ {
const newMode = !SessionData.isLightMode root.darkModeTransitionPending = true
Theme.screenTransition()
Theme.setLightMode(newMode)
break break
} }
case "doNotDisturb": case "doNotDisturb":
@@ -663,10 +623,9 @@ Column {
} }
iconRotation: { iconRotation: {
if (widgetData.id !== "darkMode") if (widgetData.id !== "darkMode") return 0
return 0
if (darkModeTransitionPending) { if (darkModeTransitionPending) {
return SessionData.isLightMode ? 180 : 0 return SessionData.isLightMode ? 0 : 180
} }
return SessionData.isLightMode ? 180 : 0 return SessionData.isLightMode ? 180 : 0
} }
@@ -676,7 +635,7 @@ Column {
case "nightMode": case "nightMode":
return DisplayService.nightModeEnabled || false return DisplayService.nightModeEnabled || false
case "darkMode": case "darkMode":
return SessionData.isLightMode return !SessionData.isLightMode
case "doNotDisturb": case "doNotDisturb":
return SessionData.doNotDisturb || false return SessionData.doNotDisturb || false
case "idleInhibitor": case "idleInhibitor":
@@ -686,7 +645,15 @@ Column {
} }
} }
enabled: !root.editMode enabled: !root.editMode
onIconRotationCompleted: {
if (root.darkModeTransitionPending && widgetData.id === "darkMode") {
root.darkModeTransitionPending = false
Theme.screenTransition()
Theme.toggleLightMode()
}
}
onClicked: { onClicked: {
if (root.editMode) if (root.editMode)
@@ -700,9 +667,7 @@ Column {
} }
case "darkMode": case "darkMode":
{ {
const newMode = !SessionData.isLightMode root.darkModeTransitionPending = true
Theme.screenTransition()
Theme.setLightMode(newMode)
break break
} }
case "doNotDisturb": case "doNotDisturb":
@@ -750,251 +715,4 @@ Column {
colorPickerModal: root.colorPickerModal colorPickerModal: root.colorPickerModal
} }
} }
Component {
id: builtinPluginWidgetComponent
Loader {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property int widgetWidth: widgetData.width || 50
width: parent.width
height: 60
property var builtinInstance: null
Component.onCompleted: {
const id = widgetData.id || ""
if (id === "builtin_vpn") {
if (root.model?.vpnLoader) {
root.model.vpnLoader.active = true
}
builtinInstance = Qt.binding(() => root.model?.vpnBuiltinInstance)
}
}
sourceComponent: {
if (!builtinInstance)
return null
const hasDetail = builtinInstance.ccDetailContent !== null
if (widgetWidth <= 25) {
return builtinSmallToggleComponent
} else if (hasDetail) {
return builtinCompoundPillComponent
} else {
return builtinToggleComponent
}
}
}
}
Component {
id: builtinCompoundPillComponent
CompoundPill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
primaryText: builtinInstance?.ccWidgetPrimaryText || "Built-in"
secondaryText: builtinInstance?.ccWidgetSecondaryText || ""
isActive: builtinInstance?.ccWidgetIsActive || false
onToggled: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
onExpandClicked: {
if (root.editMode)
return
root.expandClicked(widgetData, widgetIndex)
}
}
}
Component {
id: builtinToggleComponent
ToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
text: builtinInstance?.ccWidgetPrimaryText || "Built-in"
isActive: builtinInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
}
}
Component {
id: builtinSmallToggleComponent
SmallToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
isActive: builtinInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
}
}
Component {
id: pluginWidgetComponent
Loader {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property int widgetWidth: widgetData.width || 50
width: parent.width
height: 60
property var pluginInstance: null
property string pluginId: widgetData.id?.replace("plugin_", "") || ""
sourceComponent: {
if (!pluginInstance)
return null
const hasDetail = pluginInstance.ccDetailContent !== null
if (widgetWidth <= 25) {
return pluginSmallToggleComponent
} else if (hasDetail) {
return pluginCompoundPillComponent
} else {
return pluginToggleComponent
}
}
Component.onCompleted: {
Qt.callLater(() => {
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
if (pluginComponent) {
const instance = pluginComponent.createObject(null, {
"pluginId": pluginId,
"pluginService": PluginService,
"visible": false,
"width": 0,
"height": 0
})
if (instance) {
pluginInstance = instance
}
}
})
}
Connections {
target: PluginService
function onPluginDataChanged(changedPluginId) {
if (changedPluginId === pluginId && pluginInstance) {
pluginInstance.loadPluginData()
}
}
}
Component.onDestruction: {
if (pluginInstance) {
pluginInstance.destroy()
}
}
}
}
Component {
id: pluginCompoundPillComponent
CompoundPill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var pluginInstance: parent.pluginInstance
iconName: pluginInstance?.ccWidgetIcon || "extension"
primaryText: pluginInstance?.ccWidgetPrimaryText || "Plugin"
secondaryText: pluginInstance?.ccWidgetSecondaryText || ""
isActive: pluginInstance?.ccWidgetIsActive || false
onToggled: {
if (root.editMode)
return
if (pluginInstance) {
pluginInstance.ccWidgetToggled()
}
}
onExpandClicked: {
if (root.editMode)
return
root.expandClicked(widgetData, widgetIndex)
}
}
}
Component {
id: pluginToggleComponent
ToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var pluginInstance: parent.pluginInstance
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
iconName: pluginInstance?.ccWidgetIcon || widgetDef?.icon || "extension"
text: pluginInstance?.ccWidgetPrimaryText || widgetDef?.text || "Plugin"
secondaryText: pluginInstance?.ccWidgetSecondaryText || ""
isActive: pluginInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (pluginInstance) {
pluginInstance.ccWidgetToggled()
}
}
}
}
Component {
id: pluginSmallToggleComponent
SmallToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var pluginInstance: parent.pluginInstance
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
iconName: pluginInstance?.ccWidgetIcon || widgetDef?.icon || "extension"
isActive: pluginInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (pluginInstance && pluginInstance.ccDetailContent) {
root.expandClicked(widgetData, widgetIndex)
} else if (pluginInstance) {
pluginInstance.ccWidgetToggled()
}
}
}
}
} }

View File

@@ -7,7 +7,6 @@ Row {
id: root id: root
property var availableWidgets: [] property var availableWidgets: []
property Item popoutContent: null
signal addWidget(string widgetId) signal addWidget(string widgetId)
signal resetToDefault() signal resetToDefault()
@@ -20,9 +19,7 @@ Row {
Popup { Popup {
id: addWidgetPopup id: addWidgetPopup
parent: popoutContent anchors.centerIn: parent
x: parent ? Math.round((parent.width - width) / 2) : 0
y: parent ? Math.round((parent.height - height) / 2) : 0
width: 400 width: 400
height: 300 height: 300
modal: true modal: true
@@ -55,7 +52,7 @@ Row {
} }
Typography { Typography {
text: I18n.tr("Add Widget") text: "Add Widget"
style: Typography.Style.Subtitle style: Typography.Style.Subtitle
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -69,7 +66,6 @@ Row {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
spacing: Theme.spacingS spacing: Theme.spacingS
clip: true
model: root.availableWidgets model: root.availableWidgets
delegate: Rectangle { delegate: Rectangle {
@@ -156,7 +152,7 @@ Row {
} }
Typography { Typography {
text: I18n.tr("Add Widget") text: "Add Widget"
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -190,7 +186,7 @@ Row {
} }
Typography { Typography {
text: I18n.tr("Defaults") text: "Defaults"
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.warning color: Theme.warning
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -224,7 +220,7 @@ Row {
} }
Typography { Typography {
text: I18n.tr("Reset") text: "Reset"
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.error color: Theme.error
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -6,12 +6,12 @@ import qs.Widgets
Rectangle { Rectangle {
id: root id: root
property bool powerOptionsExpanded: false
property bool editMode: false property bool editMode: false
signal powerButtonClicked() signal powerActionRequested(string action, string title, string message)
signal lockRequested() signal lockRequested()
signal editModeToggled() signal editModeToggled()
signal settingsButtonClicked()
implicitHeight: 70 implicitHeight: 70
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -83,11 +83,13 @@ Rectangle {
DankActionButton { DankActionButton {
buttonSize: 36 buttonSize: 36
iconName: "power_settings_new" iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: root.powerButtonClicked() onClicked: {
root.powerOptionsExpanded = !root.powerOptionsExpanded
}
} }
DankActionButton { DankActionButton {
@@ -97,7 +99,6 @@ Rectangle {
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: { onClicked: {
root.settingsButtonClicked()
settingsModal.show() settingsModal.show()
} }
} }

View File

@@ -0,0 +1,70 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property bool expanded: false
signal powerActionRequested(string action, string title, string message)
implicitHeight: expanded ? 60 : 0
height: implicitHeight
clip: true
Rectangle {
width: parent.width
height: 60
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: root.expanded ? 1 : 0
opacity: root.expanded ? 1 : 0
clip: true
Row {
anchors.centerIn: parent
spacing: SessionService.hibernateSupported ? Theme.spacingS : Theme.spacingL
visible: root.expanded
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "logout"
text: "Logout"
onPressed: root.powerActionRequested("logout", "Logout", "Are you sure you want to logout?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "restart_alt"
text: "Restart"
onPressed: root.powerActionRequested("reboot", "Restart", "Are you sure you want to restart?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "bedtime"
text: "Suspend"
onPressed: root.powerActionRequested("suspend", "Suspend", "Are you sure you want to suspend?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "ac_unit"
text: "Hibernate"
visible: SessionService.hibernateSupported
onPressed: root.powerActionRequested("hibernate", "Hibernate", "Are you sure you want to hibernate?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "power_settings_new"
text: "Shutdown"
onPressed: root.powerActionRequested("poweroff", "Shutdown", "Are you sure you want to shutdown?")
}
}
}
}

View File

@@ -32,7 +32,7 @@ Row {
text: modelData.toString() text: modelData.toString()
font.pixelSize: 8 font.pixelSize: 8
font.weight: Font.Medium font.weight: Font.Medium
color: modelData === root.currentSize ? Theme.primaryText : Theme.surfaceText color: modelData === root.currentSize ? Theme.primaryContainer : Theme.surfaceText
} }
MouseArea { MouseArea {

View File

@@ -21,11 +21,13 @@ DankPopout {
id: root id: root
property string expandedSection: "" property string expandedSection: ""
property bool powerOptionsExpanded: false
property var triggerScreen: null property var triggerScreen: null
property bool editMode: false property bool editMode: false
property int expandedWidgetIndex: -1 property int expandedWidgetIndex: -1
property var expandedWidgetData: null property var expandedWidgetData: null
signal powerActionRequested(string action, string title, string message)
signal lockRequested signal lockRequested
function collapseAll() { function collapseAll() {
@@ -73,21 +75,17 @@ DankPopout {
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (shouldBeVisible) { if (shouldBeVisible) {
Qt.callLater(() => { Qt.callLater(() => {
if (NetworkService.activeService) { NetworkService.autoRefreshEnabled = NetworkService.wifiEnabled
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled if (UserInfoService)
} UserInfoService.getUptime()
if (UserInfoService) })
UserInfoService.getUptime()
})
} else { } else {
Qt.callLater(() => { Qt.callLater(() => {
if (NetworkService.activeService) { NetworkService.autoRefreshEnabled = false
NetworkService.activeService.autoRefreshEnabled = false if (BluetoothService.adapter && BluetoothService.adapter.discovering)
} BluetoothService.adapter.discovering = false
if (BluetoothService.adapter && BluetoothService.adapter.discovering) editMode = false
BluetoothService.adapter.discovering = false })
editMode = false
})
} }
} }
@@ -108,7 +106,8 @@ DankPopout {
return Qt.rgba(surface.r, surface.g, surface.b, transparency) return Qt.rgba(surface.r, surface.g, surface.b, transparency)
} }
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)
border.width: 0 border.width: 0
antialiasing: true antialiasing: true
smooth: true smooth: true
@@ -123,24 +122,25 @@ DankPopout {
HeaderPane { HeaderPane {
id: headerPane id: headerPane
width: parent.width width: parent.width
powerOptionsExpanded: root.powerOptionsExpanded
editMode: root.editMode editMode: root.editMode
onPowerOptionsExpandedChanged: root.powerOptionsExpanded = powerOptionsExpanded
onEditModeToggled: root.editMode = !root.editMode onEditModeToggled: root.editMode = !root.editMode
onPowerButtonClicked: { onPowerActionRequested: (action, title, message) => root.powerActionRequested(action, title, message)
if (powerMenuModalLoader) {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item) {
const popoutPos = controlContent.mapToItem(null, 0, 0)
const bounds = Qt.rect(popoutPos.x, popoutPos.y, controlContent.width, controlContent.height)
powerMenuModalLoader.item.openFromControlCenter(bounds, root.triggerScreen)
}
}
}
onLockRequested: { onLockRequested: {
root.close() root.close()
root.lockRequested() root.lockRequested()
} }
onSettingsButtonClicked: { }
PowerOptionsPane {
id: powerOptionsPane
width: parent.width
expanded: root.powerOptionsExpanded
onPowerActionRequested: (action, title, message) => {
root.powerOptionsExpanded = false
root.close() root.close()
root.powerActionRequested(action, title, message)
} }
} }
@@ -154,37 +154,28 @@ DankPopout {
model: widgetModel model: widgetModel
bluetoothCodecSelector: bluetoothCodecSelector bluetoothCodecSelector: bluetoothCodecSelector
colorPickerModal: root.colorPickerModal colorPickerModal: root.colorPickerModal
screenName: root.triggerScreen?.name || ""
parentScreen: root.triggerScreen
onExpandClicked: (widgetData, globalIndex) => { onExpandClicked: (widgetData, globalIndex) => {
root.expandedWidgetIndex = globalIndex root.expandedWidgetIndex = globalIndex
root.expandedWidgetData = widgetData root.expandedWidgetData = widgetData
if (widgetData.id === "diskUsage") { if (widgetData.id === "diskUsage") {
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default")) root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"))
} else if (widgetData.id === "brightnessSlider") { } else {
root.toggleSection("brightnessSlider_" + (widgetData.instanceId || "default")) root.toggleSection(widgetData.id)
} else { }
root.toggleSection(widgetData.id) }
} onRemoveWidget: (index) => widgetModel.removeWidget(index)
}
onRemoveWidget: index => widgetModel.removeWidget(index)
onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex) onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex)
onToggleWidgetSize: index => widgetModel.toggleWidgetSize(index) onToggleWidgetSize: (index) => widgetModel.toggleWidgetSize(index)
onCollapseRequested: root.collapseAll()
} }
EditControls { EditControls {
width: parent.width width: parent.width
visible: editMode visible: editMode
popoutContent: controlContent
availableWidgets: { availableWidgets: {
if (!editMode)
return []
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id) const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets()) return widgetModel.baseWidgetDefinitions.filter(w => w.allowMultiple || !existingIds.includes(w.id))
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id))
} }
onAddWidget: widgetId => widgetModel.addWidget(widgetId) onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
onResetToDefault: () => widgetModel.resetToDefault() onResetToDefault: () => widgetModel.resetToDefault()
onClearAll: () => widgetModel.clearAll() onClearAll: () => widgetModel.clearAll()
} }
@@ -207,10 +198,10 @@ DankPopout {
id: bluetoothDetailComponent id: bluetoothDetailComponent
BluetoothDetail { BluetoothDetail {
id: bluetoothDetail id: bluetoothDetail
onShowCodecSelector: function (device) { onShowCodecSelector: function(device) {
if (contentLoader.item && contentLoader.item.bluetoothCodecSelector) { if (contentLoader.item && contentLoader.item.bluetoothCodecSelector) {
contentLoader.item.bluetoothCodecSelector.show(device) contentLoader.item.bluetoothCodecSelector.show(device)
contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function (deviceAddress, codecName) { contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName) bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
}) })
} }
@@ -234,5 +225,4 @@ DankPopout {
} }
property var colorPickerModal: null property var colorPickerModal: null
property var powerMenuModalLoader: null }
}

View File

@@ -17,7 +17,7 @@ Rectangle {
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
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)
border.width: 0 border.width: 0
Row { Row {
id: headerRow id: headerRow
anchors.left: parent.left anchors.left: parent.left
@@ -27,17 +27,17 @@ Rectangle {
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingS anchors.topMargin: Theme.spacingS
height: 40 height: 40
StyledText { StyledText {
id: headerText id: headerText
text: I18n.tr("Input Devices") text: "Input Devices"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Row { Row {
id: volumeSlider id: volumeSlider
anchors.left: parent.left anchors.left: parent.left
@@ -105,7 +105,7 @@ Rectangle {
} }
} }
} }
DankFlickable { DankFlickable {
id: audioContent id: audioContent
anchors.top: hasInputVolumeSliderInCC ? headerRow.bottom : volumeSlider.bottom anchors.top: hasInputVolumeSliderInCC ? headerRow.bottom : volumeSlider.bottom
@@ -116,34 +116,34 @@ Rectangle {
anchors.topMargin: hasInputVolumeSliderInCC ? Theme.spacingM : Theme.spacingS anchors.topMargin: hasInputVolumeSliderInCC ? Theme.spacingM : Theme.spacingS
contentHeight: audioColumn.height contentHeight: audioColumn.height
clip: true clip: true
Column { Column {
id: audioColumn id: audioColumn
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: Pipewire.nodes.values.filter(node => { model: Pipewire.nodes.values.filter(node => {
return node.audio && !node.isSink && !node.isStream return node.audio && !node.isSink && !node.isStream
}) })
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
required property int index required property int index
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: { name: {
if (modelData.name.includes("bluez")) if (modelData.name.includes("bluez"))
@@ -157,11 +157,11 @@ Rectangle {
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: AudioService.displayName(modelData) text: AudioService.displayName(modelData)
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -171,7 +171,7 @@ Rectangle {
width: parent.width width: parent.width
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
StyledText { StyledText {
text: modelData === AudioService.source ? "Active" : "Available" text: modelData === AudioService.source ? "Active" : "Available"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -182,7 +182,7 @@ Rectangle {
} }
} }
} }
MouseArea { MouseArea {
id: deviceMouseArea id: deviceMouseArea
anchors.fill: parent anchors.fill: parent

View File

@@ -17,7 +17,7 @@ Rectangle {
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
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)
border.width: 0 border.width: 0
Row { Row {
id: headerRow id: headerRow
anchors.left: parent.left anchors.left: parent.left
@@ -27,10 +27,10 @@ Rectangle {
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingS anchors.topMargin: Theme.spacingS
height: 40 height: 40
StyledText { StyledText {
id: headerText id: headerText
text: I18n.tr("Audio Devices") text: "Audio Devices"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -121,34 +121,34 @@ Rectangle {
anchors.topMargin: volumeSlider.visible ? Theme.spacingS : Theme.spacingM anchors.topMargin: volumeSlider.visible ? Theme.spacingS : Theme.spacingM
contentHeight: audioColumn.height contentHeight: audioColumn.height
clip: true clip: true
Column { Column {
id: audioColumn id: audioColumn
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: Pipewire.nodes.values.filter(node => { model: Pipewire.nodes.values.filter(node => {
return node.audio && node.isSink && !node.isStream return node.audio && node.isSink && !node.isStream
}) })
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
required property int index required property int index
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: { name: {
if (modelData.name.includes("bluez")) if (modelData.name.includes("bluez"))
@@ -164,11 +164,11 @@ Rectangle {
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: AudioService.displayName(modelData) text: AudioService.displayName(modelData)
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -178,7 +178,7 @@ Rectangle {
width: parent.width width: parent.width
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
StyledText { StyledText {
text: modelData === AudioService.sink ? "Active" : "Available" text: modelData === AudioService.sink ? "Active" : "Available"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -189,7 +189,7 @@ Rectangle {
} }
} }
} }
MouseArea { MouseArea {
id: deviceMouseArea id: deviceMouseArea
anchors.fill: parent anchors.fill: parent

View File

@@ -133,7 +133,7 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Health") text: "Health"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -168,7 +168,7 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Capacity") text: "Capacity"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -237,7 +237,7 @@ Rectangle {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: I18n.tr("Power Profile Degradation") text: "Power Profile Degradation"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.error color: Theme.error
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -83,12 +83,12 @@ Item {
hoverEnabled: true hoverEnabled: true
preventStealing: true preventStealing: true
propagateComposedEvents: false propagateComposedEvents: false
onClicked: root.hide() onClicked: root.hide()
onWheel: (wheel) => { wheel.accepted = true } onWheel: (wheel) => { wheel.accepted = true }
onPositionChanged: (mouse) => { mouse.accepted = true } onPositionChanged: (mouse) => { mouse.accepted = true }
} }
Rectangle { Rectangle {
id: modalBackground id: modalBackground
anchors.fill: parent anchors.fill: parent
@@ -170,7 +170,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("Audio Codec Selection") text: "Audio Codec Selection"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
} }

View File

@@ -5,45 +5,18 @@ import Quickshell.Bluetooth
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modals
Rectangle { Rectangle {
id: root
implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
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)
border.width: 0 border.width: 0
property var bluetoothCodecModalRef: null property var bluetoothCodecModalRef: null
property var devicesBeingPaired: new Set()
signal showCodecSelector(var device) signal showCodecSelector(var device)
function isDeviceBeingPaired(deviceAddress) {
return devicesBeingPaired.has(deviceAddress)
}
function handlePairDevice(device) {
if (!device) return
const deviceAddr = device.address
devicesBeingPaired.add(deviceAddr)
devicesBeingPairedChanged()
BluetoothService.pairDevice(device, function(response) {
devicesBeingPaired.delete(deviceAddr)
devicesBeingPairedChanged()
if (response.error) {
ToastService.showError(I18n.tr("Pairing failed"), response.error)
} else if (!BluetoothService.enhancedPairingAvailable) {
ToastService.showSuccess(I18n.tr("Device paired"))
}
})
}
function updateDeviceCodecDisplay(deviceAddress, codecName) { function updateDeviceCodecDisplay(deviceAddress, codecName) {
for (let i = 0; i < pairedRepeater.count; i++) { for (let i = 0; i < pairedRepeater.count; i++) {
let item = pairedRepeater.itemAt(i) let item = pairedRepeater.itemAt(i)
@@ -53,7 +26,7 @@ Rectangle {
} }
} }
} }
Row { Row {
id: headerRow id: headerRow
anchors.left: parent.left anchors.left: parent.left
@@ -63,21 +36,21 @@ Rectangle {
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingS anchors.topMargin: Theme.spacingS
height: 40 height: 40
StyledText { StyledText {
id: headerText id: headerText
text: I18n.tr("Bluetooth Settings") text: "Bluetooth Settings"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Item { Item {
width: Math.max(0, parent.width - headerText.implicitWidth - scanButton.width - Theme.spacingM) width: Math.max(0, parent.width - headerText.implicitWidth - scanButton.width - Theme.spacingM)
height: parent.height height: parent.height
} }
Rectangle { Rectangle {
id: scanButton id: scanButton
width: 100 width: 100
@@ -91,18 +64,18 @@ Rectangle {
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0 border.width: 0
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankIcon { DankIcon {
name: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching" name: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
size: 18 size: 18
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Scanning" : "Scan" text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Scanning" : "Scan"
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
@@ -111,7 +84,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: scanMouseArea id: scanMouseArea
anchors.fill: parent anchors.fill: parent
@@ -125,7 +98,7 @@ Rectangle {
} }
} }
} }
DankFlickable { DankFlickable {
id: bluetoothContent id: bluetoothContent
anchors.top: headerRow.bottom anchors.top: headerRow.bottom
@@ -137,19 +110,19 @@ Rectangle {
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
contentHeight: bluetoothColumn.height contentHeight: bluetoothColumn.height
clip: true clip: true
Column { Column {
id: bluetoothColumn id: bluetoothColumn
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
id: pairedRepeater id: pairedRepeater
model: { model: {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
return [] return []
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))] let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
devices.sort((a, b) => { devices.sort((a, b) => {
if (a.connected && !b.connected) return -1 if (a.connected && !b.connected) return -1
@@ -158,17 +131,17 @@ Rectangle {
}) })
return devices return devices
} }
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
required property int index required property int index
property string currentCodec: BluetoothService.deviceCodecs[modelData.address] || "" property string currentCodec: BluetoothService.deviceCodecs[modelData.address] || ""
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
Component.onCompleted: { Component.onCompleted: {
if (modelData.connected && BluetoothService.isAudioDevice(modelData)) { if (modelData.connected && BluetoothService.isAudioDevice(modelData)) {
BluetoothService.refreshDeviceCodec(modelData) BluetoothService.refreshDeviceCodec(modelData)
@@ -189,13 +162,13 @@ Rectangle {
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
} }
border.width: 0 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: BluetoothService.getDeviceIcon(modelData) name: BluetoothService.getDeviceIcon(modelData)
size: Theme.iconSize - 4 size: Theme.iconSize - 4
@@ -208,11 +181,11 @@ Rectangle {
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 200 width: 200
StyledText { StyledText {
text: modelData.name || modelData.deviceName || "Unknown Device" text: modelData.name || modelData.deviceName || "Unknown Device"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -221,10 +194,10 @@ Rectangle {
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: { text: {
if (modelData.state === BluetoothDeviceState.Connecting) if (modelData.state === BluetoothDeviceState.Connecting)
@@ -245,12 +218,12 @@ Rectangle {
return Theme.surfaceVariantText return Theme.surfaceVariantText
} }
} }
StyledText { StyledText {
text: { text: {
if (modelData.batteryAvailable && modelData.battery > 0) if (modelData.batteryAvailable && modelData.battery > 0)
return "• " + Math.round(modelData.battery * 100) + "%" return "• " + Math.round(modelData.battery * 100) + "%"
var btBattery = BatteryService.bluetoothDevices.find(dev => { var btBattery = BatteryService.bluetoothDevices.find(dev => {
return dev.name === (modelData.name || modelData.deviceName) || return dev.name === (modelData.name || modelData.deviceName) ||
dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) || dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) ||
@@ -262,7 +235,7 @@ Rectangle {
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: text.length > 0 visible: text.length > 0
} }
StyledText { StyledText {
text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : "" text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -272,7 +245,7 @@ Rectangle {
} }
} }
} }
DankActionButton { DankActionButton {
id: pairedOptionsButton id: pairedOptionsButton
anchors.right: parent.right anchors.right: parent.right
@@ -289,7 +262,7 @@ Rectangle {
} }
} }
} }
MouseArea { MouseArea {
id: deviceMouseArea id: deviceMouseArea
anchors.fill: parent anchors.fill: parent
@@ -306,26 +279,26 @@ Rectangle {
} }
} }
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
visible: pairedRepeater.count > 0 && availableRepeater.count > 0 visible: pairedRepeater.count > 0 && availableRepeater.count > 0
} }
Item { Item {
width: parent.width width: parent.width
height: 80 height: 80
visible: BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0 visible: BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: "sync" name: "sync"
size: 24 size: 24
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.4) color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.4)
RotationAnimation on rotation { RotationAnimation on rotation {
running: parent.visible && BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0 running: parent.visible && BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0
loops: Animation.Infinite loops: Animation.Infinite
@@ -335,52 +308,52 @@ Rectangle {
} }
} }
} }
Repeater { Repeater {
id: availableRepeater id: availableRepeater
model: { model: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
return [] return []
var filtered = Bluetooth.devices.values.filter(dev => { var filtered = Bluetooth.devices.values.filter(dev => {
return dev && !dev.paired && !dev.pairing && !dev.blocked && return dev && !dev.paired && !dev.pairing && !dev.blocked &&
(dev.signalStrength === undefined || dev.signalStrength > 0) (dev.signalStrength === undefined || dev.signalStrength > 0)
}) })
return BluetoothService.sortDevices(filtered) return BluetoothService.sortDevices(filtered)
} }
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
required property int index required property int index
property bool canConnect: BluetoothService.canConnect(modelData) property bool canConnect: BluetoothService.canConnect(modelData)
property bool isBusy: BluetoothService.isDeviceBusy(modelData) || isDeviceBeingPaired(modelData.address) property bool isBusy: BluetoothService.isDeviceBusy(modelData)
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0 border.width: 0
opacity: (canConnect && !isBusy) ? 1 : 0.6 opacity: canConnect ? 1 : 0.6
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: BluetoothService.getDeviceIcon(modelData) name: BluetoothService.getDeviceIcon(modelData)
size: Theme.iconSize - 4 size: Theme.iconSize - 4
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 200 width: 200
StyledText { StyledText {
text: modelData.name || modelData.deviceName || "Unknown Device" text: modelData.name || modelData.deviceName || "Unknown Device"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -388,20 +361,20 @@ Rectangle {
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: { text: {
if (modelData.pairing || isBusy) return "Pairing..." if (modelData.pairing) return "Pairing..."
if (modelData.blocked) return "Blocked" if (modelData.blocked) return "Blocked"
return BluetoothService.getSignalStrength(modelData) return BluetoothService.getSignalStrength(modelData)
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
StyledText { StyledText {
text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : "" text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -411,21 +384,21 @@ Rectangle {
} }
} }
} }
StyledText { StyledText {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { text: {
if (isBusy) return "Pairing..." if (modelData.pairing) return "Pairing..."
if (!canConnect) return "Cannot pair" if (!canConnect) return "Cannot pair"
return "Pair" return "Pair"
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: (canConnect && !isBusy) ? Theme.primary : Theme.surfaceVariantText color: canConnect ? Theme.primary : Theme.surfaceVariantText
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: availableMouseArea id: availableMouseArea
anchors.fill: parent anchors.fill: parent
@@ -433,46 +406,48 @@ Rectangle {
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: canConnect && !isBusy enabled: canConnect && !isBusy
onClicked: { onClicked: {
root.handlePairDevice(modelData) if (modelData) {
BluetoothService.connectDeviceWithTrust(modelData)
}
} }
} }
} }
} }
Item { Item {
width: parent.width width: parent.width
height: 60 height: 60
visible: !BluetoothService.adapter visible: !BluetoothService.adapter
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("No Bluetooth adapter found") text: "No Bluetooth adapter found"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
} }
} }
} }
Menu { Menu {
id: bluetoothContextMenu id: bluetoothContextMenu
width: 150 width: 150
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
property var currentDevice: null property var currentDevice: null
background: Rectangle { background: Rectangle {
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 0 border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
} }
MenuItem { MenuItem {
text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? "Disconnect" : "Connect" text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? "Disconnect" : "Connect"
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {
text: parent.text text: parent.text
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -480,12 +455,12 @@ Rectangle {
leftPadding: Theme.spacingS leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: Rectangle { background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2 radius: Theme.cornerRadius / 2
} }
onTriggered: { onTriggered: {
if (bluetoothContextMenu.currentDevice) { if (bluetoothContextMenu.currentDevice) {
if (bluetoothContextMenu.currentDevice.connected) { if (bluetoothContextMenu.currentDevice.connected) {
@@ -496,12 +471,12 @@ Rectangle {
} }
} }
} }
MenuItem { MenuItem {
text: I18n.tr("Audio Codec") text: "Audio Codec"
height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0 height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0
visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected
contentItem: StyledText { contentItem: StyledText {
text: parent.text text: parent.text
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -509,23 +484,23 @@ Rectangle {
leftPadding: Theme.spacingS leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: Rectangle { background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2 radius: Theme.cornerRadius / 2
} }
onTriggered: { onTriggered: {
if (bluetoothContextMenu.currentDevice) { if (bluetoothContextMenu.currentDevice) {
showCodecSelector(bluetoothContextMenu.currentDevice) showCodecSelector(bluetoothContextMenu.currentDevice)
} }
} }
} }
MenuItem { MenuItem {
text: I18n.tr("Forget Device") text: "Forget Device"
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {
text: parent.text text: parent.text
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -533,38 +508,18 @@ Rectangle {
leftPadding: Theme.spacingS leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: Rectangle { background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent" color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2 radius: Theme.cornerRadius / 2
} }
onTriggered: { onTriggered: {
if (bluetoothContextMenu.currentDevice) { if (bluetoothContextMenu.currentDevice) {
if (BluetoothService.enhancedPairingAvailable) { bluetoothContextMenu.currentDevice.forget()
const devicePath = BluetoothService.getDevicePath(bluetoothContextMenu.currentDevice)
DMSService.bluetoothRemove(devicePath, response => {
if (response.error) {
ToastService.showError(I18n.tr("Failed to remove device"), response.error)
}
})
} else {
bluetoothContextMenu.currentDevice.forget()
}
} }
} }
} }
} }
BluetoothPairingModal {
id: bluetoothPairingModal
}
Connections {
target: DMSService
function onBluetoothPairingRequest(data) {
bluetoothPairingModal.show(data)
}
}
} }

View File

@@ -1,307 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
property string initialDeviceName: ""
property string instanceId: ""
property string screenName: ""
signal deviceNameChanged(string newDeviceName)
property string currentDeviceName: ""
function resolveDeviceName() {
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
return ""
}
if (screenName && screenName.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}
const pinnedDevice = pins[screenName]
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
if (found) {
return found.name
}
}
}
if (initialDeviceName && initialDeviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName)
if (found) {
return found.name
}
}
const currentDeviceNameFromService = DisplayService.currentDevice
if (currentDeviceNameFromService) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService)
if (found) {
return found.name
}
}
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
}
Component.onCompleted: {
currentDeviceName = resolveDeviceName()
}
property bool isPinnedToScreen: {
if (!screenName || screenName.length === 0) {
return false
}
const pins = SettingsData.brightnessDevicePins || {}
return pins[screenName] === currentDeviceName
}
function togglePinToScreen() {
if (!screenName || screenName.length === 0 || !currentDeviceName || currentDeviceName.length === 0) {
return
}
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
if (isPinnedToScreen) {
delete pins[screenName]
} else {
pins[screenName] = currentDeviceName
}
SettingsData.setBrightnessDevicePins(pins)
}
implicitHeight: brightnessContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
DankFlickable {
id: brightnessContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
contentHeight: brightnessColumn.height
clip: true
Column {
id: brightnessColumn
width: parent.width
spacing: Theme.spacingS
Item {
width: parent.width
height: 100
visible: !DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingM
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: DisplayService.brightnessAvailable ? "brightness_6" : "error"
size: 32
color: DisplayService.brightnessAvailable ? Theme.primary : Theme.error
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: DisplayService.brightnessAvailable ? "No brightness devices available" : "Brightness control not available"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
}
}
}
Rectangle {
width: parent.width
height: 40
visible: screenName && screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
Item {
anchors.fill: parent
anchors.margins: Theme.spacingM
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "monitor"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: screenName || "Unknown Monitor"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: pinRow.width + Theme.spacingS * 2
height: 28
radius: height / 2
color: isPinnedToScreen ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
Row {
id: pinRow
anchors.centerIn: parent
spacing: 4
DankIcon {
name: isPinnedToScreen ? "push_pin" : "push_pin"
size: 16
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: isPinnedToScreen ? "Pinned" : "Pin"
font.pixelSize: Theme.fontSizeSmall
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.togglePinToScreen()
}
}
}
}
Repeater {
model: DisplayService.devices || []
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 80
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
border.color: modelData.name === currentDeviceName ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.name === currentDeviceName ? 2 : 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingM
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
DankIcon {
name: {
const deviceClass = modelData.class || ""
const deviceName = modelData.name || ""
if (deviceClass === "backlight" || deviceClass === "ddc") {
const brightness = DisplayService.getDeviceBrightness(modelData.name)
if (brightness <= 33) return "brightness_low"
if (brightness <= 66) return "brightness_medium"
return "brightness_high"
} else if (deviceName.includes("kbd")) {
return "keyboard"
} else {
return "lightbulb"
}
}
size: Theme.iconSize
color: modelData.name === currentDeviceName ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: Math.round(DisplayService.getDeviceBrightness(modelData.name)) + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
StyledText {
text: {
const name = modelData.name || ""
const deviceClass = modelData.class || ""
if (deviceClass === "backlight") {
return name.replace("_", " ").replace(/\b\w/g, c => c.toUpperCase())
}
return name
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: {
const deviceClass = modelData.class || ""
if (deviceClass === "backlight") return "Backlight device"
if (deviceClass === "ddc") return "DDC/CI monitor"
if (deviceClass === "leds") return "LED device"
return deviceClass
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
currentDeviceName = modelData.name
deviceNameChanged(modelData.name)
}
}
}
}
}
}
}

View File

@@ -20,39 +20,18 @@ Rectangle {
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
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)
border.width: 0 border.width: 0
Component.onCompleted: { Component.onCompleted: {
NetworkService.addRef() NetworkService.addRef()
if (NetworkService.wifiEnabled) {
NetworkService.scanWifi()
}
} }
Component.onDestruction: { Component.onDestruction: {
NetworkService.removeRef() NetworkService.removeRef()
} }
property int currentPreferenceIndex: {
if (DMSService.apiVersion < 5) {
return 1
}
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
return 1
}
const pref = NetworkService.userPreference
const status = NetworkService.networkStatus
let index = 1
if (pref === "ethernet") {
index = 0
} else if (pref === "wifi") {
index = 1
} else {
index = status === "ethernet" ? 0 : 1
}
return index
}
Row { Row {
id: headerRow id: headerRow
anchors.left: parent.left anchors.left: parent.left
@@ -62,37 +41,38 @@ Rectangle {
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingS anchors.topMargin: Theme.spacingS
height: 40 height: 40
StyledText { StyledText {
id: headerText id: headerText
text: I18n.tr("Network Settings") text: "Network Settings"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Item { Item {
width: Math.max(0, parent.width - headerText.implicitWidth - preferenceControls.width - Theme.spacingM) width: Math.max(0, parent.width - headerText.implicitWidth - preferenceControls.width - Theme.spacingM)
height: parent.height height: parent.height
} }
DankButtonGroup { DankButtonGroup {
id: preferenceControls id: preferenceControls
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10 visible: NetworkService.ethernetConnected && NetworkService.wifiConnected
property int currentPreferenceIndex: NetworkService.userPreference === "ethernet" ? 0 : 1
model: ["Ethernet", "WiFi"] model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex currentIndex: currentPreferenceIndex
selectionMode: "single" selectionMode: "single"
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (!selected) return if (!selected) return
console.log("NetworkDetail: Setting preference to", index === 0 ? "ethernet" : "wifi")
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi") NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi")
} }
} }
} }
Item { Item {
id: wifiToggleContent id: wifiToggleContent
anchors.top: headerRow.bottom anchors.top: headerRow.bottom
@@ -100,19 +80,19 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM anchors.topMargin: Theme.spacingM
visible: currentPreferenceIndex === 1 && NetworkService.wifiToggling visible: NetworkService.wifiToggling
height: visible ? 80 : 0 height: visible ? 80 : 0
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingM spacing: Theme.spacingM
DankIcon { DankIcon {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
name: "sync" name: "sync"
size: 32 size: 32
color: Theme.primary color: Theme.primary
RotationAnimation on rotation { RotationAnimation on rotation {
running: NetworkService.wifiToggling running: NetworkService.wifiToggling
loops: Animation.Infinite loops: Animation.Infinite
@@ -121,7 +101,7 @@ Rectangle {
duration: 1000 duration: 1000
} }
} }
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..." text: NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
@@ -131,7 +111,7 @@ Rectangle {
} }
} }
} }
Item { Item {
id: wifiOffContent id: wifiOffContent
anchors.top: headerRow.bottom anchors.top: headerRow.bottom
@@ -139,30 +119,30 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM anchors.topMargin: Theme.spacingM
visible: currentPreferenceIndex === 1 && !NetworkService.wifiEnabled && !NetworkService.wifiToggling visible: !NetworkService.wifiEnabled && !NetworkService.wifiToggling
height: visible ? 120 : 0 height: visible ? 120 : 0
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingL spacing: Theme.spacingL
width: parent.width width: parent.width
DankIcon { DankIcon {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
name: "wifi_off" name: "wifi_off"
size: 48 size: 48
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
} }
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: I18n.tr("WiFi is off") text: "WiFi is off"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
Rectangle { Rectangle {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: 120 width: 120
@@ -171,15 +151,15 @@ Rectangle {
color: enableWifiButton.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) color: enableWifiButton.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
border.width: 0 border.width: 0
border.color: Theme.primary border.color: Theme.primary
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Enable WiFi") text: "Enable WiFi"
color: Theme.primary color: Theme.primary
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: enableWifiButton id: enableWifiButton
anchors.fill: parent anchors.fill: parent
@@ -187,179 +167,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: NetworkService.toggleWifiRadio() onClicked: NetworkService.toggleWifiRadio()
} }
}
}
}
DankFlickable {
id: wiredContent
anchors.top: headerRow.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
visible: currentPreferenceIndex === 0 && NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
contentHeight: wiredColumn.height
clip: true
Column {
id: wiredColumn
width: parent.width
spacing: Theme.spacingS
Repeater {
model: sortedNetworks
property var sortedNetworks: {
const currentUuid = NetworkService.ethernetConnectionUuid
const networks = NetworkService.wiredConnections
let sorted = [...networks]
sorted.sort((a, b) => {
if (a.isActive && !b.isActive) return -1
if (!a.isActive && b.isActive) return 1
return a.id.localeCompare(b.id)
})
return sorted
}
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 50
radius: Theme.cornerRadius
color: wiredNetworkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: Theme.primary
border.width: 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: "lan"
size: Theme.iconSize - 4
color: modelData.isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: 200
StyledText {
text: modelData.id || "Unknown Config"
font.pixelSize: Theme.fontSizeMedium
color: modelData.isActive ? Theme.primary : Theme.surfaceText
font.weight: modelData.isActive ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
}
}
}
DankActionButton {
id: wiredOptionsButton
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
iconName: "more_horiz"
buttonSize: 28
onClicked: {
if (wiredNetworkContextMenu.visible) {
wiredNetworkContextMenu.close()
} else {
wiredNetworkContextMenu.currentID = modelData.id
wiredNetworkContextMenu.currentUUID = modelData.uuid
wiredNetworkContextMenu.currentConnected = modelData.isActive
wiredNetworkContextMenu.popup(wiredOptionsButton, -wiredNetworkContextMenu.width + wiredOptionsButton.width, wiredOptionsButton.height + Theme.spacingXS)
}
}
}
MouseArea {
id: wiredNetworkMouseArea
anchors.fill: parent
anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: function(event) {
if (modelData.uuid !== NetworkService.ethernetConnectionUuid) {
NetworkService.connectToSpecificWiredConfig(modelData.uuid)
}
event.accepted = true
}
}
}
}
}
}
Menu {
id: wiredNetworkContextMenu
width: 150
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
property string currentID: ""
property string currentUUID: ""
property bool currentConnected: false
background: Rectangle {
color: Theme.popupBackground()
radius: Theme.cornerRadius
border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
MenuItem {
text: "Activate"
height: !wiredNetworkContextMenu.currentConnected ? 32 : 0
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2
}
onTriggered: {
if (!networkContextMenu.currentConnected) {
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID)
}
}
}
MenuItem {
text: I18n.tr("Network Info")
height: wiredNetworkContextMenu.currentConnected ? 32 : 0
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2
}
onTriggered: {
let networkData = NetworkService.getWiredNetworkInfo(wiredNetworkContextMenu.currentUUID)
networkWiredInfoModal.showNetworkInfo(wiredNetworkContextMenu.currentID, networkData)
} }
} }
} }
@@ -372,28 +180,28 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM anchors.topMargin: Theme.spacingM
visible: currentPreferenceIndex === 1 && NetworkService.wifiEnabled && !NetworkService.wifiToggling visible: NetworkService.wifiInterface && NetworkService.wifiEnabled && !NetworkService.wifiToggling
contentHeight: wifiColumn.height contentHeight: wifiColumn.height
clip: true clip: true
Column { Column {
id: wifiColumn id: wifiColumn
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Item { Item {
width: parent.width width: parent.width
height: 200 height: 200
visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling && NetworkService.isScanning visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: "refresh" name: "refresh"
size: 48 size: 48
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3) color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3)
RotationAnimation on rotation { RotationAnimation on rotation {
running: NetworkService.isScanning running: true
loops: Animation.Infinite loops: Animation.Infinite
from: 0 from: 0
to: 360 to: 360
@@ -401,38 +209,34 @@ Rectangle {
} }
} }
} }
Repeater { Repeater {
model: sortedNetworks model: {
let networks = [...NetworkService.wifiNetworks]
property var sortedNetworks: { networks.sort((a, b) => {
const ssid = NetworkService.currentWifiSSID if (a.ssid === NetworkService.currentWifiSSID) return -1
const networks = NetworkService.wifiNetworks if (b.ssid === NetworkService.currentWifiSSID) return 1
let sorted = [...networks]
sorted.sort((a, b) => {
if (a.ssid === ssid) return -1
if (b.ssid === ssid) return 1
return b.signal - a.signal return b.signal - a.signal
}) })
return sorted return networks
} }
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
required property int index required property int index
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: { name: {
let strength = modelData.signal || 0 let strength = modelData.signal || 0
@@ -444,11 +248,11 @@ Rectangle {
color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 200 width: 200
StyledText { StyledText {
text: modelData.ssid || "Unknown Network" text: modelData.ssid || "Unknown Network"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -457,32 +261,32 @@ Rectangle {
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected" : (modelData.secured ? "Secured" : "Open") text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected" : (modelData.secured ? "Secured" : "Open")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
StyledText { StyledText {
text: modelData.saved ? "Saved" : "" text: modelData.saved ? "Saved" : ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
visible: text.length > 0 visible: text.length > 0
} }
StyledText { StyledText {
text: (modelData.saved ? "• " : "") + modelData.signal + "%" text: "• " + modelData.signal + "%"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
} }
} }
} }
DankActionButton { DankActionButton {
id: optionsButton id: optionsButton
anchors.right: parent.right anchors.right: parent.right
@@ -503,7 +307,7 @@ Rectangle {
} }
} }
} }
MouseArea { MouseArea {
id: networkMouseArea id: networkMouseArea
anchors.fill: parent anchors.fill: parent
@@ -513,11 +317,7 @@ Rectangle {
onClicked: function(event) { onClicked: function(event) {
if (modelData.ssid !== NetworkService.currentWifiSSID) { if (modelData.ssid !== NetworkService.currentWifiSSID) {
if (modelData.secured && !modelData.saved) { if (modelData.secured && !modelData.saved) {
if (DMSService.apiVersion >= 7) { wifiPasswordModal.show(modelData.ssid)
NetworkService.connectToWifi(modelData.ssid)
} else if (PopoutService.wifiPasswordModal) {
PopoutService.wifiPasswordModal.show(modelData.ssid)
}
} else { } else {
NetworkService.connectToWifi(modelData.ssid) NetworkService.connectToWifi(modelData.ssid)
} }
@@ -525,34 +325,34 @@ Rectangle {
event.accepted = true event.accepted = true
} }
} }
} }
} }
} }
} }
Menu { Menu {
id: networkContextMenu id: networkContextMenu
width: 150 width: 150
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
property string currentSSID: "" property string currentSSID: ""
property bool currentSecured: false property bool currentSecured: false
property bool currentConnected: false property bool currentConnected: false
property bool currentSaved: false property bool currentSaved: false
property int currentSignal: 0 property int currentSignal: 0
background: Rectangle { background: Rectangle {
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 0 border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
} }
MenuItem { MenuItem {
text: networkContextMenu.currentConnected ? "Disconnect" : "Connect" text: networkContextMenu.currentConnected ? "Disconnect" : "Connect"
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {
text: parent.text text: parent.text
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -560,33 +360,29 @@ Rectangle {
leftPadding: Theme.spacingS leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: Rectangle { background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2 radius: Theme.cornerRadius / 2
} }
onTriggered: { onTriggered: {
if (networkContextMenu.currentConnected) { if (networkContextMenu.currentConnected) {
NetworkService.disconnectWifi() NetworkService.disconnectWifi()
} else { } else {
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) { if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
if (DMSService.apiVersion >= 7) { wifiPasswordModal.show(networkContextMenu.currentSSID)
NetworkService.connectToWifi(networkContextMenu.currentSSID)
} else if (PopoutService.wifiPasswordModal) {
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID)
}
} else { } else {
NetworkService.connectToWifi(networkContextMenu.currentSSID) NetworkService.connectToWifi(networkContextMenu.currentSSID)
} }
} }
} }
} }
MenuItem { MenuItem {
text: I18n.tr("Network Info") text: "Network Info"
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {
text: parent.text text: parent.text
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -594,23 +390,23 @@ Rectangle {
leftPadding: Theme.spacingS leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: Rectangle { background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2 radius: Theme.cornerRadius / 2
} }
onTriggered: { onTriggered: {
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID) let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData) networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData)
} }
} }
MenuItem { MenuItem {
text: I18n.tr("Forget Network") text: "Forget Network"
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0 height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0
visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected
contentItem: StyledText { contentItem: StyledText {
text: parent.text text: parent.text
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -618,23 +414,25 @@ Rectangle {
leftPadding: Theme.spacingS leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: Rectangle { background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent" color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2 radius: Theme.cornerRadius / 2
} }
onTriggered: { onTriggered: {
NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID) NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID)
} }
} }
} }
WifiPasswordModal {
id: wifiPasswordModal
}
NetworkInfoModal { NetworkInfoModal {
id: networkInfoModal id: networkInfoModal
} }
NetworkWiredInfoModal {
id: networkWiredInfoModal
}
} }

View File

@@ -1,38 +1,13 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Modules.ControlCenter.BuiltinPlugins
import "../utils/widgets.js" as WidgetUtils import "../utils/widgets.js" as WidgetUtils
QtObject { QtObject {
id: root id: root
property var vpnBuiltinInstance: null readonly property var baseWidgetDefinitions: [
{
property var vpnLoader: Loader {
active: false
sourceComponent: Component {
VpnWidget {}
}
onItemChanged: {
root.vpnBuiltinInstance = item
}
Connections {
target: SettingsData
function onControlCenterWidgetsChanged() {
const widgets = SettingsData.controlCenterWidgets || []
const hasVpnWidget = widgets.some(w => w.id === "builtin_vpn")
if (!hasVpnWidget && vpnLoader.active) {
console.log("VpnWidget: No VPN widget in control center, deactivating loader")
vpnLoader.active = false
}
}
}
}
readonly property var coreWidgetDefinitions: [{
"id": "nightMode", "id": "nightMode",
"text": "Night Mode", "text": "Night Mode",
"description": "Blue light filter", "description": "Blue light filter",
@@ -40,28 +15,32 @@ QtObject {
"type": "toggle", "type": "toggle",
"enabled": DisplayService.automationAvailable, "enabled": DisplayService.automationAvailable,
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined "warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
}, { },
{
"id": "darkMode", "id": "darkMode",
"text": "Dark Mode", "text": "Dark Mode",
"description": "System theme toggle", "description": "System theme toggle",
"icon": "contrast", "icon": "contrast",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, { },
{
"id": "doNotDisturb", "id": "doNotDisturb",
"text": "Do Not Disturb", "text": "Do Not Disturb",
"description": "Block notifications", "description": "Block notifications",
"icon": "do_not_disturb_on", "icon": "do_not_disturb_on",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, { },
{
"id": "idleInhibitor", "id": "idleInhibitor",
"text": "Keep Awake", "text": "Keep Awake",
"description": "Prevent screen timeout", "description": "Prevent screen timeout",
"icon": "motion_sensor_active", "icon": "motion_sensor_active",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, { },
{
"id": "wifi", "id": "wifi",
"text": "Network", "text": "Network",
"description": "Wi-Fi and Ethernet connection", "description": "Wi-Fi and Ethernet connection",
@@ -69,7 +48,8 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": NetworkService.wifiAvailable, "enabled": NetworkService.wifiAvailable,
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined "warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
}, { },
{
"id": "bluetooth", "id": "bluetooth",
"text": "Bluetooth", "text": "Bluetooth",
"description": "Device connections", "description": "Device connections",
@@ -77,51 +57,57 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": BluetoothService.available, "enabled": BluetoothService.available,
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined "warning": !BluetoothService.available ? "Bluetooth not available" : undefined
}, { },
{
"id": "audioOutput", "id": "audioOutput",
"text": "Audio Output", "text": "Audio Output",
"description": "Speaker settings", "description": "Speaker settings",
"icon": "volume_up", "icon": "volume_up",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, { },
{
"id": "audioInput", "id": "audioInput",
"text": "Audio Input", "text": "Audio Input",
"description": "Microphone settings", "description": "Microphone settings",
"icon": "mic", "icon": "mic",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, { },
{
"id": "volumeSlider", "id": "volumeSlider",
"text": "Volume Slider", "text": "Volume Slider",
"description": "Audio volume control", "description": "Audio volume control",
"icon": "volume_up", "icon": "volume_up",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, { },
{
"id": "brightnessSlider", "id": "brightnessSlider",
"text": "Brightness Slider", "text": "Brightness Slider",
"description": "Display brightness control", "description": "Display brightness control",
"icon": "brightness_6", "icon": "brightness_6",
"type": "slider", "type": "slider",
"enabled": DisplayService.brightnessAvailable, "enabled": DisplayService.brightnessAvailable,
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined, "warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined
"allowMultiple": true },
}, { {
"id": "inputVolumeSlider", "id": "inputVolumeSlider",
"text": "Input Volume Slider", "text": "Input Volume Slider",
"description": "Microphone volume control", "description": "Microphone volume control",
"icon": "mic", "icon": "mic",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, { },
{
"id": "battery", "id": "battery",
"text": "Battery", "text": "Battery",
"description": "Battery and power management", "description": "Battery and power management",
"icon": "battery_std", "icon": "battery_std",
"type": "action", "type": "action",
"enabled": true "enabled": true
}, { },
{
"id": "diskUsage", "id": "diskUsage",
"text": "Disk Usage", "text": "Disk Usage",
"description": "Filesystem usage monitoring", "description": "Filesystem usage monitoring",
@@ -130,68 +116,16 @@ QtObject {
"enabled": DgopService.dgopAvailable, "enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined, "warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
"allowMultiple": true "allowMultiple": true
}, { },
{
"id": "colorPicker", "id": "colorPicker",
"text": "Color Picker", "text": "Color Picker",
"description": "Choose colors from palette", "description": "Choose colors from palette",
"icon": "palette", "icon": "palette",
"type": "action", "type": "action",
"enabled": true "enabled": true
}, {
"id": "builtin_vpn",
"text": "VPN",
"description": "VPN connections",
"icon": "vpn_key",
"type": "builtin_plugin",
"enabled": DMSNetworkService.available,
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
"isBuiltinPlugin": true
}]
function getPluginWidgets() {
const plugins = []
const loadedPlugins = PluginService.getLoadedPlugins()
for (var i = 0; i < loadedPlugins.length; i++) {
const plugin = loadedPlugins[i]
if (plugin.type === "daemon") {
continue
}
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id]
if (!pluginComponent) {
continue
}
const tempInstance = pluginComponent.createObject(null)
if (!tempInstance) {
continue
}
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0
tempInstance.destroy()
if (!hasCCWidget) {
continue
}
plugins.push({
"id": "plugin_" + plugin.id,
"pluginId": plugin.id,
"text": plugin.name || "Plugin",
"description": plugin.description || "",
"icon": plugin.icon || "extension",
"type": "plugin",
"enabled": true,
"isPlugin": true
})
} }
]
return plugins
}
readonly property var baseWidgetDefinitions: coreWidgetDefinitions
function getWidgetForId(widgetId) { function getWidgetForId(widgetId) {
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId) return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)
@@ -220,4 +154,4 @@ QtObject {
function clearAll() { function clearAll() {
WidgetUtils.clearAll() WidgetUtils.clearAll()
} }
} }

View File

@@ -10,12 +10,6 @@ import qs.Widgets
PanelWindow { PanelWindow {
id: root id: root
readonly property string powerOptionsText: I18n.tr("Power Options")
readonly property string logOutText: I18n.tr("Log Out")
readonly property string suspendText: I18n.tr("Suspend")
readonly property string rebootText: I18n.tr("Reboot")
readonly property string powerOffText: I18n.tr("Power Off")
property bool powerMenuVisible: false property bool powerMenuVisible: false
signal powerActionRequested(string action, string title, string message) signal powerActionRequested(string action, string title, string message)
@@ -71,7 +65,7 @@ PanelWindow {
width: parent.width width: parent.width
StyledText { StyledText {
text: root.powerOptionsText text: "Power Options"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -124,7 +118,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: root.logOutText text: "Log Out"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -174,7 +168,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: root.suspendText text: "Suspend"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -224,7 +218,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: root.rebootText text: "Reboot"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -274,7 +268,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: root.powerOffText text: "Power Off"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -0,0 +1,63 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSource: AudioService.source
iconName: {
if (!defaultSource) return "mic_off"
let volume = defaultSource.audio.volume
let muted = defaultSource.audio.muted
if (muted || volume === 0.0) return "mic_off"
return "mic"
}
isActive: defaultSource && !defaultSource.audio.muted
primaryText: {
if (!defaultSource) {
return "No input device"
}
return defaultSource.description || "Audio Input"
}
secondaryText: {
if (!defaultSource) {
return "Select device"
}
if (defaultSource.audio.muted) {
return "Muted"
}
return Math.round(defaultSource.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSource && defaultSource.audio) {
defaultSource.audio.muted = !defaultSource.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSource || !defaultSource.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSource.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSource.audio.muted = false
defaultSource.audio.volume = newVolume / 100
wheelEvent.accepted = true
}
}

View File

@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSink: AudioService.sink
iconName: {
if (!defaultSink) return "volume_off"
let volume = defaultSink.audio.volume
let muted = defaultSink.audio.muted
if (muted || volume === 0.0) return "volume_off"
if (volume <= 0.33) return "volume_down"
if (volume <= 0.66) return "volume_up"
return "volume_up"
}
isActive: defaultSink && !defaultSink.audio.muted
primaryText: {
if (!defaultSink) {
return "No output device"
}
return defaultSink.description || "Audio Output"
}
secondaryText: {
if (!defaultSink) {
return "Select device"
}
if (defaultSink.audio.muted) {
return "Muted"
}
return Math.round(defaultSink.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSink && defaultSink.audio) {
defaultSink.audio.muted = !defaultSink.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSink || !defaultSink.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSink.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSink.audio.muted = false
defaultSink.audio.volume = newVolume / 100
AudioService.volumeChanged()
wheelEvent.accepted = true
}
}

View File

@@ -69,6 +69,9 @@ Row {
valueOverride: actualVolumePercent valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
onIsDraggingChanged: {
AudioService.suppressOSD = isDragging
}
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
if (defaultSink) { if (defaultSink) {
defaultSink.audio.volume = newValue / 100.0 defaultSink.audio.volume = newValue / 100.0

View File

@@ -0,0 +1,70 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var primaryDevice: {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) {
return null
}
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
for (let device of devices) {
if (device && device.connected) {
return device
}
}
return null
}
iconName: {
if (!BluetoothService.available) {
return "bluetooth_disabled"
}
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "bluetooth_disabled"
}
return "bluetooth"
}
isActive: !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
showExpandArea: BluetoothService.available
primaryText: {
if (!BluetoothService.available) {
return "Bluetooth"
}
if (!BluetoothService.adapter) {
return "No adapter"
}
if (!BluetoothService.adapter.enabled) {
return "Disabled"
}
return "Enabled"
}
secondaryText: {
if (!BluetoothService.available) {
return "No adapters"
}
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "Off"
}
if (primaryDevice) {
return primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device"
}
return "No devices"
}
onToggled: {
if (BluetoothService.available && BluetoothService.adapter) {
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
}
}
}

View File

@@ -8,62 +8,9 @@ import qs.Widgets
Row { Row {
id: root id: root
property string deviceName: ""
property string instanceId: ""
property string screenName: ""
property var parentScreen: null
signal iconClicked()
height: 40 height: 40
spacing: 0 spacing: 0
property string targetDeviceName: {
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
return ""
}
if (screenName && screenName.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}
const pinnedDevice = pins[screenName]
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
if (found) {
return found.name
}
}
}
if (deviceName && deviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === deviceName)
return found ? found.name : ""
}
const currentDeviceName = DisplayService.currentDevice
if (currentDeviceName) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceName)
return found ? found.name : ""
}
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
}
property var targetDevice: {
if (!targetDeviceName || !DisplayService.devices) {
return null
}
return DisplayService.devices.find(dev => dev.name === targetDeviceName) || null
}
property real targetBrightness: {
if (!targetDeviceName) {
return 0
}
return DisplayService.getDeviceBrightness(targetDeviceName)
}
Rectangle { Rectangle {
width: Theme.iconSize + Theme.spacingS * 2 width: Theme.iconSize + Theme.spacingS * 2
height: Theme.iconSize + Theme.spacingS * 2 height: Theme.iconSize + Theme.spacingS * 2
@@ -77,22 +24,25 @@ Row {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: DisplayService.devices && DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: { onClicked: function(event) {
if (DisplayService.devices && DisplayService.devices.length > 1) { if (DisplayService.devices.length > 1) {
root.iconClicked() if (deviceMenu.visible) {
deviceMenu.close()
} else {
deviceMenu.popup(iconArea, 0, iconArea.height + Theme.spacingXS)
}
event.accepted = true
} }
} }
onEntered: { onEntered: {
tooltipLoader.active = true tooltipLoader.active = true
if (tooltipLoader.item) { if (tooltipLoader.item) {
const tooltipText = targetDevice ? "bl device: " + targetDevice.name : "Backlight Control" const tooltipText = DisplayService.currentDevice ? "bl device: " + DisplayService.currentDevice : "Backlight Control"
const globalPos = iconArea.mapToGlobal(iconArea.width / 2, iconArea.height / 2) const p = iconArea.mapToItem(null, iconArea.width / 2, 0)
const screenY = root.parentScreen?.y ?? 0 tooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
const relativeY = globalPos.y - screenY - 55
tooltipLoader.item.show(tooltipText, globalPos.x, relativeY, root.parentScreen)
} }
} }
@@ -106,23 +56,15 @@ Row {
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: { name: {
if (!DisplayService.brightnessAvailable || !targetDevice) { if (!DisplayService.brightnessAvailable) return "brightness_low"
return "brightness_low"
}
if (targetDevice.class === "backlight" || targetDevice.class === "ddc") { let brightness = DisplayService.brightnessLevel
const brightness = targetBrightness if (brightness <= 33) return "brightness_low"
if (brightness <= 33) return "brightness_low" if (brightness <= 66) return "brightness_medium"
if (brightness <= 66) return "brightness_medium" return "brightness_high"
return "brightness_high"
} else if (targetDevice.name.includes("kbd")) {
return "keyboard"
} else {
return "lightbulb"
}
} }
size: Theme.iconSize size: Theme.iconSize
color: DisplayService.brightnessAvailable && targetDevice && targetBrightness > 0 ? Theme.primary : Theme.surfaceText color: DisplayService.brightnessAvailable && DisplayService.brightnessLevel > 0 ? Theme.primary : Theme.surfaceText
} }
} }
} }
@@ -130,19 +72,88 @@ Row {
DankSlider { DankSlider {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.width - (Theme.iconSize + Theme.spacingS * 2) width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
enabled: DisplayService.brightnessAvailable && targetDeviceName.length > 0 enabled: DisplayService.brightnessAvailable
minimum: 1 minimum: 1
maximum: 100 maximum: 100
value: targetBrightness value: {
let level = DisplayService.brightnessLevel
if (level > 100) {
let deviceInfo = DisplayService.getCurrentDeviceInfo()
if (deviceInfo && deviceInfo.max > 0) {
return Math.round((level / deviceInfo.max) * 100)
}
return 50
}
return level
}
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
if (DisplayService.brightnessAvailable && targetDeviceName) { if (DisplayService.brightnessAvailable) {
DisplayService.setBrightness(newValue, targetDeviceName, true) DisplayService.setBrightness(newValue)
} }
} }
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: Theme.surfaceContainerHigh trackColor: Theme.surfaceContainerHigh
} }
Menu {
id: deviceMenu
width: 200
closePolicy: Popup.CloseOnEscape
background: Rectangle {
color: Theme.popupBackground()
radius: Theme.cornerRadius
border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
Instantiator {
model: DisplayService.devices
delegate: MenuItem {
required property var modelData
required property int index
property string deviceName: modelData.name || ""
property string deviceClass: modelData.class || ""
text: deviceName
font.pixelSize: Theme.fontSizeMedium
height: 40
indicator: Rectangle {
visible: DisplayService.currentDevice === parent.deviceName
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingS
width: 4
height: parent.height - Theme.spacingS * 2
radius: 2
color: Theme.primary
}
contentItem: StyledText {
text: parent.text
font: parent.font
color: DisplayService.currentDevice === parent.deviceName ? Theme.primary : Theme.surfaceText
leftPadding: Theme.spacingL
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2
}
onTriggered: {
DisplayService.setCurrentDevice(deviceName, true)
deviceMenu.close()
}
}
onObjectAdded: (index, object) => deviceMenu.insertItem(index, object)
onObjectRemoved: (index, object) => deviceMenu.removeItem(object)
}
}
Loader { Loader {
id: tooltipLoader id: tooltipLoader
active: false active: false

View File

@@ -14,7 +14,7 @@ Rectangle {
property real maximumValue: 1.0 property real maximumValue: 1.0
property real minimumValue: 0.0 property real minimumValue: 0.0
property bool enabled: true property bool enabled: true
signal sliderValueChanged(real value) signal sliderValueChanged(real value)
width: parent ? parent.width : 200 width: parent ? parent.width : 200

View File

@@ -49,7 +49,7 @@ Rectangle {
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileRingInactive: readonly property color _tileRingInactive:
Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18) Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18)
readonly property color _tileIconActive: Theme.primaryText readonly property color _tileIconActive: Theme.primaryContainer
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
property int _padH: Theme.spacingS property int _padH: Theme.spacingS

View File

@@ -1,51 +0,0 @@
import QtQuick
import qs.Common
import qs.Widgets
StyledRect {
id: root
property string primaryMessage: ""
property string secondaryMessage: ""
radius: Theme.cornerRadius
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.1)
border.color: Theme.warning
border.width: 1
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
DankIcon {
name: "warning"
size: 16
color: Theme.warning
anchors.top: parent.top
anchors.topMargin: 2
}
Column {
width: parent.width - 16 - parent.spacing
spacing: Theme.spacingXS
StyledText {
width: parent.width
text: root.primaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
font.weight: Font.Medium
wrapMode: Text.WordWrap
}
StyledText {
width: parent.width
text: root.secondaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
visible: text.length > 0
}
}
}
}

View File

@@ -0,0 +1,78 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
isActive: {
if (NetworkService.wifiToggling) {
return false
}
if (NetworkService.networkStatus === "ethernet") {
return true
}
if (NetworkService.networkStatus === "wifi") {
return true
}
return NetworkService.wifiEnabled
}
iconName: {
if (NetworkService.wifiToggling) {
return "sync"
}
if (NetworkService.networkStatus === "ethernet") {
return "settings_ethernet"
}
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalIcon
}
if (NetworkService.wifiEnabled) {
return "wifi_off"
}
return "wifi_off"
}
primaryText: {
if (NetworkService.wifiToggling) {
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
}
if (NetworkService.networkStatus === "ethernet") {
return "Ethernet"
}
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID) {
return NetworkService.currentWifiSSID
}
if (NetworkService.wifiEnabled) {
return "Not connected"
}
return "WiFi off"
}
secondaryText: {
if (NetworkService.wifiToggling) {
return "Please wait..."
}
if (NetworkService.networkStatus === "ethernet") {
return "Connected"
}
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
}
if (NetworkService.wifiEnabled) {
return "Select network"
}
return ""
}
onToggled: {
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
NetworkService.toggleWifiRadio()
}
}
}

View File

@@ -28,7 +28,7 @@ Rectangle {
readonly property color _tileBgInactive: Theme.surfaceContainerHigh readonly property color _tileBgInactive: Theme.surfaceContainerHigh
readonly property color _tileRingActive: readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryText readonly property color _tileIconActive: Theme.primaryContainer
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
color: { color: {

View File

@@ -30,7 +30,7 @@ Rectangle {
readonly property color _tileBgInactive: Theme.surfaceContainerHigh readonly property color _tileBgInactive: Theme.surfaceContainerHigh
readonly property color _tileRingActive: readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryText readonly property color _tileIconActive: Theme.primaryContainer
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
color: { color: {

View File

@@ -64,7 +64,7 @@ Rectangle {
DankIcon { DankIcon {
name: root.iconName name: root.iconName
size: Theme.iconSize size: Theme.iconSize
color: isActive ? Theme.primaryText : Theme.primary color: isActive ? Theme.primaryContainer : Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
rotation: root.iconRotation rotation: root.iconRotation
onRotationCompleted: root.iconRotationCompleted() onRotationCompleted: root.iconRotationCompleted()
@@ -84,7 +84,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.text text: root.text
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: isActive ? Theme.primaryText : Theme.surfaceText color: isActive ? Theme.primaryContainer : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
@@ -94,7 +94,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.secondaryText text: root.secondaryText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: isActive ? Theme.primaryText : Theme.surfaceVariantText color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
visible: text.length > 0 visible: text.length > 0
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap

View File

@@ -15,11 +15,6 @@ function addWidget(widgetId) {
widget.mountPath = "/" widget.mountPath = "/"
} }
if (widgetId === "brightnessSlider") {
widget.instanceId = generateUniqueId()
widget.deviceName = ""
}
widgets.push(widget) widgets.push(widget)
SettingsData.setControlCenterWidgets(widgets) SettingsData.setControlCenterWidgets(widgets)
} }

View File

@@ -1,7 +1,5 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Services
Item { Item {
id: root id: root
@@ -18,33 +16,6 @@ Item {
anchors.topMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "bottom" ? barWindow._wingR : 0) anchors.topMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "bottom" ? barWindow._wingR : 0)
anchors.bottomMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "top" ? barWindow._wingR : 0) anchors.bottomMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "top" ? barWindow._wingR : 0)
readonly property real dpr: {
if (CompositorService.isNiri && barWindow.screen) {
const niriScale = NiriService.displayScales[barWindow.screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && barWindow.screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === barWindow.screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return barWindow.screen?.devicePixelRatio || 1
}
function requestRepaint() {
debounceTimer.restart()
}
Timer {
id: debounceTimer
interval: 50
repeat: false
onTriggered: {
barShape.requestPaint()
barTint.requestPaint()
barBorder.requestPaint()
}
}
Canvas { Canvas {
id: barShape id: barShape
anchors.fill: parent anchors.fill: parent
@@ -52,48 +23,46 @@ Item {
renderTarget: Canvas.FramebufferObject renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative renderStrategy: Canvas.Cooperative
readonly property real correctWidth: Theme.px(root.width, dpr) readonly property real correctWidth: root.width
readonly property real correctHeight: Theme.px(root.height, dpr) readonly property real correctHeight: root.height
canvasSize: Qt.size(correctWidth, correctHeight) canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0 property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr) property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
onWingChanged: root.requestRepaint() onWingChanged: requestPaint()
onRtChanged: root.requestRepaint() onRtChanged: requestPaint()
onCorrectWidthChanged: root.requestRepaint() onCorrectWidthChanged: requestPaint()
onCorrectHeightChanged: root.requestRepaint() onCorrectHeightChanged: requestPaint()
onVisibleChanged: if (visible) root.requestRepaint() onVisibleChanged: if (visible) requestPaint()
Component.onCompleted: root.requestRepaint() Component.onCompleted: requestPaint()
Connections {
target: root
function onDprChanged() { root.requestRepaint() }
}
Connections { Connections {
target: barWindow target: barWindow
function on_BgColorChanged() { root.requestRepaint() } function on_BgColorChanged() { barShape.requestPaint() }
function on_DprChanged() { barShape.requestPaint() }
} }
Connections { Connections {
target: Theme target: Theme
function onIsLightModeChanged() { root.requestRepaint() } function onIsLightModeChanged() { barShape.requestPaint() }
function onSurfaceContainerChanged() { root.requestRepaint() }
} }
onPaint: { onPaint: {
const ctx = getContext("2d") const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth const scale = barWindow._dpr
const H_raw = barWindow.isVertical ? correctWidth : correctHeight const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
const R = wing const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
const RT = rt const R = barWindow.px(wing)
const RT = barWindow.px(rt)
const H = H_raw - (R > 0 ? R : 0) const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
ctx.scale(scale, scale)
function drawTopPath() { function drawTopPath() {
ctx.beginPath() ctx.beginPath()
ctx.moveTo(RT, 0) ctx.moveTo(RT, 0)
@@ -149,50 +118,48 @@ Item {
renderTarget: Canvas.FramebufferObject renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative renderStrategy: Canvas.Cooperative
readonly property real correctWidth: Theme.px(root.width, dpr) readonly property real correctWidth: root.width
readonly property real correctHeight: Theme.px(root.height, dpr) readonly property real correctHeight: root.height
canvasSize: Qt.size(correctWidth, correctHeight) canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0 property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr) property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
property real alphaTint: (barWindow._bgColor?.a ?? 1) < 0.99 ? (Theme.stateLayerOpacity ?? 0) : 0 property real alphaTint: (barWindow._bgColor?.a ?? 1) < 0.99 ? (Theme.stateLayerOpacity ?? 0) : 0
onWingChanged: root.requestRepaint() onWingChanged: requestPaint()
onRtChanged: root.requestRepaint() onRtChanged: requestPaint()
onAlphaTintChanged: root.requestRepaint() onAlphaTintChanged: requestPaint()
onCorrectWidthChanged: root.requestRepaint() onCorrectWidthChanged: requestPaint()
onCorrectHeightChanged: root.requestRepaint() onCorrectHeightChanged: requestPaint()
onVisibleChanged: if (visible) root.requestRepaint() onVisibleChanged: if (visible) requestPaint()
Component.onCompleted: root.requestRepaint() Component.onCompleted: requestPaint()
Connections {
target: root
function onDprChanged() { root.requestRepaint() }
}
Connections { Connections {
target: barWindow target: barWindow
function on_BgColorChanged() { root.requestRepaint() } function on_BgColorChanged() { barTint.requestPaint() }
function on_DprChanged() { barTint.requestPaint() }
} }
Connections { Connections {
target: Theme target: Theme
function onIsLightModeChanged() { root.requestRepaint() } function onIsLightModeChanged() { barTint.requestPaint() }
function onSurfaceChanged() { root.requestRepaint() }
} }
onPaint: { onPaint: {
const ctx = getContext("2d") const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth const scale = barWindow._dpr
const H_raw = barWindow.isVertical ? correctWidth : correctHeight const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
const R = wing const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
const RT = rt const R = barWindow.px(wing)
const RT = barWindow.px(rt)
const H = H_raw - (R > 0 ? R : 0) const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
ctx.scale(scale, scale)
function drawTopPath() { function drawTopPath() {
ctx.beginPath() ctx.beginPath()
ctx.moveTo(RT, 0) ctx.moveTo(RT, 0)
@@ -240,167 +207,4 @@ Item {
ctx.fill() ctx.fill()
} }
} }
}
Canvas {
id: barBorder
anchors.fill: parent
visible: SettingsData.dankBarBorderEnabled
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative
readonly property real correctWidth: Theme.px(root.width, dpr)
readonly property real correctHeight: Theme.px(root.height, dpr)
canvasSize: Qt.size(correctWidth, correctHeight)
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr)
property bool borderEnabled: SettingsData.dankBarBorderEnabled
antialiasing: rt > 0 || wing > 0
onWingChanged: root.requestRepaint()
onRtChanged: root.requestRepaint()
onBorderEnabledChanged: root.requestRepaint()
onCorrectWidthChanged: root.requestRepaint()
onCorrectHeightChanged: root.requestRepaint()
onVisibleChanged: if (visible) root.requestRepaint()
Component.onCompleted: root.requestRepaint()
Connections {
target: root
function onDprChanged() { root.requestRepaint() }
}
Connections {
target: Theme
function onIsLightModeChanged() { root.requestRepaint() }
function onSurfaceTextChanged() { root.requestRepaint() }
function onPrimaryChanged() { root.requestRepaint() }
function onSecondaryChanged() { root.requestRepaint() }
function onOutlineChanged() { root.requestRepaint() }
}
Connections {
target: SettingsData
function onDankBarBorderColorChanged() { root.requestRepaint() }
function onDankBarBorderOpacityChanged() { root.requestRepaint() }
function onDankBarBorderThicknessChanged() { root.requestRepaint() }
function onDankBarSpacingChanged() { root.requestRepaint() }
function onDankBarSquareCornersChanged() { root.requestRepaint() }
function onDankBarTransparencyChanged() { root.requestRepaint() }
}
onPaint: {
if (!borderEnabled) return
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const R = wing
const RT = rt
const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
const spacing = SettingsData.dankBarSpacing
const hasEdgeGap = spacing > 0 || RT > 0
ctx.reset()
ctx.clearRect(0, 0, W, H_raw)
ctx.save()
if (isBottom) {
ctx.translate(W, H_raw)
ctx.rotate(Math.PI)
} else if (isLeft) {
ctx.translate(0, W)
ctx.rotate(-Math.PI / 2)
} else if (isRight) {
ctx.translate(H_raw, 0)
ctx.rotate(Math.PI / 2)
}
const uiThickness = Math.max(1, SettingsData.dankBarBorderThickness ?? 1)
const devThickness = Math.max(1, Math.round(Theme.px(uiThickness, dpr)))
const key = SettingsData.dankBarBorderColor || "surfaceText"
const base = (key === "surfaceText") ? Theme.surfaceText
: (key === "primary") ? Theme.primary
: Theme.secondary
const color = Theme.withAlpha(base, SettingsData.dankBarBorderOpacity ?? 1.0)
ctx.globalCompositeOperation = "source-over"
ctx.fillStyle = color
function drawTopBorder() {
if (!hasEdgeGap) {
ctx.beginPath()
ctx.rect(0, H - devThickness, W, devThickness)
ctx.fill()
} else {
const thk = devThickness
const RTi = Math.max(0, RT - thk)
const Ri = Math.max(0, R - thk)
ctx.beginPath()
if (R > 0 && Ri > 0) {
ctx.moveTo(RT, 0)
ctx.lineTo(W - RT, 0)
ctx.arcTo(W, 0, W, RT, RT)
ctx.lineTo(W, H)
ctx.lineTo(W, H + R)
ctx.arc(W - R, H + R, R, 0, -Math.PI / 2, true)
ctx.lineTo(R, H)
ctx.arc(R, H + R, R, -Math.PI / 2, -Math.PI, true)
ctx.lineTo(0, H + R)
ctx.lineTo(0, RT)
ctx.arcTo(0, 0, RT, 0, RT)
ctx.closePath()
ctx.moveTo(RT, thk)
ctx.arcTo(thk, thk, thk, RT, RTi)
ctx.lineTo(thk, H + R)
ctx.arc(R, H + R, Ri, -Math.PI, -Math.PI / 2, false)
ctx.lineTo(W - R, H + thk)
ctx.arc(W - R, H + R, Ri, -Math.PI / 2, 0, false)
ctx.lineTo(W - thk, H + R)
ctx.lineTo(W - thk, RT)
ctx.arcTo(W - thk, thk, W - RT, thk, RTi)
ctx.lineTo(RT, thk)
ctx.closePath()
} else {
ctx.moveTo(RT, 0)
ctx.lineTo(W - RT, 0)
ctx.arcTo(W, 0, W, RT, RT)
ctx.lineTo(W, H - RT)
ctx.arcTo(W, H, W - RT, H, RT)
ctx.lineTo(RT, H)
ctx.arcTo(0, H, 0, H - RT, RT)
ctx.lineTo(0, RT)
ctx.arcTo(0, 0, RT, 0, RT)
ctx.closePath()
ctx.moveTo(RT, thk)
ctx.arcTo(thk, thk, thk, RT, RTi)
ctx.lineTo(thk, H - RT)
ctx.arcTo(thk, H - thk, RT, H - thk, RTi)
ctx.lineTo(W - RT, H - thk)
ctx.arcTo(W - thk, H - thk, W - thk, H - RT, RTi)
ctx.lineTo(W - thk, RT)
ctx.arcTo(W - thk, thk, W - RT, thk, RTi)
ctx.lineTo(RT, thk)
ctx.closePath()
}
ctx.fill("evenodd")
}
}
drawTopBorder()
ctx.restore()
}
}
}

View File

@@ -9,14 +9,8 @@ Item {
property var components: null property var components: null
property bool noBackground: false property bool noBackground: false
required property var axis required property var axis
property string section: "center"
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false) readonly property bool isVertical: axis?.isVertical ?? false
readonly property real spacing: noBackground ? 2 : Theme.spacingXS readonly property real spacing: noBackground ? 2 : Theme.spacingXS
property var centerWidgets: [] property var centerWidgets: []
@@ -272,8 +266,7 @@ Item {
} }
function getWidgetComponent(widgetId) { function getWidgetComponent(widgetId) {
// Build dynamic component map including plugins const componentMap = {
let baseMap = {
"launcherButton": "launcherButtonComponent", "launcherButton": "launcherButtonComponent",
"workspaceSwitcher": "workspaceSwitcherComponent", "workspaceSwitcher": "workspaceSwitcherComponent",
"focusedWindow": "focusedWindowComponent", "focusedWindow": "focusedWindowComponent",
@@ -303,17 +296,8 @@ Item {
"systemUpdate": "systemUpdateComponent" "systemUpdate": "systemUpdateComponent"
} }
// For built-in components, get from components property const componentKey = componentMap[widgetId]
const componentKey = baseMap[widgetId] return componentKey ? root.components[componentKey] : null
if (componentKey && root.components[componentKey]) {
return root.components[componentKey]
}
// For plugin components, get from PluginService
var parts = widgetId.split(":")
var pluginId = parts[0]
let pluginComponents = PluginService.getWidgetComponents()
return pluginComponents[pluginId] || null
} }
height: parent.height height: parent.height
@@ -353,7 +337,6 @@ Item {
id: centerRepeater id: centerRepeater
model: root.widgetsModel model: root.widgetsModel
Loader { Loader {
property string widgetId: model.widgetId property string widgetId: model.widgetId
property var widgetData: model property var widgetData: model
@@ -376,90 +359,11 @@ Item {
item.spacerSize = Qt.binding(() => model.size || 20) item.spacerSize = Qt.binding(() => model.size || 20)
} }
if (root.axis && "axis" in item) { if (root.axis && "axis" in item) {
item.axis = Qt.binding(() => root.axis) item.axis = root.axis
} }
if (root.axis && "isVertical" in item) { if (root.axis && "isVertical" in item) {
try { item.isVertical = root.axis.isVertical
item.isVertical = Qt.binding(() => root.axis.isVertical)
} catch (e) {
}
} }
// Inject properties for plugin widgets
if ("section" in item) {
item.section = root.section
}
if ("parentScreen" in item) {
item.parentScreen = Qt.binding(() => root.parentScreen)
}
if ("widgetThickness" in item) {
item.widgetThickness = Qt.binding(() => root.widgetThickness)
}
if ("barThickness" in item) {
item.barThickness = Qt.binding(() => root.barThickness)
}
if ("sectionSpacing" in item) {
item.sectionSpacing = Qt.binding(() => root.spacing)
}
if ("isFirst" in item) {
item.isFirst = Qt.binding(() => {
for (var i = 0; i < centerRepeater.count; i++) {
const checkItem = centerRepeater.itemAt(i)
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item
}
}
return false
})
}
if ("isLast" in item) {
item.isLast = Qt.binding(() => {
for (var i = centerRepeater.count - 1; i >= 0; i--) {
const checkItem = centerRepeater.itemAt(i)
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item
}
}
return false
})
}
if ("isLeftBarEdge" in item) {
item.isLeftBarEdge = false
}
if ("isRightBarEdge" in item) {
item.isRightBarEdge = false
}
if ("isTopBarEdge" in item) {
item.isTopBarEdge = false
}
if ("isBottomBarEdge" in item) {
item.isBottomBarEdge = false
}
if (item.pluginService !== undefined) {
var parts = model.widgetId.split(":")
var pluginId = parts[0]
var variantId = parts.length > 1 ? parts[1] : null
if (item.pluginId !== undefined) {
item.pluginId = pluginId
}
if (item.variantId !== undefined) {
item.variantId = variantId
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
}
item.pluginService = PluginService
}
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
layoutTimer.restart() layoutTimer.restart()
} }
@@ -475,27 +379,4 @@ Item {
layoutTimer.restart() layoutTimer.restart()
} }
} }
// Listen for plugin changes and refresh components
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
// Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i)
if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(item.widgetId)
}
}
}
function onPluginUnloaded(pluginId) {
// Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i)
if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(item.widgetId)
}
}
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -8,13 +8,8 @@ Item {
property var components: null property var components: null
property bool noBackground: false property bool noBackground: false
required property var axis required property var axis
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false) readonly property bool isVertical: axis?.isVertical ?? false
implicitHeight: layoutLoader.item ? (layoutLoader.item.implicitHeight || layoutLoader.item.height) : 0 implicitHeight: layoutLoader.item ? (layoutLoader.item.implicitHeight || layoutLoader.item.height) : 0
implicitWidth: layoutLoader.item ? (layoutLoader.item.implicitWidth || layoutLoader.item.width) : 0 implicitWidth: layoutLoader.item ? (layoutLoader.item.implicitWidth || layoutLoader.item.width) : 0
@@ -28,13 +23,10 @@ Item {
Component { Component {
id: rowComp id: rowComp
Row { Row {
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS spacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater { Repeater {
id: rowRepeater
model: root.widgetsModel model: root.widgetsModel
Item { Item {
readonly property real rowSpacing: parent.widgetSpacing
width: widgetLoader.item ? widgetLoader.item.width : 0 width: widgetLoader.item ? widgetLoader.item.width : 0
height: widgetLoader.item ? widgetLoader.item.height : 0 height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost { WidgetHost {
@@ -46,15 +38,6 @@ Item {
components: root.components components: root.components
isInColumn: false isInColumn: false
axis: root.axis axis: root.axis
section: "left"
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
isLeftBarEdge: true
isRightBarEdge: false
} }
} }
} }
@@ -65,13 +48,10 @@ Item {
id: columnComp id: columnComp
Column { Column {
width: Math.max(parent.width, 200) width: Math.max(parent.width, 200)
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS spacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater { Repeater {
id: columnRepeater
model: root.widgetsModel model: root.widgetsModel
Item { Item {
readonly property real columnSpacing: parent.widgetSpacing
width: parent.width width: parent.width
height: widgetLoader.item ? widgetLoader.item.height : 0 height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost { WidgetHost {
@@ -83,15 +63,6 @@ Item {
components: root.components components: root.components
isInColumn: true isInColumn: true
axis: root.axis axis: root.axis
section: "left"
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing
isTopBarEdge: true
isBottomBarEdge: false
} }
} }
} }

View File

@@ -235,7 +235,7 @@ DankPopout {
} }
StyledText { StyledText {
text: BatteryService.batteryStatus text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: { color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) { if (BatteryService.isLowBattery && !BatteryService.isCharging) {
@@ -247,7 +247,6 @@ DankPopout {
return Theme.surfaceText; return Theme.surfaceText;
} }
font.weight: Font.Medium font.weight: Font.Medium
visible: BatteryService.batteryAvailable
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
@@ -274,7 +273,7 @@ DankPopout {
height: 32 height: 32
radius: 16 radius: 16
color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent" color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent"
anchors.top: parent.top anchors.verticalCenter: parent.verticalCenter
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -312,7 +311,7 @@ DankPopout {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Health") text: "Health"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -347,7 +346,7 @@ DankPopout {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Capacity") text: "Capacity"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -365,210 +364,6 @@ DankPopout {
} }
} }
// Individual battery details for multiple batteries
Column {
width: parent.width
spacing: Theme.spacingS
visible: !BatteryService.usePreferred && BatteryService.batteries.length > 1
StyledText {
text: I18n.tr("Individual Batteries")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
}
Repeater {
model: BatteryService.batteries
delegate: StyledRect {
required property var modelData
required property int index
width: parent.width
height: batteryColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.width: 0
Column {
id: batteryColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
// Top row: name and percentage
Row {
width: parent.width
spacing: Theme.spacingM
Column {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
width: parent.width - percentText.width - chargingIcon.width - Theme.spacingM * 2
StyledText {
text: modelData.model || `Battery ${index + 1}`
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.nativePath
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
elide: Text.ElideMiddle
width: parent.width
}
}
Item {
width: 1
height: parent.height
}
StyledText {
id: percentText
text: `${Math.round(100 * modelData.percentage)}%`
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Bold
anchors.verticalCenter: parent.verticalCenter
}
DankIcon {
id: chargingIcon
name: modelData.state === UPowerDeviceState.Charging ? "bolt" : ""
size: Theme.iconSizeSmall
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: modelData.state === UPowerDeviceState.Charging
}
}
// Bottom row: Health, Capacity and Time
Flow {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.width: 0
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: I18n.tr("Health")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
return "N/A"
return `${Math.round(modelData.healthPercentage)}%`
}
font.pixelSize: Theme.fontSizeSmall
color: {
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
return Theme.surfaceText
return modelData.healthPercentage < 80 ? Theme.error : Theme.surfaceText
}
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
StyledRect {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.width: 0
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: I18n.tr("Capacity")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: modelData.energyCapacity > 0 ? `${modelData.energyCapacity.toFixed(1)}` : "N/A"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
StyledRect {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.width: 0
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: modelData.state === UPowerDeviceState.Charging
? I18n.tr("To Full")
: modelData.state === UPowerDeviceState.Discharging
? I18n.tr("Left") : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
const time = modelData.state === UPowerDeviceState.Charging
? modelData.timeToFull
: modelData.state === UPowerDeviceState.Discharging && BatteryService.changeRate > 0
? (3600 * modelData.energy) / BatteryService.changeRate : 0
if (!time || time <= 0 || time > 86400)
return "N/A"
const hours = Math.floor(time / 3600)
const minutes = Math.floor((time % 3600) / 60)
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
}
}
}
}
DankButtonGroup { DankButtonGroup {
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
property int currentProfileIndex: { property int currentProfileIndex: {
@@ -620,7 +415,7 @@ DankPopout {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: I18n.tr("Power Profile Degradation") text: "Power Profile Degradation"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.error color: Theme.error
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -13,10 +13,6 @@ import qs.Widgets
DankPopout { DankPopout {
id: root id: root
Ref {
service: DMSNetworkService
}
property var triggerScreen: null property var triggerScreen: null
function setTriggerPosition(x, y, width, section, screen) { function setTriggerPosition(x, y, width, section, screen) {
@@ -100,7 +96,7 @@ DankPopout {
height: 32 height: 32
StyledText { StyledText {
text: I18n.tr("VPN Connections") text: "VPN Connections"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -161,11 +157,11 @@ DankPopout {
StyledText { StyledText {
text: { text: {
if (!DMSNetworkService.connected) { if (!VpnService.connected) {
return "Active: None"; return "Active: None";
} }
const names = DMSNetworkService.activeNames || []; const names = VpnService.activeNames || [];
if (names.length <= 1) { if (names.length <= 1) {
return "Active: " + (names[0] || "VPN"); return "Active: " + (names[0] || "VPN");
} }
@@ -193,7 +189,7 @@ DankPopout {
height: 28 height: 28
radius: 14 radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: DMSNetworkService.connected visible: VpnService.connected
width: 130 width: 130
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
border.width: 0 border.width: 0
@@ -210,7 +206,7 @@ DankPopout {
} }
StyledText { StyledText {
text: I18n.tr("Disconnect") text: "Disconnect"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -224,7 +220,7 @@ DankPopout {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: DMSNetworkService.disconnectAllActive() onClicked: VpnService.disconnectAllActive()
} }
} }
@@ -251,7 +247,7 @@ DankPopout {
Item { Item {
width: parent.width width: parent.width
height: DMSNetworkService.profiles.length === 0 ? 120 : 0 height: VpnService.profiles.length === 0 ? 120 : 0
visible: height > 0 visible: height > 0
Column { Column {
@@ -266,14 +262,14 @@ DankPopout {
} }
StyledText { StyledText {
text: I18n.tr("No VPN profiles found") text: "No VPN profiles found"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: I18n.tr("Add a VPN in NetworkManager") text: "Add a VPN in NetworkManager"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -284,7 +280,7 @@ DankPopout {
} }
Repeater { Repeater {
model: DMSNetworkService.profiles model: VpnService.profiles
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
@@ -292,9 +288,9 @@ DankPopout {
width: parent ? parent.width : 300 width: parent ? parent.width : 300
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight) color: rowArea.containsMouse ? Theme.primaryHoverLight : (VpnService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1 border.width: VpnService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight border.color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout { RowLayout {
anchors.left: parent.left anchors.left: parent.left
@@ -304,9 +300,9 @@ DankPopout {
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off" name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4 size: Theme.iconSize - 4
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@@ -317,7 +313,7 @@ DankPopout {
StyledText { StyledText {
text: modelData.name text: modelData.name
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
} }
StyledText { StyledText {
@@ -392,7 +388,7 @@ DankPopout {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: DMSNetworkService.toggle(modelData.uuid) onClicked: VpnService.toggle(modelData.uuid)
} }
} }

View File

@@ -8,13 +8,8 @@ Item {
property var components: null property var components: null
property bool noBackground: false property bool noBackground: false
required property var axis required property var axis
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false) readonly property bool isVertical: axis?.isVertical ?? false
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0 implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0 implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
@@ -29,14 +24,11 @@ Item {
Component { Component {
id: rowComp id: rowComp
Row { Row {
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS spacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
anchors.right: parent ? parent.right : undefined anchors.right: parent ? parent.right : undefined
Repeater { Repeater {
id: rowRepeater
model: root.widgetsModel model: root.widgetsModel
Item { Item {
readonly property real rowSpacing: parent.widgetSpacing
width: widgetLoader.item ? widgetLoader.item.width : 0 width: widgetLoader.item ? widgetLoader.item.width : 0
height: widgetLoader.item ? widgetLoader.item.height : 0 height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost { WidgetHost {
@@ -48,15 +40,6 @@ Item {
components: root.components components: root.components
isInColumn: false isInColumn: false
axis: root.axis axis: root.axis
section: "right"
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
isLeftBarEdge: false
isRightBarEdge: true
} }
} }
} }
@@ -67,13 +50,10 @@ Item {
id: columnComp id: columnComp
Column { Column {
width: parent ? parent.width : 0 width: parent ? parent.width : 0
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS spacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater { Repeater {
id: columnRepeater
model: root.widgetsModel model: root.widgetsModel
Item { Item {
readonly property real columnSpacing: parent.widgetSpacing
width: parent.width width: parent.width
height: widgetLoader.item ? widgetLoader.item.height : 0 height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost { WidgetHost {
@@ -85,15 +65,6 @@ Item {
components: root.components components: root.components
isInColumn: true isInColumn: true
axis: root.axis axis: root.axis
section: "right"
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing
isTopBarEdge: false
isBottomBarEdge: true
} }
} }
} }

View File

@@ -11,163 +11,27 @@ Loader {
property var components: null property var components: null
property bool isInColumn: false property bool isInColumn: false
property var axis: null property var axis: null
property string section: "center"
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool isFirst: false
property bool isLast: false
property real sectionSpacing: 0
property bool isLeftBarEdge: false
property bool isRightBarEdge: false
property bool isTopBarEdge: false
property bool isBottomBarEdge: false
asynchronous: false asynchronous: false
readonly property bool orientationMatches: (axis?.isVertical ?? false) === isInColumn active: getWidgetVisible(widgetId, DgopService.dgopAvailable) &&
active: orientationMatches &&
getWidgetVisible(widgetId, DgopService.dgopAvailable) &&
(widgetId !== "music" || MprisController.activePlayer !== null) (widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: getWidgetComponent(widgetId, components) sourceComponent: getWidgetComponent(widgetId, components)
opacity: getWidgetEnabled(widgetData?.enabled) ? 1 : 0 opacity: getWidgetEnabled(widgetData?.enabled) ? 1 : 0
signal contentItemReady(var item) signal contentItemReady(var item)
Binding {
target: root.item
when: root.item && "parentScreen" in root.item
property: "parentScreen"
value: root.parentScreen
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "section" in root.item
property: "section"
value: root.section
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "widgetThickness" in root.item
property: "widgetThickness"
value: root.widgetThickness
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "barThickness" in root.item
property: "barThickness"
value: root.barThickness
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "axis" in root.item
property: "axis"
value: root.axis
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "widgetData" in root.item
property: "widgetData"
value: root.widgetData
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isFirst" in root.item
property: "isFirst"
value: root.isFirst
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isLast" in root.item
property: "isLast"
value: root.isLast
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "sectionSpacing" in root.item
property: "sectionSpacing"
value: root.sectionSpacing
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isLeftBarEdge" in root.item
property: "isLeftBarEdge"
value: root.isLeftBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isRightBarEdge" in root.item
property: "isRightBarEdge"
value: root.isRightBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isTopBarEdge" in root.item
property: "isTopBarEdge"
value: root.isTopBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isBottomBarEdge" in root.item
property: "isBottomBarEdge"
value: root.isBottomBarEdge
restoreMode: Binding.RestoreNone
}
onLoaded: { onLoaded: {
if (item) { if (item) {
contentItemReady(item) contentItemReady(item)
if (widgetId === "spacer") {
item.spacerSize = Qt.binding(() => spacerSize)
}
if (axis && "axis" in item) {
item.axis = axis
}
if (axis && "isVertical" in item) { if (axis && "isVertical" in item) {
try { item.isVertical = axis.isVertical
item.isVertical = axis.isVertical
} catch (e) {
}
}
if (item.pluginService !== undefined) {
var parts = widgetId.split(":")
var pluginId = parts[0]
var variantId = parts.length > 1 ? parts[1] : null
if (item.pluginId !== undefined) {
item.pluginId = pluginId
}
if (item.variantId !== undefined) {
item.variantId = variantId
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
}
item.pluginService = PluginService
}
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
} }
} }
} }
@@ -203,15 +67,7 @@ Loader {
"systemUpdate": components.systemUpdateComponent "systemUpdate": components.systemUpdateComponent
} }
if (componentMap[widgetId]) { return componentMap[widgetId] || null
return componentMap[widgetId]
}
var parts = widgetId.split(":")
var pluginId = parts[0]
let pluginMap = PluginService.getWidgetComponents()
return pluginMap[pluginId] || null
} }
function getWidgetVisible(widgetId, dgopAvailable) { function getWidgetVisible(widgetId, dgopAvailable) {
@@ -229,4 +85,4 @@ Loader {
function getWidgetEnabled(enabled) { function getWidgetEnabled(enabled) {
return enabled !== false return enabled !== false
} }
} }

View File

@@ -1,114 +1,126 @@
import QtQuick import QtQuick
import Quickshell.Services.UPower import Quickshell.Services.UPower
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: battery id: battery
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool batteryPopupVisible: false property bool batteryPopupVisible: false
property var popoutTarget: null property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
signal toggleBatteryPopup() signal toggleBatteryPopup()
width: isVertical ? widgetThickness : (batteryContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (batteryColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = batteryArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
visible: true visible: true
content: Component { Column {
Item { id: batteryColumn
implicitWidth: battery.isVerticalOrientation ? (battery.widgetThickness - battery.horizontalPadding * 2) : batteryContent.implicitWidth visible: battery.isVertical
implicitHeight: battery.isVerticalOrientation ? batteryColumn.implicitHeight : (battery.widgetThickness - battery.horizontalPadding * 2) anchors.centerIn: parent
spacing: 1
Column { DankIcon {
id: batteryColumn name: BatteryService.getBatteryIcon()
visible: battery.isVerticalOrientation size: Theme.iconSize - 8
anchors.centerIn: parent color: {
spacing: 1 if (!BatteryService.batteryAvailable) {
return Theme.surfaceText
DankIcon {
name: BatteryService.getBatteryIcon()
size: Theme.barIconSize(battery.barThickness)
color: {
if (!BatteryService.batteryAvailable) {
return Theme.surfaceText
}
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error
}
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary
}
return Theme.surfaceText
}
anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { if (BatteryService.isLowBattery && !BatteryService.isCharging) {
text: BatteryService.batteryLevel.toString() return Theme.error
font.pixelSize: Theme.barTextSize(battery.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: BatteryService.batteryAvailable
}
}
Row {
id: batteryContent
visible: !battery.isVerticalOrientation
anchors.centerIn: parent
spacing: SettingsData.dankBarNoBackground ? 1 : 2
DankIcon {
name: BatteryService.getBatteryIcon()
size: Theme.barIconSize(battery.barThickness, -4)
color: {
if (!BatteryService.batteryAvailable) {
return Theme.surfaceText;
}
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error;
}
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: `${BatteryService.batteryLevel}%`
font.pixelSize: Theme.barTextSize(battery.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable
} }
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary
}
return Theme.surfaceText
} }
anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText {
text: BatteryService.batteryLevel.toString()
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: BatteryService.batteryAvailable
}
}
Row {
id: batteryContent
visible: !battery.isVertical
anchors.centerIn: parent
spacing: SettingsData.dankBarNoBackground ? 1 : 2
DankIcon {
name: BatteryService.getBatteryIcon()
size: Theme.iconSize - 6
color: {
if (!BatteryService.batteryAvailable) {
return Theme.surfaceText;
}
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error;
}
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: `${BatteryService.batteryLevel}%`
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable
}
} }
MouseArea { MouseArea {
x: -battery.leftMargin id: batteryArea
y: -battery.topMargin
width: battery.width + battery.leftMargin + battery.rightMargin anchors.fill: parent
height: battery.height + battery.topMargin + battery.bottomMargin hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: { onPressed: {
if (popoutTarget && popoutTarget.setTriggerPosition) { if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = battery.visualContent.mapToGlobal(0, 0) const globalPos = mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, battery.visualWidth) const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen) popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
} }
toggleBatteryPopup() toggleBatteryPopup();
} }
} }
} }

View File

@@ -1,25 +1,57 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Widgets import qs.Widgets
BasePill { Item {
id: root id: root
property bool isActive: false property bool isActive: false
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "right"
property var clipboardHistoryModal: null property var clipboardHistoryModal: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
content: Component { signal clicked()
Item {
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
DankIcon { width: widgetThickness
anchors.centerIn: parent height: widgetThickness
name: "content_paste"
size: Theme.barIconSize(root.barThickness) MouseArea {
color: Theme.surfaceText id: clipboardArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
root.clicked()
}
}
Rectangle {
id: clipboardContent
anchors.fill: parent
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent"
} }
const baseColor = clipboardArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
}
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
name: "content_paste"
size: widgetThickness - 8
color: Theme.surfaceText
} }
} }
} }

View File

@@ -1,257 +1,267 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: root id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool compactMode: false property bool compactMode: false
property string section: "center"
property var popupTarget: null
property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
signal clockClicked signal clockClicked
content: Component { width: isVertical ? widgetThickness : (clockRow.implicitWidth + horizontalPadding * 2)
height: isVertical ? (clockColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = clockMouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Column {
id: clockColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: -2
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(0)
} else {
const hours = systemClock?.date?.getHours()
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
return String(display).padStart(2, '0').charAt(0)
}
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(1)
} else {
const hours = systemClock?.date?.getHours()
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
return String(display).padStart(2, '0').charAt(1)
}
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
}
Item { Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : clockRow.implicitWidth width: 12
implicitHeight: root.isVerticalOrientation ? clockColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2) height: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Column { Rectangle {
id: clockColumn width: 12
visible: root.isVerticalOrientation height: 1
color: Theme.outlineButton
anchors.centerIn: parent anchors.centerIn: parent
spacing: -2 }
}
Row { Row {
spacing: 0 spacing: 0
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
StyledText { StyledText {
text: { text: {
if (SettingsData.use24HourClock) { const locale = Qt.locale()
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(0) const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
} else { const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const hours = systemClock?.date?.getHours() const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours return value.charAt(0)
return String(display).padStart(2, '0').charAt(0)
}
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(1)
} else {
const hours = systemClock?.date?.getHours()
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
return String(display).padStart(2, '0').charAt(1)
}
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
} }
font.pixelSize: Theme.fontSizeSmall
Row { color: Theme.surfaceText
spacing: 0 font.weight: {
anchors.horizontalCenter: parent.horizontalCenter const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
StyledText { const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0) return dayFirst ? Font.Normal : Font.Light
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
Row {
visible: SettingsData.showSeconds
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
Item {
width: 12
height: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: 12
height: 1
color: Theme.outlineButton
anchors.centerIn: parent
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
return value.charAt(0)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
return value.charAt(1)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(0)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(1)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
} }
width: 9
horizontalAlignment: Text.AlignHCenter
} }
Row { StyledText {
id: clockRow text: {
visible: !root.isVerticalOrientation const locale = Qt.locale()
anchors.centerIn: parent const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
spacing: Theme.spacingS const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
StyledText { return value.charAt(1)
id: timeText
text: {
return systemClock?.date?.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.baseline: dateText.baseline
} }
font.pixelSize: Theme.fontSizeSmall
StyledText { color: Theme.surfaceText
id: middleDot font.weight: {
text: "•" const locale = Qt.locale()
font.pixelSize: Theme.fontSizeSmall const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
color: Theme.outlineButton const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
anchors.baseline: dateText.baseline return dayFirst ? Font.Normal : Font.Light
visible: !SettingsData.clockCompactMode
} }
width: 9
horizontalAlignment: Text.AlignHCenter
}
}
StyledText { Row {
id: dateText spacing: 0
text: { anchors.horizontalCenter: parent.horizontalCenter
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) StyledText {
} text: {
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d") const locale = Qt.locale()
} const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
font.pixelSize: Theme.barTextSize(root.barThickness) const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
color: Theme.surfaceText const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
anchors.verticalCenter: parent.verticalCenter return value.charAt(0)
visible: !SettingsData.clockCompactMode
} }
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
return dayFirst ? Font.Light : Font.Normal
}
width: 9
horizontalAlignment: Text.AlignHCenter
} }
SystemClock { StyledText {
id: systemClock text: {
precision: SystemClock.Seconds const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(1)
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
return dayFirst ? Font.Light : Font.Normal
}
width: 9
horizontalAlignment: Text.AlignHCenter
} }
} }
} }
Row {
id: clockRow
visible: !root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingS
StyledText {
text: {
const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP"
return systemClock?.date?.toLocaleTimeString(Qt.locale(), format)
}
font.pixelSize: Theme.fontSizeMedium - 1
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: !SettingsData.clockCompactMode
}
StyledText {
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat)
}
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
}
font.pixelSize: Theme.fontSizeMedium - 1
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: !SettingsData.clockCompactMode
}
}
SystemClock {
id: systemClock
precision: SystemClock.Seconds
}
MouseArea { MouseArea {
x: -root.leftMargin id: clockMouseArea
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin anchors.fill: parent
height: root.height + root.topMargin + root.bottomMargin hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: { onPressed: {
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) { if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToGlobal(0, 0) const globalPos = mapToGlobal(0, 0)
const currentScreen = root.parentScreen || Screen const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, root.visualWidth) const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen) popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
} }
root.clockClicked() root.clockClicked()
} }
} }
} }

View File

@@ -1,35 +1,54 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: root id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool isActive: false property bool isActive: false
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
signal colorPickerRequested() signal clicked()
content: Component { width: isVertical ? widgetThickness : (colorPickerIcon.width + horizontalPadding * 2)
Item { height: isVertical ? (colorPickerIcon.height + horizontalPadding * 2) : widgetThickness
implicitWidth: root.widgetThickness - root.horizontalPadding * 2 radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
implicitHeight: root.widgetThickness - root.horizontalPadding * 2 color: {
if (SettingsData.dankBarNoBackground) {
DankIcon { return "transparent";
anchors.centerIn: parent
name: "palette"
size: Theme.barIconSize(root.barThickness, -4)
color: root.isActive ? Theme.primary : Theme.surfaceText
}
} }
const baseColor = colorPickerArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
DankIcon {
id: colorPickerIcon
anchors.centerIn: parent
name: "palette"
size: Theme.iconSize - 6
color: colorPickerArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
} }
MouseArea { MouseArea {
z: 1 id: colorPickerArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: { onPressed: {
root.colorPickerRequested() root.colorPickerRequested();
} }
} }
}
signal colorPickerRequested()
}

View File

@@ -1,245 +1,268 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: root id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool isActive: false property bool isActive: false
property var popoutTarget: null property string section: "right"
property var popupTarget: null
property var parentScreen: null
property var widgetData: null property var widgetData: null
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
content: Component { signal clicked()
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : controlIndicators.implicitWidth
implicitHeight: root.isVerticalOrientation ? controlColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column { width: isVertical ? widgetThickness : (controlIndicators.implicitWidth + horizontalPadding * 2)
id: controlColumn height: isVertical ? (controlColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
visible: root.isVerticalOrientation radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
anchors.centerIn: parent color: {
spacing: Theme.spacingXS if (SettingsData.dankBarNoBackground) {
return "transparent";
}
DankIcon { const baseColor = controlCenterArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
name: { return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
if (NetworkService.wifiToggling) { }
return "sync"
}
if (NetworkService.networkStatus === "ethernet") { Column {
return "lan" id: controlColumn
} visible: root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingXS
return NetworkService.wifiSignalIcon DankIcon {
} name: {
size: Theme.barIconSize(root.barThickness) if (NetworkService.wifiToggling) {
color: { return "sync"
if (NetworkService.wifiToggling) {
return Theme.primary
}
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
}
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
} }
DankIcon { if (NetworkService.networkStatus === "ethernet") {
name: "bluetooth" return "lan"
size: Theme.barIconSize(root.barThickness)
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
} }
Rectangle { return NetworkService.wifiSignalIcon
width: audioIconV.implicitWidth + 4 }
height: audioIconV.implicitHeight + 4 size: Theme.iconSize - 8
color: "transparent" color: {
anchors.horizontalCenter: parent.horizontalCenter if (NetworkService.wifiToggling) {
visible: root.showAudioIcon return Theme.primary
}
DankIcon { return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
id: audioIconV }
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showNetworkIcon
}
name: { DankIcon {
if (AudioService.sink && AudioService.sink.audio) { name: "bluetooth"
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) { size: Theme.iconSize - 8
return "volume_off" color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
} else if (AudioService.sink.audio.volume * 100 < 33) { anchors.horizontalCenter: parent.horizontalCenter
return "volume_down" visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
} else { }
return "volume_up"
} Rectangle {
} width: audioIconV.implicitWidth + 4
height: audioIconV.implicitHeight + 4
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showAudioIcon
DankIcon {
id: audioIconV
name: {
if (AudioService.sink && AudioService.sink.audio) {
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off"
} else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down"
} else {
return "volume_up" return "volume_up"
} }
size: Theme.barIconSize(root.barThickness)
color: Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
let newVolume
if (delta > 0) {
newVolume = Math.min(100, currentVolume + 5)
} else {
newVolume = Math.max(0, currentVolume - 5)
}
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = false
AudioService.sink.audio.volume = newVolume / 100
}
wheelEvent.accepted = true
}
} }
return "volume_up"
} }
size: Theme.iconSize - 8
DankIcon { color: Theme.surfaceText
name: "settings" anchors.centerIn: parent
size: Theme.barIconSize(root.barThickness)
color: root.isActive ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
}
} }
Row { MouseArea {
id: controlIndicators anchors.fill: parent
visible: !root.isVerticalOrientation hoverEnabled: true
anchors.centerIn: parent acceptedButtons: Qt.NoButton
spacing: Theme.spacingXS onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y
DankIcon { let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
id: networkIcon let newVolume
if (delta > 0) {
name: { newVolume = Math.min(100, currentVolume + 5)
if (NetworkService.wifiToggling) { } else {
return "sync"; newVolume = Math.max(0, currentVolume - 5)
}
if (NetworkService.networkStatus === "ethernet") {
return "lan";
}
return NetworkService.wifiSignalIcon;
} }
size: Theme.barIconSize(root.barThickness) if (AudioService.sink && AudioService.sink.audio) {
color: { AudioService.sink.audio.muted = false
if (NetworkService.wifiToggling) { AudioService.sink.audio.volume = newVolume / 100
return Theme.primary; AudioService.volumeChanged()
}
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
} }
anchors.verticalCenter: parent.verticalCenter wheelEvent.accepted = true
visible: root.showNetworkIcon && NetworkService.networkAvailable
}
DankIcon {
id: bluetoothIcon
name: "bluetooth"
size: Theme.barIconSize(root.barThickness)
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
}
Rectangle {
width: audioIcon.implicitWidth + 4
height: audioIcon.implicitHeight + 4
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
visible: root.showAudioIcon
DankIcon {
id: audioIcon
name: {
if (AudioService.sink && AudioService.sink.audio) {
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off";
} else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down";
} else {
return "volume_up";
}
}
return "volume_up";
}
size: Theme.barIconSize(root.barThickness)
color: Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
id: audioWheelArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y;
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
let newVolume;
if (delta > 0) {
newVolume = Math.min(100, currentVolume + 5);
} else {
newVolume = Math.max(0, currentVolume - 5);
}
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = false;
AudioService.sink.audio.volume = newVolume / 100;
}
wheelEvent.accepted = true;
}
}
}
DankIcon {
name: "mic"
size: Theme.barIconSize(root.barThickness)
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: false
}
DankIcon {
name: "settings"
size: Theme.barIconSize(root.barThickness)
color: root.isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
} }
} }
} }
DankIcon {
name: "settings"
size: Theme.iconSize - 8
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
}
}
Row {
id: controlIndicators
visible: !root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
id: networkIcon
name: {
if (NetworkService.wifiToggling) {
return "sync";
}
if (NetworkService.networkStatus === "ethernet") {
return "lan";
}
return NetworkService.wifiSignalIcon;
}
size: Theme.iconSize - 8
color: {
if (NetworkService.wifiToggling) {
return Theme.primary;
}
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
}
anchors.verticalCenter: parent.verticalCenter
visible: root.showNetworkIcon
}
DankIcon {
id: bluetoothIcon
name: "bluetooth"
size: Theme.iconSize - 8
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
}
Rectangle {
width: audioIcon.implicitWidth + 4
height: audioIcon.implicitHeight + 4
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
visible: root.showAudioIcon
DankIcon {
id: audioIcon
name: {
if (AudioService.sink && AudioService.sink.audio) {
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off";
} else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down";
} else {
return "volume_up";
}
}
return "volume_up";
}
size: Theme.iconSize - 8
color: Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
id: audioWheelArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y;
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
let newVolume;
if (delta > 0) {
newVolume = Math.min(100, currentVolume + 5);
} else {
newVolume = Math.max(0, currentVolume - 5);
}
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = false;
AudioService.sink.audio.volume = newVolume / 100;
AudioService.volumeChanged();
}
wheelEvent.accepted = true;
}
}
}
DankIcon {
name: "mic"
size: Theme.iconSize - 8
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: false // TODO: Add mic detection
}
// Fallback settings icon when all other icons are hidden
DankIcon {
name: "settings"
size: Theme.iconSize - 8
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
}
} }
MouseArea { MouseArea {
x: -root.leftMargin id: controlCenterArea
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin anchors.fill: parent
height: root.height + root.topMargin + root.bottomMargin hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: { onPressed: {
if (popoutTarget && popoutTarget.setTriggerPosition) { if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToGlobal(0, 0) const globalPos = mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth) const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen) popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
} }
root.clicked() root.clicked();
} }
} }
} }

View File

@@ -1,20 +1,35 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: root id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool showPercentage: true property bool showPercentage: true
property bool showIcon: true property bool showIcon: true
property var toggleProcessList property var toggleProcessList
property var popoutTarget: null property string section: "right"
property var widgetData: null property var popupTarget: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
width: isVertical ? widgetThickness : (cpuContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (cpuColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = cpuArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Component.onCompleted: { Component.onCompleted: {
DgopService.addRef(["cpu"]); DgopService.addRef(["cpu"]);
} }
@@ -22,119 +37,120 @@ BasePill {
DgopService.removeRef(["cpu"]); DgopService.removeRef(["cpu"]);
} }
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? cpuColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: cpuColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "memory"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--";
}
return DgopService.cpuUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: cpuContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "memory"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--%";
}
return DgopService.cpuUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100%"
}
width: root.minimumWidth ? Math.max(cpuBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
MouseArea { MouseArea {
id: cpuArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: { onPressed: {
if (popoutTarget && popoutTarget.setTriggerPosition) { if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToGlobal(0, 0) const globalPos = mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth) const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen) popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
} }
DgopService.setSortBy("cpu"); DgopService.setSortBy("cpu");
if (root.toggleProcessList) { if (root.toggleProcessList) {
root.toggleProcessList(); root.toggleProcessList();
} }
} }
} }
Column {
id: cpuColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "memory"
size: Theme.iconSize - 8
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--";
}
return DgopService.cpuUsage.toFixed(0);
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: cpuContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "memory"
size: Theme.iconSize - 8
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--%";
}
return DgopService.cpuUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
text: "100%"
}
width: Math.max(cpuBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
} }

View File

@@ -1,20 +1,35 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: root id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool showPercentage: true property bool showPercentage: true
property bool showIcon: true property bool showIcon: true
property var toggleProcessList property var toggleProcessList
property var popoutTarget: null property string section: "right"
property var widgetData: null property var popupTarget: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
width: isVertical ? widgetThickness : (cpuTempContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (cpuTempColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = cpuTempArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Component.onCompleted: { Component.onCompleted: {
DgopService.addRef(["cpu"]); DgopService.addRef(["cpu"]);
} }
@@ -22,119 +37,121 @@ BasePill {
DgopService.removeRef(["cpu"]); DgopService.removeRef(["cpu"]);
} }
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuTempContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? cpuTempColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: cpuTempColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "device_thermostat"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--";
}
return Math.round(DgopService.cpuTemperature).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: cpuTempContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "device_thermostat"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--°";
}
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100°"
}
width: root.minimumWidth ? Math.max(tempBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
MouseArea { MouseArea {
id: cpuTempArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: { onPressed: {
if (popoutTarget && popoutTarget.setTriggerPosition) { if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToGlobal(0, 0) const globalPos = mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth) const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen) popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
} }
DgopService.setSortBy("cpu"); DgopService.setSortBy("cpu");
if (root.toggleProcessList) { if (root.toggleProcessList) {
root.toggleProcessList(); root.toggleProcessList();
} }
} }
} }
Column {
id: cpuTempColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "memory"
size: Theme.iconSize - 8
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--";
}
return Math.round(DgopService.cpuTemperature).toString();
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: cpuTempContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "memory"
size: Theme.iconSize - 8
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--°";
}
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
text: "100°"
}
width: Math.max(tempBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
} }

View File

@@ -1,36 +1,43 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: root id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property var widgetData: null property var widgetData: null
property var parentScreen: null
property real widgetThickness: 30
property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/" property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/"
property bool isHovered: mouseArea.containsMouse readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
property var selectedMount: { property var selectedMount: {
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) { if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
return null return null
} }
// Force re-evaluation when mountPath changes
const currentMountPath = root.mountPath || "/" const currentMountPath = root.mountPath || "/"
// First try to find exact match
for (let i = 0; i < DgopService.diskMounts.length; i++) { for (let i = 0; i < DgopService.diskMounts.length; i++) {
if (DgopService.diskMounts[i].mount === currentMountPath) { if (DgopService.diskMounts[i].mount === currentMountPath) {
return DgopService.diskMounts[i] return DgopService.diskMounts[i]
} }
} }
// Fallback to root
for (let i = 0; i < DgopService.diskMounts.length; i++) { for (let i = 0; i < DgopService.diskMounts.length; i++) {
if (DgopService.diskMounts[i].mount === "/") { if (DgopService.diskMounts[i].mount === "/") {
return DgopService.diskMounts[i] return DgopService.diskMounts[i]
} }
} }
// Last resort - first mount
return DgopService.diskMounts[0] || null return DgopService.diskMounts[0] || null
} }
@@ -42,6 +49,17 @@ BasePill {
return parseFloat(percentStr) || 0 return parseFloat(percentStr) || 0
} }
width: isVertical ? widgetThickness : (diskContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (diskColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent"
}
const baseColor = Theme.widgetBaseBackgroundColor
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
}
Component.onCompleted: { Component.onCompleted: {
DgopService.addRef(["diskmounts"]) DgopService.addRef(["diskmounts"])
} }
@@ -51,6 +69,7 @@ BasePill {
Connections { Connections {
function onWidgetDataChanged() { function onWidgetDataChanged() {
// Force property re-evaluation by triggering change detection
root.mountPath = Qt.binding(() => { root.mountPath = Qt.binding(() => {
return (root.widgetData && root.widgetData.mountPath !== undefined) ? root.widgetData.mountPath : "/" return (root.widgetData && root.widgetData.mountPath !== undefined) ? root.widgetData.mountPath : "/"
}) })
@@ -62,18 +81,21 @@ BasePill {
const currentMountPath = root.mountPath || "/" const currentMountPath = root.mountPath || "/"
// First try to find exact match
for (let i = 0; i < DgopService.diskMounts.length; i++) { for (let i = 0; i < DgopService.diskMounts.length; i++) {
if (DgopService.diskMounts[i].mount === currentMountPath) { if (DgopService.diskMounts[i].mount === currentMountPath) {
return DgopService.diskMounts[i] return DgopService.diskMounts[i]
} }
} }
// Fallback to root
for (let i = 0; i < DgopService.diskMounts.length; i++) { for (let i = 0; i < DgopService.diskMounts.length; i++) {
if (DgopService.diskMounts[i].mount === "/") { if (DgopService.diskMounts[i].mount === "/") {
return DgopService.diskMounts[i] return DgopService.diskMounts[i]
} }
} }
// Last resort - first mount
return DgopService.diskMounts[0] || null return DgopService.diskMounts[0] || null
}) })
} }
@@ -81,112 +103,6 @@ BasePill {
target: SettingsData target: SettingsData
} }
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : diskContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? diskColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: diskColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "storage"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.diskUsagePercent > 90) {
return Theme.tempDanger
}
if (root.diskUsagePercent > 75) {
return Theme.tempWarning
}
return Theme.surfaceText
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--"
}
return root.diskUsagePercent.toFixed(0)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: diskContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "storage"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.diskUsagePercent > 90) {
return Theme.tempDanger
}
if (root.diskUsagePercent > 75) {
return Theme.tempWarning
}
return Theme.surfaceText
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (!root.selectedMount) {
return "--"
}
return root.selectedMount.mount
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
}
StyledText {
text: {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--%"
}
return root.diskUsagePercent.toFixed(0) + "%"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: diskBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100%"
}
width: Math.max(diskBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
Loader { Loader {
id: tooltipLoader id: tooltipLoader
active: false active: false
@@ -194,12 +110,11 @@ BasePill {
} }
MouseArea { MouseArea {
id: mouseArea id: diskArea
z: 1
anchors.fill: parent anchors.fill: parent
hoverEnabled: root.isVerticalOrientation hoverEnabled: root.isVertical
onEntered: { onEntered: {
if (root.isVerticalOrientation && root.selectedMount) { if (root.isVertical && root.selectedMount) {
tooltipLoader.active = true tooltipLoader.active = true
if (tooltipLoader.item) { if (tooltipLoader.item) {
const globalPos = mapToGlobal(width / 2, height / 2) const globalPos = mapToGlobal(width / 2, height / 2)
@@ -220,4 +135,107 @@ BasePill {
tooltipLoader.active = false tooltipLoader.active = false
} }
} }
}
Column {
id: diskColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "storage"
size: Theme.iconSize - 8
color: {
if (root.diskUsagePercent > 90) {
return Theme.tempDanger
}
if (root.diskUsagePercent > 75) {
return Theme.tempWarning
}
return Theme.surfaceText
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--"
}
return root.diskUsagePercent.toFixed(0)
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: diskContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "storage"
size: Theme.iconSize - 8
color: {
if (root.diskUsagePercent > 90) {
return Theme.tempDanger
}
if (root.diskUsagePercent > 75) {
return Theme.tempWarning
}
return Theme.surfaceText
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (!root.selectedMount) {
return "--"
}
return root.selectedMount.mount
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
}
StyledText {
text: {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--%"
}
return root.diskUsagePercent.toFixed(0) + "%"
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: diskBaseline
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
text: "100%"
}
width: Math.max(diskBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}

View File

@@ -4,20 +4,24 @@ import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Hyprland import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: root id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property var parentScreen
property bool compactMode: SettingsData.focusedWindowCompactMode property bool compactMode: SettingsData.focusedWindowCompactMode
property int availableWidth: 400 property int availableWidth: 400
property real widgetThickness: 30
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
readonly property int baseWidth: contentRow.implicitWidth + horizontalPadding * 2
readonly property int maxNormalWidth: 456 readonly property int maxNormalWidth: 456
readonly property int maxCompactWidth: 288 readonly property int maxCompactWidth: 288
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property var activeDesktopEntry: null property var activeDesktopEntry: null
property bool isHovered: mouseArea.containsMouse
Component.onCompleted: { Component.onCompleted: {
updateDesktopEntry() updateDesktopEntry()
@@ -69,157 +73,161 @@ BasePill {
return false return false
} }
try { const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
if (!Hyprland.toplevels) return false const activeHyprToplevel = hyprlandToplevels.find(t => t.wayland === activeWindow)
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
const activeHyprToplevel = hyprlandToplevels.find(t => t?.wayland === activeWindow)
if (!activeHyprToplevel || !activeHyprToplevel.workspace) { if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
return false
}
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
} catch (e) {
console.error("FocusedApp: hasWindowsOnCurrentWorkspace error:", e)
return false return false
} }
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
} }
return activeWindow && activeWindow.title return activeWindow && activeWindow.title
} }
width: !hasWindowsOnCurrentWorkspace ? 0 : (isVertical ? widgetThickness : (compactMode ? Math.min(baseWidth, maxCompactWidth) : Math.min(baseWidth, maxNormalWidth)))
height: !hasWindowsOnCurrentWorkspace ? 0 : (isVertical ? widgetThickness : widgetThickness)
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (!activeWindow || !activeWindow.title) {
return "transparent";
}
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
clip: true
visible: hasWindowsOnCurrentWorkspace visible: hasWindowsOnCurrentWorkspace
content: Component { IconImage {
Item { id: appIcon
implicitWidth: { anchors.centerIn: parent
if (!root.hasWindowsOnCurrentWorkspace) return 0 width: 18
if (root.isVerticalOrientation) return root.widgetThickness - root.horizontalPadding * 2 height: 18
const baseWidth = contentRow.implicitWidth visible: root.isVertical && activeWindow && status === Image.Ready
return compactMode ? Math.min(baseWidth, maxCompactWidth - root.horizontalPadding * 2) : Math.min(baseWidth, maxNormalWidth - root.horizontalPadding * 2) source: {
} if (!activeWindow || !activeWindow.appId) return ""
implicitHeight: root.widgetThickness - root.horizontalPadding * 2 const moddedId = Paths.moddedAppId(activeWindow.appId)
clip: false if (moddedId.toLowerCase().includes("steam_app")) return ""
return Quickshell.iconPath(activeDesktopEntry?.icon, true)
IconImage {
id: appIcon
anchors.centerIn: parent
width: 18
height: 18
visible: root.isVerticalOrientation && activeWindow && status === Image.Ready
source: {
if (!activeWindow || !activeWindow.appId) return ""
const moddedId = Paths.moddedAppId(activeWindow.appId)
if (moddedId.toLowerCase().includes("steam_app")) return ""
return Quickshell.iconPath(activeDesktopEntry?.icon, true)
}
smooth: true
mipmap: true
asynchronous: true
}
DankIcon {
anchors.centerIn: parent
size: 18
name: "sports_esports"
color: Theme.surfaceText
visible: {
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return moddedId.toLowerCase().includes("steam_app")
}
}
Text {
anchors.centerIn: parent
visible: {
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId) return false
if (appIcon.status === Image.Ready) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return !moddedId.toLowerCase().includes("steam_app")
}
text: {
if (!activeWindow || !activeWindow.appId) return "?"
if (activeDesktopEntry && activeDesktopEntry.name) {
return activeDesktopEntry.name.charAt(0).toUpperCase()
}
return activeWindow.appId.charAt(0).toUpperCase()
}
font.pixelSize: 10
color: Theme.surfaceText
}
Row {
id: contentRow
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.isVerticalOrientation
StyledText {
id: appText
text: {
if (!activeWindow || !activeWindow.appId) {
return "";
}
const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId);
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, compactMode ? 80 : 180)
visible: !compactMode && text.length > 0
}
StyledText {
text: "•"
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: !compactMode && appText.text && titleText.text
}
StyledText {
id: titleText
text: {
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
const appName = appText.text;
if (!title || !appName) {
return title;
}
if (title.endsWith(" - " + appName)) {
return title.substring(0, title.length - (" - " + appName).length);
}
if (title.endsWith(appName)) {
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
}
return title;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, compactMode ? 280 : 250)
visible: text.length > 0
}
}
} }
smooth: true
mipmap: true
asynchronous: true
}
DankIcon {
anchors.centerIn: parent
size: 18
name: "sports_esports"
color: Theme.surfaceText
visible: {
if (!root.isVertical || !activeWindow || !activeWindow.appId) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return moddedId.toLowerCase().includes("steam_app")
}
}
Text {
anchors.centerIn: parent
visible: {
if (!root.isVertical || !activeWindow || !activeWindow.appId) return false
if (appIcon.status === Image.Ready) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return !moddedId.toLowerCase().includes("steam_app")
}
text: {
if (!activeWindow || !activeWindow.appId) return "?"
if (activeDesktopEntry && activeDesktopEntry.name) {
return activeDesktopEntry.name.charAt(0).toUpperCase()
}
return activeWindow.appId.charAt(0).toUpperCase()
}
font.pixelSize: 10
color: Theme.surfaceText
font.weight: Font.Medium
}
Row {
id: contentRow
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.isVertical
StyledText {
id: appText
text: {
if (!activeWindow || !activeWindow.appId) {
return "";
}
const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId);
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, compactMode ? 80 : 180)
visible: !compactMode && text.length > 0
}
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: !compactMode && appText.text && titleText.text
}
StyledText {
id: titleText
text: {
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
const appName = appText.text;
if (!title || !appName) {
return title;
}
if (title.endsWith(" - " + appName)) {
return title.substring(0, title.length - (" - " + appName).length);
}
if (title.endsWith(appName)) {
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
}
return title;
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, compactMode ? 280 : 250)
visible: text.length > 0
}
} }
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: root.isVerticalOrientation hoverEnabled: root.isVertical
acceptedButtons: Qt.NoButton
onEntered: { onEntered: {
if (root.isVerticalOrientation && activeWindow && activeWindow.appId && root.parentScreen) { if (root.isVertical && activeWindow && activeWindow.appId && root.parentScreen) {
tooltipLoader.active = true tooltipLoader.active = true
if (tooltipLoader.item) { if (tooltipLoader.item) {
const globalPos = mapToGlobal(width / 2, height / 2) const globalPos = mapToGlobal(width / 2, height / 2)
@@ -251,4 +259,14 @@ BasePill {
active: false active: false
sourceComponent: DankTooltip {} sourceComponent: DankTooltip {}
} }
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }

View File

@@ -1,20 +1,25 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
BasePill { Rectangle {
id: root id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool showPercentage: true property bool showPercentage: true
property bool showIcon: true property bool showIcon: true
property var toggleProcessList property var toggleProcessList
property var popoutTarget: null property string section: "right"
property var popupTarget: null
property var parentScreen: null
property var widgetData: null property var widgetData: null
property real barThickness: 48
property real widgetThickness: 30
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0 property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
property real displayTemp: { property real displayTemp: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) { if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return 0; return 0;
@@ -28,6 +33,7 @@ BasePill {
} }
function updateWidgetPciId(pciId) { function updateWidgetPciId(pciId) {
// Find and update this widget's pciId in the settings
const sections = ["left", "center", "right"]; const sections = ["left", "center", "right"];
for (let s = 0; s < sections.length; s++) { for (let s = 0; s < sections.length; s++) {
const sectionId = sections[s]; const sectionId = sections[s];
@@ -61,6 +67,17 @@ BasePill {
} }
} }
width: isVertical ? widgetThickness : (gpuTempContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (gpuTempColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = gpuArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Component.onCompleted: { Component.onCompleted: {
DgopService.addRef(["gpu"]); DgopService.addRef(["gpu"]);
if (widgetData && widgetData.pciId) { if (widgetData && widgetData.pciId) {
@@ -74,10 +91,12 @@ BasePill {
if (widgetData && widgetData.pciId) { if (widgetData && widgetData.pciId) {
DgopService.removeGpuPciId(widgetData.pciId); DgopService.removeGpuPciId(widgetData.pciId);
} }
} }
Connections { Connections {
function onWidgetDataChanged() { function onWidgetDataChanged() {
// Force property re-evaluation by triggering change detection
root.selectedGpuIndex = Qt.binding(() => { root.selectedGpuIndex = Qt.binding(() => {
return (root.widgetData && root.widgetData.selectedGpuIndex !== undefined) ? root.widgetData.selectedGpuIndex : 0; return (root.widgetData && root.widgetData.selectedGpuIndex !== undefined) ? root.widgetData.selectedGpuIndex : 0;
}); });
@@ -86,122 +105,122 @@ BasePill {
target: SettingsData target: SettingsData
} }
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : gpuTempContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? gpuTempColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: gpuTempColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--";
}
return Math.round(root.displayTemp).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: gpuTempContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--°";
}
return Math.round(root.displayTemp) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: gpuTempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100°"
}
width: root.minimumWidth ? Math.max(gpuTempBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
MouseArea { MouseArea {
id: gpuArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: { onPressed: {
if (popoutTarget && popoutTarget.setTriggerPosition) { if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToGlobal(0, 0) const globalPos = mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth) const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen) popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
} }
DgopService.setSortBy("cpu"); DgopService.setSortBy("cpu");
if (root.toggleProcessList) { if (root.toggleProcessList) {
root.toggleProcessList(); root.toggleProcessList();
} }
} }
} }
Column {
id: gpuTempColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.iconSize - 8
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--";
}
return Math.round(root.displayTemp).toString();
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: gpuTempContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.iconSize - 8
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--°";
}
return Math.round(root.displayTemp) + "°";
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: gpuTempBaseline
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
text: "100°"
}
width: Math.max(gpuTempBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
Timer { Timer {
id: autoSaveTimer id: autoSaveTimer
@@ -211,10 +230,13 @@ BasePill {
if (DgopService.availableGpus && DgopService.availableGpus.length > 0) { if (DgopService.availableGpus && DgopService.availableGpus.length > 0) {
const firstGpu = DgopService.availableGpus[0]; const firstGpu = DgopService.availableGpus[0];
if (firstGpu && firstGpu.pciId) { if (firstGpu && firstGpu.pciId) {
// Save the first GPU's PCI ID to this widget's settings
updateWidgetPciId(firstGpu.pciId); updateWidgetPciId(firstGpu.pciId);
DgopService.addGpuPciId(firstGpu.pciId); DgopService.addGpuPciId(firstGpu.pciId);
} }
} }
} }
} }
} }

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