1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-02 03:28:28 -04:00

Compare commits

..

38 Commits

Author SHA1 Message Date
purian23 a95206bf41 fix(nix): support declarative greeter auto-login 2026-07-02 15:36:15 -04:00
bbedward 2663bcd96c lock: add blinking cursor for text input
fixes #2316
2026-07-02 15:20:36 -04:00
purian23 e423e17807 fix(greeter): update NixOS declarative configuration
- Report NixOS greeter state from `var/lib/dms-greeter`
- Allows `dms greeter status` use
- Prevent imperative greeter changes on module-managed NixOS systems
- Preserve immutable-distro sync policy
- Closes #2728
2026-07-02 15:18:53 -04:00
bbedward 70fa0ec903 dankbar: fix widget registration during visibility changes
fixes #1956
2026-07-02 15:07:43 -04:00
bbedward db3615805c process list: use onClosed signal to call hide()
fixes #2560
2026-07-02 14:55:09 -04:00
bbedward 8162e60475 clock/calendar: toggle clock enabled/disabled on resume from suspend
fixes #1298
fixes #2705
2026-07-02 14:53:23 -04:00
purian23 5594ec27ff fix(xray): fix dbar autohide w/xray blur
Fixes #2733
2026-07-02 14:08:52 -04:00
bbedward ca050ace9c battery: clamp percentage to 100
fixes #1834
2026-07-02 13:35:26 -04:00
bbedward 093acdbff7 theme: sweep everything that isnt using a spacing token from singleton,
add Theme.XXS
2026-07-02 12:00:34 -04:00
Legend-017 a98417d66b fix(spotlight): select last query text on open; last-query persistence in connected frame mode (#2726)
* fix(spotlight): Fixes 'remember last query' being ignored in connected frame mode."

* fix(spotlight): select remembered query text instead of placing cursor at end
2026-07-02 11:39:35 -04:00
Youseffo13 83b9cdcb27 fix(ui): resolve ui inconsistencies and missing i18n strings (#2719)
* Update WorkspacesTab.qml

* Update SettingsButtonGroupRow.qml

* Update WallpaperTab.qml

* Update WidgetsTabSection.qml

* add missing tooltips

* Update WallpaperTab.qml

* Update WorkspacesTab.qml
2026-07-02 11:27:15 -04:00
purian23 4ae8ef927c refactor(launcher): invert framemode emerge from top position 2026-07-02 11:18:33 -04:00
bbedward 2128e6f14e compositor/xray: re-add manual bar xray toggle 2026-07-02 10:59:34 -04:00
bbedward 9ff085c46a niri/xray: make bar xray implicit, add conflict detection for
configuration
2026-07-02 10:05:23 -04:00
purian23 57d08f6b3b refactor(Xray): Update Xray & standalone to frame transitions
- Fix dbar autohide with Xray options that could have blocked bar area content
- Fixes #2729
2026-07-01 21:39:47 -04:00
purian23 7964b66827 feat(Xray): Automatic Xray blur effects for Niri & Hyprland layouts
- Utilizes include layout within niri configs & hyprland's layout layer rules for lua configs
2026-07-01 16:15:02 -04:00
bbedward 38a03a4485 hyprland: general focus fixes related to #2561 2026-06-30 23:27:25 -04:00
purian23 a1727d18fd refactor(transparency): stop overwriting dms settings buttons/sliders by default
- Fixes #2692
2026-06-30 17:01:49 -04:00
purian23 aabcef0c9c refactor(theme): decouple foreground layers dep on blur on/off 2026-06-30 16:57:49 -04:00
purian23 19406e99b9 refactor(include): Add missing layout include & normalize config banners
- The Display config process ID now includes the output name in NiriService
2026-06-30 14:48:09 -04:00
bbedward 5432d264f1 ci: add guards to workflows 2026-06-30 13:15:01 -04:00
bbedward d704a0ba3d wallpaper: migrate auto-cycling scheduling to backend 2026-06-30 13:11:29 -04:00
bbedward 161118122e launcher: fix hyprland focus on context menu
fixes #2714
2026-06-30 10:04:44 -04:00
bbedward 9d550d7c11 desktop widgets: ensure creation order based on configured order,
improve drag and drop behavior in settings
fixes #2715
2026-06-30 09:51:00 -04:00
bbedward e40faaa442 calendar: use surfaceVariantText for adjacent month day numbers 2026-06-29 16:14:41 -04:00
bbedward 894efa58a3 theme: large consolidation of colors into Theme.qml, add
surfaceContainerLow/Lowest
2026-06-29 15:50:42 -04:00
bbedward 50570023ff ui: cleanup some anti patterns 2026-06-29 12:39:56 -04:00
bbedward f293def3a8 welcome page: fix to not shell our to external dms IPC 2026-06-29 12:12:17 -04:00
Huỳnh Thiện Lộc de3e8cffa0 feat(mpris): allow excluding specific media players by identity (#2712)
* feat(mpris): allow excluding specific media players by identity

* chore(translation): update settings search index and clean trailing whitespace
2026-06-29 12:01:19 -04:00
bbedward a874f76d72 popout: fix texture sizing during animation 2026-06-29 08:21:21 -04:00
bbedward 424d4ed701 run pre-commit fixes 2026-06-29 08:01:38 -04:00
purian23 8f2a50c00e workflow(void): update the cron schedules 2026-06-28 21:23:07 -04:00
purian23 46cddb965f distro(Void): Require dbus by default 2026-06-28 21:17:11 -04:00
purian23 1ca64abad9 void: remove dbl dms-git transform 2026-06-28 20:44:17 -04:00
purian23 32d68a034f void: automate XBPS key registration 2026-06-28 20:36:15 -04:00
purian23 1a176920d4 distros(Void): refactor to read danklinux repos for local builds 2026-06-28 20:10:49 -04:00
purian23 afa6fd9e2e distros(Void): Prep DMS to host Void packages 2026-06-28 19:54:24 -04:00
purian23 6bee1b2c86 feat(HoverMode): implement hover popout & launcher functionality in all modes
- New Hover toggle found in DankBar Settings
- New Hover to Reveal Launcher in FrameTab Settings
2026-06-27 22:47:38 -04:00
293 changed files with 6787 additions and 3367 deletions
+1
View File
@@ -24,6 +24,7 @@ jobs:
check-updates:
name: Check for updates
runs-on: ubuntu-latest
if: github.repository == 'AvengeMedia/DankMaterialShell'
outputs:
has_updates: ${{ steps.check.outputs.has_updates }}
+1
View File
@@ -24,6 +24,7 @@ jobs:
check-updates:
name: Check package/series updates
runs-on: ubuntu-latest
if: github.repository == 'AvengeMedia/DankMaterialShell'
outputs:
has_updates: ${{ steps.check.outputs.has_updates }}
+268
View File
@@ -0,0 +1,268 @@
name: Void Linux XBPS Repository
on:
schedule:
- cron: "0 2,5,14,17,20,23 * * *" # 9am, 12pm, 3pm, 6pm, 9pm, 12am EST (UTC times shown)
release:
types: [published]
workflow_dispatch:
inputs:
force_rebuild:
description: 'Force rebuilding packages even if they already exist in the repository'
type: boolean
required: false
default: false
build_git:
description: 'Build dms-git package'
type: boolean
required: false
default: true
build_dms:
description: 'Build stable dms package'
type: boolean
required: false
default: true
build_greeter:
description: 'Build stable dms-greeter package'
type: boolean
required: false
default: true
permissions:
contents: write
jobs:
build-and-deploy:
name: Build & Deploy XBPS packages
runs-on: ubuntu-latest
if: github.repository == 'AvengeMedia/DankMaterialShell'
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Checkout gh-pages branch
run: |
git clone --branch gh-pages https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git gh-pages-repo || {
echo "⚠️ gh-pages branch not found or empty, initializing a new one..."
mkdir gh-pages-repo
cd gh-pages-repo
git init
git checkout -b gh-pages
git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
cd ..
}
- name: Install XBPS Static Utilities
run: |
mkdir -p ${{ github.workspace }}/xbps
curl -s -L https://repo-default.voidlinux.org/static/xbps-static-latest.x86_64-musl.tar.xz | tar -xJ -C ${{ github.workspace }}/xbps
echo "${{ github.workspace }}/xbps/usr/bin" >> $GITHUB_PATH
- name: Clone void-packages
run: |
git clone --depth=1 https://github.com/void-linux/void-packages.git
- name: Inject templates
run: |
cp -R distro/void/srcpkgs/dms void-packages/srcpkgs/
cp -R distro/void/srcpkgs/dms-greeter void-packages/srcpkgs/
cp -R distro/void/srcpkgs/dms-git void-packages/srcpkgs/
- name: Enable unprivileged user namespaces (Ubuntu 24.04)
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true
- name: Bootstrap xbps-src
run: |
cd void-packages
./xbps-src binary-bootstrap
- name: Configure signing keys and trust
run: |
# Write private key; extract and register the public key
echo "${{ secrets.XBPS_PRIVATE_KEY }}" > /tmp/xbps_privkey.pem
chmod 600 /tmp/xbps_privkey.pem
# Extract public key in PEM format
openssl rsa -in /tmp/xbps_privkey.pem -pubout -out /tmp/dms-key.pub
rm -f /tmp/xbps_privkey.pem
# Compute MD5 fingerprint in colon-separated hex format
FINGERPRINT=$(openssl rsa -pubin -in /tmp/dms-key.pub -outform DER 2>/dev/null | openssl dgst -md5 -c | tr '[:upper:]' '[:lower:]' | awk '{print $NF}')
# Format key in XML property list (plist) format as expected by xbps
mkdir -p /tmp/keys
cat <<EOF > "/tmp/keys/${FINGERPRINT}.plist"
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>public-key</key>
<data>$(base64 -w0 /tmp/dms-key.pub)</data>
<key>public-key-size</key>
<integer>4096</integer>
<key>signature-by</key>
<string>AvengeMedia</string>
</dict>
</plist>
EOF
# Copy keys to all host and chroot trust stores
sudo mkdir -p /var/db/xbps/keys
sudo cp "/tmp/keys/${FINGERPRINT}.plist" "/var/db/xbps/keys/${FINGERPRINT}.plist"
mkdir -p void-packages/masterdir/var/db/xbps/keys
cp "/tmp/keys/${FINGERPRINT}.plist" "void-packages/masterdir/var/db/xbps/keys/${FINGERPRINT}.plist"
mkdir -p void-packages/etc/xbps.d/keys
cp "/tmp/keys/${FINGERPRINT}.plist" "void-packages/etc/xbps.d/keys/${FINGERPRINT}.plist"
mkdir -p void-packages/common/repo-keys
cp "/tmp/keys/${FINGERPRINT}.plist" "void-packages/common/repo-keys/${FINGERPRINT}.plist"
rm -rf /tmp/keys /tmp/dms-key.pub
- name: Configure repositories
run: |
# Append the repository to repos-remote templates so xbps-src translates it automatically
echo "repository=https://avengemedia.github.io/DankLinux/current" >> void-packages/etc/xbps.d/repos-remote.conf
echo "repository=https://avengemedia.github.io/DankLinux/current" >> void-packages/etc/xbps.d/repos-remote-x86_64-multilib.conf
# Add any existing compiled packages to the build cache directory to avoid rebuilds
if [ -d "gh-pages-repo/current" ]; then
mkdir -p void-packages/hostdir/binpkgs
cp -L gh-pages-repo/current/*.xbps void-packages/hostdir/binpkgs/ 2>/dev/null || true
xbps-rindex -a void-packages/hostdir/binpkgs/*.xbps 2>/dev/null || true
fi
- name: Build packages
run: |
mkdir -p gh-pages-repo/current
BUILD_DMS="${{ github.event.inputs.build_dms || 'true' }}"
BUILD_GREETER="${{ github.event.inputs.build_greeter || 'true' }}"
BUILD_GIT="${{ github.event.inputs.build_git || 'true' }}"
FORCE_REBUILD="${{ github.event.inputs.force_rebuild || 'false' }}"
if [ "${{ github.event_name }}" = "schedule" ]; then
BUILD_DMS="false"
BUILD_GREETER="false"
BUILD_GIT="true"
fi
if [ "${{ github.event_name }}" = "release" ]; then
BUILD_DMS="true"
BUILD_GREETER="true"
BUILD_GIT="false"
fi
echo "=== Starting Builds ==="
echo "DMS stable build enabled: $BUILD_DMS"
echo "Greeter stable build enabled: $BUILD_GREETER"
echo "Git build enabled: $BUILD_GIT"
echo "Force rebuild: $FORCE_REBUILD"
cd void-packages
# 1. Build dms-git (development package)
if [ "$BUILD_GIT" = "true" ]; then
# Calculate dynamic git version (tag.commits.hash)
GIT_VER=$(git -C .. describe --tags --always | sed 's/^v//; s/-/./g')
echo "🔨 Preparing dms-git version $GIT_VER"
# Stage source tarball in the xbps-src source cache.
# --transform adds a top-level directory so xbps-src can extract
# into $wrksrc (create_wrksrc=yes handles the rest).
SRC_CACHE="hostdir/sources/dms-git-${GIT_VER}"
mkdir -p "$SRC_CACHE"
tar -czf "${SRC_CACHE}/dms-git-${GIT_VER}.tar.gz" \
--exclude=void-packages \
--exclude=gh-pages-repo \
--exclude=.git \
--exclude=danklinux \
-C .. .
CHECKSUM=$(sha256sum "${SRC_CACHE}/dms-git-${GIT_VER}.tar.gz" | cut -d' ' -f1)
# Dynamically patch template version, checksum, and distfiles
sed -i "s/^version=.*/version=${GIT_VER}/" srcpkgs/dms-git/template
sed -i "s/^checksum=.*/checksum=${CHECKSUM}/" srcpkgs/dms-git/template
sed -i "s|^distfiles=.*|distfiles=\"dms-git-${GIT_VER}.tar.gz\"|" srcpkgs/dms-git/template
EXPECTED_GIT_FILE="dms-git-${GIT_VER}_1.x86_64.xbps"
if [ -f "../gh-pages-repo/current/$EXPECTED_GIT_FILE" ] && [ "$FORCE_REBUILD" != "true" ]; then
echo "✅ $EXPECTED_GIT_FILE already exists, skipping build."
else
echo "🔨 Compiling dms-git..."
./xbps-src pkg dms-git
rm -f "../gh-pages-repo/current/${EXPECTED_GIT_FILE}"
cp -L hostdir/binpkgs/dms-git-*.xbps ../gh-pages-repo/current/
fi
fi
# 2. Build stable dms package
if [ "$BUILD_DMS" = "true" ]; then
STABLE_VER=$(grep -E '^version=' srcpkgs/dms/template | cut -d= -f2 | tr -d '"')
STABLE_REV=$(grep -E '^revision=' srcpkgs/dms/template | cut -d= -f2 | tr -d '"')
EXPECTED_DMS_FILE="dms-${STABLE_VER}_${STABLE_REV}.x86_64.xbps"
if [ -f "../gh-pages-repo/current/$EXPECTED_DMS_FILE" ] && [ "$FORCE_REBUILD" != "true" ]; then
echo "✅ $EXPECTED_DMS_FILE already exists, skipping build."
else
echo "🔨 Compiling dms ($STABLE_VER)..."
./xbps-src pkg dms
rm -f "../gh-pages-repo/current/${EXPECTED_DMS_FILE}"
cp -L hostdir/binpkgs/dms-${STABLE_VER}_${STABLE_REV}.x86_64.xbps ../gh-pages-repo/current/
fi
fi
# 3. Build stable dms-greeter package
if [ "$BUILD_GREETER" = "true" ]; then
GREETER_VER=$(grep -E '^version=' srcpkgs/dms-greeter/template | cut -d= -f2 | tr -d '"')
GREETER_REV=$(grep -E '^revision=' srcpkgs/dms-greeter/template | cut -d= -f2 | tr -d '"')
EXPECTED_GREETER_FILE="dms-greeter-${GREETER_VER}_${GREETER_REV}.x86_64.xbps"
if [ -f "../gh-pages-repo/current/$EXPECTED_GREETER_FILE" ] && [ "$FORCE_REBUILD" != "true" ]; then
echo "✅ $EXPECTED_GREETER_FILE already exists, skipping build."
else
echo "🔨 Compiling dms-greeter ($GREETER_VER)..."
./xbps-src pkg dms-greeter
rm -f "../gh-pages-repo/current/${EXPECTED_GREETER_FILE}"
cp -L hostdir/binpkgs/dms-greeter-${GREETER_VER}_${GREETER_REV}.x86_64.xbps ../gh-pages-repo/current/
fi
fi
- name: Index and sign repository
run: |
cd gh-pages-repo/current
# Clean up any stale or dangling signature files to prevent O_CREAT ENOENT errors
rm -f *.sig2 *.sig
# Guard: nothing to index if no .xbps files exist
if ! ls *.xbps 1>/dev/null 2>&1; then
echo "⚠️ No .xbps files found to index, skipping."
exit 0
fi
# Regenerate repo index
xbps-rindex -a $(pwd)/*.xbps
# Sign repository
echo "${{ secrets.XBPS_PRIVATE_KEY }}" > /tmp/xbps_privkey.pem
chmod 600 /tmp/xbps_privkey.pem
xbps-rindex --sign --signedby "AvengeMedia <AvengeMedia.US@gmail.com>" --privkey /tmp/xbps_privkey.pem $(pwd)
xbps-rindex --sign-pkg --privkey /tmp/xbps_privkey.pem $(pwd)/*.xbps
rm -f /tmp/xbps_privkey.pem
- name: Deploy to gh-pages branch
run: |
cd gh-pages-repo
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add current/
git diff --quiet && git diff --staged --quiet || (git commit -m "Update XBPS packages [skip ci]" && git push origin gh-pages)
+3
View File
@@ -110,6 +110,9 @@ bin/
# Core dumps
core.*
# prek-installed local git hooks (generated from .pre-commit-config.yaml)
.githooks/
# direnv
.envrc
.direnv/
+2
View File
@@ -37,9 +37,11 @@ var resolveIncludeCmd = &cobra.Command{
"cursor.lua",
"windowrules.lua",
"cursor.kdl",
"layout.kdl",
"outputs.kdl",
"binds.kdl",
"cursor.conf",
"layout.conf",
"outputs.conf",
"binds.conf",
}, cobra.ShellCompDirectiveNoFileComp
+124 -10
View File
@@ -32,13 +32,14 @@ var greeterCmd = &cobra.Command{
var (
greeterConfigSyncFn = greeter.SyncDMSConfigs
sharedAuthSyncFn = sharedpam.SyncAuthConfig
greeterIsNixOSFn = greeter.IsNixOS
)
var greeterInstallCmd = &cobra.Command{
Use: "install",
Short: "Install and configure DMS greeter",
Long: "Install greetd and configure it to use DMS as the greeter interface",
PreRunE: preRunPrivileged,
PreRunE: preRunGreeterMutation,
Run: func(cmd *cobra.Command, args []string) {
yes, _ := cmd.Flags().GetBool("yes")
term, _ := cmd.Flags().GetBool("terminal")
@@ -64,6 +65,9 @@ var greeterSyncCmd = &cobra.Command{
Short: "Sync DMS theme and settings with greeter",
Long: "Synchronize your current user's DMS theme, settings, and wallpaper configuration with the login greeter screen. Also updates a per-user cache slot at users/<username>/ for multi-account greeter theme preview.\n\nUse --profile on secondary accounts to sync only your own users/<username>/ slot without sudo or greetd changes.",
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := rejectNixOSGreeterMutation(cmd); err != nil {
return err
}
profile, _ := cmd.Flags().GetBool("profile")
if profile {
return nil
@@ -140,7 +144,7 @@ var greeterEnableCmd = &cobra.Command{
Use: "enable",
Short: "Enable DMS greeter in greetd config",
Long: "Configure greetd to use DMS as the greeter",
PreRunE: preRunPrivileged,
PreRunE: preRunGreeterMutation,
Run: func(cmd *cobra.Command, args []string) {
yes, _ := cmd.Flags().GetBool("yes")
term, _ := cmd.Flags().GetBool("terminal")
@@ -176,7 +180,7 @@ var greeterUninstallCmd = &cobra.Command{
Use: "uninstall",
Short: "Remove DMS greeter configuration and restore previous display manager",
Long: "Disable greetd, remove DMS managed configs, and restore the system to its pre-DMS-greeter state",
PreRunE: preRunPrivileged,
PreRunE: preRunGreeterMutation,
Run: func(cmd *cobra.Command, args []string) {
yes, _ := cmd.Flags().GetBool("yes")
term, _ := cmd.Flags().GetBool("terminal")
@@ -206,6 +210,21 @@ func init() {
greeterUninstallCmd.Flags().BoolP("terminal", "t", false, "Run in a new terminal (for entering sudo password)")
}
func rejectNixOSGreeterMutation(cmd *cobra.Command) error {
if !greeterIsNixOSFn() {
return nil
}
return fmt.Errorf("dms %s is disabled on NixOS because the greeter is managed declaratively\nConfigure the DMS greeter in your NixOS module, then apply the change with your normal nixos-rebuild workflow", normalizeCommandSpec(cmd.CommandPath()))
}
func preRunGreeterMutation(cmd *cobra.Command, args []string) error {
if err := rejectNixOSGreeterMutation(cmd); err != nil {
return err
}
return preRunPrivileged(cmd, args)
}
func syncGreeterConfigsAndAuth(dmsPath, compositor string, logFunc func(string), options sharedpam.SyncAuthOptions, beforeAuth func()) error {
if err := greeterConfigSyncFn(dmsPath, compositor, logFunc, ""); err != nil {
return err
@@ -1415,24 +1434,36 @@ func readDefaultSessionCommand(configPath string) string {
return ""
}
func extractGreeterCacheDirFromCommand(command string) string {
if command == "" {
return greeter.GreeterCacheDir
}
func explicitGreeterCacheDirFromCommand(command string) (string, bool) {
tokens := strings.Fields(command)
for i := 0; i < len(tokens); i++ {
token := strings.Trim(tokens[i], "\"")
if token == "--cache-dir" && i+1 < len(tokens) {
return strings.Trim(tokens[i+1], "\"")
value := strings.Trim(tokens[i+1], "\"")
if value != "" {
return value, true
}
}
if strings.HasPrefix(token, "--cache-dir=") {
value := strings.TrimPrefix(token, "--cache-dir=")
value = strings.Trim(value, "\"")
if value != "" {
return value
return value, true
}
}
}
return "", false
}
const nixOSGreeterStateDir = "/var/lib/dms-greeter"
func greeterStatusStateDir(command string, isNixOS bool) string {
if cacheDir, ok := explicitGreeterCacheDirFromCommand(command); ok {
return cacheDir
}
if isNixOS {
return nixOSGreeterStateDir
}
return greeter.GreeterCacheDir
}
@@ -1569,6 +1600,10 @@ func checkGreeterStatus() error {
fmt.Println("=== DMS Greeter Status ===")
fmt.Println()
if greeterIsNixOSFn() {
return checkNixOSGreeterStatus()
}
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get user home directory: %w", err)
@@ -1632,7 +1667,7 @@ func checkGreeterStatus() error {
fmt.Println(" Run 'dms greeter sync' to set up group membership and permissions")
}
cacheDir := extractGreeterCacheDirFromCommand(configuredCommand)
cacheDir := greeterStatusStateDir(configuredCommand, false)
fmt.Println("\nGreeter Cache Directory:")
fmt.Printf(" Effective cache dir: %s\n", cacheDir)
if cacheDir != greeter.GreeterCacheDir {
@@ -1895,6 +1930,85 @@ func checkGreeterStatus() error {
return nil
}
func checkNixOSGreeterStatus() error {
const configPath = "/etc/greetd/config.toml"
configuredCommand := readDefaultSessionCommand(configPath)
allGood := true
fmt.Println("Greeter Configuration:")
switch {
case strings.Contains(configuredCommand, "dms-greeter"):
fmt.Println(" ✓ DMS greeter command found")
if wrapper := extractGreeterWrapperFromCommand(configuredCommand); wrapper != "" {
fmt.Printf(" Wrapper: %s\n", wrapper)
}
case configuredCommand != "":
fmt.Println(" ⚠ greetd default session does not reference dms-greeter")
allGood = false
default:
fmt.Printf(" No readable DMS command found in %s\n", configPath)
}
fmt.Println(" NixOS manages greeter configuration declaratively; apply changes through your NixOS module.")
stateDir := greeterStatusStateDir(configuredCommand, true)
fmt.Println("\nGreeter State Directory:")
fmt.Printf(" Effective state dir: %s\n", stateDir)
if stateDir == nixOSGreeterStateDir {
fmt.Println(" ✓ Using the NixOS module state path")
}
if stat, err := os.Stat(stateDir); err == nil && stat.IsDir() {
fmt.Printf(" ✓ %s exists\n", stateDir)
} else if os.IsNotExist(err) {
fmt.Printf(" ✗ %s not found\n", stateDir)
fmt.Println(" Rebuild your NixOS configuration after enabling the DMS greeter module.")
allGood = false
} else if err != nil {
fmt.Printf(" ✗ Could not inspect %s: %v\n", stateDir, err)
allGood = false
} else {
fmt.Printf(" ✗ %s is not a directory\n", stateDir)
allGood = false
}
fmt.Println("\nDeclarative Configuration Files:")
configFiles := []struct {
name string
path string
}{
{name: "Settings", path: filepath.Join(stateDir, "settings.json")},
{name: "Session state", path: filepath.Join(stateDir, "session.json")},
{name: "Color theme", path: filepath.Join(stateDir, "colors.json")},
}
for _, configFile := range configFiles {
if stat, err := os.Stat(configFile.path); err == nil && !stat.IsDir() {
fmt.Printf(" ✓ %s: %s\n", configFile.name, configFile.path)
} else if os.IsNotExist(err) {
fmt.Printf(" %s not present (optional; configure configHome/configFiles in the NixOS module)\n", configFile.name)
} else if err != nil {
fmt.Printf(" ⚠ %s could not be inspected: %v\n", configFile.name, err)
} else {
fmt.Printf(" ⚠ %s path is not a regular file: %s\n", configFile.name, configFile.path)
}
}
fmt.Println("\nGroup Membership:")
fmt.Println(" User group membership is managed by NixOS and is not required for declarative theme copies.")
fmt.Println("\nGreeter PAM Authentication:")
fmt.Println(" PAM is managed by NixOS modules.")
fmt.Println(" Configure fingerprint/U2F through security.pam.services.greetd.")
fmt.Println()
if allGood {
fmt.Println("✓ NixOS greeter state looks healthy and is managed declaratively.")
} else {
fmt.Println("⚠ Some issues detected. Update the DMS greeter module and rebuild NixOS; do not run 'dms greeter sync'.")
}
return nil
}
func recentAppArmorGreeterDenials(sampleLimit int) (int, []string, error) {
if sampleLimit <= 0 {
sampleLimit = 3
+56
View File
@@ -3,9 +3,11 @@ package main
import (
"errors"
"reflect"
"strings"
"testing"
sharedpam "github.com/AvengeMedia/DankMaterialShell/core/internal/pam"
"github.com/spf13/cobra"
)
func TestSyncGreeterConfigsAndAuthDelegatesSharedAuth(t *testing.T) {
@@ -85,3 +87,57 @@ func TestSyncGreeterConfigsAndAuthStopsOnConfigError(t *testing.T) {
t.Fatal("expected auth sync not to run after config sync failure")
}
}
func TestGreeterStatusStateDirUsesNixOSDefault(t *testing.T) {
if got := greeterStatusStateDir("", true); got != nixOSGreeterStateDir {
t.Fatalf("greeterStatusStateDir() = %q, want %q", got, nixOSGreeterStateDir)
}
}
func TestGreeterStatusStateDirHonorsExplicitOverrideOnNixOS(t *testing.T) {
command := "dms-greeter --cache-dir /srv/dms-greeter --command niri"
if got := greeterStatusStateDir(command, true); got != "/srv/dms-greeter" {
t.Fatalf("greeterStatusStateDir() = %q, want %q", got, "/srv/dms-greeter")
}
}
func TestRejectNixOSGreeterMutationBlocksImperativeCommands(t *testing.T) {
origGreeterIsNixOSFn := greeterIsNixOSFn
greeterIsNixOSFn = func() bool { return true }
t.Cleanup(func() {
greeterIsNixOSFn = origGreeterIsNixOSFn
})
for _, commandName := range []string{"install", "enable", "sync", "uninstall"} {
t.Run(commandName, func(t *testing.T) {
root := &cobra.Command{Use: "dms"}
greeterCommand := &cobra.Command{Use: "greeter"}
mutationCommand := &cobra.Command{Use: commandName}
root.AddCommand(greeterCommand)
greeterCommand.AddCommand(mutationCommand)
err := rejectNixOSGreeterMutation(mutationCommand)
if err == nil {
t.Fatalf("expected NixOS greeter %s to be rejected", commandName)
}
if !strings.Contains(err.Error(), "dms greeter "+commandName+" is disabled on NixOS") {
t.Fatalf("unexpected error: %v", err)
}
if strings.Contains(err.Error(), "/var/cache/dms-greeter") {
t.Fatalf("NixOS remediation should not recommend the non-NixOS cache path: %v", err)
}
})
}
}
func TestRejectNixOSGreeterMutationAllowsOtherDistros(t *testing.T) {
origGreeterIsNixOSFn := greeterIsNixOSFn
greeterIsNixOSFn = func() bool { return false }
t.Cleanup(func() {
greeterIsNixOSFn = origGreeterIsNixOSFn
})
if err := rejectNixOSGreeterMutation(&cobra.Command{Use: "sync"}); err != nil {
t.Fatalf("expected non-NixOS greeter command to be allowed, got %v", err)
}
}
+24
View File
@@ -0,0 +1,24 @@
package main
import (
"encoding/json"
"testing"
)
func TestDefaultImmutablePolicyAllowsSyncButBlocksEnable(t *testing.T) {
var policyFile cliPolicyFile
if err := json.Unmarshal(defaultCLIPolicyJSON, &policyFile); err != nil {
t.Fatalf("failed to parse embedded CLI policy: %v", err)
}
if policyFile.BlockedCommands == nil {
t.Fatal("embedded CLI policy has no blocked_commands")
}
blocked := normalizeBlockedCommands(*policyFile.BlockedCommands)
if !commandBlockedByPolicy("greeter enable", blocked) {
t.Fatal("expected greeter enable to remain blocked on immutable/image-based systems")
}
if commandBlockedByPolicy("greeter sync", blocked) {
t.Fatal("expected greeter sync to remain available on immutable/image-based systems")
}
}
@@ -13,6 +13,7 @@ hl.config({
input = {
kb_layout = "us",
numlock_by_default = true,
follow_mouse = 0,
touchpad = {
tap_to_click = true,
natural_scroll = true,
+10
View File
@@ -23,6 +23,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/tailscale"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
serverThemes "github.com/AvengeMedia/DankMaterialShell/core/internal/server/themes"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wallpaper"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
)
@@ -56,6 +57,15 @@ func RouteRequest(conn net.Conn, req models.Request) {
return
}
if strings.HasPrefix(req.Method, "wallpaper.") {
if wallpaperManager == nil {
models.RespondError(conn, req.ID, "wallpaper manager not initialized")
return
}
wallpaper.HandleRequest(conn, req, wallpaperManager)
return
}
if strings.HasPrefix(req.Method, "loginctl.") {
if loginctlManager == nil {
models.RespondError(conn, req.ID, "loginctl manager not initialized")
+66 -1
View File
@@ -33,13 +33,14 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/tailscale"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/trayrecovery"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wallpaper"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
)
const APIVersion = 26
const APIVersion = 27
var CLIVersion = "dev"
@@ -73,6 +74,7 @@ var clipboardManager *clipboard.Manager
var dbusManager *serverDbus.Manager
var wlContext *wlcontext.SharedContext
var themeModeManager *thememode.Manager
var wallpaperManager *wallpaper.Manager
var trayRecoveryManager *trayrecovery.Manager
var locationManager *location.Manager
var sysUpdateManager *sysupdate.Manager
@@ -347,6 +349,13 @@ func InitializeThemeModeManager() error {
return nil
}
func InitializeWallpaperManager() error {
wallpaperManager = wallpaper.NewManager()
log.Info("Wallpaper rotation scheduler initialized")
return nil
}
func InitializeTrayRecoveryManager() error {
manager, err := trayrecovery.NewManager()
if err != nil {
@@ -463,6 +472,10 @@ func getCapabilities() Capabilities {
caps = append(caps, "theme.auto")
}
if wallpaperManager != nil {
caps = append(caps, "wallpaper")
}
if dbusManager != nil {
caps = append(caps, "dbus")
}
@@ -529,6 +542,10 @@ func getServerInfo() ServerInfo {
caps = append(caps, "theme.auto")
}
if wallpaperManager != nil {
caps = append(caps, "wallpaper")
}
if locationManager != nil {
caps = append(caps, "location")
}
@@ -841,6 +858,38 @@ func handleSubscribe(conn net.Conn, req models.Request) {
}()
}
if shouldSubscribe("wallpaper") && wallpaperManager != nil {
wg.Add(1)
wallpaperChan := wallpaperManager.Subscribe(clientID + "-wallpaper")
go func() {
defer wg.Done()
defer wallpaperManager.Unsubscribe(clientID + "-wallpaper")
initialState := wallpaperManager.GetState()
select {
case eventChan <- ServiceEvent{Service: "wallpaper", Data: initialState}:
case <-stopChan:
return
}
for {
select {
case state, ok := <-wallpaperChan:
if !ok {
return
}
select {
case eventChan <- ServiceEvent{Service: "wallpaper", Data: state}:
case <-stopChan:
return
}
case <-stopChan:
return
}
}
}()
}
if shouldSubscribe("bluetooth") && bluezManager != nil {
wg.Add(1)
bluezChan := bluezManager.Subscribe(clientID + "-bluetooth")
@@ -1286,6 +1335,9 @@ func cleanupManagers() {
if themeModeManager != nil {
themeModeManager.Close()
}
if wallpaperManager != nil {
wallpaperManager.Close()
}
if trayRecoveryManager != nil {
trayRecoveryManager.Close()
}
@@ -1578,6 +1630,19 @@ func Start(printDocs bool) error {
}()
}
if err := InitializeWallpaperManager(); err != nil {
log.Warnf("Wallpaper scheduler unavailable: %v", err)
} else {
notifyCapabilityChange()
go func() {
<-loginctlReady
if loginctlManager == nil {
return
}
wallpaperManager.WatchLoginctl(loginctlManager)
}()
}
go func() {
<-loginctlReady
if loginctlManager == nil {
@@ -0,0 +1,84 @@
package wallpaper
import (
"encoding/json"
"fmt"
"net"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
)
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
if manager == nil {
models.RespondError(conn, req.ID, "wallpaper manager not initialized")
return
}
switch req.Method {
case "wallpaper.getState":
handleGetState(conn, req, manager)
case "wallpaper.setConfig":
handleSetConfig(conn, req, manager)
case "wallpaper.trigger":
handleTrigger(conn, req, manager)
case "wallpaper.subscribe":
handleSubscribe(conn, req, manager)
default:
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
}
}
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
models.Respond(conn, req.ID, manager.GetState())
}
func handleSetConfig(conn net.Conn, req models.Request, manager *Manager) {
raw, ok := params.Any(req.Params, "config")
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'config' parameter")
return
}
data, err := json.Marshal(raw)
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
manager.SetConfig(config)
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "wallpaper schedule set"})
}
func handleTrigger(conn net.Conn, req models.Request, manager *Manager) {
manager.ResetSchedule(params.StringOpt(req.Params, "target", ""))
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "wallpaper schedule reset"})
}
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
clientID := fmt.Sprintf("client-%p", conn)
stateChan := manager.Subscribe(clientID)
defer manager.Unsubscribe(clientID)
initialState := manager.GetState()
if err := json.NewEncoder(conn).Encode(models.Response[State]{
ID: req.ID,
Result: &initialState,
}); err != nil {
return
}
for state := range stateChan {
if err := json.NewEncoder(conn).Encode(models.Response[State]{
Result: &state,
}); err != nil {
return
}
}
}
+325
View File
@@ -0,0 +1,325 @@
package wallpaper
import (
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
)
type activeSchedule struct {
cfg ScheduleConfig
nextFire time.Time
}
type Manager struct {
config Config
configMutex sync.RWMutex
state *State
stateMutex sync.RWMutex
subscribers syncmap.Map[string, chan State]
stopChan chan struct{}
updateTrigger chan struct{}
resetReq chan string
wg sync.WaitGroup
}
func NewManager() *Manager {
m := &Manager{
config: Config{
Global: ScheduleConfig{Mode: "interval", IntervalSec: 300, Time: "06:00"},
Monitors: map[string]ScheduleConfig{},
},
stopChan: make(chan struct{}),
updateTrigger: make(chan struct{}, 1),
resetReq: make(chan string, 8),
}
m.state = &State{Config: m.getConfig()}
m.wg.Add(1)
go m.schedulerLoop()
return m
}
func (m *Manager) GetState() State {
m.stateMutex.RLock()
defer m.stateMutex.RUnlock()
if m.state == nil {
return State{Config: m.getConfig()}
}
return *m.state
}
func (m *Manager) Subscribe(id string) chan State {
ch := make(chan State, 64)
m.subscribers.Store(id, ch)
return ch
}
func (m *Manager) Unsubscribe(id string) {
if val, ok := m.subscribers.LoadAndDelete(id); ok {
close(val)
}
}
func (m *Manager) SetConfig(config Config) {
if config.Monitors == nil {
config.Monitors = map[string]ScheduleConfig{}
}
m.configMutex.Lock()
if reflect.DeepEqual(m.config, config) {
m.configMutex.Unlock()
return
}
m.config = config
m.configMutex.Unlock()
m.TriggerUpdate()
}
func (m *Manager) ResetSchedule(target string) {
select {
case m.resetReq <- target:
default:
}
}
func (m *Manager) TriggerUpdate() {
select {
case m.updateTrigger <- struct{}{}:
default:
}
}
func (m *Manager) Close() {
select {
case <-m.stopChan:
return
default:
close(m.stopChan)
}
m.wg.Wait()
m.subscribers.Range(func(key string, ch chan State) bool {
close(ch)
m.subscribers.Delete(key)
return true
})
}
func (m *Manager) WatchLoginctl(lm *loginctl.Manager) {
ch := lm.Subscribe("wallpaper")
m.wg.Add(1)
go func() {
defer m.wg.Done()
defer lm.Unsubscribe("wallpaper")
for {
select {
case <-m.stopChan:
return
case state, ok := <-ch:
if !ok {
return
}
if state.PreparingForSleep {
continue
}
m.TriggerUpdate()
}
}
}()
}
func (m *Manager) schedulerLoop() {
defer m.wg.Done()
schedules := map[string]*activeSchedule{}
resets := map[string]bool{}
var seq uint64
var timer *time.Timer
for {
now := time.Now()
config := m.getConfig()
active := activeSchedules(config)
for key := range schedules {
if _, ok := active[key]; !ok {
delete(schedules, key)
}
}
for key, cfg := range active {
s, ok := schedules[key]
switch {
case !ok:
schedules[key] = &activeSchedule{cfg: cfg, nextFire: computeNext(now, cfg)}
case s.cfg != cfg || resets[key]:
s.cfg = cfg
s.nextFire = computeNext(now, cfg)
}
delete(resets, key)
}
var dueKeys []string
for key, s := range schedules {
if !s.nextFire.After(now) {
dueKeys = append(dueKeys, key)
s.nextFire = computeNext(now, s.cfg)
}
}
next, hasNext := soonest(schedules)
if len(dueKeys) == 0 {
m.setState(config, next, seq, "")
}
for _, key := range dueKeys {
seq++
m.setState(config, next, seq, key)
}
waitDur := 24 * time.Hour
if hasNext {
waitDur = time.Until(next)
if waitDur < time.Second {
waitDur = time.Second
}
}
if timer != nil {
timer.Stop()
}
timer = time.NewTimer(waitDur)
select {
case <-m.stopChan:
timer.Stop()
return
case <-m.updateTrigger:
timer.Stop()
case key := <-m.resetReq:
timer.Stop()
resets[key] = true
case <-timer.C:
}
}
}
func (m *Manager) setState(config Config, next time.Time, seq uint64, target string) {
newState := State{Config: config, NextRotation: next, CycleSeq: seq, Target: target}
m.stateMutex.Lock()
if m.state != nil && statesEqual(m.state, &newState) {
m.stateMutex.Unlock()
return
}
m.state = &newState
m.stateMutex.Unlock()
m.notifySubscribers()
}
func (m *Manager) notifySubscribers() {
state := m.GetState()
m.subscribers.Range(func(key string, ch chan State) bool {
select {
case ch <- state:
default:
}
return true
})
}
func (m *Manager) getConfig() Config {
m.configMutex.RLock()
defer m.configMutex.RUnlock()
return m.config
}
func activeSchedules(config Config) map[string]ScheduleConfig {
out := map[string]ScheduleConfig{}
if config.PerMonitor {
for name, cfg := range config.Monitors {
if cfg.Enabled {
out[name] = cfg
}
}
return out
}
if config.Global.Enabled {
out[""] = config.Global
}
return out
}
func computeNext(now time.Time, cfg ScheduleConfig) time.Time {
switch cfg.Mode {
case "time":
return nextDailyTime(now, cfg.Time)
default:
sec := cfg.IntervalSec
if sec < 1 {
sec = 1
}
return now.Add(time.Duration(sec) * time.Second)
}
}
func nextDailyTime(now time.Time, hhmm string) time.Time {
hour, minute, ok := parseHHMM(hhmm)
if !ok {
return now.Add(24 * time.Hour)
}
next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location())
if !next.After(now) {
next = next.Add(24 * time.Hour)
}
return next
}
func parseHHMM(hhmm string) (int, int, bool) {
parts := strings.Split(hhmm, ":")
if len(parts) != 2 {
return 0, 0, false
}
hour, err := strconv.Atoi(parts[0])
if err != nil || hour < 0 || hour > 23 {
return 0, 0, false
}
minute, err := strconv.Atoi(parts[1])
if err != nil || minute < 0 || minute > 59 {
return 0, 0, false
}
return hour, minute, true
}
func soonest(schedules map[string]*activeSchedule) (time.Time, bool) {
var best time.Time
found := false
for _, s := range schedules {
if !found || s.nextFire.Before(best) {
best = s.nextFire
found = true
}
}
return best, found
}
func statesEqual(a, b *State) bool {
switch {
case a == nil || b == nil:
return a == b
case a.CycleSeq != b.CycleSeq:
return false
case a.Target != b.Target:
return false
case !a.NextRotation.Equal(b.NextRotation):
return false
}
return reflect.DeepEqual(a.Config, b.Config)
}
@@ -0,0 +1,106 @@
package wallpaper
import (
"testing"
"time"
)
func TestParseHHMM(t *testing.T) {
cases := []struct {
in string
hour int
minute int
ok bool
}{
{"06:00", 6, 0, true},
{"23:59", 23, 59, true},
{"00:00", 0, 0, true},
{"24:00", 0, 0, false},
{"6:5", 6, 5, true},
{"bad", 0, 0, false},
{"12", 0, 0, false},
{"12:60", 0, 0, false},
}
for _, c := range cases {
hour, minute, ok := parseHHMM(c.in)
if ok != c.ok || (ok && (hour != c.hour || minute != c.minute)) {
t.Errorf("parseHHMM(%q) = (%d, %d, %v), want (%d, %d, %v)", c.in, hour, minute, ok, c.hour, c.minute, c.ok)
}
}
}
func TestComputeNextInterval(t *testing.T) {
now := time.Date(2026, 6, 30, 12, 0, 0, 0, time.UTC)
got := computeNext(now, ScheduleConfig{Mode: "interval", IntervalSec: 300})
if want := now.Add(300 * time.Second); !got.Equal(want) {
t.Errorf("interval next = %v, want %v", got, want)
}
clamped := computeNext(now, ScheduleConfig{Mode: "interval", IntervalSec: 0})
if want := now.Add(time.Second); !clamped.Equal(want) {
t.Errorf("interval clamp = %v, want %v", clamped, want)
}
}
func TestComputeNextTime(t *testing.T) {
now := time.Date(2026, 6, 30, 12, 0, 0, 0, time.UTC)
later := computeNext(now, ScheduleConfig{Mode: "time", Time: "18:00"})
if want := time.Date(2026, 6, 30, 18, 0, 0, 0, time.UTC); !later.Equal(want) {
t.Errorf("time next (today) = %v, want %v", later, want)
}
tomorrow := computeNext(now, ScheduleConfig{Mode: "time", Time: "06:00"})
if want := time.Date(2026, 7, 1, 6, 0, 0, 0, time.UTC); !tomorrow.Equal(want) {
t.Errorf("time next (tomorrow) = %v, want %v", tomorrow, want)
}
}
func TestActiveSchedules(t *testing.T) {
global := activeSchedules(Config{
Global: ScheduleConfig{Enabled: true, Mode: "interval", IntervalSec: 60},
})
if len(global) != 1 {
t.Fatalf("global active = %d, want 1", len(global))
}
if _, ok := global[""]; !ok {
t.Errorf("global active missing global key")
}
perMonitor := activeSchedules(Config{
PerMonitor: true,
Global: ScheduleConfig{Enabled: true},
Monitors: map[string]ScheduleConfig{
"DP-1": {Enabled: true, Mode: "interval", IntervalSec: 60},
"DP-2": {Enabled: false},
},
})
if len(perMonitor) != 1 {
t.Fatalf("per-monitor active = %d, want 1", len(perMonitor))
}
if _, ok := perMonitor["DP-1"]; !ok {
t.Errorf("per-monitor active missing DP-1")
}
}
func TestSchedulerEmitsCycle(t *testing.T) {
m := NewManager()
defer m.Close()
sub := m.Subscribe("test")
defer m.Unsubscribe("test")
m.SetConfig(Config{Global: ScheduleConfig{Enabled: true, Mode: "interval", IntervalSec: 1}})
deadline := time.After(3 * time.Second)
for {
select {
case state := <-sub:
if state.CycleSeq > 0 && state.Target == "" {
return
}
case <-deadline:
t.Fatal("scheduler did not emit a cycle event within 3s")
}
}
}
+23
View File
@@ -0,0 +1,23 @@
package wallpaper
import "time"
type ScheduleConfig struct {
Enabled bool `json:"enabled"`
Mode string `json:"mode"`
IntervalSec int `json:"intervalSec"`
Time string `json:"time"`
}
type Config struct {
PerMonitor bool `json:"perMonitor"`
Global ScheduleConfig `json:"global"`
Monitors map[string]ScheduleConfig `json:"monitors"`
}
type State struct {
Config Config `json:"config"`
NextRotation time.Time `json:"nextRotation"`
CycleSeq uint64 `json:"cycleSeq"`
Target string `json:"target"`
}
+22 -6
View File
@@ -74,6 +74,9 @@ func NewManager(display wlclient.WaylandDisplay, config Config) (*Manager, error
if config.Enabled {
m.post(func() {
if m.controlsInitialized {
return
}
log.Info("Gamma control enabled at startup")
gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
m.availOutputsMu.RLock()
@@ -184,13 +187,26 @@ func (m *Manager) setupRegistry() error {
enabled := m.config.Enabled
m.configMutex.RUnlock()
if enabled && m.controlsInitialized {
m.post(func() {
if err := m.addOutputControl(output); err != nil {
log.Warnf("Failed to add output control: %v", err)
}
})
if !enabled {
return
}
m.post(func() {
if err := m.addOutputControl(output); err != nil {
log.Warnf("gamma: failed to add output control: %v", err)
return
}
if m.controlsInitialized {
return
}
// All outputs had been torn down (monitor sleep/disconnect),
// clearing controlsInitialized. Mark it ready again so the
// gamma_size event that follows control creation drives the
// reapply, instead of waiting for a manual toggle. No explicit
// apply here: applyGamma's dedup would suppress a no-change
// write anyway, and the new control isn't ready until gamma_size.
log.Info("gamma: output returned, re-establishing controls")
m.controlsInitialized = true
})
}
})
+48 -1
View File
@@ -10,6 +10,8 @@ let
inherit (lib) types;
cfg = config.programs.dank-material-shell.greeter;
cfgDms = config.programs.dank-material-shell;
cfgAutoLogin = config.services.displayManager.autoLogin;
sessionData = config.services.displayManager.sessionData;
inherit (config.services.greetd.settings.default_session) user;
@@ -48,6 +50,38 @@ let
} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"}
'';
autoLoginCommand =
pkgs.runCommand "dms-greeter-autologin-command"
{
nativeBuildInputs = [
pkgs.gnugrep
pkgs.coreutils
];
}
''
set -euo pipefail
session="${sessionData.autologinSession}"
desktops="${sessionData.desktops}"
for sessionFile in \
"$desktops/share/wayland-sessions/$session.desktop" \
"$desktops/share/xsessions/$session.desktop"
do
if [ -f "$sessionFile" ]; then
command="$(grep -m1 '^Exec=' "$sessionFile" | cut -d= -f2- || true)"
if [ -n "$command" ]; then
printf '%s\n' "$command" > "$out"
exit 0
fi
fi
done
echo "dms-greeter autologin: could not resolve Exec for session '$session'" >&2
exit 1
'';
jq = lib.getExe pkgs.jq;
in
{
@@ -155,6 +189,13 @@ in
dmsgreeter: user set for greetd default_session ${user} does not exist. Please create it before referencing it.
'';
}
{
assertion = cfgAutoLogin.enable -> sessionData.autologinSession != null;
message = ''
dms-greeter auto-login requires services.displayManager.defaultSession to be set,
or at least one session in services.displayManager.sessionPackages.
'';
}
];
# DMS currently relies on /etc/pam.d/login for lock screen password auth on NixOS.
# Declare security.pam.services.dankshell only if you want to override that runtime fallback.
@@ -165,7 +206,13 @@ in
# };
services.greetd = {
enable = lib.mkDefault true;
settings.default_session.command = lib.mkDefault (lib.getExe greeterScript);
settings = {
default_session.command = lib.mkDefault (lib.getExe greeterScript);
initial_session = lib.mkIf (cfgAutoLogin.enable && (cfgAutoLogin.user != null)) {
inherit (cfgAutoLogin) user;
command = ''${lib.getExe pkgs.bash} -lc "${pkgs.systemd}/bin/systemd-cat $(<${autoLoginCommand})"'';
};
};
};
fonts.packages = with pkgs; [
fira-code
+11
View File
@@ -16,8 +16,14 @@ pkgs.testers.runNixOSTest {
isSystemUser = true;
group = "greeter";
};
users.users.alice.isNormalUser = true;
services.greetd.settings.default_session.user = "greeter";
services.displayManager.autoLogin = {
enable = true;
user = "alice";
};
services.displayManager.defaultSession = "niri";
programs.niri.enable = true;
@@ -46,6 +52,11 @@ pkgs.testers.runNixOSTest {
greetd_config_path = config_match.group(1)
greetd_config = machine.succeed(f"cat {greetd_config_path}")
t.assertIn("dms-greeter", greetd_config)
t.assertIn("[initial_session]", greetd_config)
initial_session = greetd_config.split("[initial_session]", 1)[1]
t.assertIn('user = "alice"', initial_session)
t.assertIn("systemd-cat", initial_session)
script_match = re.search(r'command\s*=\s*"([^"]+/bin/dms-greeter)"', greetd_config)
if script_match is None:
+33 -8
View File
@@ -13,16 +13,41 @@ All build from source.
## Distribution
These packages target the official
[`void-linux/void-packages`](https://github.com/void-linux/void-packages)
repository, so they install with a plain `xbps-install dms` and no extra setup.
Most dependencies (`quickshell`, `matugen`, `cava`, `niri`, `greetd`, …) are
already in Void; `dgop` and `danksearch` are packaged alongside in the
[danklinux repo](https://github.com/AvengeMedia/danklinux/tree/master/distro/void).
This is a DMS maintained repo for VoidLinux until these packages are officially merged upstream in the Void Linux repositories, you can install them from our self-hosted custom XBPS repositories served via GitHub Pages.
### Using the Self-Hosted Repositories
We serve both stable release and development packages directly from our repository branches.
#### 1. Add Repository Configurations
Create configuration files in `/etc/xbps.d/` pointing to our repositories (needed for both stable and git/nightly variants):
```sh
echo "repository=https://avengemedia.github.io/DankMaterialShell/current" | sudo tee /etc/xbps.d/dms.conf
echo "repository=https://avengemedia.github.io/DankLinux/current" | sudo tee /etc/xbps.d/danklinux.conf
```
#### 2. Install DMS
Synchronize repositories and install the package:
* For the **stable** variant:
```sh
sudo xbps-install -S dms
```
* For the **git/nightly** variant (this will conflict with and replace the stable package):
```sh
sudo xbps-install -S dms-git
```
*Note: On the first sync, `xbps-install` will output our signing key fingerprint and ask you to type `y` to trust and import it. Verify that the key matches our official signing fingerprint.*
The templates here are the source of truth: copy each into a void-packages
checkout at `srcpkgs/<pkg>/template` to build or submit it. Only tagged releases
are packaged (no `-git`/nightly variant).
checkout at `srcpkgs/<pkg>/template` to build or submit it.
## Dependencies
+59
View File
@@ -0,0 +1,59 @@
# Template file for 'dms-git'
#
# DankMaterialShell git development release
#
# NOTE: This template is built exclusively by CI. The workflow stages a local
# source tarball and dynamically patches version, checksum, and distfiles
# before calling xbps-src. Do not attempt to build this template manually
# without first providing the source archive.
#
# The binary is built with the `distro_binary` build tag (drops the in-app
# self-update command).
pkgname=dms-git
version=1.4.6.git
revision=1
create_wrksrc=yes
build_style=go
build_wrksrc="core"
go_import_path="github.com/AvengeMedia/DankMaterialShell/core"
go_package="${go_import_path}/cmd/dms"
go_build_tags="distro_binary"
go_ldflags="-X main.Version=${version}"
short_desc="DankMaterialShell — Material 3 desktop shell for Wayland (git variant)"
maintainer="AvengeMedia <AvengeMedia.US@gmail.com>"
license="MIT"
homepage="https://danklinux.com"
# CI populates distfiles/checksum dynamically; these are placeholders.
distfiles="https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${version}.tar.gz"
checksum=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb
# Conflicts and provides
conflicts="dms"
provides="dms-${version}_${revision}"
# Optional feature deps are listed in distro/void/README.md.
depends="quickshell accountsservice dgop matugen dbus"
post_install() {
# QML shell tree (build_style=go already installed the dms binary)
vmkdir usr/share/quickshell/dms
vcopy "${wrksrc}/quickshell/*" usr/share/quickshell/dms
echo "${version}" > "${DESTDIR}/usr/share/quickshell/dms/VERSION"
# Desktop entry + icon
vinstall "${wrksrc}/assets/dms-open.desktop" 644 usr/share/applications
vinstall "${wrksrc}/assets/com.danklinux.dms.desktop" 644 usr/share/applications
vinstall "${wrksrc}/assets/danklogo.svg" 644 usr/share/icons/hicolor/scalable/apps
# Shell completions (generated by the built binary; skip when cross-building)
vmkdir usr/share/bash-completion/completions
vmkdir usr/share/zsh/site-functions
vmkdir usr/share/fish/vendor_completions.d
if [ -z "$CROSS_BUILD" ]; then
"${DESTDIR}/usr/bin/dms" completion bash > "${DESTDIR}/usr/share/bash-completion/completions/dms"
"${DESTDIR}/usr/bin/dms" completion zsh > "${DESTDIR}/usr/share/zsh/site-functions/_dms"
"${DESTDIR}/usr/bin/dms" completion fish > "${DESTDIR}/usr/share/fish/vendor_completions.d/dms.fish"
fi
vlicense "${wrksrc}/LICENSE"
}
+2 -2
View File
@@ -21,8 +21,8 @@ changelog="https://github.com/AvengeMedia/DankMaterialShell/releases"
distfiles="https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${version}.tar.gz"
checksum=f54601e522c883fa9cce02bec070e4321e47389a1cf453e7ad0bb7379ad91b61
# Optional feature deps (XBPS has no "recommends") are listed in distro/void/README.md.
depends="quickshell accountsservice dgop matugen"
# Optional feature deps are listed in distro/void/README.md.
depends="quickshell accountsservice dgop matugen dbus"
post_install() {
# QML shell tree (build_style=go already installed the dms binary)
+1 -1
View File
@@ -55,7 +55,7 @@ Singleton {
readonly property bool isDirectionalEffect: isConnectedEffect || _effect === 1
readonly property bool isDepthEffect: _effect === 2
readonly property bool isConnectedEffect: (typeof SettingsData !== "undefined") && SettingsData.connectedFrameModeActive
readonly property bool isConnectedEffect: (typeof FrameTransitionState !== "undefined") && FrameTransitionState.effectiveConnectedFrameModeActive
readonly property real effectScaleCollapsed: _effectScaleCollapsed[_effect] !== undefined ? _effectScaleCollapsed[_effect] : 0.96
readonly property real effectAnimOffset: _effectAnimOffsets[_effect] !== undefined ? _effectAnimOffsets[_effect] : 16
+21 -1
View File
@@ -7,6 +7,24 @@ function dirname(path) {
return idx > 0 ? path.substring(0, idx) : ".";
}
function sectionHeaderFor(includeLine) {
const line = String(includeLine ?? "").trim();
if (line.startsWith("require"))
return "-- DMS Include Configs";
if (line.startsWith("source"))
return "# DMS Include Configs";
return "// DMS Include Configs";
}
function managedIncludePatternFor(includeLine) {
const line = String(includeLine ?? "").trim();
if (line.startsWith("require"))
return "require.*dms[.]";
if (line.startsWith("source"))
return "source.*dms/";
return "include.*dms/";
}
function buildRepairScript(options) {
const configFile = options.configFile;
const backupFile = options.backupFile;
@@ -31,7 +49,9 @@ function buildRepairScript(options) {
for (const include of includes) {
if (!include.grepPattern || !include.includeLine)
continue;
commands.push(`if ! grep -v '^[[:space:]]*\\(//\\|#\\|--\\)' ${shQuote(configFile)} 2>/dev/null | grep -q ${shQuote(include.grepPattern)}; then echo '' >> ${shQuote(configFile)} && printf '%s\\n' ${shQuote(include.includeLine)} >> ${shQuote(configFile)}; fi`);
const sectionHeader = options.sectionHeader || sectionHeaderFor(include.includeLine);
const managedIncludePattern = managedIncludePatternFor(include.includeLine);
commands.push(`if ! grep -v '^[[:space:]]*\\(//\\|#\\|--\\)' ${shQuote(configFile)} 2>/dev/null | grep -q ${shQuote(include.grepPattern)}; then if grep -Fqx ${shQuote(sectionHeader)} ${shQuote(configFile)} 2>/dev/null || grep -v '^[[:space:]]*\\(//\\|#\\|--\\)' ${shQuote(configFile)} 2>/dev/null | grep -q ${shQuote(managedIncludePattern)}; then printf '%s\\n' ${shQuote(include.includeLine)} >> ${shQuote(configFile)}; elif [ -s ${shQuote(configFile)} ]; then printf '\\n%s\\n%s\\n' ${shQuote(sectionHeader)} ${shQuote(include.includeLine)} >> ${shQuote(configFile)}; else printf '%s\\n%s\\n' ${shQuote(sectionHeader)} ${shQuote(include.includeLine)} >> ${shQuote(configFile)}; fi; fi`);
}
return commands.join("; ");
@@ -0,0 +1,56 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
property int revision: 0
property int appliedRevision: 0
readonly property bool ready: appliedRevision >= revision
// Latched: surfaces render the last compositor-acknowledged state until the atomic flip on ack
property bool effectiveFrameEnabled: false
property string effectiveFrameMode: "connected"
readonly property bool effectiveConnectedFrameModeActive: effectiveFrameEnabled && effectiveFrameMode === "connected"
signal transitionRequested(int revision)
function begin() {
revision++;
transitionRequested(revision);
return revision;
}
function acknowledge(requestRevision) {
if (requestRevision > appliedRevision)
appliedRevision = requestRevision;
}
function syncEffective() {
effectiveFrameEnabled = SettingsData.frameEnabled;
effectiveFrameMode = SettingsData.frameMode;
}
onReadyChanged: {
if (ready)
syncEffective();
}
// Tracks settings-load changes; live toggles begin() first (ready false) so the latch holds
Connections {
target: SettingsData
function onFrameEnabledChanged() {
if (root.ready)
root.syncEffective();
}
function onFrameModeChanged() {
if (root.ready)
root.syncEffective();
}
}
Component.onCompleted: syncEffective()
}
+10
View File
@@ -25,6 +25,16 @@ Singleton {
return CompositorService.useHyprlandFocusGrab && keyboardFocus(active, customFocus) === WlrKeyboardFocus.OnDemand;
}
function captureActiveToplevel() {
return ToplevelManager.activeToplevel;
}
function restoreToplevel(toplevel) {
if (toplevel)
Qt.callLater(() => toplevel.activate());
return null;
}
property list<var> barWindows: []
function registerBarWindow(window) {
+99 -3
View File
@@ -262,7 +262,11 @@ Singleton {
}
property bool frameEnabled: false
onFrameEnabledChanged: saveSettings()
onFrameEnabledChanged: {
saveSettings();
if (!_loading)
updateFrameCompositorLayout();
}
property real frameThickness: 16
onFrameThicknessChanged: saveSettings()
property int barInsetPaddingShared: -1
@@ -295,7 +299,11 @@ Singleton {
onFrameLauncherEdgeHoverChanged: saveSettings()
readonly property string frameModalEmergeSide: frameLauncherEmergeSide === "top" ? "bottom" : "top"
property string frameMode: "connected"
onFrameModeChanged: saveSettings()
onFrameModeChanged: {
saveSettings();
if (!_loading && frameEnabled)
updateFrameCompositorLayout();
}
property var connectedFrameBarStyleBackups: ({})
onConnectedFrameBarStyleBackupsChanged: saveSettings()
readonly property bool connectedFrameModeActive: frameEnabled && frameMode === "connected"
@@ -448,6 +456,7 @@ Singleton {
property string audioScrollMode: "volume"
property int audioWheelScrollAmount: 5
property bool audioDeviceScrollVolumeEnabled: false
property var mediaExcludePlayers: []
property bool clockCompactMode: false
property int focusedWindowSize: 1
property bool focusedWindowCompactMode: false
@@ -1007,6 +1016,17 @@ Singleton {
}
]
// Standalone bar xray is unsafe when windows can render beneath its surface
function _standaloneBarXrayAvailable(configs) {
const list = configs || [];
const activeBars = list.filter(c => c && c.enabled && (c.visible ?? true));
const gapsOverride = (typeof CompositorService !== "undefined" && CompositorService.isHyprland) ? hyprlandLayoutGapsOverride : niriLayoutGapsOverride;
const layoutGaps = gapsOverride >= 0 ? gapsOverride : Math.max(4, (list[0]?.spacing ?? 4));
return activeBars.every(c => !c.autoHide && !(c.useOverlayLayer ?? false) && (c.spacing ?? 4) + (c.bottomGap ?? 0) + layoutGaps >= 0);
}
readonly property bool standaloneBarXrayAvailable: _standaloneBarXrayAvailable(barConfigs)
property bool desktopClockEnabled: false
property string desktopClockStyle: "analog"
property real desktopClockTransparency: 0.8
@@ -1322,6 +1342,35 @@ Singleton {
return true;
}
function moveDesktopWidgetInstanceToGroup(instanceId, groupId, newIndexInGroup) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const groups = desktopWidgetGroups || [];
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return false;
const [item] = instances.splice(idx, 1);
item.group = groupId || null;
const groupMatches = inst => {
if (!groupId)
return !inst.group || !groups.some(g => g.id === inst.group);
return inst.group === groupId;
};
const groupInstances = instances.filter(groupMatches);
const clamped = Math.max(0, Math.min(newIndexInGroup, groupInstances.length));
let targetGlobalIdx;
if (clamped >= groupInstances.length) {
const last = groupInstances[groupInstances.length - 1];
targetGlobalIdx = last ? instances.findIndex(inst => inst.id === last.id) + 1 : instances.length;
} else {
const targetInstance = groupInstances[clamped];
targetGlobalIdx = instances.findIndex(inst => inst.id === targetInstance.id);
}
instances.splice(targetGlobalIdx, 0, item);
desktopWidgetInstances = instances;
saveSettings();
return true;
}
function createDesktopWidgetGroup(name) {
const id = "dwg_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
const group = {
@@ -1422,6 +1471,17 @@ Singleton {
MangoService.generateLayoutConfig();
}
function updateFrameCompositorLayout() {
// Generate before begin() so compositor readiness is already pending at transitionRequested
if (typeof CompositorService !== "undefined") {
if (CompositorService.isNiri && typeof NiriService !== "undefined")
NiriService.generateNiriLayoutConfig(true);
if (CompositorService.isHyprland && typeof HyprlandService !== "undefined")
HyprlandService.generateLayoutConfig(true);
}
FrameTransitionState.begin();
}
function resolveIconTheme() {
if (iconThemePerMode && typeof SessionData !== "undefined" && SessionData.isLightMode)
return iconThemeLight;
@@ -2369,13 +2429,17 @@ Singleton {
if (index === -1)
return;
const positionChanged = updates.position !== undefined && configs[index].position !== updates.position;
const barXrayTargetWasAvailable = _standaloneBarXrayAvailable(configs);
if (updates.autoHide === false || updates.visible === false)
setBarIpcReveal(barId, false);
Object.assign(configs[index], updates);
barConfigs = _sanitizeBarConfigsForConnectedFrame(configs).configs;
const sanitizedConfigs = _sanitizeBarConfigsForConnectedFrame(configs).configs;
barConfigs = sanitizedConfigs;
updateBarConfigs();
if (!frameEnabled && _standaloneBarXrayAvailable(sanitizedConfigs) !== barXrayTargetWasAvailable)
updateCompositorLayout();
if (positionChanged) {
NotificationService.dismissAllPopups();
}
@@ -3062,6 +3126,32 @@ Singleton {
saveSettings();
}
function addMediaExcludePlayer(identity) {
if (identity === undefined || identity === null)
return;
var normalizedIdentity = identity.toString().trim().toLowerCase();
if (!normalizedIdentity)
return;
var list = mediaExcludePlayers ? mediaExcludePlayers.slice() : [];
var normalizedList = list.map(function (id) {
return id ? id.toString().trim().toLowerCase() : "";
});
if (normalizedList.indexOf(normalizedIdentity) >= 0)
return;
list.push(normalizedIdentity);
mediaExcludePlayers = list;
saveSettings();
}
function removeMediaExcludePlayer(index) {
var list = mediaExcludePlayers ? mediaExcludePlayers.slice() : [];
if (index < 0 || index >= list.length)
return;
list.splice(index, 1);
mediaExcludePlayers = list;
saveSettings();
}
property bool _pendingExpandNotificationRules: false
property int _pendingNotificationRuleIndex: -1
@@ -3482,6 +3572,9 @@ Singleton {
onLoaded: {
if (isGreeterMode)
return;
const wasLoaded = _hasLoaded;
const prevFrameEnabled = frameEnabled;
const prevFrameMode = frameMode;
_loading = true;
_hasUnsavedChanges = false;
try {
@@ -3516,6 +3609,9 @@ Singleton {
} finally {
_loading = false;
}
// External edits reload under _loading, which skips the per-property transition triggers
if (wasLoaded && !_parseError && (frameEnabled !== prevFrameEnabled || (frameEnabled && frameMode !== prevFrameMode)))
updateFrameCompositorLayout();
}
onLoadFailed: error => {
if (!isGreeterMode) {
+40
View File
@@ -17,6 +17,8 @@ const StockThemes = {
background: "#101418",
backgroundText: "#e0e2e8",
outline: "#8c9199",
surfaceContainerLowest: "#0c1014",
surfaceContainerLow: "#191c20",
surfaceContainer: "#1d2024",
surfaceContainerHigh: "#272a2f",
surfaceContainerHighest: "#32353a",
@@ -35,6 +37,8 @@ const StockThemes = {
background: "#141218",
backgroundText: "#e6e0e9",
outline: "#948f99",
surfaceContainerLowest: "#100e14",
surfaceContainerLow: "#1d1b20",
surfaceContainer: "#211f24",
surfaceContainerHigh: "#2b292f",
surfaceContainerHighest: "#36343a",
@@ -53,6 +57,8 @@ const StockThemes = {
background: "#10140f",
backgroundText: "#e0e4db",
outline: "#8c9388",
surfaceContainerLowest: "#0c100b",
surfaceContainerLow: "#191d17",
surfaceContainer: "#1d211b",
surfaceContainerHigh: "#272b25",
surfaceContainerHighest: "#323630",
@@ -71,6 +77,8 @@ const StockThemes = {
background: "#1a120e",
backgroundText: "#f0dfd8",
outline: "#a08d85",
surfaceContainerLowest: "#160e0a",
surfaceContainerLow: "#231a16",
surfaceContainer: "#271e1a",
surfaceContainerHigh: "#322824",
surfaceContainerHighest: "#3d332e",
@@ -89,6 +97,8 @@ const StockThemes = {
background: "#1a1110",
backgroundText: "#f1dedc",
outline: "#a08c89",
surfaceContainerLowest: "#160d0c",
surfaceContainerLow: "#231918",
surfaceContainer: "#271d1c",
surfaceContainerHigh: "#322826",
surfaceContainerHighest: "#3d3231",
@@ -107,6 +117,8 @@ const StockThemes = {
background: "#0e1416",
backgroundText: "#dee3e5",
outline: "#899295",
surfaceContainerLowest: "#0a1012",
surfaceContainerLow: "#171d1e",
surfaceContainer: "#1b2122",
surfaceContainerHigh: "#252b2c",
surfaceContainerHighest: "#303637",
@@ -125,6 +137,8 @@ const StockThemes = {
background: "#191112",
backgroundText: "#f0dee0",
outline: "#9f8c8e",
surfaceContainerLowest: "#150d0e",
surfaceContainerLow: "#22191a",
surfaceContainer: "#261d1e",
surfaceContainerHigh: "#312829",
surfaceContainerHighest: "#3c3233",
@@ -143,6 +157,8 @@ const StockThemes = {
background: "#17130b",
backgroundText: "#ebe1d4",
outline: "#998f80",
surfaceContainerLowest: "#130f07",
surfaceContainerLow: "#1f1b13",
surfaceContainer: "#231f17",
surfaceContainerHigh: "#2e2921",
surfaceContainerHighest: "#39342b",
@@ -161,6 +177,8 @@ const StockThemes = {
background: "#1a1110",
backgroundText: "#f1dedc",
outline: "#a08c8a",
surfaceContainerLowest: "#160d0c",
surfaceContainerLow: "#231918",
surfaceContainer: "#271d1c",
surfaceContainerHigh: "#322826",
surfaceContainerHighest: "#3d3231",
@@ -179,6 +197,8 @@ const StockThemes = {
background: "#131315",
backgroundText: "#e4e2e3",
outline: "#929092",
surfaceContainerLowest: "#252525",
surfaceContainerLow: "#303030",
surfaceContainer: "#353535",
surfaceContainerHigh: "#424242",
surfaceContainerHighest: "#505050",
@@ -203,6 +223,8 @@ const StockThemes = {
background: "#f7f9ff",
backgroundText: "#181c20",
outline: "#72777f",
surfaceContainerLowest: "#f9fbff",
surfaceContainerLow: "#eef0f6",
surfaceContainer: "#eceef4",
surfaceContainerHigh: "#e6e8ee",
surfaceContainerHighest: "#e0e2e8",
@@ -221,6 +243,8 @@ const StockThemes = {
background: "#fef7ff",
backgroundText: "#1d1b20",
outline: "#7a757f",
surfaceContainerLowest: "#fff9ff",
surfaceContainerLow: "#f4eef6",
surfaceContainer: "#f2ecf4",
surfaceContainerHigh: "#ece6ee",
surfaceContainerHighest: "#e6e0e9",
@@ -239,6 +263,8 @@ const StockThemes = {
background: "#f7fbf1",
backgroundText: "#191d17",
outline: "#72796f",
surfaceContainerLowest: "#f9fdf3",
surfaceContainerLow: "#eef1e8",
surfaceContainer: "#ecefe6",
surfaceContainerHigh: "#e6e9e0",
surfaceContainerHighest: "#e0e4db",
@@ -257,6 +283,8 @@ const StockThemes = {
background: "#fff8f6",
backgroundText: "#221a16",
outline: "#85736c",
surfaceContainerLowest: "#fffaf8",
surfaceContainerLow: "#feece5",
surfaceContainer: "#fceae3",
surfaceContainerHigh: "#f6e5de",
surfaceContainerHighest: "#f0dfd8",
@@ -275,6 +303,8 @@ const StockThemes = {
background: "#fff8f7",
backgroundText: "#231918",
outline: "#857370",
surfaceContainerLowest: "#fffaf9",
surfaceContainerLow: "#feece9",
surfaceContainer: "#fceae7",
surfaceContainerHigh: "#f7e4e1",
surfaceContainerHighest: "#f1dedc",
@@ -293,6 +323,8 @@ const StockThemes = {
background: "#f5fafc",
backgroundText: "#171d1e",
outline: "#6f797b",
surfaceContainerLowest: "#f7fcfe",
surfaceContainerLow: "#ebf1f2",
surfaceContainer: "#e9eff0",
surfaceContainerHigh: "#e3e9eb",
surfaceContainerHighest: "#dee3e5",
@@ -311,6 +343,8 @@ const StockThemes = {
background: "#fff8f7",
backgroundText: "#22191a",
outline: "#847375",
surfaceContainerLowest: "#fffaf9",
surfaceContainerLow: "#fdeced",
surfaceContainer: "#fbeaeb",
surfaceContainerHigh: "#f5e4e5",
surfaceContainerHighest: "#f0dee0",
@@ -329,6 +363,8 @@ const StockThemes = {
background: "#fff8f2",
backgroundText: "#1f1b13",
outline: "#7f7667",
surfaceContainerLowest: "#fffaf4",
surfaceContainerLow: "#f8eee1",
surfaceContainer: "#f6ecdf",
surfaceContainerHigh: "#f1e7d9",
surfaceContainerHighest: "#ebe1d4",
@@ -347,6 +383,8 @@ const StockThemes = {
background: "#fff8f7",
backgroundText: "#231918",
outline: "#857371",
surfaceContainerLowest: "#fffaf9",
surfaceContainerLow: "#feece9",
surfaceContainer: "#fceae7",
surfaceContainerHigh: "#f6e4e2",
surfaceContainerHighest: "#f1dedc",
@@ -365,6 +403,8 @@ const StockThemes = {
background: "#ffffff",
backgroundText: "#1a1a1a",
outline: "#757577",
surfaceContainerLowest: "#fafafb",
surfaceContainerLow: "#ededef",
surfaceContainer: "#e8e8ea",
surfaceContainerHigh: "#dcdcde",
surfaceContainerHighest: "#d0d0d2",
+64 -46
View File
@@ -463,6 +463,8 @@ Singleton {
"background": getMatugenColor("background", "#1a1c1e"),
"backgroundText": getMatugenColor("on_background", "#e3e8ef"),
"outline": getMatugenColor("outline", "#8e918f"),
"surfaceContainerLowest": getMatugenColor("surface_container_lowest", "#0e1013"),
"surfaceContainerLow": getMatugenColor("surface_container_low", "#181a1d"),
"surfaceContainer": getMatugenColor("surface_container", "#1e2023"),
"surfaceContainerHigh": getMatugenColor("surface_container_high", "#292b2f"),
"surfaceContainerHighest": getMatugenColor("surface_container_highest", "#343740"),
@@ -535,7 +537,9 @@ Singleton {
property color background: currentThemeData.background
property color backgroundText: currentThemeData.backgroundText
property color outline: currentThemeData.outline
property color outlineVariant: currentThemeData.outlineVariant || Qt.rgba(outline.r, outline.g, outline.b, 0.6)
property color outlineVariant: currentThemeData.outlineVariant || withAlpha(outline, 0.6)
property color surfaceContainerLowest: currentThemeData.surfaceContainerLowest || blend(surfaceContainer, surface, 1.2)
property color surfaceContainerLow: currentThemeData.surfaceContainerLow || blend(surface, surfaceContainer, 0.667)
property color surfaceContainer: currentThemeData.surfaceContainer
property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh
property color surfaceContainerHighest: currentThemeData.surfaceContainerHighest || surfaceContainerHigh
@@ -546,9 +550,9 @@ Singleton {
property color onSurface: surfaceText
property color onSurfaceVariant: surfaceVariantText
property color onPrimary: primaryText
property color onSurface_12: Qt.rgba(onSurface.r, onSurface.g, onSurface.b, 0.12)
property color onSurface_38: Qt.rgba(onSurface.r, onSurface.g, onSurface.b, 0.38)
property color onSurfaceVariant_30: Qt.rgba(onSurfaceVariant.r, onSurfaceVariant.g, onSurfaceVariant.b, 0.30)
property color onSurface_12: withAlpha(onSurface, 0.12)
property color onSurface_38: withAlpha(onSurface, 0.38)
property color onSurfaceVariant_30: withAlpha(onSurfaceVariant, 0.30)
property color error: currentThemeData.error || "#F2B8B5"
property color warning: currentThemeData.warning || "#FF9800"
@@ -557,32 +561,33 @@ Singleton {
property color tempDanger: "#ff5555"
property color success: currentThemeData.success || "#4CAF50"
property color primaryHover: Qt.rgba(primary.r, primary.g, primary.b, 0.12)
property color primaryHoverLight: Qt.rgba(primary.r, primary.g, primary.b, transparentBlurLayers ? 0.12 : 0.08)
property color primaryPressed: Qt.rgba(primary.r, primary.g, primary.b, transparentBlurLayers ? 0.24 : 0.16)
property color primarySelected: Qt.rgba(primary.r, primary.g, primary.b, 0.3)
property color primaryBackground: Qt.rgba(primary.r, primary.g, primary.b, 0.04)
property color primaryHover: withAlpha(primary, 0.12)
property color primaryHoverLight: withAlpha(primary, transparentBlurLayers ? 0.12 : 0.08)
property color primaryPressed: withAlpha(primary, transparentBlurLayers ? 0.24 : 0.16)
property color primarySelected: withAlpha(primary, 0.3)
property color primaryBackground: withAlpha(primary, 0.04)
property color secondaryHover: Qt.rgba(secondary.r, secondary.g, secondary.b, 0.08)
property color secondaryHover: withAlpha(secondary, 0.08)
property color surfaceHover: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.08)
property color surfacePressed: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.12)
property color surfaceSelected: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.15)
property color surfaceLight: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, transparentBlurLayers ? 0.3 : 0.1)
property color surfaceVariantAlpha: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.2)
property color surfaceHover: withAlpha(surfaceVariant, 0.08)
property color surfacePressed: withAlpha(surfaceVariant, 0.12)
property color surfaceSelected: withAlpha(surfaceVariant, 0.15)
property color surfaceLight: withAlpha(surfaceVariant, transparentBlurLayers ? 0.3 : 0.1)
property color surfaceVariantAlpha: withAlpha(surfaceVariant, 0.2)
readonly property bool blurForegroundLayers: BlurService.enabled && (typeof SettingsData === "undefined" || (SettingsData.blurForegroundLayers ?? true))
readonly property bool transparentBlurLayers: BlurService.enabled && !blurForegroundLayers
readonly property bool foregroundLayers: typeof SettingsData === "undefined" || (SettingsData.blurForegroundLayers ?? true)
readonly property bool blurForegroundLayers: BlurService.enabled && foregroundLayers
readonly property bool transparentBlurLayers: BlurService.enabled && !foregroundLayers
readonly property color readableSurface: withAlpha(surfaceContainer, popupTransparency)
readonly property color readableSurfaceHigh: withAlpha(surfaceContainerHigh, popupTransparency)
readonly property color floatingSurface: transparentBlurLayers ? "transparent" : readableSurface
readonly property color floatingSurfaceHigh: transparentBlurLayers ? "transparent" : readableSurfaceHigh
readonly property color floatingSurface: foregroundLayers ? readableSurface : withAlpha(readableSurface, 0)
readonly property color floatingSurfaceHigh: foregroundLayers ? readableSurfaceHigh : withAlpha(readableSurfaceHigh, 0)
readonly property color nestedSurface: floatingSurfaceHigh
readonly property real blurLayerOutlineOpacity: Math.max(0, Math.min(1, typeof SettingsData === "undefined" ? 0.12 : (SettingsData.blurLayerOutlineOpacity ?? 0.12)))
readonly property real layerOutlineOpacity: BlurService.enabled ? blurLayerOutlineOpacity : 0.08
readonly property int layerOutlineWidth: BlurService.enabled && layerOutlineOpacity > 0 ? 1 : 0
property color surfaceTextHover: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.08)
property color surfaceTextAlpha: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.3)
readonly property real layerOutlineOpacity: blurLayerOutlineOpacity
readonly property int layerOutlineWidth: layerOutlineOpacity > 0 ? 1 : 0
property color surfaceTextHover: withAlpha(surfaceText, 0.08)
property color surfaceTextAlpha: withAlpha(surfaceText, 0.3)
function roleColor(mode) {
switch (mode) {
@@ -607,6 +612,10 @@ Singleton {
return surfaceVariant;
case "s":
return surface;
case "scll":
return surfaceContainerLowest;
case "scl":
return surfaceContainerLow;
case "sc":
return surfaceContainer;
case "sch":
@@ -619,19 +628,23 @@ Singleton {
case "err":
return error;
default:
return "transparent";
return withAlpha(surface, 0);
}
}
property color surfaceTextLight: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.06)
property color surfaceTextMedium: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.7)
property color surfaceTextLight: withAlpha(surfaceText, 0.06)
property color surfaceTextSecondary: withAlpha(surfaceText, 0.6)
property color surfaceTextMedium: withAlpha(surfaceText, 0.7)
property color outlineButton: Qt.rgba(outline.r, outline.g, outline.b, 0.5)
property color outlineLight: Qt.rgba(outline.r, outline.g, outline.b, BlurService.enabled ? Math.min(1, layerOutlineOpacity * 0.625) : 0.05)
property color outlineMedium: Qt.rgba(outline.r, outline.g, outline.b, layerOutlineOpacity)
property color outlineStrong: Qt.rgba(outline.r, outline.g, outline.b, BlurService.enabled ? Math.min(1, layerOutlineOpacity * 1.5) : 0.12)
property color outlineButton: withAlpha(outline, 0.5)
property color outlineLight: withAlpha(outline, Math.min(1, layerOutlineOpacity * 0.625))
property color outlineMedium: withAlpha(outline, layerOutlineOpacity)
property color outlineStrong: withAlpha(outline, Math.min(1, layerOutlineOpacity * 1.5))
property color outlineHeavy: withAlpha(outline, 0.2)
property color errorHover: Qt.rgba(error.r, error.g, error.b, 0.12)
property color errorPressed: Qt.rgba(error.r, error.g, error.b, 0.16)
property color errorHover: withAlpha(error, 0.12)
property color errorPressed: withAlpha(error, 0.16)
property color errorSelected: withAlpha(error, 0.3)
property color warningHover: withAlpha(warning, 0.12)
readonly property color ccTileActiveBg: {
switch (SettingsData.controlCenterTileColorMode) {
@@ -646,7 +659,7 @@ Singleton {
}
}
readonly property color ccTileInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.16) : (blurForegroundLayers ? withAlpha(surfaceContainerHigh, Math.min(popupTransparency, 0.24)) : withAlpha(surfaceContainer, popupTransparency))
readonly property color ccTileInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.16) : (foregroundLayers ? withAlpha(surfaceContainerHigh, BlurService.enabled ? Math.min(popupTransparency, 0.24) : popupTransparency) : withAlpha(surfaceContainer, 0))
readonly property color ccPillInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.08) : nestedSurface
readonly property color ccPillInactiveHoverBg: transparentBlurLayers ? withAlpha(primary, 0.10) : primaryPressed
readonly property color ccSliderTrackColor: transparentBlurLayers ? surfaceText : surfaceContainerHigh
@@ -681,13 +694,13 @@ Singleton {
readonly property color ccTileRing: {
switch (SettingsData.controlCenterTileColorMode) {
case "primaryContainer":
return Qt.rgba(primary.r, primary.g, primary.b, 0.22);
return withAlpha(primary, 0.22);
case "secondary":
return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.22);
return withAlpha(surfaceText, 0.22);
case "surfaceVariant":
return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.22);
return withAlpha(surfaceText, 0.22);
default:
return Qt.rgba(primaryText.r, primaryText.g, primaryText.b, 0.22);
return withAlpha(primaryText, 0.22);
}
}
@@ -720,11 +733,11 @@ Singleton {
readonly property color buttonHover: {
switch (SettingsData.buttonColorMode) {
case "primaryContainer":
return Qt.rgba(primary.r, primary.g, primary.b, 0.12);
return withAlpha(primary, 0.12);
case "secondary":
return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.12);
return withAlpha(surfaceText, 0.12);
case "surfaceVariant":
return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.12);
return withAlpha(surfaceText, 0.12);
default:
return primaryHover;
}
@@ -733,11 +746,11 @@ Singleton {
readonly property color buttonPressed: {
switch (SettingsData.buttonColorMode) {
case "primaryContainer":
return Qt.rgba(primary.r, primary.g, primary.b, 0.16);
return withAlpha(primary, 0.16);
case "secondary":
return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.16);
return withAlpha(surfaceText, 0.16);
case "surfaceVariant":
return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.16);
return withAlpha(surfaceText, 0.16);
default:
return primaryPressed;
}
@@ -1063,12 +1076,12 @@ Singleton {
readonly property real connectedCornerRadius: {
if (typeof SettingsData === "undefined")
return 12;
return SettingsData.connectedFrameModeActive ? SettingsData.frameRounding : cornerRadius;
return FrameTransitionState.effectiveConnectedFrameModeActive ? SettingsData.frameRounding : cornerRadius;
}
readonly property color connectedSurfaceColor: {
if (typeof SettingsData === "undefined")
return withAlpha(surfaceContainer, popupTransparency);
return isConnectedEffect ? Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity) : withAlpha(surfaceContainer, popupTransparency);
return isConnectedEffect ? withAlpha(SettingsData.effectiveFrameColor, SettingsData.frameOpacity) : withAlpha(surfaceContainer, popupTransparency);
}
readonly property real connectedSurfaceRadius: isConnectedEffect ? connectedCornerRadius : cornerRadius
readonly property bool connectedSurfaceBlurEnabled: (typeof SettingsData === "undefined") ? true : (!isConnectedEffect || SettingsData.frameBlurEnabled)
@@ -1237,6 +1250,7 @@ Singleton {
return typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0;
}
property real spacingXXS: 2
property real spacingXS: 4
property real spacingS: 8
property real spacingM: 12
@@ -1468,7 +1482,7 @@ Singleton {
property string currentThemeName: currentTheme
function panelBackground() {
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, panelTransparency);
return withAlpha(surfaceContainer, panelTransparency);
}
property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency
@@ -1978,6 +1992,8 @@ Singleton {
}
function withAlpha(c, a) {
if (!c || c.r === undefined)
return Qt.rgba(0, 0, 0, 0);
return Qt.rgba(c.r, c.g, c.b, a);
}
@@ -1988,6 +2004,8 @@ Singleton {
}
function blendAlpha(c, a) {
if (!c || c.r === undefined)
return Qt.rgba(0, 0, 0, 0);
return Qt.rgba(c.r, c.g, c.b, c.a * a);
}
@@ -184,6 +184,7 @@ var SPEC = {
audioScrollMode: { def: "volume" },
audioWheelScrollAmount: { def: 5 },
audioDeviceScrollVolumeEnabled: { def: false },
mediaExcludePlayers: { def: [] },
clockCompactMode: { def: false },
focusedWindowCompactMode: { def: false },
focusedWindowSize: { def: 1 },
+34 -8
View File
@@ -173,6 +173,7 @@ Item {
}
property bool barSurfacesLoaded: true
property int pendingFrameTransitionRevision: 0
function recreateBarSurfaces() {
log.info("Recreating bar surfaces, screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","));
@@ -181,9 +182,23 @@ Item {
barSurfaceReloadAction.schedule();
}
// Holds the bar rebuild until the compositor applies the layout, so the swap lands in one pass
function runPendingFrameTransition() {
if (pendingFrameTransitionRevision <= 0 || !CompositorService.frameCompositorLayoutReady)
return;
recreateBarSurfaces();
}
DeferredAction {
id: barSurfaceReloadAction
onTriggered: root.barSurfacesLoaded = true
onTriggered: {
// Ack first so the latch flips and new bars build directly in the post-transition state
if (root.pendingFrameTransitionRevision > 0 && CompositorService.frameCompositorLayoutReady) {
FrameTransitionState.acknowledge(root.pendingFrameTransitionRevision);
root.pendingFrameTransitionRevision = 0;
}
root.barSurfacesLoaded = true;
}
}
property string _barLayoutStateJson: {
@@ -212,14 +227,23 @@ Item {
}
}
Connections {
target: FrameTransitionState
function onTransitionRequested(revision) {
root.pendingFrameTransitionRevision = Math.max(root.pendingFrameTransitionRevision, revision);
root.runPendingFrameTransition();
}
}
Connections {
target: CompositorService
function onFrameCompositorLayoutReadyChanged() {
root.runPendingFrameTransition();
}
}
Connections {
target: SettingsData
function onFrameEnabledChanged() {
root.recreateBarSurfaces();
}
function onConnectedFrameModeActiveChanged() {
root.recreateBarSurfaces();
}
function onForceDankBarLayoutRefresh() {
root.recreateBarSurfaces();
}
@@ -234,7 +258,7 @@ Item {
}
Loader {
active: SettingsData.frameEnabled && SettingsData.frameLauncherEdgeHover
active: FrameTransitionState.effectiveFrameEnabled && SettingsData.frameLauncherEdgeHover
asynchronous: false
sourceComponent: FrameLauncherHoverZone {}
}
@@ -251,6 +275,8 @@ Item {
values: JSON.parse(root._barLayoutStateJson)
}
Component.onCompleted: BarWidgetService.dankBarRepeater = dankBarRepeater
property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader
delegate: Loader {
+3 -3
View File
@@ -253,7 +253,7 @@ DankModal {
}
Row {
spacing: 4
spacing: Theme.spacingXS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
@@ -264,7 +264,7 @@ DankModal {
iconName: "view_list"
iconSize: 20
iconColor: root.viewMode === "list" ? Theme.primary : Theme.surfaceText
backgroundColor: root.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
backgroundColor: root.viewMode === "list" ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
onClicked: {
root.viewMode = "list";
}
@@ -276,7 +276,7 @@ DankModal {
iconName: "grid_view"
iconSize: 20
iconColor: root.viewMode === "grid" ? Theme.primary : Theme.surfaceText
backgroundColor: root.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
backgroundColor: root.viewMode === "grid" ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
onClicked: {
root.viewMode = "grid";
}
+2 -2
View File
@@ -224,7 +224,7 @@ DankModal {
Column {
anchors.centerIn: parent
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: I18n.tr("Passkey:")
@@ -256,7 +256,7 @@ DankModal {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
color: cancelArea.containsMouse ? Theme.surfaceTextHover : Theme.withAlpha(Theme.surfaceTextHover, 0)
border.color: Theme.surfaceVariantAlpha
border.width: 1
@@ -48,7 +48,7 @@ Rectangle {
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
width: parent.width - root.iconContainerSize - Theme.spacingS
StyledText {
@@ -24,6 +24,8 @@ FloatingWindow {
color: Theme.surfaceContainer
visible: false
onClosed: visible = false
FocusScope {
id: contentFocusScope
anchors.fill: parent
@@ -147,7 +147,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
iconName: "filter_list"
iconColor: modal.activeFilter !== "all" ? Theme.primary : Theme.surfaceText
backgroundColor: modal.activeFilter !== "all" ? Theme.primarySelected : "transparent"
backgroundColor: modal.activeFilter !== "all" ? Theme.primarySelected : Theme.withAlpha(Theme.primarySelected, 0)
tooltipText: I18n.tr("Filter by type", "Clipboard history type filter button tooltip")
onClicked: filterMenuLoader.item?.openDropdownMenu()
}
@@ -121,12 +121,12 @@ Item {
const hostX = host?.alignedX;
const hostY = host?.renderedAlignedY ?? host?.alignedY;
const globalPos = (!isNaN(hostX) && !isNaN(hostY)) ? ({
x: screenX + hostX + x,
y: screenY + hostY + y
}) : (parentHandler ? parentHandler.mapToGlobal(x, y) : ({
x: screenX + x,
y: screenY + y
}));
x: screenX + hostX + x,
y: screenY + hostY + y
}) : (parentHandler ? parentHandler.mapToGlobal(x, y) : ({
x: screenX + x,
y: screenY + y
}));
targetScreen = screenRef;
anchorX = globalPos.x - screenX + 4;
@@ -258,7 +258,7 @@ Item {
height: root.effectiveMenuHeight
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.enabled ? BlurService.borderWidth : 1
opacity: root.openState ? 1 : 0
@@ -321,7 +321,7 @@ Item {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
color: Theme.outlineHeavy
}
}
@@ -330,7 +330,7 @@ Item {
width: parent.width
height: parent.height
radius: Theme.cornerRadius
color: itemMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
color: itemMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(BlurService.hoverColor(Theme.widgetBaseHoverColor), 0)
Row {
anchors.left: parent.left
@@ -419,7 +419,7 @@ Item {
implicitWidth: saveMenuRow.implicitWidth + Theme.spacingS * 2
implicitHeight: saveMenuRow.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: saveMenuSaveArea.containsMouse ? Theme.surfaceVariant : "transparent"
color: saveMenuSaveArea.containsMouse ? Theme.surfaceVariant : Theme.withAlpha(Theme.surfaceVariant, 0)
Row {
id: saveMenuRow
@@ -457,7 +457,7 @@ Item {
implicitWidth: saveMenuCloseRow.implicitWidth + Theme.spacingS * 2
implicitHeight: saveMenuCloseRow.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: saveMenuCloseArea.containsMouse ? Theme.surfaceVariant : "transparent"
color: saveMenuCloseArea.containsMouse ? Theme.surfaceVariant : Theme.withAlpha(Theme.surfaceVariant, 0)
Row {
id: saveMenuCloseRow
@@ -495,7 +495,7 @@ Item {
implicitWidth: saveMenuPasteRow.implicitWidth + Theme.spacingS * 2
implicitHeight: saveMenuPasteRow.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: saveMenuPasteArea.containsMouse ? Theme.surfaceVariant : "transparent"
color: saveMenuPasteArea.containsMouse ? Theme.surfaceVariant : Theme.withAlpha(Theme.surfaceVariant, 0)
opacity: modal.wtypeAvailable ? 1 : 0.5
Row {
@@ -110,7 +110,7 @@ Rectangle {
iconName: "push_pin"
iconSize: Theme.iconSize - 6
iconColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primary : Theme.surfaceText
backgroundColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primarySelected : "transparent"
backgroundColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primarySelected : Theme.withAlpha(Theme.primarySelected, 0)
visible: root.showPinAction
onClicked: {
if (entry.pinned) {
@@ -228,9 +228,9 @@ Rectangle {
}
onClicked: {
if (SettingsData.clipboardClickToPaste) {
pasteRequested()
pasteRequested();
} else {
copyRequested()
copyRequested();
}
}
}
@@ -38,7 +38,6 @@ Item {
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
@@ -50,7 +49,7 @@ Item {
iconName: "push_pin"
iconSize: Theme.iconSize - 4
iconColor: header.activeTab === "saved" ? Theme.primary : Theme.surfaceText
backgroundColor: header.activeTab === "saved" ? Theme.primarySelected : "transparent"
backgroundColor: header.activeTab === "saved" ? Theme.primarySelected : Theme.withAlpha(Theme.primarySelected, 0)
visible: header.pinnedCount > 0 || header.activeTab === "saved"
tooltipText: header.activeTab === "saved" ? I18n.tr("Recent") : I18n.tr("Saved")
onClicked: tabChanged(header.activeTab === "saved" ? "recents" : "saved")
@@ -15,7 +15,7 @@ Rectangle {
height: ClipboardConstants.keyboardHintsHeight
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
color: Theme.withAlpha(Theme.surfaceContainer, 0.95)
border.color: Theme.primary
border.width: 2
opacity: visible ? 1 : 0
@@ -24,7 +24,7 @@ Rectangle {
Column {
width: parent.width - Theme.spacingL * 2
anchors.centerIn: parent
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: keyboardHints.enterToPaste ? I18n.tr("↑/↓: Navigate • Enter: Paste • Ctrl+C: Copy • Del: Delete • Ctrl+E: Edit • Ctrl+S: Pin/Unpin • F10: Help", "Keyboard hints when enter-to-paste is enabled") : I18n.tr("↑/↓: Navigate • Enter/Ctrl+C: Copy • Del: Delete • Ctrl+E: Edit • Ctrl+S: Pin/Unpin • F10: Help")
+5 -5
View File
@@ -211,14 +211,14 @@ DankModal {
radius: Theme.cornerRadius
color: {
if (keyboardNavigation && selectedButton === 0) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
return Theme.primaryHover;
} else if (cancelButton.containsMouse) {
return Theme.surfacePressed;
} else {
return Theme.surfaceVariantAlpha;
}
}
border.color: (keyboardNavigation && selectedButton === 0) ? Theme.primary : "transparent"
border.color: (keyboardNavigation && selectedButton === 0) ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: (keyboardNavigation && selectedButton === 0) ? 1 : 0
StyledText {
@@ -249,14 +249,14 @@ DankModal {
color: {
const baseColor = confirmButtonColor;
if (keyboardNavigation && selectedButton === 1) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 1);
return Theme.withAlpha(baseColor, 1);
} else if (confirmButton.containsMouse) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9);
return Theme.withAlpha(baseColor, 0.9);
} else {
return baseColor;
}
}
border.color: (keyboardNavigation && selectedButton === 1) ? "white" : "transparent"
border.color: (keyboardNavigation && selectedButton === 1) ? "white" : Qt.rgba(1, 1, 1, 0)
border.width: (keyboardNavigation && selectedButton === 1) ? 1 : 0
StyledText {
+4 -1
View File
@@ -58,6 +58,9 @@ Item {
HyprlandFocusGrab {
windows: root.contentWindow ? [root.contentWindow] : []
active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus)
property var restoreToplevel: null
onActiveChanged: restoreToplevel = active ? KeyboardFocus.captureActiveToplevel() : KeyboardFocus.restoreToplevel(restoreToplevel)
}
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null
@@ -90,7 +93,7 @@ Item {
impl.item.toggle();
}
readonly property var _desiredBackend: SettingsData.connectedFrameModeActive ? connectedComp : standaloneComp
readonly property var _desiredBackend: FrameTransitionState.effectiveConnectedFrameModeActive ? connectedComp : standaloneComp
property var _resolvedBackend: null
Component.onCompleted: _resolvedBackend = _desiredBackend
@@ -33,7 +33,7 @@ Item {
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
readonly property bool frameConnectedMode: FrameTransitionState.effectiveFrameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : ""
@@ -69,7 +69,7 @@ Item {
property real cornerRadius: Theme.cornerRadius
readonly property bool connectedSurfaceOverride: frameOwnsConnectedChrome
readonly property color effectiveBackgroundColor: connectedSurfaceOverride ? Theme.connectedSurfaceColor : backgroundColor
readonly property color effectiveBorderColor: connectedSurfaceOverride ? "transparent" : borderColor
readonly property color effectiveBorderColor: connectedSurfaceOverride ? Theme.withAlpha(borderColor, 0) : borderColor
readonly property real effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
readonly property real effectiveCornerRadius: connectedSurfaceOverride ? Theme.connectedSurfaceRadius : cornerRadius
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
@@ -659,8 +659,8 @@ Item {
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetRadius: root.effectiveCornerRadius
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
targetColor: root.frameOwnsConnectedChrome ? Theme.withAlpha(root.effectiveBackgroundColor, 0) : root.effectiveBackgroundColor
borderColor: root.frameOwnsConnectedChrome ? Theme.withAlpha(root.effectiveBorderColor, 0) : root.effectiveBorderColor
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
}
@@ -669,7 +669,7 @@ Item {
anchors.fill: parent
radius: root.effectiveCornerRadius
color: "transparent"
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? Theme.withAlpha(BlurService.borderColor, 0) : BlurService.borderColor
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
z: 100
}
@@ -53,7 +53,7 @@ Item {
readonly property alias contentWindow: contentWindow
readonly property alias clickCatcher: clickCatcher
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
readonly property bool useBackground: showBackground && !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
readonly property bool useBackground: showBackground && !FrameTransitionState.effectiveFrameEnabled && SettingsData.modalDarkenBackground
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackground
signal opened
+7 -7
View File
@@ -194,7 +194,7 @@ DankModal {
height: 40
radius: Theme.cornerRadius
color: Theme.surfaceVariantAlpha
border.color: textInput.activeFocus ? Theme.primary : "transparent"
border.color: textInput.activeFocus ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: textInput.activeFocus ? 1 : 0
TextInput {
@@ -216,7 +216,7 @@ DankModal {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
color: Theme.onSurface_38
text: root.inputPlaceholder
visible: textInput.text === "" && !textInput.activeFocus
}
@@ -238,14 +238,14 @@ DankModal {
radius: Theme.cornerRadius
color: {
if (root.keyboardNavigation && root.selectedButton === 0) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
return Theme.primaryHover;
} else if (cancelButton.containsMouse) {
return Theme.surfacePressed;
} else {
return Theme.surfaceVariantAlpha;
}
}
border.color: (root.keyboardNavigation && root.selectedButton === 0) ? Theme.primary : "transparent"
border.color: (root.keyboardNavigation && root.selectedButton === 0) ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: (root.keyboardNavigation && root.selectedButton === 0) ? 1 : 0
StyledText {
@@ -273,14 +273,14 @@ DankModal {
color: {
const baseColor = root.confirmButtonColor;
if (root.keyboardNavigation && root.selectedButton === 1) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 1);
return Theme.withAlpha(baseColor, 1);
} else if (confirmButton.containsMouse) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9);
return Theme.withAlpha(baseColor, 0.9);
} else {
return baseColor;
}
}
border.color: (root.keyboardNavigation && root.selectedButton === 1) ? "white" : "transparent"
border.color: (root.keyboardNavigation && root.selectedButton === 1) ? "white" : Qt.rgba(1, 1, 1, 0)
border.width: (root.keyboardNavigation && root.selectedButton === 1) ? 1 : 0
StyledText {
@@ -157,7 +157,7 @@ Rectangle {
width: actionContent.implicitWidth + Theme.spacingM * 2
height: actionsRow.height
radius: Theme.cornerRadius
color: index === root.selectedActionIndex ? Theme.primaryHover : actionArea.containsMouse ? Theme.surfaceHover : "transparent"
color: index === root.selectedActionIndex ? Theme.primaryHover : actionArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceHover, 0)
Row {
id: actionContent
@@ -43,6 +43,7 @@ Item {
signal queryChanged(string query)
signal viewModeChanged(string sectionId, string mode)
signal searchQueryRequested(string query)
signal sectionExpanded(string sectionId)
Ref {
service: AppSearchService
@@ -1874,6 +1875,9 @@ Item {
selectedFlatIndex = getFirstItemIndex();
}
updateSelectedItem();
if (!newCollapsed[sectionId])
sectionExpanded(sectionId);
}
function executeSelected() {
@@ -63,8 +63,8 @@ Item {
impl.item.toggleWithMode(mode);
}
readonly property bool useSpotlightBackend: !SettingsData.connectedFrameModeActive && SettingsData.launcherStyle === "spotlight"
readonly property var _desiredBackend: useSpotlightBackend ? spotlightComp : (SettingsData.connectedFrameModeActive ? connectedComp : standaloneComp)
readonly property bool useSpotlightBackend: !FrameTransitionState.effectiveConnectedFrameModeActive && SettingsData.launcherStyle === "spotlight"
readonly property var _desiredBackend: useSpotlightBackend ? spotlightComp : (FrameTransitionState.effectiveConnectedFrameModeActive ? connectedComp : standaloneComp)
property var _resolvedBackend: null
Component.onCompleted: _resolvedBackend = _desiredBackend
@@ -77,7 +77,7 @@ Item {
readonly property string preferredConnectedBarSide: SettingsData.frameLauncherEmergeSide
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
readonly property bool frameConnectedMode: FrameTransitionState.effectiveFrameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : ""
@@ -168,7 +168,7 @@ Item {
}
}
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
readonly property color effectiveBorderColor: connectedSurfaceOverride ? "transparent" : borderColor
readonly property color effectiveBorderColor: connectedSurfaceOverride ? Theme.withAlpha(borderColor, 0) : borderColor
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
@@ -359,8 +359,10 @@ Item {
contentVisible = true;
spotlightContent.closeTransientUi?.();
const targetQuery = query || (SettingsData.rememberLastQuery ? (SessionData.launcherLastQuery || "") : "");
if (spotlightContent.searchField) {
spotlightContent.searchField.text = query;
spotlightContent.searchField.text = targetQuery;
}
if (spotlightContent.controller) {
var targetMode = mode || SessionData.getLauncherRestoreMode();
@@ -375,8 +377,8 @@ Item {
spotlightContent.controller.collapsedSections = {};
spotlightContent.controller.selectedFlatIndex = 0;
spotlightContent.controller.selectedItem = null;
if (query) {
spotlightContent.controller.setSearchQuery(query);
if (targetQuery) {
spotlightContent.controller.setSearchQuery(targetQuery);
} else {
spotlightContent.controller.searchQuery = "";
spotlightContent.controller.performSearch();
@@ -422,8 +424,10 @@ Item {
Qt.callLater(() => {
root.keyboardActive = true;
if (root.spotlightContent && root.spotlightContent.searchField)
if (root.spotlightContent && root.spotlightContent.searchField) {
root.spotlightContent.searchField.forceActiveFocus();
root.spotlightContent.searchField.selectAll();
}
});
});
}
@@ -530,7 +534,9 @@ Item {
HyprlandFocusGrab {
id: focusGrab
windows: [contentWindow]
readonly property var contextMenuWindow: root.spotlightContent?.activeContextMenu?.contextWindow ?? null
readonly property bool contextMenuActive: root.spotlightContent?.activeContextMenu?.renderActive ?? false
windows: contextMenuActive && contextMenuWindow ? [contentWindow, contextMenuWindow] : [contentWindow]
active: root.useHyprlandFocusGrab && root.spotlightOpen
onCleared: {
@@ -797,8 +803,8 @@ Item {
y: contentWrapper.y
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.backgroundColor
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
targetColor: root.frameOwnsConnectedChrome ? Theme.withAlpha(root.backgroundColor, 0) : root.backgroundColor
borderColor: root.frameOwnsConnectedChrome ? Theme.withAlpha(root.effectiveBorderColor, 0) : root.effectiveBorderColor
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
targetRadius: root.cornerRadius
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
@@ -30,7 +30,7 @@ Item {
readonly property real screenWidth: effectiveScreen?.width ?? 1920
readonly property real screenHeight: effectiveScreen?.height ?? 1080
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
readonly property bool useBackgroundDarken: !FrameTransitionState.effectiveFrameEnabled && SettingsData.modalDarkenBackground
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
"allow": ["top", "overlay"],
@@ -155,7 +155,7 @@ Item {
}
if (spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus();
spotlightContent.searchField.cursorPosition = spotlightContent.searchField.text.length;
spotlightContent.searchField.selectAll();
}
}
@@ -228,7 +228,9 @@ Item {
HyprlandFocusGrab {
id: focusGrab
windows: [launcherWindow]
readonly property var contextMenuWindow: root.spotlightContent?.activeContextMenu?.contextWindow ?? null
readonly property bool contextMenuActive: root.spotlightContent?.activeContextMenu?.renderActive ?? false
windows: contextMenuActive && contextMenuWindow ? [launcherWindow, contextMenuWindow] : [launcherWindow]
active: root.useHyprlandFocusGrab && root.keyboardActive
onCleared: {
if (spotlightOpen)
@@ -79,7 +79,7 @@ Item {
readonly property real windowHeight: alignedHeight + contentY + shadowPad
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
readonly property bool useBackgroundDarken: !FrameTransitionState.effectiveFrameEnabled && SettingsData.modalDarkenBackground
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
@@ -139,6 +139,7 @@ Item {
if (spotlightContent.searchField) {
spotlightContent.searchField.text = targetQuery;
spotlightContent.searchField.selectAll();
}
if (spotlightContent.controller) {
var targetMode = mode || SessionData.getLauncherRestoreMode();
@@ -259,7 +260,9 @@ Item {
HyprlandFocusGrab {
id: focusGrab
windows: [launcherWindow]
readonly property var contextMenuWindow: root.spotlightContent?.activeContextMenu?.contextWindow ?? null
readonly property bool contextMenuActive: root.spotlightContent?.activeContextMenu?.renderActive ?? false
windows: contextMenuActive && contextMenuWindow ? [launcherWindow, contextMenuWindow] : [launcherWindow]
active: root.useHyprlandFocusGrab && root.keyboardActive
onCleared: {
@@ -36,7 +36,7 @@ Rectangle {
readonly property int computedIconSize: Math.min(48, Math.max(32, width * 0.45))
radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0)
DankRipple {
id: rippleLayer
@@ -314,9 +314,13 @@ FocusScope {
}
Item {
id: contentHolder
anchors.fill: parent
visible: !editMode
readonly property bool inverted: (root.parentModal?.frameOwnsConnectedChrome ?? false) && (root.parentModal?.resolvedConnectedBarSide === "top")
readonly property bool _connectedArcAtHeader: inverted && !(root.parentModal?.launcherArcExtenderActive ?? false)
Item {
id: footerBar
readonly property bool _connectedBottomEmerge: (root.parentModal?.frameOwnsConnectedChrome ?? false) && (root.parentModal?.resolvedConnectedBarSide === "bottom")
@@ -325,11 +329,10 @@ FocusScope {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: root.parentModal?.borderWidth ?? 1
anchors.rightMargin: root.parentModal?.borderWidth ?? 1
anchors.bottomMargin: _connectedBottomEmerge ? 0 : (root.parentModal?.borderWidth ?? 1)
height: showFooter ? (_connectedArcAtFooter ? 76 : 36) : 0
y: contentHolder.inverted ? 0 : (parent.height - height - (_connectedBottomEmerge ? 0 : (root.parentModal?.borderWidth ?? 1)))
height: showFooter ? ((_connectedArcAtFooter || contentHolder._connectedArcAtHeader) ? 76 : 36) : 0
visible: showFooter
clip: true
@@ -348,7 +351,7 @@ FocusScope {
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
spacing: 2
spacing: Theme.spacingXXS
Repeater {
model: [
@@ -381,7 +384,7 @@ FocusScope {
width: buttonContent.width + Theme.spacingM * 2
height: 28
radius: Theme.cornerRadius
color: controller.searchMode === modelData.id ? Theme.buttonBg : modeArea.containsMouse ? Theme.surfaceContainerHighest : "transparent"
color: controller.searchMode === modelData.id ? Theme.buttonBg : modeArea.containsMouse ? Theme.surfaceContainerHighest : Theme.withAlpha(Theme.surfaceContainerHighest, 0)
Row {
id: buttonContent
@@ -446,105 +449,112 @@ FocusScope {
}
}
Column {
Row {
id: searchRow
spacing: Theme.spacingS
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: footerBar.top
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingM
spacing: Theme.spacingXS
clip: false
y: contentHolder.inverted ? (parent.height - height - Theme.spacingM) : Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingS
Rectangle {
id: pluginBadge
visible: controller.activePluginName.length > 0
width: visible ? pluginBadgeContent.implicitWidth + Theme.spacingM : 0
height: searchField.height
radius: 16
color: Theme.primary
Rectangle {
id: pluginBadge
visible: controller.activePluginName.length > 0
width: visible ? pluginBadgeContent.implicitWidth + Theme.spacingM : 0
height: searchField.height
radius: 16
color: Theme.primary
Row {
id: pluginBadgeContent
anchors.centerIn: parent
spacing: Theme.spacingXS
Row {
id: pluginBadgeContent
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: "extension"
size: 14
color: Theme.primaryText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: controller.activePluginName
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.primaryText
}
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: "extension"
size: 14
color: Theme.primaryText
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: controller.activePluginName
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.primaryText
}
}
DankTextField {
id: searchField
width: parent.width - (pluginBadge.visible ? pluginBadge.width + Theme.spacingS : 0)
cornerRadius: Theme.cornerRadius
backgroundColor: root._launcherSearchFieldColor
normalBorderColor: root._launcherSearchBorderColor
focusedBorderColor: root._launcherSearchFocusedBorderColor
borderWidth: 1
focusedBorderWidth: 2
leftIconName: controller.activePluginId ? "extension" : controller.searchQuery.startsWith("/") ? "folder" : "search"
leftIconSize: Theme.iconSize
leftIconColor: Theme.surfaceVariantText
leftIconFocusedColor: Theme.primary
showClearButton: true
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
enabled: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
placeholderText: ""
ignoreUpDownKeys: true
ignoreTabKeys: true
keyForwardTargets: [root]
onTextChanged: {
controller.setSearchQuery(text);
if (actionPanel.expanded) {
actionPanel.hide();
}
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
if (root.parentModal) {
root.parentModal.hide();
}
event.accepted = true;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter)) {
if (actionPanel.expanded && actionPanel.selectedActionIndex > 0) {
actionPanel.executeSelectedAction();
} else {
controller.executeSelected();
}
event.accepted = true;
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
DankTextField {
id: searchField
width: parent.width - (pluginBadge.visible ? pluginBadge.width + Theme.spacingS : 0)
cornerRadius: Theme.cornerRadius
backgroundColor: root._launcherSearchFieldColor
normalBorderColor: root._launcherSearchBorderColor
focusedBorderColor: root._launcherSearchFocusedBorderColor
borderWidth: 1
focusedBorderWidth: 2
leftIconName: controller.activePluginId ? "extension" : controller.searchQuery.startsWith("/") ? "folder" : "search"
leftIconSize: Theme.iconSize
leftIconColor: Theme.surfaceVariantText
leftIconFocusedColor: Theme.primary
showClearButton: true
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
enabled: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
placeholderText: ""
ignoreUpDownKeys: true
ignoreTabKeys: true
keyForwardTargets: [root]
onTextChanged: {
controller.setSearchQuery(text);
if (actionPanel.expanded) {
actionPanel.hide();
}
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
if (root.parentModal) {
root.parentModal.hide();
}
event.accepted = true;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter)) {
if (actionPanel.expanded && actionPanel.selectedActionIndex > 0) {
actionPanel.executeSelectedAction();
} else {
controller.executeSelected();
}
event.accepted = true;
}
}
}
}
Item {
id: contentStack
anchors.left: parent.left
anchors.right: parent.right
anchors.top: contentHolder.inverted ? footerBar.bottom : searchRow.bottom
anchors.bottom: contentHolder.inverted ? searchRow.top : footerBar.top
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.topMargin: contentHolder.inverted && !footerBar.showFooter ? Theme.spacingM : contentStack.gap
anchors.bottomMargin: contentHolder.inverted ? contentStack.gap : 0
readonly property real gap: Theme.spacingXS
clip: false
Row {
id: categoryRow
width: parent.width
@@ -552,6 +562,8 @@ FocusScope {
height: showPluginCategories ? 36 : 0
visible: showPluginCategories
spacing: Theme.spacingS
anchors.top: parent.top
anchors.topMargin: 0
clip: true
@@ -607,6 +619,8 @@ FocusScope {
width: parent.width
height: showFileFilters ? fileFilterContent.height : 0
visible: showFileFilters
anchors.top: parent.top
anchors.topMargin: 0
readonly property bool showFileFilters: controller.searchMode === "files"
@@ -625,7 +639,7 @@ FocusScope {
Row {
id: typeChips
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
visible: DSearchService.supportsTypeFilter
Repeater {
@@ -654,7 +668,7 @@ FocusScope {
width: chipContent.width + Theme.spacingM * 2
height: sortDropdown.height
radius: Theme.cornerRadius
color: controller.fileSearchType === modelData.id ? Theme.buttonBg : chipArea.containsMouse ? Theme.surfaceContainerHighest : "transparent"
color: controller.fileSearchType === modelData.id ? Theme.buttonBg : chipArea.containsMouse ? Theme.surfaceContainerHighest : Theme.withAlpha(Theme.surfaceContainerHighest, 0)
Row {
id: chipContent
@@ -746,8 +760,12 @@ FocusScope {
}
Item {
id: resultsSlot
width: parent.width
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
anchors.top: fileFilterRow.visible ? fileFilterRow.bottom : (categoryRow.visible ? categoryRow.bottom : parent.top)
anchors.topMargin: (fileFilterRow.visible || categoryRow.visible) ? contentStack.gap : 0
anchors.bottom: actionPanel.top
anchors.bottomMargin: actionPanel.height > 0 || !contentHolder.inverted ? contentStack.gap : 0
opacity: {
if (!root.parentModal)
return 1;
@@ -760,6 +778,7 @@ FocusScope {
id: resultsList
anchors.fill: parent
controller: root.controller
leadingSectionHeaderAtBottom: contentHolder.inverted
onItemRightClicked: (index, item, sceneX, sceneY) => {
if (item && contextMenu.hasContextMenuActions(item)) {
@@ -773,6 +792,7 @@ FocusScope {
ActionPanel {
id: actionPanel
width: parent.width
anchors.bottom: parent.bottom
selectedItem: controller.selectedItem
controller: controller
}
@@ -828,7 +848,7 @@ FocusScope {
width: 40
height: 40
radius: Theme.cornerRadius
color: backButtonArea.containsMouse ? Theme.surfaceHover : "transparent"
color: backButtonArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceHover, 0)
DankIcon {
anchors.centerIn: parent
@@ -858,7 +878,7 @@ FocusScope {
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: I18n.tr("Edit App")
@@ -895,7 +915,7 @@ FocusScope {
Column {
width: parent.width
spacing: 4
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Name")
@@ -915,7 +935,7 @@ FocusScope {
Column {
width: parent.width
spacing: 4
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Icon")
@@ -935,7 +955,7 @@ FocusScope {
Column {
width: parent.width
spacing: 4
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Description")
@@ -955,7 +975,7 @@ FocusScope {
Column {
width: parent.width
spacing: 4
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Environment Variables")
@@ -981,7 +1001,7 @@ FocusScope {
Column {
width: parent.width
spacing: 4
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Extra Arguments")
@@ -1060,7 +1080,7 @@ FocusScope {
width: 90
height: 40
radius: Theme.cornerRadius
color: saveButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
color: saveButtonArea.containsMouse ? Theme.withAlpha(Theme.primary, 0.9) : Theme.primary
StyledText {
text: I18n.tr("Save")
@@ -25,6 +25,7 @@ Item {
property real anchorY: 0
property bool openState: false
property bool renderActive: false
readonly property alias contextWindow: menuWindow
readonly property bool blurActive: renderActive && openState && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
readonly property real minMenuWidth: 180
@@ -446,7 +447,17 @@ Item {
WlrLayershell.namespace: "dms:launcher-context-menu"
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (root.renderActive ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None)
// Hyprland steals the launcher's focus grab on exclusive focus; keep keys on the
// launcher window, which forwards them to the menu via handleKey().
WlrLayershell.keyboardFocus: {
if (PopoutManager.screenshotActive)
return WlrKeyboardFocus.None;
if (!root.renderActive)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.None;
return WlrKeyboardFocus.Exclusive;
}
anchors {
top: true
@@ -507,7 +518,7 @@ Item {
height: root.effectiveMenuHeight
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.enabled ? BlurService.borderWidth : 1
opacity: root.openState ? 1 : 0
@@ -585,7 +596,7 @@ Item {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
color: Theme.outlineHeavy
}
}
@@ -596,9 +607,9 @@ Item {
radius: Theme.cornerRadius
color: {
if (root.keyboardNavigation && root.selectedMenuIndex === menuItemDelegate.itemIndex) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
return Theme.primaryPressed;
}
return itemMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
return itemMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(BlurService.hoverColor(Theme.widgetBaseHoverColor), 0);
}
Row {
@@ -37,7 +37,7 @@ Rectangle {
width: parent?.width ?? 200
height: 52
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0)
radius: Theme.cornerRadius
DankRipple {
@@ -170,7 +170,7 @@ Rectangle {
height: 28
radius: 14
anchors.verticalCenter: parent.verticalCenter
color: allModeToggleArea.containsMouse ? Theme.surfaceHover : "transparent"
color: allModeToggleArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceHover, 0)
property bool isAllowed: {
if (root.item?.type !== "plugin_browse")
@@ -11,9 +11,11 @@ Item {
property var controller: null
property int gridColumns: controller?.gridColumns ?? 4
property bool leadingSectionHeaderAtBottom: false
property var _visualRows: []
property var _flatIndexToRowMap: ({})
property var _cumulativeHeights: []
readonly property bool _bottomSectionHeaderActive: leadingSectionHeaderAtBottom && (controller?.sections?.length ?? 0) > 0
signal itemRightClicked(int index, var item, real mouseX, real mouseY)
@@ -28,15 +30,17 @@ Item {
var section = sections[s];
var sectionId = section.id;
cumHeights.push(cumY);
rows.push({
_rowId: "h_" + sectionId,
type: "header",
section: section,
sectionId: sectionId,
height: 32
});
cumY += 32;
if (!root._bottomSectionHeaderActive || s > 0) {
cumHeights.push(cumY);
rows.push({
_rowId: "h_" + sectionId,
type: "header",
section: section,
sectionId: sectionId,
height: 32
});
cumY += 32;
}
if (section.collapsed)
continue;
@@ -103,6 +107,7 @@ Item {
onGridColumnsChanged: Qt.callLater(_rebuildVisualModel)
onWidthChanged: Qt.callLater(_rebuildVisualModel)
onLeadingSectionHeaderAtBottomChanged: Qt.callLater(_rebuildVisualModel)
Connections {
target: root.controller
@@ -117,12 +122,27 @@ Item {
root._cumulativeHeights = [];
root._flatIndexToRowMap = {};
}
function onSectionExpanded(sectionId) {
Qt.callLater(() => {
root._rebuildVisualModel();
Qt.callLater(() => root.revealExpandedSection(sectionId));
});
}
}
function resetScroll() {
mainListView.contentY = mainListView.originY;
}
function revealExpandedSection(sectionId) {
for (var i = 0; i < _visualRows.length; i++) {
if (_visualRows[i].sectionId === sectionId) {
mainListView.positionViewAtIndex(i, ListView.Beginning);
return;
}
}
}
function ensureVisible(index) {
if (index < 0 || !controller?.flatModel || index >= controller.flatModel.length)
return;
@@ -194,6 +214,7 @@ Item {
id: listClip
anchors.fill: parent
anchors.topMargin: BlurService.enabled && stickyHeader.visible ? 32 : 0
anchors.bottomMargin: bottomSectionHeader.visible ? bottomSectionHeader.height : 0
clip: true
DankListView {
@@ -332,6 +353,7 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: bottomSectionHeader.visible ? bottomSectionHeader.height : 0
height: 24
z: 100
visible: {
@@ -377,7 +399,7 @@ Item {
height: 32
z: 101
color: Theme.floatingSurface
visible: stickyHeaderSection !== null
visible: !root._bottomSectionHeaderActive && stickyHeaderSection !== null
readonly property int versionTrigger: root.controller?.viewModeVersion ?? 0
@@ -428,6 +450,30 @@ Item {
}
}
SectionHeader {
id: bottomSectionHeader
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
z: 101
visible: root._bottomSectionHeaderActive
section: visible ? root.controller.sections[0] : null
controller: root.controller
viewMode: {
var vt = root.controller?.viewModeVersion ?? 0;
void (vt);
return root.controller?.getSectionViewMode(section?.id ?? "") ?? "list";
}
canChangeViewMode: {
var vt = root.controller?.viewModeVersion ?? 0;
void (vt);
return root.controller?.canChangeSectionViewMode(section?.id ?? "") ?? false;
}
canCollapse: root.controller?.canCollapseSection(section?.id ?? "") ?? false
isSticky: true
popupAbove: true
}
Item {
anchors.centerIn: parent
visible: (!root.controller?.sections || root.controller.sections.length === 0) && !root.controller?.isFileSearching
+2 -2
View File
@@ -50,7 +50,7 @@ Item {
id: listComponent
Column {
spacing: 4
spacing: Theme.spacingXS
width: contentLoader.width
Repeater {
@@ -83,7 +83,7 @@ Item {
Flow {
width: contentLoader.width
spacing: 4
spacing: Theme.spacingXS
Repeater {
model: ScriptModel {
@@ -15,12 +15,14 @@ Rectangle {
property bool canChangeViewMode: true
property bool canCollapse: true
property bool isSticky: false
property bool popupAbove: false
property Item popupAboveItem: null
signal viewModeToggled
width: parent?.width ?? 200
height: 32
color: isSticky ? "transparent" : (hoverArea.containsMouse ? Theme.surfaceHover : "transparent")
color: isSticky ? Theme.withAlpha(Theme.surfaceHover, 0) : (hoverArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceHover, 0))
radius: Theme.cornerRadius
MouseArea {
@@ -81,7 +83,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: chipArea.containsMouse || categoryPopup.visible ? Theme.surfaceContainerHigh : "transparent"
color: chipArea.containsMouse || categoryPopup.visible ? Theme.surfaceContainerHigh : Theme.withAlpha(Theme.surfaceContainerHigh, 0)
border.color: categoryPopup.visible ? Theme.primary : Theme.outlineMedium
border.width: categoryPopup.visible ? 2 : 1
}
@@ -122,9 +124,10 @@ Rectangle {
if (categoryPopup.visible) {
categoryPopup.close();
} else {
const pos = categoryChip.mapToItem(Overlay.overlay, 0, 0);
categoryPopup.x = pos.x;
categoryPopup.y = pos.y + categoryChip.height + 4;
const chipPos = categoryChip.mapToItem(Overlay.overlay, 0, 0);
const abovePos = (root.popupAboveItem ?? categoryChip).mapToItem(Overlay.overlay, 0, 0);
categoryPopup.x = chipPos.x;
categoryPopup.y = root.popupAbove ? abovePos.y - categoryPopup.height - 4 : chipPos.y + categoryChip.height + 4;
categoryPopup.open();
}
}
@@ -145,7 +148,7 @@ Rectangle {
contentItem: Rectangle {
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
color: Theme.withAlpha(Theme.surfaceContainer, 1)
border.color: Theme.primary
border.width: 2
@@ -166,7 +169,7 @@ Rectangle {
anchors.fill: parent
anchors.margins: Theme.spacingS
model: root.controller?.appCategories ?? []
spacing: 2
spacing: Theme.spacingXXS
clip: true
interactive: contentHeight > height
implicitHeight: contentHeight
@@ -179,7 +182,7 @@ Rectangle {
height: 32
radius: Theme.cornerRadius
readonly property bool isCurrent: categoryChip.currentCategory === modelData
color: isCurrent ? Theme.primaryHover : catArea.containsMouse ? Theme.primaryHoverLight : "transparent"
color: isCurrent ? Theme.primaryHover : catArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0)
Row {
anchors.left: parent.left
@@ -243,7 +246,7 @@ Rectangle {
Row {
id: viewModeRow
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
visible: root.canChangeViewMode && !root.section?.collapsed
Repeater {
@@ -269,7 +272,7 @@ Rectangle {
width: 20
height: 20
radius: 4
color: root.viewMode === modelData.mode ? Theme.primaryHover : modeArea.containsMouse ? Theme.surfaceHover : "transparent"
color: root.viewMode === modelData.mode ? Theme.primaryHover : modeArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceHover, 0)
DankIcon {
anchors.centerIn: parent
@@ -325,7 +328,7 @@ Rectangle {
anchors.fill: parent
anchors.rightMargin: rightContent.width + Theme.spacingS
cursorShape: root.canCollapse ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: root.canCollapse
enabled: root.canCollapse && !leftContent.hasAppCategories
onClicked: {
if (root.canCollapse && root.controller && root.section) {
root.controller.toggleSection(root.section.id);
@@ -76,7 +76,7 @@ Rectangle {
width: parent?.width ?? 200
height: 64
radius: Theme.cornerRadius
color: root.isSelected ? Theme.primaryPressed : root.isHovered ? Theme.primaryHoverLight : "transparent"
color: root.isSelected ? Theme.primaryPressed : root.isHovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0)
Behavior on color {
ColorAnimation {
@@ -150,7 +150,7 @@ Rectangle {
anchors.right: previewFrame.visible ? previewFrame.left : metaRow.left
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
StyledText {
id: nameText
@@ -225,7 +225,7 @@ Rectangle {
height: 28
radius: height / 2
anchors.verticalCenter: parent.verticalCenter
color: quickToggleArea.containsMouse ? Theme.surfaceHover : "transparent"
color: quickToggleArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceHover, 0)
readonly property bool isAllowed: {
if (root.item?.type !== "plugin_browse")
@@ -143,7 +143,7 @@ Item {
Column {
anchors.verticalCenter: parent.verticalCenter
width: Math.min(420, root.width - 88)
spacing: 2
spacing: Theme.spacingXXS
StyledText {
width: parent.width
@@ -19,7 +19,7 @@ Rectangle {
signal rightClicked(real mouseX, real mouseY)
radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : Theme.withAlpha(Theme.primaryPressed, 0)
border.width: isSelected ? 2 : 0
border.color: Theme.primary
@@ -93,7 +93,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
color: root.isHovered ? Theme.withAlpha(Theme.surfaceVariant, 0.2) : "transparent"
color: root.isHovered ? Theme.withAlpha(Theme.surfaceVariant, 0.2) : Theme.withAlpha(Theme.surfaceVariant, 0)
}
}
@@ -155,7 +155,7 @@ Rectangle {
width: root.hasScreencopy ? 28 : 40
height: root.hasScreencopy ? 28 : 16
radius: root.hasScreencopy ? 14 : 4
color: root.hasScreencopy ? Theme.surfaceContainer : "transparent"
color: root.hasScreencopy ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0)
visible: attributionImage.status === Image.Ready
opacity: 0.95
@@ -129,7 +129,7 @@ DankModal {
width: Math.max(70, revertText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: revertArea.containsMouse ? Theme.surfaceTextHover : "transparent"
color: revertArea.containsMouse ? Theme.surfaceTextHover : Theme.withAlpha(Theme.surfaceTextHover, 0)
border.color: Theme.surfaceVariantAlpha
border.width: 1
@@ -804,7 +804,7 @@ FocusScope {
anchors.topMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
visible: viewMode === "list"
spacing: 2
spacing: Theme.spacingXXS
model: folderModel
currentIndex: selectedIndex
onCurrentIndexChanged: {
@@ -136,9 +136,9 @@ StyledRect {
if (keyboardNavigationActive && delegateRoot.index === selectedIndex)
return Theme.surfacePressed;
return mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent";
return mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : Theme.withAlpha(Theme.surfaceContainerHigh, 0);
}
border.color: keyboardNavigationActive && delegateRoot.index === selectedIndex ? Theme.primary : "transparent"
border.color: keyboardNavigationActive && delegateRoot.index === selectedIndex ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: (keyboardNavigationActive && delegateRoot.index === selectedIndex) ? 2 : 0
Component.onCompleted: {
@@ -89,7 +89,7 @@ Popup {
contentItem: Rectangle {
color: Theme.floatingSurface
radius: Theme.cornerRadius
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.enabled ? BlurService.borderWidth : 1
Column {
@@ -110,7 +110,7 @@ Popup {
if (!modelData.enabled || !area.containsMouse)
return "transparent";
if (modelData.dangerous)
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
return Theme.errorHover;
return BlurService.hoverColor(Theme.widgetBaseHoverColor);
}
@@ -139,9 +139,9 @@ StyledRect {
color: {
if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex)
return Theme.surfacePressed;
return listMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent";
return listMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : Theme.withAlpha(Theme.surfaceContainerHigh, 0);
}
border.color: keyboardNavigationActive && listDelegateRoot.index === selectedIndex ? Theme.primary : "transparent"
border.color: keyboardNavigationActive && listDelegateRoot.index === selectedIndex ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: (keyboardNavigationActive && listDelegateRoot.index === selectedIndex) ? 2 : 0
Component.onCompleted: {
@@ -41,6 +41,8 @@ FloatingWindow {
color: Theme.surfaceContainer
visible: false
onClosed: close()
onVisibleChanged: {
if (visible) {
if (parentModal && "shouldHaveFocus" in parentModal) {
@@ -26,7 +26,7 @@ Row {
width: 32
height: 32
radius: Theme.cornerRadius
color: (backButtonMouseArea.containsMouse || (backButtonFocused && keyboardNavigationActive)) && currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
color: (backButtonMouseArea.containsMouse || (backButtonFocused && keyboardNavigationActive)) && currentPath !== homeDir ? Theme.surfaceVariant : Theme.withAlpha(Theme.surfaceVariant, 0)
opacity: currentPath !== homeDir ? 1 : 0
anchors.verticalCenter: parent.verticalCenter
@@ -56,8 +56,8 @@ Row {
StyledRect {
anchors.fill: parent
radius: Theme.cornerRadius
color: pathEditMode ? Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) : "transparent"
border.color: pathEditMode ? Theme.primary : "transparent"
color: pathEditMode ? Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) : Theme.withAlpha(Theme.surfaceContainer, 0)
border.color: pathEditMode ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: pathEditMode ? 1 : 0
visible: !pathEditMode
@@ -16,7 +16,7 @@ StyledRect {
Column {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 4
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Quick Access")
@@ -34,7 +34,7 @@ StyledRect {
width: parent?.width ?? 0
height: 38
radius: Theme.cornerRadius
color: quickAccessMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : (currentPath === modelData?.path ? Theme.surfacePressed : "transparent")
color: quickAccessMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : (currentPath === modelData?.path ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0))
Row {
anchors.fill: parent
@@ -60,7 +60,7 @@ StyledRect {
width: sortColumn?.width ?? 0
height: 32
radius: Theme.cornerRadius
color: sortMouseArea.containsMouse ? Theme.surfaceVariant : (sortBy === modelData?.value ? Theme.surfacePressed : "transparent")
color: sortMouseArea.containsMouse ? Theme.surfaceVariant : (sortBy === modelData?.value ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0))
Row {
anchors.fill: parent
@@ -114,7 +114,7 @@ StyledRect {
width: sortColumn?.width ?? 0
height: 32
radius: Theme.cornerRadius
color: ascMouseArea.containsMouse ? Theme.surfaceVariant : (sortAscending ? Theme.surfacePressed : "transparent")
color: ascMouseArea.containsMouse ? Theme.surfaceVariant : (sortAscending ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0))
Row {
anchors.fill: parent
@@ -152,7 +152,7 @@ StyledRect {
width: sortColumn?.width ?? 0
height: 32
radius: Theme.cornerRadius
color: descMouseArea.containsMouse ? Theme.surfaceVariant : (!sortAscending ? Theme.surfacePressed : "transparent")
color: descMouseArea.containsMouse ? Theme.surfaceVariant : (!sortAscending ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0))
Row {
anchors.fill: parent
+1 -1
View File
@@ -13,7 +13,7 @@ Rectangle {
height: 200
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
color: Theme.withAlpha(Theme.surfaceContainer, 0.95)
border.color: Theme.secondary
border.width: 2
opacity: showFileInfo ? 1 : 0
@@ -9,7 +9,7 @@ Rectangle {
height: 80
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
color: Theme.withAlpha(Theme.surfaceContainer, 0.95)
border.color: Theme.primary
border.width: 2
opacity: showHints ? 1 : 0
@@ -20,7 +20,7 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Theme.spacingS
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: I18n.tr("Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select")
@@ -99,7 +99,7 @@ Item {
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: I18n.tr("You're All Set!", "greeter completion page title")
@@ -209,7 +209,7 @@ Item {
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: I18n.tr("System Check", "greeter doctor page title")
@@ -45,7 +45,7 @@ Rectangle {
Column {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: root.title
@@ -93,6 +93,8 @@ FloatingWindow {
color: Theme.surfaceContainer
visible: false
onClosed: visible = false
Process {
id: cheatsheetProcess
running: false
@@ -48,7 +48,7 @@ Rectangle {
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
width: parent.width - root.iconContainerSize - Theme.spacingM
StyledText {
@@ -18,7 +18,7 @@ Rectangle {
radius: Theme.cornerRadius
color: bgColor
border.width: selected ? 2 : 0
border.color: selected ? iconColor : "transparent"
border.color: selected ? iconColor : Theme.withAlpha(iconColor, 0)
scale: mouseArea.pressed ? 0.97 : 1
Behavior on scale {
@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
@@ -148,8 +147,7 @@ Item {
iconName: "tune"
title: I18n.tr("Control Center", "greeter feature card title")
description: I18n.tr("Quick system toggles", "greeter feature card description")
// This is doing an IPC since its just easier and lazier to access the bar ref
onClicked: Quickshell.execDetached(["dms", "ipc", "call", "control-center", "open"])
onClicked: BarWidgetService.getBarWindowOnFocusedScreen()?.triggerControlCenter()
}
GreeterFeatureCard {
+6 -6
View File
@@ -361,7 +361,7 @@ DankModal {
Column {
Layout.fillWidth: true
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: I18n.tr("New Session")
@@ -414,7 +414,7 @@ DankModal {
width: parent.width
height: 64
radius: Theme.cornerRadius
color: muxModal.selectedIndex === index ? Theme.primaryContainer : (sessionMouse.containsMouse ? Theme.surfaceContainerHigh : "transparent")
color: muxModal.selectedIndex === index ? Theme.primaryContainer : (sessionMouse.containsMouse ? Theme.surfaceContainerHigh : Theme.withAlpha(Theme.surfaceContainerHigh, 0))
MouseArea {
id: sessionMouse
@@ -449,7 +449,7 @@ DankModal {
// Info
Column {
Layout.fillWidth: true
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: modelData.name
@@ -478,7 +478,7 @@ DankModal {
Layout.preferredHeight: 36
radius: 18
visible: MuxService.supportsRename
color: renameMouse.containsMouse ? Theme.surfaceContainerHighest : "transparent"
color: renameMouse.containsMouse ? Theme.surfaceContainerHighest : Theme.withAlpha(Theme.surfaceContainerHighest, 0)
DankIcon {
anchors.centerIn: parent
@@ -501,7 +501,7 @@ DankModal {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
radius: 18
color: deleteMouse.containsMouse ? Theme.errorContainer : "transparent"
color: deleteMouse.containsMouse ? Theme.errorContainer : Theme.withAlpha(Theme.errorContainer, 0)
DankIcon {
anchors.centerIn: parent
@@ -601,7 +601,7 @@ DankModal {
delegate: Row {
required property var modelData
spacing: 4
spacing: Theme.spacingXS
Rectangle {
width: keyText.width + Theme.spacingS
+1 -1
View File
@@ -314,7 +314,7 @@ FocusScope {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
color: cancelArea.containsMouse ? Theme.surfaceTextHover : Theme.withAlpha(Theme.surfaceTextHover, 0)
border.color: Theme.surfaceVariantAlpha
border.width: 1
enabled: !root.isLoading
+2
View File
@@ -32,6 +32,8 @@ FloatingWindow {
color: Theme.surfaceContainer
visible: false
onClosed: hide()
onVisibleChanged: {
if (visible) {
Qt.callLater(focusContent);
+20 -20
View File
@@ -551,12 +551,12 @@ DankModal {
radius: Theme.cornerRadius
color: {
if (isSelected)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
return Theme.primaryHover;
if (mouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
return Theme.primaryHoverLight;
return Theme.surfaceHover;
}
border.color: isSelected ? Theme.primary : "transparent"
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: isSelected ? 2 : 0
Rectangle {
@@ -585,10 +585,10 @@ DankModal {
width: parent.width * root.holdProgress
color: {
if (gridButtonRect.modelData === "poweroff")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
return Theme.errorSelected;
if (gridButtonRect.modelData === "reboot")
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3);
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
return Theme.withAlpha(Theme.warning, 0.3);
return Theme.primarySelected;
}
}
}
@@ -626,13 +626,13 @@ DankModal {
width: 20
height: 16
radius: 4
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.1)
color: Theme.onSurface_12
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: gridButtonRect.actionData.key
font.pixelSize: Theme.fontSizeSmall - 1
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
color: Theme.surfaceTextSecondary
font.weight: Font.Medium
anchors.centerIn: parent
}
@@ -688,12 +688,12 @@ DankModal {
radius: Theme.cornerRadius
color: {
if (isSelected)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
return Theme.primaryHover;
if (listMouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
return Theme.primaryHoverLight;
return Theme.surfaceHover;
}
border.color: isSelected ? Theme.primary : "transparent"
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: isSelected ? 2 : 0
Rectangle {
@@ -722,10 +722,10 @@ DankModal {
width: parent.width * root.holdProgress
color: {
if (listButtonRect.modelData === "poweroff")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
return Theme.errorSelected;
if (listButtonRect.modelData === "reboot")
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3);
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
return Theme.withAlpha(Theme.warning, 0.3);
return Theme.primarySelected;
}
}
}
@@ -770,7 +770,7 @@ DankModal {
width: 28
height: 20
radius: 4
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.1)
color: Theme.onSurface_12
anchors {
right: parent.right
rightMargin: Theme.spacingM
@@ -780,7 +780,7 @@ DankModal {
StyledText {
text: listButtonRect.actionData.key
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
color: Theme.surfaceTextSecondary
font.weight: Font.Medium
anchors.centerIn: parent
}
@@ -820,7 +820,7 @@ DankModal {
DankIcon {
name: root.showHoldHint ? "warning" : "touch_app"
size: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
color: root.showHoldHint ? Theme.warning : Theme.surfaceTextSecondary
anchors.verticalCenter: parent.verticalCenter
}
@@ -840,7 +840,7 @@ DankModal {
return I18n.tr("Hold to confirm (%1s)").arg(SettingsData.powerActionHoldDuration);
}
font.pixelSize: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
color: root.showHoldHint ? Theme.warning : Theme.surfaceTextSecondary
anchors.verticalCenter: parent.verticalCenter
}
}
+7 -7
View File
@@ -174,15 +174,15 @@ DankModal {
color: {
if (isActive)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
return Theme.primaryPressed;
if (isSelected)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
return Theme.primaryHoverLight;
if (mouseArea.containsMouse)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.06);
return Theme.surfacePressed;
return Theme.surfaceHover;
}
border.color: isActive ? Theme.primary : (isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) : "transparent")
border.color: isActive ? Theme.primary : (isSelected ? Theme.withAlpha(Theme.primary, 0.5) : Theme.withAlpha(Theme.primary, 0))
border.width: (isActive || isSelected) ? 2 : 0
// Shortcut Key Badge on Top-Right Corner
@@ -193,8 +193,8 @@ DankModal {
width: 20
height: 20
radius: 4
color: isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
border.color: isActive ? Theme.primary : "transparent"
color: isActive ? Theme.primaryPressed : Theme.surfaceTextHover
border.color: isActive ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: isActive ? 1 : 0
StyledText {
+19 -14
View File
@@ -48,19 +48,22 @@ FloatingWindow {
log.warn("dgop is not available");
return;
}
if (visible) {
const modalTitle = I18n.tr("System Monitor", "sysmon window title");
for (const toplevel of ToplevelManager.toplevels.values) {
if (toplevel.title !== "System Monitor" && toplevel.title !== modalTitle)
continue;
if (toplevel.activated) {
hide();
return;
}
toplevel.activate();
if (!visible) {
show();
return;
}
const modalTitle = I18n.tr("System Monitor", "sysmon window title");
for (const toplevel of ToplevelManager.toplevels.values) {
if (toplevel.title !== "System Monitor" && toplevel.title !== modalTitle)
continue;
if (toplevel.activated) {
hide();
return;
}
toplevel.activate();
return;
}
hide();
show();
}
@@ -90,6 +93,8 @@ FloatingWindow {
color: Theme.surfaceContainer
visible: false
onClosed: hide()
onCurrentTabChanged: {
if (visible && currentTab === 0 && searchField.visible)
searchField.forceActiveFocus();
@@ -195,7 +200,7 @@ FloatingWindow {
width: 400
height: 200
radius: Theme.cornerRadius
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.1)
color: Theme.errorHover
border.color: Theme.error
border.width: 2
visible: !DgopService.dgopAvailable
@@ -300,7 +305,7 @@ FloatingWindow {
spacing: Theme.spacingM
Row {
spacing: 2
spacing: Theme.spacingXXS
Repeater {
model: [
@@ -326,8 +331,8 @@ FloatingWindow {
width: tabRowContent.implicitWidth + Theme.spacingM * 2
height: Math.round(Theme.fontSizeMedium * 3.1)
radius: Theme.cornerRadius
color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent")
border.color: currentTab === index ? Theme.primary : "transparent"
color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0))
border.color: currentTab === index ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: currentTab === index ? 1 : 0
Row {
@@ -52,7 +52,7 @@ Rectangle {
Row {
anchors.centerIn: parent
spacing: 4
spacing: Theme.spacingXS
Rectangle {
width: 28
@@ -434,7 +434,9 @@ FocusScope {
visible: active
focus: active
sourceComponent: MediaPlayerTab {}
sourceComponent: MediaPlayerTab {
parentModal: root.parentModal
}
onActiveChanged: {
if (active && item)
+4 -1
View File
@@ -8,7 +8,6 @@ import qs.Widgets
FloatingWindow {
id: settingsModal
property bool disablePopupTransparency: true
property var profileBrowser: profileBrowserLoader.item
property var wallpaperBrowser: wallpaperBrowserLoader.item
@@ -93,6 +92,8 @@ FloatingWindow {
color: Theme.surfaceContainer
visible: false
onClosed: hide()
onIsCompactModeChanged: {
enableAnimations = false;
if (!isCompactMode) {
@@ -173,6 +174,8 @@ FloatingWindow {
FocusScope {
id: contentFocusScope
property bool disablePopupTransparency: true
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
@@ -711,7 +711,7 @@ Rectangle {
rightPadding: Theme.spacingS
bottomPadding: Theme.spacingL
topPadding: Theme.spacingM + 2
spacing: 2
spacing: Theme.spacingXXS
ProfileSection {
width: parent.width - parent.leftPadding - parent.rightPadding
@@ -804,7 +804,7 @@ Rectangle {
Column {
id: searchResultsColumn
width: parent.width - parent.leftPadding - parent.rightPadding
spacing: 2
spacing: Theme.spacingXXS
visible: root.searchActive
Item {
@@ -859,7 +859,7 @@ Rectangle {
Column {
width: parent.width - Theme.iconSize - Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
StyledText {
text: resultDelegate.modelData.label
@@ -928,7 +928,7 @@ Rectangle {
width: parent.width - parent.leftPadding - parent.rightPadding
visible: !root.searchActive && root.isCategoryVisible(modelData)
spacing: 2
spacing: Theme.spacingXXS
Rectangle {
width: parent.width
@@ -1035,7 +1035,7 @@ Rectangle {
Column {
id: childrenColumn
width: parent.width
spacing: 2
spacing: Theme.spacingXXS
visible: categoryDelegate.modelData.children !== undefined && root.isCategoryExpanded(categoryDelegate.modelData.id)
clip: true
+2 -2
View File
@@ -141,7 +141,7 @@ DankModal {
Column {
width: parent.width - Theme.iconSize - 4 - chevron.width - Theme.spacingM * 2
spacing: 2
spacing: Theme.spacingXXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
@@ -215,7 +215,7 @@ DankModal {
Column {
id: bodyCol
width: parent.width - Theme.iconSize - Theme.spacingM
spacing: 4
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("No other active sessions on this seat")
+2 -2
View File
@@ -626,7 +626,7 @@ DankModal {
width: 20
height: 20
radius: 4
color: checked ? Theme.primary : "transparent"
color: checked ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.color: checked ? Theme.primary : Theme.outlineButton
border.width: 2
@@ -667,7 +667,7 @@ DankModal {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
color: cancelArea.containsMouse ? Theme.surfaceTextHover : Theme.withAlpha(Theme.surfaceTextHover, 0)
border.color: Theme.surfaceVariantAlpha
border.width: 1
+4 -2
View File
@@ -32,6 +32,8 @@ FloatingWindow {
color: Theme.surfaceContainer
visible: false
onClosed: hide()
function resetForm() {
nameInput.text = "";
appIdInput.text = "";
@@ -504,7 +506,7 @@ FloatingWindow {
width: 20
height: 20
radius: 4
color: parent.indeterminate ? Theme.surfaceVariant : (checked ? Theme.primary : "transparent")
color: parent.indeterminate ? Theme.surfaceVariant : (checked ? Theme.primary : Theme.withAlpha(Theme.primary, 0))
border.color: parent.indeterminate ? Theme.outlineButton : (checked ? Theme.primary : Theme.outlineButton)
border.width: 2
anchors.verticalCenter: parent.verticalCenter
@@ -1841,7 +1843,7 @@ FloatingWindow {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
color: cancelArea.containsMouse ? Theme.surfaceTextHover : Theme.withAlpha(Theme.surfaceTextHover, 0)
border.color: Theme.surfaceVariantAlpha
border.width: 1
+3 -1
View File
@@ -18,6 +18,8 @@ FloatingWindow {
color: Theme.surfaceContainer
visible: false
onClosed: hide()
function show(name) {
nameInput.text = name;
visible = true;
@@ -151,7 +153,7 @@ FloatingWindow {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
color: cancelArea.containsMouse ? Theme.surfaceTextHover : Theme.withAlpha(Theme.surfaceTextHover, 0)
border.color: Theme.surfaceVariantAlpha
border.width: 1
@@ -1,6 +1,7 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Item {
@@ -92,6 +93,15 @@ Item {
precision: root.needsSeconds ? SystemClock.Seconds : SystemClock.Minutes
}
Connections {
target: SessionService
function onSessionResumed() {
systemClock.enabled = false;
systemClock.enabled = true;
}
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
@@ -405,8 +405,8 @@ Item {
var c = root.accentColor;
var grad = ctx.createLinearGradient(0, 0, 0, height);
grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.3));
grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.05));
grad.addColorStop(0, Theme.withAlpha(c, 0.3));
grad.addColorStop(1, Theme.withAlpha(c, 0.05));
ctx.fillStyle = grad;
ctx.beginPath();
@@ -420,7 +420,7 @@ Item {
ctx.closePath();
ctx.fill();
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.6);
ctx.strokeStyle = Theme.withAlpha(c, 0.6);
ctx.lineWidth = 1.5;
ctx.beginPath();
for (var j = 0; j < hist.length; j++) {
@@ -431,7 +431,7 @@ Item {
ctx.stroke();
if (hist2 && hist2.length >= 2) {
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.3);
ctx.strokeStyle = Theme.withAlpha(c, 0.3);
ctx.lineWidth = 1;
ctx.beginPath();
for (var m = 0; m < hist2.length; m++) {
@@ -447,7 +447,7 @@ Item {
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 2
spacing: Theme.spacingXXS
RowLayout {
Layout.fillWidth: true
@@ -206,7 +206,7 @@ PluginComponent {
Rectangle {
height: 1
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
color: Theme.outlineStrong
}
DankFlickable {
@@ -274,7 +274,7 @@ PluginComponent {
}
Column {
spacing: 2
spacing: Theme.spacingXXS
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
@@ -96,7 +96,7 @@ PluginComponent {
width: autoLabel.implicitWidth + Theme.spacingL * 2
height: 28
radius: 14
color: root.autoMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : (autoMouseArea.containsMouse ? Theme.surfaceLight : "transparent")
color: root.autoMode ? Theme.primaryPressed : (autoMouseArea.containsMouse ? Theme.surfaceLight : Theme.withAlpha(Theme.surfaceLight, 0))
border.color: root.autoMode ? Theme.primary : Theme.outlineMedium
border.width: root.autoMode ? 1 : Theme.layerOutlineWidth
@@ -170,7 +170,7 @@ PluginComponent {
radius: Theme.cornerRadius
color: {
if (isActive)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
return Theme.primaryHover;
if (profileMouseArea.containsMouse)
return Theme.surfaceLight;
return Theme.floatingSurface;
@@ -234,7 +234,7 @@ PluginComponent {
verticalBarPill: Component {
Column {
spacing: 2
spacing: Theme.spacingXXS
DankIcon {
name: "monitor"
color: Theme.primary
@@ -333,7 +333,7 @@ PluginComponent {
width: peerListColumn.width
height: peerCardColumn.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: modelData.hostname === (TailscaleService.selfNode ? TailscaleService.selfNode.hostname : "") ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
color: modelData.hostname === (TailscaleService.selfNode ? TailscaleService.selfNode.hostname : "") ? Theme.primaryHoverLight : Theme.surfaceContainerHighest
property bool isSelf: modelData.hostname === (TailscaleService.selfNode ? TailscaleService.selfNode.hostname : "")
property bool isExpanded: detailRoot.expandedHostname === modelData.hostname
@@ -344,7 +344,7 @@ PluginComponent {
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingS
spacing: 2
spacing: Theme.spacingXXS
RowLayout {
width: parent.width
@@ -419,7 +419,7 @@ PluginComponent {
Column {
visible: isExpanded
width: parent.width
spacing: 2
spacing: Theme.spacingXXS
topPadding: 4
RowLayout {
@@ -43,7 +43,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: mouseArea.containsMouse ? hoverTint(root.color) : "transparent"
color: mouseArea.containsMouse ? hoverTint(root.color) : Theme.withAlpha(hoverTint(root.color), 0)
opacity: mouseArea.containsMouse ? 0.08 : 0.0
Behavior on opacity {
@@ -74,7 +74,7 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: Theme.spacingXXS
Typography {
width: parent.width
@@ -13,7 +13,7 @@ Rectangle {
height: menuColumn.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.16)
border.color: Theme.outlineStrong
border.width: 1
MouseArea {
@@ -24,7 +24,7 @@ Rectangle {
id: menuColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 2
spacing: Theme.spacingXXS
DankToggle {
width: parent.width

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