mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-27 05:25:19 -04:00
Compare commits
26 Commits
blur
...
37f92677cf
| Author | SHA1 | Date | |
|---|---|---|---|
| 37f92677cf | |||
| 13e8130858 | |||
| f6e590a518 | |||
| 3194fc3fbe | |||
| 3318864ece | |||
| e224417593 | |||
| 3f7f6c5d2c | |||
| 0b88055742 | |||
| 2b0826e397 | |||
| 7db04c9660 | |||
| 14d1e1d985 | |||
| 903ab1e61d | |||
| 5982655539 | |||
| 1021a210cf | |||
| e34edb15bb | |||
| 61ee5f4336 | |||
| ce2a92ec27 | |||
| 66ce79b9bf | |||
| 30dd640314 | |||
| 28f9aabcd9 | |||
| 3d9bd73336 | |||
| 3497d5f523 | |||
| 8ef1d95e65 | |||
| e9aeb9ac60 | |||
| fb02f7294d | |||
| f15d49d80a |
@@ -0,0 +1,40 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -525,5 +525,6 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
configCmd,
|
configCmd,
|
||||||
dlCmd,
|
dlCmd,
|
||||||
randrCmd,
|
randrCmd,
|
||||||
|
blurCmd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -820,10 +820,14 @@ 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"}
|
||||||
if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {
|
terminals = slices.DeleteFunc(terminals, func(t string) bool {
|
||||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", optionalFeaturesURL})
|
return !utils.CommandExists(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
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, or alacritty", optionalFeaturesURL})
|
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, foot or alacritty", optionalFeaturesURL})
|
||||||
}
|
}
|
||||||
|
|
||||||
networkResult, err := network.DetectNetworkStack()
|
networkResult, err := network.DetectNetworkStack()
|
||||||
|
|||||||
@@ -109,16 +109,41 @@ func updateArchLinux() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var packageName string
|
var packageName string
|
||||||
if isArchPackageInstalled("dms-shell-bin") {
|
var isAUR bool
|
||||||
packageName = "dms-shell-bin"
|
if isArchPackageInstalled("dms-shell") {
|
||||||
|
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: Neither dms-shell-bin nor dms-shell-git package found.")
|
fmt.Println("Info: No dms-shell 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
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +31,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if os.Geteuid() == 0 {
|
clipboard.MaybeServeAndExit()
|
||||||
|
|
||||||
|
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.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +28,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if os.Geteuid() == 0 {
|
clipboard.MaybeServeAndExit()
|
||||||
|
|
||||||
|
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.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,22 @@ 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()
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package clipboard
|
package clipboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,66 +12,142 @@ 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 CopyReader(bytes.NewReader(data), mimeType, false, false)
|
return copyForkCached(data, mimeType, 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 copyServeWithWriter(func(writer io.Writer) error {
|
return serveClipboard(data, mimeType, pasteOnce)
|
||||||
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 CopyReader(bytes.NewReader(data), mimeType, foreground, pasteOnce)
|
return copyForkCached(data, mimeType, 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 {
|
||||||
return copyFork(data, mimeType, pasteOnce)
|
buf, err := io.ReadAll(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read source: %w", err)
|
||||||
|
}
|
||||||
|
return serveClipboard(buf, mimeType, pasteOnce)
|
||||||
}
|
}
|
||||||
return copyServeReader(data, mimeType, pasteOnce)
|
return copyFork(data, mimeType, pasteOnce)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
func newForkCmd(mimeType string, pasteOnce bool, extra ...string) *exec.Cmd {
|
||||||
args := []string{os.Args[0], "cl", "copy", "--foreground"}
|
cmd := exec.Command(os.Args[0])
|
||||||
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(), "DMS_CLIP_FORKED=1")
|
cmd.Env = append(os.Environ(),
|
||||||
|
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
|
||||||
if err := cmd.Start(); err != nil {
|
return waitReady(cmd)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -83,50 +158,22 @@ 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
|
var buf [1]byte
|
||||||
if _, err := stdout.Read(buf[:]); err != nil {
|
if _, err := stdout.Read(buf[:]); err != nil {
|
||||||
return fmt.Errorf("waiting for clipboard ready: %w", err)
|
return fmt.Errorf("waiting for clipboard ready: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func signalReady() {
|
func signalReady() {
|
||||||
if os.Getenv("DMS_CLIP_FORKED") == "" {
|
if os.Getenv(envServe) == "" {
|
||||||
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{}
|
||||||
|
|
||||||
@@ -147,7 +194,7 @@ func createClipboardCacheFile() (*os.File, error) {
|
|||||||
return os.CreateTemp("", "dms-clipboard-*")
|
return os.CreateTemp("", "dms-clipboard-*")
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOnce bool) error {
|
func serveClipboard(data []byte, 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)
|
||||||
@@ -189,12 +236,10 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
|||||||
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")
|
||||||
}
|
}
|
||||||
@@ -233,18 +278,12 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
|||||||
|
|
||||||
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) {
|
||||||
defer syscall.Close(e.Fd)
|
_ = syscall.SetNonblock(e.Fd, false)
|
||||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
if err := writeTo(file); err != nil {
|
_, _ = file.Write(data)
|
||||||
select {
|
|
||||||
case sendErr <- err:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case pasted <- struct{}{}:
|
case pasted <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
@@ -266,8 +305,6 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
|||||||
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
|
||||||
@@ -521,12 +558,10 @@ 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")
|
||||||
}
|
}
|
||||||
@@ -554,12 +589,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) {
|
||||||
defer syscall.Close(e.Fd)
|
_ = syscall.SetNonblock(e.Fd, false)
|
||||||
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%
|
bind = SUPER CTRL, F, resizeactive, exact 100% 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,6 +94,7 @@ 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)$
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ 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$"#
|
||||||
|
|||||||
@@ -242,11 +242,7 @@ func (a *ArchDistribution) getDMSMapping(variant deps.PackageVariant) PackageMap
|
|||||||
return PackageMapping{Name: "dms-shell-git", Repository: RepoTypeAUR}
|
return PackageMapping{Name: "dms-shell-git", Repository: RepoTypeAUR}
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.packageInstalled("dms-shell-bin") {
|
return PackageMapping{Name: "dms-shell", Repository: RepoTypeSystem}
|
||||||
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 {
|
||||||
@@ -540,7 +536,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" || pkg == "dms-shell-bin" {
|
if pkg == "dms-shell-git" {
|
||||||
dmsShell = append(dmsShell, pkg)
|
dmsShell = append(dmsShell, pkg)
|
||||||
} else {
|
} else {
|
||||||
isDep := false
|
isDep := false
|
||||||
@@ -621,7 +617,7 @@ func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg == "dms-shell-git" || pkg == "dms-shell-bin" {
|
if pkg == "dms-shell-git" {
|
||||||
srcinfoPath := filepath.Join(packageDir, ".SRCINFO")
|
srcinfoPath := filepath.Join(packageDir, ".SRCINFO")
|
||||||
depsToRemove := []string{
|
depsToRemove := []string{
|
||||||
"depends = quickshell",
|
"depends = quickshell",
|
||||||
@@ -644,15 +640,7 @@ 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),
|
||||||
@@ -739,42 +727,9 @@ 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
|
||||||
if pkg == "dms-shell-git" || pkg == "dms-shell-bin" {
|
matches, _ := filepath.Glob(filepath.Join(packageDir, "*.pkg.tar*"))
|
||||||
// For DMS split packages, install base package
|
files = matches
|
||||||
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)
|
||||||
|
|||||||
@@ -444,20 +444,21 @@ func GetFocusedMonitor() string {
|
|||||||
|
|
||||||
type outputInfo struct {
|
type outputInfo struct {
|
||||||
x, y int32
|
x, y int32
|
||||||
|
scale float64
|
||||||
transform int32
|
transform int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOutputInfo(outputName string) (*outputInfo, bool) {
|
func getAllOutputInfos() map[string]*outputInfo {
|
||||||
display, err := client.Connect("")
|
display, err := client.Connect("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil
|
||||||
}
|
}
|
||||||
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, false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputManager *wlr_output_management.ZwlrOutputManagerV1
|
var outputManager *wlr_output_management.ZwlrOutputManagerV1
|
||||||
@@ -476,16 +477,17 @@ func getOutputInfo(outputName string) (*outputInfo, bool) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
return nil, false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if outputManager == nil {
|
if outputManager == nil {
|
||||||
return nil, false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -501,6 +503,9 @@ func getOutputInfo(outputName string) (*outputInfo, bool) {
|
|||||||
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
|
||||||
})
|
})
|
||||||
@@ -511,21 +516,32 @@ func getOutputInfo(outputName string) (*outputInfo, bool) {
|
|||||||
|
|
||||||
for !done {
|
for !done {
|
||||||
if err := ctx.Dispatch(); err != nil {
|
if err := ctx.Dispatch(); err != nil {
|
||||||
return nil, false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result := make(map[string]*outputInfo, len(heads))
|
||||||
for _, state := range heads {
|
for _, state := range heads {
|
||||||
if state.name == outputName {
|
if state.name == "" {
|
||||||
return &outputInfo{
|
continue
|
||||||
x: state.x,
|
}
|
||||||
y: state.y,
|
result[state.name] = &outputInfo{
|
||||||
transform: state.transform,
|
x: state.x,
|
||||||
}, true
|
y: state.y,
|
||||||
|
scale: state.scale,
|
||||||
|
transform: state.transform,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
return nil, false
|
func getOutputInfo(outputName string) (*outputInfo, bool) {
|
||||||
|
infos := getAllOutputInfos()
|
||||||
|
if infos == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
info, ok := infos[outputName]
|
||||||
|
return info, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDWLActiveWindow() (*WindowGeometry, error) {
|
func getDWLActiveWindow() (*WindowGeometry, error) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package screenshot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
@@ -304,22 +305,20 @@ 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])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture all outputs first to get actual buffer sizes
|
wlrInfos := getAllOutputInfos()
|
||||||
type capturedOutput struct {
|
|
||||||
output *WaylandOutput
|
|
||||||
result *CaptureResult
|
|
||||||
physX int
|
|
||||||
physY int
|
|
||||||
}
|
|
||||||
captured := make([]capturedOutput, 0, len(outputs))
|
|
||||||
|
|
||||||
var minX, minY, maxX, maxY int
|
type pendingOutput struct {
|
||||||
first := true
|
result *CaptureResult
|
||||||
|
logX float64
|
||||||
|
logY float64
|
||||||
|
scale float64
|
||||||
|
}
|
||||||
|
var pending []pendingOutput
|
||||||
|
maxScale := 1.0
|
||||||
|
|
||||||
for _, output := range outputs {
|
for _, output := range outputs {
|
||||||
result, err := s.captureWholeOutput(output)
|
result, err := s.captureWholeOutput(output)
|
||||||
@@ -328,50 +327,74 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
outX, outY := output.x, output.y
|
logX, logY := float64(output.x), float64(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 {
|
||||||
outX, outY = hx, hy
|
logX, logY = float64(hx), float64(hy)
|
||||||
}
|
}
|
||||||
if s := GetHyprlandMonitorScale(output.name); s > 0 {
|
if hs := GetHyprlandMonitorScale(output.name); hs > 0 {
|
||||||
scale = s
|
scale = hs
|
||||||
}
|
}
|
||||||
case CompositorDWL:
|
default:
|
||||||
if info, ok := getOutputInfo(output.name); ok {
|
if wlrInfos != nil {
|
||||||
outX, outY = info.x, info.y
|
if info, ok := wlrInfos[output.name]; ok {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
physX := int(float64(outX) * scale)
|
pending = append(pending, pendingOutput{result: result, logX: logX, logY: logY, scale: scale})
|
||||||
physY := int(float64(outY) * scale)
|
if scale > maxScale {
|
||||||
|
maxScale = scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
captured = append(captured, capturedOutput{
|
if len(pending) == 0 {
|
||||||
output: output,
|
return nil, fmt.Errorf("failed to capture any outputs")
|
||||||
result: result,
|
}
|
||||||
physX: physX,
|
if len(pending) == 1 {
|
||||||
physY: physY,
|
return pending[0].result, nil
|
||||||
})
|
}
|
||||||
|
|
||||||
right := physX + result.Buffer.Width
|
type layoutEntry struct {
|
||||||
bottom := physY + result.Buffer.Height
|
result *CaptureResult
|
||||||
|
canvasX int
|
||||||
|
canvasY int
|
||||||
|
canvasW int
|
||||||
|
canvasH int
|
||||||
|
}
|
||||||
|
entries := make([]layoutEntry, len(pending))
|
||||||
|
var minX, minY, maxX, maxY int
|
||||||
|
|
||||||
if first {
|
for i, p := range pending {
|
||||||
minX, minY = physX, physY
|
cx := int(math.Round(p.logX * maxScale))
|
||||||
maxX, maxY = right, bottom
|
cy := int(math.Round(p.logY * maxScale))
|
||||||
first = false
|
cw := int(math.Round(float64(p.result.Buffer.Width) * maxScale / p.scale))
|
||||||
|
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 {
|
||||||
if physX < minX {
|
minX = cx
|
||||||
minX = physX
|
|
||||||
}
|
}
|
||||||
if physY < minY {
|
if cy < minY {
|
||||||
minY = physY
|
minY = cy
|
||||||
}
|
}
|
||||||
if right > maxX {
|
if right > maxX {
|
||||||
maxX = right
|
maxX = right
|
||||||
@@ -381,35 +404,26 @@ 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 _, c := range captured {
|
for _, e := range entries {
|
||||||
c.result.Buffer.Close()
|
e.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 _, c := range captured {
|
for _, e := range entries {
|
||||||
if format == 0 {
|
if format == 0 {
|
||||||
format = c.result.Format
|
format = e.result.Format
|
||||||
}
|
}
|
||||||
s.blitBuffer(composite, c.result.Buffer, c.physX-minX, c.physY-minY, c.result.YInverted)
|
s.blitBufferScaled(composite, e.result.Buffer,
|
||||||
c.result.Buffer.Close()
|
e.canvasX-minX, e.canvasY-minY, e.canvasW, e.canvasH,
|
||||||
|
e.result.YInverted)
|
||||||
|
e.result.Buffer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CaptureResult{
|
return &CaptureResult{
|
||||||
@@ -419,32 +433,44 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) blitBuffer(dst, src *ShmBuffer, dstX, dstY int, yInverted bool) {
|
func (s *Screenshoter) blitBufferScaled(dst, src *ShmBuffer, dstX, dstY, dstW, dstH int, yInverted bool) {
|
||||||
|
if dstW <= 0 || dstH <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
srcData := src.Data()
|
srcData := src.Data()
|
||||||
dstData := dst.Data()
|
dstData := dst.Data()
|
||||||
|
|
||||||
for srcY := 0; srcY < src.Height; srcY++ {
|
for dy := 0; dy < dstH; dy++ {
|
||||||
actualSrcY := srcY
|
canvasY := dstY + dy
|
||||||
if yInverted {
|
if canvasY < 0 || canvasY >= dst.Height {
|
||||||
actualSrcY = src.Height - 1 - srcY
|
|
||||||
}
|
|
||||||
|
|
||||||
dy := dstY + srcY
|
|
||||||
if dy < 0 || dy >= dst.Height {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
srcRowOff := actualSrcY * src.Stride
|
srcY := dy * src.Height / dstH
|
||||||
dstRowOff := dy * dst.Stride
|
if yInverted {
|
||||||
|
srcY = src.Height - 1 - srcY
|
||||||
|
}
|
||||||
|
if srcY < 0 || srcY >= src.Height {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for srcX := 0; srcX < src.Width; srcX++ {
|
srcRowOff := srcY * src.Stride
|
||||||
dx := dstX + srcX
|
dstRowOff := canvasY * dst.Stride
|
||||||
if dx < 0 || dx >= dst.Width {
|
|
||||||
|
for dx := 0; dx < dstW; dx++ {
|
||||||
|
canvasX := dstX + dx
|
||||||
|
if canvasX < 0 || canvasX >= dst.Width {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
srcX := dx * src.Width / dstW
|
||||||
|
if srcX >= src.Width {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
si := srcRowOff + srcX*4
|
si := srcRowOff + srcX*4
|
||||||
di := dstRowOff + dx*4
|
di := dstRowOff + canvasX*4
|
||||||
|
|
||||||
if si+3 >= len(srcData) || di+3 >= len(dstData) {
|
if si+3 >= len(srcData) || di+3 >= len(dstData) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ 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"
|
||||||
@@ -72,6 +73,7 @@ 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
|
||||||
|
|
||||||
@@ -394,6 +396,18 @@ 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 {
|
||||||
@@ -1325,6 +1339,9 @@ 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()
|
||||||
}
|
}
|
||||||
@@ -1610,6 +1627,18 @@ 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
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -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-bin"
|
return "dms-shell"
|
||||||
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"
|
||||||
|
|||||||
@@ -7,60 +7,8 @@ import Quickshell
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property Rounding rounding: Rounding {}
|
readonly property AppearanceRounding rounding: AppearanceRounding {}
|
||||||
readonly property Spacing spacing: Spacing {}
|
readonly property AppearanceSpacing spacing: AppearanceSpacing {}
|
||||||
readonly property FontSize fontSize: FontSize {}
|
readonly property AppearanceFontSize fontSize: AppearanceFontSize {}
|
||||||
readonly property Anim anim: Anim {}
|
readonly property AppearanceAnim anim: AppearanceAnim {}
|
||||||
|
|
||||||
component Rounding: QtObject {
|
|
||||||
readonly property int small: 8
|
|
||||||
readonly property int normal: 12
|
|
||||||
readonly property int large: 16
|
|
||||||
readonly property int extraLarge: 24
|
|
||||||
readonly property int full: 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
component Spacing: QtObject {
|
|
||||||
readonly property int small: 4
|
|
||||||
readonly property int normal: 8
|
|
||||||
readonly property int large: 12
|
|
||||||
readonly property int extraLarge: 16
|
|
||||||
readonly property int huge: 24
|
|
||||||
}
|
|
||||||
|
|
||||||
component FontSize: QtObject {
|
|
||||||
readonly property int small: 12
|
|
||||||
readonly property int normal: 14
|
|
||||||
readonly property int large: 16
|
|
||||||
readonly property int extraLarge: 20
|
|
||||||
readonly property int huge: 24
|
|
||||||
}
|
|
||||||
|
|
||||||
component AnimCurves: QtObject {
|
|
||||||
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
|
|
||||||
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
|
|
||||||
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
|
|
||||||
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1
|
|
||||||
/ 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
|
|
||||||
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
|
||||||
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
|
||||||
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
|
|
||||||
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
|
|
||||||
readonly property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
component AnimDurations: QtObject {
|
|
||||||
readonly property int quick: 150
|
|
||||||
readonly property int normal: 300
|
|
||||||
readonly property int slow: 500
|
|
||||||
readonly property int extraSlow: 1000
|
|
||||||
readonly property int expressiveFastSpatial: 350
|
|
||||||
readonly property int expressiveDefaultSpatial: 500
|
|
||||||
readonly property int expressiveEffects: 200
|
|
||||||
}
|
|
||||||
|
|
||||||
component Anim: QtObject {
|
|
||||||
readonly property AnimCurves curves: AnimCurves {}
|
|
||||||
readonly property AnimDurations durations: AnimDurations {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
readonly property AppearanceAnimCurves curves: AppearanceAnimCurves {}
|
||||||
|
readonly property AppearanceAnimDurations durations: AppearanceAnimDurations {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
|
||||||
|
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
|
||||||
|
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
|
||||||
|
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
|
||||||
|
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
||||||
|
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
||||||
|
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
|
||||||
|
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
|
||||||
|
readonly property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
readonly property int quick: 150
|
||||||
|
readonly property int normal: 300
|
||||||
|
readonly property int slow: 500
|
||||||
|
readonly property int extraSlow: 1000
|
||||||
|
readonly property int expressiveFastSpatial: 350
|
||||||
|
readonly property int expressiveDefaultSpatial: 500
|
||||||
|
readonly property int expressiveEffects: 200
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
readonly property int small: 12
|
||||||
|
readonly property int normal: 14
|
||||||
|
readonly property int large: 16
|
||||||
|
readonly property int extraLarge: 20
|
||||||
|
readonly property int huge: 24
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
readonly property int small: 8
|
||||||
|
readonly property int normal: 12
|
||||||
|
readonly property int large: 16
|
||||||
|
readonly property int extraLarge: 24
|
||||||
|
readonly property int full: 1000
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
readonly property int small: 4
|
||||||
|
readonly property int normal: 8
|
||||||
|
readonly property int large: 12
|
||||||
|
readonly property int extraLarge: 16
|
||||||
|
readonly property int huge: 24
|
||||||
|
}
|
||||||
@@ -186,6 +186,14 @@ Singleton {
|
|||||||
onPopoutElevationEnabledChanged: saveSettings()
|
onPopoutElevationEnabledChanged: saveSettings()
|
||||||
property bool barElevationEnabled: true
|
property bool barElevationEnabled: true
|
||||||
onBarElevationEnabledChanged: saveSettings()
|
onBarElevationEnabledChanged: saveSettings()
|
||||||
|
property bool blurEnabled: false
|
||||||
|
onBlurEnabledChanged: saveSettings()
|
||||||
|
property string blurBorderColor: "outline"
|
||||||
|
onBlurBorderColorChanged: saveSettings()
|
||||||
|
property string blurBorderCustomColor: "#ffffff"
|
||||||
|
onBlurBorderCustomColorChanged: saveSettings()
|
||||||
|
property real blurBorderOpacity: 1.0
|
||||||
|
onBlurBorderOpacityChanged: saveSettings()
|
||||||
property string wallpaperFillMode: "Fill"
|
property string wallpaperFillMode: "Fill"
|
||||||
property bool blurredWallpaperLayer: false
|
property bool blurredWallpaperLayer: false
|
||||||
property bool blurWallpaperOnOverview: false
|
property bool blurWallpaperOnOverview: false
|
||||||
@@ -293,6 +301,7 @@ 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
|
||||||
@@ -426,6 +435,7 @@ 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
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ var SPEC = {
|
|||||||
modalElevationEnabled: { def: true },
|
modalElevationEnabled: { def: true },
|
||||||
popoutElevationEnabled: { def: true },
|
popoutElevationEnabled: { def: true },
|
||||||
barElevationEnabled: { def: true },
|
barElevationEnabled: { def: true },
|
||||||
|
blurEnabled: { def: false },
|
||||||
|
blurBorderColor: { def: "outline" },
|
||||||
|
blurBorderCustomColor: { def: "#ffffff" },
|
||||||
|
blurBorderOpacity: { def: 1.0, coerce: percentToUnit },
|
||||||
wallpaperFillMode: { def: "Fill" },
|
wallpaperFillMode: { def: "Fill" },
|
||||||
blurredWallpaperLayer: { def: false },
|
blurredWallpaperLayer: { def: false },
|
||||||
blurWallpaperOnOverview: { def: false },
|
blurWallpaperOnOverview: { def: false },
|
||||||
@@ -136,6 +140,7 @@ 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 },
|
||||||
@@ -238,6 +243,7 @@ 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 },
|
||||||
|
|||||||
@@ -221,10 +221,22 @@ 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 {
|
||||||
|
|||||||
@@ -369,9 +369,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function previous(): void {
|
function previous(): void {
|
||||||
if (MprisController.activePlayer && MprisController.activePlayer.canGoPrevious) {
|
MprisController.previousOrRewind();
|
||||||
MprisController.activePlayer.previous();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function next(): void {
|
function next(): void {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("No recent clipboard entries found")
|
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No recent clipboard entries found") : I18n.tr("Connecting to clipboard service…")
|
||||||
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: I18n.tr("No saved clipboard entries")
|
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No saved clipboard entries") : I18n.tr("Connecting to clipboard service…")
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
|
|||||||
@@ -60,15 +60,12 @@ 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();
|
||||||
ClipboardService.refresh();
|
if (clipboardAvailable)
|
||||||
|
ClipboardService.refresh();
|
||||||
keyboardController.reset();
|
keyboardController.reset();
|
||||||
|
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
|
|||||||
@@ -50,14 +50,11 @@ 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();
|
||||||
ClipboardService.refresh();
|
if (clipboardAvailable)
|
||||||
|
ClipboardService.refresh();
|
||||||
keyboardController.reset();
|
keyboardController.reset();
|
||||||
|
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
@@ -122,10 +119,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) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Quickshell
|
|||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -30,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.surfaceContainer
|
property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
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
|
||||||
@@ -59,11 +60,25 @@ Item {
|
|||||||
function open() {
|
function open() {
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
const focusedScreen = CompositorService.getFocusedScreen();
|
const focusedScreen = CompositorService.getFocusedScreen();
|
||||||
|
const screenChanged = focusedScreen && contentWindow.screen !== focusedScreen;
|
||||||
if (focusedScreen) {
|
if (focusedScreen) {
|
||||||
|
if (screenChanged)
|
||||||
|
contentWindow.visible = false;
|
||||||
contentWindow.screen = focusedScreen;
|
contentWindow.screen = focusedScreen;
|
||||||
if (!useSingleWindow)
|
if (!useSingleWindow) {
|
||||||
|
if (screenChanged)
|
||||||
|
clickCatcher.visible = false;
|
||||||
clickCatcher.screen = focusedScreen;
|
clickCatcher.screen = focusedScreen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (screenChanged) {
|
||||||
|
Qt.callLater(() => root._finishOpen());
|
||||||
|
} else {
|
||||||
|
_finishOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _finishOpen() {
|
||||||
ModalManager.openModal(root);
|
ModalManager.openModal(root);
|
||||||
shouldBeVisible = true;
|
shouldBeVisible = true;
|
||||||
if (!useSingleWindow)
|
if (!useSingleWindow)
|
||||||
@@ -215,6 +230,16 @@ Item {
|
|||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: contentWindow
|
||||||
|
readonly property real s: Math.min(1, modalContainer.scaleValue)
|
||||||
|
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
|
||||||
|
blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
|
||||||
|
blurWidth: (shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.width * s : 0
|
||||||
|
blurHeight: (shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.height * s : 0
|
||||||
|
blurRadius: root.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: root.layerNamespace
|
WlrLayershell.namespace: root.layerNamespace
|
||||||
WlrLayershell.layer: {
|
WlrLayershell.layer: {
|
||||||
if (root.useOverlayLayer)
|
if (root.useOverlayLayer)
|
||||||
@@ -393,6 +418,15 @@ Item {
|
|||||||
shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: root.shouldBeVisible
|
focus: root.shouldBeVisible
|
||||||
|
|||||||
@@ -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.surfaceContainer
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
cornerRadius: Theme.cornerRadius
|
cornerRadius: Theme.cornerRadius
|
||||||
borderColor: Theme.outlineMedium
|
borderColor: Theme.outlineMedium
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Quickshell.Wayland
|
|||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -134,40 +135,47 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
function _finishShow(query, mode) {
|
||||||
closeCleanupTimer.stop();
|
spotlightOpen = true;
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
|
||||||
if (focusedScreen)
|
|
||||||
launcherWindow.screen = focusedScreen;
|
|
||||||
|
|
||||||
spotlightOpen = true;
|
|
||||||
keyboardActive = true;
|
keyboardActive = true;
|
||||||
ModalManager.openModal(root);
|
ModalManager.openModal(root);
|
||||||
if (useHyprlandFocusGrab)
|
if (useHyprlandFocusGrab)
|
||||||
focusGrab.active = true;
|
focusGrab.active = true;
|
||||||
|
|
||||||
_ensureContentLoadedAndInitialize("", "");
|
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
closeCleanupTimer.stop();
|
||||||
|
|
||||||
|
var focusedScreen = CompositorService.getFocusedScreen();
|
||||||
|
if (focusedScreen && launcherWindow.screen !== focusedScreen) {
|
||||||
|
spotlightOpen = false;
|
||||||
|
isClosing = false;
|
||||||
|
launcherWindow.screen = focusedScreen;
|
||||||
|
Qt.callLater(() => root._finishShow("", ""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_finishShow("", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWithQuery(query) {
|
function showWithQuery(query) {
|
||||||
closeCleanupTimer.stop();
|
closeCleanupTimer.stop();
|
||||||
isClosing = false;
|
|
||||||
openedFromOverview = false;
|
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
var focusedScreen = CompositorService.getFocusedScreen();
|
||||||
if (focusedScreen)
|
if (focusedScreen && launcherWindow.screen !== focusedScreen) {
|
||||||
|
spotlightOpen = false;
|
||||||
|
isClosing = false;
|
||||||
launcherWindow.screen = focusedScreen;
|
launcherWindow.screen = focusedScreen;
|
||||||
|
Qt.callLater(() => root._finishShow(query, ""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
spotlightOpen = true;
|
_finishShow(query, "");
|
||||||
keyboardActive = true;
|
|
||||||
ModalManager.openModal(root);
|
|
||||||
if (useHyprlandFocusGrab)
|
|
||||||
focusGrab.active = true;
|
|
||||||
|
|
||||||
_ensureContentLoadedAndInitialize(query, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
@@ -191,14 +199,20 @@ Item {
|
|||||||
|
|
||||||
function showWithMode(mode) {
|
function showWithMode(mode) {
|
||||||
closeCleanupTimer.stop();
|
closeCleanupTimer.stop();
|
||||||
|
|
||||||
|
var focusedScreen = CompositorService.getFocusedScreen();
|
||||||
|
if (focusedScreen && launcherWindow.screen !== focusedScreen) {
|
||||||
|
spotlightOpen = false;
|
||||||
|
isClosing = false;
|
||||||
|
launcherWindow.screen = focusedScreen;
|
||||||
|
Qt.callLater(() => root._finishShow("", mode));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
spotlightOpen = true;
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
|
||||||
if (focusedScreen)
|
|
||||||
launcherWindow.screen = focusedScreen;
|
|
||||||
|
|
||||||
spotlightOpen = true;
|
|
||||||
keyboardActive = true;
|
keyboardActive = true;
|
||||||
ModalManager.openModal(root);
|
ModalManager.openModal(root);
|
||||||
if (useHyprlandFocusGrab)
|
if (useHyprlandFocusGrab)
|
||||||
@@ -295,6 +309,16 @@ Item {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
exclusionMode: ExclusionMode.Ignore
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: launcherWindow
|
||||||
|
readonly property real s: Math.min(1, modalContainer.scale)
|
||||||
|
blurX: root.modalX + root.modalWidth * (1 - s) * 0.5
|
||||||
|
blurY: root.modalY + root.modalHeight * (1 - s) * 0.5
|
||||||
|
blurWidth: (contentVisible && modalContainer.opacity > 0) ? root.modalWidth * s : 0
|
||||||
|
blurHeight: (contentVisible && modalContainer.opacity > 0) ? root.modalHeight * s : 0
|
||||||
|
blurRadius: root.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:spotlight"
|
WlrLayershell.namespace: "dms:spotlight"
|
||||||
WlrLayershell.layer: {
|
WlrLayershell.layer: {
|
||||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||||
@@ -428,6 +452,14 @@ Item {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ FocusScope {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !editMode
|
visible: !editMode && !(root.parentModal?.isClosing ?? false)
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: footerBar
|
id: footerBar
|
||||||
@@ -737,8 +737,6 @@ 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,6 +324,8 @@ 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;
|
||||||
@@ -449,7 +451,7 @@ Item {
|
|||||||
case "apps":
|
case "apps":
|
||||||
return "apps";
|
return "apps";
|
||||||
default:
|
default:
|
||||||
return root.controller?.searchQuery?.length > 0 ? "search_off" : "search";
|
return "search_off";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,9 +487,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 hasQuery ? I18n.tr("No apps found") : I18n.tr("Type to search apps");
|
return I18n.tr("No apps found");
|
||||||
default:
|
default:
|
||||||
return hasQuery ? I18n.tr("No results found") : I18n.tr("Type to search");
|
return I18n.tr("No results found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: checkboxRow
|
||||||
|
|
||||||
|
property alias checked: checkbox.checked
|
||||||
|
property alias label: labelText.text
|
||||||
|
property bool indeterminate: false
|
||||||
|
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
height: 24
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: checkbox
|
||||||
|
property bool checked: false
|
||||||
|
width: 20
|
||||||
|
height: 20
|
||||||
|
radius: 4
|
||||||
|
color: checkboxRow.indeterminate ? Theme.surfaceVariant : (checked ? Theme.primary : "transparent")
|
||||||
|
border.color: checkboxRow.indeterminate ? Theme.outlineButton : (checked ? Theme.primary : Theme.outlineButton)
|
||||||
|
border.width: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: checkboxRow.indeterminate ? "remove" : "check"
|
||||||
|
size: 12
|
||||||
|
color: checkboxRow.indeterminate ? Theme.surfaceVariantText : Theme.background
|
||||||
|
visible: parent.checked || checkboxRow.indeterminate
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (checkboxRow.indeterminate) {
|
||||||
|
checkboxRow.indeterminate = false;
|
||||||
|
checkbox.checked = true;
|
||||||
|
} else {
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: labelText
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: inputFieldRect
|
||||||
|
|
||||||
|
default property alias contentData: inputFieldRect.data
|
||||||
|
property bool hasFocus: false
|
||||||
|
property int fieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: fieldHeight
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceHover
|
||||||
|
border.color: hasFocus ? Theme.primary : Theme.outlineStrong
|
||||||
|
border.width: hasFocus ? 2 : 1
|
||||||
|
}
|
||||||
@@ -297,78 +297,6 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component SectionHeader: StyledText {
|
|
||||||
property string title
|
|
||||||
text: title
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.primary
|
|
||||||
topPadding: Theme.spacingM
|
|
||||||
bottomPadding: Theme.spacingXS
|
|
||||||
width: parent.width
|
|
||||||
horizontalAlignment: Text.AlignLeft
|
|
||||||
}
|
|
||||||
|
|
||||||
component CheckboxRow: Row {
|
|
||||||
property alias checked: checkbox.checked
|
|
||||||
property alias label: labelText.text
|
|
||||||
property bool indeterminate: false
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
height: 24
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: checkbox
|
|
||||||
property bool checked: false
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
radius: 4
|
|
||||||
color: parent.indeterminate ? Theme.surfaceVariant : (checked ? Theme.primary : "transparent")
|
|
||||||
border.color: parent.indeterminate ? Theme.outlineButton : (checked ? Theme.primary : Theme.outlineButton)
|
|
||||||
border.width: 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: parent.parent.indeterminate ? "remove" : "check"
|
|
||||||
size: 12
|
|
||||||
color: parent.parent.indeterminate ? Theme.surfaceVariantText : Theme.background
|
|
||||||
visible: parent.checked || parent.parent.indeterminate
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (parent.parent.indeterminate) {
|
|
||||||
parent.parent.indeterminate = false;
|
|
||||||
parent.checked = true;
|
|
||||||
} else {
|
|
||||||
parent.checked = !parent.checked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: labelText
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component InputField: Rectangle {
|
|
||||||
id: inputFieldRect
|
|
||||||
default property alias contentData: inputFieldRect.data
|
|
||||||
property bool hasFocus: false
|
|
||||||
width: parent.width
|
|
||||||
height: root.inputFieldHeight
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.surfaceHover
|
|
||||||
border.color: hasFocus ? Theme.primary : Theme.outlineStrong
|
|
||||||
border.width: hasFocus ? 2 : 1
|
|
||||||
}
|
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: true
|
||||||
@@ -447,7 +375,7 @@ FloatingWindow {
|
|||||||
width: flickable.width - Theme.spacingM
|
width: flickable.width - Theme.spacingM
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
hasFocus: nameInput.activeFocus
|
hasFocus: nameInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: nameInput
|
id: nameInput
|
||||||
@@ -460,11 +388,11 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionHeader {
|
WindowRuleSectionHeader {
|
||||||
title: I18n.tr("Match Criteria")
|
title: I18n.tr("Match Criteria")
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
hasFocus: appIdInput.activeFocus
|
hasFocus: appIdInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: appIdInput
|
id: appIdInput
|
||||||
@@ -481,7 +409,7 @@ FloatingWindow {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: addTitleBtn.visible ? parent.width - addTitleBtn.width - Theme.spacingS : parent.width
|
width: addTitleBtn.visible ? parent.width - addTitleBtn.width - Theme.spacingS : parent.width
|
||||||
hasFocus: titleInput.activeFocus
|
hasFocus: titleInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -514,7 +442,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionHeader {
|
WindowRuleSectionHeader {
|
||||||
title: I18n.tr("Window Opening")
|
title: I18n.tr("Window Opening")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,24 +450,24 @@ FloatingWindow {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: floatingToggle
|
id: floatingToggle
|
||||||
label: I18n.tr("Float")
|
label: I18n.tr("Float")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: maximizedToggle
|
id: maximizedToggle
|
||||||
label: I18n.tr("Maximize")
|
label: I18n.tr("Maximize")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: fullscreenToggle
|
id: fullscreenToggle
|
||||||
label: I18n.tr("Fullscreen")
|
label: I18n.tr("Fullscreen")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: maximizedToEdgesToggle
|
id: maximizedToEdgesToggle
|
||||||
label: I18n.tr("Max to Edges")
|
label: I18n.tr("Max to Edges")
|
||||||
visible: isNiri
|
visible: isNiri
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: openFocusedToggle
|
id: openFocusedToggle
|
||||||
label: I18n.tr("Focus")
|
label: I18n.tr("Focus")
|
||||||
visible: isNiri
|
visible: isNiri
|
||||||
@@ -563,7 +491,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: outputInput.activeFocus
|
hasFocus: outputInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -590,7 +518,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: workspaceInput.activeFocus
|
hasFocus: workspaceInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -623,7 +551,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: columnWidthInput.activeFocus
|
hasFocus: columnWidthInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -650,7 +578,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: windowHeightInput.activeFocus
|
hasFocus: windowHeightInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -666,7 +594,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionHeader {
|
WindowRuleSectionHeader {
|
||||||
title: I18n.tr("Dynamic Properties")
|
title: I18n.tr("Dynamic Properties")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,7 +602,7 @@ FloatingWindow {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: opacityEnabled
|
id: opacityEnabled
|
||||||
label: I18n.tr("Opacity")
|
label: I18n.tr("Opacity")
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -696,19 +624,19 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
visible: isNiri
|
visible: isNiri
|
||||||
|
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: vrrToggle
|
id: vrrToggle
|
||||||
label: I18n.tr("VRR On-Demand")
|
label: I18n.tr("VRR On-Demand")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: clipToGeometryToggle
|
id: clipToGeometryToggle
|
||||||
label: I18n.tr("Clip to Geometry")
|
label: I18n.tr("Clip to Geometry")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: tiledStateToggle
|
id: tiledStateToggle
|
||||||
label: I18n.tr("Tiled State")
|
label: I18n.tr("Tiled State")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: drawBorderBgToggle
|
id: drawBorderBgToggle
|
||||||
label: I18n.tr("Border with BG")
|
label: I18n.tr("Border with BG")
|
||||||
}
|
}
|
||||||
@@ -769,7 +697,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
visible: isNiri
|
visible: isNiri
|
||||||
|
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: scrollFactorEnabled
|
id: scrollFactorEnabled
|
||||||
label: I18n.tr("Scroll Factor")
|
label: I18n.tr("Scroll Factor")
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -790,7 +718,7 @@ FloatingWindow {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: cornerRadiusEnabled
|
id: cornerRadiusEnabled
|
||||||
label: I18n.tr("Corner Radius")
|
label: I18n.tr("Corner Radius")
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -807,7 +735,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionHeader {
|
WindowRuleSectionHeader {
|
||||||
title: I18n.tr("Size Constraints")
|
title: I18n.tr("Size Constraints")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,7 +755,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: minWidthInput.activeFocus
|
hasFocus: minWidthInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -854,7 +782,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: maxWidthInput.activeFocus
|
hasFocus: maxWidthInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -881,7 +809,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: minHeightInput.activeFocus
|
hasFocus: minHeightInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -908,7 +836,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: maxHeightInput.activeFocus
|
hasFocus: maxHeightInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -924,7 +852,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionHeader {
|
WindowRuleSectionHeader {
|
||||||
title: I18n.tr("Hyprland Options")
|
title: I18n.tr("Hyprland Options")
|
||||||
visible: isHyprland
|
visible: isHyprland
|
||||||
}
|
}
|
||||||
@@ -934,43 +862,43 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
visible: isHyprland
|
visible: isHyprland
|
||||||
|
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: tileToggle
|
id: tileToggle
|
||||||
label: I18n.tr("Tile")
|
label: I18n.tr("Tile")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: noFocusToggle
|
id: noFocusToggle
|
||||||
label: I18n.tr("No Focus")
|
label: I18n.tr("No Focus")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: noBorderToggle
|
id: noBorderToggle
|
||||||
label: I18n.tr("No Border")
|
label: I18n.tr("No Border")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: noShadowToggle
|
id: noShadowToggle
|
||||||
label: I18n.tr("No Shadow")
|
label: I18n.tr("No Shadow")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: noDimToggle
|
id: noDimToggle
|
||||||
label: I18n.tr("No Dim")
|
label: I18n.tr("No Dim")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: noBlurToggle
|
id: noBlurToggle
|
||||||
label: I18n.tr("No Blur")
|
label: I18n.tr("No Blur")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: noAnimToggle
|
id: noAnimToggle
|
||||||
label: I18n.tr("No Anim")
|
label: I18n.tr("No Anim")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: noRoundingToggle
|
id: noRoundingToggle
|
||||||
label: I18n.tr("No Rounding")
|
label: I18n.tr("No Rounding")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: pinToggle
|
id: pinToggle
|
||||||
label: I18n.tr("Pin")
|
label: I18n.tr("Pin")
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
WindowRuleCheckboxRow {
|
||||||
id: opaqueToggle
|
id: opaqueToggle
|
||||||
label: I18n.tr("Opaque")
|
label: I18n.tr("Opaque")
|
||||||
}
|
}
|
||||||
@@ -993,7 +921,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: sizeInput.activeFocus
|
hasFocus: sizeInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -1020,7 +948,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: moveInput.activeFocus
|
hasFocus: moveInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -1053,7 +981,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: monitorInput.activeFocus
|
hasFocus: monitorInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -1080,7 +1008,7 @@ FloatingWindow {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
WindowRuleInputField {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hasFocus: hyprWorkspaceInput.activeFocus
|
hasFocus: hyprWorkspaceInput.activeFocus
|
||||||
DankTextField {
|
DankTextField {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
property string title
|
||||||
|
text: title
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.primary
|
||||||
|
topPadding: Theme.spacingM
|
||||||
|
bottomPadding: Theme.spacingXS
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
@@ -133,7 +133,7 @@ DankPopout {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: modalAdapter
|
id: modalAdapter
|
||||||
property bool spotlightOpen: appDrawerPopout.shouldBeVisible
|
property bool spotlightOpen: appDrawerPopout.shouldBeVisible
|
||||||
property bool isClosing: false
|
readonly property bool isClosing: !appDrawerPopout.shouldBeVisible
|
||||||
|
|
||||||
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.surfaceContainerHigh
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
|
||||||
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.surfaceContainerHighest
|
color: Theme.surfaceLight
|
||||||
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.surfaceContainer
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
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 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: deviceMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
|
||||||
border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
border.color: modelData === AudioService.source ? Theme.primary : Theme.outlineLight
|
||||||
border.width: 0
|
border.width: modelData === AudioService.source ? 2 : 1
|
||||||
|
|
||||||
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 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: deviceMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
|
||||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
border.color: modelData === AudioService.sink ? Theme.primary : Theme.outlineLight
|
||||||
border.width: 0
|
border.width: modelData === AudioService.sink ? 2 : 1
|
||||||
|
|
||||||
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.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: Theme.surfaceLight
|
||||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
border.color: modelData === AudioService.sink ? Theme.primary : Theme.outlineLight
|
||||||
border.width: 0
|
border.width: modelData === AudioService.sink ? 2 : 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
@@ -129,8 +129,9 @@ 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.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: Theme.surfaceLight
|
||||||
border.width: 0
|
border.color: Theme.outlineLight
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -164,8 +165,9 @@ 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.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: Theme.surfaceLight
|
||||||
border.width: 0
|
border.color: Theme.outlineLight
|
||||||
|
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.surfaceContainer
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
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,7 +229,6 @@ 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)
|
||||||
@@ -243,8 +242,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 Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
return Theme.primaryHoverLight;
|
||||||
return Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency);
|
return Theme.surfaceLight;
|
||||||
}
|
}
|
||||||
|
|
||||||
border.color: {
|
border.color: {
|
||||||
@@ -252,8 +251,9 @@ Rectangle {
|
|||||||
return Theme.warning;
|
return Theme.warning;
|
||||||
if (isConnected)
|
if (isConnected)
|
||||||
return Theme.primary;
|
return Theme.primary;
|
||||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12);
|
return Theme.outlineLight;
|
||||||
}
|
}
|
||||||
|
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 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: availableMouseArea.containsMouse && isInteractive ? Theme.primaryHoverLight : Theme.surfaceLight
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
border.color: Theme.outlineLight
|
||||||
border.width: 0
|
border.width: 1
|
||||||
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.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: Theme.surfaceLight
|
||||||
border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
border.color: modelData.mount === currentMountPath ? Theme.primary : Theme.outlineLight
|
||||||
border.width: modelData.mount === currentMountPath ? 2 : 0
|
border.width: modelData.mount === currentMountPath ? 2 : 1
|
||||||
|
|
||||||
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 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: wiredNetworkMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
|
||||||
border.color: Theme.primary
|
border.color: isActive ? Theme.primary : Theme.outlineLight
|
||||||
border.width: 0
|
border.width: isActive ? 2 : 1
|
||||||
|
|
||||||
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 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
color: networkMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
|
||||||
border.color: wifiDelegate.isConnected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
border.color: wifiDelegate.isConnected ? Theme.primary : Theme.outlineLight
|
||||||
border.width: 0
|
border.width: wifiDelegate.isConnected ? 2 : 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: wifiContentRow
|
id: wifiContentRow
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Item {
|
|||||||
property real barThickness: 48
|
property real barThickness: 48
|
||||||
property real barSpacing: 4
|
property real barSpacing: 4
|
||||||
property var barConfig: null
|
property var barConfig: null
|
||||||
|
property var blurBarWindow: null
|
||||||
property bool overrideAxisLayout: false
|
property bool overrideAxisLayout: false
|
||||||
property bool forceVerticalLayout: false
|
property bool forceVerticalLayout: false
|
||||||
|
|
||||||
@@ -357,6 +358,7 @@ Item {
|
|||||||
barThickness: root.barThickness
|
barThickness: root.barThickness
|
||||||
barSpacing: root.barSpacing
|
barSpacing: root.barSpacing
|
||||||
barConfig: root.barConfig
|
barConfig: root.barConfig
|
||||||
|
blurBarWindow: root.blurBarWindow
|
||||||
isFirst: index === 0
|
isFirst: index === 0
|
||||||
isLast: index === centerRepeater.count - 1
|
isLast: index === centerRepeater.count - 1
|
||||||
sectionSpacing: parent.itemSpacing
|
sectionSpacing: parent.itemSpacing
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ Item {
|
|||||||
required property var rootWindow
|
required property var rootWindow
|
||||||
required property var barConfig
|
required property var barConfig
|
||||||
|
|
||||||
|
readonly property var blurBarWindow: barWindow
|
||||||
|
|
||||||
property var leftWidgetsModel
|
property var leftWidgetsModel
|
||||||
property var centerWidgetsModel
|
property var centerWidgetsModel
|
||||||
property var rightWidgetsModel
|
property var rightWidgetsModel
|
||||||
@@ -408,6 +410,12 @@ Item {
|
|||||||
value: topBarContent.barConfig
|
value: topBarContent.barConfig
|
||||||
restoreMode: Binding.RestoreNone
|
restoreMode: Binding.RestoreNone
|
||||||
}
|
}
|
||||||
|
Binding {
|
||||||
|
target: hLeftSection
|
||||||
|
property: "blurBarWindow"
|
||||||
|
value: topBarContent.blurBarWindow
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
|
|
||||||
RightSection {
|
RightSection {
|
||||||
id: hRightSection
|
id: hRightSection
|
||||||
@@ -434,6 +442,12 @@ Item {
|
|||||||
value: topBarContent.barConfig
|
value: topBarContent.barConfig
|
||||||
restoreMode: Binding.RestoreNone
|
restoreMode: Binding.RestoreNone
|
||||||
}
|
}
|
||||||
|
Binding {
|
||||||
|
target: hRightSection
|
||||||
|
property: "blurBarWindow"
|
||||||
|
value: topBarContent.blurBarWindow
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
|
|
||||||
CenterSection {
|
CenterSection {
|
||||||
id: hCenterSection
|
id: hCenterSection
|
||||||
@@ -460,6 +474,12 @@ Item {
|
|||||||
value: topBarContent.barConfig
|
value: topBarContent.barConfig
|
||||||
restoreMode: Binding.RestoreNone
|
restoreMode: Binding.RestoreNone
|
||||||
}
|
}
|
||||||
|
Binding {
|
||||||
|
target: hCenterSection
|
||||||
|
property: "blurBarWindow"
|
||||||
|
value: topBarContent.blurBarWindow
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -493,6 +513,12 @@ Item {
|
|||||||
value: topBarContent.barConfig
|
value: topBarContent.barConfig
|
||||||
restoreMode: Binding.RestoreNone
|
restoreMode: Binding.RestoreNone
|
||||||
}
|
}
|
||||||
|
Binding {
|
||||||
|
target: vLeftSection
|
||||||
|
property: "blurBarWindow"
|
||||||
|
value: topBarContent.blurBarWindow
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
|
|
||||||
CenterSection {
|
CenterSection {
|
||||||
id: vCenterSection
|
id: vCenterSection
|
||||||
@@ -520,6 +546,12 @@ Item {
|
|||||||
value: topBarContent.barConfig
|
value: topBarContent.barConfig
|
||||||
restoreMode: Binding.RestoreNone
|
restoreMode: Binding.RestoreNone
|
||||||
}
|
}
|
||||||
|
Binding {
|
||||||
|
target: vCenterSection
|
||||||
|
property: "blurBarWindow"
|
||||||
|
value: topBarContent.blurBarWindow
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
|
|
||||||
RightSection {
|
RightSection {
|
||||||
id: vRightSection
|
id: vRightSection
|
||||||
@@ -548,6 +580,12 @@ Item {
|
|||||||
value: topBarContent.barConfig
|
value: topBarContent.barConfig
|
||||||
restoreMode: Binding.RestoreNone
|
restoreMode: Binding.RestoreNone
|
||||||
}
|
}
|
||||||
|
Binding {
|
||||||
|
target: vRightSection
|
||||||
|
property: "blurBarWindow"
|
||||||
|
value: topBarContent.blurBarWindow
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,6 +969,7 @@ 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
|
||||||
|
|||||||
@@ -97,6 +97,112 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property var blurRegion: null
|
||||||
|
property var _blurWidgetItems: []
|
||||||
|
|
||||||
|
function registerBlurWidget(item) {
|
||||||
|
if (_blurWidgetItems.indexOf(item) >= 0)
|
||||||
|
return;
|
||||||
|
_blurWidgetItems = _blurWidgetItems.concat([item]);
|
||||||
|
_blurRebuildTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterBlurWidget(item) {
|
||||||
|
const idx = _blurWidgetItems.indexOf(item);
|
||||||
|
if (idx < 0)
|
||||||
|
return;
|
||||||
|
const arr = _blurWidgetItems.slice();
|
||||||
|
arr.splice(idx, 1);
|
||||||
|
_blurWidgetItems = arr;
|
||||||
|
_blurRebuildTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: _blurRebuildTimer
|
||||||
|
interval: 1
|
||||||
|
onTriggered: barBlur.rebuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: barBlur
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
readonly property bool barHasTransparency: barWindow._backgroundAlpha > 0 && barWindow._backgroundAlpha < 1
|
||||||
|
|
||||||
|
function rebuild() {
|
||||||
|
teardown();
|
||||||
|
if (!BlurService.enabled || !BlurService.available)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const widgets = barWindow._blurWidgetItems.filter(w => w && w.visible && w.width > 0 && w.height > 0);
|
||||||
|
const hasBar = barHasTransparency;
|
||||||
|
if (!hasBar && widgets.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const cr = Theme.cornerRadius;
|
||||||
|
let qml = 'import QtQuick; import Quickshell; Region {';
|
||||||
|
for (let i = 0; i < widgets.length; i++) {
|
||||||
|
qml += ` property Item w${i}; Region { item: w${i}; radius: ${cr} }`;
|
||||||
|
}
|
||||||
|
qml += '}';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const region = Qt.createQmlObject(qml, barWindow, "BarBlurRegion");
|
||||||
|
|
||||||
|
if (hasBar) {
|
||||||
|
region.x = Qt.binding(() => topBarMouseArea.x + barUnitInset.x + topBarSlide.x);
|
||||||
|
region.y = Qt.binding(() => topBarMouseArea.y + barUnitInset.y + topBarSlide.y);
|
||||||
|
region.width = Qt.binding(() => barUnitInset.width);
|
||||||
|
region.height = Qt.binding(() => barUnitInset.height);
|
||||||
|
region.radius = Qt.binding(() => barBackground.rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < widgets.length; i++) {
|
||||||
|
region[`w${i}`] = widgets[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
barWindow.BackgroundEffect.blurRegion = region;
|
||||||
|
barWindow.blurRegion = region;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("BarBlur: Failed to create blur region:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function teardown() {
|
||||||
|
if (!barWindow.blurRegion)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
barWindow.BackgroundEffect.blurRegion = null;
|
||||||
|
} catch (e) {}
|
||||||
|
barWindow.blurRegion.destroy();
|
||||||
|
barWindow.blurRegion = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBarHasTransparencyChanged: _blurRebuildTimer.restart()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: BlurService
|
||||||
|
function onEnabledChanged() {
|
||||||
|
barBlur.rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: topBarSlide
|
||||||
|
function onXChanged() {
|
||||||
|
if (barWindow.blurRegion)
|
||||||
|
barWindow.blurRegion.changed();
|
||||||
|
}
|
||||||
|
function onYChanged() {
|
||||||
|
if (barWindow.blurRegion)
|
||||||
|
barWindow.blurRegion.changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: rebuild()
|
||||||
|
Component.onDestruction: teardown()
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.layer: dBarLayer
|
WlrLayershell.layer: dBarLayer
|
||||||
WlrLayershell.namespace: "dms:bar"
|
WlrLayershell.namespace: "dms:bar"
|
||||||
|
|
||||||
@@ -711,7 +817,8 @@ PanelWindow {
|
|||||||
onHasActivePopoutChanged: evaluateReveal()
|
onHasActivePopoutChanged: evaluateReveal()
|
||||||
|
|
||||||
function updateActivePopoutState() {
|
function updateActivePopoutState() {
|
||||||
if (!barWindow.screen) return;
|
if (!barWindow.screen)
|
||||||
|
return;
|
||||||
const screenName = barWindow.screen.name;
|
const screenName = barWindow.screen.name;
|
||||||
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
|
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
|
||||||
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
|
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Item {
|
|||||||
property real barThickness: 48
|
property real barThickness: 48
|
||||||
property real barSpacing: 4
|
property real barSpacing: 4
|
||||||
property var barConfig: null
|
property var barConfig: null
|
||||||
|
property var blurBarWindow: null
|
||||||
property bool overrideAxisLayout: false
|
property bool overrideAxisLayout: false
|
||||||
property bool forceVerticalLayout: false
|
property bool forceVerticalLayout: false
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ Item {
|
|||||||
barThickness: root.barThickness
|
barThickness: root.barThickness
|
||||||
barSpacing: root.barSpacing
|
barSpacing: root.barSpacing
|
||||||
barConfig: root.barConfig
|
barConfig: root.barConfig
|
||||||
|
blurBarWindow: root.blurBarWindow
|
||||||
isFirst: index === 0
|
isFirst: index === 0
|
||||||
isLast: index === rowRepeater.count - 1
|
isLast: index === rowRepeater.count - 1
|
||||||
sectionSpacing: parent.rowSpacing
|
sectionSpacing: parent.rowSpacing
|
||||||
@@ -103,6 +105,7 @@ Item {
|
|||||||
barThickness: root.barThickness
|
barThickness: root.barThickness
|
||||||
barSpacing: root.barSpacing
|
barSpacing: root.barSpacing
|
||||||
barConfig: root.barConfig
|
barConfig: root.barConfig
|
||||||
|
blurBarWindow: root.blurBarWindow
|
||||||
isFirst: index === 0
|
isFirst: index === 0
|
||||||
isLast: index === columnRepeater.count - 1
|
isLast: index === columnRepeater.count - 1
|
||||||
sectionSpacing: parent.columnSpacing
|
sectionSpacing: parent.columnSpacing
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Item {
|
|||||||
property real barThickness: 48
|
property real barThickness: 48
|
||||||
property real barSpacing: 4
|
property real barSpacing: 4
|
||||||
property var barConfig: null
|
property var barConfig: null
|
||||||
|
property var blurBarWindow: null
|
||||||
property bool overrideAxisLayout: false
|
property bool overrideAxisLayout: false
|
||||||
property bool forceVerticalLayout: false
|
property bool forceVerticalLayout: false
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ Item {
|
|||||||
barThickness: root.barThickness
|
barThickness: root.barThickness
|
||||||
barSpacing: root.barSpacing
|
barSpacing: root.barSpacing
|
||||||
barConfig: root.barConfig
|
barConfig: root.barConfig
|
||||||
|
blurBarWindow: root.blurBarWindow
|
||||||
isFirst: index === 0
|
isFirst: index === 0
|
||||||
isLast: index === rowRepeater.count - 1
|
isLast: index === rowRepeater.count - 1
|
||||||
sectionSpacing: parent.rowSpacing
|
sectionSpacing: parent.rowSpacing
|
||||||
@@ -105,6 +107,7 @@ Item {
|
|||||||
barThickness: root.barThickness
|
barThickness: root.barThickness
|
||||||
barSpacing: root.barSpacing
|
barSpacing: root.barSpacing
|
||||||
barConfig: root.barConfig
|
barConfig: root.barConfig
|
||||||
|
blurBarWindow: root.blurBarWindow
|
||||||
isFirst: index === 0
|
isFirst: index === 0
|
||||||
isLast: index === columnRepeater.count - 1
|
isLast: index === columnRepeater.count - 1
|
||||||
sectionSpacing: parent.columnSpacing
|
sectionSpacing: parent.columnSpacing
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Loader {
|
|||||||
property real barThickness: 48
|
property real barThickness: 48
|
||||||
property real barSpacing: 4
|
property real barSpacing: 4
|
||||||
property var barConfig: null
|
property var barConfig: null
|
||||||
|
property var blurBarWindow: null
|
||||||
property bool isFirst: false
|
property bool isFirst: false
|
||||||
property bool isLast: false
|
property bool isLast: false
|
||||||
property real sectionSpacing: 0
|
property real sectionSpacing: 0
|
||||||
@@ -92,6 +93,14 @@ Loader {
|
|||||||
restoreMode: Binding.RestoreNone
|
restoreMode: Binding.RestoreNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: root.item
|
||||||
|
when: root.item && "blurBarWindow" in root.item
|
||||||
|
property: "blurBarWindow"
|
||||||
|
value: root.blurBarWindow
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
target: root.item
|
target: root.item
|
||||||
when: root.item && "axis" in root.item
|
when: root.item && "axis" in root.item
|
||||||
|
|||||||
@@ -630,7 +630,7 @@ BasePill {
|
|||||||
if (appItem.isFocused && colorizeEnabled) {
|
if (appItem.isFocused && colorizeEnabled) {
|
||||||
return mouseArea.containsMouse ? Theme.withAlpha(Qt.lighter(appItem.activeOverlayColor, 1.3), 0.4) : Theme.withAlpha(appItem.activeOverlayColor, 0.3);
|
return mouseArea.containsMouse ? Theme.withAlpha(Qt.lighter(appItem.activeOverlayColor, 1.3), 0.4) : Theme.withAlpha(appItem.activeOverlayColor, 0.3);
|
||||||
}
|
}
|
||||||
return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent";
|
return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
|
||||||
}
|
}
|
||||||
|
|
||||||
border.width: dragHandler.dragging ? 2 : 0
|
border.width: dragHandler.dragging ? 2 : 0
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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
|
||||||
@@ -10,6 +11,8 @@ 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":
|
||||||
@@ -119,5 +122,44 @@ 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Quickshell
|
|||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modules.Plugins
|
import qs.Modules.Plugins
|
||||||
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
BasePill {
|
BasePill {
|
||||||
@@ -93,6 +94,15 @@ BasePill {
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: contextMenuWindow
|
id: contextMenuWindow
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: contextMenuWindow
|
||||||
|
blurX: menuContainer.x
|
||||||
|
blurY: menuContainer.y
|
||||||
|
blurWidth: contextMenuWindow.visible ? menuContainer.width : 0
|
||||||
|
blurHeight: contextMenuWindow.visible ? menuContainer.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:clipboard-context-menu"
|
WlrLayershell.namespace: "dms:clipboard-context-menu"
|
||||||
|
|
||||||
property bool isVertical: false
|
property bool isVertical: false
|
||||||
@@ -187,8 +197,8 @@ BasePill {
|
|||||||
height: Math.max(64, menuColumn.implicitHeight + Theme.spacingS * 2)
|
height: Math.max(64, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 1
|
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||||
|
|
||||||
opacity: contextMenuWindow.visible ? 1 : 0
|
opacity: contextMenuWindow.visible ? 1 : 0
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
@@ -224,7 +234,7 @@ BasePill {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: clearAllArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: clearAllArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -264,7 +274,7 @@ BasePill {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: savedItemsArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: savedItemsArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -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: "88%"
|
text: "100%"
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
|
property Toplevel activeWindow: null
|
||||||
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,10 +38,44 @@ 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() {
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ 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 int textWidth: {
|
readonly property bool adaptiveWidthEnabled: SettingsData.mediaAdaptiveWidthEnabled
|
||||||
|
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:
|
||||||
@@ -36,10 +37,7 @@ BasePill {
|
|||||||
if (isVerticalOrientation) {
|
if (isVerticalOrientation) {
|
||||||
return widgetThickness - horizontalPadding * 2;
|
return widgetThickness - horizontalPadding * 2;
|
||||||
}
|
}
|
||||||
const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20;
|
return 0;
|
||||||
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) {
|
||||||
@@ -99,7 +97,7 @@ BasePill {
|
|||||||
|
|
||||||
if (isMouseWheelY) {
|
if (isMouseWheelY) {
|
||||||
if (deltaY > 0) {
|
if (deltaY > 0) {
|
||||||
activePlayer.previous();
|
MprisController.previousOrRewind();
|
||||||
} else {
|
} else {
|
||||||
activePlayer.next();
|
activePlayer.next();
|
||||||
}
|
}
|
||||||
@@ -107,7 +105,7 @@ BasePill {
|
|||||||
scrollAccumulatorY += deltaY;
|
scrollAccumulatorY += deltaY;
|
||||||
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
|
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
|
||||||
if (scrollAccumulatorY > 0) {
|
if (scrollAccumulatorY > 0) {
|
||||||
activePlayer.previous();
|
MprisController.previousOrRewind();
|
||||||
} else {
|
} else {
|
||||||
activePlayer.next();
|
activePlayer.next();
|
||||||
}
|
}
|
||||||
@@ -119,7 +117,28 @@ BasePill {
|
|||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Item {
|
Item {
|
||||||
implicitWidth: root.playerAvailable ? root.currentContentWidth : 0
|
id: contentRoot
|
||||||
|
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
|
||||||
|
|
||||||
@@ -132,8 +151,9 @@ BasePill {
|
|||||||
|
|
||||||
Behavior on implicitWidth {
|
Behavior on implicitWidth {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.shortDuration
|
duration: Theme.mediumDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +234,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) {
|
||||||
activePlayer.previous();
|
MprisController.previousOrRewind();
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
activePlayer.next();
|
activePlayer.next();
|
||||||
}
|
}
|
||||||
@@ -269,7 +289,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: textWidth
|
width: contentRoot.measuredTextWidth
|
||||||
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;
|
||||||
@@ -278,50 +298,95 @@ BasePill {
|
|||||||
clip: true
|
clip: true
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
StyledText {
|
Behavior on width {
|
||||||
id: mediaText
|
NumberAnimation {
|
||||||
property bool needsScrolling: implicitWidth > textContainer.width && SettingsData.scrollTitleEnabled
|
duration: Theme.mediumDuration
|
||||||
property real scrollOffset: 0
|
easing.type: Easing.BezierSpline
|
||||||
|
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();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SequentialAnimation {
|
Item {
|
||||||
id: scrollAnimation
|
id: textClip
|
||||||
running: mediaText.needsScrolling && textContainer.visible
|
anchors.fill: parent
|
||||||
loops: Animation.Infinite
|
clip: true
|
||||||
|
|
||||||
PauseAnimation {
|
StyledText {
|
||||||
duration: 2000
|
id: mediaText
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
SequentialAnimation {
|
||||||
target: mediaText
|
id: scrollAnimation
|
||||||
property: "scrollOffset"
|
running: mediaText.needsScrolling && textContainer.visible
|
||||||
from: 0
|
loops: Animation.Infinite
|
||||||
to: mediaText.implicitWidth - textContainer.width + 5
|
|
||||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
PauseAnimation {
|
||||||
easing.type: Easing.Linear
|
duration: 2000
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PauseAnimation {
|
SequentialAnimation {
|
||||||
duration: 2000
|
id: textChangeAnimation
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
ParallelAnimation {
|
||||||
target: mediaText
|
NumberAnimation {
|
||||||
property: "scrollOffset"
|
target: mediaText
|
||||||
to: 0
|
property: "opacity"
|
||||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
from: 0.7
|
||||||
easing.type: Easing.Linear
|
to: 1
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,7 +419,7 @@ BasePill {
|
|||||||
height: 20
|
height: 20
|
||||||
radius: 10
|
radius: 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
color: prevArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: prevArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
visible: root.playerAvailable
|
visible: root.playerAvailable
|
||||||
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
|
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
|
||||||
|
|
||||||
@@ -370,11 +435,7 @@ BasePill {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.playerAvailable
|
enabled: root.playerAvailable
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: MprisController.previousOrRewind()
|
||||||
if (activePlayer) {
|
|
||||||
activePlayer.previous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +472,7 @@ BasePill {
|
|||||||
height: 20
|
height: 20
|
||||||
radius: 10
|
radius: 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
color: nextArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: nextArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
visible: playerAvailable
|
visible: playerAvailable
|
||||||
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
|
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
|
||||||
|
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ BasePill {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: tabArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: tabArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -327,7 +327,7 @@ BasePill {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: newNoteArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: newNoteArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ BasePill {
|
|||||||
if (isFocused) {
|
if (isFocused) {
|
||||||
return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2);
|
return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2);
|
||||||
}
|
}
|
||||||
return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent";
|
return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
|
||||||
}
|
}
|
||||||
|
|
||||||
// App icon
|
// App icon
|
||||||
@@ -528,7 +528,7 @@ BasePill {
|
|||||||
if (isFocused) {
|
if (isFocused) {
|
||||||
return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2);
|
return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2);
|
||||||
}
|
}
|
||||||
return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent";
|
return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
|
||||||
}
|
}
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
@@ -738,6 +738,15 @@ BasePill {
|
|||||||
sourceComponent: PanelWindow {
|
sourceComponent: PanelWindow {
|
||||||
id: contextMenuWindow
|
id: contextMenuWindow
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: contextMenuWindow
|
||||||
|
blurX: contextMenuRect.x
|
||||||
|
blurY: contextMenuRect.y
|
||||||
|
blurWidth: contextMenuWindow.isVisible ? contextMenuRect.width : 0
|
||||||
|
blurHeight: contextMenuWindow.isVisible ? contextMenuRect.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
property var currentWindow: null
|
property var currentWindow: null
|
||||||
property bool isVisible: false
|
property bool isVisible: false
|
||||||
property point anchorPos: Qt.point(0, 0)
|
property point anchorPos: Qt.point(0, 0)
|
||||||
@@ -830,6 +839,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: contextMenuRect
|
||||||
x: {
|
x: {
|
||||||
if (contextMenuWindow.isVertical) {
|
if (contextMenuWindow.isVertical) {
|
||||||
if (contextMenuWindow.edge === "left") {
|
if (contextMenuWindow.edge === "left") {
|
||||||
@@ -858,13 +868,13 @@ BasePill {
|
|||||||
height: 32
|
height: 32
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.width: 1
|
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
color: closeMouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: closeMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ 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()) : [];
|
||||||
@@ -40,6 +43,76 @@ 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");
|
||||||
@@ -78,6 +151,13 @@ 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)
|
||||||
@@ -103,6 +183,7 @@ 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
|
||||||
|
|
||||||
@@ -198,10 +279,11 @@ 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.mainBarItems
|
values: root.displayedMainBarItems
|
||||||
objectProp: "key"
|
objectProp: "key"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,29 +291,7 @@ 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: {
|
property string iconSource: root.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 "";
|
|
||||||
}
|
|
||||||
|
|
||||||
width: root.trayItemSize
|
width: root.trayItemSize
|
||||||
height: root.barThickness
|
height: root.barThickness
|
||||||
@@ -287,7 +347,7 @@ BasePill {
|
|||||||
height: root.trayItemSize
|
height: root.trayItemSize
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: trayItemArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
border.width: dragHandler.dragging ? 2 : 0
|
border.width: dragHandler.dragging ? 2 : 0
|
||||||
border.color: Theme.primary
|
border.color: Theme.primary
|
||||||
opacity: dragHandler.dragging ? 0.8 : 1.0
|
opacity: dragHandler.dragging ? 0.8 : 1.0
|
||||||
@@ -371,7 +431,8 @@ BasePill {
|
|||||||
}
|
}
|
||||||
if (!delegateRoot.trayItem.hasMenu)
|
if (!delegateRoot.trayItem.hasMenu)
|
||||||
return;
|
return;
|
||||||
root.menuOpen = false;
|
if (root.useOverflowPopup)
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,8 +441,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 = index;
|
root.draggedIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - index) : index;
|
||||||
root.dropTargetIndex = index;
|
root.dropTargetIndex = root.draggedIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!dragHandler.dragging)
|
if (!dragHandler.dragging)
|
||||||
@@ -391,7 +452,8 @@ 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 newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
|
const visualTargetIndex = 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;
|
||||||
}
|
}
|
||||||
@@ -407,7 +469,8 @@ 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;
|
||||||
}
|
}
|
||||||
root.menuOpen = false;
|
if (root.useOverflowPopup)
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,11 +488,11 @@ BasePill {
|
|||||||
height: root.trayItemSize
|
height: root.trayItemSize
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: caretArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: caretArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: root.menuOpen ? "expand_less" : "expand_more"
|
name: root.toggleIconName()
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -451,6 +514,301 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,219 +817,23 @@ 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.mainBarItems
|
values: root.reverseInlineVertical ? [] : root.displayedMainBarItems
|
||||||
objectProp: "key"
|
objectProp: "key"
|
||||||
}
|
}
|
||||||
|
delegate: verticalMainTrayItemDelegate
|
||||||
|
}
|
||||||
|
|
||||||
delegate: Item {
|
Repeater {
|
||||||
id: delegateRoot
|
model: ScriptModel {
|
||||||
property var trayItem: modelData.item
|
values: root.reverseInlineVertical ? root.displayedInlineExpandedItems : []
|
||||||
property string itemKey: modelData.key
|
objectProp: "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 ? 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 {
|
||||||
@@ -685,18 +847,11 @@ BasePill {
|
|||||||
height: root.trayItemSize
|
height: root.trayItemSize
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: caretAreaVert.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
color: caretAreaVert.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: {
|
name: root.toggleIconName()
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -718,12 +873,38 @@ 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: overflowMenu
|
id: overflowMenu
|
||||||
visible: root.menuOpen
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: overflowMenu
|
||||||
|
blurX: menuContainer.x
|
||||||
|
blurY: menuContainer.y
|
||||||
|
blurWidth: root.menuOpen ? menuContainer.width : 0
|
||||||
|
blurHeight: root.menuOpen ? menuContainer.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: root.useOverflowPopup && root.menuOpen
|
||||||
screen: root.parentScreen
|
screen: root.parentScreen
|
||||||
WlrLayershell.layer: WlrLayershell.Top
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
@@ -739,13 +920,14 @@ BasePill {
|
|||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: [overflowMenu]
|
windows: [overflowMenu]
|
||||||
active: CompositorService.useHyprlandFocusGrab && root.menuOpen
|
active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: PopoutManager
|
target: PopoutManager
|
||||||
function onPopoutOpening() {
|
function onPopoutOpening() {
|
||||||
root.menuOpen = false;
|
if (root.useOverflowPopup)
|
||||||
|
root.menuOpen = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -990,6 +1172,15 @@ BasePill {
|
|||||||
layer.samples: 4
|
layer.samples: 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "transparent"
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
Grid {
|
Grid {
|
||||||
id: menuGrid
|
id: menuGrid
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -1002,35 +1193,12 @@ BasePill {
|
|||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
property var trayItem: modelData
|
property var trayItem: modelData
|
||||||
property string iconSource: {
|
property string iconSource: root.trayIconSourceFor(trayItem)
|
||||||
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
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
|
color: itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
id: menuIconImg
|
id: menuIconImg
|
||||||
@@ -1191,6 +1359,15 @@ BasePill {
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: menuWindow
|
id: menuWindow
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: menuWindow
|
||||||
|
blurX: trayMenuContainer.x
|
||||||
|
blurY: trayMenuContainer.y
|
||||||
|
blurWidth: menuRoot.showMenu ? trayMenuContainer.width : 0
|
||||||
|
blurHeight: menuRoot.showMenu ? trayMenuContainer.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:tray-menu-window"
|
WlrLayershell.namespace: "dms:tray-menu-window"
|
||||||
visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
|
visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
|
||||||
WlrLayershell.layer: WlrLayershell.Top
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
@@ -1285,7 +1462,8 @@ BasePill {
|
|||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
updatePosition();
|
updatePosition();
|
||||||
root.menuOpen = false;
|
if (root.useOverflowPopup)
|
||||||
|
root.menuOpen = false;
|
||||||
PopoutManager.closeAllPopouts();
|
PopoutManager.closeAllPopouts();
|
||||||
ModalManager.closeAllModalsExcept(null);
|
ModalManager.closeAllModalsExcept(null);
|
||||||
}
|
}
|
||||||
@@ -1302,7 +1480,7 @@ BasePill {
|
|||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
const clickX = mouse.x + menuWindow.maskX;
|
const clickX = mouse.x + menuWindow.maskX;
|
||||||
const clickY = mouse.y + menuWindow.maskY;
|
const clickY = mouse.y + menuWindow.maskY;
|
||||||
const outsideContent = clickX < menuContainer.x || clickX > menuContainer.x + menuContainer.width || clickY < menuContainer.y || clickY > menuContainer.y + menuContainer.height;
|
const outsideContent = clickX < trayMenuContainer.x || clickX > trayMenuContainer.x + trayMenuContainer.width || clickY < trayMenuContainer.y || clickY > trayMenuContainer.y + trayMenuContainer.height;
|
||||||
|
|
||||||
if (!outsideContent)
|
if (!outsideContent)
|
||||||
return;
|
return;
|
||||||
@@ -1360,7 +1538,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: menuContainer
|
id: trayMenuContainer
|
||||||
|
|
||||||
readonly property real rawWidth: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2))
|
readonly property real rawWidth: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||||
readonly property real rawHeight: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2)
|
readonly property real rawHeight: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||||
@@ -1438,6 +1616,15 @@ BasePill {
|
|||||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "transparent"
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
QsMenuAnchor {
|
QsMenuAnchor {
|
||||||
id: submenuHydrator
|
id: submenuHydrator
|
||||||
anchor.window: menuWindow
|
anchor.window: menuWindow
|
||||||
@@ -1470,7 +1657,7 @@ BasePill {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 28
|
height: 28
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: visibilityToggleArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
|
color: visibilityToggleArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -1523,7 +1710,7 @@ BasePill {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 28
|
height: 28
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: backArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
|
color: backArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -1574,7 +1761,7 @@ BasePill {
|
|||||||
color: {
|
color: {
|
||||||
if (menuEntry?.isSeparator)
|
if (menuEntry?.isSeparator)
|
||||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
|
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
|
||||||
return itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0);
|
return itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -17,8 +17,49 @@ Item {
|
|||||||
property real widgetHeight: 30
|
property real widgetHeight: 30
|
||||||
property real barThickness: 48
|
property real barThickness: 48
|
||||||
property var barConfig: null
|
property var barConfig: 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);
|
||||||
@@ -538,6 +579,60 @@ 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();
|
||||||
@@ -751,8 +846,15 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
id: edgeMouseArea
|
||||||
acceptedButtons: Qt.RightButton
|
z: -1
|
||||||
|
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
|
||||||
@@ -765,12 +867,20 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
if (mouse.button === Qt.RightButton) {
|
const rootPos = edgeMouseArea.mapToItem(root, mouse.x, mouse.y);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1845,5 +1955,27 @@ Item {
|
|||||||
if (useExtWorkspace && !DMSService.activeSubscriptions.includes("extworkspace")) {
|
if (useExtWorkspace && !DMSService.activeSubscriptions.includes("extworkspace")) {
|
||||||
DMSService.addSubscription("extworkspace");
|
DMSService.addSubscription("extworkspace");
|
||||||
}
|
}
|
||||||
|
_updateBlurRegistration();
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool _blurRegistered: false
|
||||||
|
readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0
|
||||||
|
|
||||||
|
on_ShouldBlurChanged: _updateBlurRegistration()
|
||||||
|
|
||||||
|
function _updateBlurRegistration() {
|
||||||
|
if (_shouldBlur && !_blurRegistered) {
|
||||||
|
blurBarWindow.registerBlurWidget(visualBackground);
|
||||||
|
_blurRegistered = true;
|
||||||
|
} else if (!_shouldBlur && _blurRegistered) {
|
||||||
|
if (blurBarWindow && blurBarWindow.unregisterBlurWidget)
|
||||||
|
blurBarWindow.unregisterBlurWidget(visualBackground);
|
||||||
|
_blurRegistered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget)
|
||||||
|
blurBarWindow.unregisterBlurWidget(visualBackground);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -487,17 +487,7 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: MprisController.previousOrRewind()
|
||||||
if (!activePlayer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activePlayer.position > 8 && activePlayer.canSeek) {
|
|
||||||
activePlayer.position = 0;
|
|
||||||
} else {
|
|
||||||
activePlayer.previous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,14 +145,7 @@ Card {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: MprisController.previousOrRewind()
|
||||||
if (!activePlayer) return
|
|
||||||
if (activePlayer.position > 8 && activePlayer.canSeek) {
|
|
||||||
activePlayer.position = 0
|
|
||||||
} else {
|
|
||||||
activePlayer.previous()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ Variants {
|
|||||||
delegate: PanelWindow {
|
delegate: PanelWindow {
|
||||||
id: dock
|
id: dock
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: dock
|
||||||
|
blurX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x
|
||||||
|
blurY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y
|
||||||
|
blurWidth: dock.hasApps && dock.reveal ? dockBackground.width : 0
|
||||||
|
blurHeight: dock.hasApps && dock.reveal ? dockBackground.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:dock"
|
WlrLayershell.namespace: "dms:dock"
|
||||||
|
|
||||||
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
||||||
@@ -562,6 +571,15 @@ Variants {
|
|||||||
color: Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "transparent"
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape {
|
Shape {
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ import qs.Widgets
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: root
|
||||||
|
blurX: menuContainer.x
|
||||||
|
blurY: menuContainer.y
|
||||||
|
blurWidth: root.visible ? menuContainer.width : 0
|
||||||
|
blurHeight: root.visible ? menuContainer.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:dock-context-menu"
|
WlrLayershell.namespace: "dms:dock-context-menu"
|
||||||
|
|
||||||
property var appData: null
|
property var appData: null
|
||||||
@@ -168,8 +177,8 @@ PanelWindow {
|
|||||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 1
|
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||||
|
|
||||||
opacity: root.visible ? 1 : 0
|
opacity: root.visible ? 1 : 0
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
|
|||||||
@@ -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.activePlayer?.previous()
|
onClicked: MprisController.previousOrRewind()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Services.Notifications
|
import Quickshell.Services.Notifications
|
||||||
@@ -11,6 +10,15 @@ import qs.Widgets
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: win
|
id: win
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: win
|
||||||
|
blurX: content.x + content.cardInset + swipeTx.x + tx.x
|
||||||
|
blurY: content.y + content.cardInset + swipeTx.y + tx.y
|
||||||
|
blurWidth: !win._finalized ? Math.max(0, content.width - content.cardInset * 2) : 0
|
||||||
|
blurHeight: !win._finalized ? Math.max(0, content.height - content.cardInset * 2) : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:notification-popup"
|
WlrLayershell.namespace: "dms:notification-popup"
|
||||||
|
|
||||||
required property var notificationData
|
required property var notificationData
|
||||||
@@ -436,6 +444,16 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: content.cardInset
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: backgroundContainer
|
id: backgroundContainer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Item {
|
|||||||
property real barThickness: 48
|
property real barThickness: 48
|
||||||
property real barSpacing: 4
|
property real barSpacing: 4
|
||||||
property var barConfig: null
|
property var barConfig: null
|
||||||
|
property var blurBarWindow: null
|
||||||
property alias content: contentLoader.sourceComponent
|
property alias content: contentLoader.sourceComponent
|
||||||
property bool isVerticalOrientation: axis?.isVertical ?? false
|
property bool isVerticalOrientation: axis?.isVertical ?? false
|
||||||
property bool isFirst: false
|
property bool isFirst: false
|
||||||
@@ -106,7 +107,7 @@ Item {
|
|||||||
const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
|
const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
|
||||||
const isHovered = root.enableBackgroundHover && (mouseArea.containsMouse || (root.isHovered || false));
|
const isHovered = root.enableBackgroundHover && (mouseArea.containsMouse || (root.isHovered || false));
|
||||||
const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency;
|
const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency;
|
||||||
const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
const baseColor = isHovered ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.widgetBaseBackgroundColor;
|
||||||
|
|
||||||
if (Theme.widgetBackgroundHasAlpha) {
|
if (Theme.widgetBackgroundHasAlpha) {
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
|
||||||
@@ -169,4 +170,26 @@ Item {
|
|||||||
root.wheel(wheelEvent);
|
root.wheel(wheelEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool _blurRegistered: false
|
||||||
|
readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0
|
||||||
|
|
||||||
|
on_ShouldBlurChanged: _updateBlurRegistration()
|
||||||
|
|
||||||
|
function _updateBlurRegistration() {
|
||||||
|
if (_shouldBlur && !_blurRegistered) {
|
||||||
|
blurBarWindow.registerBlurWidget(visualContent);
|
||||||
|
_blurRegistered = true;
|
||||||
|
} else if (!_shouldBlur && _blurRegistered) {
|
||||||
|
if (blurBarWindow && blurBarWindow.unregisterBlurWidget)
|
||||||
|
blurBarWindow.unregisterBlurWidget(visualContent);
|
||||||
|
_blurRegistered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: _updateBlurRegistration()
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget)
|
||||||
|
blurBarWindow.unregisterBlurWidget(visualContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Item {
|
|||||||
property real barThickness: 48
|
property real barThickness: 48
|
||||||
property real barSpacing: 4
|
property real barSpacing: 4
|
||||||
property var barConfig: null
|
property var barConfig: null
|
||||||
|
property var blurBarWindow: null
|
||||||
property string pluginId: ""
|
property string pluginId: ""
|
||||||
property var pluginService: null
|
property var pluginService: null
|
||||||
|
|
||||||
@@ -182,6 +183,7 @@ Item {
|
|||||||
barThickness: root.barThickness
|
barThickness: root.barThickness
|
||||||
barSpacing: root.barSpacing
|
barSpacing: root.barSpacing
|
||||||
barConfig: root.barConfig
|
barConfig: root.barConfig
|
||||||
|
blurBarWindow: root.blurBarWindow
|
||||||
content: root.horizontalBarPill
|
content: root.horizontalBarPill
|
||||||
|
|
||||||
states: State {
|
states: State {
|
||||||
@@ -241,6 +243,7 @@ Item {
|
|||||||
barThickness: root.barThickness
|
barThickness: root.barThickness
|
||||||
barSpacing: root.barSpacing
|
barSpacing: root.barSpacing
|
||||||
barConfig: root.barConfig
|
barConfig: root.barConfig
|
||||||
|
blurBarWindow: root.blurBarWindow
|
||||||
content: root.verticalBarPill
|
content: root.verticalBarPill
|
||||||
isVerticalOrientation: true
|
isVerticalOrientation: true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: gaugeRoot
|
||||||
|
|
||||||
|
property real value: 0
|
||||||
|
property string label: ""
|
||||||
|
property string sublabel: ""
|
||||||
|
property string detail: ""
|
||||||
|
property color accentColor: Theme.primary
|
||||||
|
property color detailColor: Theme.surfaceVariantText
|
||||||
|
|
||||||
|
readonly property real thickness: Math.max(4, Math.min(width, height) / 15)
|
||||||
|
readonly property real glowExtra: thickness * 1.4
|
||||||
|
readonly property real arcPadding: (thickness + glowExtra) / 2
|
||||||
|
|
||||||
|
readonly property real innerDiameter: width - (arcPadding + thickness + glowExtra) * 2
|
||||||
|
readonly property real maxTextWidth: innerDiameter * 0.9
|
||||||
|
readonly property real baseLabelSize: Math.round(width * 0.18)
|
||||||
|
readonly property real labelSize: Math.round(Math.min(baseLabelSize, maxTextWidth / Math.max(1, label.length * 0.65)))
|
||||||
|
readonly property real sublabelSize: Math.round(Math.min(width * 0.13, maxTextWidth / Math.max(1, sublabel.length * 0.7)))
|
||||||
|
readonly property real detailSize: Math.round(Math.min(width * 0.12, maxTextWidth / Math.max(1, detail.length * 0.65)))
|
||||||
|
|
||||||
|
property real animValue: 0
|
||||||
|
|
||||||
|
onValueChanged: animValue = Math.min(1, Math.max(0, value))
|
||||||
|
|
||||||
|
Behavior on animValue {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: animValue = Math.min(1, Math.max(0, value))
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: glowCanvas
|
||||||
|
anchors.fill: parent
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d");
|
||||||
|
ctx.reset();
|
||||||
|
const cx = width / 2;
|
||||||
|
const cy = height / 2;
|
||||||
|
const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding;
|
||||||
|
const startAngle = -Math.PI * 0.5;
|
||||||
|
const endAngle = Math.PI * 1.5;
|
||||||
|
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
|
||||||
|
if (gaugeRoot.animValue > 0) {
|
||||||
|
const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, radius, startAngle, prog);
|
||||||
|
ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.2);
|
||||||
|
ctx.lineWidth = gaugeRoot.thickness + gaugeRoot.glowExtra;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: gaugeRoot
|
||||||
|
function onAnimValueChanged() {
|
||||||
|
glowCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
function onAccentColorChanged() {
|
||||||
|
glowCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
function onWidthChanged() {
|
||||||
|
glowCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
function onHeightChanged() {
|
||||||
|
glowCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: requestPaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: arcCanvas
|
||||||
|
anchors.fill: parent
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d");
|
||||||
|
ctx.reset();
|
||||||
|
const cx = width / 2;
|
||||||
|
const cy = height / 2;
|
||||||
|
const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding;
|
||||||
|
const startAngle = -Math.PI * 0.5;
|
||||||
|
const endAngle = Math.PI * 1.5;
|
||||||
|
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, radius, startAngle, endAngle);
|
||||||
|
ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.1);
|
||||||
|
ctx.lineWidth = gaugeRoot.thickness;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
if (gaugeRoot.animValue > 0) {
|
||||||
|
const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, radius, startAngle, prog);
|
||||||
|
ctx.strokeStyle = gaugeRoot.accentColor;
|
||||||
|
ctx.lineWidth = gaugeRoot.thickness;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: gaugeRoot
|
||||||
|
function onAnimValueChanged() {
|
||||||
|
arcCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
function onAccentColorChanged() {
|
||||||
|
arcCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
function onWidthChanged() {
|
||||||
|
arcCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
function onHeightChanged() {
|
||||||
|
arcCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: requestPaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: gaugeRoot.label
|
||||||
|
font.pixelSize: gaugeRoot.labelSize
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: gaugeRoot.sublabel
|
||||||
|
font.pixelSize: gaugeRoot.sublabelSize
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: gaugeRoot.accentColor
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: gaugeRoot.detail
|
||||||
|
font.pixelSize: gaugeRoot.detailSize
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: gaugeRoot.detailColor
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: gaugeRoot.detail.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
property string label: ""
|
||||||
|
property string value: ""
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: label + ":"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: value
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: card
|
||||||
|
|
||||||
|
property string title: ""
|
||||||
|
property string icon: ""
|
||||||
|
property string value: ""
|
||||||
|
property string subtitle: ""
|
||||||
|
property color accentColor: Theme.primary
|
||||||
|
property var history: []
|
||||||
|
property var history2: null
|
||||||
|
property real maxValue: 100
|
||||||
|
property bool showSecondary: false
|
||||||
|
property string extraInfo: ""
|
||||||
|
property color extraInfoColor: Theme.surfaceVariantText
|
||||||
|
property int historySize: 60
|
||||||
|
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
border.color: Theme.outlineLight
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: graphCanvas
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 4
|
||||||
|
renderStrategy: Canvas.Cooperative
|
||||||
|
|
||||||
|
property var hist: card.history
|
||||||
|
property var hist2: card.history2
|
||||||
|
|
||||||
|
onHistChanged: requestPaint()
|
||||||
|
onHist2Changed: requestPaint()
|
||||||
|
onWidthChanged: requestPaint()
|
||||||
|
onHeightChanged: requestPaint()
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d");
|
||||||
|
ctx.reset();
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
if (!hist || hist.length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let max = card.maxValue;
|
||||||
|
if (max <= 0) {
|
||||||
|
max = 1;
|
||||||
|
for (let k = 0; k < hist.length; k++)
|
||||||
|
max = Math.max(max, hist[k]);
|
||||||
|
if (hist2) {
|
||||||
|
for (let l = 0; l < hist2.length; l++)
|
||||||
|
max = Math.max(max, hist2[l]);
|
||||||
|
}
|
||||||
|
max *= 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const c = card.accentColor;
|
||||||
|
const grad = ctx.createLinearGradient(0, 0, 0, height);
|
||||||
|
grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.25));
|
||||||
|
grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.02));
|
||||||
|
|
||||||
|
ctx.fillStyle = grad;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, height);
|
||||||
|
for (let i = 0; i < hist.length; i++) {
|
||||||
|
const x = (width / (card.historySize - 1)) * i;
|
||||||
|
const y = height - (hist[i] / max) * height * 0.8;
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
ctx.lineTo((width / (card.historySize - 1)) * (hist.length - 1), height);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.8);
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let j = 0; j < hist.length; j++) {
|
||||||
|
const px = (width / (card.historySize - 1)) * j;
|
||||||
|
const py = height - (hist[j] / max) * height * 0.8;
|
||||||
|
j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
if (hist2 && hist2.length >= 2 && card.showSecondary) {
|
||||||
|
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.4);
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.setLineDash([4, 4]);
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let m = 0; m < hist2.length; m++) {
|
||||||
|
const sx = (width / (card.historySize - 1)) * m;
|
||||||
|
const sy = height - (hist2[m] / max) * height * 0.8;
|
||||||
|
m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: card.icon
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: card.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: card.title
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: card.extraInfo
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: card.extraInfoColor
|
||||||
|
visible: card.extraInfo.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: card.value
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: card.subtitle
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ import QtQuick.Layouts
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -73,6 +72,7 @@ Item {
|
|||||||
PerformanceCard {
|
PerformanceCard {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
historySize: root.historySize
|
||||||
title: "CPU"
|
title: "CPU"
|
||||||
icon: "memory"
|
icon: "memory"
|
||||||
value: DgopService.cpuUsage.toFixed(1) + "%"
|
value: DgopService.cpuUsage.toFixed(1) + "%"
|
||||||
@@ -88,6 +88,7 @@ Item {
|
|||||||
PerformanceCard {
|
PerformanceCard {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
historySize: root.historySize
|
||||||
title: I18n.tr("Memory")
|
title: I18n.tr("Memory")
|
||||||
icon: "sd_card"
|
icon: "sd_card"
|
||||||
value: DgopService.memoryUsage.toFixed(1) + "%"
|
value: DgopService.memoryUsage.toFixed(1) + "%"
|
||||||
@@ -109,6 +110,7 @@ Item {
|
|||||||
PerformanceCard {
|
PerformanceCard {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
historySize: root.historySize
|
||||||
title: I18n.tr("Network")
|
title: I18n.tr("Network")
|
||||||
icon: "swap_horiz"
|
icon: "swap_horiz"
|
||||||
value: "↓ " + root.formatBytes(DgopService.networkRxRate)
|
value: "↓ " + root.formatBytes(DgopService.networkRxRate)
|
||||||
@@ -125,6 +127,7 @@ Item {
|
|||||||
PerformanceCard {
|
PerformanceCard {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
historySize: root.historySize
|
||||||
title: I18n.tr("Disk")
|
title: I18n.tr("Disk")
|
||||||
icon: "storage"
|
icon: "storage"
|
||||||
value: "R: " + root.formatBytes(DgopService.diskReadRate)
|
value: "R: " + root.formatBytes(DgopService.diskReadRate)
|
||||||
@@ -146,159 +149,4 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component PerformanceCard: Rectangle {
|
|
||||||
id: card
|
|
||||||
|
|
||||||
property string title: ""
|
|
||||||
property string icon: ""
|
|
||||||
property string value: ""
|
|
||||||
property string subtitle: ""
|
|
||||||
property color accentColor: Theme.primary
|
|
||||||
property var history: []
|
|
||||||
property var history2: null
|
|
||||||
property real maxValue: 100
|
|
||||||
property bool showSecondary: false
|
|
||||||
property string extraInfo: ""
|
|
||||||
property color extraInfoColor: Theme.surfaceVariantText
|
|
||||||
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
|
||||||
border.color: Theme.outlineLight
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Canvas {
|
|
||||||
id: graphCanvas
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 4
|
|
||||||
renderStrategy: Canvas.Cooperative
|
|
||||||
|
|
||||||
property var hist: card.history
|
|
||||||
property var hist2: card.history2
|
|
||||||
|
|
||||||
onHistChanged: requestPaint()
|
|
||||||
onHist2Changed: requestPaint()
|
|
||||||
onWidthChanged: requestPaint()
|
|
||||||
onHeightChanged: requestPaint()
|
|
||||||
|
|
||||||
onPaint: {
|
|
||||||
const ctx = getContext("2d");
|
|
||||||
ctx.reset();
|
|
||||||
ctx.clearRect(0, 0, width, height);
|
|
||||||
|
|
||||||
if (!hist || hist.length < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let max = card.maxValue;
|
|
||||||
if (max <= 0) {
|
|
||||||
max = 1;
|
|
||||||
for (let k = 0; k < hist.length; k++)
|
|
||||||
max = Math.max(max, hist[k]);
|
|
||||||
if (hist2) {
|
|
||||||
for (let l = 0; l < hist2.length; l++)
|
|
||||||
max = Math.max(max, hist2[l]);
|
|
||||||
}
|
|
||||||
max *= 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const c = card.accentColor;
|
|
||||||
const grad = ctx.createLinearGradient(0, 0, 0, height);
|
|
||||||
grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.25));
|
|
||||||
grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.02));
|
|
||||||
|
|
||||||
ctx.fillStyle = grad;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, height);
|
|
||||||
for (let i = 0; i < hist.length; i++) {
|
|
||||||
const x = (width / (root.historySize - 1)) * i;
|
|
||||||
const y = height - (hist[i] / max) * height * 0.8;
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
ctx.lineTo((width / (root.historySize - 1)) * (hist.length - 1), height);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.8);
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.beginPath();
|
|
||||||
for (let j = 0; j < hist.length; j++) {
|
|
||||||
const px = (width / (root.historySize - 1)) * j;
|
|
||||||
const py = height - (hist[j] / max) * height * 0.8;
|
|
||||||
j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
|
|
||||||
}
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
if (hist2 && hist2.length >= 2 && card.showSecondary) {
|
|
||||||
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.4);
|
|
||||||
ctx.lineWidth = 1.5;
|
|
||||||
ctx.setLineDash([4, 4]);
|
|
||||||
ctx.beginPath();
|
|
||||||
for (let m = 0; m < hist2.length; m++) {
|
|
||||||
const sx = (width / (root.historySize - 1)) * m;
|
|
||||||
const sy = height - (hist2[m] / max) * height * 0.8;
|
|
||||||
m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy);
|
|
||||||
}
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.setLineDash([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: card.icon
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: card.accentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: card.title
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: card.extraInfo
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: card.extraInfoColor
|
|
||||||
visible: card.extraInfo.length > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: card.value
|
|
||||||
font.pixelSize: Theme.fontSizeXLarge
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: card.subtitle
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Popup {
|
Popup {
|
||||||
@@ -186,8 +187,8 @@ Popup {
|
|||||||
contentItem: Rectangle {
|
contentItem: Rectangle {
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 1
|
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: keyboardHandler
|
id: keyboardHandler
|
||||||
|
|||||||
@@ -0,0 +1,334 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: processItemRoot
|
||||||
|
|
||||||
|
property var process: null
|
||||||
|
property bool isExpanded: false
|
||||||
|
property bool isSelected: false
|
||||||
|
property var contextMenu: null
|
||||||
|
|
||||||
|
signal toggleExpand
|
||||||
|
signal clicked
|
||||||
|
signal contextMenuRequested(real mouseX, real mouseY)
|
||||||
|
|
||||||
|
readonly property int processPid: process?.pid ?? 0
|
||||||
|
readonly property real processCpu: process?.cpu ?? 0
|
||||||
|
readonly property int processMemKB: process?.memoryKB ?? 0
|
||||||
|
readonly property string processCmd: process?.command ?? ""
|
||||||
|
readonly property string processFullCmd: process?.fullCommand ?? processCmd
|
||||||
|
|
||||||
|
height: isExpanded ? (44 + expandedRect.height + Theme.spacingXS) : 44
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (isSelected)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
|
||||||
|
return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent";
|
||||||
|
}
|
||||||
|
border.color: {
|
||||||
|
if (isSelected)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
||||||
|
return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
||||||
|
}
|
||||||
|
border.width: 1
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: processMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
onClicked: mouse => {
|
||||||
|
if (mouse.button === Qt.RightButton) {
|
||||||
|
processItemRoot.contextMenuRequested(mouse.x, mouse.y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processItemRoot.clicked();
|
||||||
|
processItemRoot.toggleExpand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumWidth: 200
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: DgopService.getProcessIcon(processItemRoot.processCmd)
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: {
|
||||||
|
if (processItemRoot.processCpu > 80)
|
||||||
|
return Theme.error;
|
||||||
|
if (processItemRoot.processCpu > 50)
|
||||||
|
return Theme.warning;
|
||||||
|
return Theme.surfaceText;
|
||||||
|
}
|
||||||
|
opacity: 0.8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: processItemRoot.processCmd
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: Math.min(implicitWidth, 280)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 70
|
||||||
|
height: 24
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (processItemRoot.processCpu > 80)
|
||||||
|
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15);
|
||||||
|
if (processItemRoot.processCpu > 50)
|
||||||
|
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||||
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: DgopService.formatCpuUsage(processItemRoot.processCpu)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: {
|
||||||
|
if (processItemRoot.processCpu > 80)
|
||||||
|
return Theme.error;
|
||||||
|
if (processItemRoot.processCpu > 50)
|
||||||
|
return Theme.warning;
|
||||||
|
return Theme.surfaceText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 70
|
||||||
|
height: 24
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (processItemRoot.processMemKB > 2 * 1024 * 1024)
|
||||||
|
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15);
|
||||||
|
if (processItemRoot.processMemKB > 1024 * 1024)
|
||||||
|
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||||
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: DgopService.formatMemoryUsage(processItemRoot.processMemKB)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: {
|
||||||
|
if (processItemRoot.processMemKB > 2 * 1024 * 1024)
|
||||||
|
return Theme.error;
|
||||||
|
if (processItemRoot.processMemKB > 1024 * 1024)
|
||||||
|
return Theme.warning;
|
||||||
|
return Theme.surfaceText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: 80
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: processItemRoot.processPid > 0 ? processItemRoot.processPid.toString() : ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: 40
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: processItemRoot.isExpanded ? "expand_less" : "expand_more"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: expandedRect
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
height: processItemRoot.isExpanded ? (expandedContent.implicitHeight + Theme.spacingS * 2) : 0
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
radius: Theme.cornerRadius - 2
|
||||||
|
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.6)
|
||||||
|
clip: true
|
||||||
|
visible: processItemRoot.isExpanded
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: expandedContent
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: cmdLabel
|
||||||
|
text: I18n.tr("Full Command:", "process detail label")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 2
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: cmdText
|
||||||
|
text: processItemRoot.processFullCmd
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 2
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: copyBtn
|
||||||
|
Layout.preferredWidth: 24
|
||||||
|
Layout.preferredHeight: 24
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
radius: Theme.cornerRadius - 2
|
||||||
|
color: copyMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "content_copy"
|
||||||
|
size: 14
|
||||||
|
color: copyMouseArea.containsMouse ? Theme.primary : Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: copyMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
Quickshell.execDetached(["dms", "cl", "copy", processItemRoot.processFullCmd]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "PPID:"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 2
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: (processItemRoot.process?.ppid ?? 0) > 0 ? processItemRoot.process.ppid.toString() : "--"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 2
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Mem:"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 2
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: (processItemRoot.process?.memoryPercent ?? 0).toFixed(1) + "%"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 2
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -374,162 +374,4 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component CircleGauge: Item {
|
|
||||||
id: gaugeRoot
|
|
||||||
|
|
||||||
property real value: 0
|
|
||||||
property string label: ""
|
|
||||||
property string sublabel: ""
|
|
||||||
property string detail: ""
|
|
||||||
property color accentColor: Theme.primary
|
|
||||||
property color detailColor: Theme.surfaceVariantText
|
|
||||||
|
|
||||||
readonly property real thickness: Math.max(4, Math.min(width, height) / 15)
|
|
||||||
readonly property real glowExtra: thickness * 1.4
|
|
||||||
readonly property real arcPadding: (thickness + glowExtra) / 2
|
|
||||||
|
|
||||||
readonly property real innerDiameter: width - (arcPadding + thickness + glowExtra) * 2
|
|
||||||
readonly property real maxTextWidth: innerDiameter * 0.9
|
|
||||||
readonly property real baseLabelSize: Math.round(width * 0.18)
|
|
||||||
readonly property real labelSize: Math.round(Math.min(baseLabelSize, maxTextWidth / Math.max(1, label.length * 0.65)))
|
|
||||||
readonly property real sublabelSize: Math.round(Math.min(width * 0.13, maxTextWidth / Math.max(1, sublabel.length * 0.7)))
|
|
||||||
readonly property real detailSize: Math.round(Math.min(width * 0.12, maxTextWidth / Math.max(1, detail.length * 0.65)))
|
|
||||||
|
|
||||||
property real animValue: 0
|
|
||||||
|
|
||||||
onValueChanged: animValue = Math.min(1, Math.max(0, value))
|
|
||||||
|
|
||||||
Behavior on animValue {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: animValue = Math.min(1, Math.max(0, value))
|
|
||||||
|
|
||||||
Canvas {
|
|
||||||
id: glowCanvas
|
|
||||||
anchors.fill: parent
|
|
||||||
onPaint: {
|
|
||||||
const ctx = getContext("2d");
|
|
||||||
ctx.reset();
|
|
||||||
const cx = width / 2;
|
|
||||||
const cy = height / 2;
|
|
||||||
const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding;
|
|
||||||
const startAngle = -Math.PI * 0.5;
|
|
||||||
const endAngle = Math.PI * 1.5;
|
|
||||||
|
|
||||||
ctx.lineCap = "round";
|
|
||||||
|
|
||||||
if (gaugeRoot.animValue > 0) {
|
|
||||||
const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(cx, cy, radius, startAngle, prog);
|
|
||||||
ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.2);
|
|
||||||
ctx.lineWidth = gaugeRoot.thickness + gaugeRoot.glowExtra;
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: gaugeRoot
|
|
||||||
function onAnimValueChanged() {
|
|
||||||
glowCanvas.requestPaint();
|
|
||||||
}
|
|
||||||
function onAccentColorChanged() {
|
|
||||||
glowCanvas.requestPaint();
|
|
||||||
}
|
|
||||||
function onWidthChanged() {
|
|
||||||
glowCanvas.requestPaint();
|
|
||||||
}
|
|
||||||
function onHeightChanged() {
|
|
||||||
glowCanvas.requestPaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: requestPaint()
|
|
||||||
}
|
|
||||||
|
|
||||||
Canvas {
|
|
||||||
id: arcCanvas
|
|
||||||
anchors.fill: parent
|
|
||||||
onPaint: {
|
|
||||||
const ctx = getContext("2d");
|
|
||||||
ctx.reset();
|
|
||||||
const cx = width / 2;
|
|
||||||
const cy = height / 2;
|
|
||||||
const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding;
|
|
||||||
const startAngle = -Math.PI * 0.5;
|
|
||||||
const endAngle = Math.PI * 1.5;
|
|
||||||
|
|
||||||
ctx.lineCap = "round";
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(cx, cy, radius, startAngle, endAngle);
|
|
||||||
ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.1);
|
|
||||||
ctx.lineWidth = gaugeRoot.thickness;
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
if (gaugeRoot.animValue > 0) {
|
|
||||||
const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(cx, cy, radius, startAngle, prog);
|
|
||||||
ctx.strokeStyle = gaugeRoot.accentColor;
|
|
||||||
ctx.lineWidth = gaugeRoot.thickness;
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: gaugeRoot
|
|
||||||
function onAnimValueChanged() {
|
|
||||||
arcCanvas.requestPaint();
|
|
||||||
}
|
|
||||||
function onAccentColorChanged() {
|
|
||||||
arcCanvas.requestPaint();
|
|
||||||
}
|
|
||||||
function onWidthChanged() {
|
|
||||||
arcCanvas.requestPaint();
|
|
||||||
}
|
|
||||||
function onHeightChanged() {
|
|
||||||
arcCanvas.requestPaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: requestPaint()
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 1
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: gaugeRoot.label
|
|
||||||
font.pixelSize: gaugeRoot.labelSize
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: gaugeRoot.sublabel
|
|
||||||
font.pixelSize: gaugeRoot.sublabelSize
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: gaugeRoot.accentColor
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: gaugeRoot.detail
|
|
||||||
font.pixelSize: gaugeRoot.detailSize
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: gaugeRoot.detailColor
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: gaugeRoot.detail.length > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,402 +368,4 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component SortableHeader: Item {
|
|
||||||
id: headerItem
|
|
||||||
|
|
||||||
property string text: ""
|
|
||||||
property string sortKey: ""
|
|
||||||
property string currentSort: ""
|
|
||||||
property bool sortAscending: false
|
|
||||||
property int alignment: Text.AlignHCenter
|
|
||||||
|
|
||||||
signal clicked
|
|
||||||
|
|
||||||
readonly property bool isActive: sortKey === currentSort
|
|
||||||
|
|
||||||
height: 36
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 2
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: headerItem.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (headerMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06) : "transparent")
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: headerItem.alignment === Text.AlignLeft
|
|
||||||
visible: headerItem.alignment !== Text.AlignLeft
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: headerItem.text
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: headerItem.isActive ? Font.Bold : Font.Medium
|
|
||||||
color: headerItem.isActive ? Theme.primary : Theme.surfaceText
|
|
||||||
opacity: headerItem.isActive ? 1 : 0.8
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: headerItem.sortAscending ? "arrow_upward" : "arrow_downward"
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: Theme.primary
|
|
||||||
visible: headerItem.isActive
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: headerItem.alignment !== Text.AlignLeft
|
|
||||||
visible: headerItem.alignment === Text.AlignLeft
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: headerMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: headerItem.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component ProcessItem: Rectangle {
|
|
||||||
id: processItemRoot
|
|
||||||
|
|
||||||
property var process: null
|
|
||||||
property bool isExpanded: false
|
|
||||||
property bool isSelected: false
|
|
||||||
property var contextMenu: null
|
|
||||||
|
|
||||||
signal toggleExpand
|
|
||||||
signal clicked
|
|
||||||
signal contextMenuRequested(real mouseX, real mouseY)
|
|
||||||
|
|
||||||
readonly property int processPid: process?.pid ?? 0
|
|
||||||
readonly property real processCpu: process?.cpu ?? 0
|
|
||||||
readonly property int processMemKB: process?.memoryKB ?? 0
|
|
||||||
readonly property string processCmd: process?.command ?? ""
|
|
||||||
readonly property string processFullCmd: process?.fullCommand ?? processCmd
|
|
||||||
|
|
||||||
height: isExpanded ? (44 + expandedRect.height + Theme.spacingXS) : 44
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
if (isSelected)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
|
|
||||||
return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent";
|
|
||||||
}
|
|
||||||
border.color: {
|
|
||||||
if (isSelected)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
|
||||||
return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
|
||||||
}
|
|
||||||
border.width: 1
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: processMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: mouse => {
|
|
||||||
if (mouse.button === Qt.RightButton) {
|
|
||||||
processItemRoot.contextMenuRequested(mouse.x, mouse.y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processItemRoot.clicked();
|
|
||||||
processItemRoot.toggleExpand();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.minimumWidth: 200
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: DgopService.getProcessIcon(processItemRoot.processCmd)
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: {
|
|
||||||
if (processItemRoot.processCpu > 80)
|
|
||||||
return Theme.error;
|
|
||||||
if (processItemRoot.processCpu > 50)
|
|
||||||
return Theme.warning;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
opacity: 0.8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: processItemRoot.processCmd
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
elide: Text.ElideRight
|
|
||||||
width: Math.min(implicitWidth, 280)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: 100
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 70
|
|
||||||
height: 24
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
if (processItemRoot.processCpu > 80)
|
|
||||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15);
|
|
||||||
if (processItemRoot.processCpu > 50)
|
|
||||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
|
||||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: DgopService.formatCpuUsage(processItemRoot.processCpu)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: {
|
|
||||||
if (processItemRoot.processCpu > 80)
|
|
||||||
return Theme.error;
|
|
||||||
if (processItemRoot.processCpu > 50)
|
|
||||||
return Theme.warning;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: 100
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 70
|
|
||||||
height: 24
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
if (processItemRoot.processMemKB > 2 * 1024 * 1024)
|
|
||||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15);
|
|
||||||
if (processItemRoot.processMemKB > 1024 * 1024)
|
|
||||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
|
||||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: DgopService.formatMemoryUsage(processItemRoot.processMemKB)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: {
|
|
||||||
if (processItemRoot.processMemKB > 2 * 1024 * 1024)
|
|
||||||
return Theme.error;
|
|
||||||
if (processItemRoot.processMemKB > 1024 * 1024)
|
|
||||||
return Theme.warning;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: 80
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: processItemRoot.processPid > 0 ? processItemRoot.processPid.toString() : ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: 40
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: processItemRoot.isExpanded ? "expand_less" : "expand_more"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: expandedRect
|
|
||||||
width: parent.width - Theme.spacingM * 2
|
|
||||||
height: processItemRoot.isExpanded ? (expandedContent.implicitHeight + Theme.spacingS * 2) : 0
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
radius: Theme.cornerRadius - 2
|
|
||||||
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.6)
|
|
||||||
clip: true
|
|
||||||
visible: processItemRoot.isExpanded
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: expandedContent
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: cmdLabel
|
|
||||||
text: I18n.tr("Full Command:", "process detail label")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 2
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: cmdText
|
|
||||||
text: processItemRoot.processFullCmd
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 2
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
elide: Text.ElideMiddle
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: copyBtn
|
|
||||||
Layout.preferredWidth: 24
|
|
||||||
Layout.preferredHeight: 24
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
radius: Theme.cornerRadius - 2
|
|
||||||
color: copyMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent"
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "content_copy"
|
|
||||||
size: 14
|
|
||||||
color: copyMouseArea.containsMouse ? Theme.primary : Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: copyMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
Quickshell.execDetached(["dms", "cl", "copy", processItemRoot.processFullCmd]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "PPID:"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 2
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: (processItemRoot.process?.ppid ?? 0) > 0 ? processItemRoot.process.ppid.toString() : "--"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 2
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Mem:"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 2
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: (processItemRoot.process?.memoryPercent ?? 0).toFixed(1) + "%"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 2
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: headerItem
|
||||||
|
|
||||||
|
property string text: ""
|
||||||
|
property string sortKey: ""
|
||||||
|
property string currentSort: ""
|
||||||
|
property bool sortAscending: false
|
||||||
|
property int alignment: Text.AlignHCenter
|
||||||
|
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
readonly property bool isActive: sortKey === currentSort
|
||||||
|
|
||||||
|
height: 36
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: headerItem.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (headerMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06) : "transparent")
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: headerItem.alignment === Text.AlignLeft
|
||||||
|
visible: headerItem.alignment !== Text.AlignLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: headerItem.text
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: headerItem.isActive ? Font.Bold : Font.Medium
|
||||||
|
color: headerItem.isActive ? Theme.primary : Theme.surfaceText
|
||||||
|
opacity: headerItem.isActive ? 1 : 0.8
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: headerItem.sortAscending ? "arrow_upward" : "arrow_downward"
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
visible: headerItem.isActive
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: headerItem.alignment !== Text.AlignLeft
|
||||||
|
visible: headerItem.alignment === Text.AlignLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: headerMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: headerItem.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -358,29 +358,4 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component InfoRow: RowLayout {
|
|
||||||
property string label: ""
|
|
||||||
property string value: ""
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: label + ":"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
Layout.preferredWidth: 100
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: value
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
Layout.fillWidth: true
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,13 @@ 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")]
|
||||||
|
|||||||
@@ -91,6 +91,16 @@ 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"]
|
||||||
|
|||||||
@@ -125,6 +125,15 @@ Item {
|
|||||||
return Theme.warning;
|
return Theme.warning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openBlurBorderColorPicker() {
|
||||||
|
PopoutService.colorPickerModal.selectedColor = SettingsData.blurBorderCustomColor ?? "#ffffff";
|
||||||
|
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Blur Border Color");
|
||||||
|
PopoutService.colorPickerModal.onColorSelectedCallback = function (color) {
|
||||||
|
SettingsData.set("blurBorderCustomColor", color.toString());
|
||||||
|
};
|
||||||
|
PopoutService.colorPickerModal.open();
|
||||||
|
}
|
||||||
|
|
||||||
function openM3ShadowColorPicker() {
|
function openM3ShadowColorPicker() {
|
||||||
PopoutService.colorPickerModal.selectedColor = SettingsData.m3ElevationCustomColor ?? "#000000";
|
PopoutService.colorPickerModal.selectedColor = SettingsData.m3ElevationCustomColor ?? "#000000";
|
||||||
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Shadow Color");
|
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Shadow Color");
|
||||||
@@ -1816,6 +1825,77 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "background", "transparency", "glass", "frosted"]
|
||||||
|
title: I18n.tr("Background Blur")
|
||||||
|
settingKey: "blurEnabled"
|
||||||
|
iconName: "blur_on"
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "background", "transparency", "glass", "frosted"]
|
||||||
|
settingKey: "blurEnabled"
|
||||||
|
text: I18n.tr("Background Blur")
|
||||||
|
description: BlurService.available ? I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.") : I18n.tr("Requires a newer version of Quickshell")
|
||||||
|
checked: SettingsData.blurEnabled ?? false
|
||||||
|
enabled: BlurService.available
|
||||||
|
onToggled: checked => SettingsData.set("blurEnabled", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDropdownRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "border", "outline", "edge"]
|
||||||
|
settingKey: "blurBorderColor"
|
||||||
|
text: I18n.tr("Blur Border Color")
|
||||||
|
description: I18n.tr("Border color around blurred surfaces")
|
||||||
|
visible: SettingsData.blurEnabled
|
||||||
|
options: [I18n.tr("Outline", "blur border color"), I18n.tr("Primary", "blur border color"), I18n.tr("Secondary", "blur border color"), I18n.tr("Text Color", "blur border color"), I18n.tr("Custom", "blur border color")]
|
||||||
|
currentValue: {
|
||||||
|
switch (SettingsData.blurBorderColor) {
|
||||||
|
case "primary":
|
||||||
|
return I18n.tr("Primary", "blur border color");
|
||||||
|
case "secondary":
|
||||||
|
return I18n.tr("Secondary", "blur border color");
|
||||||
|
case "surfaceText":
|
||||||
|
return I18n.tr("Text Color", "blur border color");
|
||||||
|
case "custom":
|
||||||
|
return I18n.tr("Custom", "blur border color");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Outline", "blur border color");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onValueChanged: value => {
|
||||||
|
if (value === I18n.tr("Primary", "blur border color")) {
|
||||||
|
SettingsData.set("blurBorderColor", "primary");
|
||||||
|
} else if (value === I18n.tr("Secondary", "blur border color")) {
|
||||||
|
SettingsData.set("blurBorderColor", "secondary");
|
||||||
|
} else if (value === I18n.tr("Text Color", "blur border color")) {
|
||||||
|
SettingsData.set("blurBorderColor", "surfaceText");
|
||||||
|
} else if (value === I18n.tr("Custom", "blur border color")) {
|
||||||
|
SettingsData.set("blurBorderColor", "custom");
|
||||||
|
openBlurBorderColorPicker();
|
||||||
|
} else {
|
||||||
|
SettingsData.set("blurBorderColor", "outline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "border", "opacity"]
|
||||||
|
settingKey: "blurBorderOpacity"
|
||||||
|
text: I18n.tr("Blur Border Opacity")
|
||||||
|
visible: SettingsData.blurEnabled
|
||||||
|
value: Math.round((SettingsData.blurBorderOpacity ?? 1.0) * 100)
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
unit: "%"
|
||||||
|
defaultValue: 100
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("blurBorderOpacity", newValue / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
tab: "theme"
|
tab: "theme"
|
||||||
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
|
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
|
||||||
@@ -2602,7 +2682,6 @@ Item {
|
|||||||
onToggled: checked => SettingsData.set("matugenTemplateNeovim", checked)
|
onToggled: checked => SettingsData.set("matugenTemplateNeovim", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
text: I18n.tr("Dark mode base")
|
text: I18n.tr("Dark mode base")
|
||||||
tab: "theme"
|
tab: "theme"
|
||||||
|
|||||||
@@ -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"];
|
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"];
|
||||||
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,6 +712,8 @@ 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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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
|
||||||
@@ -40,7 +39,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"];
|
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"];
|
||||||
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]];
|
||||||
@@ -52,15 +51,14 @@ Column {
|
|||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
RowLayout {
|
Row {
|
||||||
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
|
||||||
Layout.alignment: Qt.AlignVCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -68,7 +66,7 @@ Column {
|
|||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
Layout.alignment: Qt.AlignVCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,7 +437,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"
|
visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "keyboard_layout_name" || modelData.id === "appsDock" || modelData.id === "systemTray"
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: compactModeButton
|
id: compactModeButton
|
||||||
@@ -545,6 +543,39 @@ 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
|
||||||
@@ -933,6 +964,88 @@ 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
|
||||||
|
|
||||||
@@ -981,10 +1094,26 @@ Column {
|
|||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: [
|
model: [
|
||||||
{ label: I18n.tr("Percentage"), mode: 0, icon: "percent" },
|
{
|
||||||
{ label: I18n.tr("Total"), mode: 1, icon: "storage" },
|
label: I18n.tr("Percentage"),
|
||||||
{ label: I18n.tr("Remaining"), mode: 2, icon: "hourglass_empty" },
|
mode: 0,
|
||||||
{ label: I18n.tr("Remaining / Total"), mode: 3, icon: "pie_chart" }
|
icon: "percent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 {
|
||||||
@@ -1316,20 +1445,7 @@ Column {
|
|||||||
id: longestControlCenterLabelMetrics
|
id: longestControlCenterLabelMetrics
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
text: {
|
text: {
|
||||||
const labels = [
|
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")];
|
||||||
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)
|
||||||
@@ -1340,6 +1456,7 @@ Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
|
id: groupRepeater
|
||||||
model: controlCenterContextMenu.controlCenterGroups
|
model: controlCenterContextMenu.controlCenterGroups
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
@@ -1569,8 +1686,6 @@ Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
id: groupRepeater
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ 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
|
||||||
|
|
||||||
@@ -67,6 +68,16 @@ 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));
|
||||||
@@ -395,7 +406,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; do
|
for event_key in audio-volume-change power-plug power-unplug message message-new-instant desktop-login; do
|
||||||
found=0
|
found=0
|
||||||
|
|
||||||
case "$event_key" in
|
case "$event_key" in
|
||||||
@@ -457,7 +468,8 @@ 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 = {
|
||||||
@@ -551,6 +563,10 @@ EOFCONFIG
|
|||||||
criticalNotificationSound.destroy();
|
criticalNotificationSound.destroy();
|
||||||
criticalNotificationSound = null;
|
criticalNotificationSound = null;
|
||||||
}
|
}
|
||||||
|
if (loginSound) {
|
||||||
|
loginSound.destroy();
|
||||||
|
loginSound = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSoundPlayers() {
|
function createSoundPlayers() {
|
||||||
@@ -622,6 +638,19 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -661,6 +690,31 @@ 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();
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Wayland // ! Import is needed despite what qmlls says
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool quickshellSupported: false
|
||||||
|
property bool compositorSupported: false
|
||||||
|
property bool available: quickshellSupported && compositorSupported
|
||||||
|
readonly property bool enabled: available && (SettingsData.blurEnabled ?? false)
|
||||||
|
|
||||||
|
readonly property color borderColor: {
|
||||||
|
if (!enabled)
|
||||||
|
return "transparent";
|
||||||
|
const opacity = SettingsData.blurBorderOpacity ?? 0.5;
|
||||||
|
switch (SettingsData.blurBorderColor ?? "outline") {
|
||||||
|
case "primary":
|
||||||
|
return Theme.withAlpha(Theme.primary, opacity);
|
||||||
|
case "secondary":
|
||||||
|
return Theme.withAlpha(Theme.secondary, opacity);
|
||||||
|
case "surfaceText":
|
||||||
|
return Theme.withAlpha(Theme.surfaceText, opacity);
|
||||||
|
case "custom":
|
||||||
|
return Theme.withAlpha(SettingsData.blurBorderCustomColor ?? "#ffffff", opacity);
|
||||||
|
default:
|
||||||
|
return Theme.withAlpha(Theme.outline, opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readonly property int borderWidth: enabled ? 1 : 0
|
||||||
|
|
||||||
|
function hoverColor(baseColor, hoverAlpha) {
|
||||||
|
if (!enabled)
|
||||||
|
return baseColor;
|
||||||
|
return Theme.withAlpha(baseColor, hoverAlpha ?? 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBlurRegion(targetWindow) {
|
||||||
|
if (!available)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const region = Qt.createQmlObject(`
|
||||||
|
import Quickshell
|
||||||
|
Region {}
|
||||||
|
`, targetWindow, "BlurRegion");
|
||||||
|
targetWindow.BackgroundEffect.blurRegion = region;
|
||||||
|
return region;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("BlurService: Failed to create blur region:", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reapplyBlurRegion(targetWindow, region) {
|
||||||
|
if (!region || !available)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
targetWindow.BackgroundEffect.blurRegion = region;
|
||||||
|
region.changed();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyBlurRegion(targetWindow, region) {
|
||||||
|
if (!region)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
targetWindow.BackgroundEffect.blurRegion = null;
|
||||||
|
} catch (e) {}
|
||||||
|
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: {
|
||||||
|
try {
|
||||||
|
const test = Qt.createQmlObject(`
|
||||||
|
import Quickshell
|
||||||
|
Region { radius: 0 }
|
||||||
|
`, root, "BlurAvailabilityTest");
|
||||||
|
test.destroy();
|
||||||
|
quickshellSupported = true;
|
||||||
|
console.info("BlurService: Quickshell blur support available");
|
||||||
|
blurProbe.running = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.info("BlurService: BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -255,6 +255,12 @@ 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
|
||||||
|
|||||||
@@ -10,4 +10,20 @@ 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell.Services.Notifications
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: wrapper
|
||||||
|
|
||||||
|
property bool popup: false
|
||||||
|
property bool removedByLimit: false
|
||||||
|
property bool isPersistent: true
|
||||||
|
property int seq: 0
|
||||||
|
property string persistedImagePath: ""
|
||||||
|
|
||||||
|
onPopupChanged: {
|
||||||
|
if (!popup) {
|
||||||
|
NotificationService.removeFromVisibleNotifications(wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property Timer timer: Timer {
|
||||||
|
interval: {
|
||||||
|
if (!wrapper.notification)
|
||||||
|
return 5000;
|
||||||
|
switch (wrapper.urgency) {
|
||||||
|
case NotificationUrgency.Low:
|
||||||
|
return SettingsData.notificationTimeoutLow;
|
||||||
|
case NotificationUrgency.Critical:
|
||||||
|
return SettingsData.notificationTimeoutCritical;
|
||||||
|
default:
|
||||||
|
return SettingsData.notificationTimeoutNormal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repeat: false
|
||||||
|
running: false
|
||||||
|
onTriggered: {
|
||||||
|
if (interval > 0) {
|
||||||
|
wrapper.popup = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property date time: new Date()
|
||||||
|
readonly property string timeStr: {
|
||||||
|
NotificationService.timeUpdateTick;
|
||||||
|
NotificationService.clockFormatChanged;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now.getTime() - time.getTime();
|
||||||
|
const minutes = Math.floor(diff / 60000);
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
|
||||||
|
if (hours < 1) {
|
||||||
|
if (minutes < 1) {
|
||||||
|
return "now";
|
||||||
|
}
|
||||||
|
return `${minutes}m ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
|
const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate());
|
||||||
|
const daysDiff = Math.floor((nowDate - timeDate) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (daysDiff === 0) {
|
||||||
|
return formatTime(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const localeName = (typeof I18n !== "undefined" && I18n.locale) ? I18n.locale().name : "en-US";
|
||||||
|
const weekday = time.toLocaleDateString(localeName, {
|
||||||
|
weekday: "long"
|
||||||
|
});
|
||||||
|
return `${weekday}, ${formatTime(time)}`;
|
||||||
|
} catch (e) {
|
||||||
|
return formatTime(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(date) {
|
||||||
|
let use24Hour = true;
|
||||||
|
try {
|
||||||
|
if (typeof SettingsData !== "undefined" && SettingsData.use24HourClock !== undefined) {
|
||||||
|
use24Hour = SettingsData.use24HourClock;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
use24Hour = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use24Hour) {
|
||||||
|
return date.toLocaleTimeString(Qt.locale(), "HH:mm");
|
||||||
|
} else {
|
||||||
|
return date.toLocaleTimeString(Qt.locale(), "h:mm AP");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required property Notification notification
|
||||||
|
readonly property string summary: (notification?.summary ?? "").replace(/<img\b[^>]*>/gi, "")
|
||||||
|
readonly property string body: (notification?.body ?? "").replace(/<img\b[^>]*>/gi, "")
|
||||||
|
readonly property string htmlBody: NotificationService._resolveHtmlBody(body)
|
||||||
|
readonly property string appIcon: notification?.appIcon ?? ""
|
||||||
|
readonly property string appName: {
|
||||||
|
if (!notification)
|
||||||
|
return "app";
|
||||||
|
if (notification.appName == "") {
|
||||||
|
const entry = DesktopEntries.heuristicLookup(notification.desktopEntry);
|
||||||
|
if (entry && entry.name)
|
||||||
|
return entry.name.toLowerCase();
|
||||||
|
}
|
||||||
|
return notification.appName || "app";
|
||||||
|
}
|
||||||
|
readonly property string desktopEntry: notification?.desktopEntry ?? ""
|
||||||
|
readonly property string image: notification?.image ?? ""
|
||||||
|
readonly property string cleanImage: {
|
||||||
|
if (!image)
|
||||||
|
return "";
|
||||||
|
return Paths.strip(image);
|
||||||
|
}
|
||||||
|
property int urgencyOverride: notification?.urgency ?? NotificationUrgency.Normal
|
||||||
|
readonly property int urgency: urgencyOverride
|
||||||
|
readonly property list<NotificationAction> actions: notification?.actions ?? []
|
||||||
|
|
||||||
|
readonly property Connections conn: Connections {
|
||||||
|
target: wrapper.notification?.Retainable ?? null
|
||||||
|
|
||||||
|
function onDropped(): void {
|
||||||
|
NotificationService.allWrappers = NotificationService.allWrappers.filter(w => w !== wrapper);
|
||||||
|
NotificationService.notifications = NotificationService.notifications.filter(w => w !== wrapper);
|
||||||
|
|
||||||
|
if (NotificationService.bulkDismissing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupKey = NotificationService.getGroupKey(wrapper);
|
||||||
|
const remainingInGroup = NotificationService.notifications.filter(n => NotificationService.getGroupKey(n) === groupKey);
|
||||||
|
|
||||||
|
if (remainingInGroup.length <= 1) {
|
||||||
|
NotificationService.clearGroupExpansionState(groupKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationService.cleanupExpansionStates();
|
||||||
|
NotificationService._recomputeGroupsLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAboutToDestroy(): void {
|
||||||
|
wrapper.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -655,150 +655,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component NotifWrapper: QtObject {
|
|
||||||
id: wrapper
|
|
||||||
|
|
||||||
property bool popup: false
|
|
||||||
property bool removedByLimit: false
|
|
||||||
property bool isPersistent: true
|
|
||||||
property int seq: 0
|
|
||||||
property string persistedImagePath: ""
|
|
||||||
|
|
||||||
onPopupChanged: {
|
|
||||||
if (!popup) {
|
|
||||||
removeFromVisibleNotifications(wrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property Timer timer: Timer {
|
|
||||||
interval: {
|
|
||||||
if (!wrapper.notification)
|
|
||||||
return 5000;
|
|
||||||
switch (wrapper.urgency) {
|
|
||||||
case NotificationUrgency.Low:
|
|
||||||
return SettingsData.notificationTimeoutLow;
|
|
||||||
case NotificationUrgency.Critical:
|
|
||||||
return SettingsData.notificationTimeoutCritical;
|
|
||||||
default:
|
|
||||||
return SettingsData.notificationTimeoutNormal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repeat: false
|
|
||||||
running: false
|
|
||||||
onTriggered: {
|
|
||||||
if (interval > 0) {
|
|
||||||
wrapper.popup = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property date time: new Date()
|
|
||||||
readonly property string timeStr: {
|
|
||||||
root.timeUpdateTick;
|
|
||||||
root.clockFormatChanged;
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const diff = now.getTime() - time.getTime();
|
|
||||||
const minutes = Math.floor(diff / 60000);
|
|
||||||
const hours = Math.floor(minutes / 60);
|
|
||||||
|
|
||||||
if (hours < 1) {
|
|
||||||
if (minutes < 1) {
|
|
||||||
return "now";
|
|
||||||
}
|
|
||||||
return `${minutes}m ago`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
||||||
const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate());
|
|
||||||
const daysDiff = Math.floor((nowDate - timeDate) / (1000 * 60 * 60 * 24));
|
|
||||||
|
|
||||||
if (daysDiff === 0) {
|
|
||||||
return formatTime(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const localeName = (typeof I18n !== "undefined" && I18n.locale) ? I18n.locale().name : "en-US";
|
|
||||||
const weekday = time.toLocaleDateString(localeName, {
|
|
||||||
weekday: "long"
|
|
||||||
});
|
|
||||||
return `${weekday}, ${formatTime(time)}`;
|
|
||||||
} catch (e) {
|
|
||||||
return formatTime(time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTime(date) {
|
|
||||||
let use24Hour = true;
|
|
||||||
try {
|
|
||||||
if (typeof SettingsData !== "undefined" && SettingsData.use24HourClock !== undefined) {
|
|
||||||
use24Hour = SettingsData.use24HourClock;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
use24Hour = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use24Hour) {
|
|
||||||
return date.toLocaleTimeString(Qt.locale(), "HH:mm");
|
|
||||||
} else {
|
|
||||||
return date.toLocaleTimeString(Qt.locale(), "h:mm AP");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required property Notification notification
|
|
||||||
readonly property string summary: (notification?.summary ?? "").replace(/<img\b[^>]*>/gi, "")
|
|
||||||
readonly property string body: (notification?.body ?? "").replace(/<img\b[^>]*>/gi, "")
|
|
||||||
readonly property string htmlBody: root._resolveHtmlBody(body)
|
|
||||||
readonly property string appIcon: notification?.appIcon ?? ""
|
|
||||||
readonly property string appName: {
|
|
||||||
if (!notification)
|
|
||||||
return "app";
|
|
||||||
if (notification.appName == "") {
|
|
||||||
const entry = DesktopEntries.heuristicLookup(notification.desktopEntry);
|
|
||||||
if (entry && entry.name)
|
|
||||||
return entry.name.toLowerCase();
|
|
||||||
}
|
|
||||||
return notification.appName || "app";
|
|
||||||
}
|
|
||||||
readonly property string desktopEntry: notification?.desktopEntry ?? ""
|
|
||||||
readonly property string image: notification?.image ?? ""
|
|
||||||
readonly property string cleanImage: {
|
|
||||||
if (!image)
|
|
||||||
return "";
|
|
||||||
return Paths.strip(image);
|
|
||||||
}
|
|
||||||
property int urgencyOverride: notification?.urgency ?? NotificationUrgency.Normal
|
|
||||||
readonly property int urgency: urgencyOverride
|
|
||||||
readonly property list<NotificationAction> actions: notification?.actions ?? []
|
|
||||||
|
|
||||||
readonly property Connections conn: Connections {
|
|
||||||
target: wrapper.notification?.Retainable ?? null
|
|
||||||
|
|
||||||
function onDropped(): void {
|
|
||||||
root.allWrappers = root.allWrappers.filter(w => w !== wrapper);
|
|
||||||
root.notifications = root.notifications.filter(w => w !== wrapper);
|
|
||||||
|
|
||||||
if (root.bulkDismissing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupKey = getGroupKey(wrapper);
|
|
||||||
const remainingInGroup = root.notifications.filter(n => getGroupKey(n) === groupKey);
|
|
||||||
|
|
||||||
if (remainingInGroup.length <= 1) {
|
|
||||||
clearGroupExpansionState(groupKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupExpansionStates();
|
|
||||||
root._recomputeGroupsLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAboutToDestroy(): void {
|
|
||||||
wrapper.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: notifComponent
|
id: notifComponent
|
||||||
NotifWrapper {}
|
NotifWrapper {}
|
||||||
|
|||||||
@@ -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.surfaceVariant
|
color: selected ? Theme.buttonBg : Theme.withAlpha(Theme.surfaceVariant, Theme.popupTransparency)
|
||||||
border.color: "transparent"
|
border.color: "transparent"
|
||||||
border.width: 0
|
border.width: 0
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,16 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: root
|
||||||
|
blurX: shadowBuffer
|
||||||
|
blurY: shadowBuffer
|
||||||
|
blurWidth: shouldBeVisible ? alignedWidth : 0
|
||||||
|
blurHeight: shouldBeVisible ? alignedHeight : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
readonly property real dpr: CompositorService.getScreenScale(screen)
|
readonly property real dpr: CompositorService.getScreenScale(screen)
|
||||||
@@ -256,15 +266,15 @@ PanelWindow {
|
|||||||
scale: shouldBeVisible ? 1 : 0.9
|
scale: shouldBeVisible ? 1 : 0.9
|
||||||
|
|
||||||
property bool childHovered: false
|
property bool childHovered: false
|
||||||
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
|
readonly property real popupSurfaceAlpha: Theme.popupTransparency
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: background
|
id: background
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, osdContainer.popupSurfaceAlpha)
|
color: Theme.withAlpha(Theme.surfaceContainer, osdContainer.popupSurfaceAlpha)
|
||||||
border.color: Theme.outlineMedium
|
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
|
||||||
border.width: 1
|
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||||
z: -1
|
z: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +286,7 @@ PanelWindow {
|
|||||||
level: Theme.elevationLevel3
|
level: Theme.elevationLevel3
|
||||||
fallbackOffset: 6
|
fallbackOffset: 6
|
||||||
targetRadius: Theme.cornerRadius
|
targetRadius: Theme.cornerRadius
|
||||||
targetColor: Theme.surfaceContainer
|
targetColor: Theme.withAlpha(Theme.surfaceContainer, osdContainer.popupSurfaceAlpha)
|
||||||
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"
|
||||||
|
|||||||
@@ -398,6 +398,17 @@ Item {
|
|||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
id: popoutBlur
|
||||||
|
targetWindow: contentWindow
|
||||||
|
readonly property real s: Math.min(1, contentContainer.scaleValue)
|
||||||
|
blurX: contentContainer.x + contentContainer.width * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
|
||||||
|
blurY: contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
|
||||||
|
blurWidth: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.width * s : 0
|
||||||
|
blurHeight: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.height * s : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: root.layerNamespace
|
WlrLayershell.namespace: root.layerNamespace
|
||||||
WlrLayershell.layer: {
|
WlrLayershell.layer: {
|
||||||
switch (Quickshell.env("DMS_POPOUT_LAYER")) {
|
switch (Quickshell.env("DMS_POPOUT_LAYER")) {
|
||||||
@@ -565,14 +576,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
|
||||||
border.color: Theme.outlineMedium
|
|
||||||
border.width: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: contentLoader
|
id: contentLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -580,6 +583,21 @@ 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 {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user