1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-15 00:32:47 -04:00

Compare commits

..

2 Commits

Author SHA1 Message Date
bbedward 6e6416c8ba blur: fix dankbar auto-hide blur, fix synchronization in popouts and
modals
2026-03-30 10:40:52 -04:00
bbedward a0b2debd7e blur: add blur support with ext-bg-effect 2026-03-30 09:33:26 -04:00
84 changed files with 24615 additions and 23132 deletions
-40
View File
@@ -1,40 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/AvengeMedia/DankMaterialShell/core/internal/blur"
"github.com/spf13/cobra"
)
var blurCmd = &cobra.Command{
Use: "blur",
Short: "Background blur utilities",
}
var blurCheckCmd = &cobra.Command{
Use: "check",
Short: "Check if the compositor supports background blur (ext-background-effect-v1)",
Args: cobra.NoArgs,
Run: runBlurCheck,
}
func init() {
blurCmd.AddCommand(blurCheckCmd)
}
func runBlurCheck(cmd *cobra.Command, args []string) {
supported, err := blur.ProbeSupport()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
switch supported {
case true:
fmt.Println("supported")
default:
fmt.Println("unsupported")
}
}
-1
View File
@@ -525,6 +525,5 @@ func getCommonCommands() []*cobra.Command {
configCmd, configCmd,
dlCmd, dlCmd,
randrCmd, randrCmd,
blurCmd,
} }
} }
+3 -7
View File
@@ -820,14 +820,10 @@ func checkOptionalDependencies() []checkResult {
results = append(results, checkImageFormatPlugins()...) results = append(results, checkImageFormatPlugins()...)
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"} terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
terminals = slices.DeleteFunc(terminals, func(t string) bool { if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {
return !utils.CommandExists(t) results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", optionalFeaturesURL})
})
if len(terminals) > 0 {
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, strings.Join(terminals, ", "), "", optionalFeaturesURL})
} else { } else {
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, foot or alacritty", optionalFeaturesURL}) results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty", optionalFeaturesURL})
} }
networkResult, err := network.DetectNetworkStack() networkResult, err := network.DetectNetworkStack()
+3 -28
View File
@@ -109,41 +109,16 @@ func updateArchLinux() error {
} }
var packageName string var packageName string
var isAUR bool if isArchPackageInstalled("dms-shell-bin") {
if isArchPackageInstalled("dms-shell") { packageName = "dms-shell-bin"
packageName = "dms-shell"
} else if isArchPackageInstalled("dms-shell-git") { } else if isArchPackageInstalled("dms-shell-git") {
packageName = "dms-shell-git" packageName = "dms-shell-git"
isAUR = true
} else if isArchPackageInstalled("dms-shell-bin") {
packageName = "dms-shell-bin"
isAUR = true
} else { } else {
fmt.Println("Info: No dms-shell package found.") fmt.Println("Info: Neither dms-shell-bin nor dms-shell-git package found.")
fmt.Println("Info: Falling back to git-based update method...") fmt.Println("Info: Falling back to git-based update method...")
return updateOtherDistros() return updateOtherDistros()
} }
if !isAUR {
fmt.Printf("This will update %s using pacman.\n", packageName)
if !confirmUpdate() {
return errdefs.ErrUpdateCancelled
}
fmt.Printf("\nRunning: sudo pacman -S %s\n", packageName)
cmd := exec.Command("sudo", "pacman", "-S", "--noconfirm", packageName)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Error: Failed to update using pacman: %v\n", err)
return err
}
fmt.Println("dms successfully updated")
return nil
}
var helper string var helper string
var updateCmd *exec.Cmd var updateCmd *exec.Cmd
+1 -4
View File
@@ -5,7 +5,6 @@ package main
import ( import (
"os" "os"
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
) )
@@ -31,9 +30,7 @@ func init() {
} }
func main() { func main() {
clipboard.MaybeServeAndExit() if os.Geteuid() == 0 {
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
log.Fatal("This program should not be run as root. Exiting.") log.Fatal("This program should not be run as root. Exiting.")
} }
+1 -4
View File
@@ -5,7 +5,6 @@ package main
import ( import (
"os" "os"
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
) )
@@ -28,9 +27,7 @@ func init() {
} }
func main() { func main() {
clipboard.MaybeServeAndExit() if os.Geteuid() == 0 {
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
log.Fatal("This program should not be run as root. Exiting.") log.Fatal("This program should not be run as root. Exiting.")
} }
-16
View File
@@ -7,22 +7,6 @@ import (
"strings" "strings"
) )
// isReadOnlyCommand returns true if the CLI args indicate a command that is
// safe to run as root (e.g. shell completion, help).
func isReadOnlyCommand(args []string) bool {
for _, arg := range args[1:] {
if strings.HasPrefix(arg, "-") {
continue
}
switch arg {
case "completion", "help", "__complete":
return true
}
return false
}
return false
}
func isArchPackageInstalled(packageName string) bool { func isArchPackageInstalled(packageName string) bool {
cmd := exec.Command("pacman", "-Q", packageName) cmd := exec.Command("pacman", "-Q", packageName)
err := cmd.Run() err := cmd.Run()
-35
View File
@@ -1,35 +0,0 @@
package blur
import (
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
client "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
const extBackgroundEffectInterface = "ext_background_effect_manager_v1"
func ProbeSupport() (bool, error) {
display, err := client.Connect("")
if err != nil {
return false, err
}
defer display.Context().Close()
registry, err := display.GetRegistry()
if err != nil {
return false, err
}
found := false
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
switch e.Interface {
case extBackgroundEffectInterface:
found = true
}
})
if err := wlhelpers.Roundtrip(display, display.Context()); err != nil {
return false, err
}
return found, nil
}
+84 -119
View File
@@ -1,6 +1,7 @@
package clipboard package clipboard
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
@@ -12,142 +13,66 @@ import (
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
) )
const envServe = "_DMS_CLIPBOARD_SERVE"
const envMime = "_DMS_CLIPBOARD_MIME"
const envPasteOnce = "_DMS_CLIPBOARD_PASTE_ONCE"
const envCacheFile = "_DMS_CLIPBOARD_CACHE"
// MaybeServeAndExit intercepts before cobra when re-exec'd as a clipboard
// child. Reads source data into memory, deletes any cache file, then serves.
func MaybeServeAndExit() {
if os.Getenv(envServe) == "" {
return
}
mimeType := os.Getenv(envMime)
pasteOnce := os.Getenv(envPasteOnce) == "1"
cachePath := os.Getenv(envCacheFile)
var data []byte
var err error
switch {
case cachePath != "":
data, err = os.ReadFile(cachePath)
os.Remove(cachePath)
default:
data, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "clipboard: read source: %v\n", err)
os.Exit(1)
}
if err := serveClipboard(data, mimeType, pasteOnce); err != nil {
fmt.Fprintf(os.Stderr, "clipboard: serve: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}
func Copy(data []byte, mimeType string) error { func Copy(data []byte, mimeType string) error {
return copyForkCached(data, mimeType, false) return CopyReader(bytes.NewReader(data), mimeType, false, false)
} }
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error { func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
if foreground { if foreground {
return serveClipboard(data, mimeType, pasteOnce) return copyServeWithWriter(func(writer io.Writer) error {
total := 0
for total < len(data) {
n, err := writer.Write(data[total:])
total += n
if err != nil {
return err
}
}
if total != len(data) {
return io.ErrShortWrite
}
return nil
}, mimeType, pasteOnce)
} }
return copyForkCached(data, mimeType, pasteOnce) return CopyReader(bytes.NewReader(data), mimeType, foreground, pasteOnce)
} }
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error { func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
if foreground { if !foreground {
buf, err := io.ReadAll(data) return copyFork(data, mimeType, pasteOnce)
if err != nil {
return fmt.Errorf("read source: %w", err)
}
return serveClipboard(buf, mimeType, pasteOnce)
} }
return copyFork(data, mimeType, pasteOnce) return copyServeReader(data, mimeType, pasteOnce)
} }
func newForkCmd(mimeType string, pasteOnce bool, extra ...string) *exec.Cmd { func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
cmd := exec.Command(os.Args[0]) args := []string{os.Args[0], "cl", "copy", "--foreground"}
if pasteOnce {
args = append(args, "--paste-once")
}
args = append(args, "--type", mimeType)
cmd := exec.Command(args[0], args[1:]...)
cmd.Stderr = nil cmd.Stderr = nil
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
cmd.Env = append(os.Environ(), cmd.Env = append(os.Environ(), "DMS_CLIP_FORKED=1")
envServe+"=1",
envMime+"="+mimeType,
)
if pasteOnce {
cmd.Env = append(cmd.Env, envPasteOnce+"=1")
}
cmd.Env = append(cmd.Env, extra...)
return cmd
}
func waitReady(cmd *exec.Cmd) error {
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
return fmt.Errorf("stdout pipe: %w", err) return fmt.Errorf("stdout pipe: %w", err)
} }
if err := cmd.Start(); err != nil {
return fmt.Errorf("start: %w", err)
}
var buf [1]byte
if _, err := stdout.Read(buf[:]); err != nil {
return fmt.Errorf("waiting for clipboard ready: %w", err)
}
return nil
}
func copyForkCached(data []byte, mimeType string, pasteOnce bool) error {
cacheFile, err := createClipboardCacheFile()
if err != nil {
return fmt.Errorf("create cache file: %w", err)
}
cachePath := cacheFile.Name()
if _, err := cacheFile.Write(data); err != nil {
cacheFile.Close()
os.Remove(cachePath)
return fmt.Errorf("write cache file: %w", err)
}
if err := cacheFile.Close(); err != nil {
os.Remove(cachePath)
return fmt.Errorf("close cache file: %w", err)
}
cmd := newForkCmd(mimeType, pasteOnce, envCacheFile+"="+cachePath)
cmd.Stdin = nil
if err := waitReady(cmd); err != nil {
os.Remove(cachePath)
return err
}
return nil
}
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
cmd := newForkCmd(mimeType, pasteOnce)
switch src := data.(type) { switch src := data.(type) {
case *os.File: case *os.File:
cmd.Stdin = src cmd.Stdin = src
return waitReady(cmd) if err := cmd.Start(); err != nil {
return fmt.Errorf("start: %w", err)
}
default: default:
stdin, err := cmd.StdinPipe() stdin, err := cmd.StdinPipe()
if err != nil { if err != nil {
return fmt.Errorf("stdin pipe: %w", err) return fmt.Errorf("stdin pipe: %w", err)
} }
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return fmt.Errorf("start: %w", err) return fmt.Errorf("start: %w", err)
} }
@@ -158,22 +83,50 @@ func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
if err := stdin.Close(); err != nil { if err := stdin.Close(); err != nil {
return fmt.Errorf("close stdin: %w", err) return fmt.Errorf("close stdin: %w", err)
} }
var buf [1]byte
if _, err := stdout.Read(buf[:]); err != nil {
return fmt.Errorf("waiting for clipboard ready: %w", err)
}
return nil
} }
var buf [1]byte
if _, err := stdout.Read(buf[:]); err != nil {
return fmt.Errorf("waiting for clipboard ready: %w", err)
}
return nil
} }
func signalReady() { func signalReady() {
if os.Getenv(envServe) == "" { if os.Getenv("DMS_CLIP_FORKED") == "" {
return return
} }
os.Stdout.Write([]byte{1}) os.Stdout.Write([]byte{1})
} }
func copyServeReader(data io.Reader, mimeType string, pasteOnce bool) error {
cachedData, err := createClipboardCacheFile()
if err != nil {
return fmt.Errorf("create clipboard cache file: %w", err)
}
defer os.Remove(cachedData.Name())
if _, err := io.Copy(cachedData, data); err != nil {
return fmt.Errorf("cache clipboard data: %w", err)
}
if err := cachedData.Close(); err != nil {
return fmt.Errorf("close temp cache file: %w", err)
}
return copyServeWithWriter(func(writer io.Writer) error {
cachedFile, err := os.Open(cachedData.Name())
if err != nil {
return fmt.Errorf("open temp cache file: %w", err)
}
defer cachedFile.Close()
if _, err := io.Copy(writer, cachedFile); err != nil {
return fmt.Errorf("write clipboard data: %w", err)
}
return nil
}, mimeType, pasteOnce)
}
func createClipboardCacheFile() (*os.File, error) { func createClipboardCacheFile() (*os.File, error) {
preferredDirs := []string{} preferredDirs := []string{}
@@ -194,7 +147,7 @@ func createClipboardCacheFile() (*os.File, error) {
return os.CreateTemp("", "dms-clipboard-*") return os.CreateTemp("", "dms-clipboard-*")
} }
func serveClipboard(data []byte, mimeType string, pasteOnce bool) error { func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOnce bool) error {
display, err := wlclient.Connect("") display, err := wlclient.Connect("")
if err != nil { if err != nil {
return fmt.Errorf("wayland connect: %w", err) return fmt.Errorf("wayland connect: %w", err)
@@ -236,10 +189,12 @@ func serveClipboard(data []byte, mimeType string, pasteOnce bool) error {
if bindErr != nil { if bindErr != nil {
return fmt.Errorf("registry bind: %w", bindErr) return fmt.Errorf("registry bind: %w", bindErr)
} }
if dataControlMgr == nil { if dataControlMgr == nil {
return fmt.Errorf("compositor does not support ext_data_control_manager_v1") return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
} }
defer dataControlMgr.Destroy() defer dataControlMgr.Destroy()
if seat == nil { if seat == nil {
return fmt.Errorf("no seat available") return fmt.Errorf("no seat available")
} }
@@ -278,12 +233,18 @@ func serveClipboard(data []byte, mimeType string, pasteOnce bool) error {
cancelled := make(chan struct{}) cancelled := make(chan struct{})
pasted := make(chan struct{}, 1) pasted := make(chan struct{}, 1)
sendErr := make(chan error, 1)
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) { source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
_ = syscall.SetNonblock(e.Fd, false) defer syscall.Close(e.Fd)
file := os.NewFile(uintptr(e.Fd), "pipe") file := os.NewFile(uintptr(e.Fd), "pipe")
defer file.Close() defer file.Close()
_, _ = file.Write(data) if err := writeTo(file); err != nil {
select {
case sendErr <- err:
default:
}
}
select { select {
case pasted <- struct{}{}: case pasted <- struct{}{}:
default: default:
@@ -305,6 +266,8 @@ func serveClipboard(data []byte, mimeType string, pasteOnce bool) error {
select { select {
case <-cancelled: case <-cancelled:
return nil return nil
case err := <-sendErr:
return err
case <-pasted: case <-pasted:
if pasteOnce { if pasteOnce {
return nil return nil
@@ -558,10 +521,12 @@ func copyMultiServe(offers []Offer, pasteOnce bool) error {
if bindErr != nil { if bindErr != nil {
return fmt.Errorf("registry bind: %w", bindErr) return fmt.Errorf("registry bind: %w", bindErr)
} }
if dataControlMgr == nil { if dataControlMgr == nil {
return fmt.Errorf("compositor does not support ext_data_control_manager_v1") return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
} }
defer dataControlMgr.Destroy() defer dataControlMgr.Destroy()
if seat == nil { if seat == nil {
return fmt.Errorf("no seat available") return fmt.Errorf("no seat available")
} }
@@ -589,12 +554,12 @@ func copyMultiServe(offers []Offer, pasteOnce bool) error {
pasted := make(chan struct{}, 1) pasted := make(chan struct{}, 1)
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) { source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
_ = syscall.SetNonblock(e.Fd, false) defer syscall.Close(e.Fd)
file := os.NewFile(uintptr(e.Fd), "pipe") file := os.NewFile(uintptr(e.Fd), "pipe")
defer file.Close() defer file.Close()
if data, ok := offerMap[e.MimeType]; ok { if data, ok := offerMap[e.MimeType]; ok {
_, _ = file.Write(data) file.Write(data)
} }
select { select {
@@ -137,7 +137,7 @@ bind = SUPER, bracketright, layoutmsg, preselect r
# === Sizing & Layout === # === Sizing & Layout ===
bind = SUPER, R, layoutmsg, togglesplit bind = SUPER, R, layoutmsg, togglesplit
bind = SUPER CTRL, F, resizeactive, exact 100% 100% bind = SUPER CTRL, F, resizeactive, exact 100%
# === Move/resize windows with mainMod + LMB/RMB and dragging === # === Move/resize windows with mainMod + LMB/RMB and dragging ===
bindmd = SUPER, mouse:272, Move window, movewindow bindmd = SUPER, mouse:272, Move window, movewindow
@@ -94,7 +94,6 @@ windowrule = tile on, match:class ^(gnome-control-center)$
windowrule = tile on, match:class ^(pavucontrol)$ windowrule = tile on, match:class ^(pavucontrol)$
windowrule = tile on, match:class ^(nm-connection-editor)$ windowrule = tile on, match:class ^(nm-connection-editor)$
windowrule = float on, match:class ^(org\.gnome\.Calculator)$
windowrule = float on, match:class ^(gnome-calculator)$ windowrule = float on, match:class ^(gnome-calculator)$
windowrule = float on, match:class ^(galculator)$ windowrule = float on, match:class ^(galculator)$
windowrule = float on, match:class ^(blueman-manager)$ windowrule = float on, match:class ^(blueman-manager)$
-1
View File
@@ -224,7 +224,6 @@ window-rule {
open-floating false open-floating false
} }
window-rule { window-rule {
match app-id=r#"^org\.gnome\.Calculator$"#
match app-id=r#"^gnome-calculator$"# match app-id=r#"^gnome-calculator$"#
match app-id=r#"^galculator$"# match app-id=r#"^galculator$"#
match app-id=r#"^blueman-manager$"# match app-id=r#"^blueman-manager$"#
+51 -6
View File
@@ -242,7 +242,11 @@ func (a *ArchDistribution) getDMSMapping(variant deps.PackageVariant) PackageMap
return PackageMapping{Name: "dms-shell-git", Repository: RepoTypeAUR} return PackageMapping{Name: "dms-shell-git", Repository: RepoTypeAUR}
} }
return PackageMapping{Name: "dms-shell", Repository: RepoTypeSystem} if a.packageInstalled("dms-shell-bin") {
return PackageMapping{Name: "dms-shell-bin", Repository: RepoTypeAUR}
}
return PackageMapping{Name: "dms-shell-bin", Repository: RepoTypeAUR}
} }
func (a *ArchDistribution) detectXwaylandSatellite() deps.Dependency { func (a *ArchDistribution) detectXwaylandSatellite() deps.Dependency {
@@ -536,7 +540,7 @@ func (a *ArchDistribution) reorderAURPackages(packages []string) []string {
var dmsShell []string var dmsShell []string
for _, pkg := range packages { for _, pkg := range packages {
if pkg == "dms-shell-git" { if pkg == "dms-shell-git" || pkg == "dms-shell-bin" {
dmsShell = append(dmsShell, pkg) dmsShell = append(dmsShell, pkg)
} else { } else {
isDep := false isDep := false
@@ -617,7 +621,7 @@ func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context,
} }
} }
if pkg == "dms-shell-git" { if pkg == "dms-shell-git" || pkg == "dms-shell-bin" {
srcinfoPath := filepath.Join(packageDir, ".SRCINFO") srcinfoPath := filepath.Join(packageDir, ".SRCINFO")
depsToRemove := []string{ depsToRemove := []string{
"depends = quickshell", "depends = quickshell",
@@ -640,7 +644,15 @@ func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context,
} }
srcinfoPath = filepath.Join(packageDir, ".SRCINFO") srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
{ if pkg == "dms-shell-bin" {
progressChan <- InstallProgressMsg{
Phase: PhaseAURPackages,
Progress: startProgress + 0.35*(endProgress-startProgress),
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
IsComplete: false,
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
}
} else {
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseAURPackages, Phase: PhaseAURPackages,
Progress: startProgress + 0.3*(endProgress-startProgress), Progress: startProgress + 0.3*(endProgress-startProgress),
@@ -727,9 +739,42 @@ func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context,
CommandInfo: "sudo pacman -U built-package", CommandInfo: "sudo pacman -U built-package",
} }
// Find .pkg.tar* files - for split packages, install the base and any installed compositor variants
var files []string var files []string
matches, _ := filepath.Glob(filepath.Join(packageDir, "*.pkg.tar*")) if pkg == "dms-shell-git" || pkg == "dms-shell-bin" {
files = matches // For DMS split packages, install base package
pattern := filepath.Join(packageDir, fmt.Sprintf("%s-%s*.pkg.tar*", pkg, "*"))
matches, err := filepath.Glob(pattern)
if err == nil {
for _, match := range matches {
basename := filepath.Base(match)
// Always include base package
if !strings.Contains(basename, "hyprland") && !strings.Contains(basename, "niri") {
files = append(files, match)
}
}
}
// Also update compositor-specific packages if they're installed
if strings.HasSuffix(pkg, "-git") {
if a.packageInstalled("dms-shell-hyprland-git") {
hyprlandPattern := filepath.Join(packageDir, "dms-shell-hyprland-git-*.pkg.tar*")
if hyprlandMatches, err := filepath.Glob(hyprlandPattern); err == nil && len(hyprlandMatches) > 0 {
files = append(files, hyprlandMatches[0])
}
}
if a.packageInstalled("dms-shell-niri-git") {
niriPattern := filepath.Join(packageDir, "dms-shell-niri-git-*.pkg.tar*")
if niriMatches, err := filepath.Glob(niriPattern); err == nil && len(niriMatches) > 0 {
files = append(files, niriMatches[0])
}
}
}
} else {
// For other packages, install all built packages
matches, _ := filepath.Glob(filepath.Join(packageDir, "*.pkg.tar*"))
files = matches
}
if len(files) == 0 { if len(files) == 0 {
return fmt.Errorf("no package files found after building %s", pkg) return fmt.Errorf("no package files found after building %s", pkg)
+13 -29
View File
@@ -444,21 +444,20 @@ func GetFocusedMonitor() string {
type outputInfo struct { type outputInfo struct {
x, y int32 x, y int32
scale float64
transform int32 transform int32
} }
func getAllOutputInfos() map[string]*outputInfo { func getOutputInfo(outputName string) (*outputInfo, bool) {
display, err := client.Connect("") display, err := client.Connect("")
if err != nil { if err != nil {
return nil return nil, false
} }
ctx := display.Context() ctx := display.Context()
defer ctx.Close() defer ctx.Close()
registry, err := display.GetRegistry() registry, err := display.GetRegistry()
if err != nil { if err != nil {
return nil return nil, false
} }
var outputManager *wlr_output_management.ZwlrOutputManagerV1 var outputManager *wlr_output_management.ZwlrOutputManagerV1
@@ -477,17 +476,16 @@ func getAllOutputInfos() map[string]*outputInfo {
}) })
if err := wlhelpers.Roundtrip(display, ctx); err != nil { if err := wlhelpers.Roundtrip(display, ctx); err != nil {
return nil return nil, false
} }
if outputManager == nil { if outputManager == nil {
return nil return nil, false
} }
type headState struct { type headState struct {
name string name string
x, y int32 x, y int32
scale float64
transform int32 transform int32
} }
heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState) heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState)
@@ -503,9 +501,6 @@ func getAllOutputInfos() map[string]*outputInfo {
state.x = pe.X state.x = pe.X
state.y = pe.Y state.y = pe.Y
}) })
e.Head.SetScaleHandler(func(se wlr_output_management.ZwlrOutputHeadV1ScaleEvent) {
state.scale = se.Scale
})
e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) { e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) {
state.transform = te.Transform state.transform = te.Transform
}) })
@@ -516,32 +511,21 @@ func getAllOutputInfos() map[string]*outputInfo {
for !done { for !done {
if err := ctx.Dispatch(); err != nil { if err := ctx.Dispatch(); err != nil {
return nil return nil, false
} }
} }
result := make(map[string]*outputInfo, len(heads))
for _, state := range heads { for _, state := range heads {
if state.name == "" { if state.name == outputName {
continue return &outputInfo{
} x: state.x,
result[state.name] = &outputInfo{ y: state.y,
x: state.x, transform: state.transform,
y: state.y, }, true
scale: state.scale,
transform: state.transform,
} }
} }
return result
}
func getOutputInfo(outputName string) (*outputInfo, bool) { return nil, false
infos := getAllOutputInfos()
if infos == nil {
return nil, false
}
info, ok := infos[outputName]
return info, ok
} }
func getDWLActiveWindow() (*WindowGeometry, error) { func getDWLActiveWindow() (*WindowGeometry, error) {
+67 -93
View File
@@ -2,7 +2,6 @@ package screenshot
import ( import (
"fmt" "fmt"
"math"
"sync" "sync"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
@@ -305,20 +304,22 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
if len(outputs) == 0 { if len(outputs) == 0 {
return nil, fmt.Errorf("no outputs available") return nil, fmt.Errorf("no outputs available")
} }
if len(outputs) == 1 { if len(outputs) == 1 {
return s.captureWholeOutput(outputs[0]) return s.captureWholeOutput(outputs[0])
} }
wlrInfos := getAllOutputInfos() // Capture all outputs first to get actual buffer sizes
type capturedOutput struct {
type pendingOutput struct { output *WaylandOutput
result *CaptureResult result *CaptureResult
logX float64 physX int
logY float64 physY int
scale float64
} }
var pending []pendingOutput captured := make([]capturedOutput, 0, len(outputs))
maxScale := 1.0
var minX, minY, maxX, maxY int
first := true
for _, output := range outputs { for _, output := range outputs {
result, err := s.captureWholeOutput(output) result, err := s.captureWholeOutput(output)
@@ -327,74 +328,50 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
continue continue
} }
logX, logY := float64(output.x), float64(output.y) outX, outY := output.x, output.y
scale := float64(output.scale) scale := float64(output.scale)
switch DetectCompositor() { switch DetectCompositor() {
case CompositorHyprland: case CompositorHyprland:
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok { if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
logX, logY = float64(hx), float64(hy) outX, outY = hx, hy
} }
if hs := GetHyprlandMonitorScale(output.name); hs > 0 { if s := GetHyprlandMonitorScale(output.name); s > 0 {
scale = hs scale = s
} }
default: case CompositorDWL:
if wlrInfos != nil { if info, ok := getOutputInfo(output.name); ok {
if info, ok := wlrInfos[output.name]; ok { outX, outY = info.x, info.y
logX, logY = float64(info.x), float64(info.y)
if info.scale > 0 {
scale = info.scale
}
}
} }
} }
if scale <= 0 { if scale <= 0 {
scale = 1.0 scale = 1.0
} }
pending = append(pending, pendingOutput{result: result, logX: logX, logY: logY, scale: scale}) physX := int(float64(outX) * scale)
if scale > maxScale { physY := int(float64(outY) * scale)
maxScale = scale
}
}
if len(pending) == 0 { captured = append(captured, capturedOutput{
return nil, fmt.Errorf("failed to capture any outputs") output: output,
} result: result,
if len(pending) == 1 { physX: physX,
return pending[0].result, nil physY: physY,
} })
type layoutEntry struct { right := physX + result.Buffer.Width
result *CaptureResult bottom := physY + result.Buffer.Height
canvasX int
canvasY int
canvasW int
canvasH int
}
entries := make([]layoutEntry, len(pending))
var minX, minY, maxX, maxY int
for i, p := range pending { if first {
cx := int(math.Round(p.logX * maxScale)) minX, minY = physX, physY
cy := int(math.Round(p.logY * maxScale)) maxX, maxY = right, bottom
cw := int(math.Round(float64(p.result.Buffer.Width) * maxScale / p.scale)) first = false
ch := int(math.Round(float64(p.result.Buffer.Height) * maxScale / p.scale))
entries[i] = layoutEntry{result: p.result, canvasX: cx, canvasY: cy, canvasW: cw, canvasH: ch}
right := cx + cw
bottom := cy + ch
if i == 0 {
minX, minY, maxX, maxY = cx, cy, right, bottom
continue continue
} }
if cx < minX {
minX = cx if physX < minX {
minX = physX
} }
if cy < minY { if physY < minY {
minY = cy minY = physY
} }
if right > maxX { if right > maxX {
maxX = right maxX = right
@@ -404,26 +381,35 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
} }
} }
if len(captured) == 0 {
return nil, fmt.Errorf("failed to capture any outputs")
}
if len(captured) == 1 {
return captured[0].result, nil
}
totalW := maxX - minX totalW := maxX - minX
totalH := maxY - minY totalH := maxY - minY
composite, err := CreateShmBuffer(totalW, totalH, totalW*4)
compositeStride := totalW * 4
composite, err := CreateShmBuffer(totalW, totalH, compositeStride)
if err != nil { if err != nil {
for _, e := range entries { for _, c := range captured {
e.result.Buffer.Close() c.result.Buffer.Close()
} }
return nil, fmt.Errorf("create composite buffer: %w", err) return nil, fmt.Errorf("create composite buffer: %w", err)
} }
composite.Clear() composite.Clear()
var format uint32 var format uint32
for _, e := range entries { for _, c := range captured {
if format == 0 { if format == 0 {
format = e.result.Format format = c.result.Format
} }
s.blitBufferScaled(composite, e.result.Buffer, s.blitBuffer(composite, c.result.Buffer, c.physX-minX, c.physY-minY, c.result.YInverted)
e.canvasX-minX, e.canvasY-minY, e.canvasW, e.canvasH, c.result.Buffer.Close()
e.result.YInverted)
e.result.Buffer.Close()
} }
return &CaptureResult{ return &CaptureResult{
@@ -433,44 +419,32 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
}, nil }, nil
} }
func (s *Screenshoter) blitBufferScaled(dst, src *ShmBuffer, dstX, dstY, dstW, dstH int, yInverted bool) { func (s *Screenshoter) blitBuffer(dst, src *ShmBuffer, dstX, dstY int, yInverted bool) {
if dstW <= 0 || dstH <= 0 {
return
}
srcData := src.Data() srcData := src.Data()
dstData := dst.Data() dstData := dst.Data()
for dy := 0; dy < dstH; dy++ { for srcY := 0; srcY < src.Height; srcY++ {
canvasY := dstY + dy actualSrcY := srcY
if canvasY < 0 || canvasY >= dst.Height {
continue
}
srcY := dy * src.Height / dstH
if yInverted { if yInverted {
srcY = src.Height - 1 - srcY actualSrcY = src.Height - 1 - srcY
} }
if srcY < 0 || srcY >= src.Height {
dy := dstY + srcY
if dy < 0 || dy >= dst.Height {
continue continue
} }
srcRowOff := srcY * src.Stride srcRowOff := actualSrcY * src.Stride
dstRowOff := canvasY * dst.Stride dstRowOff := dy * dst.Stride
for dx := 0; dx < dstW; dx++ { for srcX := 0; srcX < src.Width; srcX++ {
canvasX := dstX + dx dx := dstX + srcX
if canvasX < 0 || canvasX >= dst.Width { if dx < 0 || dx >= dst.Width {
continue
}
srcX := dx * src.Width / dstW
if srcX >= src.Width {
continue continue
} }
si := srcRowOff + srcX*4 si := srcRowOff + srcX*4
di := dstRowOff + canvasX*4 di := dstRowOff + dx*4
if si+3 >= len(srcData) || di+3 >= len(dstData) { if si+3 >= len(srcData) || di+3 >= len(dstData) {
continue continue
-29
View File
@@ -31,7 +31,6 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/trayrecovery"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
@@ -73,7 +72,6 @@ var clipboardManager *clipboard.Manager
var dbusManager *serverDbus.Manager var dbusManager *serverDbus.Manager
var wlContext *wlcontext.SharedContext var wlContext *wlcontext.SharedContext
var themeModeManager *thememode.Manager var themeModeManager *thememode.Manager
var trayRecoveryManager *trayrecovery.Manager
var locationManager *location.Manager var locationManager *location.Manager
var geoClientInstance geolocation.Client var geoClientInstance geolocation.Client
@@ -396,18 +394,6 @@ func InitializeThemeModeManager() error {
return nil return nil
} }
func InitializeTrayRecoveryManager() error {
manager, err := trayrecovery.NewManager()
if err != nil {
return err
}
trayRecoveryManager = manager
log.Info("TrayRecovery manager initialized")
return nil
}
func InitializeLocationManager(geoClient geolocation.Client) error { func InitializeLocationManager(geoClient geolocation.Client) error {
manager, err := location.NewManager(geoClient) manager, err := location.NewManager(geoClient)
if err != nil { if err != nil {
@@ -1339,9 +1325,6 @@ func cleanupManagers() {
if themeModeManager != nil { if themeModeManager != nil {
themeModeManager.Close() themeModeManager.Close()
} }
if trayRecoveryManager != nil {
trayRecoveryManager.Close()
}
if wlContext != nil { if wlContext != nil {
wlContext.Close() wlContext.Close()
} }
@@ -1627,18 +1610,6 @@ func Start(printDocs bool) error {
}() }()
} }
go func() {
<-loginctlReady
if loginctlManager == nil {
return
}
if err := InitializeTrayRecoveryManager(); err != nil {
log.Warnf("TrayRecovery manager unavailable: %v", err)
} else {
trayRecoveryManager.WatchLoginctl(loginctlManager)
}
}()
go func() { go func() {
geoClient := geolocation.NewClient() geoClient := geolocation.NewClient()
geoClientInstance = geoClient geoClientInstance = geoClient
@@ -1,93 +0,0 @@
package trayrecovery
import (
"fmt"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
"github.com/godbus/dbus/v5"
)
const resumeDelay = 3 * time.Second
type Manager struct {
conn *dbus.Conn
stopChan chan struct{}
wg sync.WaitGroup
}
func NewManager() (*Manager, error) {
conn, err := dbus.ConnectSessionBus()
if err != nil {
return nil, fmt.Errorf("failed to connect to session bus: %w", err)
}
m := &Manager{
conn: conn,
stopChan: make(chan struct{}),
}
// Run a startup scan after a delay — covers the case where the process
// was killed during suspend and restarted by systemd (Type=dbus).
// The fresh process never sees the PrepareForSleep true→false transition,
// so the loginctl watcher alone is not enough.
go m.scheduleRecovery()
return m, nil
}
// WatchLoginctl subscribes to loginctl session state changes and triggers
// tray recovery after resume from suspend (PrepareForSleep false transition).
// This handles the case where the process survives suspend.
func (m *Manager) WatchLoginctl(lm *loginctl.Manager) {
ch := lm.Subscribe("tray-recovery")
m.wg.Add(1)
go func() {
defer m.wg.Done()
defer lm.Unsubscribe("tray-recovery")
wasSleeping := false
for {
select {
case <-m.stopChan:
return
case state, ok := <-ch:
if !ok {
return
}
if state.PreparingForSleep {
wasSleeping = true
continue
}
if wasSleeping {
wasSleeping = false
go m.scheduleRecovery()
}
}
}
}()
}
func (m *Manager) scheduleRecovery() {
select {
case <-time.After(resumeDelay):
m.recoverTrayItems()
case <-m.stopChan:
}
}
func (m *Manager) Close() {
select {
case <-m.stopChan:
return
default:
close(m.stopChan)
}
m.wg.Wait()
if m.conn != nil {
m.conn.Close()
}
log.Info("TrayRecovery manager closed")
}
@@ -1,262 +0,0 @@
package trayrecovery
import (
"context"
"strings"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/godbus/dbus/v5"
)
const (
sniWatcherDest = "org.kde.StatusNotifierWatcher"
sniWatcherPath = "/StatusNotifierWatcher"
sniWatcherIface = "org.kde.StatusNotifierWatcher"
sniItemIface = "org.kde.StatusNotifierItem"
dbusIface = "org.freedesktop.DBus"
propsIface = "org.freedesktop.DBus.Properties"
probeTimeout = 300 * time.Millisecond
connProbeTimeout = 150 * time.Millisecond
batchSize = 30
)
var excludedPrefixes = []string{
"org.freedesktop.",
"org.gnome.",
"org.kde.StatusNotifier",
"com.canonical.AppMenu",
"org.mpris.",
"org.pipewire.",
"org.pulseaudio",
"fi.epitaph",
"quickshell",
"org.kde.quickshell",
}
func (m *Manager) recoverTrayItems() {
registeredItems := m.getRegisteredItems()
allNames := m.getDBusNames()
if allNames == nil {
return
}
registeredConnIDs := m.buildRegisteredConnIDs(registeredItems)
count := len(registeredItems)
log.Infof("TrayRecoveryService: scanning DBus for unregistered SNI items (%d already registered)...", count)
m.scanWellKnownNames(allNames, registeredItems, registeredConnIDs)
m.scanConnectionIDs(allNames, registeredItems, registeredConnIDs)
}
func (m *Manager) getRegisteredItems() []string {
obj := m.conn.Object(sniWatcherDest, sniWatcherPath)
variant, err := obj.GetProperty(sniWatcherIface + ".RegisteredStatusNotifierItems")
if err != nil {
log.Warnf("TrayRecoveryService: failed to get registered items: %v", err)
return nil
}
switch v := variant.Value().(type) {
case []string:
return v
case []any:
items := make([]string, 0, len(v))
for _, elem := range v {
if s, ok := elem.(string); ok {
items = append(items, s)
}
}
return items
}
return nil
}
func (m *Manager) getDBusNames() []string {
var names []string
err := m.conn.BusObject().Call(dbusIface+".ListNames", 0).Store(&names)
if err != nil {
log.Warnf("TrayRecoveryService: failed to list bus names: %v", err)
return nil
}
return names
}
func (m *Manager) getNameOwner(name string) string {
var owner string
err := m.conn.BusObject().Call(dbusIface+".GetNameOwner", 0, name).Store(&owner)
if err != nil {
return ""
}
return owner
}
// buildRegisteredConnIDs resolves every registered SNI item (well-known name
// or :1.xxx connection ID) to a canonical connection ID. This prevents
// duplicates in both directions.
func (m *Manager) buildRegisteredConnIDs(registeredItems []string) map[string]bool {
connIDs := make(map[string]bool, len(registeredItems))
for _, item := range registeredItems {
name := extractName(item)
if strings.HasPrefix(name, ":1.") {
connIDs[name] = true
} else {
owner := m.getNameOwner(name)
if owner != "" {
connIDs[owner] = true
}
}
}
return connIDs
}
// scanWellKnownNames probes well-known names (e.g. DinoX, nm-applet) for
// unregistered SNI items and re-registers them.
func (m *Manager) scanWellKnownNames(allNames []string, registeredItems []string, registeredConnIDs map[string]bool) {
registeredRaw := strings.Join(registeredItems, "\n")
for _, name := range allNames {
if strings.HasPrefix(name, ":") {
continue
}
if strings.Contains(registeredRaw, name) {
continue
}
// Skip if this name's connection ID is already in the registered set
// (handles the case where the app registered via connection ID instead)
connForName := m.getNameOwner(name)
if connForName != "" && registeredConnIDs[connForName] {
continue
}
if isExcludedName(name) {
continue
}
short := shortName(name)
objectPaths := []string{
"/StatusNotifierItem",
"/org/ayatana/NotificationItem/" + short,
}
for _, objPath := range objectPaths {
if m.probeSNI(name, objPath, probeTimeout) {
m.registerSNI(name)
// Update set so the connection-ID section won't double-register this app
if connForName != "" {
registeredConnIDs[connForName] = true
}
break
}
}
}
}
// scanConnectionIDs probes all :1.xxx connections in parallel for unregistered
// SNI items (e.g. Vesktop, Electron apps). Most non-SNI connections return an
// error instantly, so this is fast.
func (m *Manager) scanConnectionIDs(allNames []string, registeredItems []string, registeredConnIDs map[string]bool) {
registeredRaw := strings.Join(registeredItems, "\n")
registeredLower := strings.ToLower(registeredRaw)
var wg sync.WaitGroup
sem := make(chan struct{}, batchSize)
for _, name := range allNames {
if !strings.HasPrefix(name, ":1.") {
continue
}
if registeredConnIDs[name] {
continue
}
sem <- struct{}{}
wg.Add(1)
go func(conn string) {
defer wg.Done()
defer func() { <-sem }()
sniID := m.getSNIId(conn, connProbeTimeout)
if sniID == "" {
return
}
// Skip if an item with the same Id is already registered (case-insensitive)
if strings.Contains(registeredLower, strings.ToLower(sniID)) {
return
}
m.registerSNI(conn)
log.Infof("TrayRecovery: re-registered %s (Id: %s)", conn, sniID)
}(name)
}
wg.Wait()
}
func (m *Manager) probeSNI(dest, path string, timeout time.Duration) bool {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
obj := m.conn.Object(dest, dbus.ObjectPath(path))
var props map[string]dbus.Variant
err := obj.CallWithContext(ctx, propsIface+".GetAll", 0, sniItemIface).Store(&props)
if err != nil {
return false
}
_, hasID := props["Id"]
return hasID
}
func (m *Manager) getSNIId(dest string, timeout time.Duration) string {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
obj := m.conn.Object(dest, "/StatusNotifierItem")
var variant dbus.Variant
err := obj.CallWithContext(ctx, propsIface+".Get", 0, sniItemIface, "Id").Store(&variant)
if err != nil {
return ""
}
id, _ := variant.Value().(string)
return id
}
func (m *Manager) registerSNI(name string) {
obj := m.conn.Object(sniWatcherDest, sniWatcherPath)
call := obj.Call(sniWatcherIface+".RegisterStatusNotifierItem", 0, name)
if call.Err != nil {
log.Warnf("TrayRecovery: failed to register %s: %v", name, call.Err)
return
}
log.Infof("TrayRecovery: re-registered %s", name)
}
func extractName(item string) string {
if idx := strings.IndexByte(item, '/'); idx != -1 {
return item[:idx]
}
return item
}
func shortName(name string) string {
parts := strings.Split(name, ".")
if len(parts) > 0 {
return parts[len(parts)-1]
}
return name
}
func isExcludedName(name string) bool {
for _, prefix := range excludedPrefixes {
if strings.HasPrefix(name, prefix) {
return true
}
}
return false
}
+1 -1
View File
@@ -139,7 +139,7 @@ func dmsPackageName(distroID string, dependencies []deps.Dependency) string {
if isGit { if isGit {
return "dms-shell-git" return "dms-shell-git"
} }
return "dms-shell" return "dms-shell-bin"
case distros.FamilyFedora, distros.FamilyUbuntu, distros.FamilyDebian, distros.FamilySUSE: case distros.FamilyFedora, distros.FamilyUbuntu, distros.FamilyDebian, distros.FamilySUSE:
if isGit { if isGit {
return "dms-git" return "dms-git"
-2
View File
@@ -301,7 +301,6 @@ Singleton {
property var workspaceNameIcons: ({}) property var workspaceNameIcons: ({})
property bool waveProgressEnabled: true property bool waveProgressEnabled: true
property bool scrollTitleEnabled: true property bool scrollTitleEnabled: true
property bool mediaAdaptiveWidthEnabled: true
property bool audioVisualizerEnabled: true property bool audioVisualizerEnabled: true
property string audioScrollMode: "volume" property string audioScrollMode: "volume"
property int audioWheelScrollAmount: 5 property int audioWheelScrollAmount: 5
@@ -435,7 +434,6 @@ Singleton {
property bool soundNewNotification: true property bool soundNewNotification: true
property bool soundVolumeChanged: true property bool soundVolumeChanged: true
property bool soundPluggedIn: true property bool soundPluggedIn: true
property bool soundLogin: false
property int acMonitorTimeout: 0 property int acMonitorTimeout: 0
property int acLockTimeout: 0 property int acLockTimeout: 0
@@ -140,7 +140,6 @@ var SPEC = {
workspaceNameIcons: { def: {} }, workspaceNameIcons: { def: {} },
waveProgressEnabled: { def: true }, waveProgressEnabled: { def: true },
scrollTitleEnabled: { def: true }, scrollTitleEnabled: { def: true },
mediaAdaptiveWidthEnabled: { def: true },
audioVisualizerEnabled: { def: true }, audioVisualizerEnabled: { def: true },
audioScrollMode: { def: "volume" }, audioScrollMode: { def: "volume" },
audioWheelScrollAmount: { def: 5 }, audioWheelScrollAmount: { def: 5 },
@@ -243,7 +242,6 @@ var SPEC = {
soundsEnabled: { def: true }, soundsEnabled: { def: true },
useSystemSoundTheme: { def: false }, useSystemSoundTheme: { def: false },
soundLogin: { def: false },
soundNewNotification: { def: true }, soundNewNotification: { def: true },
soundVolumeChanged: { def: true }, soundVolumeChanged: { def: true },
soundPluggedIn: { def: true }, soundPluggedIn: { def: true },
-12
View File
@@ -221,22 +221,10 @@ Item {
} }
} }
Timer {
id: loginSoundTimer
// Half a second delay before playing login sound, otherwise the sound may be cut off
// 50 is the minimum that seems to work, but 500 is safer
interval: 500
repeat: false
onTriggered: {
AudioService.playLoginSoundIfApplicable();
}
}
Component.onCompleted: { Component.onCompleted: {
dockRecreateDebounce.start(); dockRecreateDebounce.start();
// Force PolkitService singleton to initialize // Force PolkitService singleton to initialize
PolkitService.polkitAvailable; PolkitService.polkitAvailable;
loginSoundTimer.start();
} }
Loader { Loader {
+3 -1
View File
@@ -369,7 +369,9 @@ Item {
} }
function previous(): void { function previous(): void {
MprisController.previousOrRewind(); if (MprisController.activePlayer && MprisController.activePlayer.canGoPrevious) {
MprisController.activePlayer.previous();
}
} }
function next(): void { function next(): void {
@@ -122,7 +122,7 @@ Item {
} }
StyledText { StyledText {
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No recent clipboard entries found") : I18n.tr("Connecting to clipboard service…") text: I18n.tr("No recent clipboard entries found")
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
@@ -181,7 +181,7 @@ Item {
} }
StyledText { StyledText {
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No saved clipboard entries") : I18n.tr("Connecting to clipboard service…") text: I18n.tr("No saved clipboard entries")
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
@@ -60,12 +60,15 @@ DankModal {
} }
function show() { function show() {
if (!clipboardAvailable) {
ToastService.showError(I18n.tr("Clipboard service not available"));
return;
}
open(); open();
activeImageLoads = 0; activeImageLoads = 0;
shouldHaveFocus = true; shouldHaveFocus = true;
ClipboardService.reset(); ClipboardService.reset();
if (clipboardAvailable) ClipboardService.refresh();
ClipboardService.refresh();
keyboardController.reset(); keyboardController.reset();
Qt.callLater(function () { Qt.callLater(function () {
@@ -50,11 +50,14 @@ DankPopout {
} }
function show() { function show() {
if (!clipboardAvailable) {
ToastService.showError(I18n.tr("Clipboard service not available"));
return;
}
open(); open();
activeImageLoads = 0; activeImageLoads = 0;
ClipboardService.reset(); ClipboardService.reset();
if (clipboardAvailable) ClipboardService.refresh();
ClipboardService.refresh();
keyboardController.reset(); keyboardController.reset();
Qt.callLater(function () { Qt.callLater(function () {
@@ -119,10 +122,10 @@ DankPopout {
onBackgroundClicked: hide() onBackgroundClicked: hide()
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (!shouldBeVisible) if (!shouldBeVisible) {
return; return;
if (clipboardAvailable) }
ClipboardService.refresh(); ClipboardService.refresh();
keyboardController.reset(); keyboardController.reset();
Qt.callLater(function () { Qt.callLater(function () {
if (contentLoader.item?.searchField) { if (contentLoader.item?.searchField) {
+1 -1
View File
@@ -31,7 +31,7 @@ Item {
property real animationOffset: Theme.spacingL property real animationOffset: Theme.spacingL
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) property color backgroundColor: Theme.surfaceContainer
property color borderColor: Theme.outlineMedium property color borderColor: Theme.outlineMedium
property real borderWidth: 0 property real borderWidth: 0
property real cornerRadius: Theme.cornerRadius property real cornerRadius: Theme.cornerRadius
+1 -1
View File
@@ -132,7 +132,7 @@ DankModal {
modalWidth: 680 modalWidth: 680
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 680 modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 680
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) backgroundColor: Theme.surfaceContainer
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1
@@ -311,7 +311,7 @@ FocusScope {
Item { Item {
anchors.fill: parent anchors.fill: parent
visible: !editMode && !(root.parentModal?.isClosing ?? false) visible: !editMode
Item { Item {
id: footerBar id: footerBar
@@ -737,6 +737,8 @@ FocusScope {
Item { Item {
width: parent.width 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) height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
opacity: root.parentModal?.isClosing ? 0 : 1
ResultsList { ResultsList {
id: resultsList id: resultsList
anchors.fill: parent anchors.fill: parent
@@ -324,8 +324,6 @@ Item {
height: 24 height: 24
z: 100 z: 100
visible: { visible: {
if (BlurService.enabled)
return false;
if (mainListView.contentHeight <= mainListView.height) if (mainListView.contentHeight <= mainListView.height)
return false; return false;
var atBottom = mainListView.contentY >= mainListView.contentHeight - mainListView.height + mainListView.originY - 5; var atBottom = mainListView.contentY >= mainListView.contentHeight - mainListView.height + mainListView.originY - 5;
@@ -451,7 +449,7 @@ Item {
case "apps": case "apps":
return "apps"; return "apps";
default: default:
return "search_off"; return root.controller?.searchQuery?.length > 0 ? "search_off" : "search";
} }
} }
} }
@@ -487,9 +485,9 @@ Item {
case "plugins": case "plugins":
return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins"); return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins");
case "apps": case "apps":
return I18n.tr("No apps found"); return hasQuery ? I18n.tr("No apps found") : I18n.tr("Type to search apps");
default: default:
return I18n.tr("No results found"); return hasQuery ? I18n.tr("No results found") : I18n.tr("Type to search");
} }
} }
} }
@@ -133,7 +133,7 @@ DankPopout {
QtObject { QtObject {
id: modalAdapter id: modalAdapter
property bool spotlightOpen: appDrawerPopout.shouldBeVisible property bool spotlightOpen: appDrawerPopout.shouldBeVisible
readonly property bool isClosing: !appDrawerPopout.shouldBeVisible property bool isClosing: false
function hide() { function hide() {
appDrawerPopout.close(); appDrawerPopout.close();
@@ -34,7 +34,7 @@ PluginComponent {
id: detailRoot id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2 implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) color: Theme.surfaceContainerHigh
DankActionButton { DankActionButton {
anchors.top: parent.top anchors.top: parent.top
@@ -252,7 +252,7 @@ PluginComponent {
width: parent ? parent.width : 300 width: parent ? parent.width : 300
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.surfaceContainerHighest
border.width: 1 border.width: 1
border.color: Theme.outlineLight border.color: Theme.outlineLight
opacity: 1.0 opacity: 1.0
@@ -33,7 +33,7 @@ Row {
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle { background: Rectangle {
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) color: Theme.surfaceContainer
border.color: Theme.primarySelected border.color: Theme.primarySelected
border.width: 0 border.width: 0
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -207,9 +207,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.source ? Theme.primary : Theme.outlineLight border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData === AudioService.source ? 2 : 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -218,9 +218,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.sink ? Theme.primary : Theme.outlineLight border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData === AudioService.sink ? 2 : 1 border.width: 0
DankRipple { DankRipple {
id: deviceRipple id: deviceRipple
@@ -397,9 +397,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.sink ? Theme.primary : Theme.outlineLight border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData === AudioService.sink ? 2 : 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -129,9 +129,8 @@ Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 64 height: 64
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: Theme.outlineLight border.width: 0
border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -165,9 +164,8 @@ Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 64 height: 64
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: Theme.outlineLight border.width: 0
border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -153,7 +153,7 @@ Item {
width: 320 width: 320
height: contentColumn.implicitHeight + Theme.spacingL * 2 height: contentColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) color: Theme.surfaceContainer
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0 border.width: 0
opacity: modalVisible ? 1 : 0 opacity: modalVisible ? 1 : 0
@@ -229,6 +229,7 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 0
Component.onCompleted: { Component.onCompleted: {
if (!isConnected) if (!isConnected)
@@ -242,8 +243,8 @@ Rectangle {
if (isConnecting) if (isConnecting)
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12); return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
if (deviceMouseArea.containsMouse) if (deviceMouseArea.containsMouse)
return Theme.primaryHoverLight; return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
return Theme.surfaceLight; return Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency);
} }
border.color: { border.color: {
@@ -251,9 +252,8 @@ Rectangle {
return Theme.warning; return Theme.warning;
if (isConnected) if (isConnected)
return Theme.primary; return Theme.primary;
return Theme.outlineLight; return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12);
} }
border.width: (isConnecting || isConnected) ? 2 : 1
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -490,9 +490,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: availableMouseArea.containsMouse && isInteractive ? Theme.primaryHoverLight : Theme.surfaceLight color: availableMouseArea.containsMouse && isInteractive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: Theme.outlineLight border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 0
opacity: isInteractive ? 1 : 0.6 opacity: isInteractive ? 1 : 0.6
Row { Row {
@@ -79,9 +79,9 @@ Rectangle {
width: parent.width width: parent.width
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData.mount === currentMountPath ? Theme.primary : Theme.outlineLight border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.mount === currentMountPath ? 2 : 1 border.width: modelData.mount === currentMountPath ? 2 : 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -308,9 +308,9 @@ Rectangle {
width: parent.width width: parent.width
height: wiredContentRow.implicitHeight + Theme.spacingM * 2 height: wiredContentRow.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: wiredNetworkMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight color: wiredNetworkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: isActive ? Theme.primary : Theme.outlineLight border.color: Theme.primary
border.width: isActive ? 2 : 1 border.width: 0
Row { Row {
id: wiredContentRow id: wiredContentRow
@@ -565,9 +565,9 @@ Rectangle {
width: wifiContent.width width: wifiContent.width
height: wifiContentRow.implicitHeight + Theme.spacingM * 2 height: wifiContentRow.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: networkMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: wifiDelegate.isConnected ? Theme.primary : Theme.outlineLight border.color: wifiDelegate.isConnected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: wifiDelegate.isConnected ? 2 : 1 border.width: 0
Row { Row {
id: wifiContentRow id: wifiContentRow
@@ -969,7 +969,6 @@ Item {
axis: barWindow.axis axis: barWindow.axis
barSpacing: barConfig?.spacing ?? 4 barSpacing: barConfig?.spacing ?? 4
barConfig: topBarContent.barConfig barConfig: topBarContent.barConfig
widgetData: parent.widgetData
isAutoHideBar: topBarContent.barConfig?.autoHide ?? false isAutoHideBar: topBarContent.barConfig?.autoHide ?? false
isAtBottom: barWindow.axis?.edge === "bottom" isAtBottom: barWindow.axis?.edge === "bottom"
visible: SettingsData.getFilteredScreens("systemTray").includes(barWindow.screen) && SystemTray.items.values.length > 0 visible: SettingsData.getFilteredScreens("systemTray").includes(barWindow.screen) && SystemTray.items.values.length > 0
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell.Services.UPower
import qs.Common import qs.Common
import qs.Modules.Plugins import qs.Modules.Plugins
import qs.Services import qs.Services
@@ -11,8 +10,6 @@ BasePill {
property bool batteryPopupVisible: false property bool batteryPopupVisible: false
property var popoutTarget: null property var popoutTarget: null
property real touchpadAccumulator: 0
readonly property int barPosition: { readonly property int barPosition: {
switch (axis?.edge) { switch (axis?.edge) {
case "top": case "top":
@@ -122,44 +119,5 @@ BasePill {
battery.triggerRipple(this, mouse.x, mouse.y); battery.triggerRipple(this, mouse.x, mouse.y);
toggleBatteryPopup(); toggleBatteryPopup();
} }
onWheel: wheel => {
var delta = wheel.angleDelta.y;
if (delta === 0)
return;
// Check if this is a touchpad
if (delta !== 120 && delta !== -120) {
touchpadAccumulator += delta;
console.info("Acc: "+touchpadAccumulator);
if (Math.abs(touchpadAccumulator) < 500)
return;
delta = touchpadAccumulator;
touchpadAccumulator = 0;
}
console.info("Trigger! Delta: "+delta)
// This is after the other delta checks so it only shows on valid Y scroll
if (typeof PowerProfiles === "undefined") {
ToastService.showError("power-profiles-daemon not available");
return;
}
// Get list of profiles, and current index
const profiles = [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []);
var index = profiles.findIndex(profile => PowerProfiles.profile === profile);
// Step once based on mouse wheel direction
if (delta > 0) index += 1;
else index -= 1;
// Already at end of list, can't go further
if (index < 0 || index >= profiles.length) return;
// Set new profile
PowerProfiles.profile = profiles[index];
if (PowerProfiles.profile !== profiles[index]) {
ToastService.showError("Failed to set power profile");
}
}
} }
} }
@@ -102,7 +102,7 @@ BasePill {
StyledTextMetrics { StyledTextMetrics {
id: cpuBaseline id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
text: "100%" text: "88%"
} }
StyledTextMetrics { StyledTextMetrics {
@@ -17,7 +17,7 @@ BasePill {
property int availableWidth: 400 property int availableWidth: 400
readonly property int maxNormalWidth: 456 readonly property int maxNormalWidth: 456
readonly property int maxCompactWidth: 288 readonly property int maxCompactWidth: 288
property Toplevel activeWindow: null readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property var activeDesktopEntry: null property var activeDesktopEntry: null
property bool isHovered: mouseArea.containsMouse property bool isHovered: mouseArea.containsMouse
property bool isAutoHideBar: false property bool isAutoHideBar: false
@@ -38,44 +38,10 @@ BasePill {
return 0; return 0;
} }
function updateActiveWindow() {
const active = ToplevelManager.activeToplevel;
if (!active) {
// Only clear if our tracked window is no longer alive
if (activeWindow) {
const alive = ToplevelManager.toplevels?.values;
if (alive && !Array.from(alive).some(t => t === activeWindow))
activeWindow = null;
}
return;
}
if (!parentScreen || CompositorService.filterCurrentDisplay([active], parentScreen?.name)?.length > 0) {
activeWindow = active;
}
// else: active window is on a different screen so keep the previous value
}
Component.onCompleted: { Component.onCompleted: {
updateActiveWindow();
updateDesktopEntry(); updateDesktopEntry();
} }
Connections {
target: ToplevelManager
function onActiveToplevelChanged() {
root.updateActiveWindow();
}
}
Connections {
target: CompositorService
function onToplevelsChanged() {
root.updateActiveWindow();
}
}
Connections { Connections {
target: DesktopEntries target: DesktopEntries
function onApplicationsChanged() { function onApplicationsChanged() {
+53 -114
View File
@@ -19,8 +19,7 @@ BasePill {
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
property bool compactMode: false property bool compactMode: false
property var widgetData: null property var widgetData: null
readonly property bool adaptiveWidthEnabled: SettingsData.mediaAdaptiveWidthEnabled readonly property int textWidth: {
readonly property int maxTextWidth: {
const size = widgetData?.mediaSize !== undefined ? widgetData.mediaSize : SettingsData.mediaSize; const size = widgetData?.mediaSize !== undefined ? widgetData.mediaSize : SettingsData.mediaSize;
switch (size) { switch (size) {
case 0: case 0:
@@ -37,7 +36,10 @@ BasePill {
if (isVerticalOrientation) { if (isVerticalOrientation) {
return widgetThickness - horizontalPadding * 2; return widgetThickness - horizontalPadding * 2;
} }
return 0; const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20;
const audioVizWidth = 20;
const contentWidth = audioVizWidth + Theme.spacingXS + controlsWidth;
return contentWidth + (textWidth > 0 ? textWidth + Theme.spacingXS : 0);
} }
readonly property int currentContentHeight: { readonly property int currentContentHeight: {
if (!isVerticalOrientation) { if (!isVerticalOrientation) {
@@ -97,7 +99,7 @@ BasePill {
if (isMouseWheelY) { if (isMouseWheelY) {
if (deltaY > 0) { if (deltaY > 0) {
MprisController.previousOrRewind(); activePlayer.previous();
} else { } else {
activePlayer.next(); activePlayer.next();
} }
@@ -105,7 +107,7 @@ BasePill {
scrollAccumulatorY += deltaY; scrollAccumulatorY += deltaY;
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) { if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
if (scrollAccumulatorY > 0) { if (scrollAccumulatorY > 0) {
MprisController.previousOrRewind(); activePlayer.previous();
} else { } else {
activePlayer.next(); activePlayer.next();
} }
@@ -117,28 +119,7 @@ BasePill {
content: Component { content: Component {
Item { Item {
id: contentRoot implicitWidth: root.playerAvailable ? root.currentContentWidth : 0
readonly property real measuredTextWidth: {
if (!root.playerAvailable || root.maxTextWidth <= 0 || !textContainer.visible)
return 0;
// Preserve the fixed-width text slot even if metadata is briefly empty.
if (!root.adaptiveWidthEnabled)
return root.maxTextWidth;
if (textContainer.displayText.length === 0)
return 0;
const rawWidth = mediaText.contentWidth;
if (!isFinite(rawWidth) || rawWidth <= 0)
return 0;
return Math.min(root.maxTextWidth, Math.ceil(rawWidth));
}
readonly property int horizontalContentWidth: {
const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20;
const audioVizWidth = 20;
const baseWidth = audioVizWidth + Theme.spacingXS + controlsWidth;
return baseWidth + (measuredTextWidth > 0 ? measuredTextWidth + Theme.spacingXS : 0);
}
implicitWidth: root.playerAvailable ? (root.isVerticalOrientation ? root.currentContentWidth : horizontalContentWidth) : 0
implicitHeight: root.playerAvailable ? root.currentContentHeight : 0 implicitHeight: root.playerAvailable ? root.currentContentHeight : 0
opacity: root.playerAvailable ? 1 : 0 opacity: root.playerAvailable ? 1 : 0
@@ -151,9 +132,8 @@ BasePill {
Behavior on implicitWidth { Behavior on implicitWidth {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.shortDuration
easing.type: Easing.BezierSpline easing.type: Theme.standardEasing
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
} }
} }
@@ -234,7 +214,7 @@ BasePill {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
activePlayer.togglePlaying(); activePlayer.togglePlaying();
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
MprisController.previousOrRewind(); activePlayer.previous();
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
activePlayer.next(); activePlayer.next();
} }
@@ -289,7 +269,7 @@ BasePill {
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: contentRoot.measuredTextWidth width: textWidth
height: root.widgetThickness height: root.widgetThickness
visible: { visible: {
const size = widgetData?.mediaSize !== undefined ? widgetData.mediaSize : SettingsData.mediaSize; const size = widgetData?.mediaSize !== undefined ? widgetData.mediaSize : SettingsData.mediaSize;
@@ -298,95 +278,50 @@ BasePill {
clip: true clip: true
color: "transparent" color: "transparent"
Behavior on width { StyledText {
NumberAnimation { id: mediaText
duration: Theme.mediumDuration property bool needsScrolling: implicitWidth > textContainer.width && SettingsData.scrollTitleEnabled
easing.type: Easing.BezierSpline property real scrollOffset: 0
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
anchors.verticalCenter: parent.verticalCenter
text: textContainer.displayText
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
wrapMode: Text.NoWrap
x: needsScrolling ? -scrollOffset : 0
onTextChanged: {
scrollOffset = 0;
scrollAnimation.restart();
} }
}
Item { SequentialAnimation {
id: textClip id: scrollAnimation
anchors.fill: parent running: mediaText.needsScrolling && textContainer.visible
clip: true loops: Animation.Infinite
StyledText { PauseAnimation {
id: mediaText duration: 2000
property bool needsScrolling: implicitWidth > textContainer.width && SettingsData.scrollTitleEnabled
property real scrollOffset: 0
property real textShift: 0
anchors.verticalCenter: parent.verticalCenter
text: textContainer.displayText
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
wrapMode: Text.NoWrap
x: (needsScrolling ? -scrollOffset : 0) + textShift
opacity: 1
onTextChanged: {
scrollOffset = 0;
textShift = 0;
scrollAnimation.restart();
textChangeAnimation.restart();
} }
SequentialAnimation { NumberAnimation {
id: scrollAnimation target: mediaText
running: mediaText.needsScrolling && textContainer.visible property: "scrollOffset"
loops: Animation.Infinite from: 0
to: mediaText.implicitWidth - textContainer.width + 5
PauseAnimation { duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
duration: 2000 easing.type: Easing.Linear
}
NumberAnimation {
target: mediaText
property: "scrollOffset"
from: 0
to: mediaText.implicitWidth - textContainer.width + 5
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
easing.type: Easing.Linear
}
PauseAnimation {
duration: 2000
}
NumberAnimation {
target: mediaText
property: "scrollOffset"
to: 0
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
easing.type: Easing.Linear
}
} }
SequentialAnimation { PauseAnimation {
id: textChangeAnimation duration: 2000
}
ParallelAnimation { NumberAnimation {
NumberAnimation { target: mediaText
target: mediaText property: "scrollOffset"
property: "opacity" to: 0
from: 0.7 duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
to: 1 easing.type: Easing.Linear
duration: Theme.shortDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
NumberAnimation {
target: mediaText
property: "textShift"
from: 4
to: 0
duration: Theme.shortDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
}
} }
} }
} }
@@ -435,7 +370,11 @@ BasePill {
anchors.fill: parent anchors.fill: parent
enabled: root.playerAvailable enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: MprisController.previousOrRewind() onClicked: {
if (activePlayer) {
activePlayer.previous();
}
}
} }
} }
@@ -16,11 +16,8 @@ BasePill {
enableCursor: false enableCursor: false
property var parentWindow: null property var parentWindow: null
property var widgetData: null
property string section: "right"
property bool isAtBottom: false property bool isAtBottom: false
property bool isAutoHideBar: false property bool isAutoHideBar: false
property bool useOverflowPopup: !widgetData?.trayUseInlineExpansion
readonly property var hiddenTrayIds: { readonly property var hiddenTrayIds: {
const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || ""; const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || "";
return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : []; return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : [];
@@ -43,76 +40,6 @@ BasePill {
return `${id}::${tooltipTitle}`; return `${id}::${tooltipTitle}`;
} }
function trayIconSourceFor(trayItem) {
let icon = trayItem && trayItem.icon;
if (typeof icon === 'string' || icon instanceof String) {
if (icon === "")
return "";
if (icon.includes("?path=")) {
const split = icon.split("?path=");
if (split.length !== 2)
return icon;
const name = split[0];
const path = split[1];
let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
}
return `file://${path}/${fileName}`;
}
if (icon.startsWith("/") && !icon.startsWith("file://"))
return `file://${icon}`;
return icon;
}
return "";
}
function activateInlineTrayItem(trayItem, anchorItem) {
if (!trayItem)
return;
if (!trayItem.onlyMenu) {
trayItem.activate();
return;
}
if (!trayItem.hasMenu)
return;
root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
}
function openInlineTrayContextMenu(trayItem, areaItem, mouse, anchorItem) {
if (!trayItem) {
return;
}
if (!trayItem.hasMenu) {
const gp = areaItem.mapToGlobal(mouse.x, mouse.y);
root.callContextMenuFallback(trayItem.id, Math.round(gp.x), Math.round(gp.y));
return;
}
root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
}
function toggleIconName() {
const edge = root.axis?.edge;
if (root.useOverflowPopup) {
switch (edge) {
case "left":
return root.menuOpen ? "keyboard_arrow_left" : "keyboard_arrow_right";
case "right":
return root.menuOpen ? "keyboard_arrow_right" : "keyboard_arrow_left";
case "bottom":
return root.menuOpen ? "keyboard_arrow_down" : "keyboard_arrow_up";
case "top":
return root.menuOpen ? "keyboard_arrow_up" : "keyboard_arrow_down";
}
}
if (edge === "left" || edge === "right") {
return root.menuOpen == (root.section !== "right") ? "keyboard_arrow_up" : "keyboard_arrow_down";
}
return root.menuOpen != (root.section === "right") ? "keyboard_arrow_left" : "keyboard_arrow_right";
}
// ! TODO - replace with either native dbus client (like plugins use) or just a DMS cli or something // ! TODO - replace with either native dbus client (like plugins use) or just a DMS cli or something
function callContextMenuFallback(trayItemId, globalX, globalY) { function callContextMenuFallback(trayItemId, globalX, globalY) {
const script = ['ITEMS=$(dbus-send --session --print-reply --dest=org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierWatcher string:RegisteredStatusNotifierItems 2>/dev/null)', 'while IFS= read -r line; do', ' line="${line#*\\\"}"', ' line="${line%\\\"*}"', ' [ -z "$line" ] && continue', ' BUS="${line%%/*}"', ' OBJ="/${line#*/}"', ' ID=$(dbus-send --session --print-reply --dest="$BUS" "$OBJ" org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierItem string:Id 2>/dev/null | grep -oP "(?<=\\\")(.*?)(?=\\\")" | tail -1)', ' if [ "$ID" = "$1" ]; then', ' dbus-send --session --type=method_call --dest="$BUS" "$OBJ" org.kde.StatusNotifierItem.ContextMenu int32:"$2" int32:"$3"', ' exit 0', ' fi', 'done <<< "$ITEMS"',].join("\n"); const script = ['ITEMS=$(dbus-send --session --print-reply --dest=org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierWatcher string:RegisteredStatusNotifierItems 2>/dev/null)', 'while IFS= read -r line; do', ' line="${line#*\\\"}"', ' line="${line%\\\"*}"', ' [ -z "$line" ] && continue', ' BUS="${line%%/*}"', ' OBJ="/${line#*/}"', ' ID=$(dbus-send --session --print-reply --dest="$BUS" "$OBJ" org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierItem string:Id 2>/dev/null | grep -oP "(?<=\\\")(.*?)(?=\\\")" | tail -1)', ' if [ "$ID" = "$1" ]; then', ' dbus-send --session --type=method_call --dest="$BUS" "$OBJ" org.kde.StatusNotifierItem.ContextMenu int32:"$2" int32:"$3"', ' exit 0', ' fi', 'done <<< "$ITEMS"',].join("\n");
@@ -151,13 +78,6 @@ BasePill {
item: item item: item
})) }))
readonly property var hiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item))) readonly property var hiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
readonly property bool reverseInlineHorizontal: !useOverflowPopup && !isVerticalOrientation && section === "right"
readonly property bool reverseInlineVertical: !useOverflowPopup && isVerticalOrientation && section === "right"
readonly property var displayedMainBarItems: reverseInlineHorizontal ? [...mainBarItems].reverse() : mainBarItems
readonly property var displayedInlineExpandedItems: (reverseInlineHorizontal ? [...hiddenBarItems].reverse() : hiddenBarItems).map(item => ({
key: getTrayItemKey(item),
item: item
}))
function moveTrayItemInFullOrder(visibleFromIndex, visibleToIndex) { function moveTrayItemInFullOrder(visibleFromIndex, visibleToIndex) {
if (visibleFromIndex === visibleToIndex || visibleFromIndex < 0 || visibleToIndex < 0) if (visibleFromIndex === visibleToIndex || visibleFromIndex < 0 || visibleToIndex < 0)
@@ -183,7 +103,6 @@ BasePill {
property int dropTargetIndex: -1 property int dropTargetIndex: -1
property bool suppressShiftAnimation: false property bool suppressShiftAnimation: false
readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length
readonly property bool inlineExpanded: hasHiddenItems && !useOverflowPopup && menuOpen
visible: allTrayItems.length > 0 visible: allTrayItems.length > 0
opacity: allTrayItems.length > 0 ? 1 : 0 opacity: allTrayItems.length > 0 ? 1 : 0
@@ -279,11 +198,10 @@ BasePill {
id: rowComp id: rowComp
Row { Row {
spacing: 0 spacing: 0
layoutDirection: root.reverseInlineHorizontal ? Qt.RightToLeft : Qt.LeftToRight
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
values: root.displayedMainBarItems values: root.mainBarItems
objectProp: "key" objectProp: "key"
} }
@@ -291,7 +209,29 @@ BasePill {
id: delegateRoot id: delegateRoot
property var trayItem: modelData.item property var trayItem: modelData.item
property string itemKey: modelData.key property string itemKey: modelData.key
property string iconSource: root.trayIconSourceFor(trayItem) property string iconSource: {
let icon = trayItem && trayItem.icon;
if (typeof icon === 'string' || icon instanceof String) {
if (icon === "")
return "";
if (icon.includes("?path=")) {
const split = icon.split("?path=");
if (split.length !== 2)
return icon;
const name = split[0];
const path = split[1];
let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
}
return `file://${path}/${fileName}`;
}
if (icon.startsWith("/") && !icon.startsWith("file://"))
return `file://${icon}`;
return icon;
}
return "";
}
width: root.trayItemSize width: root.trayItemSize
height: root.barThickness height: root.barThickness
@@ -431,8 +371,7 @@ BasePill {
} }
if (!delegateRoot.trayItem.hasMenu) if (!delegateRoot.trayItem.hasMenu)
return; return;
if (root.useOverflowPopup) root.menuOpen = false;
root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis); root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
@@ -441,8 +380,8 @@ BasePill {
const distance = Math.abs(mouse.x - dragHandler.dragStartPos.x); const distance = Math.abs(mouse.x - dragHandler.dragStartPos.x);
if (distance > 5) { if (distance > 5) {
dragHandler.dragging = true; dragHandler.dragging = true;
root.draggedIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - index) : index; root.draggedIndex = index;
root.dropTargetIndex = root.draggedIndex; root.dropTargetIndex = index;
} }
} }
if (!dragHandler.dragging) if (!dragHandler.dragging)
@@ -452,8 +391,7 @@ BasePill {
dragHandler.dragAxisOffset = axisOffset; dragHandler.dragAxisOffset = axisOffset;
const itemSize = root.trayItemSize; const itemSize = root.trayItemSize;
const slotOffset = Math.round(axisOffset / itemSize); const slotOffset = Math.round(axisOffset / itemSize);
const visualTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset)); const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
const newTargetIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - visualTargetIndex) : visualTargetIndex;
if (newTargetIndex !== root.dropTargetIndex) { if (newTargetIndex !== root.dropTargetIndex) {
root.dropTargetIndex = newTargetIndex; root.dropTargetIndex = newTargetIndex;
} }
@@ -469,8 +407,7 @@ BasePill {
root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y)); root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
return; return;
} }
if (root.useOverflowPopup) root.menuOpen = false;
root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis); root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
} }
@@ -492,7 +429,7 @@ BasePill {
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: root.toggleIconName() name: root.menuOpen ? "expand_less" : "expand_more"
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
color: Theme.widgetTextColor color: Theme.widgetTextColor
} }
@@ -514,301 +451,6 @@ BasePill {
} }
} }
} }
Repeater {
model: ScriptModel {
values: root.displayedInlineExpandedItems
objectProp: "key"
}
delegate: inlineExpandedTrayItemDelegate
}
}
}
Component {
id: inlineExpandedTrayItemDelegate
Item {
property var trayItem: modelData.item
property string itemKey: modelData.key
property string iconSource: root.trayIconSourceFor(trayItem)
width: root.isVerticalOrientation ? root.barThickness : (root.inlineExpanded ? root.trayItemSize : 0)
height: root.isVerticalOrientation ? (root.inlineExpanded ? root.trayItemSize : 0) : root.barThickness
visible: width > 0 || height > 0
Behavior on width {
enabled: !root.isVerticalOrientation
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on height {
enabled: root.isVerticalOrientation
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Rectangle {
id: inlineVisualContent
width: root.trayItemSize
height: root.trayItemSize
x: root.isVerticalOrientation ? Math.round((parent.width - width) / 2) : (root.reverseInlineHorizontal ? parent.width - width : 0)
y: root.isVerticalOrientation ? (root.reverseInlineVertical ? parent.height - height : 0) : Math.round((parent.height - height) / 2)
radius: Theme.cornerRadius
color: inlineTrayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
opacity: root.inlineExpanded ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
IconImage {
id: inlineIconImg
anchors.centerIn: parent
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
source: iconSource
asynchronous: true
smooth: true
mipmap: true
visible: status === Image.Ready
}
Text {
anchors.centerIn: parent
visible: !inlineIconImg.visible
text: {
const itemId = trayItem?.id || "";
if (!itemId)
return "?";
return itemId.charAt(0).toUpperCase();
}
font.pixelSize: 10
color: Theme.widgetTextColor
}
DankRipple {
id: inlineItemRipple
cornerRadius: Theme.cornerRadius
}
}
MouseArea {
id: inlineTrayItemArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
enabled: root.inlineExpanded
onPressed: mouse => {
const pos = mapToItem(inlineVisualContent, mouse.x, mouse.y);
inlineItemRipple.trigger(pos.x, pos.y);
}
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
root.activateInlineTrayItem(trayItem, inlineVisualContent);
return;
}
if (mouse.button !== Qt.RightButton)
return;
root.openInlineTrayContextMenu(trayItem, inlineTrayItemArea, mouse, inlineVisualContent);
}
}
}
}
Component {
id: verticalMainTrayItemDelegate
Item {
property var trayItem: modelData.item
property string itemKey: modelData.key
property string iconSource: root.trayIconSourceFor(trayItem)
width: root.barThickness
height: root.trayItemSize
z: dragHandler.dragging ? 100 : 0
property real shiftOffset: {
if (root.draggedIndex < 0)
return 0;
if (index === root.draggedIndex)
return 0;
const dragIdx = root.draggedIndex;
const dropIdx = root.dropTargetIndex;
const shiftAmount = root.trayItemSize;
if (dropIdx < 0)
return 0;
if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
return -shiftAmount;
if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
return shiftAmount;
return 0;
}
transform: Translate {
y: shiftOffset
Behavior on y {
enabled: !root.suppressShiftAnimation
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
}
Item {
id: dragHandler
anchors.fill: parent
property bool dragging: false
property point dragStartPos: Qt.point(0, 0)
property real dragAxisOffset: 0
property bool longPressing: false
Timer {
id: longPressTimer
interval: 400
repeat: false
onTriggered: dragHandler.longPressing = true
}
}
Rectangle {
id: visualContent
width: root.trayItemSize
height: root.trayItemSize
anchors.centerIn: parent
radius: Theme.cornerRadius
color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
border.width: dragHandler.dragging ? 2 : 0
border.color: Theme.primary
opacity: dragHandler.dragging ? 0.8 : 1.0
transform: Translate {
y: dragHandler.dragging ? dragHandler.dragAxisOffset : 0
}
IconImage {
id: iconImg
anchors.centerIn: parent
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
source: iconSource
asynchronous: true
smooth: true
mipmap: true
visible: status === Image.Ready
}
Text {
anchors.centerIn: parent
visible: !iconImg.visible
text: {
const itemId = trayItem?.id || "";
if (!itemId)
return "?";
return itemId.charAt(0).toUpperCase();
}
font.pixelSize: 10
color: Theme.widgetTextColor
}
DankRipple {
id: itemRipple
cornerRadius: Theme.cornerRadius
}
}
MouseArea {
id: trayItemArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
onPressed: mouse => {
const pos = mapToItem(visualContent, mouse.x, mouse.y);
itemRipple.trigger(pos.x, pos.y);
if (mouse.button === Qt.LeftButton) {
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start();
}
}
onReleased: mouse => {
longPressTimer.stop();
const wasDragging = dragHandler.dragging;
const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;
if (didReorder) {
root.suppressShiftAnimation = true;
root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex);
Qt.callLater(() => root.suppressShiftAnimation = false);
}
dragHandler.longPressing = false;
dragHandler.dragging = false;
dragHandler.dragAxisOffset = 0;
root.draggedIndex = -1;
root.dropTargetIndex = -1;
if (wasDragging || mouse.button !== Qt.LeftButton)
return;
if (!trayItem)
return;
if (!trayItem.onlyMenu) {
trayItem.activate();
return;
}
if (!trayItem.hasMenu)
return;
if (root.useOverflowPopup)
root.menuOpen = false;
root.showForTrayItem(trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
}
onPositionChanged: mouse => {
if (dragHandler.longPressing && !dragHandler.dragging) {
const distance = Math.abs(mouse.y - dragHandler.dragStartPos.y);
if (distance > 5) {
dragHandler.dragging = true;
root.draggedIndex = index;
root.dropTargetIndex = root.draggedIndex;
}
}
if (!dragHandler.dragging)
return;
const axisOffset = mouse.y - dragHandler.dragStartPos.y;
dragHandler.dragAxisOffset = axisOffset;
const itemSize = root.trayItemSize;
const slotOffset = Math.round(axisOffset / itemSize);
const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
if (newTargetIndex !== root.dropTargetIndex) {
root.dropTargetIndex = newTargetIndex;
}
}
onClicked: mouse => {
if (dragHandler.dragging)
return;
if (mouse.button !== Qt.RightButton)
return;
root.openInlineTrayContextMenu(trayItem, trayItemArea, mouse, visualContent);
}
}
} }
} }
@@ -817,23 +459,219 @@ BasePill {
Column { Column {
spacing: 0 spacing: 0
// Column lacks layoutDirection, so we use four repeaters with mutually exclusive models to control whether main items or expanded items appear above/ below the toggle button.
// When reverseInlineVertical is true the first and third repeaters are empty and the second and fourth are active, and vice-versa.
// Because items are swapped between repeaters rather than reversed within a single list, vertical drag-and-drop indices don't need remapping (unlike the horizontal RightToLeft case).
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
values: root.reverseInlineVertical ? [] : root.displayedMainBarItems values: root.mainBarItems
objectProp: "key" objectProp: "key"
} }
delegate: verticalMainTrayItemDelegate
}
Repeater { delegate: Item {
model: ScriptModel { id: delegateRoot
values: root.reverseInlineVertical ? root.displayedInlineExpandedItems : [] property var trayItem: modelData.item
objectProp: "key" property string itemKey: modelData.key
property string iconSource: {
let icon = trayItem && trayItem.icon;
if (typeof icon === 'string' || icon instanceof String) {
if (icon === "")
return "";
if (icon.includes("?path=")) {
const split = icon.split("?path=");
if (split.length !== 2)
return icon;
const name = split[0];
const path = split[1];
let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
}
return `file://${path}/${fileName}`;
}
if (icon.startsWith("/") && !icon.startsWith("file://"))
return `file://${icon}`;
return icon;
}
return "";
}
width: root.barThickness
height: root.trayItemSize
z: dragHandler.dragging ? 100 : 0
property real shiftOffset: {
if (root.draggedIndex < 0)
return 0;
if (index === root.draggedIndex)
return 0;
const dragIdx = root.draggedIndex;
const dropIdx = root.dropTargetIndex;
const shiftAmount = root.trayItemSize;
if (dropIdx < 0)
return 0;
if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
return -shiftAmount;
if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
return shiftAmount;
return 0;
}
transform: Translate {
y: delegateRoot.shiftOffset
Behavior on y {
enabled: !root.suppressShiftAnimation
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
}
Item {
id: dragHandler
anchors.fill: parent
property bool dragging: false
property point dragStartPos: Qt.point(0, 0)
property real dragAxisOffset: 0
property bool longPressing: false
Timer {
id: longPressTimer
interval: 400
repeat: false
onTriggered: dragHandler.longPressing = true
}
}
Rectangle {
id: visualContent
width: root.trayItemSize
height: root.trayItemSize
anchors.centerIn: parent
radius: Theme.cornerRadius
color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
border.width: dragHandler.dragging ? 2 : 0
border.color: Theme.primary
opacity: dragHandler.dragging ? 0.8 : 1.0
transform: Translate {
y: dragHandler.dragging ? dragHandler.dragAxisOffset : 0
}
IconImage {
id: iconImg
anchors.centerIn: parent
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
source: delegateRoot.iconSource
asynchronous: true
smooth: true
mipmap: true
visible: status === Image.Ready
}
Text {
anchors.centerIn: parent
visible: !iconImg.visible
text: {
const itemId = trayItem?.id || "";
if (!itemId)
return "?";
return itemId.charAt(0).toUpperCase();
}
font.pixelSize: 10
color: Theme.widgetTextColor
}
DankRipple {
id: itemRipple
cornerRadius: Theme.cornerRadius
}
}
MouseArea {
id: trayItemArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
onPressed: mouse => {
const pos = mapToItem(visualContent, mouse.x, mouse.y);
itemRipple.trigger(pos.x, pos.y);
if (mouse.button === Qt.LeftButton) {
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start();
}
}
onReleased: mouse => {
longPressTimer.stop();
const wasDragging = dragHandler.dragging;
const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;
if (didReorder) {
root.suppressShiftAnimation = true;
root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex);
Qt.callLater(() => root.suppressShiftAnimation = false);
}
dragHandler.longPressing = false;
dragHandler.dragging = false;
dragHandler.dragAxisOffset = 0;
root.draggedIndex = -1;
root.dropTargetIndex = -1;
if (wasDragging || mouse.button !== Qt.LeftButton)
return;
if (!delegateRoot.trayItem)
return;
if (!delegateRoot.trayItem.onlyMenu) {
delegateRoot.trayItem.activate();
return;
}
if (!delegateRoot.trayItem.hasMenu)
return;
root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
}
onPositionChanged: mouse => {
if (dragHandler.longPressing && !dragHandler.dragging) {
const distance = Math.abs(mouse.y - dragHandler.dragStartPos.y);
if (distance > 5) {
dragHandler.dragging = true;
root.draggedIndex = index;
root.dropTargetIndex = index;
}
}
if (!dragHandler.dragging)
return;
const axisOffset = mouse.y - dragHandler.dragStartPos.y;
dragHandler.dragAxisOffset = axisOffset;
const itemSize = root.trayItemSize;
const slotOffset = Math.round(axisOffset / itemSize);
const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
if (newTargetIndex !== root.dropTargetIndex) {
root.dropTargetIndex = newTargetIndex;
}
}
onClicked: mouse => {
if (dragHandler.dragging)
return;
if (mouse.button !== Qt.RightButton)
return;
if (!delegateRoot.trayItem?.hasMenu) {
const gp = trayItemArea.mapToGlobal(mouse.x, mouse.y);
root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
return;
}
root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
}
}
} }
delegate: inlineExpandedTrayItemDelegate
} }
Item { Item {
@@ -851,7 +689,14 @@ BasePill {
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: root.toggleIconName() name: {
const edge = root.axis?.edge;
if (edge === "left") {
return root.menuOpen ? "chevron_left" : "chevron_right";
} else {
return root.menuOpen ? "chevron_right" : "chevron_left";
}
}
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
color: Theme.widgetTextColor color: Theme.widgetTextColor
} }
@@ -873,22 +718,6 @@ BasePill {
} }
} }
} }
Repeater {
model: ScriptModel {
values: root.reverseInlineVertical ? [] : root.displayedInlineExpandedItems
objectProp: "key"
}
delegate: inlineExpandedTrayItemDelegate
}
Repeater {
model: ScriptModel {
values: root.reverseInlineVertical ? root.displayedMainBarItems : []
objectProp: "key"
}
delegate: verticalMainTrayItemDelegate
}
} }
} }
@@ -904,7 +733,7 @@ BasePill {
blurRadius: Theme.cornerRadius blurRadius: Theme.cornerRadius
} }
visible: root.useOverflowPopup && root.menuOpen visible: root.menuOpen
screen: root.parentScreen screen: root.parentScreen
WlrLayershell.layer: WlrLayershell.Top WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
@@ -920,14 +749,13 @@ BasePill {
HyprlandFocusGrab { HyprlandFocusGrab {
windows: [overflowMenu] windows: [overflowMenu]
active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen active: CompositorService.useHyprlandFocusGrab && root.menuOpen
} }
Connections { Connections {
target: PopoutManager target: PopoutManager
function onPopoutOpening() { function onPopoutOpening() {
if (root.useOverflowPopup) root.menuOpen = false;
root.menuOpen = false;
} }
} }
@@ -1193,7 +1021,30 @@ BasePill {
delegate: Rectangle { delegate: Rectangle {
property var trayItem: modelData property var trayItem: modelData
property string iconSource: root.trayIconSourceFor(trayItem) property string iconSource: {
let icon = trayItem?.icon;
if (typeof icon === 'string' || icon instanceof String) {
if (icon === "")
return "";
if (icon.includes("?path=")) {
const split = icon.split("?path=");
if (split.length !== 2)
return icon;
const name = split[0];
const path = split[1];
let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
}
return `file://${path}/${fileName}`;
}
if (icon.startsWith("/") && !icon.startsWith("file://")) {
return `file://${icon}`;
}
return icon;
}
return "";
}
width: root.trayItemSize + 4 width: root.trayItemSize + 4
height: root.trayItemSize + 4 height: root.trayItemSize + 4
@@ -1462,8 +1313,7 @@ BasePill {
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
updatePosition(); updatePosition();
if (root.useOverflowPopup) root.menuOpen = false;
root.menuOpen = false;
PopoutManager.closeAllPopouts(); PopoutManager.closeAllPopouts();
ModalManager.closeAllModalsExcept(null); ModalManager.closeAllModalsExcept(null);
} }
@@ -20,46 +20,6 @@ Item {
property var blurBarWindow: null property var blurBarWindow: null
property var hyprlandOverviewLoader: null property var hyprlandOverviewLoader: null
property var parentScreen: null property var parentScreen: null
readonly property real _leftMargin: {
if (isVertical)
return 0;
root.x;
if (!root.parent)
return 0;
const gap = root.mapToItem(null, 0, 0).x;
return (gap > 0 && gap < 30) ? gap + 5 : 0;
}
readonly property real _rightMargin: {
if (isVertical)
return 0;
root.x;
root.width;
if (!root.parent || !blurBarWindow)
return 0;
const gap = blurBarWindow.width - root.mapToItem(null, root.width, 0).x;
return (gap > 0 && gap < 30) ? gap + 5 : 0;
}
readonly property real _topMargin: {
if (!isVertical)
return 0;
root.y;
if (!root.parent)
return 0;
const gap = root.mapToItem(null, 0, 0).y;
return (gap > 0 && gap < 30) ? gap + 5 : 0;
}
readonly property real _bottomMargin: {
if (!isVertical)
return 0;
root.y;
root.height;
if (!root.parent || !blurBarWindow)
return 0;
const gap = blurBarWindow.height - root.mapToItem(null, 0, root.height).y;
return (gap > 0 && gap < 30) ? gap + 5 : 0;
}
property int _desktopEntriesUpdateTrigger: 0 property int _desktopEntriesUpdateTrigger: 0
readonly property var sortedToplevels: { readonly property var sortedToplevels: {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName); return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
@@ -579,60 +539,6 @@ Item {
}); });
} }
function switchToWorkspaceByModelData(data) {
if (!data)
return;
if (root.useExtWorkspace && (data.id || data.name)) {
ExtWorkspaceService.activateWorkspace(data.id || data.name, data.groupID || "");
return;
}
switch (CompositorService.compositor) {
case "niri":
if (data.idx !== undefined)
NiriService.switchToWorkspace(data.idx);
break;
case "hyprland":
if (data.id)
Hyprland.dispatch(`workspace ${data.id}`);
break;
case "dwl":
if (data.tag !== undefined)
DwlService.switchToTag(root.screenName, data.tag);
break;
case "sway":
case "scroll":
case "miracle":
if (data.num)
try {
I3.dispatch(`workspace number ${data.num}`);
} catch (_) {}
break;
}
}
function findClosestWorkspaceIndex(localX, localY) {
if (workspaceRepeater.count === 0)
return -1;
let closestIdx = -1;
let closestDist = Infinity;
for (let i = 0; i < workspaceRepeater.count; i++) {
const item = workspaceRepeater.itemAt(i);
if (!item)
continue;
const center = item.mapToItem(root, item.width / 2, item.height / 2);
const dist = isVertical ? Math.abs(localY - center.y) : Math.abs(localX - center.x);
if (dist < closestDist) {
closestDist = dist;
closestIdx = i;
}
}
return closestIdx;
}
function switchWorkspace(direction) { function switchWorkspace(direction) {
if (useExtWorkspace) { if (useExtWorkspace) {
const realWorkspaces = getRealWorkspaces(); const realWorkspaces = getRealWorkspaces();
@@ -846,15 +752,8 @@ Item {
} }
MouseArea { MouseArea {
id: edgeMouseArea anchors.fill: parent
z: -1 acceptedButtons: Qt.RightButton
x: -root._leftMargin
y: -root._topMargin
width: root.width + root._leftMargin + root._rightMargin
height: root.height + root._topMargin + root._bottomMargin
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
property real touchpadAccumulator: 0 property real touchpadAccumulator: 0
property real mouseAccumulator: 0 property real mouseAccumulator: 0
@@ -867,20 +766,12 @@ Item {
} }
onClicked: mouse => { onClicked: mouse => {
const rootPos = edgeMouseArea.mapToItem(root, mouse.x, mouse.y); if (mouse.button === Qt.RightButton) {
switch (mouse.button) {
case Qt.RightButton:
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
NiriService.toggleOverview(); NiriService.toggleOverview();
} else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) { } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen; root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
} }
break;
case Qt.LeftButton:
const idx = root.findClosestWorkspaceIndex(rootPos.x, rootPos.y);
if (idx >= 0)
root.switchToWorkspaceByModelData(root.workspaceList[idx]);
break;
} }
} }
+11 -1
View File
@@ -487,7 +487,17 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: MprisController.previousOrRewind() onClicked: {
if (!activePlayer) {
return;
}
if (activePlayer.position > 8 && activePlayer.canSeek) {
activePlayer.position = 0;
} else {
activePlayer.previous();
}
}
} }
} }
} }
@@ -145,7 +145,14 @@ Card {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: MprisController.previousOrRewind() onClicked: {
if (!activePlayer) return
if (activePlayer.position > 8 && activePlayer.canSeek) {
activePlayer.position = 0
} else {
activePlayer.previous()
}
}
} }
} }
@@ -1338,7 +1338,7 @@ Item {
enabled: MprisController.activePlayer?.canGoPrevious ?? false enabled: MprisController.activePlayer?.canGoPrevious ?? false
hoverEnabled: enabled hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: MprisController.previousOrRewind() onClicked: MprisController.activePlayer?.previous()
} }
} }
@@ -46,13 +46,6 @@ Item {
onToggled: checked => SettingsData.set("audioVisualizerEnabled", checked) onToggled: checked => SettingsData.set("audioVisualizerEnabled", checked)
} }
SettingsToggleRow {
text: I18n.tr("Adaptive Media Width")
description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size")
checked: SettingsData.mediaAdaptiveWidthEnabled
onToggled: checked => SettingsData.set("mediaAdaptiveWidthEnabled", checked)
}
SettingsDropdownRow { SettingsDropdownRow {
property var scrollOptsInternal: ["volume", "song", "nothing"] property var scrollOptsInternal: ["volume", "song", "nothing"]
property var scrollOptsDisplay: [I18n.tr("Change Volume", "media scroll wheel option"), I18n.tr("Change Song", "media scroll wheel option"), I18n.tr("Nothing", "media scroll wheel option")] property var scrollOptsDisplay: [I18n.tr("Change Volume", "media scroll wheel option"), I18n.tr("Change Song", "media scroll wheel option"), I18n.tr("Nothing", "media scroll wheel option")]
-10
View File
@@ -91,16 +91,6 @@ Item {
visible: AudioService.gsettingsAvailable visible: AudioService.gsettingsAvailable
} }
SettingsToggleRow {
tab: "sounds"
tags: ["sound", "login", "startup", "boot"]
settingKey: "soundLogin"
text: I18n.tr("Login")
description: I18n.tr("Play sound after logging in")
checked: SettingsData.soundLogin
onToggled: checked => SettingsData.set("soundLogin", checked)
}
SettingsToggleRow { SettingsToggleRow {
tab: "sounds" tab: "sounds"
tags: ["sound", "notification", "new"] tags: ["sound", "notification", "new"]
+1 -3
View File
@@ -430,7 +430,7 @@ Item {
"id": widget.id, "id": widget.id,
"enabled": widget.enabled "enabled": widget.enabled
}; };
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion"]; var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined) if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]]; result[keys[i]] = widget[keys[i]];
@@ -712,8 +712,6 @@ Item {
item.barMaxVisibleRunningApps = widget.barMaxVisibleRunningApps; item.barMaxVisibleRunningApps = widget.barMaxVisibleRunningApps;
if (widget.barShowOverflowBadge !== undefined) if (widget.barShowOverflowBadge !== undefined)
item.barShowOverflowBadge = widget.barShowOverflowBadge; item.barShowOverflowBadge = widget.barShowOverflowBadge;
if (widget.trayUseInlineExpansion !== undefined)
item.trayUseInlineExpansion = widget.trayUseInlineExpansion;
} }
widgets.push(item); widgets.push(item);
}); });
+27 -142
View File
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
import qs.Services import qs.Services
@@ -39,7 +40,7 @@ Column {
"id": widget.id, "id": widget.id,
"enabled": widget.enabled "enabled": widget.enabled
}; };
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion"]; var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined) if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]]; result[keys[i]] = widget[keys[i]];
@@ -51,14 +52,15 @@ Column {
height: implicitHeight height: implicitHeight
spacing: Theme.spacingM spacing: Theme.spacingM
Row { RowLayout {
width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
DankIcon { DankIcon {
name: root.titleIcon name: root.titleIcon
size: Theme.iconSize size: Theme.iconSize
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter Layout.alignment: Qt.AlignVCenter
} }
StyledText { StyledText {
@@ -66,7 +68,7 @@ Column {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter Layout.alignment: Qt.AlignVCenter
} }
} }
@@ -437,7 +439,7 @@ Column {
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "keyboard_layout_name" || modelData.id === "appsDock" || modelData.id === "systemTray" visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "keyboard_layout_name" || modelData.id === "appsDock"
DankActionButton { DankActionButton {
id: compactModeButton id: compactModeButton
@@ -543,39 +545,6 @@ Column {
} }
} }
DankActionButton {
id: trayMenuButton
buttonSize: 32
visible: modelData.id === "systemTray"
iconName: "more_vert"
iconSize: 18
iconColor: Theme.outline
onClicked: {
trayContextMenu.widgetData = modelData;
trayContextMenu.sectionId = root.sectionId;
trayContextMenu.widgetIndex = index;
var buttonPos = trayMenuButton.mapToItem(root, 0, 0);
var popupWidth = trayContextMenu.width;
var popupHeight = trayContextMenu.height;
var xPos = buttonPos.x - popupWidth - Theme.spacingS;
if (xPos < 0)
xPos = buttonPos.x + trayMenuButton.width + Theme.spacingS;
var yPos = buttonPos.y - popupHeight / 2 + trayMenuButton.height / 2;
if (yPos < 0) {
yPos = Theme.spacingS;
} else if (yPos + popupHeight > root.height) {
yPos = root.height - popupHeight - Theme.spacingS;
}
trayContextMenu.x = xPos;
trayContextMenu.y = yPos;
trayContextMenu.open();
}
}
Rectangle { Rectangle {
id: compactModeTooltip id: compactModeTooltip
width: tooltipText.contentWidth + Theme.spacingM * 2 width: tooltipText.contentWidth + Theme.spacingM * 2
@@ -964,88 +933,6 @@ Column {
} }
} }
Popup {
id: trayContextMenu
property var widgetData: null
property string sectionId: ""
property int widgetIndex: -1
readonly property var currentWidgetData: (widgetIndex >= 0 && widgetIndex < root.items.length) ? root.items[widgetIndex] : widgetData
width: 220
height: contentColumn.implicitHeight + Theme.spacingS * 2
padding: 0
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle {
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
}
contentItem: Item {
Column {
id: contentColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 2
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: trayOverflowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "arrow_selector_tool"
size: 16
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Use Inline Expansion")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
id: trayOverflowToggle
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
width: 40
height: 20
checked: trayContextMenu.currentWidgetData?.trayUseInlineExpansion ?? false
}
MouseArea {
id: trayOverflowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const newValue = !(trayContextMenu.currentWidgetData?.trayUseInlineExpansion ?? false);
root.overflowSettingChanged(trayContextMenu.sectionId, trayContextMenu.widgetIndex, "trayUseInlineExpansion", newValue);
}
}
}
}
}
}
Popup { Popup {
id: diskUsageContextMenu id: diskUsageContextMenu
@@ -1094,26 +981,10 @@ Column {
Repeater { Repeater {
model: [ model: [
{ { label: I18n.tr("Percentage"), mode: 0, icon: "percent" },
label: I18n.tr("Percentage"), { label: I18n.tr("Total"), mode: 1, icon: "storage" },
mode: 0, { label: I18n.tr("Remaining"), mode: 2, icon: "hourglass_empty" },
icon: "percent" { label: I18n.tr("Remaining / Total"), mode: 3, icon: "pie_chart" }
},
{
label: I18n.tr("Total"),
mode: 1,
icon: "storage"
},
{
label: I18n.tr("Remaining"),
mode: 2,
icon: "hourglass_empty"
},
{
label: I18n.tr("Remaining / Total"),
mode: 3,
icon: "pie_chart"
}
] ]
delegate: Rectangle { delegate: Rectangle {
@@ -1445,7 +1316,20 @@ Column {
id: longestControlCenterLabelMetrics id: longestControlCenterLabelMetrics
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
text: { text: {
const labels = [I18n.tr("Network"), I18n.tr("VPN"), I18n.tr("Bluetooth"), I18n.tr("Audio"), I18n.tr("Volume"), I18n.tr("Microphone"), I18n.tr("Microphone Volume"), I18n.tr("Brightness"), I18n.tr("Brightness Value"), I18n.tr("Battery"), I18n.tr("Printer"), I18n.tr("Screen Sharing")]; const labels = [
I18n.tr("Network"),
I18n.tr("VPN"),
I18n.tr("Bluetooth"),
I18n.tr("Audio"),
I18n.tr("Volume"),
I18n.tr("Microphone"),
I18n.tr("Microphone Volume"),
I18n.tr("Brightness"),
I18n.tr("Brightness Value"),
I18n.tr("Battery"),
I18n.tr("Printer"),
I18n.tr("Screen Sharing")
];
let longest = ""; let longest = "";
for (let i = 0; i < labels.length; i++) { for (let i = 0; i < labels.length; i++) {
if (labels[i].length > longest.length) if (labels[i].length > longest.length)
@@ -1456,7 +1340,6 @@ Column {
} }
Repeater { Repeater {
id: groupRepeater
model: controlCenterContextMenu.controlCenterGroups model: controlCenterContextMenu.controlCenterGroups
delegate: Item { delegate: Item {
@@ -1686,6 +1569,8 @@ Column {
} }
} }
} }
id: groupRepeater
} }
} }
} }
+2 -56
View File
@@ -26,7 +26,6 @@ Singleton {
property var powerUnplugSound: null property var powerUnplugSound: null
property var normalNotificationSound: null property var normalNotificationSound: null
property var criticalNotificationSound: null property var criticalNotificationSound: null
property var loginSound: null
property real notificationsVolume: 1.0 property real notificationsVolume: 1.0
property bool notificationsAudioMuted: false property bool notificationsAudioMuted: false
@@ -68,16 +67,6 @@ Singleton {
} }
} }
// Used in playLoginSoundIfApplicable()
Process {
id: loginSoundChecker
onExited: (exitCode) => {
if (exitCode === 0) {
playLoginSound();
}
}
}
function getAvailableSinks() { function getAvailableSinks() {
const hidden = SessionData.hiddenOutputDeviceNames ?? []; const hidden = SessionData.hiddenOutputDeviceNames ?? [];
return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream && !hidden.includes(node.name)); return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream && !hidden.includes(node.name));
@@ -406,7 +395,7 @@ EOFCONFIG
const themesToSearch = themeName !== "freedesktop" ? `${themeName} freedesktop` : themeName; const themesToSearch = themeName !== "freedesktop" ? `${themeName} freedesktop` : themeName;
const script = ` const script = `
for event_key in audio-volume-change power-plug power-unplug message message-new-instant desktop-login; do for event_key in audio-volume-change power-plug power-unplug message message-new-instant; do
found=0 found=0
case "$event_key" in case "$event_key" in
@@ -468,8 +457,7 @@ EOFCONFIG
"power-plug": "../assets/sounds/plasma/power-plug.wav", "power-plug": "../assets/sounds/plasma/power-plug.wav",
"power-unplug": "../assets/sounds/plasma/power-unplug.wav", "power-unplug": "../assets/sounds/plasma/power-unplug.wav",
"message": "../assets/sounds/freedesktop/message.wav", "message": "../assets/sounds/freedesktop/message.wav",
"message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav", "message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav"
"desktop-login": "../assets/sounds/freedesktop/desktop-login.wav"
}; };
const specialConditions = { const specialConditions = {
@@ -563,10 +551,6 @@ EOFCONFIG
criticalNotificationSound.destroy(); criticalNotificationSound.destroy();
criticalNotificationSound = null; criticalNotificationSound = null;
} }
if (loginSound) {
loginSound.destroy();
loginSound = null;
}
} }
function createSoundPlayers() { function createSoundPlayers() {
@@ -638,19 +622,6 @@ EOFCONFIG
} }
} }
`, root, "AudioService.CriticalNotificationSound"); `, root, "AudioService.CriticalNotificationSound");
const loginPath = getSoundPath("desktop-login");
loginSound = Qt.createQmlObject(`
import QtQuick
import QtMultimedia
MediaPlayer {
source: "${loginPath}"
audioOutput: AudioOutput {
${deviceProperty}volume: notificationsVolume
}
}
`, root, "AudioService.LoginSound");
} catch (e) { } catch (e) {
console.warn("AudioService: Error creating sound players:", e); console.warn("AudioService: Error creating sound players:", e);
} }
@@ -690,31 +661,6 @@ EOFCONFIG
criticalNotificationSound.play(); criticalNotificationSound.play();
} }
function playLoginSound() {
if (!soundsAvailable || !loginSound || notificationsAudioMuted || isMediaPlaying()) {
return;
}
loginSound.play();
}
function playLoginSoundIfApplicable() {
if (SettingsData.soundsEnabled && SettingsData.soundLogin && !notificationsAudioMuted) {
// plays login sound on session start, but only if a specific file doesn't exist,
// to prevent it from playing on every DMS restart during the session
const runtimeDir = Quickshell.env("XDG_RUNTIME_DIR");
const sessionId = Quickshell.env("XDG_SESSION_ID") || "0";
if (!runtimeDir) return;
const loginFile = `${runtimeDir}/danklinux.login-${sessionId}`;
// if file doesn't exist, touch it (0)
// If it exists, do nothing (1)
loginSoundChecker.command = ["sh", "-c", `[ ! -f ${loginFile} ] && touch ${loginFile}`];
loginSoundChecker.running = true;
}
}
function playVolumeChangeSoundIfEnabled() { function playVolumeChangeSoundIfEnabled() {
if (SettingsData.soundsEnabled && SettingsData.soundVolumeChanged && !notificationsAudioMuted) { if (SettingsData.soundsEnabled && SettingsData.soundVolumeChanged && !notificationsAudioMuted) {
playVolumeChangeSound(); playVolumeChangeSound();
+3 -28
View File
@@ -3,16 +3,13 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Wayland // ! Import is needed despite what qmlls says import Quickshell.Wayland // ! Import is needed despite what qmlls says
import qs.Common import qs.Common
Singleton { Singleton {
id: root id: root
property bool quickshellSupported: false property bool available: false
property bool compositorSupported: false
property bool available: quickshellSupported && compositorSupported
readonly property bool enabled: available && (SettingsData.blurEnabled ?? false) readonly property bool enabled: available && (SettingsData.blurEnabled ?? false)
readonly property color borderColor: { readonly property color borderColor: {
@@ -75,27 +72,6 @@ Singleton {
region.destroy(); region.destroy();
} }
Process {
id: blurProbe
running: false
command: ["dms", "blur", "check"]
stdout: StdioCollector {
onStreamFinished: {
root.compositorSupported = text.trim() === "supported";
if (root.compositorSupported)
console.info("BlurService: Compositor supports ext-background-effect-v1");
else
console.info("BlurService: Compositor does not support ext-background-effect-v1");
}
}
onExited: exitCode => {
if (exitCode !== 0)
console.warn("BlurService: blur probe failed with code:", exitCode);
}
}
Component.onCompleted: { Component.onCompleted: {
try { try {
const test = Qt.createQmlObject(` const test = Qt.createQmlObject(`
@@ -103,9 +79,8 @@ Singleton {
Region { radius: 0 } Region { radius: 0 }
`, root, "BlurAvailabilityTest"); `, root, "BlurAvailabilityTest");
test.destroy(); test.destroy();
quickshellSupported = true; available = true;
console.info("BlurService: Quickshell blur support available"); console.info("BlurService: Initialized with blur support");
blurProbe.running = true;
} catch (e) { } catch (e) {
console.info("BlurService: BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell."); console.info("BlurService: BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell.");
} }
-6
View File
@@ -255,12 +255,6 @@ Singleton {
return pinnedEntries.some(pinnedEntry => pinnedEntry.hash === entryHash); return pinnedEntries.some(pinnedEntry => pinnedEntry.hash === entryHash);
} }
onClipboardAvailableChanged: {
if (!clipboardAvailable || refCount <= 0)
return;
refresh();
}
Connections { Connections {
target: DMSService target: DMSService
enabled: root.refCount > 0 enabled: root.refCount > 0
-16
View File
@@ -10,20 +10,4 @@ Singleton {
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null
Timer {
interval: 1000
running: root.activePlayer?.playbackState === MprisPlaybackState.Playing
repeat: true
onTriggered: root.activePlayer?.positionChanged()
}
function previousOrRewind(): void {
if (!activePlayer)
return;
if (activePlayer.position > 8 && activePlayer.canSeek)
activePlayer.position = 0;
else if (activePlayer.canGoPrevious)
activePlayer.previous();
}
} }
+1 -1
View File
@@ -89,7 +89,7 @@ Row {
width: Math.max(contentItem.implicitWidth + root.buttonPadding * 2, root.minButtonWidth) + (selected ? 4 : 0) width: Math.max(contentItem.implicitWidth + root.buttonPadding * 2, root.minButtonWidth) + (selected ? 4 : 0)
height: root.buttonHeight height: root.buttonHeight
color: selected ? Theme.buttonBg : Theme.withAlpha(Theme.surfaceVariant, Theme.popupTransparency) color: selected ? Theme.buttonBg : Theme.surfaceVariant
border.color: "transparent" border.color: "transparent"
border.width: 0 border.width: 0
+2 -2
View File
@@ -266,7 +266,7 @@ PanelWindow {
scale: shouldBeVisible ? 1 : 0.9 scale: shouldBeVisible ? 1 : 0.9
property bool childHovered: false property bool childHovered: false
readonly property real popupSurfaceAlpha: Theme.popupTransparency readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
Rectangle { Rectangle {
id: background id: background
@@ -286,7 +286,7 @@ PanelWindow {
level: Theme.elevationLevel3 level: Theme.elevationLevel3
fallbackOffset: 6 fallbackOffset: 6
targetRadius: Theme.cornerRadius targetRadius: Theme.cornerRadius
targetColor: Theme.withAlpha(Theme.surfaceContainer, osdContainer.popupSurfaceAlpha) targetColor: Theme.surfaceContainer
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
+8 -15
View File
@@ -576,6 +576,14 @@ Item {
} }
} }
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.borderWidth
}
Loader { Loader {
id: contentLoader id: contentLoader
anchors.fill: parent anchors.fill: parent
@@ -583,21 +591,6 @@ Item {
asynchronous: false asynchronous: false
} }
} }
Rectangle {
width: parent.width
height: parent.height
x: contentWrapper.x
y: contentWrapper.y
opacity: contentWrapper.opacity
scale: contentWrapper.scale
visible: contentWrapper.visible
radius: Theme.cornerRadius
color: "transparent"
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.borderWidth
z: 100
}
} }
Item { Item {
+2 -4
View File
@@ -238,7 +238,7 @@ Rectangle {
width: fieldContent.width + Theme.spacingM * 2 width: fieldContent.width + Theme.spacingM * 2
height: 32 height: 32
radius: Theme.cornerRadius - 2 radius: Theme.cornerRadius - 2
color: Theme.surfaceLight color: Theme.surfaceContainerHigh
border.width: 1 border.width: 1
border.color: Theme.outlineLight border.color: Theme.outlineLight
@@ -272,9 +272,7 @@ Rectangle {
checked: configData ? (configData.autoconnect || false) : false checked: configData ? (configData.autoconnect || false) : false
visible: !VPNService.configLoading && configData !== null visible: !VPNService.configLoading && configData !== null
onToggled: checked => { onToggled: checked => {
VPNService.updateConfig(profile.uuid, { VPNService.updateConfig(profile.uuid, {autoconnect: checked});
autoconnect: checked
});
} }
} }
File diff suppressed because it is too large Load Diff
@@ -86,13 +86,13 @@ def create_poeditor_json(translations):
references.append(ref) references.append(ref)
contexts = sorted(data['contexts']) if data['contexts'] else [] contexts = sorted(data['contexts']) if data['contexts'] else []
comment = " | ".join(contexts) if contexts else "" context_str = " | ".join(contexts) if contexts else term
entry = { entry = {
"term": term, "term": term,
"context": term, "context": context_str,
"reference": ", ".join(references), "reference": ", ".join(references),
"comment": comment "comment": ""
} }
poeditor_data.append(entry) poeditor_data.append(entry)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -2112,26 +2112,6 @@
], ],
"icon": "history" "icon": "history"
}, },
{
"section": "rememberLastQuery",
"label": "Remember Last Query",
"tabIndex": 9,
"category": "Launcher",
"keywords": [
"autofill",
"drawer",
"last",
"launcher",
"menu",
"opened",
"query",
"remember",
"remembered",
"search",
"start"
],
"description": "Autofill last remembered query when opened"
},
{ {
"section": "searchAppActions", "section": "searchAppActions",
"label": "Search App Actions", "label": "Search App Actions",
@@ -2398,47 +2378,6 @@
], ],
"icon": "schedule" "icon": "schedule"
}, },
{
"section": "blurEnabled",
"label": "Background Blur",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"alert",
"alerts",
"appearance",
"background",
"bars",
"behind",
"blur",
"colors",
"compositor",
"config",
"configuration",
"configure",
"frosted",
"glass",
"look",
"modals",
"notif",
"notifications",
"notifs",
"panel",
"popouts",
"requires",
"scheme",
"setup",
"statusbar",
"style",
"support",
"taskbar",
"theme",
"topbar",
"transparency"
],
"icon": "blur_on",
"description": "Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration."
},
{ {
"section": "barElevationEnabled", "section": "barElevationEnabled",
"label": "Bar Shadows", "label": "Bar Shadows",
@@ -2466,49 +2405,6 @@
], ],
"description": "Shadow elevation on bars and panels" "description": "Shadow elevation on bars and panels"
}, },
{
"section": "blurBorderColor",
"label": "Blur Border Color",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"appearance",
"around",
"blur",
"blurred",
"border",
"color",
"colors",
"colour",
"edge",
"hue",
"look",
"outline",
"scheme",
"style",
"surfaces",
"theme",
"tint"
],
"description": "Border color around blurred surfaces"
},
{
"section": "blurBorderOpacity",
"label": "Blur Border Opacity",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"appearance",
"blur",
"border",
"colors",
"look",
"opacity",
"scheme",
"style",
"theme"
]
},
{ {
"section": "niriLayoutBorderSize", "section": "niriLayoutBorderSize",
"label": "Border Size", "label": "Border Size",
@@ -4488,6 +4384,27 @@
], ],
"description": "Automatically lock the screen when DMS starts" "description": "Automatically lock the screen when DMS starts"
}, },
{
"section": "lockBeforeSuspend",
"label": "Lock before suspend",
"tabIndex": 11,
"category": "Lock Screen",
"keywords": [
"automatic",
"automatically",
"before",
"lock",
"login",
"password",
"prepares",
"screen",
"security",
"sleep",
"suspend",
"system"
],
"description": "Automatically lock the screen when the system prepares to suspend"
},
{ {
"section": "lockScreenNotificationMode", "section": "lockScreenNotificationMode",
"label": "Notification Display", "label": "Notification Display",
@@ -5101,26 +5018,6 @@
], ],
"description": "Play sounds for system events" "description": "Play sounds for system events"
}, },
{
"section": "soundLogin",
"label": "Login",
"tabIndex": 15,
"category": "Sounds",
"keywords": [
"after",
"audio",
"boot",
"effects",
"logging",
"login",
"play",
"sfx",
"sound",
"sounds",
"startup"
],
"description": "Play sound after logging in"
},
{ {
"section": "soundNewNotification", "section": "soundNewNotification",
"label": "New Notification", "label": "New Notification",
@@ -6510,27 +6407,6 @@
"icon": "schedule", "icon": "schedule",
"description": "Gradually fade the screen before locking with a configurable grace period" "description": "Gradually fade the screen before locking with a configurable grace period"
}, },
{
"section": "lockBeforeSuspend",
"label": "Lock before suspend",
"tabIndex": 21,
"category": "Power & Sleep",
"keywords": [
"automatically",
"before",
"energy",
"lock",
"power",
"prepares",
"screen",
"security",
"shutdown",
"sleep",
"suspend",
"system"
],
"description": "Automatically lock the screen when the system prepares to suspend"
},
{ {
"section": "fadeToLockGracePeriod", "section": "fadeToLockGracePeriod",
"label": "Lock fade grace period", "label": "Lock fade grace period",
+44 -198
View File
@@ -1182,13 +1182,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Applying authentication changes…",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Apps", "term": "Apps",
"translation": "", "translation": "",
@@ -1385,34 +1378,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Authentication changes applied.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Authentication changes apply automatically.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Authentication changes apply automatically. Fingerprint-only login may not unlock Keyring.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Authentication changes need sudo. Opening terminal so you can use password or fingerprint.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Authentication error - try again", "term": "Authentication error - try again",
"translation": "", "translation": "",
@@ -1553,13 +1518,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Autofill last remembered query when opened",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Automatic Color Mode", "term": "Automatic Color Mode",
"translation": "", "translation": "",
@@ -1714,13 +1672,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Background Blur",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Background Opacity", "term": "Background Opacity",
"translation": "", "translation": "",
@@ -1728,13 +1679,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Background authentication sync failed. Trying terminal mode.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Background image", "term": "Background image",
"translation": "", "translation": "",
@@ -1931,20 +1875,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Blur Border Color",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Blur Border Opacity",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Blur Wallpaper Layer", "term": "Blur Wallpaper Layer",
"translation": "", "translation": "",
@@ -1959,13 +1889,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Blur wallpaper when niri overview is open", "term": "Blur wallpaper when niri overview is open",
"translation": "", "translation": "",
@@ -2015,13 +1938,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Border color around blurred surfaces",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Border with BG", "term": "Border with BG",
"translation": "", "translation": "",
@@ -2373,7 +2289,7 @@
"comment": "" "comment": ""
}, },
{ {
"term": "Check sync status on demand. Sync copies your theme, settings, and wallpaper configuration to the login screen. Authentication changes apply automatically.", "term": "Check sync status on demand. Sync copies your theme, settings, PAM config, and wallpaper to the login screen in one step. Must run Sync to apply changes.",
"translation": "", "translation": "",
"context": "", "context": "",
"reference": "", "reference": "",
@@ -3628,7 +3544,7 @@
{ {
"term": "Custom", "term": "Custom",
"translation": "", "translation": "",
"context": "blur border color | shadow color option | theme category option", "context": "shadow color option | theme category option",
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
@@ -3801,7 +3717,7 @@
"comment": "" "comment": ""
}, },
{ {
"term": "DMS greeter needs: greetd, dms-greeter. Fingerprint: fprintd, pam_fprintd. Security keys: pam_u2f. Add your user to the greeter group. Authentication changes apply automatically and may open a terminal when sudo authentication is required.", "term": "DMS greeter needs: greetd, dms-greeter. Fingerprint: fprintd, pam_fprintd. Security keys: pam_u2f. Add your user to the greeter group. Sync checks sudo first and opens a terminal when interactive authentication is required.",
"translation": "", "translation": "",
"context": "", "context": "",
"reference": "", "reference": "",
@@ -4872,7 +4788,7 @@
"comment": "" "comment": ""
}, },
{ {
"term": "Enable fingerprint or security key for DMS Greeter. Authentication changes apply automatically.", "term": "Enable fingerprint or security key for DMS Greeter. Run Sync to apply and configure PAM.",
"translation": "", "translation": "",
"context": "", "context": "",
"reference": "", "reference": "",
@@ -4920,13 +4836,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Enabled, but no prints are enrolled yet. Authentication changes apply automatically once you enroll fingerprints.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Enabled, but no prints are enrolled yet. Enroll fingerprints and run Sync.", "term": "Enabled, but no prints are enrolled yet. Enroll fingerprints and run Sync.",
"translation": "", "translation": "",
@@ -4935,7 +4844,7 @@
"comment": "" "comment": ""
}, },
{ {
"term": "Enabled, but no registered security key was found yet. Authentication changes apply automatically once your key is registered or your U2F config is updated.", "term": "Enabled, but no prints are enrolled yet. Enroll fingerprints to use it.",
"translation": "", "translation": "",
"context": "", "context": "",
"reference": "", "reference": "",
@@ -4948,6 +4857,13 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Enabled, but no registered security key was found yet. Register a key or update your U2F config.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Enabled, but security-key availability could not be confirmed.", "term": "Enabled, but security-key availability could not be confirmed.",
"translation": "", "translation": "",
@@ -5893,27 +5809,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Fingerprint error",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Fingerprint error: %1",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Fingerprint not recognized (%1/%2). Please try again or use password.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and enroll later.", "term": "Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and enroll later.",
"translation": "", "translation": "",
@@ -7244,13 +7139,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Incorrect password - try again",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Indicator Style", "term": "Indicator Style",
"translation": "", "translation": "",
@@ -7314,13 +7202,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Insert your security key...",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Install", "term": "Install",
"translation": "", "translation": "",
@@ -8049,13 +7930,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Lock screen authentication changes apply automatically and may open a terminal when sudo authentication is required.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Locked", "term": "Locked",
"translation": "", "translation": "",
@@ -8441,13 +8315,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Maximum fingerprint attempts reached. Please use password.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Maximum number of clipboard entries to keep", "term": "Maximum number of clipboard entries to keep",
"translation": "", "translation": "",
@@ -10264,7 +10131,7 @@
{ {
"term": "Outline", "term": "Outline",
"translation": "", "translation": "",
"context": "blur border color | outline color", "context": "outline color",
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
@@ -11195,7 +11062,7 @@
{ {
"term": "Primary", "term": "Primary",
"translation": "", "translation": "",
"context": "blur border color | button color option | color option | primary color | shadow color option | tile color option", "context": "button color option | color option | primary color | shadow color option | tile color option",
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
@@ -11612,13 +11479,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Remember Last Query",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Remember last session", "term": "Remember last session",
"translation": "", "translation": "",
@@ -11745,13 +11605,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Requires a newer version of Quickshell",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Requires night mode support", "term": "Requires night mode support",
"translation": "", "translation": "",
@@ -11997,6 +11850,20 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Run Sync to apply.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Run Sync to apply. Fingerprint-only login may not unlock GNOME Keyring.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Run User Templates", "term": "Run User Templates",
"translation": "", "translation": "",
@@ -12392,7 +12259,7 @@
{ {
"term": "Secondary", "term": "Secondary",
"translation": "", "translation": "",
"context": "blur border color | button color option | color option | secondary color | tile color option", "context": "button color option | color option | secondary color | tile color option",
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
@@ -14188,13 +14055,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Terminal fallback failed. Install a supported terminal emulator or run 'dms auth sync' manually.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Terminal fallback failed. Install one of the supported terminal emulators or run 'dms greeter sync' manually.", "term": "Terminal fallback failed. Install one of the supported terminal emulators or run 'dms greeter sync' manually.",
"translation": "", "translation": "",
@@ -14202,13 +14062,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Terminal fallback opened. Complete authentication setup there; it will close automatically when done.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Terminal fallback opened. Complete sync there; it will close automatically when done.", "term": "Terminal fallback opened. Complete sync there; it will close automatically when done.",
"translation": "", "translation": "",
@@ -14223,13 +14076,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Terminal opened. Complete authentication setup there; it will close automatically when done.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Terminal opened. Complete sync authentication there; it will close automatically when done.", "term": "Terminal opened. Complete sync authentication there; it will close automatically when done.",
"translation": "", "translation": "",
@@ -14282,7 +14128,7 @@
{ {
"term": "Text Color", "term": "Text Color",
"translation": "", "translation": "",
"context": "blur border color | shadow color option", "context": "shadow color option",
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
@@ -14643,13 +14489,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Too many attempts - locked out",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Too many failed attempts - account may be locked", "term": "Too many failed attempts - account may be locked",
"translation": "", "translation": "",
@@ -14734,13 +14573,6 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Touch your security key...",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Transform", "term": "Transform",
"translation": "", "translation": "",
@@ -14846,6 +14678,20 @@
"reference": "", "reference": "",
"comment": "" "comment": ""
}, },
{
"term": "Type to search",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Type to search apps",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{ {
"term": "Type to search files", "term": "Type to search files",
"translation": "", "translation": "",