mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-22 11:05:22 -04:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d94117a69 | |||
| 92569d8b4d | |||
| fdee09b583 | |||
| b60af507d7 | |||
| 2cc12b70d2 | |||
| 2df1dfe0bd | |||
| abf084eea2 | |||
| 08fd6e26d8 | |||
| 29e8470f2e | |||
| 573785d4ce | |||
| 5483303714 | |||
| 5a5cc4f4e9 | |||
| cd672c341f | |||
| 12438d63c2 | |||
| 35255e4053 | |||
| 8856d45887 | |||
| 38af56c6fd | |||
| 9111e4809d | |||
| d08c7c5e55 | |||
| 69f3dee25a | |||
| 8155970ba2 | |||
| d356957dad | |||
| e7ccb702a3 | |||
| bf3ce6deb2 | |||
| f5295fb35d | |||
| 6c5836722a | |||
| 5716249bd9 | |||
| 4d0aab773b | |||
| e50ac208e3 | |||
| bcb5617194 | |||
| d3c23ba737 |
@@ -235,7 +235,7 @@ Conditionally show/hide the bar pill:
|
|||||||
```qml
|
```qml
|
||||||
PluginComponent {
|
PluginComponent {
|
||||||
visibilityCommand: "pgrep -x myapp"
|
visibilityCommand: "pgrep -x myapp"
|
||||||
visibilityInterval: 5000 // check every 5 seconds
|
visibilityInterval: 5 // seconds between checks; polling pauses while the bar is hidden
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -115,3 +115,5 @@ core.*
|
|||||||
.direnv/
|
.direnv/
|
||||||
quickshell/dms-plugins
|
quickshell/dms-plugins
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func runResolveInclude(cmd *cobra.Command, args []string) {
|
|||||||
result, err = checkHyprlandInclude(filename)
|
result, err = checkHyprlandInclude(filename)
|
||||||
case "niri":
|
case "niri":
|
||||||
result, err = checkNiriInclude(filename)
|
result, err = checkNiriInclude(filename)
|
||||||
case "mangowc", "dwl", "mango":
|
case "mangowc", "mango":
|
||||||
result, err = checkMangoWCInclude(filename)
|
result, err = checkMangoWCInclude(filename)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unknown compositor: %s", compositor)
|
log.Fatalf("Unknown compositor: %s", compositor)
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ const (
|
|||||||
catConfigFiles
|
catConfigFiles
|
||||||
catServices
|
catServices
|
||||||
catEnvironment
|
catEnvironment
|
||||||
|
catFonts
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c category) String() string {
|
func (c category) String() string {
|
||||||
@@ -147,6 +148,8 @@ func (c category) String() string {
|
|||||||
return "Services"
|
return "Services"
|
||||||
case catEnvironment:
|
case catEnvironment:
|
||||||
return "Environment"
|
return "Environment"
|
||||||
|
case catFonts:
|
||||||
|
return "Fonts"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
@@ -213,6 +216,7 @@ func runDoctor(cmd *cobra.Command, args []string) {
|
|||||||
checkConfigurationFiles(),
|
checkConfigurationFiles(),
|
||||||
checkSystemdServices(),
|
checkSystemdServices(),
|
||||||
checkEnvironmentVars(),
|
checkEnvironmentVars(),
|
||||||
|
checkFonts(),
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -1135,3 +1139,100 @@ func formatResultsPlain(results []checkResult) string {
|
|||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkFonts() []checkResult {
|
||||||
|
var results []checkResult
|
||||||
|
url := doctorDocsURL + "#fonts"
|
||||||
|
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
settingsPath := filepath.Join(configDir, "DankMaterialShell", "settings.json")
|
||||||
|
|
||||||
|
fontFamily := "Inter Variable"
|
||||||
|
monoFontFamily := "Fira Code"
|
||||||
|
|
||||||
|
if data, err := os.ReadFile(settingsPath); err == nil {
|
||||||
|
var settings struct {
|
||||||
|
FontFamily string `json:"fontFamily"`
|
||||||
|
MonoFontFamily string `json:"monoFontFamily"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &settings); err == nil {
|
||||||
|
if settings.FontFamily != "" {
|
||||||
|
fontFamily = settings.FontFamily
|
||||||
|
}
|
||||||
|
if settings.MonoFontFamily != "" {
|
||||||
|
monoFontFamily = settings.MonoFontFamily
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.CommandExists("fc-list") {
|
||||||
|
results = append(results, checkResult{catFonts, "Fontconfig Tools", statusWarn, "fc-list not installed", "Cannot verify if fonts are cached.", url})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve font list
|
||||||
|
output, err := exec.Command("fc-list", ":", "family").Output()
|
||||||
|
if err != nil {
|
||||||
|
results = append(results, checkResult{catFonts, "Fontconfig Cache", statusError, "Failed to query font list", "Fontconfig cache query failed. Try running 'fc-cache -fv'.", url})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
outStr := string(output)
|
||||||
|
if len(strings.TrimSpace(outStr)) == 0 {
|
||||||
|
results = append(results, checkResult{catFonts, "Fontconfig Cache", statusError, "Cache is empty", "No fonts found in fontconfig cache. Try running 'fc-cache -fv'.", url})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerFonts := strings.ToLower(outStr)
|
||||||
|
|
||||||
|
// Helper to check if a font exists
|
||||||
|
hasFont := func(name string) bool {
|
||||||
|
target := strings.ToLower(strings.TrimSpace(name))
|
||||||
|
if target == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(lowerFonts, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Each line can have comma-separated families
|
||||||
|
families := strings.Split(line, ",")
|
||||||
|
for _, fam := range families {
|
||||||
|
if strings.TrimSpace(fam) == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal Font Check
|
||||||
|
if hasFont(fontFamily) {
|
||||||
|
results = append(results, checkResult{catFonts, "Normal Font", statusOK, fontFamily, "Available", url})
|
||||||
|
} else {
|
||||||
|
results = append(results, checkResult{
|
||||||
|
catFonts, "Normal Font", statusWarn,
|
||||||
|
fmt.Sprintf("'%s' not found", fontFamily),
|
||||||
|
"Font is not registered. Try running 'fc-cache -fv' or install the font.",
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monospace Font Check
|
||||||
|
if hasFont(monoFontFamily) {
|
||||||
|
results = append(results, checkResult{catFonts, "Monospace Font", statusOK, monoFontFamily, "Available", url})
|
||||||
|
} else {
|
||||||
|
results = append(results, checkResult{
|
||||||
|
catFonts, "Monospace Font", statusWarn,
|
||||||
|
fmt.Sprintf("'%s' not found", monoFontFamily),
|
||||||
|
"Font is not registered. Try running 'fc-cache -fv' or install the font.",
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ var greeterSyncCmd = &cobra.Command{
|
|||||||
auth, _ := cmd.Flags().GetBool("auth")
|
auth, _ := cmd.Flags().GetBool("auth")
|
||||||
local, _ := cmd.Flags().GetBool("local")
|
local, _ := cmd.Flags().GetBool("local")
|
||||||
profile, _ := cmd.Flags().GetBool("profile")
|
profile, _ := cmd.Flags().GetBool("profile")
|
||||||
autologinOnly, _ := cmd.Flags().GetBool("autologin-only")
|
autologinOnly, _ := cmd.Flags().GetBool("autologin")
|
||||||
term, _ := cmd.Flags().GetBool("terminal")
|
term, _ := cmd.Flags().GetBool("terminal")
|
||||||
if term {
|
if term {
|
||||||
if err := syncInTerminal(yes, auth, local, profile, autologinOnly); err != nil {
|
if err := syncInTerminal(yes, auth, local, profile, autologinOnly); err != nil {
|
||||||
@@ -101,7 +101,7 @@ func init() {
|
|||||||
greeterSyncCmd.Flags().BoolP("auth", "a", false, "Configure PAM for fingerprint and U2F (adds both if modules exist); overrides UI toggles")
|
greeterSyncCmd.Flags().BoolP("auth", "a", false, "Configure PAM for fingerprint and U2F (adds both if modules exist); overrides UI toggles")
|
||||||
greeterSyncCmd.Flags().BoolP("local", "l", false, "Developer mode: force greetd config to use a local DMS checkout path")
|
greeterSyncCmd.Flags().BoolP("local", "l", false, "Developer mode: force greetd config to use a local DMS checkout path")
|
||||||
greeterSyncCmd.Flags().BoolP("profile", "p", false, "Sync only your per-user greeter slot (no sudo; for secondary accounts)")
|
greeterSyncCmd.Flags().BoolP("profile", "p", false, "Sync only your per-user greeter slot (no sudo; for secondary accounts)")
|
||||||
greeterSyncCmd.Flags().Bool("autologin-only", false, "Apply only greeter auto-login on startup settings to greetd (no theme or auth sync)")
|
greeterSyncCmd.Flags().Bool("autologin", false, "Apply only greeter auto-login on startup settings to greetd (no theme or auth sync)")
|
||||||
}
|
}
|
||||||
|
|
||||||
var greeterEnableCmd = &cobra.Command{
|
var greeterEnableCmd = &cobra.Command{
|
||||||
@@ -544,7 +544,7 @@ func syncInTerminal(nonInteractive bool, forceAuth bool, local bool, profileOnly
|
|||||||
syncFlags = append(syncFlags, "--profile")
|
syncFlags = append(syncFlags, "--profile")
|
||||||
}
|
}
|
||||||
if autologinOnly {
|
if autologinOnly {
|
||||||
syncFlags = append(syncFlags, "--autologin-only")
|
syncFlags = append(syncFlags, "--autologin")
|
||||||
}
|
}
|
||||||
shellSyncCmd := "dms greeter sync"
|
shellSyncCmd := "dms greeter sync"
|
||||||
if len(syncFlags) > 0 {
|
if len(syncFlags) > 0 {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Modes:
|
|||||||
full - Capture the focused output
|
full - Capture the focused output
|
||||||
all - Capture all outputs combined
|
all - Capture all outputs combined
|
||||||
output - Capture a specific output by name
|
output - Capture a specific output by name
|
||||||
window - Capture the focused window (Hyprland/DWL)
|
window - Capture the focused window (Hyprland/Mango)
|
||||||
last - Capture the last selected region
|
last - Capture the last selected region
|
||||||
|
|
||||||
Output format (--format):
|
Output format (--format):
|
||||||
@@ -97,7 +97,7 @@ If no previous region exists, falls back to interactive selection.`,
|
|||||||
var ssWindowCmd = &cobra.Command{
|
var ssWindowCmd = &cobra.Command{
|
||||||
Use: "window",
|
Use: "window",
|
||||||
Short: "Capture the focused window",
|
Short: "Capture the focused window",
|
||||||
Long: `Capture the currently focused window. Supported on Hyprland and DWL.`,
|
Long: `Capture the currently focused window. Supported on Hyprland and Mango.`,
|
||||||
Run: runScreenshotWindow,
|
Run: runScreenshotWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -294,7 +294,14 @@ func runSetup() error {
|
|||||||
|
|
||||||
wm, wmSelected := promptCompositor()
|
wm, wmSelected := promptCompositor()
|
||||||
terminal, terminalSelected := promptTerminal()
|
terminal, terminalSelected := promptTerminal()
|
||||||
useSystemd := promptSystemd()
|
useSystemd := true
|
||||||
|
if wmSelected {
|
||||||
|
if wm == deps.WindowManagerMango {
|
||||||
|
useSystemd = false
|
||||||
|
} else {
|
||||||
|
useSystemd = promptSystemd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !wmSelected && !terminalSelected {
|
if !wmSelected && !terminalSelected {
|
||||||
fmt.Println("No configurations selected. Exiting.")
|
fmt.Println("No configurations selected. Exiting.")
|
||||||
|
|||||||
+152
-6
@@ -2,7 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -192,6 +194,7 @@ func runShellInteractive(session bool) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
ensureFontCache()
|
||||||
log.Infof("Spawning quickshell with -p %s", configPath)
|
log.Infof("Spawning quickshell with -p %s", configPath)
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
||||||
@@ -227,8 +230,10 @@ func runShellInteractive(session bool) {
|
|||||||
|
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
tracker := &stderrTracker{parent: os.Stderr}
|
||||||
|
cmd.Stderr = tracker
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
log.Fatalf("Error starting quickshell: %v", err)
|
log.Fatalf("Error starting quickshell: %v", err)
|
||||||
}
|
}
|
||||||
@@ -277,7 +282,9 @@ func runShellInteractive(session bool) {
|
|||||||
case <-errChan:
|
case <-errChan:
|
||||||
cancel()
|
cancel()
|
||||||
os.Remove(socketPath)
|
os.Remove(socketPath)
|
||||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||||
|
logStartupFailure(startTime, exitCode, tracker)
|
||||||
|
os.Exit(exitCode)
|
||||||
case <-time.After(500 * time.Millisecond):
|
case <-time.After(500 * time.Millisecond):
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +301,9 @@ func runShellInteractive(session bool) {
|
|||||||
cmd.Process.Signal(syscall.SIGTERM)
|
cmd.Process.Signal(syscall.SIGTERM)
|
||||||
}
|
}
|
||||||
os.Remove(socketPath)
|
os.Remove(socketPath)
|
||||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||||
|
logStartupFailure(startTime, exitCode, tracker)
|
||||||
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,6 +443,7 @@ func runShellDaemon(session bool) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
ensureFontCache()
|
||||||
log.Infof("Spawning quickshell with -p %s", configPath)
|
log.Infof("Spawning quickshell with -p %s", configPath)
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
||||||
@@ -478,8 +488,10 @@ func runShellDaemon(session bool) {
|
|||||||
|
|
||||||
cmd.Stdin = devNull
|
cmd.Stdin = devNull
|
||||||
cmd.Stdout = devNull
|
cmd.Stdout = devNull
|
||||||
cmd.Stderr = devNull
|
tracker := &stderrTracker{parent: devNull}
|
||||||
|
cmd.Stderr = tracker
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
log.Fatalf("Error starting daemon: %v", err)
|
log.Fatalf("Error starting daemon: %v", err)
|
||||||
}
|
}
|
||||||
@@ -528,7 +540,9 @@ func runShellDaemon(session bool) {
|
|||||||
case <-errChan:
|
case <-errChan:
|
||||||
cancel()
|
cancel()
|
||||||
os.Remove(socketPath)
|
os.Remove(socketPath)
|
||||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||||
|
logStartupFailure(startTime, exitCode, tracker)
|
||||||
|
os.Exit(exitCode)
|
||||||
case <-time.After(500 * time.Millisecond):
|
case <-time.After(500 * time.Millisecond):
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,7 +557,9 @@ func runShellDaemon(session bool) {
|
|||||||
cmd.Process.Signal(syscall.SIGTERM)
|
cmd.Process.Signal(syscall.SIGTERM)
|
||||||
}
|
}
|
||||||
os.Remove(socketPath)
|
os.Remove(socketPath)
|
||||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||||
|
logStartupFailure(startTime, exitCode, tracker)
|
||||||
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -748,3 +764,133 @@ func printIPCHelp() {
|
|||||||
fmt.Printf(" %-16s %s\n", targetName, strings.Join(funcNames, ", "))
|
fmt.Printf(" %-16s %s\n", targetName, strings.Join(funcNames, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensureFontCache rebuilds the fontconfig cache if user-configured fonts are missing while skipping defaults
|
||||||
|
func ensureFontCache() {
|
||||||
|
if _, err := exec.LookPath("fc-list"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("fc-cache"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var fontsToCheck []string
|
||||||
|
|
||||||
|
if configDir, err := os.UserConfigDir(); err == nil {
|
||||||
|
settingsPath := filepath.Join(configDir, "DankMaterialShell", "settings.json")
|
||||||
|
if data, err := os.ReadFile(settingsPath); err == nil {
|
||||||
|
var settings struct {
|
||||||
|
FontFamily string `json:"fontFamily"`
|
||||||
|
MonoFontFamily string `json:"monoFontFamily"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &settings); err == nil {
|
||||||
|
if settings.FontFamily != "" && settings.FontFamily != "Inter Variable" {
|
||||||
|
fontsToCheck = append(fontsToCheck, settings.FontFamily)
|
||||||
|
}
|
||||||
|
if settings.MonoFontFamily != "" && settings.MonoFontFamily != "Fira Code" {
|
||||||
|
fontsToCheck = append(fontsToCheck, settings.MonoFontFamily)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fontsToCheck) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := exec.Command("fc-list", ":", "family").Output()
|
||||||
|
if err != nil || len(strings.TrimSpace(string(output))) == 0 {
|
||||||
|
log.Warnf("Font cache appears empty or corrupt, rebuilding...")
|
||||||
|
rebuildFontCache()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFonts := strings.ToLower(string(output))
|
||||||
|
var missing []string
|
||||||
|
for _, font := range fontsToCheck {
|
||||||
|
if !fontInCache(strings.ToLower(font), cacheFonts) {
|
||||||
|
missing = append(missing, font)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missing) > 0 {
|
||||||
|
log.Warnf("Font(s) not found in cache: %s — rebuilding...", strings.Join(missing, ", "))
|
||||||
|
rebuildFontCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fontInCache(target, cache string) bool {
|
||||||
|
for _, line := range strings.Split(cache, "\n") {
|
||||||
|
for _, fam := range strings.Split(strings.TrimSpace(line), ",") {
|
||||||
|
if strings.TrimSpace(fam) == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func rebuildFontCache() {
|
||||||
|
cmd := exec.Command("fc-cache", "-f")
|
||||||
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
log.Warnf("Failed to rebuild font cache: %v\n%s", err, string(output))
|
||||||
|
} else {
|
||||||
|
log.Infof("Font cache rebuilt successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stderrTracker struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
buf strings.Builder
|
||||||
|
parent io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stderrTracker) Write(p []byte) (n int, err error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.buf.Len() < 8192 {
|
||||||
|
s.buf.Write(p)
|
||||||
|
}
|
||||||
|
if s.parent != nil {
|
||||||
|
return s.parent.Write(p)
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stderrTracker) String() string {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// logStartupFailure logs diagnostic advice if qs crashes within 5s of launch.
|
||||||
|
func logStartupFailure(startTime time.Time, exitCode int, tracker *stderrTracker) {
|
||||||
|
if time.Since(startTime) >= 5*time.Second || exitCode == 0 || exitCode > 128 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if containsFontCrashSignature(tracker.String()) {
|
||||||
|
log.Errorf("DMS startup failed due to a potential font/rendering crash. Try running 'fc-cache -fv' and restarting DMS.")
|
||||||
|
} else {
|
||||||
|
log.Errorf("DMS startup failed (exit code %d). Run 'dms doctor' for more diagnostics.", exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsFontCrashSignature(logStr string) bool {
|
||||||
|
logStr = strings.ToLower(logStr)
|
||||||
|
signatures := []string{
|
||||||
|
"fontconfig",
|
||||||
|
"freetype",
|
||||||
|
"ft_load_glyph",
|
||||||
|
"ft_face",
|
||||||
|
"fc-list",
|
||||||
|
"fc-cache",
|
||||||
|
"glyph",
|
||||||
|
"typeface",
|
||||||
|
}
|
||||||
|
for _, sig := range signatures {
|
||||||
|
if strings.Contains(logStr, sig) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -520,6 +520,18 @@ func TestHyprlandConfigStructure(t *testing.T) {
|
|||||||
assert.Contains(t, HyprlandLuaConfig, "input =")
|
assert.Contains(t, HyprlandLuaConfig, "input =")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMangoConfigStructure(t *testing.T) {
|
||||||
|
assert.Contains(t, MangoConfig, "exec-once=dms run")
|
||||||
|
assert.NotContains(t, MangoConfig, "exec_once=dms run")
|
||||||
|
assert.Contains(t, MangoConfig, "source=./dms/binds.conf")
|
||||||
|
assert.Contains(t, MangoBindsConfig, "bind=SUPER,H,focusdir,left")
|
||||||
|
assert.Contains(t, MangoBindsConfig, "bind=SUPER,J,focusdir,down")
|
||||||
|
assert.Contains(t, MangoBindsConfig, "bind=SUPER,K,focusdir,up")
|
||||||
|
assert.Contains(t, MangoBindsConfig, "bind=SUPER,L,focusdir,right")
|
||||||
|
assert.Contains(t, MangoBindsConfig, "gesturebind=none,right,3,viewtoleft_have_client")
|
||||||
|
assert.Contains(t, MangoBindsConfig, "gesturebind=none,left,3,viewtoright_have_client")
|
||||||
|
}
|
||||||
|
|
||||||
func TestGhosttyConfigStructure(t *testing.T) {
|
func TestGhosttyConfigStructure(t *testing.T) {
|
||||||
assert.Contains(t, GhosttyConfig, "window-decoration = false")
|
assert.Contains(t, GhosttyConfig, "window-decoration = false")
|
||||||
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
|
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# DMS default keybinds (MangoWM) — managed by DMS, regenerated by `dms setup`.
|
# DMS default keybinds (MangoWM) — managed by DMS, regenerated by `dms setup`.
|
||||||
# Format: bind=MODS,key,action[,args]
|
# Format: bind=MODS,key,action[,args]
|
||||||
# Descriptions go on the line ABOVE each bind (mango does not strip inline
|
# Put bind descriptions above bind lines; inline # comments break Mango spawn args.
|
||||||
# comments — a trailing `# ...` would be passed to spawn as extra arguments).
|
|
||||||
|
|
||||||
# === Application Launchers ===
|
# === Application Launchers ===
|
||||||
# Open Terminal
|
# Open Terminal
|
||||||
@@ -52,131 +51,90 @@ bind=CTRL,Print,spawn,dms screenshot full
|
|||||||
bind=ALT,Print,spawn,dms screenshot window
|
bind=ALT,Print,spawn,dms screenshot window
|
||||||
|
|
||||||
# === Audio Controls ===
|
# === Audio Controls ===
|
||||||
# Volume Up
|
|
||||||
bind=none,XF86AudioRaiseVolume,spawn,dms ipc call audio increment 3
|
bind=none,XF86AudioRaiseVolume,spawn,dms ipc call audio increment 3
|
||||||
# Volume Down
|
|
||||||
bind=none,XF86AudioLowerVolume,spawn,dms ipc call audio decrement 3
|
bind=none,XF86AudioLowerVolume,spawn,dms ipc call audio decrement 3
|
||||||
# Mute Output
|
|
||||||
bind=none,XF86AudioMute,spawn,dms ipc call audio mute
|
bind=none,XF86AudioMute,spawn,dms ipc call audio mute
|
||||||
# Mute Microphone
|
|
||||||
bind=none,XF86AudioMicMute,spawn,dms ipc call audio micmute
|
bind=none,XF86AudioMicMute,spawn,dms ipc call audio micmute
|
||||||
# Play/Pause
|
|
||||||
bind=none,XF86AudioPlay,spawn,dms ipc call mpris playPause
|
bind=none,XF86AudioPlay,spawn,dms ipc call mpris playPause
|
||||||
# Play/Pause
|
|
||||||
bind=none,XF86AudioPause,spawn,dms ipc call mpris playPause
|
bind=none,XF86AudioPause,spawn,dms ipc call mpris playPause
|
||||||
# Previous Track
|
|
||||||
bind=none,XF86AudioPrev,spawn,dms ipc call mpris previous
|
bind=none,XF86AudioPrev,spawn,dms ipc call mpris previous
|
||||||
# Next Track
|
|
||||||
bind=none,XF86AudioNext,spawn,dms ipc call mpris next
|
bind=none,XF86AudioNext,spawn,dms ipc call mpris next
|
||||||
|
|
||||||
# === Brightness Controls ===
|
# === Brightness Controls ===
|
||||||
# Brightness Up
|
|
||||||
bind=none,XF86MonBrightnessUp,spawn,dms ipc call brightness increment 5
|
bind=none,XF86MonBrightnessUp,spawn,dms ipc call brightness increment 5
|
||||||
# Brightness Down
|
|
||||||
bind=none,XF86MonBrightnessDown,spawn,dms ipc call brightness decrement 5
|
bind=none,XF86MonBrightnessDown,spawn,dms ipc call brightness decrement 5
|
||||||
|
|
||||||
# === Window Management ===
|
# === Window Management ===
|
||||||
# Close Window
|
# Close Window
|
||||||
bind=SUPER,q,killclient,
|
bind=SUPER,q,killclient,
|
||||||
# Toggle Fullscreen
|
|
||||||
bind=SUPER,f,togglefullscreen,
|
bind=SUPER,f,togglefullscreen,
|
||||||
# Toggle Maximize
|
|
||||||
bind=SUPER,a,togglemaximizescreen,
|
bind=SUPER,a,togglemaximizescreen,
|
||||||
# Toggle Floating
|
|
||||||
bind=SUPER+SHIFT,space,togglefloating,
|
bind=SUPER+SHIFT,space,togglefloating,
|
||||||
# Toggle Overview
|
|
||||||
bind=SUPER,o,toggleoverview
|
bind=SUPER,o,toggleoverview
|
||||||
bind=ALT,Tab,toggleoverview
|
bind=ALT,Tab,toggleoverview
|
||||||
# Exit Compositor
|
# Exit Compositor
|
||||||
bind=SUPER+SHIFT,e,quit,
|
bind=SUPER+SHIFT,e,quit,
|
||||||
|
|
||||||
# === Focus Navigation ===
|
# === Focus Navigation ===
|
||||||
# Focus Next Window
|
|
||||||
bind=SUPER,Tab,focusstack,next
|
bind=SUPER,Tab,focusstack,next
|
||||||
# Focus Previous Window
|
|
||||||
bind=SUPER+SHIFT,Tab,focusstack,prev
|
bind=SUPER+SHIFT,Tab,focusstack,prev
|
||||||
# Focus Left
|
|
||||||
bind=SUPER,Left,focusdir,left
|
bind=SUPER,Left,focusdir,left
|
||||||
# Focus Right
|
bind=SUPER,H,focusdir,left
|
||||||
bind=SUPER,Right,focusdir,right
|
bind=SUPER,Right,focusdir,right
|
||||||
# Focus Up
|
bind=SUPER,L,focusdir,right
|
||||||
bind=SUPER,Up,focusdir,up
|
bind=SUPER,Up,focusdir,up
|
||||||
# Focus Down
|
bind=SUPER,K,focusdir,up
|
||||||
bind=SUPER,Down,focusdir,down
|
bind=SUPER,Down,focusdir,down
|
||||||
|
bind=SUPER,J,focusdir,down
|
||||||
|
|
||||||
# === Window Movement ===
|
# === Window Movement ===
|
||||||
# Move Window Left
|
|
||||||
bind=SUPER+SHIFT,Left,exchange_client,left
|
bind=SUPER+SHIFT,Left,exchange_client,left
|
||||||
# Move Window Right
|
|
||||||
bind=SUPER+SHIFT,Right,exchange_client,right
|
bind=SUPER+SHIFT,Right,exchange_client,right
|
||||||
# Move Window Up
|
|
||||||
bind=SUPER+SHIFT,Up,exchange_client,up
|
bind=SUPER+SHIFT,Up,exchange_client,up
|
||||||
# Move Window Down
|
|
||||||
bind=SUPER+SHIFT,Down,exchange_client,down
|
bind=SUPER+SHIFT,Down,exchange_client,down
|
||||||
|
bind=SUPER+SHIFT,H,exchange_client,left
|
||||||
|
bind=SUPER+SHIFT,L,exchange_client,right
|
||||||
|
bind=SUPER+SHIFT,K,exchange_client,up
|
||||||
|
bind=SUPER+SHIFT,J,exchange_client,down
|
||||||
|
|
||||||
# === Monitor Navigation ===
|
# === Monitor Navigation ===
|
||||||
# Focus Monitor Left
|
|
||||||
bind=SUPER+ALT,Left,focusmon,left
|
bind=SUPER+ALT,Left,focusmon,left
|
||||||
# Focus Monitor Right
|
|
||||||
bind=SUPER+ALT,Right,focusmon,right
|
bind=SUPER+ALT,Right,focusmon,right
|
||||||
# Move to Monitor Left
|
|
||||||
bind=SUPER+ALT+SHIFT,Left,tagmon,left
|
bind=SUPER+ALT+SHIFT,Left,tagmon,left
|
||||||
# Move to Monitor Right
|
|
||||||
bind=SUPER+ALT+SHIFT,Right,tagmon,right
|
bind=SUPER+ALT+SHIFT,Right,tagmon,right
|
||||||
|
|
||||||
# === Layout ===
|
# === Layout ===
|
||||||
# Cycle Layout
|
# Cycle Layout - Gaps, Floating, Tiling
|
||||||
bind=SUPER,j,switch_layout
|
bind=SUPER+ALT,j,switch_layout
|
||||||
# Increase Gaps
|
|
||||||
bind=SUPER+SHIFT,equal,incgaps,1
|
bind=SUPER+SHIFT,equal,incgaps,1
|
||||||
# Decrease Gaps
|
|
||||||
bind=SUPER+SHIFT,minus,incgaps,-1
|
bind=SUPER+SHIFT,minus,incgaps,-1
|
||||||
|
|
||||||
# === Tags (1-9): view tag ===
|
# === Tags (1-9): view tag ===
|
||||||
# View Tag 1
|
|
||||||
bind=SUPER,1,view,1
|
bind=SUPER,1,view,1
|
||||||
# View Tag 2
|
|
||||||
bind=SUPER,2,view,2
|
bind=SUPER,2,view,2
|
||||||
# View Tag 3
|
|
||||||
bind=SUPER,3,view,3
|
bind=SUPER,3,view,3
|
||||||
# View Tag 4
|
|
||||||
bind=SUPER,4,view,4
|
bind=SUPER,4,view,4
|
||||||
# View Tag 5
|
|
||||||
bind=SUPER,5,view,5
|
bind=SUPER,5,view,5
|
||||||
# View Tag 6
|
|
||||||
bind=SUPER,6,view,6
|
bind=SUPER,6,view,6
|
||||||
# View Tag 7
|
|
||||||
bind=SUPER,7,view,7
|
bind=SUPER,7,view,7
|
||||||
# View Tag 8
|
|
||||||
bind=SUPER,8,view,8
|
bind=SUPER,8,view,8
|
||||||
# View Tag 9
|
|
||||||
bind=SUPER,9,view,9
|
bind=SUPER,9,view,9
|
||||||
|
|
||||||
# === Tags (1-9): move focused window to tag ===
|
# === Tags (1-9): move focused window to tag ===
|
||||||
# Move to Tag 1
|
|
||||||
bind=SUPER+SHIFT,1,tag,1
|
bind=SUPER+SHIFT,1,tag,1
|
||||||
# Move to Tag 2
|
|
||||||
bind=SUPER+SHIFT,2,tag,2
|
bind=SUPER+SHIFT,2,tag,2
|
||||||
# Move to Tag 3
|
|
||||||
bind=SUPER+SHIFT,3,tag,3
|
bind=SUPER+SHIFT,3,tag,3
|
||||||
# Move to Tag 4
|
|
||||||
bind=SUPER+SHIFT,4,tag,4
|
bind=SUPER+SHIFT,4,tag,4
|
||||||
# Move to Tag 5
|
|
||||||
bind=SUPER+SHIFT,5,tag,5
|
bind=SUPER+SHIFT,5,tag,5
|
||||||
# Move to Tag 6
|
|
||||||
bind=SUPER+SHIFT,6,tag,6
|
bind=SUPER+SHIFT,6,tag,6
|
||||||
# Move to Tag 7
|
|
||||||
bind=SUPER+SHIFT,7,tag,7
|
bind=SUPER+SHIFT,7,tag,7
|
||||||
# Move to Tag 8
|
|
||||||
bind=SUPER+SHIFT,8,tag,8
|
bind=SUPER+SHIFT,8,tag,8
|
||||||
# Move to Tag 9
|
|
||||||
bind=SUPER+SHIFT,9,tag,9
|
bind=SUPER+SHIFT,9,tag,9
|
||||||
|
|
||||||
# === Touchpad Gestures ===
|
# === Touchpad Gestures ===
|
||||||
# Syntax: gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS
|
|
||||||
# 3-finger horizontal swipe: switch between occupied workspaces
|
# 3-finger horizontal swipe: switch between occupied workspaces
|
||||||
gesturebind=none,left,3,viewtoleft_have_client
|
gesturebind=none,right,3,viewtoleft_have_client
|
||||||
gesturebind=none,right,3,viewtoright_have_client
|
gesturebind=none,left,3,viewtoright_have_client
|
||||||
# 4-finger vertical swipe: toggle the overview
|
# 4-finger vertical swipe: toggle the overview
|
||||||
gesturebind=none,up,4,toggleoverview
|
gesturebind=none,up,4,toggleoverview
|
||||||
gesturebind=none,down,4,toggleoverview
|
gesturebind=none,down,4,toggleoverview
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
env=XDG_CURRENT_DESKTOP,mango
|
env=XDG_CURRENT_DESKTOP,mango
|
||||||
env=XDG_SESSION_TYPE,wayland
|
env=XDG_SESSION_TYPE,wayland
|
||||||
|
|
||||||
# exec_once runs only at startup. Do NOT use exec= for the shell: mango re-runs
|
# exec-once runs only at startup. Do NOT use exec= for the shell: mango re-runs
|
||||||
# every exec= on each config reload, and DMS reloads the config, which would
|
# every exec= on each config reload, and DMS reloads the config, which would
|
||||||
# spawn a new shell on every reload.
|
# spawn a new shell on every reload.
|
||||||
exec_once=dms run
|
exec-once=dms run
|
||||||
|
|
||||||
source=./dms/colors.conf
|
source=./dms/colors.conf
|
||||||
source=./dms/layout.conf
|
source=./dms/layout.conf
|
||||||
|
|||||||
@@ -2153,18 +2153,6 @@ vt = 1
|
|||||||
commandLine := fmt.Sprintf(`command = "%s"`, commandValue)
|
commandLine := fmt.Sprintf(`command = "%s"`, commandValue)
|
||||||
newConfig := upsertDefaultSession(configContent, greeterUser, commandLine)
|
newConfig := upsertDefaultSession(configContent, greeterUser, commandLine)
|
||||||
|
|
||||||
homeDir, homeErr := os.UserHomeDir()
|
|
||||||
if homeErr == nil {
|
|
||||||
enabled, loginUser, sessionExec, resolveErr := resolveGreeterAutoLoginState(GreeterCacheDir, homeDir)
|
|
||||||
if resolveErr != nil {
|
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to resolve greeter auto-login state: %v", resolveErr))
|
|
||||||
} else if enabled && loginUser != "" && sessionExec != "" {
|
|
||||||
newConfig = upsertInitialSession(newConfig, loginUser, sessionExec, true)
|
|
||||||
} else {
|
|
||||||
newConfig = upsertInitialSession(newConfig, "", "", false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writeGreetdConfig(configPath, newConfig, logFunc, sudoPassword, fmt.Sprintf("✓ Updated greetd configuration (user: %s, command: %s)", greeterUser, commandValue)); err != nil {
|
if err := writeGreetdConfig(configPath, newConfig, logFunc, sudoPassword, fmt.Sprintf("✓ Updated greetd configuration (user: %s, command: %s)", greeterUser, commandValue)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
@@ -228,11 +229,20 @@ func (m *MangoWCProvider) SetBind(key, action, description string, options map[s
|
|||||||
}
|
}
|
||||||
|
|
||||||
normalizedKey := strings.ToLower(key)
|
normalizedKey := strings.ToLower(key)
|
||||||
|
prefix := "bind"
|
||||||
|
if existing, ok := existingBinds[normalizedKey]; ok && existing.Prefix != "" {
|
||||||
|
prefix = existing.Prefix
|
||||||
|
}
|
||||||
|
if optionPrefix := m.bindPrefixFromOptions(options); optionPrefix != "" {
|
||||||
|
prefix = optionPrefix
|
||||||
|
}
|
||||||
|
|
||||||
existingBinds[normalizedKey] = &mangowcOverrideBind{
|
existingBinds[normalizedKey] = &mangowcOverrideBind{
|
||||||
Key: key,
|
Key: key,
|
||||||
Action: action,
|
Action: action,
|
||||||
Description: description,
|
Description: description,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
Prefix: prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.writeOverrideBinds(existingBinds)
|
return m.writeOverrideBinds(existingBinds)
|
||||||
@@ -246,7 +256,7 @@ func (m *MangoWCProvider) RemoveBind(key string) error {
|
|||||||
|
|
||||||
normalizedKey := strings.ToLower(key)
|
normalizedKey := strings.ToLower(key)
|
||||||
delete(existingBinds, normalizedKey)
|
delete(existingBinds, normalizedKey)
|
||||||
return m.writeOverrideBinds(existingBinds)
|
return m.writeOverrideBindsWithRemoved(existingBinds, map[string]bool{normalizedKey: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) ResetBind(key string) error {
|
func (m *MangoWCProvider) ResetBind(key string) error {
|
||||||
@@ -258,6 +268,7 @@ type mangowcOverrideBind struct {
|
|||||||
Action string
|
Action string
|
||||||
Description string
|
Description string
|
||||||
Options map[string]any
|
Options map[string]any
|
||||||
|
Prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) loadOverrideBinds() (map[string]*mangowcOverrideBind, error) {
|
func (m *MangoWCProvider) loadOverrideBinds() (map[string]*mangowcOverrideBind, error) {
|
||||||
@@ -272,62 +283,99 @@ func (m *MangoWCProvider) loadOverrideBinds() (map[string]*mangowcOverrideBind,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(string(data), "\n")
|
var pendingComment string
|
||||||
for _, line := range lines {
|
for _, line := range strings.Split(string(data), "\n") {
|
||||||
line = strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if trimmed == "" {
|
||||||
|
pendingComment = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(trimmed, "#") {
|
||||||
|
pendingComment = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||||
|
if isMangoWCSectionComment(pendingComment) {
|
||||||
|
pendingComment = ""
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(line, "bind") {
|
bind, ok := m.parseOverrideBindLine(line, pendingComment)
|
||||||
|
pendingComment = ""
|
||||||
|
if !ok || bind == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(line, "=", 2)
|
binds[strings.ToLower(bind.Key)] = bind
|
||||||
if len(parts) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
content := strings.TrimSpace(parts[1])
|
|
||||||
commentParts := strings.SplitN(content, "#", 2)
|
|
||||||
bindContent := strings.TrimSpace(commentParts[0])
|
|
||||||
|
|
||||||
var comment string
|
|
||||||
if len(commentParts) > 1 {
|
|
||||||
comment = strings.TrimSpace(commentParts[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.SplitN(bindContent, ",", 4)
|
|
||||||
if len(fields) < 3 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mods := strings.TrimSpace(fields[0])
|
|
||||||
keyName := strings.TrimSpace(fields[1])
|
|
||||||
command := strings.TrimSpace(fields[2])
|
|
||||||
|
|
||||||
var params string
|
|
||||||
if len(fields) > 3 {
|
|
||||||
params = strings.TrimSpace(fields[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
keyStr := m.buildKeyString(mods, keyName)
|
|
||||||
normalizedKey := strings.ToLower(keyStr)
|
|
||||||
action := command
|
|
||||||
if params != "" {
|
|
||||||
action = command + " " + params
|
|
||||||
}
|
|
||||||
|
|
||||||
binds[normalizedKey] = &mangowcOverrideBind{
|
|
||||||
Key: keyStr,
|
|
||||||
Action: action,
|
|
||||||
Description: comment,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return binds, nil
|
return binds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) parseOverrideBindLine(line, precedingComment string) (*mangowcOverrideBind, bool) {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
parts := strings.SplitN(trimmed, "=", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := strings.TrimSpace(parts[0])
|
||||||
|
if !m.isBindPrefix(prefix) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
content := strings.TrimSpace(parts[1])
|
||||||
|
commentParts := strings.SplitN(content, "#", 2)
|
||||||
|
bindContent := strings.TrimSpace(commentParts[0])
|
||||||
|
|
||||||
|
description := strings.TrimSpace(precedingComment)
|
||||||
|
if isMangoWCSectionComment(description) {
|
||||||
|
description = ""
|
||||||
|
}
|
||||||
|
if len(commentParts) > 1 {
|
||||||
|
description = strings.TrimSpace(commentParts[1])
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(description, MangoWCHideComment) {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.SplitN(bindContent, ",", 4)
|
||||||
|
if len(fields) < 3 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
mods := strings.TrimSpace(fields[0])
|
||||||
|
keyName := strings.TrimSpace(fields[1])
|
||||||
|
command := strings.TrimSpace(fields[2])
|
||||||
|
|
||||||
|
var params string
|
||||||
|
if len(fields) > 3 {
|
||||||
|
params = strings.TrimSpace(fields[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
action := command
|
||||||
|
if params != "" {
|
||||||
|
action = command + " " + params
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mangowcOverrideBind{
|
||||||
|
Key: m.buildKeyString(mods, keyName),
|
||||||
|
Action: action,
|
||||||
|
Description: description,
|
||||||
|
Prefix: prefix,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) isBindPrefix(prefix string) bool {
|
||||||
|
if !strings.HasPrefix(prefix, "bind") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ch := range strings.TrimPrefix(prefix, "bind") {
|
||||||
|
if !strings.ContainsRune("lsrp", ch) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) buildKeyString(mods, key string) string {
|
func (m *MangoWCProvider) buildKeyString(mods, key string) string {
|
||||||
if mods == "" || strings.EqualFold(mods, "none") {
|
if mods == "" || strings.EqualFold(mods, "none") {
|
||||||
return key
|
return key
|
||||||
@@ -362,21 +410,113 @@ func (m *MangoWCProvider) getBindSortPriority(action string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) writeOverrideBinds(binds map[string]*mangowcOverrideBind) error {
|
func (m *MangoWCProvider) writeOverrideBinds(binds map[string]*mangowcOverrideBind) error {
|
||||||
|
return m.writeOverrideBindsWithRemoved(binds, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) writeOverrideBindsWithRemoved(binds map[string]*mangowcOverrideBind, removed map[string]bool) error {
|
||||||
overridePath := m.GetOverridePath()
|
overridePath := m.GetOverridePath()
|
||||||
content := m.generateBindsContent(binds)
|
existingContent := ""
|
||||||
|
if data, err := os.ReadFile(overridePath); err == nil {
|
||||||
|
existingContent = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
content := m.generatePreservedBindsContent(existingContent, binds, removed)
|
||||||
return os.WriteFile(overridePath, []byte(content), 0o644)
|
return os.WriteFile(overridePath, []byte(content), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) generateBindsContent(binds map[string]*mangowcOverrideBind) string {
|
func (m *MangoWCProvider) generatePreservedBindsContent(existingContent string, binds map[string]*mangowcOverrideBind, removed map[string]bool) string {
|
||||||
if len(binds) == 0 {
|
useStockScaffold := m.shouldUseStockScaffold(existingContent)
|
||||||
return ""
|
source := existingContent
|
||||||
|
if useStockScaffold {
|
||||||
|
source = m.stockBindsScaffold(binds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remaining := make(map[string]*mangowcOverrideBind, len(binds))
|
||||||
|
for key, bind := range binds {
|
||||||
|
remaining[key] = bind
|
||||||
|
}
|
||||||
|
if useStockScaffold {
|
||||||
|
m.dropReplacedStockBinds(remaining)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines []string
|
||||||
|
for _, line := range strings.Split(source, "\n") {
|
||||||
|
templateBind, ok := m.parseOverrideBindLine(line, m.previousComment(lines))
|
||||||
|
if !ok || templateBind == nil {
|
||||||
|
lines = append(lines, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedKey := strings.ToLower(templateBind.Key)
|
||||||
|
m.dropPreviousDescriptionComment(&lines)
|
||||||
|
|
||||||
|
if bind, exists := remaining[normalizedKey]; exists {
|
||||||
|
if useStockScaffold && bind.Description == "" {
|
||||||
|
bind = m.copyBindWithDescription(bind, templateBind.Description)
|
||||||
|
}
|
||||||
|
m.writeBindLineToLines(&lines, bind)
|
||||||
|
delete(remaining, normalizedKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if useStockScaffold && !removed[normalizedKey] {
|
||||||
|
m.writeBindLineToLines(&lines, templateBind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(remaining) > 0 {
|
||||||
|
m.trimTrailingEmptyLines(&lines)
|
||||||
|
if len(lines) > 0 {
|
||||||
|
lines = append(lines, "")
|
||||||
|
}
|
||||||
|
lines = append(lines, "# === Custom Keybinds ===")
|
||||||
|
for _, bind := range m.sortedBinds(remaining) {
|
||||||
|
m.writeBindLineToLines(&lines, bind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.trimTrailingEmptyLines(&lines)
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.Join(lines, "\n") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) shouldUseStockScaffold(content string) bool {
|
||||||
|
if strings.TrimSpace(content) == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.Contains(content, "gesturebind=") && strings.Contains(content, "# ===") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !strings.Contains(content, "gesturebind=") && (strings.Count(content, "\nbind=")+strings.Count(content, "\nbindl=")+strings.Count(content, "\nbinds=")+strings.Count(content, "\nbindr=")+strings.Count(content, "\nbindp=") >= 10 || strings.Contains(content, "dms ipc call"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) stockBindsScaffold(binds map[string]*mangowcOverrideBind) string {
|
||||||
|
terminalCommand := "ghostty"
|
||||||
|
for _, key := range []string{"super+t", "super+return"} {
|
||||||
|
if bind, ok := binds[key]; ok {
|
||||||
|
command, params := m.parseAction(bind.Action)
|
||||||
|
if command == "spawn" && strings.TrimSpace(params) != "" && !strings.Contains(params, "dms ") {
|
||||||
|
terminalCommand = params
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.ReplaceAll(config.MangoBindsConfig, "{{TERMINAL_COMMAND}}", terminalCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) dropReplacedStockBinds(binds map[string]*mangowcOverrideBind) {
|
||||||
|
if bind, ok := binds["super+j"]; ok && bind.Action == "switch_layout" {
|
||||||
|
delete(binds, "super+j")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) sortedBinds(binds map[string]*mangowcOverrideBind) []*mangowcOverrideBind {
|
||||||
bindList := make([]*mangowcOverrideBind, 0, len(binds))
|
bindList := make([]*mangowcOverrideBind, 0, len(binds))
|
||||||
for _, bind := range binds {
|
for _, bind := range binds {
|
||||||
bindList = append(bindList, bind)
|
bindList = append(bindList, bind)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(bindList, func(i, j int) bool {
|
sort.Slice(bindList, func(i, j int) bool {
|
||||||
pi, pj := m.getBindSortPriority(bindList[i].Action), m.getBindSortPriority(bindList[j].Action)
|
pi, pj := m.getBindSortPriority(bindList[i].Action), m.getBindSortPriority(bindList[j].Action)
|
||||||
if pi != pj {
|
if pi != pj {
|
||||||
@@ -384,13 +524,55 @@ func (m *MangoWCProvider) generateBindsContent(binds map[string]*mangowcOverride
|
|||||||
}
|
}
|
||||||
return bindList[i].Key < bindList[j].Key
|
return bindList[i].Key < bindList[j].Key
|
||||||
})
|
})
|
||||||
|
return bindList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) writeBindLineToLines(lines *[]string, bind *mangowcOverrideBind) {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for _, bind := range bindList {
|
m.writeBindLine(&sb, bind)
|
||||||
m.writeBindLine(&sb, bind)
|
text := strings.TrimSuffix(sb.String(), "\n")
|
||||||
|
if text == "" {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
*lines = append(*lines, strings.Split(text, "\n")...)
|
||||||
|
}
|
||||||
|
|
||||||
return sb.String()
|
func (m *MangoWCProvider) previousComment(lines []string) string {
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimSpace(lines[len(lines)-1])
|
||||||
|
if !strings.HasPrefix(trimmed, "#") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
comment := strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||||
|
if isMangoWCSectionComment(comment) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return comment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) dropPreviousDescriptionComment(lines *[]string) {
|
||||||
|
if len(*lines) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimSpace((*lines)[len(*lines)-1])
|
||||||
|
if !strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, "# ===") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*lines = (*lines)[:len(*lines)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) trimTrailingEmptyLines(lines *[]string) {
|
||||||
|
for len(*lines) > 0 && strings.TrimSpace((*lines)[len(*lines)-1]) == "" {
|
||||||
|
*lines = (*lines)[:len(*lines)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) copyBindWithDescription(bind *mangowcOverrideBind, description string) *mangowcOverrideBind {
|
||||||
|
copy := *bind
|
||||||
|
copy.Description = description
|
||||||
|
return ©
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) writeBindLine(sb *strings.Builder, bind *mangowcOverrideBind) {
|
func (m *MangoWCProvider) writeBindLine(sb *strings.Builder, bind *mangowcOverrideBind) {
|
||||||
@@ -405,7 +587,12 @@ func (m *MangoWCProvider) writeBindLine(sb *strings.Builder, bind *mangowcOverri
|
|||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString("bind=")
|
prefix := bind.Prefix
|
||||||
|
if prefix == "" {
|
||||||
|
prefix = "bind"
|
||||||
|
}
|
||||||
|
sb.WriteString(prefix)
|
||||||
|
sb.WriteString("=")
|
||||||
if mods == "" {
|
if mods == "" {
|
||||||
sb.WriteString("none")
|
sb.WriteString("none")
|
||||||
} else {
|
} else {
|
||||||
@@ -424,6 +611,36 @@ func (m *MangoWCProvider) writeBindLine(sb *strings.Builder, bind *mangowcOverri
|
|||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MangoWCProvider) bindPrefixFromOptions(options map[string]any) string {
|
||||||
|
if options == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
value, ok := options["flags"]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
flags := ""
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
flags = v
|
||||||
|
case fmt.Stringer:
|
||||||
|
flags = v.String()
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
flags = strings.TrimSpace(flags)
|
||||||
|
if flags == "" {
|
||||||
|
return "bind"
|
||||||
|
}
|
||||||
|
var clean strings.Builder
|
||||||
|
for _, ch := range flags {
|
||||||
|
if strings.ContainsRune("lsrp", ch) && !strings.ContainsRune(clean.String(), ch) {
|
||||||
|
clean.WriteRune(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "bind" + clean.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) parseKeyString(keyStr string) (mods, key string) {
|
func (m *MangoWCProvider) parseKeyString(keyStr string) (mods, key string) {
|
||||||
parts := strings.Split(keyStr, "+")
|
parts := strings.Split(keyStr, "+")
|
||||||
switch len(parts) {
|
switch len(parts) {
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ const (
|
|||||||
|
|
||||||
var MangoWCModSeparators = []rune{'+', ' '}
|
var MangoWCModSeparators = []rune{'+', ' '}
|
||||||
|
|
||||||
|
func isMangoWCSectionComment(comment string) bool {
|
||||||
|
return strings.HasPrefix(strings.TrimSpace(comment), "===")
|
||||||
|
}
|
||||||
|
|
||||||
type MangoWCKeyBinding struct {
|
type MangoWCKeyBinding struct {
|
||||||
Mods []string `json:"mods"`
|
Mods []string `json:"mods"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
@@ -235,6 +239,9 @@ func (p *MangoWCParser) ParseKeys() []MangoWCKeyBinding {
|
|||||||
}
|
}
|
||||||
if strings.HasPrefix(trimmed, "#") {
|
if strings.HasPrefix(trimmed, "#") {
|
||||||
pendingComment = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
pendingComment = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||||
|
if isMangoWCSectionComment(pendingComment) {
|
||||||
|
pendingComment = ""
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(trimmed, "bind") {
|
if !strings.HasPrefix(trimmed, "bind") {
|
||||||
@@ -414,6 +421,9 @@ func (p *MangoWCParser) parseFileWithSource(filePath string) ([]MangoWCKeyBindin
|
|||||||
|
|
||||||
if strings.HasPrefix(trimmed, "#") {
|
if strings.HasPrefix(trimmed, "#") {
|
||||||
pendingComment = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
pendingComment = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||||
|
if isMangoWCSectionComment(pendingComment) {
|
||||||
|
pendingComment = ""
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,7 +493,7 @@ func (p *MangoWCParser) parseDMSBindsDirectly(dmsBindsPath string) []MangoWCKeyB
|
|||||||
// line directly above) is the description: mango feeds inline comments to spawn
|
// line directly above) is the description: mango feeds inline comments to spawn
|
||||||
// as argv, so DMS keeps descriptions on the line above; inline `#` is a fallback.
|
// as argv, so DMS keeps descriptions on the line above; inline `#` is a fallback.
|
||||||
func (p *MangoWCParser) getKeybindAtLineContent(line string, precedingComment string) *MangoWCKeyBinding {
|
func (p *MangoWCParser) getKeybindAtLineContent(line string, precedingComment string) *MangoWCKeyBinding {
|
||||||
bindMatch := regexp.MustCompile(`^(bind[lsr]*)\s*=\s*(.+)$`)
|
bindMatch := regexp.MustCompile(`^(bind[lsrp]*)\s*=\s*(.+)$`)
|
||||||
matches := bindMatch.FindStringSubmatch(line)
|
matches := bindMatch.FindStringSubmatch(line)
|
||||||
if len(matches) < 3 {
|
if len(matches) < 3 {
|
||||||
return nil
|
return nil
|
||||||
@@ -499,6 +509,9 @@ func (p *MangoWCParser) getKeybindAtLineContent(line string, precedingComment st
|
|||||||
}
|
}
|
||||||
if comment == "" {
|
if comment == "" {
|
||||||
comment = strings.TrimSpace(precedingComment)
|
comment = strings.TrimSpace(precedingComment)
|
||||||
|
if isMangoWCSectionComment(comment) {
|
||||||
|
comment = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(comment, MangoWCHideComment) {
|
if strings.HasPrefix(comment, MangoWCHideComment) {
|
||||||
|
|||||||
@@ -71,9 +71,10 @@ func TestMangoWCAutogenerateComment(t *testing.T) {
|
|||||||
|
|
||||||
func TestMangoWCGetKeybindAtLine(t *testing.T) {
|
func TestMangoWCGetKeybindAtLine(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
line string
|
line string
|
||||||
expected *MangoWCKeyBinding
|
precedingComment string
|
||||||
|
expected *MangoWCKeyBinding
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "basic_keybind",
|
name: "basic_keybind",
|
||||||
@@ -157,6 +158,41 @@ func TestMangoWCGetKeybindAtLine(t *testing.T) {
|
|||||||
Comment: "dms ipc call lock lock",
|
Comment: "dms ipc call lock lock",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "bindp_flag",
|
||||||
|
line: "bindp=SUPER,p,spawn,pass-through",
|
||||||
|
expected: &MangoWCKeyBinding{
|
||||||
|
Mods: []string{"SUPER"},
|
||||||
|
Key: "p",
|
||||||
|
Command: "spawn",
|
||||||
|
Params: "pass-through",
|
||||||
|
Comment: "pass-through",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "preceding_comment",
|
||||||
|
line: "bind=SUPER+SHIFT,S,spawn,dms screenshot",
|
||||||
|
precedingComment: "Screenshot: Interactive",
|
||||||
|
expected: &MangoWCKeyBinding{
|
||||||
|
Mods: []string{"SUPER", "SHIFT"},
|
||||||
|
Key: "S",
|
||||||
|
Command: "spawn",
|
||||||
|
Params: "dms screenshot",
|
||||||
|
Comment: "Screenshot: Interactive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "section_header_not_description",
|
||||||
|
line: "bind=none,XF86AudioRaiseVolume,spawn,dms ipc call audio increment 3",
|
||||||
|
precedingComment: "=== Audio Controls ===",
|
||||||
|
expected: &MangoWCKeyBinding{
|
||||||
|
Mods: []string{},
|
||||||
|
Key: "XF86AudioRaiseVolume",
|
||||||
|
Command: "spawn",
|
||||||
|
Params: "dms ipc call audio increment 3",
|
||||||
|
Comment: "dms ipc call audio increment 3",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "keybind_with_spaces",
|
name: "keybind_with_spaces",
|
||||||
line: "bind = SUPER, r, reload_config",
|
line: "bind = SUPER, r, reload_config",
|
||||||
@@ -174,7 +210,7 @@ func TestMangoWCGetKeybindAtLine(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
parser := NewMangoWCParser("")
|
parser := NewMangoWCParser("")
|
||||||
parser.contentLines = []string{tt.line}
|
parser.contentLines = []string{tt.line}
|
||||||
result := parser.getKeybindAtLine(0, "")
|
result := parser.getKeybindAtLine(0, tt.precedingComment)
|
||||||
|
|
||||||
if tt.expected == nil {
|
if tt.expected == nil {
|
||||||
if result != nil {
|
if result != nil {
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package providers
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMangoWCProviderName(t *testing.T) {
|
func TestMangoWCProviderName(t *testing.T) {
|
||||||
@@ -318,3 +321,138 @@ bind=Ctrl,1,view,1,0
|
|||||||
t.Error("Did not find terminal keybind with correct key and description")
|
t.Error("Did not find terminal keybind with correct key and description")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMangoWCSetBindPreservesStockCommentsAndGestures(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
dmsDir := filepath.Join(tmpDir, "dms")
|
||||||
|
if err := os.MkdirAll(dmsDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create dms dir: %v", err)
|
||||||
|
}
|
||||||
|
bindsPath := filepath.Join(dmsDir, "binds.conf")
|
||||||
|
stock := strings.ReplaceAll(config.MangoBindsConfig, "{{TERMINAL_COMMAND}}", "ghostty")
|
||||||
|
if err := os.WriteFile(bindsPath, []byte(stock), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write stock binds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := NewMangoWCProvider(tmpDir)
|
||||||
|
if err := provider.SetBind("SUPER+SHIFT+S", "spawn dms screenshot", "Screenshot: Interactive", nil); err != nil {
|
||||||
|
t.Fatalf("SetBind failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBytes, err := os.ReadFile(bindsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read binds: %v", err)
|
||||||
|
}
|
||||||
|
content := string(contentBytes)
|
||||||
|
|
||||||
|
for _, want := range []string{
|
||||||
|
"# === Application Launchers ===",
|
||||||
|
"# === Touchpad Gestures ===",
|
||||||
|
"gesturebind=none,right,3,viewtoleft_have_client",
|
||||||
|
"gesturebind=none,left,3,viewtoright_have_client",
|
||||||
|
"# Screenshot: Interactive\nbind=SUPER+SHIFT,S,spawn,dms screenshot",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(content, want) {
|
||||||
|
t.Fatalf("expected saved binds to contain %q\ncontent:\n%s", want, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(content, "# === Audio Controls ===\n# === Audio Controls ===") {
|
||||||
|
t.Fatalf("section header should not be duplicated as a bind description\ncontent:\n%s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMangoWCSetBindRestoresScaffoldForStrippedFile(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
dmsDir := filepath.Join(tmpDir, "dms")
|
||||||
|
if err := os.MkdirAll(dmsDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create dms dir: %v", err)
|
||||||
|
}
|
||||||
|
bindsPath := filepath.Join(dmsDir, "binds.conf")
|
||||||
|
stripped := `bind=SUPER,t,spawn,ghostty
|
||||||
|
bind=SUPER,Return,spawn,ghostty
|
||||||
|
bind=SUPER,space,spawn,dms ipc call spotlight toggle
|
||||||
|
bind=SUPER,v,spawn,dms ipc call clipboard toggle
|
||||||
|
bind=SUPER,q,killclient
|
||||||
|
bind=SUPER,Left,focusdir,left
|
||||||
|
bind=SUPER,Right,focusdir,right
|
||||||
|
bind=SUPER,Up,focusdir,up
|
||||||
|
bind=SUPER,Down,focusdir,down
|
||||||
|
bind=SUPER,1,view,1
|
||||||
|
bind=SUPER,2,view,2
|
||||||
|
bind=SUPER,3,view,3
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(bindsPath, []byte(stripped), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write stripped binds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := NewMangoWCProvider(tmpDir)
|
||||||
|
if err := provider.SetBind("SUPER+SHIFT+S", "spawn dms screenshot", "Screenshot: Interactive", nil); err != nil {
|
||||||
|
t.Fatalf("SetBind failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBytes, err := os.ReadFile(bindsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read binds: %v", err)
|
||||||
|
}
|
||||||
|
content := string(contentBytes)
|
||||||
|
|
||||||
|
for _, want := range []string{
|
||||||
|
"# DMS default keybinds (MangoWM)",
|
||||||
|
"# === Touchpad Gestures ===",
|
||||||
|
"gesturebind=none,right,3,viewtoleft_have_client",
|
||||||
|
"bind=SUPER,H,focusdir,left",
|
||||||
|
"bind=SUPER,J,focusdir,down",
|
||||||
|
"bind=SUPER,K,focusdir,up",
|
||||||
|
"bind=SUPER,L,focusdir,right",
|
||||||
|
"# === Custom Keybinds ===",
|
||||||
|
"# Screenshot: Interactive\nbind=SUPER+SHIFT,S,spawn,dms screenshot",
|
||||||
|
"bind=SUPER,t,spawn,ghostty",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(content, want) {
|
||||||
|
t.Fatalf("expected restored binds to contain %q\ncontent:\n%s", want, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(content, "{{TERMINAL_COMMAND}}") {
|
||||||
|
t.Fatalf("terminal placeholder should have been resolved\ncontent:\n%s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMangoWCRemoveBindPreservesNonBindLines(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
dmsDir := filepath.Join(tmpDir, "dms")
|
||||||
|
if err := os.MkdirAll(dmsDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create dms dir: %v", err)
|
||||||
|
}
|
||||||
|
bindsPath := filepath.Join(dmsDir, "binds.conf")
|
||||||
|
stock := strings.ReplaceAll(config.MangoBindsConfig, "{{TERMINAL_COMMAND}}", "ghostty")
|
||||||
|
if err := os.WriteFile(bindsPath, []byte(stock), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write stock binds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := NewMangoWCProvider(tmpDir)
|
||||||
|
if err := provider.RemoveBind("SUPER+Tab"); err != nil {
|
||||||
|
t.Fatalf("RemoveBind failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBytes, err := os.ReadFile(bindsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read binds: %v", err)
|
||||||
|
}
|
||||||
|
content := string(contentBytes)
|
||||||
|
|
||||||
|
if strings.Contains(content, "bind=SUPER,Tab,focusstack,next") {
|
||||||
|
t.Fatalf("removed bind should be absent\ncontent:\n%s", content)
|
||||||
|
}
|
||||||
|
if strings.Contains(content, "# Focus Next Window") {
|
||||||
|
t.Fatalf("removed bind description should be absent\ncontent:\n%s", content)
|
||||||
|
}
|
||||||
|
for _, want := range []string{
|
||||||
|
"# === Focus Navigation ===",
|
||||||
|
"# === Touchpad Gestures ===",
|
||||||
|
"gesturebind=none,down,4,toggleoverview",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(content, want) {
|
||||||
|
t.Fatalf("expected non-bind line %q to be preserved\ncontent:\n%s", want, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,791 +0,0 @@
|
|||||||
// Generated by go-wayland-scanner
|
|
||||||
// https://github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/cmd/go-wayland-scanner
|
|
||||||
// XML file : internal/proto/xml/dwl-ipc-unstable-v2.xml
|
|
||||||
//
|
|
||||||
// dwl_ipc_unstable_v2 Protocol Copyright:
|
|
||||||
|
|
||||||
package dwl_ipc
|
|
||||||
|
|
||||||
import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
|
||||||
|
|
||||||
// ZdwlIpcManagerV2InterfaceName is the name of the interface as it appears in the [client.Registry].
|
|
||||||
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
|
||||||
// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies.
|
|
||||||
const ZdwlIpcManagerV2InterfaceName = "zdwl_ipc_manager_v2"
|
|
||||||
|
|
||||||
// ZdwlIpcManagerV2 : manage dwl state
|
|
||||||
//
|
|
||||||
// This interface is exposed as a global in wl_registry.
|
|
||||||
//
|
|
||||||
// Clients can use this interface to get a dwl_ipc_output.
|
|
||||||
// After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events.
|
|
||||||
// The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client.
|
|
||||||
type ZdwlIpcManagerV2 struct {
|
|
||||||
client.BaseProxy
|
|
||||||
tagsHandler ZdwlIpcManagerV2TagsHandlerFunc
|
|
||||||
layoutHandler ZdwlIpcManagerV2LayoutHandlerFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewZdwlIpcManagerV2 : manage dwl state
|
|
||||||
//
|
|
||||||
// This interface is exposed as a global in wl_registry.
|
|
||||||
//
|
|
||||||
// Clients can use this interface to get a dwl_ipc_output.
|
|
||||||
// After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events.
|
|
||||||
// The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client.
|
|
||||||
func NewZdwlIpcManagerV2(ctx *client.Context) *ZdwlIpcManagerV2 {
|
|
||||||
zdwlIpcManagerV2 := &ZdwlIpcManagerV2{}
|
|
||||||
ctx.Register(zdwlIpcManagerV2)
|
|
||||||
return zdwlIpcManagerV2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release : release dwl_ipc_manager
|
|
||||||
//
|
|
||||||
// Indicates that the client will not the dwl_ipc_manager object anymore.
|
|
||||||
// Objects created through this instance are not affected.
|
|
||||||
func (i *ZdwlIpcManagerV2) Release() error {
|
|
||||||
defer i.MarkZombie()
|
|
||||||
const opcode = 0
|
|
||||||
const _reqBufLen = 8
|
|
||||||
var _reqBuf [_reqBufLen]byte
|
|
||||||
l := 0
|
|
||||||
client.PutUint32(_reqBuf[l:4], i.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
|
||||||
l += 4
|
|
||||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOutput : get a dwl_ipc_outout for a wl_output
|
|
||||||
//
|
|
||||||
// Get a dwl_ipc_outout for the specified wl_output.
|
|
||||||
func (i *ZdwlIpcManagerV2) GetOutput(output *client.Output) (*ZdwlIpcOutputV2, error) {
|
|
||||||
id := NewZdwlIpcOutputV2(i.Context())
|
|
||||||
const opcode = 1
|
|
||||||
const _reqBufLen = 8 + 4 + 4
|
|
||||||
var _reqBuf [_reqBufLen]byte
|
|
||||||
l := 0
|
|
||||||
client.PutUint32(_reqBuf[l:4], i.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], id.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], output.ID())
|
|
||||||
l += 4
|
|
||||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcManagerV2TagsEvent : Announces tag amount
|
|
||||||
//
|
|
||||||
// This event is sent after binding.
|
|
||||||
// A roundtrip after binding guarantees the client recieved all tags.
|
|
||||||
type ZdwlIpcManagerV2TagsEvent struct {
|
|
||||||
Amount uint32
|
|
||||||
}
|
|
||||||
type ZdwlIpcManagerV2TagsHandlerFunc func(ZdwlIpcManagerV2TagsEvent)
|
|
||||||
|
|
||||||
// SetTagsHandler : sets handler for ZdwlIpcManagerV2TagsEvent
|
|
||||||
func (i *ZdwlIpcManagerV2) SetTagsHandler(f ZdwlIpcManagerV2TagsHandlerFunc) {
|
|
||||||
i.tagsHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcManagerV2LayoutEvent : Announces a layout
|
|
||||||
//
|
|
||||||
// This event is sent after binding.
|
|
||||||
// A roundtrip after binding guarantees the client recieved all layouts.
|
|
||||||
type ZdwlIpcManagerV2LayoutEvent struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
type ZdwlIpcManagerV2LayoutHandlerFunc func(ZdwlIpcManagerV2LayoutEvent)
|
|
||||||
|
|
||||||
// SetLayoutHandler : sets handler for ZdwlIpcManagerV2LayoutEvent
|
|
||||||
func (i *ZdwlIpcManagerV2) SetLayoutHandler(f ZdwlIpcManagerV2LayoutHandlerFunc) {
|
|
||||||
i.layoutHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ZdwlIpcManagerV2) Dispatch(opcode uint32, fd int, data []byte) {
|
|
||||||
switch opcode {
|
|
||||||
case 0:
|
|
||||||
if i.tagsHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcManagerV2TagsEvent
|
|
||||||
l := 0
|
|
||||||
e.Amount = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.tagsHandler(e)
|
|
||||||
case 1:
|
|
||||||
if i.layoutHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcManagerV2LayoutEvent
|
|
||||||
l := 0
|
|
||||||
nameLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
|
||||||
l += 4
|
|
||||||
e.Name = client.String(data[l : l+nameLen])
|
|
||||||
l += nameLen
|
|
||||||
|
|
||||||
i.layoutHandler(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2InterfaceName is the name of the interface as it appears in the [client.Registry].
|
|
||||||
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
|
||||||
// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies.
|
|
||||||
const ZdwlIpcOutputV2InterfaceName = "zdwl_ipc_output_v2"
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2 : control dwl output
|
|
||||||
//
|
|
||||||
// Observe and control a dwl output.
|
|
||||||
//
|
|
||||||
// Events are double-buffered:
|
|
||||||
// Clients should cache events and redraw when a dwl_ipc_output.frame event is sent.
|
|
||||||
//
|
|
||||||
// Request are not double-buffered:
|
|
||||||
// The compositor will update immediately upon request.
|
|
||||||
type ZdwlIpcOutputV2 struct {
|
|
||||||
client.BaseProxy
|
|
||||||
toggleVisibilityHandler ZdwlIpcOutputV2ToggleVisibilityHandlerFunc
|
|
||||||
activeHandler ZdwlIpcOutputV2ActiveHandlerFunc
|
|
||||||
tagHandler ZdwlIpcOutputV2TagHandlerFunc
|
|
||||||
layoutHandler ZdwlIpcOutputV2LayoutHandlerFunc
|
|
||||||
titleHandler ZdwlIpcOutputV2TitleHandlerFunc
|
|
||||||
appidHandler ZdwlIpcOutputV2AppidHandlerFunc
|
|
||||||
layoutSymbolHandler ZdwlIpcOutputV2LayoutSymbolHandlerFunc
|
|
||||||
frameHandler ZdwlIpcOutputV2FrameHandlerFunc
|
|
||||||
fullscreenHandler ZdwlIpcOutputV2FullscreenHandlerFunc
|
|
||||||
floatingHandler ZdwlIpcOutputV2FloatingHandlerFunc
|
|
||||||
xHandler ZdwlIpcOutputV2XHandlerFunc
|
|
||||||
yHandler ZdwlIpcOutputV2YHandlerFunc
|
|
||||||
widthHandler ZdwlIpcOutputV2WidthHandlerFunc
|
|
||||||
heightHandler ZdwlIpcOutputV2HeightHandlerFunc
|
|
||||||
lastLayerHandler ZdwlIpcOutputV2LastLayerHandlerFunc
|
|
||||||
kbLayoutHandler ZdwlIpcOutputV2KbLayoutHandlerFunc
|
|
||||||
keymodeHandler ZdwlIpcOutputV2KeymodeHandlerFunc
|
|
||||||
scalefactorHandler ZdwlIpcOutputV2ScalefactorHandlerFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewZdwlIpcOutputV2 : control dwl output
|
|
||||||
//
|
|
||||||
// Observe and control a dwl output.
|
|
||||||
//
|
|
||||||
// Events are double-buffered:
|
|
||||||
// Clients should cache events and redraw when a dwl_ipc_output.frame event is sent.
|
|
||||||
//
|
|
||||||
// Request are not double-buffered:
|
|
||||||
// The compositor will update immediately upon request.
|
|
||||||
func NewZdwlIpcOutputV2(ctx *client.Context) *ZdwlIpcOutputV2 {
|
|
||||||
zdwlIpcOutputV2 := &ZdwlIpcOutputV2{}
|
|
||||||
ctx.Register(zdwlIpcOutputV2)
|
|
||||||
return zdwlIpcOutputV2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release : release dwl_ipc_outout
|
|
||||||
//
|
|
||||||
// Indicates to that the client no longer needs this dwl_ipc_output.
|
|
||||||
func (i *ZdwlIpcOutputV2) Release() error {
|
|
||||||
defer i.MarkZombie()
|
|
||||||
const opcode = 0
|
|
||||||
const _reqBufLen = 8
|
|
||||||
var _reqBuf [_reqBufLen]byte
|
|
||||||
l := 0
|
|
||||||
client.PutUint32(_reqBuf[l:4], i.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
|
||||||
l += 4
|
|
||||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTags : Set the active tags of this output
|
|
||||||
//
|
|
||||||
// tagmask: bitmask of the tags that should be set.
|
|
||||||
// toggleTagset: toggle the selected tagset, zero for invalid, nonzero for valid.
|
|
||||||
func (i *ZdwlIpcOutputV2) SetTags(tagmask, toggleTagset uint32) error {
|
|
||||||
const opcode = 1
|
|
||||||
const _reqBufLen = 8 + 4 + 4
|
|
||||||
var _reqBuf [_reqBufLen]byte
|
|
||||||
l := 0
|
|
||||||
client.PutUint32(_reqBuf[l:4], i.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(tagmask))
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(toggleTagset))
|
|
||||||
l += 4
|
|
||||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClientTags : Set the tags of the focused client.
|
|
||||||
//
|
|
||||||
// The tags are updated as follows:
|
|
||||||
// new_tags = (current_tags AND and_tags) XOR xor_tags
|
|
||||||
func (i *ZdwlIpcOutputV2) SetClientTags(andTags, xorTags uint32) error {
|
|
||||||
const opcode = 2
|
|
||||||
const _reqBufLen = 8 + 4 + 4
|
|
||||||
var _reqBuf [_reqBufLen]byte
|
|
||||||
l := 0
|
|
||||||
client.PutUint32(_reqBuf[l:4], i.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(andTags))
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(xorTags))
|
|
||||||
l += 4
|
|
||||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLayout : Set the layout of this output
|
|
||||||
//
|
|
||||||
// index: index of a layout recieved by dwl_ipc_manager.layout
|
|
||||||
func (i *ZdwlIpcOutputV2) SetLayout(index uint32) error {
|
|
||||||
const opcode = 3
|
|
||||||
const _reqBufLen = 8 + 4
|
|
||||||
var _reqBuf [_reqBufLen]byte
|
|
||||||
l := 0
|
|
||||||
client.PutUint32(_reqBuf[l:4], i.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(index))
|
|
||||||
l += 4
|
|
||||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quit : Quit mango
|
|
||||||
// This request allows clients to instruct the compositor to quit mango.
|
|
||||||
func (i *ZdwlIpcOutputV2) Quit() error {
|
|
||||||
const opcode = 4
|
|
||||||
const _reqBufLen = 8
|
|
||||||
var _reqBuf [_reqBufLen]byte
|
|
||||||
l := 0
|
|
||||||
client.PutUint32(_reqBuf[l:4], i.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
|
||||||
l += 4
|
|
||||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendDispatch : Set the active tags of this output
|
|
||||||
//
|
|
||||||
// dispatch: dispatch name.
|
|
||||||
// arg1: arg1.
|
|
||||||
// arg2: arg2.
|
|
||||||
// arg3: arg3.
|
|
||||||
// arg4: arg4.
|
|
||||||
// arg5: arg5.
|
|
||||||
func (i *ZdwlIpcOutputV2) SendDispatch(dispatch, arg1, arg2, arg3, arg4, arg5 string) error {
|
|
||||||
const opcode = 5
|
|
||||||
dispatchLen := client.PaddedLen(len(dispatch) + 1)
|
|
||||||
arg1Len := client.PaddedLen(len(arg1) + 1)
|
|
||||||
arg2Len := client.PaddedLen(len(arg2) + 1)
|
|
||||||
arg3Len := client.PaddedLen(len(arg3) + 1)
|
|
||||||
arg4Len := client.PaddedLen(len(arg4) + 1)
|
|
||||||
arg5Len := client.PaddedLen(len(arg5) + 1)
|
|
||||||
_reqBufLen := 8 + (4 + dispatchLen) + (4 + arg1Len) + (4 + arg2Len) + (4 + arg3Len) + (4 + arg4Len) + (4 + arg5Len)
|
|
||||||
_reqBuf := make([]byte, _reqBufLen)
|
|
||||||
l := 0
|
|
||||||
client.PutUint32(_reqBuf[l:4], i.ID())
|
|
||||||
l += 4
|
|
||||||
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
|
||||||
l += 4
|
|
||||||
client.PutString(_reqBuf[l:l+(4+dispatchLen)], dispatch)
|
|
||||||
l += (4 + dispatchLen)
|
|
||||||
client.PutString(_reqBuf[l:l+(4+arg1Len)], arg1)
|
|
||||||
l += (4 + arg1Len)
|
|
||||||
client.PutString(_reqBuf[l:l+(4+arg2Len)], arg2)
|
|
||||||
l += (4 + arg2Len)
|
|
||||||
client.PutString(_reqBuf[l:l+(4+arg3Len)], arg3)
|
|
||||||
l += (4 + arg3Len)
|
|
||||||
client.PutString(_reqBuf[l:l+(4+arg4Len)], arg4)
|
|
||||||
l += (4 + arg4Len)
|
|
||||||
client.PutString(_reqBuf[l:l+(4+arg5Len)], arg5)
|
|
||||||
l += (4 + arg5Len)
|
|
||||||
err := i.Context().WriteMsg(_reqBuf, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type ZdwlIpcOutputV2TagState uint32
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2TagState :
|
|
||||||
const (
|
|
||||||
// ZdwlIpcOutputV2TagStateNone : no state
|
|
||||||
ZdwlIpcOutputV2TagStateNone ZdwlIpcOutputV2TagState = 0
|
|
||||||
// ZdwlIpcOutputV2TagStateActive : tag is active
|
|
||||||
ZdwlIpcOutputV2TagStateActive ZdwlIpcOutputV2TagState = 1
|
|
||||||
// ZdwlIpcOutputV2TagStateUrgent : tag has at least one urgent client
|
|
||||||
ZdwlIpcOutputV2TagStateUrgent ZdwlIpcOutputV2TagState = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e ZdwlIpcOutputV2TagState) Name() string {
|
|
||||||
switch e {
|
|
||||||
case ZdwlIpcOutputV2TagStateNone:
|
|
||||||
return "none"
|
|
||||||
case ZdwlIpcOutputV2TagStateActive:
|
|
||||||
return "active"
|
|
||||||
case ZdwlIpcOutputV2TagStateUrgent:
|
|
||||||
return "urgent"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ZdwlIpcOutputV2TagState) Value() string {
|
|
||||||
switch e {
|
|
||||||
case ZdwlIpcOutputV2TagStateNone:
|
|
||||||
return "0"
|
|
||||||
case ZdwlIpcOutputV2TagStateActive:
|
|
||||||
return "1"
|
|
||||||
case ZdwlIpcOutputV2TagStateUrgent:
|
|
||||||
return "2"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ZdwlIpcOutputV2TagState) String() string {
|
|
||||||
return e.Name() + "=" + e.Value()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2ToggleVisibilityEvent : Toggle client visibilty
|
|
||||||
//
|
|
||||||
// Indicates the client should hide or show themselves.
|
|
||||||
// If the client is visible then hide, if hidden then show.
|
|
||||||
type ZdwlIpcOutputV2ToggleVisibilityEvent struct{}
|
|
||||||
type ZdwlIpcOutputV2ToggleVisibilityHandlerFunc func(ZdwlIpcOutputV2ToggleVisibilityEvent)
|
|
||||||
|
|
||||||
// SetToggleVisibilityHandler : sets handler for ZdwlIpcOutputV2ToggleVisibilityEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetToggleVisibilityHandler(f ZdwlIpcOutputV2ToggleVisibilityHandlerFunc) {
|
|
||||||
i.toggleVisibilityHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2ActiveEvent : Update the selected output.
|
|
||||||
//
|
|
||||||
// Indicates if the output is active. Zero is invalid, nonzero is valid.
|
|
||||||
type ZdwlIpcOutputV2ActiveEvent struct {
|
|
||||||
Active uint32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2ActiveHandlerFunc func(ZdwlIpcOutputV2ActiveEvent)
|
|
||||||
|
|
||||||
// SetActiveHandler : sets handler for ZdwlIpcOutputV2ActiveEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetActiveHandler(f ZdwlIpcOutputV2ActiveHandlerFunc) {
|
|
||||||
i.activeHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2TagEvent : Update the state of a tag.
|
|
||||||
//
|
|
||||||
// Indicates that a tag has been updated.
|
|
||||||
type ZdwlIpcOutputV2TagEvent struct {
|
|
||||||
Tag uint32
|
|
||||||
State uint32
|
|
||||||
Clients uint32
|
|
||||||
Focused uint32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2TagHandlerFunc func(ZdwlIpcOutputV2TagEvent)
|
|
||||||
|
|
||||||
// SetTagHandler : sets handler for ZdwlIpcOutputV2TagEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetTagHandler(f ZdwlIpcOutputV2TagHandlerFunc) {
|
|
||||||
i.tagHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2LayoutEvent : Update the layout.
|
|
||||||
//
|
|
||||||
// Indicates a new layout is selected.
|
|
||||||
type ZdwlIpcOutputV2LayoutEvent struct {
|
|
||||||
Layout uint32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2LayoutHandlerFunc func(ZdwlIpcOutputV2LayoutEvent)
|
|
||||||
|
|
||||||
// SetLayoutHandler : sets handler for ZdwlIpcOutputV2LayoutEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetLayoutHandler(f ZdwlIpcOutputV2LayoutHandlerFunc) {
|
|
||||||
i.layoutHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2TitleEvent : Update the title.
|
|
||||||
//
|
|
||||||
// Indicates the title has changed.
|
|
||||||
type ZdwlIpcOutputV2TitleEvent struct {
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2TitleHandlerFunc func(ZdwlIpcOutputV2TitleEvent)
|
|
||||||
|
|
||||||
// SetTitleHandler : sets handler for ZdwlIpcOutputV2TitleEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetTitleHandler(f ZdwlIpcOutputV2TitleHandlerFunc) {
|
|
||||||
i.titleHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2AppidEvent : Update the appid.
|
|
||||||
//
|
|
||||||
// Indicates the appid has changed.
|
|
||||||
type ZdwlIpcOutputV2AppidEvent struct {
|
|
||||||
Appid string
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2AppidHandlerFunc func(ZdwlIpcOutputV2AppidEvent)
|
|
||||||
|
|
||||||
// SetAppidHandler : sets handler for ZdwlIpcOutputV2AppidEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetAppidHandler(f ZdwlIpcOutputV2AppidHandlerFunc) {
|
|
||||||
i.appidHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2LayoutSymbolEvent : Update the current layout symbol
|
|
||||||
//
|
|
||||||
// Indicates the layout has changed. Since layout symbols are dynamic.
|
|
||||||
// As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying.
|
|
||||||
// You can ignore the zdwl_ipc_output.layout event.
|
|
||||||
type ZdwlIpcOutputV2LayoutSymbolEvent struct {
|
|
||||||
Layout string
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2LayoutSymbolHandlerFunc func(ZdwlIpcOutputV2LayoutSymbolEvent)
|
|
||||||
|
|
||||||
// SetLayoutSymbolHandler : sets handler for ZdwlIpcOutputV2LayoutSymbolEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetLayoutSymbolHandler(f ZdwlIpcOutputV2LayoutSymbolHandlerFunc) {
|
|
||||||
i.layoutSymbolHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2FrameEvent : The update sequence is done.
|
|
||||||
//
|
|
||||||
// Indicates that a sequence of status updates have finished and the client should redraw.
|
|
||||||
type ZdwlIpcOutputV2FrameEvent struct{}
|
|
||||||
type ZdwlIpcOutputV2FrameHandlerFunc func(ZdwlIpcOutputV2FrameEvent)
|
|
||||||
|
|
||||||
// SetFrameHandler : sets handler for ZdwlIpcOutputV2FrameEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetFrameHandler(f ZdwlIpcOutputV2FrameHandlerFunc) {
|
|
||||||
i.frameHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2FullscreenEvent : Update fullscreen status
|
|
||||||
//
|
|
||||||
// Indicates if the selected client on this output is fullscreen.
|
|
||||||
type ZdwlIpcOutputV2FullscreenEvent struct {
|
|
||||||
IsFullscreen uint32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2FullscreenHandlerFunc func(ZdwlIpcOutputV2FullscreenEvent)
|
|
||||||
|
|
||||||
// SetFullscreenHandler : sets handler for ZdwlIpcOutputV2FullscreenEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetFullscreenHandler(f ZdwlIpcOutputV2FullscreenHandlerFunc) {
|
|
||||||
i.fullscreenHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2FloatingEvent : Update the floating status
|
|
||||||
//
|
|
||||||
// Indicates if the selected client on this output is floating.
|
|
||||||
type ZdwlIpcOutputV2FloatingEvent struct {
|
|
||||||
IsFloating uint32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2FloatingHandlerFunc func(ZdwlIpcOutputV2FloatingEvent)
|
|
||||||
|
|
||||||
// SetFloatingHandler : sets handler for ZdwlIpcOutputV2FloatingEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetFloatingHandler(f ZdwlIpcOutputV2FloatingHandlerFunc) {
|
|
||||||
i.floatingHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2XEvent : Update the x coordinates
|
|
||||||
//
|
|
||||||
// Indicates if x coordinates of the selected client.
|
|
||||||
type ZdwlIpcOutputV2XEvent struct {
|
|
||||||
X int32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2XHandlerFunc func(ZdwlIpcOutputV2XEvent)
|
|
||||||
|
|
||||||
// SetXHandler : sets handler for ZdwlIpcOutputV2XEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetXHandler(f ZdwlIpcOutputV2XHandlerFunc) {
|
|
||||||
i.xHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2YEvent : Update the y coordinates
|
|
||||||
//
|
|
||||||
// Indicates if y coordinates of the selected client.
|
|
||||||
type ZdwlIpcOutputV2YEvent struct {
|
|
||||||
Y int32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2YHandlerFunc func(ZdwlIpcOutputV2YEvent)
|
|
||||||
|
|
||||||
// SetYHandler : sets handler for ZdwlIpcOutputV2YEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetYHandler(f ZdwlIpcOutputV2YHandlerFunc) {
|
|
||||||
i.yHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2WidthEvent : Update the width
|
|
||||||
//
|
|
||||||
// Indicates if width of the selected client.
|
|
||||||
type ZdwlIpcOutputV2WidthEvent struct {
|
|
||||||
Width int32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2WidthHandlerFunc func(ZdwlIpcOutputV2WidthEvent)
|
|
||||||
|
|
||||||
// SetWidthHandler : sets handler for ZdwlIpcOutputV2WidthEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetWidthHandler(f ZdwlIpcOutputV2WidthHandlerFunc) {
|
|
||||||
i.widthHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2HeightEvent : Update the height
|
|
||||||
//
|
|
||||||
// Indicates if height of the selected client.
|
|
||||||
type ZdwlIpcOutputV2HeightEvent struct {
|
|
||||||
Height int32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2HeightHandlerFunc func(ZdwlIpcOutputV2HeightEvent)
|
|
||||||
|
|
||||||
// SetHeightHandler : sets handler for ZdwlIpcOutputV2HeightEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetHeightHandler(f ZdwlIpcOutputV2HeightHandlerFunc) {
|
|
||||||
i.heightHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2LastLayerEvent : last map layer.
|
|
||||||
//
|
|
||||||
// last map layer.
|
|
||||||
type ZdwlIpcOutputV2LastLayerEvent struct {
|
|
||||||
LastLayer string
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2LastLayerHandlerFunc func(ZdwlIpcOutputV2LastLayerEvent)
|
|
||||||
|
|
||||||
// SetLastLayerHandler : sets handler for ZdwlIpcOutputV2LastLayerEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetLastLayerHandler(f ZdwlIpcOutputV2LastLayerHandlerFunc) {
|
|
||||||
i.lastLayerHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2KbLayoutEvent : current keyboard layout.
|
|
||||||
//
|
|
||||||
// current keyboard layout.
|
|
||||||
type ZdwlIpcOutputV2KbLayoutEvent struct {
|
|
||||||
KbLayout string
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2KbLayoutHandlerFunc func(ZdwlIpcOutputV2KbLayoutEvent)
|
|
||||||
|
|
||||||
// SetKbLayoutHandler : sets handler for ZdwlIpcOutputV2KbLayoutEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetKbLayoutHandler(f ZdwlIpcOutputV2KbLayoutHandlerFunc) {
|
|
||||||
i.kbLayoutHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2KeymodeEvent : current keybind mode.
|
|
||||||
//
|
|
||||||
// current keybind mode.
|
|
||||||
type ZdwlIpcOutputV2KeymodeEvent struct {
|
|
||||||
Keymode string
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2KeymodeHandlerFunc func(ZdwlIpcOutputV2KeymodeEvent)
|
|
||||||
|
|
||||||
// SetKeymodeHandler : sets handler for ZdwlIpcOutputV2KeymodeEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetKeymodeHandler(f ZdwlIpcOutputV2KeymodeHandlerFunc) {
|
|
||||||
i.keymodeHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZdwlIpcOutputV2ScalefactorEvent : scale factor of monitor.
|
|
||||||
//
|
|
||||||
// scale factor of monitor.
|
|
||||||
type ZdwlIpcOutputV2ScalefactorEvent struct {
|
|
||||||
Scalefactor uint32
|
|
||||||
}
|
|
||||||
type ZdwlIpcOutputV2ScalefactorHandlerFunc func(ZdwlIpcOutputV2ScalefactorEvent)
|
|
||||||
|
|
||||||
// SetScalefactorHandler : sets handler for ZdwlIpcOutputV2ScalefactorEvent
|
|
||||||
func (i *ZdwlIpcOutputV2) SetScalefactorHandler(f ZdwlIpcOutputV2ScalefactorHandlerFunc) {
|
|
||||||
i.scalefactorHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ZdwlIpcOutputV2) Dispatch(opcode uint32, fd int, data []byte) {
|
|
||||||
switch opcode {
|
|
||||||
case 0:
|
|
||||||
if i.toggleVisibilityHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2ToggleVisibilityEvent
|
|
||||||
|
|
||||||
i.toggleVisibilityHandler(e)
|
|
||||||
case 1:
|
|
||||||
if i.activeHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2ActiveEvent
|
|
||||||
l := 0
|
|
||||||
e.Active = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.activeHandler(e)
|
|
||||||
case 2:
|
|
||||||
if i.tagHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2TagEvent
|
|
||||||
l := 0
|
|
||||||
e.Tag = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
e.State = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
e.Clients = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
e.Focused = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.tagHandler(e)
|
|
||||||
case 3:
|
|
||||||
if i.layoutHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2LayoutEvent
|
|
||||||
l := 0
|
|
||||||
e.Layout = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.layoutHandler(e)
|
|
||||||
case 4:
|
|
||||||
if i.titleHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2TitleEvent
|
|
||||||
l := 0
|
|
||||||
titleLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
|
||||||
l += 4
|
|
||||||
e.Title = client.String(data[l : l+titleLen])
|
|
||||||
l += titleLen
|
|
||||||
|
|
||||||
i.titleHandler(e)
|
|
||||||
case 5:
|
|
||||||
if i.appidHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2AppidEvent
|
|
||||||
l := 0
|
|
||||||
appidLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
|
||||||
l += 4
|
|
||||||
e.Appid = client.String(data[l : l+appidLen])
|
|
||||||
l += appidLen
|
|
||||||
|
|
||||||
i.appidHandler(e)
|
|
||||||
case 6:
|
|
||||||
if i.layoutSymbolHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2LayoutSymbolEvent
|
|
||||||
l := 0
|
|
||||||
layoutLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
|
||||||
l += 4
|
|
||||||
e.Layout = client.String(data[l : l+layoutLen])
|
|
||||||
l += layoutLen
|
|
||||||
|
|
||||||
i.layoutSymbolHandler(e)
|
|
||||||
case 7:
|
|
||||||
if i.frameHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2FrameEvent
|
|
||||||
|
|
||||||
i.frameHandler(e)
|
|
||||||
case 8:
|
|
||||||
if i.fullscreenHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2FullscreenEvent
|
|
||||||
l := 0
|
|
||||||
e.IsFullscreen = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.fullscreenHandler(e)
|
|
||||||
case 9:
|
|
||||||
if i.floatingHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2FloatingEvent
|
|
||||||
l := 0
|
|
||||||
e.IsFloating = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.floatingHandler(e)
|
|
||||||
case 10:
|
|
||||||
if i.xHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2XEvent
|
|
||||||
l := 0
|
|
||||||
e.X = int32(client.Uint32(data[l : l+4]))
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.xHandler(e)
|
|
||||||
case 11:
|
|
||||||
if i.yHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2YEvent
|
|
||||||
l := 0
|
|
||||||
e.Y = int32(client.Uint32(data[l : l+4]))
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.yHandler(e)
|
|
||||||
case 12:
|
|
||||||
if i.widthHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2WidthEvent
|
|
||||||
l := 0
|
|
||||||
e.Width = int32(client.Uint32(data[l : l+4]))
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.widthHandler(e)
|
|
||||||
case 13:
|
|
||||||
if i.heightHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2HeightEvent
|
|
||||||
l := 0
|
|
||||||
e.Height = int32(client.Uint32(data[l : l+4]))
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.heightHandler(e)
|
|
||||||
case 14:
|
|
||||||
if i.lastLayerHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2LastLayerEvent
|
|
||||||
l := 0
|
|
||||||
lastLayerLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
|
||||||
l += 4
|
|
||||||
e.LastLayer = client.String(data[l : l+lastLayerLen])
|
|
||||||
l += lastLayerLen
|
|
||||||
|
|
||||||
i.lastLayerHandler(e)
|
|
||||||
case 15:
|
|
||||||
if i.kbLayoutHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2KbLayoutEvent
|
|
||||||
l := 0
|
|
||||||
kbLayoutLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
|
||||||
l += 4
|
|
||||||
e.KbLayout = client.String(data[l : l+kbLayoutLen])
|
|
||||||
l += kbLayoutLen
|
|
||||||
|
|
||||||
i.kbLayoutHandler(e)
|
|
||||||
case 16:
|
|
||||||
if i.keymodeHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2KeymodeEvent
|
|
||||||
l := 0
|
|
||||||
keymodeLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
|
||||||
l += 4
|
|
||||||
e.Keymode = client.String(data[l : l+keymodeLen])
|
|
||||||
l += keymodeLen
|
|
||||||
|
|
||||||
i.keymodeHandler(e)
|
|
||||||
case 17:
|
|
||||||
if i.scalefactorHandler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var e ZdwlIpcOutputV2ScalefactorEvent
|
|
||||||
l := 0
|
|
||||||
e.Scalefactor = client.Uint32(data[l : l+4])
|
|
||||||
l += 4
|
|
||||||
|
|
||||||
i.scalefactorHandler(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package qmlchecks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLockScreenPasswordFieldBypassesTextInputIME(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("../../../quickshell/Modules/Lock/LockScreenContent.qml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read lock screen QML: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(data)
|
||||||
|
textInputPasswordField := regexp.MustCompile(`(?s)TextInput\s*\{[^{}]*id:\s*passwordField`)
|
||||||
|
if textInputPasswordField.MatchString(content) {
|
||||||
|
t.Fatalf("passwordField must not be a TextInput because TextInput can route physical keyboard input through IME")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(content, "Keys.onPressed") || !strings.Contains(content, "event.text") {
|
||||||
|
t.Fatalf("passwordField should handle physical key text manually instead of relying on a text input control")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
||||||
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
@@ -19,9 +18,9 @@ const (
|
|||||||
CompositorHyprland
|
CompositorHyprland
|
||||||
CompositorSway
|
CompositorSway
|
||||||
CompositorNiri
|
CompositorNiri
|
||||||
CompositorDWL
|
|
||||||
CompositorScroll
|
CompositorScroll
|
||||||
CompositorMiracle
|
CompositorMiracle
|
||||||
|
CompositorMango
|
||||||
)
|
)
|
||||||
|
|
||||||
var detectedCompositor Compositor = -1
|
var detectedCompositor Compositor = -1
|
||||||
@@ -36,8 +35,14 @@ func DetectCompositor() Compositor {
|
|||||||
swaySocket := os.Getenv("SWAYSOCK")
|
swaySocket := os.Getenv("SWAYSOCK")
|
||||||
scrollSocket := os.Getenv("SCROLLSOCK")
|
scrollSocket := os.Getenv("SCROLLSOCK")
|
||||||
miracleSocket := os.Getenv("MIRACLESOCK")
|
miracleSocket := os.Getenv("MIRACLESOCK")
|
||||||
|
mangoSocket := os.Getenv("MANGO_INSTANCE_SIGNATURE")
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case mangoSocket != "":
|
||||||
|
if _, err := os.Stat(mangoSocket); err == nil {
|
||||||
|
detectedCompositor = CompositorMango
|
||||||
|
return detectedCompositor
|
||||||
|
}
|
||||||
case niriSocket != "":
|
case niriSocket != "":
|
||||||
if _, err := os.Stat(niriSocket); err == nil {
|
if _, err := os.Stat(niriSocket); err == nil {
|
||||||
detectedCompositor = CompositorNiri
|
detectedCompositor = CompositorNiri
|
||||||
@@ -63,66 +68,29 @@ func DetectCompositor() Compositor {
|
|||||||
return detectedCompositor
|
return detectedCompositor
|
||||||
}
|
}
|
||||||
|
|
||||||
if detectDWLProtocol() {
|
|
||||||
detectedCompositor = CompositorDWL
|
|
||||||
return detectedCompositor
|
|
||||||
}
|
|
||||||
|
|
||||||
detectedCompositor = CompositorUnknown
|
detectedCompositor = CompositorUnknown
|
||||||
return detectedCompositor
|
return detectedCompositor
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectDWLProtocol() bool {
|
|
||||||
display, err := client.Connect("")
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ctx := display.Context()
|
|
||||||
defer ctx.Close()
|
|
||||||
|
|
||||||
registry, err := display.GetRegistry()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
|
||||||
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
|
||||||
if e.Interface == dwl_ipc.ZdwlIpcManagerV2InterfaceName {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetCompositorDWL() {
|
|
||||||
detectedCompositor = CompositorDWL
|
|
||||||
}
|
|
||||||
|
|
||||||
type WindowGeometry struct {
|
type WindowGeometry struct {
|
||||||
X int32
|
X int32
|
||||||
Y int32
|
Y int32
|
||||||
Width int32
|
Width int32
|
||||||
Height int32
|
Height int32
|
||||||
Output string
|
Output string
|
||||||
Scale float64
|
Scale float64
|
||||||
OutputX int32
|
OutputX int32
|
||||||
OutputY int32
|
OutputY int32
|
||||||
OutputTransform int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetActiveWindow() (*WindowGeometry, error) {
|
func GetActiveWindow() (*WindowGeometry, error) {
|
||||||
switch DetectCompositor() {
|
switch DetectCompositor() {
|
||||||
case CompositorHyprland:
|
case CompositorHyprland:
|
||||||
return getHyprlandActiveWindow()
|
return getHyprlandActiveWindow()
|
||||||
case CompositorDWL:
|
case CompositorMango:
|
||||||
return getDWLActiveWindow()
|
return getMangoActiveWindow()
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("window capture requires Hyprland or DWL")
|
return nil, fmt.Errorf("window capture requires Hyprland or Mango")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +253,93 @@ func getMiracleFocusedMonitor() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mangoMonitor struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
X int32 `json:"x"`
|
||||||
|
Y int32 `json:"y"`
|
||||||
|
Scale float64 `json:"scale"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMangoMonitors() []mangoMonitor {
|
||||||
|
output, err := exec.Command("mmsg", "get", "all-monitors").Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Monitors []mangoMonitor `json:"monitors"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(output, &data); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data.Monitors
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMangoFocusedMonitor() string {
|
||||||
|
for _, m := range getMangoMonitors() {
|
||||||
|
if m.Active {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type mangoClient struct {
|
||||||
|
Monitor string `json:"monitor"`
|
||||||
|
IsFocused bool `json:"is_focused"`
|
||||||
|
X int32 `json:"x"`
|
||||||
|
Y int32 `json:"y"`
|
||||||
|
Width int32 `json:"width"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMangoActiveWindow() (*WindowGeometry, error) {
|
||||||
|
output, err := exec.Command("mmsg", "get", "all-clients").Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("mmsg get all-clients: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Clients []mangoClient `json:"clients"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(output, &data); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse all-clients: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range data.Clients {
|
||||||
|
if !c.IsFocused {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.Width <= 0 || c.Height <= 0 {
|
||||||
|
return nil, fmt.Errorf("no active window")
|
||||||
|
}
|
||||||
|
|
||||||
|
geom := &WindowGeometry{
|
||||||
|
X: c.X,
|
||||||
|
Y: c.Y,
|
||||||
|
Width: c.Width,
|
||||||
|
Height: c.Height,
|
||||||
|
Output: c.Monitor,
|
||||||
|
Scale: 1.0,
|
||||||
|
}
|
||||||
|
for _, m := range getMangoMonitors() {
|
||||||
|
if m.Name != c.Monitor {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
geom.OutputX = m.X
|
||||||
|
geom.OutputY = m.Y
|
||||||
|
if m.Scale > 0 {
|
||||||
|
geom.Scale = m.Scale
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return geom, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no focused window")
|
||||||
|
}
|
||||||
|
|
||||||
type niriWorkspace struct {
|
type niriWorkspace struct {
|
||||||
Output string `json:"output"`
|
Output string `json:"output"`
|
||||||
IsFocused bool `json:"is_focused"`
|
IsFocused bool `json:"is_focused"`
|
||||||
@@ -309,121 +364,6 @@ func getNiriFocusedMonitor() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var dwlActiveOutput string
|
|
||||||
|
|
||||||
func SetDWLActiveOutput(name string) {
|
|
||||||
dwlActiveOutput = name
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDWLFocusedMonitor() string {
|
|
||||||
if dwlActiveOutput != "" {
|
|
||||||
return dwlActiveOutput
|
|
||||||
}
|
|
||||||
return queryDWLActiveOutput()
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryDWLActiveOutput() string {
|
|
||||||
display, err := client.Connect("")
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ctx := display.Context()
|
|
||||||
defer ctx.Close()
|
|
||||||
|
|
||||||
registry, err := display.GetRegistry()
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var dwlManager *dwl_ipc.ZdwlIpcManagerV2
|
|
||||||
outputs := make(map[uint32]*client.Output)
|
|
||||||
|
|
||||||
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
|
||||||
switch e.Interface {
|
|
||||||
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
|
||||||
mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx)
|
|
||||||
if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
|
||||||
dwlManager = mgr
|
|
||||||
}
|
|
||||||
case client.OutputInterfaceName:
|
|
||||||
out := client.NewOutput(ctx)
|
|
||||||
version := e.Version
|
|
||||||
if version > 4 {
|
|
||||||
version = 4
|
|
||||||
}
|
|
||||||
if err := registry.Bind(e.Name, e.Interface, version, out); err == nil {
|
|
||||||
outputs[e.Name] = out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if dwlManager == nil || len(outputs) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
outputNames := make(map[uint32]string)
|
|
||||||
for name, out := range outputs {
|
|
||||||
n := name
|
|
||||||
out.SetNameHandler(func(e client.OutputNameEvent) {
|
|
||||||
outputNames[n] = e.Name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type outputState struct {
|
|
||||||
name string
|
|
||||||
active bool
|
|
||||||
gotFrame bool
|
|
||||||
}
|
|
||||||
states := make(map[uint32]*outputState)
|
|
||||||
|
|
||||||
for name, out := range outputs {
|
|
||||||
dwlOut, err := dwlManager.GetOutput(out)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
state := &outputState{name: outputNames[name]}
|
|
||||||
states[name] = state
|
|
||||||
|
|
||||||
dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
|
||||||
state.active = e.Active != 0
|
|
||||||
})
|
|
||||||
dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
|
||||||
state.gotFrame = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
allFramesReceived := func() bool {
|
|
||||||
for _, s := range states {
|
|
||||||
if !s.gotFrame {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for !allFramesReceived() {
|
|
||||||
if err := ctx.Dispatch(); err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, state := range states {
|
|
||||||
if state.active {
|
|
||||||
return state.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFocusedMonitor() string {
|
func GetFocusedMonitor() string {
|
||||||
switch DetectCompositor() {
|
switch DetectCompositor() {
|
||||||
case CompositorHyprland:
|
case CompositorHyprland:
|
||||||
@@ -436,8 +376,8 @@ func GetFocusedMonitor() string {
|
|||||||
return getMiracleFocusedMonitor()
|
return getMiracleFocusedMonitor()
|
||||||
case CompositorNiri:
|
case CompositorNiri:
|
||||||
return getNiriFocusedMonitor()
|
return getNiriFocusedMonitor()
|
||||||
case CompositorDWL:
|
case CompositorMango:
|
||||||
return getDWLFocusedMonitor()
|
return getMangoFocusedMonitor()
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -534,161 +474,3 @@ func getAllOutputInfos() map[string]*outputInfo {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
display, err := client.Connect("")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("connect: %w", err)
|
|
||||||
}
|
|
||||||
ctx := display.Context()
|
|
||||||
defer ctx.Close()
|
|
||||||
|
|
||||||
registry, err := display.GetRegistry()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("get registry: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var dwlManager *dwl_ipc.ZdwlIpcManagerV2
|
|
||||||
outputs := make(map[uint32]*client.Output)
|
|
||||||
|
|
||||||
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
|
||||||
switch e.Interface {
|
|
||||||
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
|
||||||
mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx)
|
|
||||||
if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
|
||||||
dwlManager = mgr
|
|
||||||
}
|
|
||||||
case client.OutputInterfaceName:
|
|
||||||
out := client.NewOutput(ctx)
|
|
||||||
version := e.Version
|
|
||||||
if version > 4 {
|
|
||||||
version = 4
|
|
||||||
}
|
|
||||||
if err := registry.Bind(e.Name, e.Interface, version, out); err == nil {
|
|
||||||
outputs[e.Name] = out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
|
||||||
return nil, fmt.Errorf("roundtrip: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dwlManager == nil {
|
|
||||||
return nil, fmt.Errorf("dwl_ipc_manager not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(outputs) == 0 {
|
|
||||||
return nil, fmt.Errorf("no outputs found")
|
|
||||||
}
|
|
||||||
|
|
||||||
outputNames := make(map[uint32]string)
|
|
||||||
for name, out := range outputs {
|
|
||||||
n := name
|
|
||||||
out.SetNameHandler(func(e client.OutputNameEvent) {
|
|
||||||
outputNames[n] = e.Name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
|
||||||
return nil, fmt.Errorf("roundtrip: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dwlOutputState struct {
|
|
||||||
output *dwl_ipc.ZdwlIpcOutputV2
|
|
||||||
name string
|
|
||||||
active bool
|
|
||||||
x, y int32
|
|
||||||
w, h int32
|
|
||||||
scalefactor uint32
|
|
||||||
gotFrame bool
|
|
||||||
}
|
|
||||||
|
|
||||||
dwlOutputs := make(map[uint32]*dwlOutputState)
|
|
||||||
for name, out := range outputs {
|
|
||||||
dwlOut, err := dwlManager.GetOutput(out)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
state := &dwlOutputState{output: dwlOut, name: outputNames[name]}
|
|
||||||
dwlOutputs[name] = state
|
|
||||||
|
|
||||||
dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
|
||||||
state.active = e.Active != 0
|
|
||||||
})
|
|
||||||
dwlOut.SetXHandler(func(e dwl_ipc.ZdwlIpcOutputV2XEvent) {
|
|
||||||
state.x = e.X
|
|
||||||
})
|
|
||||||
dwlOut.SetYHandler(func(e dwl_ipc.ZdwlIpcOutputV2YEvent) {
|
|
||||||
state.y = e.Y
|
|
||||||
})
|
|
||||||
dwlOut.SetWidthHandler(func(e dwl_ipc.ZdwlIpcOutputV2WidthEvent) {
|
|
||||||
state.w = e.Width
|
|
||||||
})
|
|
||||||
dwlOut.SetHeightHandler(func(e dwl_ipc.ZdwlIpcOutputV2HeightEvent) {
|
|
||||||
state.h = e.Height
|
|
||||||
})
|
|
||||||
dwlOut.SetScalefactorHandler(func(e dwl_ipc.ZdwlIpcOutputV2ScalefactorEvent) {
|
|
||||||
state.scalefactor = e.Scalefactor
|
|
||||||
})
|
|
||||||
dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
|
||||||
state.gotFrame = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
allFramesReceived := func() bool {
|
|
||||||
for _, s := range dwlOutputs {
|
|
||||||
if !s.gotFrame {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for !allFramesReceived() {
|
|
||||||
if err := ctx.Dispatch(); err != nil {
|
|
||||||
return nil, fmt.Errorf("dispatch: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, state := range dwlOutputs {
|
|
||||||
if !state.active {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if state.w <= 0 || state.h <= 0 {
|
|
||||||
return nil, fmt.Errorf("no active window")
|
|
||||||
}
|
|
||||||
scale := float64(state.scalefactor) / 100.0
|
|
||||||
if scale <= 0 {
|
|
||||||
scale = 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
geom := &WindowGeometry{
|
|
||||||
X: state.x,
|
|
||||||
Y: state.y,
|
|
||||||
Width: state.w,
|
|
||||||
Height: state.h,
|
|
||||||
Output: state.name,
|
|
||||||
Scale: scale,
|
|
||||||
}
|
|
||||||
|
|
||||||
if info, ok := getOutputInfo(state.name); ok {
|
|
||||||
geom.OutputX = info.x
|
|
||||||
geom.OutputY = info.y
|
|
||||||
geom.OutputTransform = info.transform
|
|
||||||
}
|
|
||||||
|
|
||||||
return geom, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("no active output found")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -156,14 +156,14 @@ func (s *Screenshoter) captureWindow() (*CaptureResult, error) {
|
|||||||
switch DetectCompositor() {
|
switch DetectCompositor() {
|
||||||
case CompositorHyprland:
|
case CompositorHyprland:
|
||||||
return s.captureAndCrop(output, region)
|
return s.captureAndCrop(output, region)
|
||||||
case CompositorDWL:
|
case CompositorMango:
|
||||||
return s.captureDWLWindow(output, region, geom)
|
return s.captureMangoWindow(output, region, geom)
|
||||||
default:
|
default:
|
||||||
return s.captureRegionOnOutput(output, region)
|
return s.captureRegionOnOutput(output, region)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureDWLWindow(output *WaylandOutput, region Region, geom *WindowGeometry) (*CaptureResult, error) {
|
func (s *Screenshoter) captureMangoWindow(output *WaylandOutput, region Region, geom *WindowGeometry) (*CaptureResult, error) {
|
||||||
result, err := s.captureWholeOutput(output)
|
result, err := s.captureWholeOutput(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -628,7 +628,7 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
|||||||
w := int32(float64(region.Width) * scale)
|
w := int32(float64(region.Width) * scale)
|
||||||
h := int32(float64(region.Height) * scale)
|
h := int32(float64(region.Height) * scale)
|
||||||
|
|
||||||
if DetectCompositor() == CompositorDWL {
|
if DetectCompositor() == CompositorMango {
|
||||||
scaledOutW := int32(float64(output.width) * scale)
|
scaledOutW := int32(float64(output.width) * scale)
|
||||||
scaledOutH := int32(float64(output.height) * scale)
|
scaledOutH := int32(float64(output.height) * scale)
|
||||||
if localX >= scaledOutW {
|
if localX >= scaledOutW {
|
||||||
|
|||||||
@@ -935,7 +935,7 @@ func (m *Manager) CreateHistoryEntryFromPinned(pinnedEntry *Entry) error {
|
|||||||
Pinned: false,
|
Pinned: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.storeEntryWithoutDedup(newEntry); err != nil {
|
if err := m.storeEntry(newEntry); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,36 +945,6 @@ func (m *Manager) CreateHistoryEntryFromPinned(pinnedEntry *Entry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) storeEntryWithoutDedup(entry Entry) error {
|
|
||||||
if m.db == nil {
|
|
||||||
return fmt.Errorf("database not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Hash = computeHash(entry.Data)
|
|
||||||
|
|
||||||
return m.db.Update(func(tx *bolt.Tx) error {
|
|
||||||
b := tx.Bucket([]byte("clipboard"))
|
|
||||||
|
|
||||||
id, err := b.NextSequence()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.ID = id
|
|
||||||
|
|
||||||
encoded, err := encodeEntry(entry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.Put(itob(id), encoded); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.trimLengthInTx(b)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) ClearHistory() {
|
func (m *Manager) ClearHistory() {
|
||||||
if m.db == nil {
|
if m.db == nil {
|
||||||
return
|
return
|
||||||
@@ -1653,6 +1623,37 @@ func (m *Manager) UnpinEntry(id uint64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if entry.Pinned {
|
||||||
|
currentKey := itob(id)
|
||||||
|
var keepKey []byte
|
||||||
|
var deleteKeys [][]byte
|
||||||
|
|
||||||
|
c := b.Cursor()
|
||||||
|
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||||
|
if bytes.Equal(k, currentKey) || extractHash(v) != entry.Hash {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
duplicate, err := decodeEntryMeta(v)
|
||||||
|
if err == nil && !duplicate.Pinned {
|
||||||
|
key := append([]byte(nil), k...)
|
||||||
|
if keepKey == nil {
|
||||||
|
keepKey = key
|
||||||
|
} else {
|
||||||
|
deleteKeys = append(deleteKeys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if keepKey != nil {
|
||||||
|
for _, key := range deleteKeys {
|
||||||
|
if err := b.Delete(key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.Delete(currentKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
entry.Pinned = false
|
entry.Pinned = false
|
||||||
encoded, err := encodeEntry(entry)
|
encoded, err := encodeEntry(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
|
||||||
mocks_wlcontext "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlcontext"
|
mocks_wlcontext "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlcontext"
|
||||||
)
|
)
|
||||||
@@ -273,6 +274,110 @@ func TestHandleGetEntry_MissingIDReturnsNullResult(t *testing.T) {
|
|||||||
assert.Nil(t, resp.Result)
|
assert.Nil(t, resp.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnpinEntry_KeepsTopUnpinnedDuplicate(t *testing.T) {
|
||||||
|
m := newTestManagerWithDB(t)
|
||||||
|
|
||||||
|
require.NoError(t, m.storeEntry(Entry{
|
||||||
|
Data: []byte("saved content"),
|
||||||
|
MimeType: "text/plain;charset=utf-8",
|
||||||
|
Preview: "saved content",
|
||||||
|
Size: len("saved content"),
|
||||||
|
Timestamp: time.Now().Add(-time.Minute).Truncate(time.Second),
|
||||||
|
IsImage: false,
|
||||||
|
}))
|
||||||
|
|
||||||
|
history := m.GetHistory()
|
||||||
|
require.Len(t, history, 1)
|
||||||
|
pinnedID := history[0].ID
|
||||||
|
require.NoError(t, m.PinEntry(pinnedID))
|
||||||
|
|
||||||
|
pinnedEntry, err := m.GetEntry(pinnedID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, pinnedEntry.Pinned)
|
||||||
|
|
||||||
|
// Bypass storeEntry to simulate legacy duplicate ordinary history entries.
|
||||||
|
insertLegacyUnpinnedDuplicate := func(timestamp time.Time) Entry {
|
||||||
|
duplicate := Entry{
|
||||||
|
Data: pinnedEntry.Data,
|
||||||
|
MimeType: pinnedEntry.MimeType,
|
||||||
|
Preview: pinnedEntry.Preview,
|
||||||
|
Size: pinnedEntry.Size,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
IsImage: pinnedEntry.IsImage,
|
||||||
|
Pinned: false,
|
||||||
|
}
|
||||||
|
duplicate.Hash = computeHash(duplicate.Data)
|
||||||
|
|
||||||
|
require.NoError(t, m.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("clipboard"))
|
||||||
|
id, err := b.NextSequence()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
duplicate.ID = id
|
||||||
|
|
||||||
|
encoded, err := encodeEntry(duplicate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.Put(itob(id), encoded)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
olderHistoryDuplicate := insertLegacyUnpinnedDuplicate(time.Now().Add(time.Hour))
|
||||||
|
topHistoryDuplicate := insertLegacyUnpinnedDuplicate(time.Now().Add(-time.Hour))
|
||||||
|
require.Greater(t, topHistoryDuplicate.ID, olderHistoryDuplicate.ID)
|
||||||
|
require.True(t, olderHistoryDuplicate.Timestamp.After(topHistoryDuplicate.Timestamp))
|
||||||
|
|
||||||
|
history = m.GetHistory()
|
||||||
|
require.Len(t, history, 3)
|
||||||
|
require.Equal(t, topHistoryDuplicate.ID, history[0].ID)
|
||||||
|
require.NoError(t, m.UnpinEntry(pinnedID))
|
||||||
|
|
||||||
|
history = m.GetHistory()
|
||||||
|
require.Len(t, history, 1)
|
||||||
|
assert.False(t, history[0].Pinned)
|
||||||
|
assert.Equal(t, pinnedEntry.Hash, history[0].Hash)
|
||||||
|
assert.Equal(t, topHistoryDuplicate.ID, history[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateHistoryEntryFromPinned_KeepsLatestUnpinnedDuplicate(t *testing.T) {
|
||||||
|
m := newTestManagerWithDB(t)
|
||||||
|
|
||||||
|
require.NoError(t, m.storeEntry(Entry{
|
||||||
|
Data: []byte("saved content"),
|
||||||
|
MimeType: "text/plain;charset=utf-8",
|
||||||
|
Preview: "saved content",
|
||||||
|
Size: len("saved content"),
|
||||||
|
Timestamp: time.Now().Add(-time.Minute).Truncate(time.Second),
|
||||||
|
IsImage: false,
|
||||||
|
}))
|
||||||
|
|
||||||
|
history := m.GetHistory()
|
||||||
|
require.Len(t, history, 1)
|
||||||
|
pinnedID := history[0].ID
|
||||||
|
require.NoError(t, m.PinEntry(pinnedID))
|
||||||
|
|
||||||
|
pinnedEntry, err := m.GetEntry(pinnedID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, pinnedEntry.Pinned)
|
||||||
|
require.NoError(t, m.CreateHistoryEntryFromPinned(pinnedEntry))
|
||||||
|
firstDuplicate := m.GetHistory()[0]
|
||||||
|
require.NotEqual(t, pinnedID, firstDuplicate.ID)
|
||||||
|
require.NoError(t, m.CreateHistoryEntryFromPinned(pinnedEntry))
|
||||||
|
latestDuplicate := m.GetHistory()[0]
|
||||||
|
|
||||||
|
history = m.GetHistory()
|
||||||
|
require.Len(t, history, 2)
|
||||||
|
assert.Equal(t, latestDuplicate.ID, history[0].ID)
|
||||||
|
assert.False(t, history[0].Pinned)
|
||||||
|
assert.Equal(t, pinnedID, history[1].ID)
|
||||||
|
assert.True(t, history[1].Pinned)
|
||||||
|
assert.NotEqual(t, firstDuplicate.ID, latestDuplicate.ID)
|
||||||
|
}
|
||||||
|
|
||||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
subscribers: make(map[string]chan State),
|
subscribers: make(map[string]chan State),
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
package dwl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
|
||||||
if manager == nil {
|
|
||||||
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch req.Method {
|
|
||||||
case "dwl.getState":
|
|
||||||
handleGetState(conn, req, manager)
|
|
||||||
case "dwl.setTags":
|
|
||||||
handleSetTags(conn, req, manager)
|
|
||||||
case "dwl.setClientTags":
|
|
||||||
handleSetClientTags(conn, req, manager)
|
|
||||||
case "dwl.setLayout":
|
|
||||||
handleSetLayout(conn, req, manager)
|
|
||||||
case "dwl.subscribe":
|
|
||||||
handleSubscribe(conn, req, manager)
|
|
||||||
default:
|
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
|
||||||
state := manager.GetState()
|
|
||||||
models.Respond(conn, req.ID, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSetTags(conn net.Conn, req models.Request, manager *Manager) {
|
|
||||||
output, ok := models.Get[string](req, "output")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tagmask, ok := models.Get[float64](req, "tagmask")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'tagmask' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleTagset, ok := models.Get[float64](req, "toggleTagset")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'toggleTagset' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.SetTags(output, uint32(tagmask), uint32(toggleTagset)); err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "tags set"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSetClientTags(conn net.Conn, req models.Request, manager *Manager) {
|
|
||||||
output, ok := models.Get[string](req, "output")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
andTags, ok := models.Get[float64](req, "andTags")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'andTags' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
xorTags, ok := models.Get[float64](req, "xorTags")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'xorTags' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.SetClientTags(output, uint32(andTags), uint32(xorTags)); err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "client tags set"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSetLayout(conn net.Conn, req models.Request, manager *Manager) {
|
|
||||||
output, ok := models.Get[string](req, "output")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
index, ok := models.Get[float64](req, "index")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'index' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.SetLayout(output, uint32(index)); err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "layout set"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
|
||||||
stateChan := manager.Subscribe(clientID)
|
|
||||||
defer manager.Unsubscribe(clientID)
|
|
||||||
|
|
||||||
initialState := manager.GetState()
|
|
||||||
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
|
||||||
ID: req.ID,
|
|
||||||
Result: &initialState,
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for state := range stateChan {
|
|
||||||
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
|
||||||
Result: &state,
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,522 +0,0 @@
|
|||||||
package dwl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewManager(display wlclient.WaylandDisplay) (*Manager, error) {
|
|
||||||
m := &Manager{
|
|
||||||
display: display,
|
|
||||||
ctx: display.Context(),
|
|
||||||
cmdq: make(chan cmd, 128),
|
|
||||||
outputSetupReq: make(chan uint32, 16),
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
layouts: make([]string, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.setupRegistry(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.updateState()
|
|
||||||
|
|
||||||
m.notifierWg.Add(1)
|
|
||||||
go m.notifier()
|
|
||||||
|
|
||||||
m.wg.Add(1)
|
|
||||||
go m.waylandActor()
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) post(fn func()) {
|
|
||||||
select {
|
|
||||||
case m.cmdq <- cmd{fn: fn}:
|
|
||||||
default:
|
|
||||||
log.Warn("DWL actor command queue full, dropping command")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) waylandActor() {
|
|
||||||
defer m.wg.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-m.stopChan:
|
|
||||||
return
|
|
||||||
case c := <-m.cmdq:
|
|
||||||
c.fn()
|
|
||||||
case outputID := <-m.outputSetupReq:
|
|
||||||
out, exists := m.outputs.Load(outputID)
|
|
||||||
if !exists {
|
|
||||||
log.Warnf("DWL: Output %d no longer exists, skipping setup", outputID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.ipcOutput != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2)
|
|
||||||
if !ok || mgr == nil {
|
|
||||||
log.Errorf("DWL: Manager not available for output %d setup", outputID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("DWL: Setting up ipcOutput for dynamically added output %d", outputID)
|
|
||||||
if err := m.setupOutput(mgr, out.output); err != nil {
|
|
||||||
log.Errorf("DWL: Failed to setup output %d: %v", outputID, err)
|
|
||||||
} else {
|
|
||||||
m.updateState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) setupRegistry() error {
|
|
||||||
log.Info("DWL: starting registry setup")
|
|
||||||
|
|
||||||
registry, err := m.display.GetRegistry()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get registry: %w", err)
|
|
||||||
}
|
|
||||||
m.registry = registry
|
|
||||||
|
|
||||||
outputs := make([]*wlclient.Output, 0)
|
|
||||||
outputRegNames := make(map[uint32]uint32)
|
|
||||||
var dwlMgr *dwl_ipc.ZdwlIpcManagerV2
|
|
||||||
|
|
||||||
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
|
||||||
switch e.Interface {
|
|
||||||
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
|
||||||
log.Infof("DWL: found %s", dwl_ipc.ZdwlIpcManagerV2InterfaceName)
|
|
||||||
manager := dwl_ipc.NewZdwlIpcManagerV2(m.ctx)
|
|
||||||
version := e.Version
|
|
||||||
if version > 2 {
|
|
||||||
version = 2
|
|
||||||
}
|
|
||||||
if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
|
|
||||||
dwlMgr = manager
|
|
||||||
log.Info("DWL: manager bound successfully")
|
|
||||||
|
|
||||||
// Set handlers immediately after binding, before roundtrips
|
|
||||||
manager.SetTagsHandler(func(e dwl_ipc.ZdwlIpcManagerV2TagsEvent) {
|
|
||||||
log.Infof("DWL: Tags count: %d", e.Amount)
|
|
||||||
m.tagCount = e.Amount
|
|
||||||
m.updateState()
|
|
||||||
})
|
|
||||||
|
|
||||||
manager.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcManagerV2LayoutEvent) {
|
|
||||||
log.Infof("DWL: Layout: %s", e.Name)
|
|
||||||
m.layouts = append(m.layouts, e.Name)
|
|
||||||
m.updateState()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
log.Errorf("DWL: failed to bind manager: %v", err)
|
|
||||||
}
|
|
||||||
case "wl_output":
|
|
||||||
log.Debugf("DWL: found wl_output (name=%d)", e.Name)
|
|
||||||
output := wlclient.NewOutput(m.ctx)
|
|
||||||
|
|
||||||
outState := &outputState{
|
|
||||||
registryName: e.Name,
|
|
||||||
output: output,
|
|
||||||
tags: make([]TagState, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
|
|
||||||
log.Debugf("DWL: Output name: %s (registry=%d)", ev.Name, e.Name)
|
|
||||||
outState.name = ev.Name
|
|
||||||
})
|
|
||||||
|
|
||||||
output.SetDescriptionHandler(func(ev wlclient.OutputDescriptionEvent) {
|
|
||||||
log.Debugf("DWL: Output description: %s", ev.Description)
|
|
||||||
})
|
|
||||||
|
|
||||||
version := e.Version
|
|
||||||
if version > 4 {
|
|
||||||
version = 4
|
|
||||||
}
|
|
||||||
if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
|
|
||||||
outputID := output.ID()
|
|
||||||
outState.id = outputID
|
|
||||||
log.Infof("DWL: Bound wl_output id=%d registry_name=%d", outputID, e.Name)
|
|
||||||
outputs = append(outputs, output)
|
|
||||||
outputRegNames[outputID] = e.Name
|
|
||||||
|
|
||||||
m.outputs.Store(outputID, outState)
|
|
||||||
|
|
||||||
if m.manager != nil {
|
|
||||||
select {
|
|
||||||
case m.outputSetupReq <- outputID:
|
|
||||||
log.Debugf("DWL: Queued setup for output %d", outputID)
|
|
||||||
default:
|
|
||||||
log.Warnf("DWL: Setup queue full, output %d will not be initialized", outputID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Errorf("DWL: Failed to bind wl_output: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) {
|
|
||||||
m.post(func() {
|
|
||||||
var outToRelease *outputState
|
|
||||||
m.outputs.Range(func(id uint32, out *outputState) bool {
|
|
||||||
if out.registryName == e.Name {
|
|
||||||
log.Infof("DWL: Output %d removed", id)
|
|
||||||
outToRelease = out
|
|
||||||
m.outputs.Delete(id)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if outToRelease != nil {
|
|
||||||
if ipcOut, ok := outToRelease.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok && ipcOut != nil {
|
|
||||||
m.wlMutex.Lock()
|
|
||||||
ipcOut.Release()
|
|
||||||
m.wlMutex.Unlock()
|
|
||||||
log.Debugf("DWL: Released ipcOutput for removed output %d", outToRelease.id)
|
|
||||||
}
|
|
||||||
m.updateState()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := m.display.Roundtrip(); err != nil {
|
|
||||||
return fmt.Errorf("first roundtrip failed: %w", err)
|
|
||||||
}
|
|
||||||
if err := m.display.Roundtrip(); err != nil {
|
|
||||||
return fmt.Errorf("second roundtrip failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dwlMgr == nil {
|
|
||||||
log.Info("DWL: manager not found in registry")
|
|
||||||
return fmt.Errorf("dwl_ipc_manager_v2 not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
m.manager = dwlMgr
|
|
||||||
|
|
||||||
for _, output := range outputs {
|
|
||||||
if err := m.setupOutput(dwlMgr, output); err != nil {
|
|
||||||
log.Warnf("DWL: Failed to setup output %d: %v", output.ID(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.display.Roundtrip(); err != nil {
|
|
||||||
return fmt.Errorf("final roundtrip failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("DWL: registry setup complete")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclient.Output) error {
|
|
||||||
m.wlMutex.Lock()
|
|
||||||
ipcOutput, err := manager.GetOutput(output)
|
|
||||||
m.wlMutex.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get dwl output: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
outState, exists := m.outputs.Load(output.ID())
|
|
||||||
if !exists {
|
|
||||||
return fmt.Errorf("output state not found for id %d", output.ID())
|
|
||||||
}
|
|
||||||
outState.ipcOutput = ipcOutput
|
|
||||||
|
|
||||||
ipcOutput.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
|
||||||
outState.active = e.Active
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcOutput.SetTagHandler(func(e dwl_ipc.ZdwlIpcOutputV2TagEvent) {
|
|
||||||
updated := false
|
|
||||||
for i, tag := range outState.tags {
|
|
||||||
if tag.Tag == e.Tag {
|
|
||||||
outState.tags[i] = TagState{
|
|
||||||
Tag: e.Tag,
|
|
||||||
State: e.State,
|
|
||||||
Clients: e.Clients,
|
|
||||||
Focused: e.Focused,
|
|
||||||
}
|
|
||||||
updated = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !updated {
|
|
||||||
outState.tags = append(outState.tags, TagState{
|
|
||||||
Tag: e.Tag,
|
|
||||||
State: e.State,
|
|
||||||
Clients: e.Clients,
|
|
||||||
Focused: e.Focused,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
m.updateState()
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcOutput.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutEvent) {
|
|
||||||
outState.layout = e.Layout
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcOutput.SetTitleHandler(func(e dwl_ipc.ZdwlIpcOutputV2TitleEvent) {
|
|
||||||
outState.title = e.Title
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcOutput.SetAppidHandler(func(e dwl_ipc.ZdwlIpcOutputV2AppidEvent) {
|
|
||||||
outState.appID = e.Appid
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcOutput.SetLayoutSymbolHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutSymbolEvent) {
|
|
||||||
outState.layoutSymbol = e.Layout
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcOutput.SetKbLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2KbLayoutEvent) {
|
|
||||||
outState.kbLayout = e.KbLayout
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcOutput.SetKeymodeHandler(func(e dwl_ipc.ZdwlIpcOutputV2KeymodeEvent) {
|
|
||||||
outState.keymode = e.Keymode
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcOutput.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
|
||||||
m.updateState()
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) updateState() {
|
|
||||||
outputs := make(map[string]*OutputState)
|
|
||||||
activeOutput := ""
|
|
||||||
|
|
||||||
m.outputs.Range(func(key uint32, out *outputState) bool {
|
|
||||||
name := out.name
|
|
||||||
if name == "" {
|
|
||||||
name = fmt.Sprintf("output-%d", out.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
tagsCopy := make([]TagState, len(out.tags))
|
|
||||||
copy(tagsCopy, out.tags)
|
|
||||||
|
|
||||||
outputs[name] = &OutputState{
|
|
||||||
Name: name,
|
|
||||||
Active: out.active,
|
|
||||||
Tags: tagsCopy,
|
|
||||||
Layout: out.layout,
|
|
||||||
LayoutSymbol: out.layoutSymbol,
|
|
||||||
Title: out.title,
|
|
||||||
AppID: out.appID,
|
|
||||||
KbLayout: out.kbLayout,
|
|
||||||
Keymode: out.keymode,
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.active != 0 {
|
|
||||||
activeOutput = name
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
newState := State{
|
|
||||||
Outputs: outputs,
|
|
||||||
TagCount: m.tagCount,
|
|
||||||
Layouts: m.layouts,
|
|
||||||
ActiveOutput: activeOutput,
|
|
||||||
}
|
|
||||||
|
|
||||||
m.stateMutex.Lock()
|
|
||||||
m.state = &newState
|
|
||||||
m.stateMutex.Unlock()
|
|
||||||
|
|
||||||
m.notifySubscribers()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) notifier() {
|
|
||||||
defer m.notifierWg.Done()
|
|
||||||
const minGap = 100 * time.Millisecond
|
|
||||||
timer := time.NewTimer(minGap)
|
|
||||||
timer.Stop()
|
|
||||||
var pending bool
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-m.stopChan:
|
|
||||||
timer.Stop()
|
|
||||||
return
|
|
||||||
case <-m.dirty:
|
|
||||||
if pending {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pending = true
|
|
||||||
timer.Reset(minGap)
|
|
||||||
case <-timer.C:
|
|
||||||
if !pending {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState := m.GetState()
|
|
||||||
|
|
||||||
if m.lastNotified != nil && !stateChanged(m.lastNotified, ¤tState) {
|
|
||||||
pending = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
m.subscribers.Range(func(key string, ch chan State) bool {
|
|
||||||
select {
|
|
||||||
case ch <- currentState:
|
|
||||||
default:
|
|
||||||
log.Warn("DWL: subscriber channel full, dropping update")
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
stateCopy := currentState
|
|
||||||
m.lastNotified = &stateCopy
|
|
||||||
pending = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) ensureOutputSetup(out *outputState) error {
|
|
||||||
if out.ipcOutput != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("output not yet initialized - setup in progress, retry in a moment")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32) error {
|
|
||||||
availableOutputs := make([]string, 0)
|
|
||||||
var targetOut *outputState
|
|
||||||
m.outputs.Range(func(key uint32, out *outputState) bool {
|
|
||||||
name := out.name
|
|
||||||
if name == "" {
|
|
||||||
name = fmt.Sprintf("output-%d", out.id)
|
|
||||||
}
|
|
||||||
availableOutputs = append(availableOutputs, name)
|
|
||||||
if name == outputName {
|
|
||||||
targetOut = out
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if targetOut == nil {
|
|
||||||
return fmt.Errorf("output not found: %s (available: %v)", outputName, availableOutputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.ensureOutputSetup(targetOut); err != nil {
|
|
||||||
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.wlMutex.Lock()
|
|
||||||
err := ipcOut.SetTags(tagmask, toggleTagset)
|
|
||||||
m.wlMutex.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) SetClientTags(outputName string, andTags uint32, xorTags uint32) error {
|
|
||||||
var targetOut *outputState
|
|
||||||
m.outputs.Range(func(key uint32, out *outputState) bool {
|
|
||||||
name := out.name
|
|
||||||
if name == "" {
|
|
||||||
name = fmt.Sprintf("output-%d", out.id)
|
|
||||||
}
|
|
||||||
if name == outputName {
|
|
||||||
targetOut = out
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if targetOut == nil {
|
|
||||||
return fmt.Errorf("output not found: %s", outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.ensureOutputSetup(targetOut); err != nil {
|
|
||||||
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.wlMutex.Lock()
|
|
||||||
err := ipcOut.SetClientTags(andTags, xorTags)
|
|
||||||
m.wlMutex.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) SetLayout(outputName string, index uint32) error {
|
|
||||||
var targetOut *outputState
|
|
||||||
m.outputs.Range(func(key uint32, out *outputState) bool {
|
|
||||||
name := out.name
|
|
||||||
if name == "" {
|
|
||||||
name = fmt.Sprintf("output-%d", out.id)
|
|
||||||
}
|
|
||||||
if name == outputName {
|
|
||||||
targetOut = out
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if targetOut == nil {
|
|
||||||
return fmt.Errorf("output not found: %s", outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.ensureOutputSetup(targetOut); err != nil {
|
|
||||||
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.wlMutex.Lock()
|
|
||||||
err := ipcOut.SetLayout(index)
|
|
||||||
m.wlMutex.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() {
|
|
||||||
close(m.stopChan)
|
|
||||||
m.wg.Wait()
|
|
||||||
m.notifierWg.Wait()
|
|
||||||
|
|
||||||
m.subscribers.Range(func(key string, ch chan State) bool {
|
|
||||||
close(ch)
|
|
||||||
m.subscribers.Delete(key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
m.outputs.Range(func(key uint32, out *outputState) bool {
|
|
||||||
if ipcOut, ok := out.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok {
|
|
||||||
ipcOut.Release()
|
|
||||||
}
|
|
||||||
m.outputs.Delete(key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2); ok {
|
|
||||||
mgr.Release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
package dwl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStateChanged_BothNil(t *testing.T) {
|
|
||||||
assert.True(t, stateChanged(nil, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OneNil(t *testing.T) {
|
|
||||||
s := &State{TagCount: 9}
|
|
||||||
assert.True(t, stateChanged(s, nil))
|
|
||||||
assert.True(t, stateChanged(nil, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_TagCountDiffers(t *testing.T) {
|
|
||||||
a := &State{TagCount: 9, Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
|
||||||
b := &State{TagCount: 10, Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_LayoutLengthDiffers(t *testing.T) {
|
|
||||||
a := &State{TagCount: 9, Layouts: []string{"tile"}, Outputs: make(map[string]*OutputState)}
|
|
||||||
b := &State{TagCount: 9, Layouts: []string{"tile", "monocle"}, Outputs: make(map[string]*OutputState)}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_ActiveOutputDiffers(t *testing.T) {
|
|
||||||
a := &State{TagCount: 9, ActiveOutput: "eDP-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
|
||||||
b := &State{TagCount: 9, ActiveOutput: "HDMI-A-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputCountDiffers(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Outputs: map[string]*OutputState{"eDP-1": {}},
|
|
||||||
Layouts: []string{},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Outputs: map[string]*OutputState{},
|
|
||||||
Layouts: []string{},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_OutputFieldsDiffer(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Active: 1, Layout: 0, Title: "Firefox"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Active: 0, Layout: 0, Title: "Firefox"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
|
|
||||||
b.Outputs["eDP-1"].Active = 1
|
|
||||||
b.Outputs["eDP-1"].Layout = 1
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
|
|
||||||
b.Outputs["eDP-1"].Layout = 0
|
|
||||||
b.Outputs["eDP-1"].Title = "Code"
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_TagsDiffer(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Tags: []TagState{{Tag: 1, State: 2, Clients: 2, Focused: 1}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
|
|
||||||
b.Outputs["eDP-1"].Tags[0].State = 1
|
|
||||||
b.Outputs["eDP-1"].Tags[0].Clients = 3
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_Equal(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
ActiveOutput: "eDP-1",
|
|
||||||
Layouts: []string{"tile", "monocle"},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {
|
|
||||||
Name: "eDP-1",
|
|
||||||
Active: 1,
|
|
||||||
Layout: 0,
|
|
||||||
LayoutSymbol: "[]=",
|
|
||||||
Title: "Firefox",
|
|
||||||
AppID: "firefox",
|
|
||||||
KbLayout: "us",
|
|
||||||
Keymode: "",
|
|
||||||
Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
ActiveOutput: "eDP-1",
|
|
||||||
Layouts: []string{"tile", "monocle"},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {
|
|
||||||
Name: "eDP-1",
|
|
||||||
Active: 1,
|
|
||||||
Layout: 0,
|
|
||||||
LayoutSymbol: "[]=",
|
|
||||||
Title: "Firefox",
|
|
||||||
AppID: "firefox",
|
|
||||||
KbLayout: "us",
|
|
||||||
Keymode: "",
|
|
||||||
Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.False(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentGetState(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
state: &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{"tile"},
|
|
||||||
Outputs: map[string]*OutputState{"eDP-1": {Name: "eDP-1"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 50
|
|
||||||
const iterations = 100
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
s := m.GetState()
|
|
||||||
_ = s.TagCount
|
|
||||||
_ = s.Outputs
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < goroutines/2; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
m.stateMutex.Lock()
|
|
||||||
m.state = &State{
|
|
||||||
TagCount: uint32(j % 10),
|
|
||||||
Layouts: []string{"tile", "monocle"},
|
|
||||||
Outputs: map[string]*OutputState{"eDP-1": {Active: uint32(j % 2)}},
|
|
||||||
}
|
|
||||||
m.stateMutex.Unlock()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 20
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
subID := string(rune('a' + id))
|
|
||||||
ch := m.Subscribe(subID)
|
|
||||||
assert.NotNil(t, ch)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
m.Unsubscribe(subID)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const goroutines = 30
|
|
||||||
const iterations = 50
|
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
key := uint32(id)
|
|
||||||
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
state := &outputState{
|
|
||||||
id: key,
|
|
||||||
name: "test-output",
|
|
||||||
active: uint32(j % 2),
|
|
||||||
tags: []TagState{{Tag: uint32(j), State: 1}},
|
|
||||||
}
|
|
||||||
m.outputs.Store(key, state)
|
|
||||||
|
|
||||||
if loaded, ok := m.outputs.Load(key); ok {
|
|
||||||
assert.Equal(t, key, loaded.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.outputs.Range(func(k uint32, v *outputState) bool {
|
|
||||||
_ = v.name
|
|
||||||
_ = v.active
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
m.outputs.Delete(key)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
dirty: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
m.notifySubscribers()
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(t, m.dirty, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_PostQueueFull(t *testing.T) {
|
|
||||||
m := &Manager{
|
|
||||||
cmdq: make(chan cmd, 2),
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
m.post(func() {})
|
|
||||||
|
|
||||||
assert.Len(t, m.cmdq, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_GetStateNilState(t *testing.T) {
|
|
||||||
m := &Manager{}
|
|
||||||
|
|
||||||
s := m.GetState()
|
|
||||||
assert.NotNil(t, s.Outputs)
|
|
||||||
assert.NotNil(t, s.Layouts)
|
|
||||||
assert.Equal(t, uint32(0), s.TagCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTagState_Fields(t *testing.T) {
|
|
||||||
tag := TagState{
|
|
||||||
Tag: 1,
|
|
||||||
State: 2,
|
|
||||||
Clients: 3,
|
|
||||||
Focused: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, uint32(1), tag.Tag)
|
|
||||||
assert.Equal(t, uint32(2), tag.State)
|
|
||||||
assert.Equal(t, uint32(3), tag.Clients)
|
|
||||||
assert.Equal(t, uint32(1), tag.Focused)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputState_Fields(t *testing.T) {
|
|
||||||
out := OutputState{
|
|
||||||
Name: "eDP-1",
|
|
||||||
Active: 1,
|
|
||||||
Tags: []TagState{{Tag: 1}},
|
|
||||||
Layout: 0,
|
|
||||||
LayoutSymbol: "[]=",
|
|
||||||
Title: "Firefox",
|
|
||||||
AppID: "firefox",
|
|
||||||
KbLayout: "us",
|
|
||||||
Keymode: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "eDP-1", out.Name)
|
|
||||||
assert.Equal(t, uint32(1), out.Active)
|
|
||||||
assert.Len(t, out.Tags, 1)
|
|
||||||
assert.Equal(t, "[]=", out.LayoutSymbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_NewOutputAppears(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Name: "eDP-1"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Name: "eDP-1"},
|
|
||||||
"HDMI-A-1": {Name: "HDMI-A-1"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStateChanged_TagsLengthDiffers(t *testing.T) {
|
|
||||||
a := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Tags: []TagState{{Tag: 1}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := &State{
|
|
||||||
TagCount: 9,
|
|
||||||
Layouts: []string{},
|
|
||||||
Outputs: map[string]*OutputState{
|
|
||||||
"eDP-1": {Tags: []TagState{{Tag: 1}, {Tag: 2}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, stateChanged(a, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewManager_GetRegistryError(t *testing.T) {
|
|
||||||
mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
|
|
||||||
|
|
||||||
mockDisplay.EXPECT().Context().Return(nil)
|
|
||||||
mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry"))
|
|
||||||
|
|
||||||
_, err := NewManager(mockDisplay)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "failed to get registry")
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
package dwl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TagState struct {
|
|
||||||
Tag uint32 `json:"tag"`
|
|
||||||
State uint32 `json:"state"`
|
|
||||||
Clients uint32 `json:"clients"`
|
|
||||||
Focused uint32 `json:"focused"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutputState struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Active uint32 `json:"active"`
|
|
||||||
Tags []TagState `json:"tags"`
|
|
||||||
Layout uint32 `json:"layout"`
|
|
||||||
LayoutSymbol string `json:"layoutSymbol"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
AppID string `json:"appId"`
|
|
||||||
KbLayout string `json:"kbLayout"`
|
|
||||||
Keymode string `json:"keymode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type State struct {
|
|
||||||
Outputs map[string]*OutputState `json:"outputs"`
|
|
||||||
TagCount uint32 `json:"tagCount"`
|
|
||||||
Layouts []string `json:"layouts"`
|
|
||||||
ActiveOutput string `json:"activeOutput"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type cmd struct {
|
|
||||||
fn func()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
display wlclient.WaylandDisplay
|
|
||||||
ctx *wlclient.Context
|
|
||||||
registry *wlclient.Registry
|
|
||||||
manager any
|
|
||||||
|
|
||||||
outputs syncmap.Map[uint32, *outputState]
|
|
||||||
|
|
||||||
tagCount uint32
|
|
||||||
layouts []string
|
|
||||||
|
|
||||||
wlMutex sync.Mutex
|
|
||||||
cmdq chan cmd
|
|
||||||
outputSetupReq chan uint32
|
|
||||||
stopChan chan struct{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
|
|
||||||
subscribers syncmap.Map[string, chan State]
|
|
||||||
dirty chan struct{}
|
|
||||||
notifierWg sync.WaitGroup
|
|
||||||
lastNotified *State
|
|
||||||
|
|
||||||
stateMutex sync.RWMutex
|
|
||||||
state *State
|
|
||||||
}
|
|
||||||
|
|
||||||
type outputState struct {
|
|
||||||
id uint32
|
|
||||||
registryName uint32
|
|
||||||
output *wlclient.Output
|
|
||||||
ipcOutput any
|
|
||||||
name string
|
|
||||||
active uint32
|
|
||||||
tags []TagState
|
|
||||||
layout uint32
|
|
||||||
layoutSymbol string
|
|
||||||
title string
|
|
||||||
appID string
|
|
||||||
kbLayout string
|
|
||||||
keymode string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) GetState() State {
|
|
||||||
m.stateMutex.RLock()
|
|
||||||
defer m.stateMutex.RUnlock()
|
|
||||||
if m.state == nil {
|
|
||||||
return State{
|
|
||||||
Outputs: make(map[string]*OutputState),
|
|
||||||
Layouts: []string{},
|
|
||||||
TagCount: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stateCopy := *m.state
|
|
||||||
return stateCopy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan State {
|
|
||||||
ch := make(chan State, 64)
|
|
||||||
|
|
||||||
m.subscribers.Store(id, ch)
|
|
||||||
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
|
||||||
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
|
||||||
close(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) notifySubscribers() {
|
|
||||||
select {
|
|
||||||
case m.dirty <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stateChanged(old, new *State) bool {
|
|
||||||
if old == nil || new == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if old.TagCount != new.TagCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if len(old.Layouts) != len(new.Layouts) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if old.ActiveOutput != new.ActiveOutput {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if len(old.Outputs) != len(new.Outputs) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, newOut := range new.Outputs {
|
|
||||||
oldOut, exists := old.Outputs[name]
|
|
||||||
if !exists {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if oldOut.Active != newOut.Active {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if oldOut.Layout != newOut.Layout {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if oldOut.LayoutSymbol != newOut.LayoutSymbol {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if oldOut.Title != newOut.Title {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if oldOut.AppID != newOut.AppID {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if oldOut.KbLayout != newOut.KbLayout {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if oldOut.Keymode != newOut.Keymode {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if len(oldOut.Tags) != len(newOut.Tags) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for i, newTag := range newOut.Tags {
|
|
||||||
if i >= len(oldOut.Tags) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
oldTag := oldOut.Tags[i]
|
|
||||||
if oldTag.Tag != newTag.Tag || oldTag.State != newTag.State ||
|
|
||||||
oldTag.Clients != newTag.Clients || oldTag.Focused != newTag.Focused {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||||
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
|
||||||
@@ -125,15 +124,6 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "dwl.") {
|
|
||||||
if dwlManager == nil {
|
|
||||||
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dwl.HandleRequest(conn, req, dwlManager)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "brightness.") {
|
if strings.HasPrefix(req.Method, "brightness.") {
|
||||||
if brightnessManager == nil {
|
if brightnessManager == nil {
|
||||||
models.RespondError(conn, req.ID, "brightness manager not initialized")
|
models.RespondError(conn, req.ID, "brightness manager not initialized")
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||||
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
|
||||||
@@ -39,7 +38,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APIVersion = 24
|
const APIVersion = 25
|
||||||
|
|
||||||
var CLIVersion = "dev"
|
var CLIVersion = "dev"
|
||||||
|
|
||||||
@@ -66,7 +65,6 @@ var bluezManager *bluez.Manager
|
|||||||
var appPickerManager *apppicker.Manager
|
var appPickerManager *apppicker.Manager
|
||||||
var cupsManager *cups.Manager
|
var cupsManager *cups.Manager
|
||||||
var tailscaleManager *tailscale.Manager
|
var tailscaleManager *tailscale.Manager
|
||||||
var dwlManager *dwl.Manager
|
|
||||||
var brightnessManager *brightness.Manager
|
var brightnessManager *brightness.Manager
|
||||||
var wlrOutputManager *wlroutput.Manager
|
var wlrOutputManager *wlroutput.Manager
|
||||||
var evdevManager *evdev.Manager
|
var evdevManager *evdev.Manager
|
||||||
@@ -252,30 +250,6 @@ func InitializeCupsManager() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeDwlManager() error {
|
|
||||||
log.Info("Attempting to initialize DWL IPC...")
|
|
||||||
|
|
||||||
if wlContext == nil {
|
|
||||||
ctx, err := wlcontext.New()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to create shared Wayland context: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wlContext = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
manager, err := dwl.NewManager(wlContext.Display())
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("Failed to initialize dwl manager: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dwlManager = manager
|
|
||||||
|
|
||||||
log.Info("DWL IPC initialized successfully")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitializeBrightnessManager() error {
|
func InitializeBrightnessManager() error {
|
||||||
manager, err := brightness.NewManager()
|
manager, err := brightness.NewManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -468,10 +442,6 @@ func getCapabilities() Capabilities {
|
|||||||
caps = append(caps, "tailscale")
|
caps = append(caps, "tailscale")
|
||||||
}
|
}
|
||||||
|
|
||||||
if dwlManager != nil {
|
|
||||||
caps = append(caps, "dwl")
|
|
||||||
}
|
|
||||||
|
|
||||||
if brightnessManager != nil {
|
if brightnessManager != nil {
|
||||||
caps = append(caps, "brightness")
|
caps = append(caps, "brightness")
|
||||||
}
|
}
|
||||||
@@ -538,10 +508,6 @@ func getServerInfo() ServerInfo {
|
|||||||
caps = append(caps, "tailscale")
|
caps = append(caps, "tailscale")
|
||||||
}
|
}
|
||||||
|
|
||||||
if dwlManager != nil {
|
|
||||||
caps = append(caps, "dwl")
|
|
||||||
}
|
|
||||||
|
|
||||||
if brightnessManager != nil {
|
if brightnessManager != nil {
|
||||||
caps = append(caps, "brightness")
|
caps = append(caps, "brightness")
|
||||||
}
|
}
|
||||||
@@ -1046,38 +1012,6 @@ func handleSubscribe(conn net.Conn, req models.Request) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldSubscribe("dwl") && dwlManager != nil {
|
|
||||||
wg.Add(1)
|
|
||||||
dwlChan := dwlManager.Subscribe(clientID + "-dwl")
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
defer dwlManager.Unsubscribe(clientID + "-dwl")
|
|
||||||
|
|
||||||
initialState := dwlManager.GetState()
|
|
||||||
select {
|
|
||||||
case eventChan <- ServiceEvent{Service: "dwl", Data: initialState}:
|
|
||||||
case <-stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case state, ok := <-dwlChan:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case eventChan <- ServiceEvent{Service: "dwl", Data: state}:
|
|
||||||
case <-stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if shouldSubscribe("brightness") && brightnessManager != nil {
|
if shouldSubscribe("brightness") && brightnessManager != nil {
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
brightnessStateChan := brightnessManager.Subscribe(clientID + "-brightness-state")
|
brightnessStateChan := brightnessManager.Subscribe(clientID + "-brightness-state")
|
||||||
@@ -1333,9 +1267,6 @@ func cleanupManagers() {
|
|||||||
if cupsManager != nil {
|
if cupsManager != nil {
|
||||||
cupsManager.Close()
|
cupsManager.Close()
|
||||||
}
|
}
|
||||||
if dwlManager != nil {
|
|
||||||
dwlManager.Close()
|
|
||||||
}
|
|
||||||
if brightnessManager != nil {
|
if brightnessManager != nil {
|
||||||
brightnessManager.Close()
|
brightnessManager.Close()
|
||||||
}
|
}
|
||||||
@@ -1502,19 +1433,6 @@ func Start(printDocs bool) error {
|
|||||||
log.Info(" cups.resumePrinter - Resume printer (params: printerName)")
|
log.Info(" cups.resumePrinter - Resume printer (params: printerName)")
|
||||||
log.Info(" cups.cancelJob - Cancel job (params: printerName, jobID)")
|
log.Info(" cups.cancelJob - Cancel job (params: printerName, jobID)")
|
||||||
log.Info(" cups.purgeJobs - Cancel all jobs (params: printerName)")
|
log.Info(" cups.purgeJobs - Cancel all jobs (params: printerName)")
|
||||||
log.Info("DWL:")
|
|
||||||
log.Info(" dwl.getState - Get current dwl state (tags, windows, layouts, keyboard)")
|
|
||||||
log.Info(" dwl.setTags - Set active tags (params: output, tagmask, toggleTagset)")
|
|
||||||
log.Info(" dwl.setClientTags - Set focused client tags (params: output, andTags, xorTags)")
|
|
||||||
log.Info(" dwl.setLayout - Set layout (params: output, index)")
|
|
||||||
log.Info(" dwl.subscribe - Subscribe to dwl state changes (streaming)")
|
|
||||||
log.Info(" Output state includes:")
|
|
||||||
log.Info(" - tags : Tag states (active, clients, focused)")
|
|
||||||
log.Info(" - layoutSymbol : Current layout name")
|
|
||||||
log.Info(" - title : Focused window title")
|
|
||||||
log.Info(" - appId : Focused window app ID")
|
|
||||||
log.Info(" - kbLayout : Current keyboard layout")
|
|
||||||
log.Info(" - keymode : Current keybind mode")
|
|
||||||
log.Info("Brightness:")
|
log.Info("Brightness:")
|
||||||
log.Info(" brightness.getState - Get current brightness state for all devices")
|
log.Info(" brightness.getState - Get current brightness state for all devices")
|
||||||
log.Info(" brightness.setBrightness - Set device brightness (params: device, percent)")
|
log.Info(" brightness.setBrightness - Set device brightness (params: device, percent)")
|
||||||
@@ -1691,10 +1609,6 @@ func Start(printDocs bool) error {
|
|||||||
log.Debugf("AppPicker manager unavailable: %v", err)
|
log.Debugf("AppPicker manager unavailable: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := InitializeDwlManager(); err != nil {
|
|
||||||
log.Debugf("DWL manager unavailable: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := InitializeWlrOutputManager(); err != nil {
|
if err := InitializeWlrOutputManager(); err != nil {
|
||||||
log.Debugf("WlrOutput manager unavailable: %v", err)
|
log.Debugf("WlrOutput manager unavailable: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ func (m Model) viewInstallComplete() string {
|
|||||||
|
|
||||||
wm := m.selectedWindowManager()
|
wm := m.selectedWindowManager()
|
||||||
|
|
||||||
// mango launches DMS via `exec_once=dms run` (not a systemd session target)
|
// mango launches DMS via `exec-once=dms run` (not a systemd session target)
|
||||||
loginHint := "If you do not have a greeter, login with \"niri-session\" or \"Hyprland\""
|
loginHint := "If you do not have a greeter, login with \"niri-session\" or \"Hyprland\""
|
||||||
switch wm {
|
switch wm {
|
||||||
case deps.WindowManagerNiri:
|
case deps.WindowManagerNiri:
|
||||||
@@ -223,7 +223,7 @@ func (m Model) viewInstallComplete() string {
|
|||||||
|
|
||||||
b.WriteString(labelStyle.Render("Troubleshooting:") + "\n")
|
b.WriteString(labelStyle.Render("Troubleshooting:") + "\n")
|
||||||
if wm == deps.WindowManagerMango {
|
if wm == deps.WindowManagerMango {
|
||||||
b.WriteString(labelStyle.Render(" Disable autostart: ") + cmdStyle.Render("remove 'exec_once=dms run' from ~/.config/mango/config.conf") + "\n")
|
b.WriteString(labelStyle.Render(" Disable autostart: ") + cmdStyle.Render("remove 'exec-once=dms run' from ~/.config/mango/config.conf") + "\n")
|
||||||
b.WriteString(labelStyle.Render(" View logs: ") + cmdStyle.Render("qs -p ~/.config/quickshell/dms log") + "\n")
|
b.WriteString(labelStyle.Render(" View logs: ") + cmdStyle.Render("qs -p ~/.config/quickshell/dms log") + "\n")
|
||||||
} else {
|
} else {
|
||||||
b.WriteString(labelStyle.Render(" Disable autostart: ") + cmdStyle.Render("systemctl --user disable dms") + "\n")
|
b.WriteString(labelStyle.Render(" Disable autostart: ") + cmdStyle.Render("systemctl --user disable dms") + "\n")
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ Singleton {
|
|||||||
readonly property int durMed: 450
|
readonly property int durMed: 450
|
||||||
readonly property int durLong: 600
|
readonly property int durLong: 600
|
||||||
|
|
||||||
|
// Navigation feedback stays responsive even when ambient shell motion is slow.
|
||||||
|
readonly property int settingsNavigationStateDuration: 180
|
||||||
|
readonly property int settingsNavigationRippleDuration: 200
|
||||||
|
|
||||||
readonly property int slidePx: 80
|
readonly property int slidePx: 80
|
||||||
|
|
||||||
readonly property var emphasized: [0.05, 0.00, 0.133333, 0.06, 0.166667, 0.40, 0.208333, 0.82, 0.25, 1.00, 1.00, 1.00]
|
readonly property var emphasized: [0.05, 0.00, 0.133333, 0.06, 0.166667, 0.40, 0.208333, 0.82, 0.25, 1.00, 1.00, 1.00]
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var modalHandle
|
||||||
|
required property string claimPrefix
|
||||||
|
property string surfaceKind: "modal"
|
||||||
|
property string screenName: ""
|
||||||
|
property bool enabled: false
|
||||||
|
property bool active: false
|
||||||
|
property bool presented: false
|
||||||
|
property bool dockBlocked: false
|
||||||
|
property string dockSide: ""
|
||||||
|
|
||||||
|
property alias claimId: lease.claimId
|
||||||
|
property alias claimedScreenName: lease.claimedScreenName
|
||||||
|
|
||||||
|
signal recoveryRequested
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
function _isCurrentModal(name) {
|
||||||
|
return !!name && ModalManager.isCurrentModal(modalHandle, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectedSurfaceLease {
|
||||||
|
id: lease
|
||||||
|
claimPrefix: root.claimPrefix
|
||||||
|
screenName: root.screenName
|
||||||
|
enabled: root.enabled
|
||||||
|
active: root.active
|
||||||
|
presented: root.presented
|
||||||
|
dockBlocked: root.dockBlocked
|
||||||
|
dockSide: root.dockSide
|
||||||
|
isCurrentOwner: function(name) {
|
||||||
|
return root._isCurrentModal(name);
|
||||||
|
}
|
||||||
|
hasOwner: function(name, ownerId) {
|
||||||
|
return ConnectedModeState.hasModalOwner(name, ownerId);
|
||||||
|
}
|
||||||
|
statePresent: function(name, ownerId) {
|
||||||
|
return ConnectedModeState.hasModalOwner(name, ownerId) && ConnectedModeState.hasSurfaceDescriptor(name, root.surfaceKind, ownerId);
|
||||||
|
}
|
||||||
|
claimState: function(name, state, ownerId) {
|
||||||
|
return ConnectedModeState.claimModalState(name, state, ownerId);
|
||||||
|
}
|
||||||
|
ensureState: function(name, state, ownerId) {
|
||||||
|
return ConnectedModeState.ensureModalState(name, state, ownerId);
|
||||||
|
}
|
||||||
|
releaseState: function(name, ownerId) {
|
||||||
|
return ConnectedModeState.clearModalState(name, ownerId);
|
||||||
|
}
|
||||||
|
updateAnimationState: function(name, ownerId, animX, animY) {
|
||||||
|
return ConnectedModeState.setModalAnim(name, animX, animY, ownerId);
|
||||||
|
}
|
||||||
|
updateBodyState: function(name, ownerId, bodyX, bodyY, bodyW, bodyH) {
|
||||||
|
return ConnectedModeState.setModalBody(name, bodyX, bodyY, bodyW, bodyH, ownerId);
|
||||||
|
}
|
||||||
|
requestDockRetract: function(ownerId, name, side) {
|
||||||
|
return ConnectedModeState.requestDockRetract(ownerId, name, side);
|
||||||
|
}
|
||||||
|
releaseDockRetract: function(ownerId) {
|
||||||
|
return ConnectedModeState.releaseDockRetract(ownerId);
|
||||||
|
}
|
||||||
|
onRecoveryRequested: root.recoveryRequested()
|
||||||
|
}
|
||||||
|
|
||||||
|
function publish(state) {
|
||||||
|
return lease.publish(Object.assign({}, state, {
|
||||||
|
"kind": root.surfaceKind,
|
||||||
|
"screenName": root.screenName,
|
||||||
|
"presented": root.presented,
|
||||||
|
"dockRetractSide": root.dockBlocked ? root.dockSide : ""
|
||||||
|
}), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAnim(animX, animY) {
|
||||||
|
return lease.updateAnim(animX, animY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBody(bodyX, bodyY, bodyW, bodyH) {
|
||||||
|
return lease.updateBody(bodyX, bodyY, bodyW, bodyH);
|
||||||
|
}
|
||||||
|
|
||||||
|
function release() {
|
||||||
|
return lease.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ModalManager
|
||||||
|
function onModalChanged() {
|
||||||
|
lease.requestRecovery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ConnectedModeState
|
||||||
|
function onModalOwnersChanged() {
|
||||||
|
lease.checkOwnershipRecovery();
|
||||||
|
}
|
||||||
|
function onModalStatesChanged() {
|
||||||
|
lease.checkStateRecovery();
|
||||||
|
}
|
||||||
|
function onSurfaceDescriptorsChanged() {
|
||||||
|
lease.checkStateRecovery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,123 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import "ConnectedSurfaceDescriptor.js" as SurfaceDescriptor
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property var surfaceDescriptors: ({})
|
||||||
|
|
||||||
|
function _surfaceSlot(kind) {
|
||||||
|
return SurfaceDescriptor.slotForKind(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
function surfaceDescriptor(screenName, kind) {
|
||||||
|
const slot = _surfaceSlot(kind);
|
||||||
|
const screenDescriptors = screenName ? surfaceDescriptors[screenName] : null;
|
||||||
|
const descriptor = screenDescriptors && screenDescriptors[slot] ? screenDescriptors[slot] : SurfaceDescriptor.empty(kind, screenName);
|
||||||
|
let bodyRect = descriptor.bodyRect;
|
||||||
|
let animationOffset = descriptor.animationOffset;
|
||||||
|
if (slot === "popout" && popoutScreen === screenName) {
|
||||||
|
bodyRect = {
|
||||||
|
"x": popoutBodyX,
|
||||||
|
"y": popoutBodyY,
|
||||||
|
"width": popoutBodyW,
|
||||||
|
"height": popoutBodyH
|
||||||
|
};
|
||||||
|
animationOffset = {
|
||||||
|
"x": popoutAnimX,
|
||||||
|
"y": popoutAnimY
|
||||||
|
};
|
||||||
|
} else if (slot === "modal" && modalStates[screenName]) {
|
||||||
|
const modal = modalStates[screenName];
|
||||||
|
bodyRect = {
|
||||||
|
"x": modal.bodyX,
|
||||||
|
"y": modal.bodyY,
|
||||||
|
"width": modal.bodyW,
|
||||||
|
"height": modal.bodyH
|
||||||
|
};
|
||||||
|
animationOffset = {
|
||||||
|
"x": modal.animX,
|
||||||
|
"y": modal.animY
|
||||||
|
};
|
||||||
|
} else if (slot === "dock" && dockStates[screenName]) {
|
||||||
|
const dock = dockStates[screenName];
|
||||||
|
const slide = dockSlides[screenName] || {
|
||||||
|
"x": dock.slideX,
|
||||||
|
"y": dock.slideY
|
||||||
|
};
|
||||||
|
bodyRect = {
|
||||||
|
"x": dock.bodyX,
|
||||||
|
"y": dock.bodyY,
|
||||||
|
"width": dock.bodyW,
|
||||||
|
"height": dock.bodyH
|
||||||
|
};
|
||||||
|
animationOffset = {
|
||||||
|
"x": slide.x,
|
||||||
|
"y": slide.y
|
||||||
|
};
|
||||||
|
} else if (slot === "notification" && notificationStates[screenName]) {
|
||||||
|
const notification = notificationStates[screenName];
|
||||||
|
bodyRect = {
|
||||||
|
"x": notification.bodyX,
|
||||||
|
"y": notification.bodyY,
|
||||||
|
"width": notification.bodyW,
|
||||||
|
"height": notification.bodyH
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return SurfaceDescriptor.normalize({
|
||||||
|
"bodyRect": bodyRect,
|
||||||
|
"animationOffset": animationOffset
|
||||||
|
}, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasSurfaceDescriptor(screenName, kind, ownerId) {
|
||||||
|
const descriptor = surfaceDescriptor(screenName, kind);
|
||||||
|
return descriptor.phase !== "hidden" && (!ownerId || descriptor.ownerId === ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setSurfaceDescriptor(screenName, slotKind, state, ownerId) {
|
||||||
|
if (!screenName || !state)
|
||||||
|
return false;
|
||||||
|
const slot = _surfaceSlot(slotKind);
|
||||||
|
const currentScreen = surfaceDescriptors[screenName] || {};
|
||||||
|
const previous = currentScreen[slot] || SurfaceDescriptor.empty(state.kind || slotKind, screenName);
|
||||||
|
let normalized = SurfaceDescriptor.normalize(Object.assign({}, state, {
|
||||||
|
"ownerId": ownerId !== undefined ? ownerId : previous.ownerId,
|
||||||
|
"screenName": screenName,
|
||||||
|
"revision": previous.revision
|
||||||
|
}), previous);
|
||||||
|
if (SurfaceDescriptor.same(previous, normalized))
|
||||||
|
return true;
|
||||||
|
normalized = SurfaceDescriptor.withRevision(normalized, previous.revision + 1);
|
||||||
|
const nextScreen = _cloneDict(currentScreen);
|
||||||
|
nextScreen[slot] = normalized;
|
||||||
|
const next = _cloneDict(surfaceDescriptors);
|
||||||
|
next[screenName] = nextScreen;
|
||||||
|
surfaceDescriptors = next;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _clearSurfaceDescriptor(screenName, kind, ownerId) {
|
||||||
|
if (!screenName)
|
||||||
|
return false;
|
||||||
|
const slot = _surfaceSlot(kind);
|
||||||
|
const currentScreen = surfaceDescriptors[screenName];
|
||||||
|
const current = currentScreen ? currentScreen[slot] : null;
|
||||||
|
if (!current || (ownerId && current.ownerId !== ownerId))
|
||||||
|
return false;
|
||||||
|
const nextScreen = _cloneDict(currentScreen);
|
||||||
|
delete nextScreen[slot];
|
||||||
|
const next = _cloneDict(surfaceDescriptors);
|
||||||
|
if (Object.keys(nextScreen).length > 0)
|
||||||
|
next[screenName] = nextScreen;
|
||||||
|
else
|
||||||
|
delete next[screenName];
|
||||||
|
surfaceDescriptors = next;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var emptyDockState: ({
|
readonly property var emptyDockState: ({
|
||||||
"reveal": false,
|
"reveal": false,
|
||||||
"barSide": "bottom",
|
"barSide": "bottom",
|
||||||
@@ -18,7 +131,6 @@ Singleton {
|
|||||||
"slideY": 0
|
"slideY": 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// Popout state (updated by DankPopout when connectedFrameModeActive)
|
|
||||||
property string popoutOwnerId: ""
|
property string popoutOwnerId: ""
|
||||||
property bool popoutVisible: false
|
property bool popoutVisible: false
|
||||||
property string popoutBarSide: "top"
|
property string popoutBarSide: "top"
|
||||||
@@ -32,12 +144,12 @@ Singleton {
|
|||||||
property bool popoutOmitStartConnector: false
|
property bool popoutOmitStartConnector: false
|
||||||
property bool popoutOmitEndConnector: false
|
property bool popoutOmitEndConnector: false
|
||||||
|
|
||||||
// Dock state (updated by Dock when connectedFrameModeActive), keyed by screen.name
|
|
||||||
property var dockStates: ({})
|
property var dockStates: ({})
|
||||||
|
|
||||||
// Dock slide offsets — hot-path updates separated from full geometry state
|
|
||||||
property var dockSlides: ({})
|
property var dockSlides: ({})
|
||||||
|
|
||||||
|
property var surfaceRevisions: ({})
|
||||||
|
|
||||||
function _cloneDict(src) {
|
function _cloneDict(src) {
|
||||||
const next = {};
|
const next = {};
|
||||||
for (const k in src)
|
for (const k in src)
|
||||||
@@ -45,16 +157,33 @@ Singleton {
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _bumpSurfaceRevision(screenName) {
|
||||||
|
if (!screenName)
|
||||||
|
return;
|
||||||
|
const next = _cloneDict(surfaceRevisions);
|
||||||
|
next[screenName] = Number(next[screenName] || 0) + 1;
|
||||||
|
surfaceRevisions = next;
|
||||||
|
}
|
||||||
|
|
||||||
function hasPopoutOwner(claimId) {
|
function hasPopoutOwner(claimId) {
|
||||||
return !!claimId && popoutOwnerId === claimId;
|
return !!claimId && popoutOwnerId === claimId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function claimPopout(claimId, state) {
|
function claimPopout(claimId, state) {
|
||||||
if (!claimId)
|
if (!claimId || !state)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
const previousScreen = popoutScreen;
|
||||||
popoutOwnerId = claimId;
|
popoutOwnerId = claimId;
|
||||||
return updatePopout(claimId, state);
|
const ok = updatePopout(claimId, state);
|
||||||
|
if (ok) {
|
||||||
|
if (previousScreen && previousScreen !== popoutScreen) {
|
||||||
|
_clearSurfaceDescriptor(previousScreen, "popout");
|
||||||
|
_bumpSurfaceRevision(previousScreen);
|
||||||
|
}
|
||||||
|
_bumpSurfaceRevision(popoutScreen);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePopout(claimId, state) {
|
function updatePopout(claimId, state) {
|
||||||
@@ -84,6 +213,21 @@ Singleton {
|
|||||||
if (state.omitEndConnector !== undefined)
|
if (state.omitEndConnector !== undefined)
|
||||||
popoutOmitEndConnector = !!state.omitEndConnector;
|
popoutOmitEndConnector = !!state.omitEndConnector;
|
||||||
|
|
||||||
|
_setSurfaceDescriptor(popoutScreen, "popout", Object.assign({}, state, {
|
||||||
|
"kind": "popout",
|
||||||
|
"screenName": popoutScreen,
|
||||||
|
"visible": popoutVisible,
|
||||||
|
"presented": state.presented !== undefined ? !!state.presented : popoutVisible,
|
||||||
|
"barSide": popoutBarSide,
|
||||||
|
"bodyX": popoutBodyX,
|
||||||
|
"bodyY": popoutBodyY,
|
||||||
|
"bodyW": popoutBodyW,
|
||||||
|
"bodyH": popoutBodyH,
|
||||||
|
"animX": popoutAnimX,
|
||||||
|
"animY": popoutAnimY,
|
||||||
|
"omitStartConnector": popoutOmitStartConnector,
|
||||||
|
"omitEndConnector": popoutOmitEndConnector
|
||||||
|
}), claimId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +235,7 @@ Singleton {
|
|||||||
if (!hasPopoutOwner(claimId))
|
if (!hasPopoutOwner(claimId))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
const releasedScreen = popoutScreen;
|
||||||
popoutOwnerId = "";
|
popoutOwnerId = "";
|
||||||
popoutVisible = false;
|
popoutVisible = false;
|
||||||
popoutBarSide = "top";
|
popoutBarSide = "top";
|
||||||
@@ -103,6 +248,8 @@ Singleton {
|
|||||||
popoutScreen = "";
|
popoutScreen = "";
|
||||||
popoutOmitStartConnector = false;
|
popoutOmitStartConnector = false;
|
||||||
popoutOmitEndConnector = false;
|
popoutOmitEndConnector = false;
|
||||||
|
_clearSurfaceDescriptor(releasedScreen, "popout", claimId);
|
||||||
|
_bumpSurfaceRevision(releasedScreen);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,12 +319,23 @@ Singleton {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const normalized = _normalizeDockState(state);
|
const normalized = _normalizeDockState(state);
|
||||||
if (_sameDockState(dockStates[screenName], normalized))
|
const descriptorState = Object.assign({}, state, normalized, {
|
||||||
return true;
|
"kind": "dock",
|
||||||
|
"screenName": screenName,
|
||||||
const next = _cloneDict(dockStates);
|
"visible": normalized.reveal,
|
||||||
next[screenName] = normalized;
|
"presented": normalized.reveal,
|
||||||
dockStates = next;
|
"phase": normalized.reveal ? (state.phase || "open") : "hidden"
|
||||||
|
});
|
||||||
|
const previous = dockStates[screenName] || emptyDockState;
|
||||||
|
const stateChanged = !_sameDockState(dockStates[screenName], normalized);
|
||||||
|
if (stateChanged) {
|
||||||
|
const next = _cloneDict(dockStates);
|
||||||
|
next[screenName] = normalized;
|
||||||
|
dockStates = next;
|
||||||
|
}
|
||||||
|
_setSurfaceDescriptor(screenName, "dock", descriptorState, "dock:" + screenName);
|
||||||
|
if (!!previous.reveal !== !!normalized.reveal)
|
||||||
|
_bumpSurfaceRevision(screenName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,13 +346,14 @@ Singleton {
|
|||||||
const next = _cloneDict(dockStates);
|
const next = _cloneDict(dockStates);
|
||||||
delete next[screenName];
|
delete next[screenName];
|
||||||
dockStates = next;
|
dockStates = next;
|
||||||
|
_clearSurfaceDescriptor(screenName, "dock");
|
||||||
|
|
||||||
// Also clear corresponding slide
|
|
||||||
if (dockSlides[screenName]) {
|
if (dockSlides[screenName]) {
|
||||||
const nextSlides = _cloneDict(dockSlides);
|
const nextSlides = _cloneDict(dockSlides);
|
||||||
delete nextSlides[screenName];
|
delete nextSlides[screenName];
|
||||||
dockSlides = nextSlides;
|
dockSlides = nextSlides;
|
||||||
}
|
}
|
||||||
|
_bumpSurfaceRevision(screenName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,12 +417,22 @@ Singleton {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const normalized = _normalizeNotificationState(state);
|
const normalized = _normalizeNotificationState(state);
|
||||||
if (_sameNotificationState(notificationStates[screenName], normalized))
|
const descriptorState = Object.assign({}, state, normalized, {
|
||||||
return true;
|
"kind": "notification",
|
||||||
|
"screenName": screenName,
|
||||||
const next = _cloneDict(notificationStates);
|
"presented": normalized.visible,
|
||||||
next[screenName] = normalized;
|
"phase": normalized.visible ? (state.phase || "open") : "hidden"
|
||||||
notificationStates = next;
|
});
|
||||||
|
const previous = notificationStates[screenName] || emptyNotificationState;
|
||||||
|
const stateChanged = !_sameNotificationState(notificationStates[screenName], normalized);
|
||||||
|
if (stateChanged) {
|
||||||
|
const next = _cloneDict(notificationStates);
|
||||||
|
next[screenName] = normalized;
|
||||||
|
notificationStates = next;
|
||||||
|
}
|
||||||
|
_setSurfaceDescriptor(screenName, "notification", descriptorState, "notification:" + screenName);
|
||||||
|
if (!!previous.visible !== !!normalized.visible)
|
||||||
|
_bumpSurfaceRevision(screenName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,10 +443,11 @@ Singleton {
|
|||||||
const next = _cloneDict(notificationStates);
|
const next = _cloneDict(notificationStates);
|
||||||
delete next[screenName];
|
delete next[screenName];
|
||||||
notificationStates = next;
|
notificationStates = next;
|
||||||
|
_clearSurfaceDescriptor(screenName, "notification");
|
||||||
|
_bumpSurfaceRevision(screenName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DankModal / DankLauncherV2Modal State
|
|
||||||
readonly property var emptyModalState: ({
|
readonly property var emptyModalState: ({
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"barSide": "bottom",
|
"barSide": "bottom",
|
||||||
@@ -330,52 +500,77 @@ Singleton {
|
|||||||
modalOwners = nextOwners;
|
modalOwners = nextOwners;
|
||||||
}
|
}
|
||||||
const normalized = _normalizeModalState(state);
|
const normalized = _normalizeModalState(state);
|
||||||
if (_sameModalState(modalStates[screenName], normalized))
|
|
||||||
return true;
|
|
||||||
const next = _cloneDict(modalStates);
|
const next = _cloneDict(modalStates);
|
||||||
next[screenName] = normalized;
|
next[screenName] = normalized;
|
||||||
modalStates = next;
|
modalStates = next;
|
||||||
|
_setSurfaceDescriptor(screenName, "modal", Object.assign({}, state, normalized, {
|
||||||
|
"kind": state.kind || "modal",
|
||||||
|
"screenName": screenName
|
||||||
|
}), ownerId || "");
|
||||||
|
_bumpSurfaceRevision(screenName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateModalState(screenName, state, ownerId) {
|
function updateModalState(screenName, state, ownerId) {
|
||||||
if (!screenName || !state)
|
if (!screenName || !state)
|
||||||
return false;
|
return false;
|
||||||
if (ownerId && modalOwners[screenName] && modalOwners[screenName] !== ownerId)
|
if (ownerId && modalOwners[screenName] !== ownerId)
|
||||||
return false;
|
return false;
|
||||||
const normalized = _normalizeModalState(state);
|
const normalized = _normalizeModalState(state);
|
||||||
if (_sameModalState(modalStates[screenName], normalized))
|
const descriptorState = Object.assign({}, state, normalized, {
|
||||||
return true;
|
"kind": state.kind || (surfaceDescriptor(screenName, "modal").kind || "modal"),
|
||||||
const next = _cloneDict(modalStates);
|
"screenName": screenName
|
||||||
next[screenName] = normalized;
|
});
|
||||||
modalStates = next;
|
if (!_sameModalState(modalStates[screenName], normalized)) {
|
||||||
|
const next = _cloneDict(modalStates);
|
||||||
|
next[screenName] = normalized;
|
||||||
|
modalStates = next;
|
||||||
|
}
|
||||||
|
_setSurfaceDescriptor(screenName, "modal", descriptorState, ownerId || modalOwners[screenName] || "");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setModalState(screenName, state) {
|
function hasModalOwner(screenName, ownerId) {
|
||||||
return updateModalState(screenName, state, null);
|
return !!screenName && !!ownerId && modalOwners[screenName] === ownerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureModalState(screenName, state, ownerId) {
|
||||||
|
if (!screenName || !state || !ownerId)
|
||||||
|
return false;
|
||||||
|
const currentOwner = modalOwners[screenName] || "";
|
||||||
|
if (currentOwner && currentOwner !== ownerId)
|
||||||
|
return false;
|
||||||
|
if (!currentOwner)
|
||||||
|
return claimModalState(screenName, state, ownerId);
|
||||||
|
return updateModalState(screenName, state, ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearModalState(screenName, ownerId) {
|
function clearModalState(screenName, ownerId) {
|
||||||
if (!screenName || !modalStates[screenName])
|
if (!screenName)
|
||||||
return false;
|
return false;
|
||||||
if (ownerId && modalOwners[screenName] && modalOwners[screenName] !== ownerId)
|
if (ownerId && modalOwners[screenName] !== ownerId)
|
||||||
|
return false;
|
||||||
|
if (!modalStates[screenName] && !modalOwners[screenName])
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const next = _cloneDict(modalStates);
|
if (modalStates[screenName]) {
|
||||||
delete next[screenName];
|
const next = _cloneDict(modalStates);
|
||||||
modalStates = next;
|
delete next[screenName];
|
||||||
|
modalStates = next;
|
||||||
|
}
|
||||||
|
|
||||||
if (modalOwners[screenName]) {
|
if (modalOwners[screenName]) {
|
||||||
const nextOwners = _cloneDict(modalOwners);
|
const nextOwners = _cloneDict(modalOwners);
|
||||||
delete nextOwners[screenName];
|
delete nextOwners[screenName];
|
||||||
modalOwners = nextOwners;
|
modalOwners = nextOwners;
|
||||||
}
|
}
|
||||||
|
_clearSurfaceDescriptor(screenName, "modal", ownerId);
|
||||||
|
_bumpSurfaceRevision(screenName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setModalAnim(screenName, animX, animY, ownerId) {
|
function setModalAnim(screenName, animX, animY, ownerId) {
|
||||||
if (ownerId && modalOwners[screenName] && modalOwners[screenName] !== ownerId)
|
if (ownerId && modalOwners[screenName] !== ownerId)
|
||||||
return false;
|
return false;
|
||||||
const cur = screenName ? modalStates[screenName] : null;
|
const cur = screenName ? modalStates[screenName] : null;
|
||||||
if (!cur)
|
if (!cur)
|
||||||
@@ -394,7 +589,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setModalBody(screenName, bodyX, bodyY, bodyW, bodyH, ownerId) {
|
function setModalBody(screenName, bodyX, bodyY, bodyW, bodyH, ownerId) {
|
||||||
if (ownerId && modalOwners[screenName] && modalOwners[screenName] !== ownerId)
|
if (ownerId && modalOwners[screenName] !== ownerId)
|
||||||
return false;
|
return false;
|
||||||
const cur = screenName ? modalStates[screenName] : null;
|
const cur = screenName ? modalStates[screenName] : null;
|
||||||
if (!cur)
|
if (!cur)
|
||||||
@@ -453,9 +648,6 @@ Singleton {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune state for screens that are no longer connected. Stale entries
|
|
||||||
// accumulate across hotplug cycles otherwise — Frame's per-screen
|
|
||||||
// FrameInstance doesn't notice when its peer dicts go orphan.
|
|
||||||
function _pruneToLiveScreens() {
|
function _pruneToLiveScreens() {
|
||||||
const live = {};
|
const live = {};
|
||||||
const screens = Quickshell.screens || [];
|
const screens = Quickshell.screens || [];
|
||||||
@@ -492,6 +684,12 @@ Singleton {
|
|||||||
const nextModalOwners = pruneKeyed(modalOwners);
|
const nextModalOwners = pruneKeyed(modalOwners);
|
||||||
if (nextModalOwners !== null)
|
if (nextModalOwners !== null)
|
||||||
modalOwners = nextModalOwners;
|
modalOwners = nextModalOwners;
|
||||||
|
const nextSurfaceRevisions = pruneKeyed(surfaceRevisions);
|
||||||
|
if (nextSurfaceRevisions !== null)
|
||||||
|
surfaceRevisions = nextSurfaceRevisions;
|
||||||
|
const nextDescriptors = pruneKeyed(surfaceDescriptors);
|
||||||
|
if (nextDescriptors !== null)
|
||||||
|
surfaceDescriptors = nextDescriptors;
|
||||||
|
|
||||||
let retractChanged = false;
|
let retractChanged = false;
|
||||||
const nextRetract = {};
|
const nextRetract = {};
|
||||||
@@ -512,7 +710,12 @@ Singleton {
|
|||||||
Connections {
|
Connections {
|
||||||
target: Quickshell
|
target: Quickshell
|
||||||
function onScreensChanged() {
|
function onScreensChanged() {
|
||||||
root._pruneToLiveScreens();
|
screenPruneAction.schedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeferredAction {
|
||||||
|
id: screenPruneAction
|
||||||
|
onTriggered: root._pruneToLiveScreens()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
.pragma library
|
||||||
|
|
||||||
|
var VALID_KINDS = {
|
||||||
|
"popout": true,
|
||||||
|
"modal": true,
|
||||||
|
"launcher": true,
|
||||||
|
"dock": true,
|
||||||
|
"notification": true
|
||||||
|
};
|
||||||
|
|
||||||
|
var VALID_PHASES = {
|
||||||
|
"opening": true,
|
||||||
|
"open": true,
|
||||||
|
"closing": true,
|
||||||
|
"hidden": true,
|
||||||
|
"recovering": true
|
||||||
|
};
|
||||||
|
|
||||||
|
function _number(value, fallback) {
|
||||||
|
var n = Number(value);
|
||||||
|
return isNaN(n) ? fallback : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bool(value, fallback) {
|
||||||
|
return value === undefined ? fallback : !!value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _kind(value, fallback) {
|
||||||
|
if (VALID_KINDS[value])
|
||||||
|
return value;
|
||||||
|
return VALID_KINDS[fallback] ? fallback : "modal";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _defaultBarSide(kind) {
|
||||||
|
return kind === "popout" || kind === "notification" ? "top" : "bottom";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _barSide(value, fallback) {
|
||||||
|
if (value === "top" || value === "bottom" || value === "left" || value === "right")
|
||||||
|
return value;
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function slotForKind(kind) {
|
||||||
|
return kind === "launcher" ? "modal" : _kind(kind, "modal");
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferPhase(visible, presented, requestedPhase) {
|
||||||
|
if (VALID_PHASES[requestedPhase])
|
||||||
|
return requestedPhase;
|
||||||
|
if (!visible && !presented)
|
||||||
|
return "hidden";
|
||||||
|
if (!visible && presented)
|
||||||
|
return "closing";
|
||||||
|
return "open";
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(input, defaults) {
|
||||||
|
var source = input || {};
|
||||||
|
var base = defaults || {};
|
||||||
|
var kind = _kind(source.kind, base.kind);
|
||||||
|
var defaultSide = _defaultBarSide(kind);
|
||||||
|
var sourceRect = source.bodyRect || {};
|
||||||
|
var baseRect = base.bodyRect || {};
|
||||||
|
var sourceOffset = source.animationOffset || {};
|
||||||
|
var baseOffset = base.animationOffset || {};
|
||||||
|
var visible = _bool(source.visible !== undefined ? source.visible : source.reveal, _bool(base.visible !== undefined ? base.visible : base.reveal, false));
|
||||||
|
var presented = _bool(source.presented, _bool(base.presented, visible));
|
||||||
|
var bodyRect = {
|
||||||
|
"x": _number(sourceRect.x !== undefined ? sourceRect.x : source.bodyX, _number(baseRect.x !== undefined ? baseRect.x : base.bodyX, 0)),
|
||||||
|
"y": _number(sourceRect.y !== undefined ? sourceRect.y : source.bodyY, _number(baseRect.y !== undefined ? baseRect.y : base.bodyY, 0)),
|
||||||
|
"width": Math.max(0, _number(sourceRect.width !== undefined ? sourceRect.width : source.bodyW, _number(baseRect.width !== undefined ? baseRect.width : base.bodyW, 0))),
|
||||||
|
"height": Math.max(0, _number(sourceRect.height !== undefined ? sourceRect.height : source.bodyH, _number(baseRect.height !== undefined ? baseRect.height : base.bodyH, 0)))
|
||||||
|
};
|
||||||
|
var animationOffset = {
|
||||||
|
"x": _number(sourceOffset.x !== undefined ? sourceOffset.x : (source.animX !== undefined ? source.animX : source.slideX), _number(baseOffset.x !== undefined ? baseOffset.x : (base.animX !== undefined ? base.animX : base.slideX), 0)),
|
||||||
|
"y": _number(sourceOffset.y !== undefined ? sourceOffset.y : (source.animY !== undefined ? source.animY : source.slideY), _number(baseOffset.y !== undefined ? baseOffset.y : (base.animY !== undefined ? base.animY : base.slideY), 0))
|
||||||
|
};
|
||||||
|
var screenName = source.screenName !== undefined ? source.screenName : (source.screen !== undefined ? source.screen : (base.screenName !== undefined ? base.screenName : base.screen));
|
||||||
|
var opacity = Math.max(0, Math.min(1, _number(source.opacity, _number(base.opacity, 1))));
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ownerId": String(source.ownerId !== undefined ? source.ownerId : (base.ownerId || "")),
|
||||||
|
"kind": kind,
|
||||||
|
"screenName": String(screenName || ""),
|
||||||
|
"phase": inferPhase(visible, presented, source.phase !== undefined ? source.phase : base.phase),
|
||||||
|
"visible": visible,
|
||||||
|
"presented": presented,
|
||||||
|
"barSide": _barSide(source.barSide, _barSide(base.barSide, defaultSide)),
|
||||||
|
"bodyRect": bodyRect,
|
||||||
|
"animationOffset": animationOffset,
|
||||||
|
"scale": Math.max(0, _number(source.scale, _number(base.scale, 1))),
|
||||||
|
"opacity": opacity,
|
||||||
|
"omitStartConnector": _bool(source.omitStartConnector, _bool(base.omitStartConnector, false)),
|
||||||
|
"omitEndConnector": _bool(source.omitEndConnector, _bool(base.omitEndConnector, false)),
|
||||||
|
"dockRetractSide": String(source.dockRetractSide !== undefined ? source.dockRetractSide : (base.dockRetractSide || "")),
|
||||||
|
"revision": Math.max(0, Math.floor(_number(source.revision, _number(base.revision, 0))))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function empty(kind, screenName) {
|
||||||
|
return normalize({
|
||||||
|
"kind": kind,
|
||||||
|
"screenName": screenName || "",
|
||||||
|
"phase": "hidden",
|
||||||
|
"visible": false,
|
||||||
|
"presented": false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function withRevision(descriptor, revision) {
|
||||||
|
var next = normalize(descriptor);
|
||||||
|
next.revision = Math.max(0, Math.floor(_number(revision, next.revision)));
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withAnimationOffset(descriptor, x, y) {
|
||||||
|
var next = normalize(descriptor);
|
||||||
|
next.animationOffset = {
|
||||||
|
"x": x === undefined ? next.animationOffset.x : _number(x, next.animationOffset.x),
|
||||||
|
"y": y === undefined ? next.animationOffset.y : _number(y, next.animationOffset.y)
|
||||||
|
};
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withBodyRect(descriptor, x, y, width, height) {
|
||||||
|
var next = normalize(descriptor);
|
||||||
|
next.bodyRect = {
|
||||||
|
"x": x === undefined ? next.bodyRect.x : _number(x, next.bodyRect.x),
|
||||||
|
"y": y === undefined ? next.bodyRect.y : _number(y, next.bodyRect.y),
|
||||||
|
"width": width === undefined ? next.bodyRect.width : Math.max(0, _number(width, next.bodyRect.width)),
|
||||||
|
"height": height === undefined ? next.bodyRect.height : Math.max(0, _number(height, next.bodyRect.height))
|
||||||
|
};
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function same(a, b, threshold) {
|
||||||
|
if (!a || !b)
|
||||||
|
return false;
|
||||||
|
var epsilon = threshold === undefined ? 0.5 : Math.max(0, Number(threshold));
|
||||||
|
return a.ownerId === b.ownerId
|
||||||
|
&& a.kind === b.kind
|
||||||
|
&& a.screenName === b.screenName
|
||||||
|
&& a.phase === b.phase
|
||||||
|
&& a.visible === b.visible
|
||||||
|
&& a.presented === b.presented
|
||||||
|
&& a.barSide === b.barSide
|
||||||
|
&& Math.abs(a.bodyRect.x - b.bodyRect.x) < epsilon
|
||||||
|
&& Math.abs(a.bodyRect.y - b.bodyRect.y) < epsilon
|
||||||
|
&& Math.abs(a.bodyRect.width - b.bodyRect.width) < epsilon
|
||||||
|
&& Math.abs(a.bodyRect.height - b.bodyRect.height) < epsilon
|
||||||
|
&& Math.abs(a.animationOffset.x - b.animationOffset.x) < epsilon
|
||||||
|
&& Math.abs(a.animationOffset.y - b.animationOffset.y) < epsilon
|
||||||
|
&& Math.abs(a.scale - b.scale) < 0.0001
|
||||||
|
&& Math.abs(a.opacity - b.opacity) < 0.0001
|
||||||
|
&& a.omitStartConnector === b.omitStartConnector
|
||||||
|
&& a.omitEndConnector === b.omitEndConnector
|
||||||
|
&& a.dockRetractSide === b.dockRetractSide;
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
.pragma library
|
||||||
|
|
||||||
|
function _number(value, fallback) {
|
||||||
|
var n = Number(value);
|
||||||
|
return isNaN(n) ? fallback : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function snap(value, dpr) {
|
||||||
|
var scale = dpr || 1;
|
||||||
|
return Math.round(_number(value, 0) * scale) / scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHorizontal(side) {
|
||||||
|
return side === "top" || side === "bottom";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVertical(side) {
|
||||||
|
return side === "left" || side === "right";
|
||||||
|
}
|
||||||
|
|
||||||
|
function bodyRect(descriptor, dpr) {
|
||||||
|
var source = descriptor && descriptor.bodyRect ? descriptor.bodyRect : descriptor || {};
|
||||||
|
return {
|
||||||
|
"x": snap(source.x !== undefined ? source.x : source.bodyX, dpr),
|
||||||
|
"y": snap(source.y !== undefined ? source.y : source.bodyY, dpr),
|
||||||
|
"width": Math.max(0, snap(source.width !== undefined ? source.width : source.bodyW, dpr)),
|
||||||
|
"height": Math.max(0, snap(source.height !== undefined ? source.height : source.bodyH, dpr))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function animatedBodyRect(descriptor, dpr) {
|
||||||
|
var rect = bodyRect(descriptor, dpr);
|
||||||
|
var offset = descriptor && descriptor.animationOffset ? descriptor.animationOffset : descriptor || {};
|
||||||
|
var side = descriptor && descriptor.barSide ? descriptor.barSide : "bottom";
|
||||||
|
var dx = isVertical(side) ? Math.max(-rect.width, Math.min(_number(offset.x !== undefined ? offset.x : offset.animX, 0), rect.width)) : 0;
|
||||||
|
var dy = isHorizontal(side) ? Math.max(-rect.height, Math.min(_number(offset.y !== undefined ? offset.y : offset.animY, 0), rect.height)) : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
"x": snap(rect.x + (side === "right" ? dx : 0), dpr),
|
||||||
|
"y": snap(rect.y + (side === "bottom" ? dy : 0), dpr),
|
||||||
|
"width": Math.max(0, snap(rect.width - Math.abs(dx), dpr)),
|
||||||
|
"height": Math.max(0, snap(rect.height - Math.abs(dy), dpr)),
|
||||||
|
"dx": snap(dx, dpr),
|
||||||
|
"dy": snap(dy, dpr)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function translatedBodyRect(descriptor, dpr) {
|
||||||
|
var rect = bodyRect(descriptor, dpr);
|
||||||
|
var offset = descriptor && descriptor.animationOffset ? descriptor.animationOffset : {};
|
||||||
|
return {
|
||||||
|
"x": snap(rect.x + _number(offset.x, 0), dpr),
|
||||||
|
"y": snap(rect.y + _number(offset.y, 0), dpr),
|
||||||
|
"width": rect.width,
|
||||||
|
"height": rect.height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectorRadii(descriptor, rect, connectedRadius, surfaceRadius, dpr, nearIncludesSurface) {
|
||||||
|
var side = descriptor && descriptor.barSide ? descriptor.barSide : "bottom";
|
||||||
|
var horizontal = isHorizontal(side);
|
||||||
|
var extent = horizontal ? rect.height : rect.width;
|
||||||
|
var crossSize = horizontal ? rect.width : rect.height;
|
||||||
|
var nearLimit = nearIncludesSurface ? Math.min(connectedRadius, surfaceRadius, extent, crossSize / 2) : Math.min(connectedRadius, extent, crossSize / 2);
|
||||||
|
var farLimit = Math.min(connectedRadius, surfaceRadius, crossSize / 2);
|
||||||
|
var near = snap(Math.max(0, nearLimit), dpr);
|
||||||
|
var far = snap(Math.max(0, farLimit), dpr);
|
||||||
|
var omitStart = !!(descriptor && descriptor.omitStartConnector);
|
||||||
|
var omitEnd = !!(descriptor && descriptor.omitEndConnector);
|
||||||
|
return {
|
||||||
|
"near": near,
|
||||||
|
"far": far,
|
||||||
|
"start": omitStart ? 0 : near,
|
||||||
|
"end": omitEnd ? 0 : near,
|
||||||
|
"farStart": omitStart ? far : 0,
|
||||||
|
"farEnd": omitEnd ? far : 0,
|
||||||
|
"farExtent": Math.max(omitStart ? far : 0, omitEnd ? far : 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _connectorWidth(side, spacing, radius) {
|
||||||
|
return isVertical(side) ? spacing + radius : radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _connectorHeight(side, spacing, radius) {
|
||||||
|
return isVertical(side) ? radius : spacing + radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectorRect(side, rect, placement, spacing, radius, dpr) {
|
||||||
|
var width = _connectorWidth(side, spacing, radius);
|
||||||
|
var height = _connectorHeight(side, spacing, radius);
|
||||||
|
var seamX = isVertical(side) ? (side === "left" ? rect.x : rect.x + rect.width) : (placement === "left" ? rect.x : rect.x + rect.width);
|
||||||
|
var seamY = side === "top" ? rect.y : (side === "bottom" ? rect.y + rect.height : (placement === "left" ? rect.y : rect.y + rect.height));
|
||||||
|
var x = isVertical(side) ? (side === "left" ? seamX : seamX - width) : (placement === "left" ? seamX - width : seamX);
|
||||||
|
var y = side === "top" ? seamY : (side === "bottom" ? seamY - height : (placement === "left" ? seamY - height : seamY));
|
||||||
|
return {
|
||||||
|
"x": snap(x, dpr),
|
||||||
|
"y": snap(y, dpr),
|
||||||
|
"width": Math.max(0, snap(width, dpr)),
|
||||||
|
"height": Math.max(0, snap(height, dpr))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function farConnectorRect(side, rect, placement, radius, dpr) {
|
||||||
|
var x;
|
||||||
|
var y;
|
||||||
|
if (isHorizontal(side)) {
|
||||||
|
x = placement === "left" ? rect.x : rect.x + rect.width - radius;
|
||||||
|
y = side === "top" ? rect.y + rect.height : rect.y - radius;
|
||||||
|
} else {
|
||||||
|
x = side === "left" ? rect.x + rect.width : rect.x - radius;
|
||||||
|
y = placement === "left" ? rect.y : rect.y + rect.height - radius;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"x": snap(x, dpr),
|
||||||
|
"y": snap(y, dpr),
|
||||||
|
"width": Math.max(0, snap(radius, dpr)),
|
||||||
|
"height": Math.max(0, snap(radius, dpr))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function farBodyCapRect(side, rect, placement, radius, dpr) {
|
||||||
|
var x;
|
||||||
|
var y;
|
||||||
|
if (isHorizontal(side)) {
|
||||||
|
x = placement === "left" ? rect.x : rect.x + rect.width - radius;
|
||||||
|
y = side === "top" ? rect.y + rect.height - radius : rect.y;
|
||||||
|
} else {
|
||||||
|
x = side === "left" ? rect.x + rect.width - radius : rect.x;
|
||||||
|
y = placement === "left" ? rect.y : rect.y + rect.height - radius;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"x": snap(x, dpr),
|
||||||
|
"y": snap(y, dpr),
|
||||||
|
"width": Math.max(0, snap(radius, dpr)),
|
||||||
|
"height": Math.max(0, snap(radius, dpr))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function chromeBounds(rect, side, startRadius, endRadius, farExtent, dpr) {
|
||||||
|
var horizontal = isHorizontal(side);
|
||||||
|
var bodyOffsetX = horizontal ? startRadius : (side === "right" ? farExtent : 0);
|
||||||
|
var bodyOffsetY = horizontal ? (side === "bottom" ? farExtent : 0) : startRadius;
|
||||||
|
return {
|
||||||
|
"x": snap(rect.x - bodyOffsetX, dpr),
|
||||||
|
"y": snap(rect.y - bodyOffsetY, dpr),
|
||||||
|
"width": Math.max(0, snap(horizontal ? rect.width + startRadius + endRadius : rect.width + farExtent, dpr)),
|
||||||
|
"height": Math.max(0, snap(horizontal ? rect.height + farExtent : rect.height + startRadius + endRadius, dpr)),
|
||||||
|
"bodyOffsetX": snap(bodyOffsetX, dpr),
|
||||||
|
"bodyOffsetY": snap(bodyOffsetY, dpr)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillBounds(rect, side, seamOverlap, dpr) {
|
||||||
|
var overlapX = isHorizontal(side) ? seamOverlap : 0;
|
||||||
|
var overlapY = isVertical(side) ? seamOverlap : 0;
|
||||||
|
return {
|
||||||
|
"x": snap(rect.x - overlapX, dpr),
|
||||||
|
"y": snap(rect.y - overlapY, dpr),
|
||||||
|
"width": Math.max(0, snap(rect.width + overlapX * 2, dpr)),
|
||||||
|
"height": Math.max(0, snap(rect.height + overlapY * 2, dpr))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function clipEnvelope(rect, side, radii, seamOverlap, dpr) {
|
||||||
|
var fill = fillBounds(rect, side, seamOverlap, dpr);
|
||||||
|
var chrome = chromeBounds(fill, side, radii.start, radii.end, radii.farExtent, dpr);
|
||||||
|
return {
|
||||||
|
"x": chrome.x,
|
||||||
|
"y": chrome.y,
|
||||||
|
"width": chrome.width,
|
||||||
|
"height": chrome.height,
|
||||||
|
"bodyX": snap(fill.x - chrome.x, dpr),
|
||||||
|
"bodyY": snap(fill.y - chrome.y, dpr),
|
||||||
|
"bodyWidth": fill.width,
|
||||||
|
"bodyHeight": fill.height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function blurRegions(descriptor, rect, radii, dpr) {
|
||||||
|
var side = descriptor.barSide;
|
||||||
|
var regions = [bodyRect(rect, dpr)];
|
||||||
|
if (radii.start > 0)
|
||||||
|
regions.push(connectorRect(side, rect, "left", 0, radii.start, dpr));
|
||||||
|
if (radii.end > 0)
|
||||||
|
regions.push(connectorRect(side, rect, "right", 0, radii.end, dpr));
|
||||||
|
if (radii.farStart > 0) {
|
||||||
|
regions.push(farConnectorRect(side, rect, "left", radii.farStart, dpr));
|
||||||
|
regions.push(farBodyCapRect(side, rect, "left", radii.farStart, dpr));
|
||||||
|
}
|
||||||
|
if (radii.farEnd > 0) {
|
||||||
|
regions.push(farConnectorRect(side, rect, "right", radii.farEnd, dpr));
|
||||||
|
regions.push(farBodyCapRect(side, rect, "right", radii.farEnd, dpr));
|
||||||
|
}
|
||||||
|
return regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unionBounds(rects, padding, dpr) {
|
||||||
|
var minX = Infinity;
|
||||||
|
var minY = Infinity;
|
||||||
|
var maxX = -Infinity;
|
||||||
|
var maxY = -Infinity;
|
||||||
|
for (var i = 0; i < rects.length; i++) {
|
||||||
|
var rect = rects[i];
|
||||||
|
if (!rect || rect.width <= 0 || rect.height <= 0)
|
||||||
|
continue;
|
||||||
|
minX = Math.min(minX, rect.x);
|
||||||
|
minY = Math.min(minY, rect.y);
|
||||||
|
maxX = Math.max(maxX, rect.x + rect.width);
|
||||||
|
maxY = Math.max(maxY, rect.y + rect.height);
|
||||||
|
}
|
||||||
|
if (minX === Infinity)
|
||||||
|
return {"x": 0, "y": 0, "width": 0, "height": 0};
|
||||||
|
var pad = Math.max(0, _number(padding, 0));
|
||||||
|
return {
|
||||||
|
"x": snap(minX - pad, dpr),
|
||||||
|
"y": snap(minY - pad, dpr),
|
||||||
|
"width": Math.max(0, snap(maxX - minX + pad * 2, dpr)),
|
||||||
|
"height": Math.max(0, snap(maxY - minY + pad * 2, dpr))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowSourceBounds(descriptor, rect, radii, padding, dpr) {
|
||||||
|
return unionBounds(blurRegions(descriptor, rect, radii, dpr), padding, dpr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stableEqual(a, b, dpr) {
|
||||||
|
if (!a || !b)
|
||||||
|
return false;
|
||||||
|
var threshold = 0.5 / (dpr || 1);
|
||||||
|
return Math.abs(a.x - b.x) < threshold && Math.abs(a.y - b.y) < threshold && Math.abs(a.width - b.width) < threshold && Math.abs(a.height - b.height) < threshold;
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string claimPrefix
|
||||||
|
required property var isCurrentOwner
|
||||||
|
required property var hasOwner
|
||||||
|
required property var claimState
|
||||||
|
required property var ensureState
|
||||||
|
required property var releaseState
|
||||||
|
|
||||||
|
property var statePresent: null
|
||||||
|
property var updateAnimationState: null
|
||||||
|
property var updateBodyState: null
|
||||||
|
property var requestDockRetract: null
|
||||||
|
property var releaseDockRetract: null
|
||||||
|
|
||||||
|
property string screenName: ""
|
||||||
|
property bool enabled: false
|
||||||
|
property bool active: false
|
||||||
|
property bool presented: false
|
||||||
|
property bool dockBlocked: false
|
||||||
|
property string dockSide: ""
|
||||||
|
property bool renewTokenOnRecovery: true
|
||||||
|
|
||||||
|
property string claimId: ""
|
||||||
|
property string claimedScreenName: ""
|
||||||
|
property int _claimSerial: 0
|
||||||
|
|
||||||
|
signal recoveryRequested
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
function _nextClaimId() {
|
||||||
|
_claimSerial += 1;
|
||||||
|
return claimPrefix + ":" + (new Date()).getTime() + ":" + _claimSerial + ":" + Math.floor(Math.random() * 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isCurrent(name) {
|
||||||
|
return !!name && !!isCurrentOwner && !!isCurrentOwner(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _hasOwner(name, ownerId) {
|
||||||
|
return !!name && !!ownerId && !!hasOwner && !!hasOwner(name, ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _hasState(name, ownerId) {
|
||||||
|
return !statePresent || !!statePresent(name, ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _shouldRecover() {
|
||||||
|
return active && enabled && _isCurrent(screenName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestRecovery() {
|
||||||
|
if (!_shouldRecover())
|
||||||
|
return false;
|
||||||
|
recoveryRequested();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkOwnershipRecovery() {
|
||||||
|
if (!_shouldRecover())
|
||||||
|
return false;
|
||||||
|
if (claimedScreenName === screenName && _hasOwner(screenName, claimId))
|
||||||
|
return false;
|
||||||
|
recoveryRequested();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStateRecovery() {
|
||||||
|
if (!_shouldRecover())
|
||||||
|
return false;
|
||||||
|
if (claimedScreenName === screenName && _hasOwner(screenName, claimId) && _hasState(screenName, claimId))
|
||||||
|
return false;
|
||||||
|
recoveryRequested();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkRecovery() {
|
||||||
|
return checkStateRecovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginClaim() {
|
||||||
|
if (claimId && releaseDockRetract)
|
||||||
|
releaseDockRetract(claimId);
|
||||||
|
claimId = _nextClaimId();
|
||||||
|
claimedScreenName = "";
|
||||||
|
return claimId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _syncDockRetract() {
|
||||||
|
if (!claimId)
|
||||||
|
return;
|
||||||
|
if (dockBlocked && presented && dockSide && requestDockRetract)
|
||||||
|
requestDockRetract(claimId, screenName, dockSide);
|
||||||
|
else if (releaseDockRetract)
|
||||||
|
releaseDockRetract(claimId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function publish(state, forceClaim) {
|
||||||
|
if (!enabled || !screenName || !state) {
|
||||||
|
release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claimedScreenName && claimedScreenName !== screenName)
|
||||||
|
release();
|
||||||
|
|
||||||
|
const current = _isCurrent(screenName);
|
||||||
|
let claiming = !!forceClaim || !claimId;
|
||||||
|
if (claiming && !current)
|
||||||
|
return false;
|
||||||
|
if (!claimId)
|
||||||
|
beginClaim();
|
||||||
|
|
||||||
|
let published = claiming ? claimState(screenName, state, claimId) : ensureState(screenName, state, claimId);
|
||||||
|
if (!published && !claiming && current) {
|
||||||
|
if (renewTokenOnRecovery) {
|
||||||
|
beginClaim();
|
||||||
|
} else if (releaseDockRetract) {
|
||||||
|
releaseDockRetract(claimId);
|
||||||
|
}
|
||||||
|
published = claimState(screenName, state, claimId);
|
||||||
|
}
|
||||||
|
if (!published)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
claimedScreenName = screenName;
|
||||||
|
_syncDockRetract();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAnim(animX, animY) {
|
||||||
|
if (!enabled || !claimId || !claimedScreenName || !updateAnimationState)
|
||||||
|
return false;
|
||||||
|
if (!_hasOwner(claimedScreenName, claimId)) {
|
||||||
|
requestRecovery();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return updateAnimationState(claimedScreenName, claimId, animX, animY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBody(bodyX, bodyY, bodyW, bodyH) {
|
||||||
|
if (!enabled || !claimId || !claimedScreenName || !updateBodyState)
|
||||||
|
return false;
|
||||||
|
if (!_hasOwner(claimedScreenName, claimId)) {
|
||||||
|
requestRecovery();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return updateBodyState(claimedScreenName, claimId, bodyX, bodyY, bodyW, bodyH);
|
||||||
|
}
|
||||||
|
|
||||||
|
function release() {
|
||||||
|
if (!claimId) {
|
||||||
|
claimedScreenName = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const releasedClaimId = claimId;
|
||||||
|
const releasedScreenName = claimedScreenName;
|
||||||
|
claimId = "";
|
||||||
|
claimedScreenName = "";
|
||||||
|
|
||||||
|
if (releaseDockRetract)
|
||||||
|
releaseDockRetract(releasedClaimId);
|
||||||
|
if (releasedScreenName)
|
||||||
|
return !!releaseState(releasedScreenName, releasedClaimId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: release()
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -19,7 +18,11 @@ Item {
|
|||||||
property real bottomRightRadius: targetRadius
|
property real bottomRightRadius: targetRadius
|
||||||
property color borderColor: "transparent"
|
property color borderColor: "transparent"
|
||||||
property real borderWidth: 0
|
property real borderWidth: 0
|
||||||
property bool useCustomSource: false
|
|
||||||
|
property real sourceX: 0
|
||||||
|
property real sourceY: 0
|
||||||
|
property real sourceWidth: width
|
||||||
|
property real sourceHeight: height
|
||||||
|
|
||||||
property bool shadowEnabled: Theme.elevationEnabled
|
property bool shadowEnabled: Theme.elevationEnabled
|
||||||
property real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0
|
property real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0
|
||||||
@@ -28,36 +31,24 @@ Item {
|
|||||||
property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset)
|
property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset)
|
||||||
property color shadowColor: Theme.elevationShadowColor(level)
|
property color shadowColor: Theme.elevationShadowColor(level)
|
||||||
property real shadowOpacity: 1
|
property real shadowOpacity: 1
|
||||||
property real blurMax: Theme.elevationBlurMax
|
|
||||||
|
|
||||||
property alias sourceRect: sourceRect
|
readonly property var _ambient: Theme.elevationAmbient(level)
|
||||||
|
readonly property real _pad: shadowEnabled ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), _ambient.blurPx + _ambient.spreadPx) + 2) : 0
|
||||||
|
|
||||||
layer.enabled: shadowEnabled
|
ShaderEffect {
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
autoPaddingEnabled: true
|
|
||||||
shadowEnabled: true
|
|
||||||
blurEnabled: false
|
|
||||||
maskEnabled: false
|
|
||||||
shadowBlur: Math.max(0, Math.min(1, root.shadowBlurPx / Math.max(1, root.blurMax)))
|
|
||||||
shadowScale: 1 + (2 * root.shadowSpreadPx) / Math.max(1, Math.min(root.width, root.height))
|
|
||||||
shadowHorizontalOffset: root.shadowOffsetX
|
|
||||||
shadowVerticalOffset: root.shadowOffsetY
|
|
||||||
blurMax: root.blurMax
|
|
||||||
shadowColor: root.shadowColor
|
|
||||||
shadowOpacity: root.shadowOpacity
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: sourceRect
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !root.useCustomSource
|
anchors.margins: -root._pad
|
||||||
topLeftRadius: root.topLeftRadius
|
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/elevation_rect.frag.qsb")
|
||||||
topRightRadius: root.topRightRadius
|
|
||||||
bottomLeftRadius: root.bottomLeftRadius
|
property real widthPx: width
|
||||||
bottomRightRadius: root.bottomRightRadius
|
property real heightPx: height
|
||||||
color: root.targetColor
|
property real borderWidth: root.borderWidth
|
||||||
border.color: root.borderColor
|
property vector4d rectPx: Qt.vector4d(root._pad + root.sourceX, root._pad + root.sourceY, root.sourceWidth, root.sourceHeight)
|
||||||
border.width: root.borderWidth
|
property vector4d cornerRadius: Qt.vector4d(root.topLeftRadius, root.topRightRadius, root.bottomRightRadius, root.bottomLeftRadius)
|
||||||
|
property vector4d fillColor: Qt.vector4d(root.targetColor.r, root.targetColor.g, root.targetColor.b, root.targetColor.a)
|
||||||
|
property vector4d borderColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a)
|
||||||
|
property vector4d shadowColor: Qt.vector4d(root.shadowColor.r, root.shadowColor.g, root.shadowColor.b, root.shadowEnabled ? root.shadowColor.a * root.shadowOpacity : 0)
|
||||||
|
property vector4d shadowParam: Qt.vector4d(Math.max(0, root.shadowBlurPx), root.shadowSpreadPx, root.shadowOffsetX, root.shadowOffsetY)
|
||||||
|
property vector4d ambientParam: Qt.vector4d(root._ambient.blurPx, root._ambient.spreadPx, root.shadowEnabled ? root._ambient.alpha * root.shadowOpacity : 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
// Manages keyboard focus policy for popouts, modals, and Hyprland focus grabs
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
function keyboardFocus(active, customFocus) {
|
||||||
|
if (PopoutManager.screenshotActive)
|
||||||
|
return WlrKeyboardFocus.None;
|
||||||
|
if (customFocus !== null && customFocus !== undefined)
|
||||||
|
return customFocus;
|
||||||
|
if (!active)
|
||||||
|
return WlrKeyboardFocus.None;
|
||||||
|
if (CompositorService.useHyprlandFocusGrab)
|
||||||
|
return WlrKeyboardFocus.OnDemand;
|
||||||
|
return WlrKeyboardFocus.Exclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wantsGrab(active, customFocus) {
|
||||||
|
return CompositorService.useHyprlandFocusGrab && keyboardFocus(active, customFocus) === WlrKeyboardFocus.OnDemand;
|
||||||
|
}
|
||||||
|
|
||||||
|
property list<var> barWindows: []
|
||||||
|
|
||||||
|
function registerBarWindow(window) {
|
||||||
|
if (!window || barWindows.indexOf(window) !== -1)
|
||||||
|
return;
|
||||||
|
barWindows = barWindows.concat([window]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterBarWindow(window) {
|
||||||
|
const idx = barWindows.indexOf(window);
|
||||||
|
if (idx === -1)
|
||||||
|
return;
|
||||||
|
const next = barWindows.slice();
|
||||||
|
next.splice(idx, 1);
|
||||||
|
barWindows = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,11 @@ Singleton {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCurrentModal(modal, screenName) {
|
||||||
|
const name = screenName || modal?.effectiveScreen?.name || "unknown";
|
||||||
|
return currentModalsByScreen[name] === modal;
|
||||||
|
}
|
||||||
|
|
||||||
function closeModal(modal) {
|
function closeModal(modal) {
|
||||||
const screenName = modal.effectiveScreen?.name ?? "unknown";
|
const screenName = modal.effectiveScreen?.name ?? "unknown";
|
||||||
if (currentModalsByScreen[screenName] === modal) {
|
if (currentModalsByScreen[screenName] === modal) {
|
||||||
|
|||||||
@@ -98,6 +98,11 @@ Singleton {
|
|||||||
return currentPopoutsByScreen[screen.name] || null;
|
return currentPopoutsByScreen[screen.name] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCurrentPopout(popout, screenName) {
|
||||||
|
const name = screenName || popout?.screen?.name || "";
|
||||||
|
return !!name && currentPopoutsByScreen[name] === popout;
|
||||||
|
}
|
||||||
|
|
||||||
function requestPopout(popout, tabIndex, triggerSource) {
|
function requestPopout(popout, tabIndex, triggerSource) {
|
||||||
if (!popout || !popout.screen)
|
if (!popout || !popout.screen)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ Singleton {
|
|||||||
property int mangoLayoutGapsOverride: -1
|
property int mangoLayoutGapsOverride: -1
|
||||||
property int mangoLayoutRadiusOverride: -1
|
property int mangoLayoutRadiusOverride: -1
|
||||||
property int mangoLayoutBorderSize: -1
|
property int mangoLayoutBorderSize: -1
|
||||||
|
property bool mangoTrackpadNaturalScrolling: true
|
||||||
|
|
||||||
property int firstDayOfWeek: -1
|
property int firstDayOfWeek: -1
|
||||||
property bool showWeekNumber: false
|
property bool showWeekNumber: false
|
||||||
@@ -488,9 +489,6 @@ Singleton {
|
|||||||
"hideOnTouch": false,
|
"hideOnTouch": false,
|
||||||
"inactiveTimeout": 0
|
"inactiveTimeout": 0
|
||||||
},
|
},
|
||||||
"dwl": {
|
|
||||||
"cursorHideTimeout": 0
|
|
||||||
},
|
|
||||||
"mango": {
|
"mango": {
|
||||||
"cursorHideTimeout": 0
|
"cursorHideTimeout": 0
|
||||||
}
|
}
|
||||||
@@ -517,6 +515,8 @@ Singleton {
|
|||||||
property bool notepadUseMonospace: true
|
property bool notepadUseMonospace: true
|
||||||
property string notepadFontFamily: ""
|
property string notepadFontFamily: ""
|
||||||
property real notepadFontSize: 14
|
property real notepadFontSize: 14
|
||||||
|
property real notificationSummaryFontSize: Spec.SPEC.notificationSummaryFontSize.def
|
||||||
|
property real notificationBodyFontSize: Spec.SPEC.notificationBodyFontSize.def
|
||||||
property bool notepadShowLineNumbers: false
|
property bool notepadShowLineNumbers: false
|
||||||
property real notepadTransparencyOverride: -1
|
property real notepadTransparencyOverride: -1
|
||||||
property real notepadLastCustomTransparency: 0.7
|
property real notepadLastCustomTransparency: 0.7
|
||||||
@@ -697,6 +697,7 @@ Singleton {
|
|||||||
property int notificationTimeoutNormal: 5000
|
property int notificationTimeoutNormal: 5000
|
||||||
property int notificationTimeoutCritical: 0
|
property int notificationTimeoutCritical: 0
|
||||||
property bool notificationCompactMode: false
|
property bool notificationCompactMode: false
|
||||||
|
property bool notificationShowTimeoutBar: false
|
||||||
property bool notificationDedupeEnabled: true
|
property bool notificationDedupeEnabled: true
|
||||||
property int notificationPopupPosition: SettingsData.Position.Top
|
property int notificationPopupPosition: SettingsData.Position.Top
|
||||||
property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short
|
property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short
|
||||||
@@ -1223,8 +1224,6 @@ Singleton {
|
|||||||
NiriService.generateNiriLayoutConfig();
|
NiriService.generateNiriLayoutConfig();
|
||||||
if (CompositorService.isHyprland && typeof HyprlandService !== "undefined")
|
if (CompositorService.isHyprland && typeof HyprlandService !== "undefined")
|
||||||
HyprlandService.generateLayoutConfig();
|
HyprlandService.generateLayoutConfig();
|
||||||
if (CompositorService.isDwl && typeof DwlService !== "undefined")
|
|
||||||
DwlService.generateLayoutConfig();
|
|
||||||
if (CompositorService.isMango && typeof MangoService !== "undefined")
|
if (CompositorService.isMango && typeof MangoService !== "undefined")
|
||||||
MangoService.generateLayoutConfig();
|
MangoService.generateLayoutConfig();
|
||||||
}
|
}
|
||||||
@@ -2240,6 +2239,9 @@ Singleton {
|
|||||||
|
|
||||||
function getFilteredScreens(componentId) {
|
function getFilteredScreens(componentId) {
|
||||||
var prefs = screenPreferences && screenPreferences[componentId] || ["all"];
|
var prefs = screenPreferences && screenPreferences[componentId] || ["all"];
|
||||||
|
if (componentId === "wallpaper" && Array.isArray(prefs) && prefs.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
if (!prefs || prefs.length === 0 || prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")) {
|
if (!prefs || prefs.length === 0 || prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")) {
|
||||||
return Quickshell.screens;
|
return Quickshell.screens;
|
||||||
}
|
}
|
||||||
@@ -2447,10 +2449,6 @@ Singleton {
|
|||||||
HyprlandService.generateCursorConfig();
|
HyprlandService.generateCursorConfig();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (CompositorService.isDwl && typeof DwlService !== "undefined") {
|
|
||||||
DwlService.generateCursorConfig();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (CompositorService.isMango && typeof MangoService !== "undefined") {
|
if (CompositorService.isMango && typeof MangoService !== "undefined") {
|
||||||
MangoService.generateCursorConfig();
|
MangoService.generateCursorConfig();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string selectedBarId: "default"
|
||||||
|
|
||||||
|
function normalizeSelectedBar() {
|
||||||
|
if (SettingsData.getBarConfig(selectedBarId))
|
||||||
|
return;
|
||||||
|
selectedBarId = SettingsData.barConfigs[0]?.id ?? "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
|
||||||
|
function onBarConfigsChanged() {
|
||||||
|
root.normalizeSelectedBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -911,6 +911,16 @@ Singleton {
|
|||||||
}
|
}
|
||||||
return Qt.rgba(r, g, b, alpha);
|
return Qt.rgba(r, g, b, alpha);
|
||||||
}
|
}
|
||||||
|
function elevationAmbient(level) {
|
||||||
|
const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0;
|
||||||
|
const alpha = ((level && level.alpha !== undefined) ? level.alpha : 0.3) * 0.5;
|
||||||
|
return {
|
||||||
|
blurPx: blur * 1.75,
|
||||||
|
spreadPx: 1,
|
||||||
|
alpha: alpha
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function elevationTintOpacity(level) {
|
function elevationTintOpacity(level) {
|
||||||
if (!level)
|
if (!level)
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function launchGreeterAutoLoginSyncTerminalFallback(details) {
|
function launchGreeterAutoLoginSyncTerminalFallback(details) {
|
||||||
ToastService.showWarning(I18n.tr("Opening terminal to update greetd"), I18n.tr("DMS needs administrator access. The terminal closes automatically when done.") + (details ? "\n\n" + details : ""), "dms greeter sync --autologin-only", "greeter-autologin-sync");
|
ToastService.showWarning(I18n.tr("Opening terminal to update greetd"), I18n.tr("DMS needs administrator access. The terminal closes automatically when done.") + (details ? "\n\n" + details : ""), "dms greeter sync --autologin", "greeter-autologin-sync");
|
||||||
greeterAutoLoginSyncTerminalFallbackStderr = "";
|
greeterAutoLoginSyncTerminalFallbackStderr = "";
|
||||||
greeterAutoLoginSyncTerminalFallbackProcess.running = true;
|
greeterAutoLoginSyncTerminalFallbackProcess.running = true;
|
||||||
}
|
}
|
||||||
@@ -530,7 +530,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property var greeterAutoLoginSyncProcess: Process {
|
property var greeterAutoLoginSyncProcess: Process {
|
||||||
command: ["dms", "greeter", "sync", "--yes", "--autologin-only"]
|
command: ["dms", "greeter", "sync", "--yes", "--autologin"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
@@ -570,7 +570,7 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
const enabling = root.settingsRoot && root.settingsRoot.greeterAutoLogin;
|
const enabling = root.settingsRoot && root.settingsRoot.greeterAutoLogin;
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
ToastService.showWarning(enabling ? I18n.tr("Applying auto-login on startup…") : I18n.tr("Disabling auto-login on startup…"), "", "dms greeter sync --autologin-only", "greeter-autologin-sync");
|
ToastService.showWarning(enabling ? I18n.tr("Applying auto-login on startup...") : I18n.tr("Disabling auto-login on startup..."), "", "dms greeter sync --autologin", "greeter-autologin-sync");
|
||||||
root.greeterAutoLoginSyncProcess.running = true;
|
root.greeterAutoLoginSyncProcess.running = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -580,7 +580,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property var greeterAutoLoginSyncTerminalFallbackProcess: Process {
|
property var greeterAutoLoginSyncTerminalFallbackProcess: Process {
|
||||||
command: ["dms", "greeter", "sync", "--terminal", "--yes", "--autologin-only"]
|
command: ["dms", "greeter", "sync", "--terminal", "--yes", "--autologin"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stderr: StdioCollector {
|
stderr: StdioCollector {
|
||||||
@@ -592,7 +592,7 @@ Singleton {
|
|||||||
root.greeterAutoLoginSyncSuccessToast("");
|
root.greeterAutoLoginSyncSuccessToast("");
|
||||||
} else {
|
} else {
|
||||||
let details = (root.greeterAutoLoginSyncTerminalFallbackStderr || "").trim();
|
let details = (root.greeterAutoLoginSyncTerminalFallbackStderr || "").trim();
|
||||||
ToastService.showError(I18n.tr("Couldn't open a terminal for the auto-login update.") + " (exit " + exitCode + ")", details, "dms greeter sync --autologin-only", "greeter-autologin-sync");
|
ToastService.showError(I18n.tr("Couldn't open a terminal for the auto-login update.") + " (exit " + exitCode + ")", details, "dms greeter sync --autologin", "greeter-autologin-sync");
|
||||||
}
|
}
|
||||||
root.finishGreeterAutoLoginSync();
|
root.finishGreeterAutoLoginSync();
|
||||||
}
|
}
|
||||||
@@ -645,7 +645,7 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
const err = (root.authApplySudoProbeStderr || "").trim();
|
const err = (root.authApplySudoProbeStderr || "").trim();
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
ToastService.showInfo(I18n.tr("Applying authentication changes…"), "", "", "auth-sync");
|
ToastService.showInfo(I18n.tr("Applying authentication changes..."), "", "", "auth-sync");
|
||||||
root.authApplyProcess.running = true;
|
root.authApplyProcess.running = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ var SPEC = {
|
|||||||
mangoLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" },
|
mangoLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" },
|
||||||
mangoLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
|
mangoLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
|
||||||
mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
|
mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
|
||||||
|
mangoTrackpadNaturalScrolling: { def: true, onChange: "updateCompositorCursor" },
|
||||||
|
|
||||||
firstDayOfWeek: { def: -1 },
|
firstDayOfWeek: { def: -1 },
|
||||||
showWeekNumber: { def: false },
|
showWeekNumber: { def: false },
|
||||||
@@ -237,7 +238,7 @@ var SPEC = {
|
|||||||
qt6ctAvailable: { def: false, persist: false },
|
qt6ctAvailable: { def: false, persist: false },
|
||||||
gtkAvailable: { def: false, persist: false },
|
gtkAvailable: { def: false, persist: false },
|
||||||
|
|
||||||
cursorSettings: { def: { theme: "System Default", size: 24, niri: { hideWhenTyping: false, hideAfterInactiveMs: 0 }, hyprland: { hideOnKeyPress: false, hideOnTouch: false, inactiveTimeout: 0 }, dwl: { cursorHideTimeout: 0 } }, onChange: "updateCompositorCursor" },
|
cursorSettings: { def: { theme: "System Default", size: 24, niri: { hideWhenTyping: false, hideAfterInactiveMs: 0 }, hyprland: { hideOnKeyPress: false, hideOnTouch: false, inactiveTimeout: 0 }, dwl: { cursorHideTimeout: 0 }, mango: { cursorHideTimeout: 0 } }, onChange: "updateCompositorCursor" },
|
||||||
availableCursorThemes: { def: ["System Default"], persist: false },
|
availableCursorThemes: { def: ["System Default"], persist: false },
|
||||||
systemDefaultCursorTheme: { def: "", persist: false },
|
systemDefaultCursorTheme: { def: "", persist: false },
|
||||||
|
|
||||||
@@ -259,6 +260,8 @@ var SPEC = {
|
|||||||
notepadUseMonospace: { def: true },
|
notepadUseMonospace: { def: true },
|
||||||
notepadFontFamily: { def: "" },
|
notepadFontFamily: { def: "" },
|
||||||
notepadFontSize: { def: 14 },
|
notepadFontSize: { def: 14 },
|
||||||
|
notificationSummaryFontSize: { def: 0 },
|
||||||
|
notificationBodyFontSize: { def: 0 },
|
||||||
notepadShowLineNumbers: { def: false },
|
notepadShowLineNumbers: { def: false },
|
||||||
notepadTransparencyOverride: { def: -1 },
|
notepadTransparencyOverride: { def: -1 },
|
||||||
notepadLastCustomTransparency: { def: 0.7 },
|
notepadLastCustomTransparency: { def: 0.7 },
|
||||||
@@ -405,6 +408,7 @@ var SPEC = {
|
|||||||
notificationTimeoutNormal: { def: 5000 },
|
notificationTimeoutNormal: { def: 5000 },
|
||||||
notificationTimeoutCritical: { def: 0 },
|
notificationTimeoutCritical: { def: 0 },
|
||||||
notificationCompactMode: { def: false },
|
notificationCompactMode: { def: false },
|
||||||
|
notificationShowTimeoutBar: { def: false },
|
||||||
notificationDedupeEnabled: { def: true },
|
notificationDedupeEnabled: { def: true },
|
||||||
notificationPopupPosition: { def: 0 },
|
notificationPopupPosition: { def: 0 },
|
||||||
notificationAnimationSpeed: { def: 1 },
|
notificationAnimationSpeed: { def: 1 },
|
||||||
@@ -593,6 +597,7 @@ function getValidKeys() {
|
|||||||
|
|
||||||
function set(root, key, value, saveFn, hooks) {
|
function set(root, key, value, saveFn, hooks) {
|
||||||
if (!(key in SPEC)) return;
|
if (!(key in SPEC)) return;
|
||||||
|
if (value === undefined || value === null) value = SPEC[key].def;
|
||||||
root[key] = value;
|
root[key] = value;
|
||||||
var hookName = SPEC[key].onChange;
|
var hookName = SPEC[key].onChange;
|
||||||
if (hookName && hooks && hooks[hookName]) {
|
if (hookName && hooks && hooks[hookName]) {
|
||||||
|
|||||||
+20
-2
@@ -328,6 +328,16 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property bool hadRealScreen: true
|
property bool hadRealScreen: true
|
||||||
|
property var previousRealScreenNames: []
|
||||||
|
|
||||||
|
function _getRealScreenNames() {
|
||||||
|
const names = [];
|
||||||
|
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||||
|
if (Quickshell.screens[i].name.length > 0)
|
||||||
|
names.push(Quickshell.screens[i].name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
function _hasRealScreen() {
|
function _hasRealScreen() {
|
||||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||||
@@ -353,14 +363,20 @@ Item {
|
|||||||
target: Quickshell
|
target: Quickshell
|
||||||
function onScreensChanged() {
|
function onScreensChanged() {
|
||||||
const hasReal = root._hasRealScreen();
|
const hasReal = root._hasRealScreen();
|
||||||
|
const currentNames = root._getRealScreenNames();
|
||||||
log.info("Screens changed:", Quickshell.screens.length,
|
log.info("Screens changed:", Quickshell.screens.length,
|
||||||
Quickshell.screens.map(s => "'" + s.name + "'").join(","),
|
Quickshell.screens.map(s => "'" + s.name + "'").join(","),
|
||||||
"hasReal:", hasReal, "hadReal:", root.hadRealScreen);
|
"hasReal:", hasReal, "hadReal:", root.hadRealScreen);
|
||||||
if (!root.hadRealScreen && hasReal) {
|
const fullReconnect = !root.hadRealScreen && hasReal;
|
||||||
log.info("Real screen reappeared after placeholder state, triggering surface recovery");
|
const partialReconnect = root.previousRealScreenNames.length > 0
|
||||||
|
&& currentNames.some(name => !root.previousRealScreenNames.includes(name));
|
||||||
|
if (fullReconnect || partialReconnect) {
|
||||||
|
log.info("Screen reconnect detected, triggering surface recovery",
|
||||||
|
"full:", fullReconnect, "partial:", partialReconnect);
|
||||||
root.triggerSurfaceRecovery("screen-reconnect");
|
root.triggerSurfaceRecovery("screen-reconnect");
|
||||||
}
|
}
|
||||||
root.hadRealScreen = hasReal;
|
root.hadRealScreen = hasReal;
|
||||||
|
root.previousRealScreenNames = currentNames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1124,6 +1140,7 @@ Item {
|
|||||||
id: powerMenuModal
|
id: powerMenuModal
|
||||||
|
|
||||||
onPowerActionRequested: (action, title, message) => {
|
onPowerActionRequested: (action, title, message) => {
|
||||||
|
PopoutService.closeControlCenter();
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "logout":
|
case "logout":
|
||||||
SessionService.logout();
|
SessionService.logout();
|
||||||
@@ -1144,6 +1161,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLockRequested: {
|
onLockRequested: {
|
||||||
|
PopoutService.closeControlCenter();
|
||||||
lock.activate();
|
lock.activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -337,9 +337,6 @@ Item {
|
|||||||
const focusedWs = I3.workspaces.values.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces.values.find(ws => ws.focused === true);
|
||||||
return focusedWs?.monitor?.name || "";
|
return focusedWs?.monitor?.name || "";
|
||||||
}
|
}
|
||||||
if (CompositorService.isDwl && DwlService.activeOutput) {
|
|
||||||
return DwlService.activeOutput;
|
|
||||||
}
|
|
||||||
if (CompositorService.isMango && MangoService.activeOutput) {
|
if (CompositorService.isMango && MangoService.activeOutput) {
|
||||||
return MangoService.activeOutput;
|
return MangoService.activeOutput;
|
||||||
}
|
}
|
||||||
@@ -947,7 +944,7 @@ Item {
|
|||||||
|
|
||||||
function tabs(): string {
|
function tabs(): string {
|
||||||
if (!PopoutService.settingsModal)
|
if (!PopoutService.settingsModal)
|
||||||
return "wallpaper\ntheme\ntypography\ntime_weather\nsounds\ndankbar\ndankbar_settings\ndankbar_widgets\nworkspaces\nmedia_player\nnotifications\nosd\nrunning_apps\nupdater\ndock\nlauncher\nkeybinds\ndisplays\nnetwork\nprinters\nlock_screen\npower_sleep\nplugins\nabout";
|
return "wallpaper\ntheme\ntypography\ntime_weather\nsounds\ndankbar\ndankbar_settings\ndankbar_appearance\ndankbar_widgets\nframe\nworkspaces\ncompositor\nmedia_player\nnotifications\nosd\nrunning_apps\nupdater\ndock\nlauncher\nkeybinds\ndisplays\nnetwork\nprinters\nlock_screen\npower_sleep\nplugins\nabout";
|
||||||
var modal = PopoutService.settingsModal;
|
var modal = PopoutService.settingsModal;
|
||||||
var ids = [];
|
var ids = [];
|
||||||
var structure = modal.sidebar?.categoryStructure ?? [];
|
var structure = modal.sidebar?.categoryStructure ?? [];
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Hyprland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -11,11 +10,6 @@ DankModal {
|
|||||||
|
|
||||||
layerNamespace: "dms:bluetooth-pairing"
|
layerNamespace: "dms:bluetooth-pairing"
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
|
||||||
windows: [root.contentWindow]
|
|
||||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
property string deviceName: ""
|
property string deviceName: ""
|
||||||
property string deviceAddress: ""
|
property string deviceAddress: ""
|
||||||
property string requestType: ""
|
property string requestType: ""
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No recent clipboard entries found") : I18n.tr("Connecting to clipboard service…")
|
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
|
||||||
@@ -149,8 +149,8 @@ Item {
|
|||||||
listView: clipboardListView
|
listView: clipboardListView
|
||||||
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
||||||
onDeleteRequested: clipboardContent.modal.deleteEntry(modelData)
|
onDeleteRequested: clipboardContent.modal.deleteEntry(modelData)
|
||||||
onPinRequested: clipboardContent.modal.pinEntry(modelData)
|
onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry)
|
||||||
onUnpinRequested: clipboardContent.modal.unpinEntry(modelData)
|
onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry)
|
||||||
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +202,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No saved clipboard entries") : I18n.tr("Connecting to clipboard service…")
|
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
|
||||||
@@ -223,8 +223,8 @@ Item {
|
|||||||
listView: savedListView
|
listView: savedListView
|
||||||
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
||||||
onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData)
|
onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData)
|
||||||
onPinRequested: clipboardContent.modal.pinEntry(modelData)
|
onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry)
|
||||||
onUnpinRequested: clipboardContent.modal.unpinEntry(modelData)
|
onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry)
|
||||||
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ Rectangle {
|
|||||||
|
|
||||||
signal copyRequested
|
signal copyRequested
|
||||||
signal deleteRequested
|
signal deleteRequested
|
||||||
signal pinRequested
|
signal pinRequested(var targetEntry)
|
||||||
signal unpinRequested
|
signal unpinRequested(var targetEntry)
|
||||||
signal editRequested
|
signal editRequested
|
||||||
|
|
||||||
readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
|
readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
|
||||||
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
|
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
|
||||||
readonly property bool hasPinnedDuplicate: !entry.pinned && ClipboardService.hashedPinnedEntry(entry.hash)
|
readonly property var pinnedDuplicateEntry: !entry.pinned ? ClipboardService.getPinnedEntryByHash(entry.hash) : null
|
||||||
|
readonly property bool effectivePinned: entry.pinned || pinnedDuplicateEntry !== null
|
||||||
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
@@ -66,9 +67,19 @@ Rectangle {
|
|||||||
DankActionButton {
|
DankActionButton {
|
||||||
iconName: "push_pin"
|
iconName: "push_pin"
|
||||||
iconSize: Theme.iconSize - 6
|
iconSize: Theme.iconSize - 6
|
||||||
iconColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primary : Theme.surfaceText
|
iconColor: effectivePinned ? Theme.primary : Theme.surfaceText
|
||||||
backgroundColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primarySelected : "transparent"
|
backgroundColor: effectivePinned ? Theme.primarySelected : "transparent"
|
||||||
onClicked: entry.pinned ? unpinRequested() : pinRequested()
|
onClicked: {
|
||||||
|
if (entry.pinned) {
|
||||||
|
unpinRequested(entry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pinnedDuplicateEntry) {
|
||||||
|
unpinRequested(pinnedDuplicateEntry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pinRequested(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Item {
|
|||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
iconColor: header.activeTab === "saved" ? Theme.primary : Theme.surfaceText
|
iconColor: header.activeTab === "saved" ? Theme.primary : Theme.surfaceText
|
||||||
backgroundColor: header.activeTab === "saved" ? Theme.primarySelected : "transparent"
|
backgroundColor: header.activeTab === "saved" ? Theme.primarySelected : "transparent"
|
||||||
visible: header.pinnedCount > 0
|
visible: header.pinnedCount > 0 || header.activeTab === "saved"
|
||||||
tooltipText: header.activeTab === "saved" ? I18n.tr("Recent") : I18n.tr("Saved")
|
tooltipText: header.activeTab === "saved" ? I18n.tr("Recent") : I18n.tr("Saved")
|
||||||
onClicked: tabChanged(header.activeTab === "saved" ? "recents" : "saved")
|
onClicked: tabChanged(header.activeTab === "saved" ? "recents" : "saved")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,18 @@ FocusScope {
|
|||||||
signal instantCloseRequested
|
signal instantCloseRequested
|
||||||
|
|
||||||
onActiveTabChanged: {
|
onActiveTabChanged: {
|
||||||
|
if (activeTab === "saved" && pinnedCount === 0) {
|
||||||
|
activeTab = "recents";
|
||||||
|
return;
|
||||||
|
}
|
||||||
ClipboardService.selectedIndex = 0;
|
ClipboardService.selectedIndex = 0;
|
||||||
ClipboardService.keyboardNavigationActive = false;
|
ClipboardService.keyboardNavigationActive = false;
|
||||||
}
|
}
|
||||||
|
onPinnedCountChanged: {
|
||||||
|
if (activeTab === "saved" && pinnedCount === 0) {
|
||||||
|
activeTab = "recents";
|
||||||
|
}
|
||||||
|
}
|
||||||
onSearchTextChanged: ClipboardService.searchText = searchText
|
onSearchTextChanged: ClipboardService.searchText = searchText
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Hyprland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Clipboard
|
import qs.Modals.Clipboard
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
@@ -12,11 +11,6 @@ DankModal {
|
|||||||
|
|
||||||
layerNamespace: "dms:clipboard"
|
layerNamespace: "dms:clipboard"
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
|
||||||
windows: [clipboardHistoryModal.contentWindow]
|
|
||||||
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (shouldBeVisible) {
|
if (shouldBeVisible) {
|
||||||
hide();
|
hide();
|
||||||
@@ -64,6 +58,7 @@ DankModal {
|
|||||||
readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable
|
readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable
|
||||||
|
|
||||||
visible: false
|
visible: false
|
||||||
|
keepContentLoaded: true
|
||||||
modalWidth: ClipboardConstants.modalWidth
|
modalWidth: ClipboardConstants.modalWidth
|
||||||
modalHeight: ClipboardConstants.modalHeight
|
modalHeight: ClipboardConstants.modalHeight
|
||||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
|
|||||||
@@ -59,8 +59,13 @@ QtObject {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selectedEntry = entries[ClipboardService.selectedIndex];
|
const selectedEntry = entries[ClipboardService.selectedIndex];
|
||||||
if (modal.activeTab === "saved") {
|
if (selectedEntry.pinned) {
|
||||||
modal.unpinEntry(selectedEntry);
|
modal.unpinEntry(selectedEntry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pinnedDuplicate = ClipboardService.getPinnedEntryByHash(selectedEntry.hash);
|
||||||
|
if (pinnedDuplicate) {
|
||||||
|
modal.unpinEntry(pinnedDuplicate);
|
||||||
} else {
|
} else {
|
||||||
modal.pinEntry(selectedEntry);
|
modal.pinEntry(selectedEntry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
@@ -52,8 +53,13 @@ Item {
|
|||||||
focus: true
|
focus: true
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hyprland OnDemand grab delivers keyboard focus to the modal content surface.
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
windows: root.contentWindow ? [root.contentWindow] : []
|
||||||
|
active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus)
|
||||||
|
}
|
||||||
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
|
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
|
||||||
readonly property var clickCatcher: impl.item ? impl.item.clickCatcher : null
|
|
||||||
readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null
|
readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null
|
||||||
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920
|
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920
|
||||||
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080
|
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080
|
||||||
@@ -96,8 +102,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer Loader source-component swap until impl is fully closed; avoids
|
|
||||||
// tearing down a modal mid-animation when frame mode is toggled.
|
|
||||||
function _maybeResolveBackend() {
|
function _maybeResolveBackend() {
|
||||||
if (_resolvedBackend === _desiredBackend)
|
if (_resolvedBackend === _desiredBackend)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ Item {
|
|||||||
property bool closeOnBackgroundClick: true
|
property bool closeOnBackgroundClick: true
|
||||||
property string animationType: "scale"
|
property string animationType: "scale"
|
||||||
|
|
||||||
// Opposite side from the launcher by default; subclasses may override
|
|
||||||
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
|
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
|
||||||
|
|
||||||
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
|
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
|
||||||
@@ -87,16 +86,13 @@ Item {
|
|||||||
property real frozenMotionOffsetX: 0
|
property real frozenMotionOffsetX: 0
|
||||||
property real frozenMotionOffsetY: 0
|
property real frozenMotionOffsetY: 0
|
||||||
readonly property alias contentWindow: contentWindow
|
readonly property alias contentWindow: contentWindow
|
||||||
readonly property alias clickCatcher: clickCatcher
|
|
||||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||||
readonly property bool useBackground: false
|
readonly property bool useBackground: false
|
||||||
readonly property bool useSingleWindow: CompositorService.isHyprland
|
|
||||||
|
|
||||||
signal opened
|
signal opened
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
signal backgroundClicked
|
signal backgroundClicked
|
||||||
|
|
||||||
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
|
|
||||||
Timer {
|
Timer {
|
||||||
id: _syncTimer
|
id: _syncTimer
|
||||||
interval: 0
|
interval: 0
|
||||||
@@ -105,52 +101,65 @@ Item {
|
|||||||
|
|
||||||
property bool animationsEnabled: true
|
property bool animationsEnabled: true
|
||||||
|
|
||||||
property string _chromeClaimId: ""
|
|
||||||
property bool _fullSyncPending: false
|
property bool _fullSyncPending: false
|
||||||
|
|
||||||
function _nextChromeClaimId() {
|
|
||||||
return layerNamespace + ":modal:" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _currentScreenName() {
|
function _currentScreenName() {
|
||||||
return effectiveScreen ? effectiveScreen.name : "";
|
return effectiveScreen ? effectiveScreen.name : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function _publishModalChromeState(isClaim) {
|
ConnectedModalChrome {
|
||||||
const screenName = _currentScreenName();
|
id: modalChrome
|
||||||
if (!screenName)
|
modalHandle: root.modalHandle
|
||||||
return;
|
claimPrefix: root.layerNamespace + ":modal"
|
||||||
|
surfaceKind: "modal"
|
||||||
|
screenName: root._currentScreenName()
|
||||||
|
enabled: root.frameOwnsConnectedChrome
|
||||||
|
active: root.shouldBeVisible
|
||||||
|
presented: root.shouldBeVisible || contentWindow.visible
|
||||||
|
dockBlocked: root._dockBlocksEmergence
|
||||||
|
dockSide: root.resolvedConnectedBarSide
|
||||||
|
onRecoveryRequested: root._queueFullSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _publishModalChromeState() {
|
||||||
|
const presented = shouldBeVisible || contentWindow.visible;
|
||||||
|
const phase = !presented ? "hidden" : (!shouldBeVisible && contentWindow.visible ? "closing" : (!contentWindow.visible ? "opening" : "open"));
|
||||||
|
const bodyRect = {
|
||||||
|
"x": alignedX,
|
||||||
|
"y": alignedY,
|
||||||
|
"width": alignedWidth,
|
||||||
|
"height": alignedHeight
|
||||||
|
};
|
||||||
|
const animationOffset = {
|
||||||
|
"x": modalContainer ? modalContainer.animX : 0,
|
||||||
|
"y": modalContainer ? modalContainer.animY : 0
|
||||||
|
};
|
||||||
const state = {
|
const state = {
|
||||||
"visible": shouldBeVisible || contentWindow.visible,
|
"kind": "modal",
|
||||||
|
"screenName": root._currentScreenName(),
|
||||||
|
"phase": phase,
|
||||||
|
"visible": presented,
|
||||||
|
"presented": presented,
|
||||||
"barSide": resolvedConnectedBarSide,
|
"barSide": resolvedConnectedBarSide,
|
||||||
|
"bodyRect": bodyRect,
|
||||||
|
"animationOffset": animationOffset,
|
||||||
|
"scale": 1,
|
||||||
|
"opacity": Theme.connectedSurfaceColor.a,
|
||||||
"bodyX": alignedX,
|
"bodyX": alignedX,
|
||||||
"bodyY": alignedY,
|
"bodyY": alignedY,
|
||||||
"bodyW": alignedWidth,
|
"bodyW": alignedWidth,
|
||||||
"bodyH": alignedHeight,
|
"bodyH": alignedHeight,
|
||||||
"animX": modalContainer ? modalContainer.animX : 0,
|
"animX": animationOffset.x,
|
||||||
"animY": modalContainer ? modalContainer.animY : 0,
|
"animY": animationOffset.y,
|
||||||
"omitStartConnector": false,
|
"omitStartConnector": false,
|
||||||
"omitEndConnector": false
|
"omitEndConnector": false,
|
||||||
|
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
|
||||||
};
|
};
|
||||||
if (isClaim)
|
return modalChrome.publish(state);
|
||||||
ConnectedModeState.claimModalState(screenName, state, _chromeClaimId);
|
|
||||||
else
|
|
||||||
ConnectedModeState.updateModalState(screenName, state, _chromeClaimId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _syncModalChromeState() {
|
function _syncModalChromeState() {
|
||||||
if (!frameOwnsConnectedChrome) {
|
_publishModalChromeState();
|
||||||
_releaseModalChrome();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const isClaim = !_chromeClaimId;
|
|
||||||
if (!_chromeClaimId)
|
|
||||||
_chromeClaimId = _nextChromeClaimId();
|
|
||||||
_publishModalChromeState(isClaim);
|
|
||||||
if (_dockBlocksEmergence && (shouldBeVisible || contentWindow.visible))
|
|
||||||
ConnectedModeState.requestDockRetract(_chromeClaimId, _currentScreenName(), resolvedConnectedBarSide);
|
|
||||||
else
|
|
||||||
ConnectedModeState.releaseDockRetract(_chromeClaimId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool _animSyncQueued: false
|
property bool _animSyncQueued: false
|
||||||
@@ -187,32 +196,21 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _syncModalAnim() {
|
function _syncModalAnim() {
|
||||||
if (!frameOwnsConnectedChrome || !_chromeClaimId)
|
if (!frameOwnsConnectedChrome)
|
||||||
return;
|
return;
|
||||||
const screenName = _currentScreenName();
|
if (!modalContainer)
|
||||||
if (!screenName || !modalContainer)
|
|
||||||
return;
|
return;
|
||||||
ConnectedModeState.setModalAnim(screenName, modalContainer.animX, modalContainer.animY, _chromeClaimId);
|
modalChrome.updateAnim(modalContainer.animX, modalContainer.animY);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _syncModalBody() {
|
function _syncModalBody() {
|
||||||
if (!frameOwnsConnectedChrome || !_chromeClaimId)
|
if (!frameOwnsConnectedChrome)
|
||||||
return;
|
return;
|
||||||
const screenName = _currentScreenName();
|
modalChrome.updateBody(alignedX, alignedY, alignedWidth, alignedHeight);
|
||||||
if (!screenName)
|
|
||||||
return;
|
|
||||||
ConnectedModeState.setModalBody(screenName, alignedX, alignedY, alignedWidth, alignedHeight, _chromeClaimId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _releaseModalChrome() {
|
function _releaseModalChrome() {
|
||||||
if (!_chromeClaimId)
|
modalChrome.release();
|
||||||
return;
|
|
||||||
ConnectedModeState.releaseDockRetract(_chromeClaimId);
|
|
||||||
const claimId = _chromeClaimId;
|
|
||||||
_chromeClaimId = "";
|
|
||||||
const screenName = _currentScreenName();
|
|
||||||
if (screenName)
|
|
||||||
ConnectedModeState.clearModalState(screenName, claimId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFrameOwnsConnectedChromeChanged: _syncModalChromeState()
|
onFrameOwnsConnectedChromeChanged: _syncModalChromeState()
|
||||||
@@ -223,8 +221,6 @@ Item {
|
|||||||
onAlignedWidthChanged: _queueBodySync()
|
onAlignedWidthChanged: _queueBodySync()
|
||||||
onAlignedHeightChanged: _queueBodySync()
|
onAlignedHeightChanged: _queueBodySync()
|
||||||
|
|
||||||
Component.onDestruction: _releaseModalChrome()
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: contentWindow
|
target: contentWindow
|
||||||
function onVisibleChanged() {
|
function onVisibleChanged() {
|
||||||
@@ -244,22 +240,16 @@ Item {
|
|||||||
const focusedScreen = CompositorService.getFocusedScreen();
|
const focusedScreen = CompositorService.getFocusedScreen();
|
||||||
if (focusedScreen) {
|
if (focusedScreen) {
|
||||||
contentWindow.screen = focusedScreen;
|
contentWindow.screen = focusedScreen;
|
||||||
if (!useSingleWindow)
|
|
||||||
clickCatcher.screen = focusedScreen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModalManager.openModal(modalHandle);
|
||||||
if (Theme.isDirectionalEffect || root.useBackground) {
|
if (Theme.isDirectionalEffect || root.useBackground) {
|
||||||
if (!useSingleWindow)
|
|
||||||
clickCatcher.visible = true;
|
|
||||||
contentWindow.visible = true;
|
contentWindow.visible = true;
|
||||||
}
|
}
|
||||||
ModalManager.openModal(modalHandle);
|
|
||||||
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
animationsEnabled = true;
|
animationsEnabled = true;
|
||||||
shouldBeVisible = true;
|
shouldBeVisible = true;
|
||||||
if (!useSingleWindow && !clickCatcher.visible)
|
|
||||||
clickCatcher.visible = true;
|
|
||||||
if (!contentWindow.visible)
|
if (!contentWindow.visible)
|
||||||
contentWindow.visible = true;
|
contentWindow.visible = true;
|
||||||
opened();
|
opened();
|
||||||
@@ -286,8 +276,6 @@ Item {
|
|||||||
ModalManager.closeModal(modalHandle);
|
ModalManager.closeModal(modalHandle);
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
if (!useSingleWindow)
|
|
||||||
clickCatcher.visible = false;
|
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
Qt.callLater(() => animationsEnabled = true);
|
Qt.callLater(() => animationsEnabled = true);
|
||||||
}
|
}
|
||||||
@@ -317,13 +305,15 @@ Item {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (screenStillExists)
|
if (screenStillExists) {
|
||||||
|
if (root.shouldBeVisible)
|
||||||
|
root._queueFullSync();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
root._releaseModalChrome();
|
||||||
const newScreen = CompositorService.getFocusedScreen();
|
const newScreen = CompositorService.getFocusedScreen();
|
||||||
if (newScreen) {
|
if (newScreen) {
|
||||||
contentWindow.screen = newScreen;
|
contentWindow.screen = newScreen;
|
||||||
if (!useSingleWindow)
|
|
||||||
clickCatcher.screen = newScreen;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,29 +325,12 @@ Item {
|
|||||||
if (shouldBeVisible)
|
if (shouldBeVisible)
|
||||||
return;
|
return;
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
if (!useSingleWindow)
|
|
||||||
clickCatcher.visible = false;
|
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shadowRenderPadding is zeroed when frame owns the chrome
|
|
||||||
// Wayland then clips any content translating past
|
|
||||||
readonly property var shadowLevel: Theme.elevationLevel3
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
readonly property real shadowFallbackOffset: 6
|
readonly property real shadowFallbackOffset: 6
|
||||||
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
|
||||||
readonly property real shadowMotionPadding: {
|
|
||||||
if (frameOwnsConnectedChrome)
|
|
||||||
return 0;
|
|
||||||
if (animationType === "slide")
|
|
||||||
return 30;
|
|
||||||
if (Theme.isDirectionalEffect)
|
|
||||||
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.9);
|
|
||||||
if (Theme.isDepthEffect)
|
|
||||||
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.35);
|
|
||||||
return Math.max(0, animationOffset);
|
|
||||||
}
|
|
||||||
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
|
||||||
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||||
|
|
||||||
@@ -367,7 +340,6 @@ Item {
|
|||||||
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
|
|
||||||
readonly property real _connectedAlignedX: {
|
readonly property real _connectedAlignedX: {
|
||||||
switch (resolvedConnectedBarSide) {
|
switch (resolvedConnectedBarSide) {
|
||||||
case "top":
|
case "top":
|
||||||
@@ -430,57 +402,6 @@ Item {
|
|||||||
}
|
}
|
||||||
})(), dpr)
|
})(), dpr)
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: clickCatcher
|
|
||||||
visible: false
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
|
|
||||||
WlrLayershell.layer: WlrLayershell.Top
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
mask: Region {
|
|
||||||
item: Rectangle {
|
|
||||||
x: root.alignedX
|
|
||||||
y: root.alignedY
|
|
||||||
width: root.alignedWidth
|
|
||||||
height: root.alignedHeight
|
|
||||||
}
|
|
||||||
intersection: Intersection.Xor
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
|
||||||
onClicked: root.backgroundClicked()
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: -1
|
|
||||||
color: "black"
|
|
||||||
opacity: (!root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
|
||||||
visible: opacity > 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: contentWindow
|
id: contentWindow
|
||||||
visible: false
|
visible: false
|
||||||
@@ -490,8 +411,8 @@ Item {
|
|||||||
targetWindow: contentWindow
|
targetWindow: contentWindow
|
||||||
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
|
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
|
||||||
readonly property real s: Math.min(1, modalContainer.scaleValue)
|
readonly property real s: Math.min(1, modalContainer.scaleValue)
|
||||||
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
|
blurX: connectedReveal.x + 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)
|
blurY: connectedReveal.y + modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
|
||||||
blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
|
blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
|
||||||
blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
|
blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
|
||||||
blurRadius: root.effectiveCornerRadius
|
blurRadius: root.effectiveCornerRadius
|
||||||
@@ -505,36 +426,15 @@ Item {
|
|||||||
"error": true
|
"error": true
|
||||||
})
|
})
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: {
|
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus)
|
||||||
if (customKeyboardFocus !== null)
|
|
||||||
return customKeyboardFocus;
|
|
||||||
if (!shouldHaveFocus)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (root.useHyprlandFocusGrab)
|
|
||||||
return WlrKeyboardFocus.OnDemand;
|
|
||||||
return WlrKeyboardFocus.Exclusive;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
right: root.useSingleWindow
|
right: true
|
||||||
bottom: root.useSingleWindow
|
bottom: true
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property real actualMarginLeft: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
|
||||||
readonly property real actualMarginTop: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
|
||||||
|
|
||||||
WlrLayershell.margins {
|
|
||||||
left: actualMarginLeft
|
|
||||||
top: actualMarginTop
|
|
||||||
right: 0
|
|
||||||
bottom: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
|
|
||||||
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible)
|
if (visible)
|
||||||
return;
|
return;
|
||||||
@@ -546,7 +446,7 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
z: -2
|
z: -2
|
||||||
onClicked: root.backgroundClicked()
|
onClicked: root.backgroundClicked()
|
||||||
}
|
}
|
||||||
@@ -555,7 +455,7 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
z: -1
|
z: -1
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
@@ -569,249 +469,256 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: connectedReveal
|
||||||
x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr)
|
// Clip to final footprint while frame-owned chrome grows from the bar edge.
|
||||||
y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr)
|
x: root.alignedX
|
||||||
|
y: root.alignedY
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.alignedHeight
|
height: root.alignedHeight
|
||||||
|
clip: root.frameOwnsConnectedChrome
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.useSingleWindow && root.shouldBeVisible
|
|
||||||
hoverEnabled: false
|
|
||||||
acceptedButtons: Qt.AllButtons
|
|
||||||
onPressed: mouse.accepted = true
|
|
||||||
onClicked: mouse.accepted = true
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool slide: root.animationType === "slide"
|
|
||||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
|
||||||
readonly property bool depthEffect: Theme.isDepthEffect
|
|
||||||
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
|
||||||
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
|
||||||
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
|
||||||
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
|
||||||
readonly property real customDistLeft: customAnchorX
|
|
||||||
readonly property real customDistRight: root.screenWidth - customAnchorX
|
|
||||||
readonly property real customDistTop: customAnchorY
|
|
||||||
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
|
||||||
// Connected emergence: travel from the resolved bar edge, matching DankPopout cadence.
|
|
||||||
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
|
||||||
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
|
||||||
readonly property real offsetX: {
|
|
||||||
if (root.frameOwnsConnectedChrome) {
|
|
||||||
switch (root.resolvedConnectedBarSide) {
|
|
||||||
case "left":
|
|
||||||
return -connectedEmergenceTravelX;
|
|
||||||
case "right":
|
|
||||||
return connectedEmergenceTravelX;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (slide && !directionalEffect && !depthEffect)
|
|
||||||
return 15;
|
|
||||||
if (directionalEffect) {
|
|
||||||
switch (root.positioning) {
|
|
||||||
case "top-right":
|
|
||||||
return 0;
|
|
||||||
case "custom":
|
|
||||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
|
||||||
return -directionalTravel;
|
|
||||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
|
||||||
return directionalTravel;
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (depthEffect) {
|
|
||||||
switch (root.positioning) {
|
|
||||||
case "top-right":
|
|
||||||
return 0;
|
|
||||||
case "custom":
|
|
||||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
|
||||||
return -depthTravel;
|
|
||||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
|
||||||
return depthTravel;
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
readonly property real offsetY: {
|
|
||||||
if (root.frameOwnsConnectedChrome) {
|
|
||||||
switch (root.resolvedConnectedBarSide) {
|
|
||||||
case "top":
|
|
||||||
return -connectedEmergenceTravelY;
|
|
||||||
case "bottom":
|
|
||||||
return connectedEmergenceTravelY;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (slide && !directionalEffect && !depthEffect)
|
|
||||||
return -30;
|
|
||||||
if (directionalEffect) {
|
|
||||||
switch (root.positioning) {
|
|
||||||
case "top-right":
|
|
||||||
return -Math.max(directionalTravel * 0.65, 96);
|
|
||||||
case "custom":
|
|
||||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
|
||||||
return -directionalTravel;
|
|
||||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
|
||||||
return directionalTravel;
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
// Default to sliding down from top when centered
|
|
||||||
return -Math.max(directionalTravel, root.screenHeight * 0.24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (depthEffect) {
|
|
||||||
switch (root.positioning) {
|
|
||||||
case "top-right":
|
|
||||||
return -depthTravel * 0.75;
|
|
||||||
case "custom":
|
|
||||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
|
||||||
return -depthTravel;
|
|
||||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
|
||||||
return depthTravel;
|
|
||||||
return depthTravel * 0.45;
|
|
||||||
default:
|
|
||||||
return -depthTravel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return root.animationOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
|
||||||
|
|
||||||
// openProgress: 0 = closed (at frozenMotionOffset, scaleCollapsed), 1 = open (at 0, scale 1).
|
|
||||||
QtObject {
|
|
||||||
id: morph
|
|
||||||
property real openProgress: root.shouldBeVisible ? 1 : 0
|
|
||||||
Behavior on openProgress {
|
|
||||||
enabled: root.animationsEnabled
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress)
|
|
||||||
readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress)
|
|
||||||
readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress
|
|
||||||
|
|
||||||
onAnimXChanged: if (root.frameOwnsConnectedChrome)
|
|
||||||
root._queueAnimSync()
|
|
||||||
onAnimYChanged: if (root.frameOwnsConnectedChrome)
|
|
||||||
root._queueAnimSync()
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: contentContainer
|
id: modalContainer
|
||||||
anchors.centerIn: parent
|
x: Theme.snap(animX, root.dpr)
|
||||||
width: parent.width
|
y: Theme.snap(animY, root.dpr)
|
||||||
height: parent.height
|
|
||||||
clip: false
|
width: root.alignedWidth
|
||||||
|
height: root.alignedHeight
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.shouldBeVisible
|
||||||
|
hoverEnabled: false
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
onPressed: mouse.accepted = true
|
||||||
|
onClicked: mouse.accepted = true
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool slide: root.animationType === "slide"
|
||||||
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
|
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
||||||
|
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
||||||
|
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
||||||
|
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
||||||
|
readonly property real customDistLeft: customAnchorX
|
||||||
|
readonly property real customDistRight: root.screenWidth - customAnchorX
|
||||||
|
readonly property real customDistTop: customAnchorY
|
||||||
|
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
||||||
|
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
||||||
|
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
||||||
|
readonly property real offsetX: {
|
||||||
|
if (root.frameOwnsConnectedChrome) {
|
||||||
|
switch (root.resolvedConnectedBarSide) {
|
||||||
|
case "left":
|
||||||
|
return -connectedEmergenceTravelX;
|
||||||
|
case "right":
|
||||||
|
return connectedEmergenceTravelX;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (slide && !directionalEffect && !depthEffect)
|
||||||
|
return 15;
|
||||||
|
if (directionalEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return 0;
|
||||||
|
case "custom":
|
||||||
|
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||||
|
return -directionalTravel;
|
||||||
|
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||||
|
return directionalTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return 0;
|
||||||
|
case "custom":
|
||||||
|
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||||
|
return -depthTravel;
|
||||||
|
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||||
|
return depthTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
readonly property real offsetY: {
|
||||||
|
if (root.frameOwnsConnectedChrome) {
|
||||||
|
switch (root.resolvedConnectedBarSide) {
|
||||||
|
case "top":
|
||||||
|
return -connectedEmergenceTravelY;
|
||||||
|
case "bottom":
|
||||||
|
return connectedEmergenceTravelY;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (slide && !directionalEffect && !depthEffect)
|
||||||
|
return -30;
|
||||||
|
if (directionalEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return -Math.max(directionalTravel * 0.65, 96);
|
||||||
|
case "custom":
|
||||||
|
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||||
|
return -directionalTravel;
|
||||||
|
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||||
|
return directionalTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -Math.max(directionalTravel, root.screenHeight * 0.24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return -depthTravel * 0.75;
|
||||||
|
case "custom":
|
||||||
|
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||||
|
return -depthTravel;
|
||||||
|
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||||
|
return depthTravel;
|
||||||
|
return depthTravel * 0.45;
|
||||||
|
default:
|
||||||
|
return -depthTravel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root.animationOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: morph
|
||||||
|
property real openProgress: root.shouldBeVisible ? 1 : 0
|
||||||
|
Behavior on openProgress {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress)
|
||||||
|
readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress)
|
||||||
|
readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress
|
||||||
|
|
||||||
|
onAnimXChanged: if (root.frameOwnsConnectedChrome)
|
||||||
|
root._queueAnimSync()
|
||||||
|
onAnimYChanged: if (root.frameOwnsConnectedChrome)
|
||||||
|
root._queueAnimSync()
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: animatedContent
|
id: contentContainer
|
||||||
anchors.fill: parent
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
clip: false
|
clip: false
|
||||||
|
|
||||||
property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
Item {
|
||||||
|
id: animatedContent
|
||||||
opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
|
||||||
scale: modalContainer.scaleValue
|
|
||||||
transformOrigin: Item.Center
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on publishedOpacity {
|
|
||||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevationShadow {
|
|
||||||
id: modalShadowLayer
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
level: root.shadowLevel
|
|
||||||
fallbackOffset: root.shadowFallbackOffset
|
|
||||||
targetRadius: root.effectiveCornerRadius
|
|
||||||
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
|
|
||||||
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
|
|
||||||
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
|
|
||||||
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: root.effectiveCornerRadius
|
|
||||||
color: "transparent"
|
|
||||||
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
|
|
||||||
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
|
|
||||||
z: 100
|
|
||||||
}
|
|
||||||
|
|
||||||
FocusScope {
|
|
||||||
anchors.fill: parent
|
|
||||||
focus: root.shouldBeVisible
|
|
||||||
clip: false
|
clip: false
|
||||||
|
|
||||||
Item {
|
property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||||
id: directContentWrapper
|
|
||||||
|
opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||||
|
scale: modalContainer.scaleValue
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on publishedOpacity {
|
||||||
|
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElevationShadow {
|
||||||
|
id: modalShadowLayer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: root.directContent !== null
|
level: root.shadowLevel
|
||||||
focus: true
|
fallbackOffset: root.shadowFallbackOffset
|
||||||
|
targetRadius: root.effectiveCornerRadius
|
||||||
|
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
|
||||||
|
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
|
||||||
|
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
|
||||||
|
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.effectiveCornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
|
||||||
|
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: root.shouldBeVisible
|
||||||
clip: false
|
clip: false
|
||||||
|
|
||||||
Component.onCompleted: {
|
Item {
|
||||||
if (root.directContent) {
|
id: directContentWrapper
|
||||||
root.directContent.parent = directContentWrapper;
|
anchors.fill: parent
|
||||||
root.directContent.anchors.fill = directContentWrapper;
|
visible: root.directContent !== null
|
||||||
Qt.callLater(() => root.directContent.forceActiveFocus());
|
focus: true
|
||||||
}
|
clip: false
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Component.onCompleted: {
|
||||||
target: root
|
|
||||||
function onDirectContentChanged() {
|
|
||||||
if (root.directContent) {
|
if (root.directContent) {
|
||||||
root.directContent.parent = directContentWrapper;
|
root.directContent.parent = directContentWrapper;
|
||||||
root.directContent.anchors.fill = directContentWrapper;
|
root.directContent.anchors.fill = directContentWrapper;
|
||||||
Qt.callLater(() => root.directContent.forceActiveFocus());
|
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onDirectContentChanged() {
|
||||||
|
if (root.directContent) {
|
||||||
|
root.directContent.parent = directContentWrapper;
|
||||||
|
root.directContent.anchors.fill = directContentWrapper;
|
||||||
|
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: contentLoader
|
id: contentLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
|
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
focus: true
|
focus: true
|
||||||
clip: false
|
clip: false
|
||||||
visible: root.directContent === null
|
visible: root.directContent === null
|
||||||
|
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (item) {
|
if (item) {
|
||||||
Qt.callLater(() => item.forceActiveFocus());
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ Item {
|
|||||||
id: clickCatcher
|
id: clickCatcher
|
||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
updatesEnabled: false
|
||||||
|
|
||||||
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
|
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
|
||||||
WlrLayershell.layer: WlrLayershell.Top
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
@@ -259,15 +260,7 @@ Item {
|
|||||||
"error": true
|
"error": true
|
||||||
})
|
})
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: {
|
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus)
|
||||||
if (customKeyboardFocus !== null)
|
|
||||||
return customKeyboardFocus;
|
|
||||||
if (!shouldHaveFocus)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (root.useHyprlandFocusGrab)
|
|
||||||
return WlrKeyboardFocus.OnDemand;
|
|
||||||
return WlrKeyboardFocus.Exclusive;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Hyprland
|
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
@@ -13,11 +12,6 @@ DankModal {
|
|||||||
|
|
||||||
layerNamespace: "dms:color-picker"
|
layerNamespace: "dms:color-picker"
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
|
||||||
windows: [root.contentWindow]
|
|
||||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
property string pickerTitle: I18n.tr("Choose Color")
|
property string pickerTitle: I18n.tr("Choose Color")
|
||||||
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
|
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
|
||||||
property var onColorSelectedCallback: null
|
property var onColorSelectedCallback: null
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ Item {
|
|||||||
property string _pendingMode: ""
|
property string _pendingMode: ""
|
||||||
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
||||||
|
|
||||||
// Animation state — matches DankPopout/DankModal pattern
|
|
||||||
property bool animationsEnabled: true
|
property bool animationsEnabled: true
|
||||||
property bool _motionActive: false
|
property bool _motionActive: false
|
||||||
property real _frozenMotionX: 0
|
property real _frozenMotionX: 0
|
||||||
@@ -108,8 +107,6 @@ Item {
|
|||||||
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameEdgeInsetForSide is the full inset; do not add frameBarSize.
|
|
||||||
// Positions the modal flush to the emerge side, centered on the cross axis.
|
|
||||||
readonly property var _connectedModalPos: {
|
readonly property var _connectedModalPos: {
|
||||||
const fallback = {
|
const fallback = {
|
||||||
"x": (screenWidth - modalWidth) / 2,
|
"x": (screenWidth - modalWidth) / 2,
|
||||||
@@ -175,8 +172,6 @@ Item {
|
|||||||
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
|
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
|
||||||
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
||||||
|
|
||||||
// Shadow padding for the content window (render padding only, no motion padding).
|
|
||||||
// Zeroed when frame owns the chrome and Wayland clips past the bar edge
|
|
||||||
readonly property var shadowLevel: Theme.elevationLevel3
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
readonly property real shadowFallbackOffset: 6
|
readonly property real shadowFallbackOffset: 6
|
||||||
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
||||||
@@ -203,81 +198,76 @@ Item {
|
|||||||
}
|
}
|
||||||
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
|
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
|
||||||
|
|
||||||
// For directional/depth: window extends from screen top (content slides within)
|
readonly property real _ccX: _connectedChromeX
|
||||||
// For standard: small window tightly around the modal + shadow padding
|
readonly property real _ccY: _connectedChromeY
|
||||||
readonly property bool _needsExtendedWindow: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) || Theme.isDepthEffect
|
|
||||||
// Content window geometry
|
|
||||||
readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr)
|
|
||||||
readonly property real _cwMarginTop: launcherArcExtenderActive ? _connectedChromeY : (_needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr))
|
|
||||||
readonly property real _cwWidth: alignedWidth + shadowPad * 2
|
|
||||||
readonly property real _cwHeight: {
|
|
||||||
if (launcherArcExtenderActive)
|
|
||||||
return _connectedChromeHeight;
|
|
||||||
if (Theme.isDirectionalEffect && !Theme.isConnectedEffect)
|
|
||||||
return screenHeight + shadowPad;
|
|
||||||
if (Theme.isDepthEffect)
|
|
||||||
return alignedY + alignedHeight + shadowPad;
|
|
||||||
return alignedHeight + shadowPad * 2;
|
|
||||||
}
|
|
||||||
// Where the content container sits inside the content window
|
|
||||||
readonly property real _ccX: shadowPad
|
|
||||||
readonly property real _ccY: launcherArcExtenderActive ? 0 : (_needsExtendedWindow ? alignedY : shadowPad)
|
|
||||||
|
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
|
|
||||||
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
|
|
||||||
Timer {
|
Timer {
|
||||||
id: _syncTimer
|
id: _syncTimer
|
||||||
interval: 0
|
interval: 0
|
||||||
onTriggered: root._flushSync()
|
onTriggered: root._flushSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
property string _chromeClaimId: ""
|
|
||||||
property bool _fullSyncPending: false
|
property bool _fullSyncPending: false
|
||||||
|
|
||||||
function _nextChromeClaimId() {
|
|
||||||
return "dms:launcher-v2:" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _currentScreenName() {
|
function _currentScreenName() {
|
||||||
return effectiveScreen ? effectiveScreen.name : "";
|
return effectiveScreen ? effectiveScreen.name : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function _publishModalChromeState(isClaim) {
|
ConnectedModalChrome {
|
||||||
const screenName = _currentScreenName();
|
id: modalChrome
|
||||||
if (!screenName)
|
modalHandle: root.modalHandle
|
||||||
return;
|
claimPrefix: "dms:launcher-v2"
|
||||||
|
surfaceKind: "launcher"
|
||||||
|
screenName: root._currentScreenName()
|
||||||
|
enabled: root.frameOwnsConnectedChrome
|
||||||
|
active: root.spotlightOpen
|
||||||
|
presented: root.spotlightOpen || contentWindow.visible
|
||||||
|
dockBlocked: root._dockBlocksEmergence
|
||||||
|
dockSide: root.resolvedConnectedBarSide
|
||||||
|
onRecoveryRequested: root._queueFullSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _publishModalChromeState() {
|
||||||
|
const presented = spotlightOpen || contentWindow.visible;
|
||||||
|
const phase = !presented ? "hidden" : (isClosing ? "closing" : (!contentWindow.visible ? "opening" : "open"));
|
||||||
|
const bodyRect = {
|
||||||
|
"x": _connectedChromeX,
|
||||||
|
"y": _connectedChromeY,
|
||||||
|
"width": _connectedChromeWidth,
|
||||||
|
"height": _connectedChromeHeight
|
||||||
|
};
|
||||||
|
const animationOffset = {
|
||||||
|
"x": contentContainer ? contentContainer.animX : 0,
|
||||||
|
"y": contentContainer ? contentContainer.animY : 0
|
||||||
|
};
|
||||||
const state = {
|
const state = {
|
||||||
"visible": spotlightOpen || contentWindow.visible,
|
"kind": "launcher",
|
||||||
|
"screenName": root._currentScreenName(),
|
||||||
|
"phase": phase,
|
||||||
|
"visible": presented,
|
||||||
|
"presented": presented,
|
||||||
"barSide": resolvedConnectedBarSide,
|
"barSide": resolvedConnectedBarSide,
|
||||||
|
"bodyRect": bodyRect,
|
||||||
|
"animationOffset": animationOffset,
|
||||||
|
"scale": 1,
|
||||||
|
"opacity": Theme.connectedSurfaceColor.a,
|
||||||
"bodyX": _connectedChromeX,
|
"bodyX": _connectedChromeX,
|
||||||
"bodyY": _connectedChromeY,
|
"bodyY": _connectedChromeY,
|
||||||
"bodyW": _connectedChromeWidth,
|
"bodyW": _connectedChromeWidth,
|
||||||
"bodyH": _connectedChromeHeight,
|
"bodyH": _connectedChromeHeight,
|
||||||
"animX": contentContainer ? contentContainer.animX : 0,
|
"animX": animationOffset.x,
|
||||||
"animY": contentContainer ? contentContainer.animY : 0,
|
"animY": animationOffset.y,
|
||||||
"omitStartConnector": false,
|
"omitStartConnector": false,
|
||||||
"omitEndConnector": false
|
"omitEndConnector": false,
|
||||||
|
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
|
||||||
};
|
};
|
||||||
if (isClaim)
|
return modalChrome.publish(state);
|
||||||
ConnectedModeState.claimModalState(screenName, state, _chromeClaimId);
|
|
||||||
else
|
|
||||||
ConnectedModeState.updateModalState(screenName, state, _chromeClaimId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _syncModalChromeState() {
|
function _syncModalChromeState() {
|
||||||
if (!frameOwnsConnectedChrome) {
|
_publishModalChromeState();
|
||||||
_releaseModalChrome();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const isClaim = !_chromeClaimId;
|
|
||||||
if (!_chromeClaimId)
|
|
||||||
_chromeClaimId = _nextChromeClaimId();
|
|
||||||
_publishModalChromeState(isClaim);
|
|
||||||
if (_dockBlocksEmergence && (spotlightOpen || contentWindow.visible))
|
|
||||||
ConnectedModeState.requestDockRetract(_chromeClaimId, _currentScreenName(), resolvedConnectedBarSide);
|
|
||||||
else
|
|
||||||
ConnectedModeState.releaseDockRetract(_chromeClaimId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool _animSyncQueued: false
|
property bool _animSyncQueued: false
|
||||||
@@ -314,32 +304,21 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _syncModalAnim() {
|
function _syncModalAnim() {
|
||||||
if (!frameOwnsConnectedChrome || !_chromeClaimId)
|
if (!frameOwnsConnectedChrome)
|
||||||
return;
|
return;
|
||||||
const screenName = _currentScreenName();
|
if (!contentContainer)
|
||||||
if (!screenName || !contentContainer)
|
|
||||||
return;
|
return;
|
||||||
ConnectedModeState.setModalAnim(screenName, contentContainer.animX, contentContainer.animY, _chromeClaimId);
|
modalChrome.updateAnim(contentContainer.animX, contentContainer.animY);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _syncModalBody() {
|
function _syncModalBody() {
|
||||||
if (!frameOwnsConnectedChrome || !_chromeClaimId)
|
if (!frameOwnsConnectedChrome)
|
||||||
return;
|
return;
|
||||||
const screenName = _currentScreenName();
|
modalChrome.updateBody(_connectedChromeX, _connectedChromeY, _connectedChromeWidth, _connectedChromeHeight);
|
||||||
if (!screenName)
|
|
||||||
return;
|
|
||||||
ConnectedModeState.setModalBody(screenName, _connectedChromeX, _connectedChromeY, _connectedChromeWidth, _connectedChromeHeight, _chromeClaimId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _releaseModalChrome() {
|
function _releaseModalChrome() {
|
||||||
if (!_chromeClaimId)
|
modalChrome.release();
|
||||||
return;
|
|
||||||
ConnectedModeState.releaseDockRetract(_chromeClaimId);
|
|
||||||
const claimId = _chromeClaimId;
|
|
||||||
_chromeClaimId = "";
|
|
||||||
const screenName = _currentScreenName();
|
|
||||||
if (screenName)
|
|
||||||
ConnectedModeState.clearModalState(screenName, claimId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFrameOwnsConnectedChromeChanged: _syncModalChromeState()
|
onFrameOwnsConnectedChromeChanged: _syncModalChromeState()
|
||||||
@@ -351,8 +330,6 @@ Item {
|
|||||||
onAlignedWidthChanged: _queueBodySync()
|
onAlignedWidthChanged: _queueBodySync()
|
||||||
onAlignedHeightChanged: _queueBodySync()
|
onAlignedHeightChanged: _queueBodySync()
|
||||||
|
|
||||||
Component.onDestruction: _releaseModalChrome()
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: contentWindow
|
target: contentWindow
|
||||||
function onVisibleChanged() {
|
function onVisibleChanged() {
|
||||||
@@ -381,8 +358,6 @@ Item {
|
|||||||
return;
|
return;
|
||||||
contentVisible = true;
|
contentVisible = true;
|
||||||
spotlightContent.closeTransientUi?.();
|
spotlightContent.closeTransientUi?.();
|
||||||
// NOTE: forceActiveFocus() is deliberately NOT called here.
|
|
||||||
// It is deferred to after animation starts to avoid compositor IPC stalls.
|
|
||||||
|
|
||||||
if (spotlightContent.searchField) {
|
if (spotlightContent.searchField) {
|
||||||
spotlightContent.searchField.text = query;
|
spotlightContent.searchField.text = query;
|
||||||
@@ -420,40 +395,29 @@ Item {
|
|||||||
isClosing = false;
|
isClosing = false;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
|
|
||||||
// Disable animations so the snap is instant
|
|
||||||
animationsEnabled = false;
|
animationsEnabled = false;
|
||||||
|
|
||||||
// Freeze the collapsed offsets (they depend on height which could change)
|
|
||||||
_frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0;
|
_frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0;
|
||||||
_frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset);
|
_frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset);
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
var focusedScreen = CompositorService.getFocusedScreen();
|
||||||
if (focusedScreen) {
|
if (focusedScreen) {
|
||||||
backgroundWindow.screen = focusedScreen;
|
|
||||||
contentWindow.screen = focusedScreen;
|
contentWindow.screen = focusedScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// _motionActive = false ensures motionX/Y snap to frozen collapsed position
|
|
||||||
_motionActive = false;
|
_motionActive = false;
|
||||||
|
|
||||||
// Make windows visible but do NOT request keyboard focus yet
|
|
||||||
ModalManager.openModal(modalHandle);
|
ModalManager.openModal(modalHandle);
|
||||||
spotlightOpen = true;
|
spotlightOpen = true;
|
||||||
backgroundWindow.visible = true;
|
|
||||||
contentWindow.visible = true;
|
contentWindow.visible = true;
|
||||||
if (useHyprlandFocusGrab)
|
|
||||||
focusGrab.active = true;
|
|
||||||
|
|
||||||
// Load content and initialize (but no forceActiveFocus — that's deferred)
|
|
||||||
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||||
|
|
||||||
// Frame 1: enable animations and trigger enter motion
|
// Defer focus until after enter motion starts (avoids compositor IPC stalls).
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root.animationsEnabled = true;
|
root.animationsEnabled = true;
|
||||||
root._motionActive = true;
|
root._motionActive = true;
|
||||||
|
|
||||||
// Frame 2: request keyboard focus + activate search field
|
|
||||||
// Double-deferred to avoid compositor IPC competing with animation frames
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root.keyboardActive = true;
|
root.keyboardActive = true;
|
||||||
if (root.spotlightContent && root.spotlightContent.searchField)
|
if (root.spotlightContent && root.spotlightContent.searchField)
|
||||||
@@ -476,16 +440,13 @@ Item {
|
|||||||
spotlightContent?.closeTransientUi?.();
|
spotlightContent?.closeTransientUi?.();
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
isClosing = true;
|
isClosing = true;
|
||||||
// For directional effects, defer contentVisible=false so content stays rendered during exit slide
|
|
||||||
if (!Theme.isDirectionalEffect)
|
if (!Theme.isDirectionalEffect)
|
||||||
contentVisible = false;
|
contentVisible = false;
|
||||||
|
|
||||||
// Trigger exit animation — Behaviors will animate motionX/Y to frozen collapsed position
|
|
||||||
_motionActive = false;
|
_motionActive = false;
|
||||||
|
|
||||||
keyboardActive = false;
|
keyboardActive = false;
|
||||||
spotlightOpen = false;
|
spotlightOpen = false;
|
||||||
focusGrab.active = false;
|
|
||||||
ModalManager.closeModal(modalHandle);
|
ModalManager.closeModal(modalHandle);
|
||||||
closeCleanupTimer.start();
|
closeCleanupTimer.start();
|
||||||
}
|
}
|
||||||
@@ -522,7 +483,6 @@ Item {
|
|||||||
isClosing = false;
|
isClosing = false;
|
||||||
contentVisible = false;
|
contentVisible = false;
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
backgroundWindow.visible = false;
|
|
||||||
if (root.unloadContentOnClose)
|
if (root.unloadContentOnClose)
|
||||||
launcherContentLoader.active = false;
|
launcherContentLoader.active = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
@@ -541,7 +501,7 @@ Item {
|
|||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
id: focusGrab
|
||||||
windows: [contentWindow]
|
windows: [contentWindow]
|
||||||
active: false
|
active: root.useHyprlandFocusGrab && root.spotlightOpen
|
||||||
|
|
||||||
onCleared: {
|
onCleared: {
|
||||||
if (spotlightOpen) {
|
if (spotlightOpen) {
|
||||||
@@ -579,15 +539,18 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needsReset)
|
if (!needsReset) {
|
||||||
|
if (root.spotlightOpen)
|
||||||
|
root._queueFullSync();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newScreen = CompositorService.getFocusedScreen() ?? Quickshell.screens[0];
|
const newScreen = CompositorService.getFocusedScreen() ?? Quickshell.screens[0];
|
||||||
if (!newScreen)
|
if (!newScreen)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
root._releaseModalChrome();
|
||||||
root._windowEnabled = false;
|
root._windowEnabled = false;
|
||||||
backgroundWindow.screen = newScreen;
|
|
||||||
contentWindow.screen = newScreen;
|
contentWindow.screen = newScreen;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root._windowEnabled = true;
|
root._windowEnabled = true;
|
||||||
@@ -595,73 +558,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: backgroundWindow
|
|
||||||
visible: false
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
readonly property real _topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
|
|
||||||
readonly property real _bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
|
|
||||||
readonly property real _leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
|
|
||||||
readonly property real _rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
|
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:spotlight:bg"
|
|
||||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
WlrLayershell.margins {
|
|
||||||
top: backgroundWindow._topMargin
|
|
||||||
bottom: backgroundWindow._bottomMargin
|
|
||||||
left: backgroundWindow._leftMargin
|
|
||||||
right: backgroundWindow._rightMargin
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
bottom: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
}
|
|
||||||
|
|
||||||
mask: Region {
|
|
||||||
item: (spotlightOpen || isClosing) ? bgFullScreenMask : null
|
|
||||||
|
|
||||||
Region {
|
|
||||||
item: bgContentHole
|
|
||||||
intersection: Intersection.Subtract
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: bgFullScreenMask
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: bgContentHole
|
|
||||||
visible: false
|
|
||||||
x: root._cwMarginLeft + contentContainer.x - backgroundWindow._leftMargin
|
|
||||||
y: root._cwMarginTop + contentContainer.y - backgroundWindow._topMargin
|
|
||||||
width: root.alignedWidth
|
|
||||||
height: root.contentSurfaceHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: backgroundDarken
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "black"
|
|
||||||
opacity: 0
|
|
||||||
visible: false
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: spotlightOpen
|
|
||||||
onClicked: root.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: contentWindow
|
id: contentWindow
|
||||||
visible: false
|
visible: false
|
||||||
@@ -681,23 +577,31 @@ Item {
|
|||||||
WlrLayershell.namespace: "dms:spotlight"
|
WlrLayershell.namespace: "dms:spotlight"
|
||||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
|
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.margins {
|
|
||||||
left: root._cwMarginLeft
|
|
||||||
top: root._cwMarginTop
|
|
||||||
}
|
|
||||||
|
|
||||||
implicitWidth: root._cwWidth
|
|
||||||
implicitHeight: root._cwHeight
|
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: contentInputMask
|
item: (root.spotlightOpen || root.isClosing) ? dismissArea : contentInputMask
|
||||||
|
|
||||||
|
Region {
|
||||||
|
item: (root.spotlightOpen || root.isClosing) ? contentInputMask : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: dismissArea
|
||||||
|
visible: false
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
|
||||||
|
anchors.bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
|
||||||
|
anchors.leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
|
||||||
|
anchors.rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -709,16 +613,31 @@ Item {
|
|||||||
height: root.contentSurfaceHeight
|
height: root.contentSurfaceHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: dismissArea
|
||||||
|
enabled: root.spotlightOpen
|
||||||
|
z: -2
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: contentContainer
|
id: contentContainer
|
||||||
|
|
||||||
// For directional/depth: contentContainer is at alignedY from window top (window starts at screen top)
|
|
||||||
// For standard: contentContainer is at shadowPad from window top (window starts near modal)
|
|
||||||
x: root._ccX
|
x: root._ccX
|
||||||
y: root._ccY
|
y: root._ccY
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.contentSurfaceHeight
|
height: root.contentSurfaceHeight
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.spotlightOpen
|
||||||
|
hoverEnabled: false
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
onPressed: mouse.accepted = true
|
||||||
|
onClicked: mouse.accepted = true
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
|
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
|
||||||
readonly property bool dockTop: dockEdge === 0
|
readonly property bool dockTop: dockEdge === 0
|
||||||
readonly property bool dockBottom: dockEdge === 1
|
readonly property bool dockBottom: dockEdge === 1
|
||||||
@@ -773,7 +692,6 @@ Item {
|
|||||||
return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
|
return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
// openProgress: 0 = closed (at frozenMotion, scaleCollapsed), 1 = open (at 0, scale 1).
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: morph
|
id: morph
|
||||||
property real openProgress: root._motionActive ? 1 : 0
|
property real openProgress: root._motionActive ? 1 : 0
|
||||||
@@ -832,7 +750,6 @@ Item {
|
|||||||
width: contentContainer.width
|
width: contentContainer.width
|
||||||
height: contentContainer.height
|
height: contentContainer.height
|
||||||
|
|
||||||
// Shadow mirrors contentWrapper position/scale/opacity
|
|
||||||
ElevationShadow {
|
ElevationShadow {
|
||||||
id: launcherShadowLayer
|
id: launcherShadowLayer
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -850,7 +767,6 @@ Item {
|
|||||||
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
// contentWrapper moves inside static contentContainer — DankPopout pattern
|
|
||||||
Item {
|
Item {
|
||||||
id: contentWrapper
|
id: contentWrapper
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -84,14 +84,14 @@ Item {
|
|||||||
readonly property real alignedX: Theme.snap(modalX, dpr)
|
readonly property real alignedX: Theme.snap(modalX, dpr)
|
||||||
readonly property real alignedY: Theme.snap(modalY, dpr)
|
readonly property real alignedY: Theme.snap(modalY, dpr)
|
||||||
|
|
||||||
// Extra headroom above the window for the slide-in animation
|
// Extra headroom above the content for the slide-in animation
|
||||||
readonly property real _animHeadroom: 16
|
readonly property real _animHeadroom: 16
|
||||||
readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr))
|
readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr))
|
||||||
readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr))
|
readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr))
|
||||||
readonly property real contentX: Theme.snap(alignedX - windowX, dpr)
|
readonly property real contentX: Theme.snap(alignedX - windowX, dpr)
|
||||||
readonly property real contentY: Theme.snap(alignedY - windowY, dpr)
|
readonly property real contentY: Theme.snap(alignedY - windowY, dpr)
|
||||||
readonly property real windowWidth: alignedWidth + contentX + shadowPad
|
|
||||||
readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr)
|
readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr)
|
||||||
|
readonly property real windowWidth: alignedWidth + contentX + shadowPad
|
||||||
readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom
|
readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom
|
||||||
|
|
||||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
@@ -114,6 +114,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
||||||
|
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
|
||||||
|
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
|
|
||||||
@@ -164,8 +165,6 @@ Item {
|
|||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
keyboardActive = true;
|
keyboardActive = true;
|
||||||
ModalManager.openModal(modalHandle);
|
ModalManager.openModal(modalHandle);
|
||||||
if (useHyprlandFocusGrab)
|
|
||||||
focusGrab.active = true;
|
|
||||||
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +200,6 @@ Item {
|
|||||||
contentVisible = false;
|
contentVisible = false;
|
||||||
keyboardActive = false;
|
keyboardActive = false;
|
||||||
spotlightOpen = false;
|
spotlightOpen = false;
|
||||||
focusGrab.active = false;
|
|
||||||
ModalManager.closeModal(modalHandle);
|
ModalManager.closeModal(modalHandle);
|
||||||
closeCleanupTimer.start();
|
closeCleanupTimer.start();
|
||||||
}
|
}
|
||||||
@@ -231,7 +229,7 @@ Item {
|
|||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
id: focusGrab
|
||||||
windows: [launcherWindow]
|
windows: [launcherWindow]
|
||||||
active: false
|
active: root.useHyprlandFocusGrab && root.keyboardActive
|
||||||
onCleared: {
|
onCleared: {
|
||||||
if (spotlightOpen)
|
if (spotlightOpen)
|
||||||
hide();
|
hide();
|
||||||
@@ -270,8 +268,9 @@ Item {
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: clickCatcher
|
id: clickCatcher
|
||||||
screen: launcherWindow.screen
|
screen: launcherWindow.screen
|
||||||
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken
|
visible: (spotlightOpen || isClosing) && !root.useSingleWindow
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
updatesEnabled: false
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
||||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||||
@@ -337,24 +336,24 @@ Item {
|
|||||||
WlrLayershell.namespace: "dms:spotlight"
|
WlrLayershell.namespace: "dms:spotlight"
|
||||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
|
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
left: true
|
left: true
|
||||||
right: root.useBackgroundDarken
|
right: root.useSingleWindow
|
||||||
bottom: root.useBackgroundDarken
|
bottom: root.useSingleWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.margins {
|
WlrLayershell.margins {
|
||||||
left: root.useBackgroundDarken ? 0 : root.windowX
|
left: root.useSingleWindow ? 0 : root.windowX
|
||||||
top: root.useBackgroundDarken ? 0 : root.windowY
|
top: root.useSingleWindow ? 0 : root.windowY
|
||||||
right: 0
|
right: 0
|
||||||
bottom: 0
|
bottom: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth
|
implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
|
||||||
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight
|
implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: inputMask
|
item: inputMask
|
||||||
@@ -364,15 +363,15 @@ Item {
|
|||||||
id: inputMask
|
id: inputMask
|
||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
x: root.useBackgroundDarken ? 0 : modalContainer.x
|
x: root.useSingleWindow ? 0 : modalContainer.x
|
||||||
y: root.useBackgroundDarken ? 0 : modalContainer.y + modalContainer.slideOffset
|
y: root.useSingleWindow ? 0 : modalContainer.y + modalContainer.slideOffset
|
||||||
width: root.useBackgroundDarken ? launcherWindow.width : root.alignedWidth
|
width: root.useSingleWindow ? launcherWindow.width : root.alignedWidth
|
||||||
height: root.useBackgroundDarken ? launcherWindow.height : root._contentImplicitH
|
height: root.useSingleWindow ? launcherWindow.height : root._contentImplicitH
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.useBackgroundDarken && spotlightOpen
|
enabled: root.useSingleWindow && spotlightOpen
|
||||||
z: -2
|
z: -2
|
||||||
onClicked: root.hide()
|
onClicked: root.hide()
|
||||||
}
|
}
|
||||||
@@ -396,13 +395,23 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: modalContainer
|
||||||
x: root.useBackgroundDarken ? root.alignedX : root.contentX
|
x: root.useSingleWindow ? root.alignedX : root.contentX
|
||||||
y: root.useBackgroundDarken ? root.alignedY : root.contentY
|
y: root.useSingleWindow ? root.alignedY : root.contentY
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root._animatedContentH
|
height: root._animatedContentH
|
||||||
visible: _renderActive
|
visible: _renderActive
|
||||||
z: 0
|
z: 0
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: spotlightOpen
|
||||||
|
hoverEnabled: false
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
onPressed: mouse.accepted = true
|
||||||
|
onClicked: mouse.accepted = true
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
property bool _renderActive: contentVisible
|
property bool _renderActive: contentVisible
|
||||||
property real slideOffset: contentVisible ? 0 : -root._animHeadroom
|
property real slideOffset: contentVisible ? 0 : -root._animHeadroom
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ Item {
|
|||||||
|
|
||||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
|
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
|
||||||
|
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
|
||||||
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
|
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
|
||||||
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||||
"allow": ["top", "overlay"],
|
"allow": ["top", "overlay"],
|
||||||
@@ -172,8 +173,6 @@ Item {
|
|||||||
|
|
||||||
keyboardActive = true;
|
keyboardActive = true;
|
||||||
ModalManager.openModal(modalHandle);
|
ModalManager.openModal(modalHandle);
|
||||||
if (useHyprlandFocusGrab)
|
|
||||||
focusGrab.active = true;
|
|
||||||
|
|
||||||
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||||
}
|
}
|
||||||
@@ -211,7 +210,6 @@ Item {
|
|||||||
|
|
||||||
keyboardActive = false;
|
keyboardActive = false;
|
||||||
spotlightOpen = false;
|
spotlightOpen = false;
|
||||||
focusGrab.active = false;
|
|
||||||
ModalManager.closeModal(modalHandle);
|
ModalManager.closeModal(modalHandle);
|
||||||
|
|
||||||
closeCleanupTimer.start();
|
closeCleanupTimer.start();
|
||||||
@@ -262,7 +260,7 @@ Item {
|
|||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
id: focusGrab
|
||||||
windows: [launcherWindow]
|
windows: [launcherWindow]
|
||||||
active: false
|
active: root.useHyprlandFocusGrab && root.keyboardActive
|
||||||
|
|
||||||
onCleared: {
|
onCleared: {
|
||||||
if (spotlightOpen) {
|
if (spotlightOpen) {
|
||||||
@@ -306,8 +304,9 @@ Item {
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: clickCatcher
|
id: clickCatcher
|
||||||
screen: launcherWindow.screen
|
screen: launcherWindow.screen
|
||||||
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken
|
visible: (spotlightOpen || isClosing) && !root.useSingleWindow
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
updatesEnabled: false
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
||||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||||
@@ -373,24 +372,24 @@ Item {
|
|||||||
WlrLayershell.namespace: "dms:spotlight"
|
WlrLayershell.namespace: "dms:spotlight"
|
||||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
|
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
left: true
|
left: true
|
||||||
right: root.useBackgroundDarken
|
right: root.useSingleWindow
|
||||||
bottom: root.useBackgroundDarken
|
bottom: root.useSingleWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.margins {
|
WlrLayershell.margins {
|
||||||
left: root.useBackgroundDarken ? 0 : root.windowX
|
left: root.useSingleWindow ? 0 : root.windowX
|
||||||
top: root.useBackgroundDarken ? 0 : root.windowY
|
top: root.useSingleWindow ? 0 : root.windowY
|
||||||
right: 0
|
right: 0
|
||||||
bottom: 0
|
bottom: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth
|
implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
|
||||||
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight
|
implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: launcherInputMask
|
item: launcherInputMask
|
||||||
@@ -400,15 +399,15 @@ Item {
|
|||||||
id: launcherInputMask
|
id: launcherInputMask
|
||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
x: root.useBackgroundDarken ? 0 : modalContainer.x
|
x: root.useSingleWindow ? 0 : modalContainer.x
|
||||||
y: root.useBackgroundDarken ? 0 : modalContainer.y
|
y: root.useSingleWindow ? 0 : modalContainer.y
|
||||||
width: root.useBackgroundDarken ? launcherWindow.width : modalContainer.width
|
width: root.useSingleWindow ? launcherWindow.width : modalContainer.width
|
||||||
height: root.useBackgroundDarken ? launcherWindow.height : modalContainer.height
|
height: root.useSingleWindow ? launcherWindow.height : modalContainer.height
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.useBackgroundDarken && spotlightOpen
|
enabled: root.useSingleWindow && spotlightOpen
|
||||||
z: -2
|
z: -2
|
||||||
onClicked: root.hide()
|
onClicked: root.hide()
|
||||||
}
|
}
|
||||||
@@ -432,13 +431,23 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: modalContainer
|
||||||
x: root.useBackgroundDarken ? root.alignedX : root.contentX
|
x: root.useSingleWindow ? root.alignedX : root.contentX
|
||||||
y: root.useBackgroundDarken ? root.alignedY : root.contentY
|
y: root.useSingleWindow ? root.alignedY : root.contentY
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.alignedHeight
|
height: root.alignedHeight
|
||||||
visible: _renderActive
|
visible: _renderActive
|
||||||
z: 0
|
z: 0
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: spotlightOpen
|
||||||
|
hoverEnabled: false
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
onPressed: mouse.accepted = true
|
||||||
|
onClicked: mouse.accepted = true
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
property bool _renderActive: contentVisible
|
property bool _renderActive: contentVisible
|
||||||
property real publishedScale: contentVisible ? 1 : 0.96
|
property real publishedScale: contentVisible ? 1 : 0.96
|
||||||
property real publishedOpacity: contentVisible ? 1 : 0
|
property real publishedOpacity: contentVisible ? 1 : 0
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ DankModal {
|
|||||||
|
|
||||||
shouldBeVisible: false
|
shouldBeVisible: false
|
||||||
allowStacking: true
|
allowStacking: true
|
||||||
|
useOverlayLayer: true
|
||||||
modalWidth: 420
|
modalWidth: 420
|
||||||
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 200
|
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 200
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ DankModal {
|
|||||||
closeOnEscapeKey: true
|
closeOnEscapeKey: true
|
||||||
closeOnBackgroundClick: true
|
closeOnBackgroundClick: true
|
||||||
allowStacking: true
|
allowStacking: true
|
||||||
|
useOverlayLayer: true
|
||||||
keepPopoutsOpen: true
|
keepPopoutsOpen: true
|
||||||
|
|
||||||
onBackgroundClicked: close()
|
onBackgroundClicked: close()
|
||||||
|
|||||||
@@ -320,8 +320,6 @@ Item {
|
|||||||
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings";
|
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings";
|
||||||
else if (CompositorService.isHyprland)
|
else if (CompositorService.isHyprland)
|
||||||
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-1";
|
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-1";
|
||||||
else if (CompositorService.isDwl)
|
|
||||||
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2";
|
|
||||||
else if (CompositorService.isMango)
|
else if (CompositorService.isMango)
|
||||||
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2";
|
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2";
|
||||||
Qt.openUrlExternally(url);
|
Qt.openUrlExternally(url);
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ Item {
|
|||||||
title: I18n.tr("Multi-Monitor", "greeter feature card title")
|
title: I18n.tr("Multi-Monitor", "greeter feature card title")
|
||||||
description: I18n.tr("Per-screen config", "greeter feature card description")
|
description: I18n.tr("Per-screen config", "greeter feature card description")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const hasDisplayConfig = CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango;
|
const hasDisplayConfig = CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango;
|
||||||
PopoutService.openSettingsWithTab(hasDisplayConfig ? "display_config" : "display_widgets");
|
PopoutService.openSettingsWithTab(hasDisplayConfig ? "display_config" : "display_widgets");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import QtQml
|
import QtQml
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell.Hyprland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -14,8 +13,8 @@ DankModal {
|
|||||||
useOverlayLayer: true
|
useOverlayLayer: true
|
||||||
property real scrollStep: 60
|
property real scrollStep: 60
|
||||||
property var activeFlickable: null
|
property var activeFlickable: null
|
||||||
property real _maxW: Math.min(Screen.width * 0.92, 1200)
|
property real _maxW: Math.min(root.screenWidth * 0.92, 1200)
|
||||||
property real _maxH: Math.min(Screen.height * 0.92, 900)
|
property real _maxH: Math.min(root.screenHeight * 0.92, 900)
|
||||||
modalWidth: _maxW
|
modalWidth: _maxW
|
||||||
modalHeight: _maxH
|
modalHeight: _maxH
|
||||||
onBackgroundClicked: close()
|
onBackgroundClicked: close()
|
||||||
@@ -29,11 +28,6 @@ DankModal {
|
|||||||
KeybindsService.loadCheatsheet();
|
KeybindsService.loadCheatsheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
|
||||||
windows: [root.contentWindow]
|
|
||||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollDown() {
|
function scrollDown() {
|
||||||
if (!root.activeFlickable)
|
if (!root.activeFlickable)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell.Hyprland
|
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
@@ -45,12 +44,6 @@ DankModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
|
||||||
id: grab
|
|
||||||
windows: [muxModal.contentWindow]
|
|
||||||
active: CompositorService.isHyprland && muxModal.shouldHaveFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (shouldBeVisible) {
|
if (shouldBeVisible) {
|
||||||
hide();
|
hide();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Hyprland
|
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
@@ -11,11 +10,6 @@ DankModal {
|
|||||||
|
|
||||||
layerNamespace: "dms:notification-center-modal"
|
layerNamespace: "dms:notification-center-modal"
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
|
||||||
windows: [notificationModal.contentWindow]
|
|
||||||
active: notificationModal.useHyprlandFocusGrab && notificationModal.shouldHaveFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool notificationModalOpen: false
|
property bool notificationModalOpen: false
|
||||||
property var notificationListRef: null
|
property var notificationListRef: null
|
||||||
property var historyListRef: null
|
property var historyListRef: null
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Hyprland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -13,11 +12,6 @@ DankModal {
|
|||||||
layerNamespace: "dms:power-menu"
|
layerNamespace: "dms:power-menu"
|
||||||
keepPopoutsOpen: true
|
keepPopoutsOpen: true
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
|
||||||
windows: [root.contentWindow]
|
|
||||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
property int selectedIndex: 0
|
property int selectedIndex: 0
|
||||||
property int selectedRow: 0
|
property int selectedRow: 0
|
||||||
property int selectedCol: 0
|
property int selectedCol: 0
|
||||||
@@ -352,9 +346,11 @@ DankModal {
|
|||||||
break;
|
break;
|
||||||
case Qt.Key_P:
|
case Qt.Key_P:
|
||||||
if (!(event.modifiers & Qt.ControlModifier)) {
|
if (!(event.modifiers & Qt.ControlModifier)) {
|
||||||
const idx = visibleActions.indexOf("poweroff");
|
if (visibleActions.includes("poweroff")) {
|
||||||
startHold("poweroff", idx);
|
const idx = visibleActions.indexOf("poweroff");
|
||||||
event.accepted = true;
|
startHold("poweroff", idx);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
|
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
@@ -373,28 +369,40 @@ DankModal {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_R:
|
case Qt.Key_R:
|
||||||
startHold("reboot", visibleActions.indexOf("reboot"));
|
if (visibleActions.includes("reboot")) {
|
||||||
event.accepted = true;
|
startHold("reboot", visibleActions.indexOf("reboot"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_X:
|
case Qt.Key_X:
|
||||||
startHold("logout", visibleActions.indexOf("logout"));
|
if (visibleActions.includes("logout")) {
|
||||||
event.accepted = true;
|
startHold("logout", visibleActions.indexOf("logout"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_L:
|
case Qt.Key_L:
|
||||||
startHold("lock", visibleActions.indexOf("lock"));
|
if (visibleActions.includes("lock")) {
|
||||||
event.accepted = true;
|
startHold("lock", visibleActions.indexOf("lock"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_S:
|
case Qt.Key_S:
|
||||||
startHold("suspend", visibleActions.indexOf("suspend"));
|
if (visibleActions.includes("suspend")) {
|
||||||
event.accepted = true;
|
startHold("suspend", visibleActions.indexOf("suspend"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_H:
|
case Qt.Key_H:
|
||||||
startHold("hibernate", visibleActions.indexOf("hibernate"));
|
if (visibleActions.includes("hibernate")) {
|
||||||
event.accepted = true;
|
startHold("hibernate", visibleActions.indexOf("hibernate"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_D:
|
case Qt.Key_D:
|
||||||
startHold("restart", visibleActions.indexOf("restart"));
|
if (visibleActions.includes("restart")) {
|
||||||
event.accepted = true;
|
startHold("restart", visibleActions.indexOf("restart"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,9 +453,11 @@ DankModal {
|
|||||||
break;
|
break;
|
||||||
case Qt.Key_P:
|
case Qt.Key_P:
|
||||||
if (!(event.modifiers & Qt.ControlModifier)) {
|
if (!(event.modifiers & Qt.ControlModifier)) {
|
||||||
const idx = visibleActions.indexOf("poweroff");
|
if (visibleActions.includes("poweroff")) {
|
||||||
startHold("poweroff", idx);
|
const idx = visibleActions.indexOf("poweroff");
|
||||||
event.accepted = true;
|
startHold("poweroff", idx);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
|
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
|
||||||
selectedIndex = selectedRow * gridColumns + selectedCol;
|
selectedIndex = selectedRow * gridColumns + selectedCol;
|
||||||
@@ -469,28 +479,40 @@ DankModal {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_R:
|
case Qt.Key_R:
|
||||||
startHold("reboot", visibleActions.indexOf("reboot"));
|
if (visibleActions.includes("reboot")) {
|
||||||
event.accepted = true;
|
startHold("reboot", visibleActions.indexOf("reboot"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_X:
|
case Qt.Key_X:
|
||||||
startHold("logout", visibleActions.indexOf("logout"));
|
if (visibleActions.includes("logout")) {
|
||||||
event.accepted = true;
|
startHold("logout", visibleActions.indexOf("logout"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_L:
|
case Qt.Key_L:
|
||||||
startHold("lock", visibleActions.indexOf("lock"));
|
if (visibleActions.includes("lock")) {
|
||||||
event.accepted = true;
|
startHold("lock", visibleActions.indexOf("lock"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_S:
|
case Qt.Key_S:
|
||||||
startHold("suspend", visibleActions.indexOf("suspend"));
|
if (visibleActions.includes("suspend")) {
|
||||||
event.accepted = true;
|
startHold("suspend", visibleActions.indexOf("suspend"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_H:
|
case Qt.Key_H:
|
||||||
startHold("hibernate", visibleActions.indexOf("hibernate"));
|
if (visibleActions.includes("hibernate")) {
|
||||||
event.accepted = true;
|
startHold("hibernate", visibleActions.indexOf("hibernate"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_D:
|
case Qt.Key_D:
|
||||||
startHold("restart", visibleActions.indexOf("restart"));
|
if (visibleActions.includes("restart")) {
|
||||||
event.accepted = true;
|
startHold("restart", visibleActions.indexOf("restart"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modules.Settings
|
import qs.Modules.Settings
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
id: root
|
id: root
|
||||||
@@ -105,6 +106,61 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: compositorLayoutLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 37
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: CompositorLayoutTab {}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item)
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: windowRulesLoader
|
||||||
|
|
||||||
|
property bool loadedOnce: false
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 38 || loadedOnce
|
||||||
|
visible: root.currentIndex === 38 && status === Loader.Ready
|
||||||
|
focus: visible
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
sourceComponent: WindowRulesTab {
|
||||||
|
pageActive: root.currentIndex === 38
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoaded: loadedOnce = true
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSpinner {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: root.currentIndex === 38 && windowRulesLoader.status === Loader.Loading
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: dankBarAppearanceLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 6
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: DankBarAppearanceTab {
|
||||||
|
parentModal: root.parentModal
|
||||||
|
}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item)
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: dockLoader
|
id: dockLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -370,7 +426,7 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: defaultAppsLoader
|
id: defaultAppsLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentIndex === 34
|
active: root.currentIndex === 34
|
||||||
@@ -432,19 +488,33 @@ FocusScope {
|
|||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: widgetsLoader
|
id: widgetsLoader
|
||||||
|
|
||||||
|
property bool loadedOnce: false
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentIndex === 22
|
active: root.currentIndex === 22 || loadedOnce
|
||||||
visible: active
|
visible: root.currentIndex === 22 && status === Loader.Ready
|
||||||
focus: active
|
focus: visible
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: WidgetsTab {
|
sourceComponent: WidgetsTab {
|
||||||
parentModal: root.parentModal
|
parentModal: root.parentModal
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveChanged: {
|
onLoaded: {
|
||||||
if (active && item)
|
loadedOnce = true;
|
||||||
|
if (visible && item)
|
||||||
Qt.callLater(() => item.forceActiveFocus());
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
}
|
}
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible && item)
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSpinner {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: root.currentIndex === 22 && widgetsLoader.status === Loader.Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -479,23 +549,6 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: windowRulesLoader
|
|
||||||
anchors.fill: parent
|
|
||||||
active: root.currentIndex === 28
|
|
||||||
visible: active
|
|
||||||
focus: active
|
|
||||||
|
|
||||||
sourceComponent: WindowRulesTab {
|
|
||||||
parentModal: root.parentModal
|
|
||||||
}
|
|
||||||
|
|
||||||
onActiveChanged: {
|
|
||||||
if (active && item)
|
|
||||||
Qt.callLater(() => item.forceActiveFocus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: audioLoader
|
id: audioLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ Rectangle {
|
|||||||
property bool searchActive: searchField.text.length > 0
|
property bool searchActive: searchField.text.length > 0
|
||||||
property int searchSelectedIndex: 0
|
property int searchSelectedIndex: 0
|
||||||
property int keyboardHighlightIndex: -1
|
property int keyboardHighlightIndex: -1
|
||||||
|
readonly property int navigationStateDuration: Theme.currentAnimationSpeed === SettingsData.AnimationSpeed.None ? 0 : Anims.settingsNavigationStateDuration
|
||||||
|
|
||||||
function focusSearch() {
|
function focusSearch() {
|
||||||
searchField.forceActiveFocus();
|
searchField.forceActiveFocus();
|
||||||
@@ -101,6 +102,13 @@ Rectangle {
|
|||||||
"icon": "volume_up",
|
"icon": "volume_up",
|
||||||
"tabIndex": 15,
|
"tabIndex": 15,
|
||||||
"soundsOnly": true
|
"soundsOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "compositor_layout",
|
||||||
|
"text": CompositorService.isNiri ? "niri" : (CompositorService.isHyprland ? "Hyprland" : "MangoWC"),
|
||||||
|
"icon": "crop_square",
|
||||||
|
"tabIndex": 37,
|
||||||
|
"layoutCapable": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -115,12 +123,24 @@ Rectangle {
|
|||||||
"icon": "tune",
|
"icon": "tune",
|
||||||
"tabIndex": 3
|
"tabIndex": 3
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "dankbar_appearance",
|
||||||
|
"text": I18n.tr("Appearance"),
|
||||||
|
"icon": "palette",
|
||||||
|
"tabIndex": 6
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "dankbar_widgets",
|
"id": "dankbar_widgets",
|
||||||
"text": I18n.tr("Widgets"),
|
"text": I18n.tr("Widgets"),
|
||||||
"icon": "widgets",
|
"icon": "widgets",
|
||||||
"tabIndex": 22
|
"tabIndex": 22
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "workspaces",
|
||||||
|
"text": I18n.tr("Workspaces"),
|
||||||
|
"icon": "view_module",
|
||||||
|
"tabIndex": 4
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "frame",
|
"id": "frame",
|
||||||
"text": I18n.tr("Frame"),
|
"text": I18n.tr("Frame"),
|
||||||
@@ -131,16 +151,10 @@ Rectangle {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "workspaces_widgets",
|
"id": "workspaces_widgets",
|
||||||
"text": I18n.tr("Workspaces & Widgets"),
|
"text": I18n.tr("Widgets & Notifications"),
|
||||||
"icon": "dashboard",
|
"icon": "dashboard",
|
||||||
"collapsedByDefault": true,
|
"collapsedByDefault": true,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
|
||||||
"id": "workspaces",
|
|
||||||
"text": I18n.tr("Workspaces"),
|
|
||||||
"icon": "view_module",
|
|
||||||
"tabIndex": 4
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "media_player",
|
"id": "media_player",
|
||||||
"text": I18n.tr("Media Player"),
|
"text": I18n.tr("Media Player"),
|
||||||
@@ -252,6 +266,13 @@ Rectangle {
|
|||||||
"icon": "line_start",
|
"icon": "line_start",
|
||||||
"tabIndex": 36,
|
"tabIndex": 36,
|
||||||
"autostartOnly": true
|
"autostartOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "window_rules",
|
||||||
|
"text": I18n.tr("Window Rules"),
|
||||||
|
"icon": "select_window",
|
||||||
|
"tabIndex": 38,
|
||||||
|
"windowRulesCapable": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -305,13 +326,6 @@ Rectangle {
|
|||||||
"text": I18n.tr("Users"),
|
"text": I18n.tr("Users"),
|
||||||
"icon": "manage_accounts",
|
"icon": "manage_accounts",
|
||||||
"tabIndex": 35
|
"tabIndex": 35
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "window_rules",
|
|
||||||
"text": I18n.tr("Window Rules"),
|
|
||||||
"icon": "select_window",
|
|
||||||
"tabIndex": 28,
|
|
||||||
"windowRulesCapable": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -372,6 +386,8 @@ Rectangle {
|
|||||||
return false;
|
return false;
|
||||||
if (item.windowRulesCapable && !CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
if (item.windowRulesCapable && !CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
||||||
return false;
|
return false;
|
||||||
|
if (item.layoutCapable && !CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
||||||
|
return false;
|
||||||
if (item.niriOnly && !CompositorService.isNiri)
|
if (item.niriOnly && !CompositorService.isNiri)
|
||||||
return false;
|
return false;
|
||||||
if (item.clipboardOnly && (!DMSService.isConnected || DMSService.apiVersion < 23))
|
if (item.clipboardOnly && (!DMSService.isConnected || DMSService.apiVersion < 23))
|
||||||
@@ -544,6 +560,8 @@ Rectangle {
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
var normalized = name.toLowerCase().replace(/[_\-\s]/g, "");
|
var normalized = name.toLowerCase().replace(/[_\-\s]/g, "");
|
||||||
|
if (normalized === "compositor")
|
||||||
|
normalized = "workspaces";
|
||||||
|
|
||||||
for (var i = 0; i < categoryStructure.length; i++) {
|
for (var i = 0; i < categoryStructure.length; i++) {
|
||||||
var cat = categoryStructure[i];
|
var cat = categoryStructure[i];
|
||||||
@@ -588,7 +606,7 @@ Rectangle {
|
|||||||
id: __m1
|
id: __m1
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
text: I18n.tr("Workspaces & Widgets")
|
text: I18n.tr("Widgets & Notifications")
|
||||||
}
|
}
|
||||||
StyledTextMetrics {
|
StyledTextMetrics {
|
||||||
id: __m2
|
id: __m2
|
||||||
@@ -782,6 +800,7 @@ Rectangle {
|
|||||||
id: resultRipple
|
id: resultRipple
|
||||||
rippleColor: root.searchSelectedIndex === resultDelegate.index ? Theme.buttonText : Theme.surfaceText
|
rippleColor: root.searchSelectedIndex === resultDelegate.index ? Theme.buttonText : Theme.surfaceText
|
||||||
cornerRadius: resultDelegate.radius
|
cornerRadius: resultDelegate.radius
|
||||||
|
animationDuration: Anims.settingsNavigationRippleDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -837,8 +856,9 @@ Rectangle {
|
|||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Theme.shortDuration
|
duration: root.navigationStateDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.expressiveEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -912,6 +932,7 @@ Rectangle {
|
|||||||
id: categoryRipple
|
id: categoryRipple
|
||||||
rippleColor: categoryRow.isActive ? Theme.buttonText : Theme.surfaceText
|
rippleColor: categoryRow.isActive ? Theme.buttonText : Theme.surfaceText
|
||||||
cornerRadius: categoryRow.radius
|
cornerRadius: categoryRow.radius
|
||||||
|
animationDuration: Anims.settingsNavigationRippleDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -967,8 +988,9 @@ Rectangle {
|
|||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Theme.shortDuration
|
duration: root.navigationStateDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.expressiveEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1009,6 +1031,7 @@ Rectangle {
|
|||||||
id: childRipple
|
id: childRipple
|
||||||
rippleColor: childDelegate.isActive ? Theme.buttonText : Theme.surfaceText
|
rippleColor: childDelegate.isActive ? Theme.buttonText : Theme.surfaceText
|
||||||
cornerRadius: childDelegate.radius
|
cornerRadius: childDelegate.radius
|
||||||
|
animationDuration: Anims.settingsNavigationRippleDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -1049,8 +1072,9 @@ Rectangle {
|
|||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Theme.shortDuration
|
duration: root.navigationStateDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.expressiveEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ PluginComponent {
|
|||||||
service: CupsService
|
service: CupsService
|
||||||
}
|
}
|
||||||
|
|
||||||
ccWidgetIcon: CupsService.cupsAvailable && CupsService.getPrintersNum() > 0 ? "print" : "print_disabled"
|
ccWidgetIcon: "print"
|
||||||
ccWidgetPrimaryText: I18n.tr("Printers")
|
ccWidgetPrimaryText: I18n.tr("Printers")
|
||||||
ccWidgetSecondaryText: {
|
ccWidgetSecondaryText: {
|
||||||
if (CupsService.cupsAvailable && CupsService.getPrintersNum() > 0) {
|
if (CupsService.cupsAvailable && CupsService.getPrintersNum() > 0) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ PluginComponent {
|
|||||||
service: DMSNetworkService
|
service: DMSNetworkService
|
||||||
}
|
}
|
||||||
|
|
||||||
ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
|
ccWidgetIcon: "vpn_key"
|
||||||
ccWidgetPrimaryText: I18n.tr("VPN")
|
ccWidgetPrimaryText: I18n.tr("VPN")
|
||||||
ccWidgetSecondaryText: {
|
ccWidgetSecondaryText: {
|
||||||
if (!DMSNetworkService.connected)
|
if (!DMSNetworkService.connected)
|
||||||
|
|||||||
@@ -102,6 +102,120 @@ Column {
|
|||||||
item.z = 1000;
|
item.z = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCompoundPillIconBlinking(id) {
|
||||||
|
if (id === "wifi") return NetworkService.isWifiConnecting;
|
||||||
|
if (id === "bluetooth") return BluetoothService.connecting;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCompoundPillIconName(id, widgetDef) {
|
||||||
|
switch (id) {
|
||||||
|
case "wifi": {
|
||||||
|
if (NetworkService.wifiToggling) return "sync";
|
||||||
|
if (NetworkService.isConnecting && !NetworkService.ethernetConnected) return NetworkService.wifiSignalIcon;
|
||||||
|
const status = NetworkService.networkStatus;
|
||||||
|
if (status === "ethernet") return "settings_ethernet";
|
||||||
|
if (status === "vpn") return NetworkService.ethernetConnected ? "settings_ethernet" : NetworkService.wifiSignalIcon;
|
||||||
|
if (status === "wifi") return NetworkService.wifiSignalIcon;
|
||||||
|
return "wifi";
|
||||||
|
}
|
||||||
|
case "bluetooth": {
|
||||||
|
return "bluetooth";
|
||||||
|
}
|
||||||
|
case "audioOutput": {
|
||||||
|
if (!AudioService.sink?.audio) return "volume_off";
|
||||||
|
let volume = AudioService.sink.audio.volume;
|
||||||
|
let muted = AudioService.sink.audio.muted;
|
||||||
|
if (muted) return "volume_off";
|
||||||
|
if (volume === 0.0) return "volume_mute";
|
||||||
|
if (volume <= 0.33) return "volume_down";
|
||||||
|
if (volume <= 0.66) return "volume_up";
|
||||||
|
return "volume_up";
|
||||||
|
}
|
||||||
|
case "audioInput": {
|
||||||
|
if (!AudioService.source?.audio) return "mic_off";
|
||||||
|
return AudioService.source.audio.muted ? "mic_off" : "mic";
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return widgetDef?.icon || "help";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCompoundPillIsActive(id) {
|
||||||
|
switch (id) {
|
||||||
|
case "wifi": {
|
||||||
|
if (NetworkService.wifiToggling) return false;
|
||||||
|
const status = NetworkService.networkStatus;
|
||||||
|
if (status === "ethernet") return true;
|
||||||
|
if (status === "vpn") return NetworkService.ethernetConnected || NetworkService.wifiConnected;
|
||||||
|
if (status === "wifi") return true;
|
||||||
|
return NetworkService.wifiEnabled;
|
||||||
|
}
|
||||||
|
case "bluetooth":
|
||||||
|
return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled);
|
||||||
|
case "audioOutput":
|
||||||
|
return !!(AudioService.sink?.audio && !AudioService.sink.audio.muted);
|
||||||
|
case "audioInput":
|
||||||
|
return !!(AudioService.source?.audio && !AudioService.source.audio.muted);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCompoundPillToggled(id) {
|
||||||
|
switch (id) {
|
||||||
|
case "wifi": {
|
||||||
|
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
|
||||||
|
NetworkService.toggleWifiRadio();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bluetooth": {
|
||||||
|
if (BluetoothService.available && BluetoothService.adapter) {
|
||||||
|
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "audioOutput": {
|
||||||
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "audioInput": {
|
||||||
|
if (AudioService.source && AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = !AudioService.source.audio.muted;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCompoundPillWheelEvent(id, wheelEvent) {
|
||||||
|
if (id === "audioOutput") {
|
||||||
|
if (!AudioService.sink || !AudioService.sink.audio) return;
|
||||||
|
let delta = wheelEvent.angleDelta.y;
|
||||||
|
let maxVol = AudioService.sinkMaxVolume;
|
||||||
|
let currentVolume = AudioService.sink.audio.volume * 100;
|
||||||
|
let newVolume;
|
||||||
|
if (delta > 0) newVolume = Math.min(maxVol, currentVolume + 5);
|
||||||
|
else newVolume = Math.max(0, currentVolume - 5);
|
||||||
|
AudioService.sink.audio.muted = false;
|
||||||
|
AudioService.sink.audio.volume = newVolume / 100;
|
||||||
|
wheelEvent.accepted = true;
|
||||||
|
} else if (id === "audioInput") {
|
||||||
|
if (!AudioService.source || !AudioService.source.audio) return;
|
||||||
|
let delta = wheelEvent.angleDelta.y;
|
||||||
|
let currentVolume = AudioService.source.audio.volume * 100;
|
||||||
|
let newVolume;
|
||||||
|
if (delta > 0) newVolume = Math.min(100, currentVolume + 5);
|
||||||
|
else newVolume = Math.max(0, currentVolume - 5);
|
||||||
|
AudioService.source.audio.muted = false;
|
||||||
|
AudioService.source.audio.volume = newVolume / 100;
|
||||||
|
wheelEvent.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function componentForWidget(widgetData) {
|
function componentForWidget(widgetData) {
|
||||||
const id = widgetData.id || "";
|
const id = widgetData.id || "";
|
||||||
const widgetWidth = widgetData.width || 50;
|
const widgetWidth = widgetData.width || 50;
|
||||||
@@ -114,7 +228,7 @@ Column {
|
|||||||
case "bluetooth":
|
case "bluetooth":
|
||||||
case "audioOutput":
|
case "audioOutput":
|
||||||
case "audioInput":
|
case "audioInput":
|
||||||
return compoundPillComponent;
|
return widgetWidth <= 25 ? smallCompoundComponent : compoundPillComponent;
|
||||||
case "volumeSlider":
|
case "volumeSlider":
|
||||||
return audioSliderComponent;
|
return audioSliderComponent;
|
||||||
case "brightnessSlider":
|
case "brightnessSlider":
|
||||||
@@ -126,7 +240,7 @@ Column {
|
|||||||
case "diskUsage":
|
case "diskUsage":
|
||||||
return widgetWidth <= 25 ? smallDiskUsageComponent : diskUsagePillComponent;
|
return widgetWidth <= 25 ? smallDiskUsageComponent : diskUsagePillComponent;
|
||||||
case "colorPicker":
|
case "colorPicker":
|
||||||
return colorPickerPillComponent;
|
return widgetWidth <= 25 ? smallColorPickerComponent : colorPickerPillComponent;
|
||||||
case "doNotDisturb":
|
case "doNotDisturb":
|
||||||
return widgetWidth <= 25 ? smallToggleComponent : dndPillComponent;
|
return widgetWidth <= 25 ? smallToggleComponent : dndPillComponent;
|
||||||
default:
|
default:
|
||||||
@@ -329,69 +443,8 @@ Column {
|
|||||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 60
|
height: 60
|
||||||
iconBlinking: {
|
iconBlinking: root.getCompoundPillIconBlinking(widgetData.id || "")
|
||||||
const id = widgetData.id || "";
|
iconName: root.getCompoundPillIconName(widgetData.id || "", widgetDef)
|
||||||
if (id === "wifi")
|
|
||||||
return NetworkService.isWifiConnecting;
|
|
||||||
if (id === "bluetooth")
|
|
||||||
return BluetoothService.connecting;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
iconName: {
|
|
||||||
switch (widgetData.id || "") {
|
|
||||||
case "wifi":
|
|
||||||
{
|
|
||||||
if (NetworkService.wifiToggling)
|
|
||||||
return "sync";
|
|
||||||
if (NetworkService.isConnecting && !NetworkService.ethernetConnected)
|
|
||||||
return NetworkService.wifiSignalIcon;
|
|
||||||
|
|
||||||
const status = NetworkService.networkStatus;
|
|
||||||
if (status === "ethernet")
|
|
||||||
return "settings_ethernet";
|
|
||||||
if (status === "vpn")
|
|
||||||
return NetworkService.ethernetConnected ? "settings_ethernet" : NetworkService.wifiSignalIcon;
|
|
||||||
if (status === "wifi")
|
|
||||||
return NetworkService.wifiSignalIcon;
|
|
||||||
if (NetworkService.wifiEnabled)
|
|
||||||
return "wifi_off";
|
|
||||||
return "wifi_off";
|
|
||||||
}
|
|
||||||
case "bluetooth":
|
|
||||||
{
|
|
||||||
if (!BluetoothService.available)
|
|
||||||
return "bluetooth_disabled";
|
|
||||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
|
|
||||||
return "bluetooth_disabled";
|
|
||||||
return "bluetooth";
|
|
||||||
}
|
|
||||||
case "audioOutput":
|
|
||||||
{
|
|
||||||
if (!AudioService.sink?.audio)
|
|
||||||
return "volume_off";
|
|
||||||
let volume = AudioService.sink.audio.volume;
|
|
||||||
let muted = AudioService.sink.audio.muted;
|
|
||||||
if (muted)
|
|
||||||
return "volume_off";
|
|
||||||
if (volume === 0.0)
|
|
||||||
return "volume_mute";
|
|
||||||
if (volume <= 0.33)
|
|
||||||
return "volume_down";
|
|
||||||
if (volume <= 0.66)
|
|
||||||
return "volume_up";
|
|
||||||
return "volume_up";
|
|
||||||
}
|
|
||||||
case "audioInput":
|
|
||||||
{
|
|
||||||
if (!AudioService.source?.audio)
|
|
||||||
return "mic_off";
|
|
||||||
let muted = AudioService.source.audio.muted;
|
|
||||||
return muted ? "mic_off" : "mic";
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return widgetDef?.icon || "help";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
primaryText: {
|
primaryText: {
|
||||||
switch (widgetData.id || "") {
|
switch (widgetData.id || "") {
|
||||||
case "wifi":
|
case "wifi":
|
||||||
@@ -506,66 +559,12 @@ Column {
|
|||||||
return widgetDef?.description || "";
|
return widgetDef?.description || "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isActive: {
|
isActive: root.getCompoundPillIsActive(widgetData.id || "")
|
||||||
switch (widgetData.id || "") {
|
|
||||||
case "wifi":
|
|
||||||
{
|
|
||||||
if (NetworkService.wifiToggling)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const status = NetworkService.networkStatus;
|
|
||||||
if (status === "ethernet")
|
|
||||||
return true;
|
|
||||||
if (status === "vpn")
|
|
||||||
return NetworkService.ethernetConnected || NetworkService.wifiConnected;
|
|
||||||
if (status === "wifi")
|
|
||||||
return true;
|
|
||||||
return NetworkService.wifiEnabled;
|
|
||||||
}
|
|
||||||
case "bluetooth":
|
|
||||||
return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled);
|
|
||||||
case "audioOutput":
|
|
||||||
return !!(AudioService.sink?.audio && !AudioService.sink.audio.muted);
|
|
||||||
case "audioInput":
|
|
||||||
return !!(AudioService.source?.audio && !AudioService.source.audio.muted);
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enabled: widgetDef?.enabled ?? true
|
enabled: widgetDef?.enabled ?? true
|
||||||
onToggled: {
|
onToggled: {
|
||||||
if (root.editMode)
|
if (root.editMode)
|
||||||
return;
|
return;
|
||||||
switch (widgetData.id || "") {
|
root.handleCompoundPillToggled(widgetData.id || "");
|
||||||
case "wifi":
|
|
||||||
{
|
|
||||||
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
|
|
||||||
NetworkService.toggleWifiRadio();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "bluetooth":
|
|
||||||
{
|
|
||||||
if (BluetoothService.available && BluetoothService.adapter) {
|
|
||||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "audioOutput":
|
|
||||||
{
|
|
||||||
if (AudioService.sink && AudioService.sink.audio) {
|
|
||||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "audioInput":
|
|
||||||
{
|
|
||||||
if (AudioService.source && AudioService.source.audio) {
|
|
||||||
AudioService.source.audio.muted = !AudioService.source.audio.muted;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onExpandClicked: {
|
onExpandClicked: {
|
||||||
if (root.editMode)
|
if (root.editMode)
|
||||||
@@ -575,35 +574,7 @@ Column {
|
|||||||
onWheelEvent: function (wheelEvent) {
|
onWheelEvent: function (wheelEvent) {
|
||||||
if (root.editMode)
|
if (root.editMode)
|
||||||
return;
|
return;
|
||||||
const id = widgetData.id || "";
|
root.handleCompoundPillWheelEvent(widgetData.id || "", wheelEvent);
|
||||||
if (id === "audioOutput") {
|
|
||||||
if (!AudioService.sink || !AudioService.sink.audio)
|
|
||||||
return;
|
|
||||||
let delta = wheelEvent.angleDelta.y;
|
|
||||||
let maxVol = AudioService.sinkMaxVolume;
|
|
||||||
let currentVolume = AudioService.sink.audio.volume * 100;
|
|
||||||
let newVolume;
|
|
||||||
if (delta > 0)
|
|
||||||
newVolume = Math.min(maxVol, currentVolume + 5);
|
|
||||||
else
|
|
||||||
newVolume = Math.max(0, currentVolume - 5);
|
|
||||||
AudioService.sink.audio.muted = false;
|
|
||||||
AudioService.sink.audio.volume = newVolume / 100;
|
|
||||||
wheelEvent.accepted = true;
|
|
||||||
} else if (id === "audioInput") {
|
|
||||||
if (!AudioService.source || !AudioService.source.audio)
|
|
||||||
return;
|
|
||||||
let delta = wheelEvent.angleDelta.y;
|
|
||||||
let currentVolume = AudioService.source.audio.volume * 100;
|
|
||||||
let newVolume;
|
|
||||||
if (delta > 0)
|
|
||||||
newVolume = Math.min(100, currentVolume + 5);
|
|
||||||
else
|
|
||||||
newVolume = Math.max(0, currentVolume - 5);
|
|
||||||
AudioService.source.audio.muted = false;
|
|
||||||
AudioService.source.audio.volume = newVolume / 100;
|
|
||||||
wheelEvent.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -736,7 +707,7 @@ Column {
|
|||||||
case "darkMode":
|
case "darkMode":
|
||||||
return "contrast";
|
return "contrast";
|
||||||
case "idleInhibitor":
|
case "idleInhibitor":
|
||||||
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle";
|
return "motion_sensor_active";
|
||||||
default:
|
default:
|
||||||
return "help";
|
return "help";
|
||||||
}
|
}
|
||||||
@@ -821,9 +792,9 @@ Column {
|
|||||||
case "darkMode":
|
case "darkMode":
|
||||||
return "contrast";
|
return "contrast";
|
||||||
case "doNotDisturb":
|
case "doNotDisturb":
|
||||||
return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off";
|
return "do_not_disturb_on";
|
||||||
case "idleInhibitor":
|
case "idleInhibitor":
|
||||||
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle";
|
return "motion_sensor_active";
|
||||||
default:
|
default:
|
||||||
return "help";
|
return "help";
|
||||||
}
|
}
|
||||||
@@ -1223,4 +1194,47 @@ Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: smallCompoundComponent
|
||||||
|
SmallCompoundButton {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
iconBlinking: root.getCompoundPillIconBlinking(widgetData.id || "")
|
||||||
|
iconName: root.getCompoundPillIconName(widgetData.id || "", widgetDef)
|
||||||
|
isActive: root.getCompoundPillIsActive(widgetData.id || "")
|
||||||
|
enabled: (widgetDef?.enabled ?? true) && !root.editMode
|
||||||
|
onToggled: {
|
||||||
|
if (root.editMode) return;
|
||||||
|
root.handleCompoundPillToggled(widgetData.id || "");
|
||||||
|
}
|
||||||
|
onExpandClicked: {
|
||||||
|
if (root.editMode) return;
|
||||||
|
root.expandClicked(widgetData, widgetIndex);
|
||||||
|
}
|
||||||
|
onWheelEvent: function(wheelEvent) {
|
||||||
|
if (root.editMode) return;
|
||||||
|
root.handleCompoundPillWheelEvent(widgetData.id || "", wheelEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: smallColorPickerComponent
|
||||||
|
SmallColorPickerButton {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
colorPickerModal: root.colorPickerModal
|
||||||
|
onClicked: {
|
||||||
|
if (root.editMode) return;
|
||||||
|
if (root.colorPickerModal)
|
||||||
|
root.colorPickerModal.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Typography {
|
Typography {
|
||||||
text: DgopService.uptime ? I18n.tr("up") + " " + DgopService.uptime.slice(3) : I18n.tr("Unknown")
|
text: DgopService.uptime ? I18n.tr("up", "uptime prefix, e.g. 'up 4h 2m'") + " " + DgopService.uptime.slice(3) : I18n.tr("Unknown")
|
||||||
style: Typography.Style.Caption
|
style: Typography.Style.Caption
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,15 +109,7 @@ DankPopout {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
customKeyboardFocus: {
|
customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null
|
||||||
if (!shouldBeVisible)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (anyModalOpen)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (CompositorService.useHyprlandFocusGrab)
|
|
||||||
return WlrKeyboardFocus.OnDemand;
|
|
||||||
return WlrKeyboardFocus.Exclusive;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBackgroundClicked: close()
|
onBackgroundClicked: close()
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import qs.Modules.ControlCenter.Widgets
|
|||||||
CompoundPill {
|
CompoundPill {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
iconName: SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
|
iconName: "do_not_disturb_on"
|
||||||
iconColor: SessionData.doNotDisturb ? Theme.primary : Theme.surfaceText
|
iconColor: SessionData.doNotDisturb ? Theme.primary : Theme.surfaceText
|
||||||
primaryText: I18n.tr("Do Not Disturb")
|
primaryText: I18n.tr("Do Not Disturb")
|
||||||
isActive: SessionData.doNotDisturb
|
isActive: SessionData.doNotDisturb
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
property var colorPickerModal: null
|
||||||
|
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
|
||||||
|
height: 48
|
||||||
|
radius: Theme.cornerRadius === 0 ? 0 : Theme.cornerRadius
|
||||||
|
|
||||||
|
function hoverTint(base) {
|
||||||
|
const factor = 1.2;
|
||||||
|
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
color: Theme.primary
|
||||||
|
border.color: Theme.ccTileRing
|
||||||
|
border.width: 1
|
||||||
|
antialiasing: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: parent.radius
|
||||||
|
color: hoverTint(root.color)
|
||||||
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
|
visible: opacity > 0
|
||||||
|
antialiasing: true
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "palette"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primaryText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankRipple {
|
||||||
|
id: ripple
|
||||||
|
cornerRadius: root.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: root.enabled
|
||||||
|
onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
|
||||||
|
onClicked: root.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
property string iconName: ""
|
||||||
|
property bool isActive: false
|
||||||
|
property bool iconBlinking: false
|
||||||
|
|
||||||
|
// Left click expands the widget (primary detail action), right click toggles on/off.
|
||||||
|
signal toggled
|
||||||
|
signal expandClicked
|
||||||
|
signal wheelEvent(var wheelEvent)
|
||||||
|
|
||||||
|
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
|
||||||
|
height: 48
|
||||||
|
radius: {
|
||||||
|
if (Theme.cornerRadius === 0)
|
||||||
|
return 0;
|
||||||
|
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hoverTint(base) {
|
||||||
|
const factor = 1.2;
|
||||||
|
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||||
|
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
||||||
|
readonly property color _tileRingActive: Theme.ccTileRing
|
||||||
|
readonly property color _tileIconActive: Theme.ccTileActiveText
|
||||||
|
readonly property color _tileIconInactive: Theme.ccTileInactiveIcon
|
||||||
|
|
||||||
|
color: {
|
||||||
|
if (isActive)
|
||||||
|
return _tileBgActive;
|
||||||
|
const baseColor = mouseArea.containsMouse ? Theme.ccPillInactiveHoverBg : _tileBgInactive;
|
||||||
|
return baseColor;
|
||||||
|
}
|
||||||
|
border.color: isActive ? _tileRingActive : Theme.outlineMedium
|
||||||
|
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
||||||
|
antialiasing: true
|
||||||
|
opacity: enabled ? 1.0 : 0.6
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: parent.radius
|
||||||
|
color: hoverTint(root.color)
|
||||||
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
|
visible: opacity > 0
|
||||||
|
antialiasing: true
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: tileIcon
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: iconName
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: isActive ? _tileIconActive : _tileIconInactive
|
||||||
|
|
||||||
|
DankBlink {
|
||||||
|
target: tileIcon
|
||||||
|
running: root.iconBlinking
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankRipple {
|
||||||
|
id: ripple
|
||||||
|
cornerRadius: root.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
enabled: root.enabled
|
||||||
|
onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
|
||||||
|
onClicked: mouse => {
|
||||||
|
if (mouse.button === Qt.RightButton)
|
||||||
|
root.toggled();
|
||||||
|
else
|
||||||
|
root.expandClicked();
|
||||||
|
}
|
||||||
|
onWheel: function (ev) {
|
||||||
|
root.wheelEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ Item {
|
|||||||
// M3 elevation shadow — Level 2 baseline (navigation bar), with per-bar override support
|
// M3 elevation shadow — Level 2 baseline (navigation bar), with per-bar override support
|
||||||
readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0
|
readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0
|
||||||
readonly property var elevLevel: Theme.elevationLevel2
|
readonly property var elevLevel: Theme.elevationLevel2
|
||||||
readonly property bool shadowEnabled: !BlurService.enabled && ((Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride)
|
readonly property bool shadowEnabled: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride
|
||||||
readonly property string autoBarShadowDirection: isTop ? "top" : (isBottom ? "bottom" : (isLeft ? "left" : (isRight ? "right" : "top")))
|
readonly property string autoBarShadowDirection: isTop ? "top" : (isBottom ? "bottom" : (isLeft ? "left" : (isRight ? "right" : "top")))
|
||||||
readonly property string globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
|
readonly property string globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
|
||||||
readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit"
|
readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit"
|
||||||
@@ -207,7 +207,6 @@ Item {
|
|||||||
shadowOffsetX: root.shadowOffsetX
|
shadowOffsetX: root.shadowOffsetX
|
||||||
shadowOffsetY: root.shadowOffsetY
|
shadowOffsetY: root.shadowOffsetY
|
||||||
shadowColor: root.shadowColor
|
shadowColor: root.shadowColor
|
||||||
blurMax: Theme.elevationBlurMax
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
|||||||
@@ -108,8 +108,6 @@ Item {
|
|||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||||
focusedScreenName = focusedWs?.monitor?.name || "";
|
focusedScreenName = focusedWs?.monitor?.name || "";
|
||||||
} else if (CompositorService.isDwl && DwlService.activeOutput) {
|
|
||||||
focusedScreenName = DwlService.activeOutput;
|
|
||||||
} else if (CompositorService.isMango && MangoService.activeOutput) {
|
} else if (CompositorService.isMango && MangoService.activeOutput) {
|
||||||
focusedScreenName = MangoService.activeOutput;
|
focusedScreenName = MangoService.activeOutput;
|
||||||
}
|
}
|
||||||
@@ -139,8 +137,6 @@ Item {
|
|||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||||
focusedScreenName = focusedWs?.monitor?.name || "";
|
focusedScreenName = focusedWs?.monitor?.name || "";
|
||||||
} else if (CompositorService.isDwl && DwlService.activeOutput) {
|
|
||||||
focusedScreenName = DwlService.activeOutput;
|
|
||||||
} else if (CompositorService.isMango && MangoService.activeOutput) {
|
} else if (CompositorService.isMango && MangoService.activeOutput) {
|
||||||
focusedScreenName = MangoService.activeOutput;
|
focusedScreenName = MangoService.activeOutput;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ Item {
|
|||||||
readonly property real _frameEdgeFloorInset: (SettingsData.frameEnabled && _usesFrameBarChrome) ? Math.max(0, SettingsData.frameThickness - _edgeBaseMargin) : 0
|
readonly property real _frameEdgeFloorInset: (SettingsData.frameEnabled && _usesFrameBarChrome) ? Math.max(0, SettingsData.frameThickness - _edgeBaseMargin) : 0
|
||||||
readonly property bool _barIsVertical: _hasBarWindow ? barWindow.isVertical : false
|
readonly property bool _barIsVertical: _hasBarWindow ? barWindow.isVertical : false
|
||||||
readonly property string _barScreenName: _hasBarWindow ? (barWindow.screenName || "") : ""
|
readonly property string _barScreenName: _hasBarWindow ? (barWindow.screenName || "") : ""
|
||||||
readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService
|
|
||||||
readonly property bool hasAdjacentTopBarLive: _hasBarWindow && barWindow.hasAdjacentTopBar
|
readonly property bool hasAdjacentTopBarLive: _hasBarWindow && barWindow.hasAdjacentTopBar
|
||||||
readonly property bool hasAdjacentBottomBarLive: _hasBarWindow && barWindow.hasAdjacentBottomBar
|
readonly property bool hasAdjacentBottomBarLive: _hasBarWindow && barWindow.hasAdjacentBottomBar
|
||||||
readonly property bool hasAdjacentLeftBarLive: _hasBarWindow && barWindow.hasAdjacentLeftBar
|
readonly property bool hasAdjacentLeftBarLive: _hasBarWindow && barWindow.hasAdjacentLeftBar
|
||||||
@@ -190,16 +189,16 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return monitorWorkspaces.sort((a, b) => a.id - b.id);
|
return monitorWorkspaces.sort((a, b) => a.id - b.id);
|
||||||
} else if (CompositorService.isDwl || CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
if (!dwlSvc.available) {
|
if (!MangoService.available) {
|
||||||
return [0];
|
return [0];
|
||||||
}
|
}
|
||||||
if (SettingsData.dwlShowAllTags) {
|
if (SettingsData.dwlShowAllTags) {
|
||||||
return Array.from({
|
return Array.from({
|
||||||
length: dwlSvc.tagCount
|
length: MangoService.tagCount
|
||||||
}, (_, i) => i);
|
}, (_, i) => i);
|
||||||
}
|
}
|
||||||
return dwlSvc.getVisibleTags(screenName);
|
return MangoService.getVisibleTags(screenName);
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const workspaces = I3.workspaces?.values || [];
|
const workspaces = I3.workspaces?.values || [];
|
||||||
if (workspaces.length === 0)
|
if (workspaces.length === 0)
|
||||||
@@ -235,13 +234,13 @@ Item {
|
|||||||
const monitors = Hyprland.monitors?.values || [];
|
const monitors = Hyprland.monitors?.values || [];
|
||||||
const currentMonitor = monitors.find(monitor => monitor.name === screenName);
|
const currentMonitor = monitors.find(monitor => monitor.name === screenName);
|
||||||
return currentMonitor?.activeWorkspace?.id ?? 1;
|
return currentMonitor?.activeWorkspace?.id ?? 1;
|
||||||
} else if (CompositorService.isDwl || CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
if (!dwlSvc.available)
|
if (!MangoService.available)
|
||||||
return 0;
|
return 0;
|
||||||
const outputState = dwlSvc.getOutputState(screenName);
|
const outputState = MangoService.getOutputState(screenName);
|
||||||
if (!outputState || !outputState.tags)
|
if (!outputState || !outputState.tags)
|
||||||
return 0;
|
return 0;
|
||||||
const activeTags = dwlSvc.getActiveTags(screenName);
|
const activeTags = MangoService.getActiveTags(screenName);
|
||||||
return activeTags.length > 0 ? activeTags[0] : 0;
|
return activeTags.length > 0 ? activeTags[0] : 0;
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
if (!screenName || SettingsData.workspaceFollowFocus) {
|
if (!screenName || SettingsData.workspaceFollowFocus) {
|
||||||
@@ -283,14 +282,14 @@ Item {
|
|||||||
if (nextIndex !== validIndex) {
|
if (nextIndex !== validIndex) {
|
||||||
HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id);
|
HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id);
|
||||||
}
|
}
|
||||||
} else if (CompositorService.isDwl || CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
const currentTag = getCurrentWorkspace();
|
const currentTag = getCurrentWorkspace();
|
||||||
const currentIndex = realWorkspaces.findIndex(tag => tag === currentTag);
|
const currentIndex = realWorkspaces.findIndex(tag => tag === currentTag);
|
||||||
const validIndex = currentIndex === -1 ? 0 : currentIndex;
|
const validIndex = currentIndex === -1 ? 0 : currentIndex;
|
||||||
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
|
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
|
||||||
|
|
||||||
if (nextIndex !== validIndex) {
|
if (nextIndex !== validIndex) {
|
||||||
dwlSvc.switchToTag(_barScreenName, realWorkspaces[nextIndex]);
|
MangoService.switchToTag(_barScreenName, realWorkspaces[nextIndex]);
|
||||||
}
|
}
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const currentWs = getCurrentWorkspace();
|
const currentWs = getCurrentWorkspace();
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ PanelWindow {
|
|||||||
id: barWindow
|
id: barWindow
|
||||||
readonly property var log: Log.scoped("DankBarWindow")
|
readonly property var log: Log.scoped("DankBarWindow")
|
||||||
|
|
||||||
|
Component.onDestruction: KeyboardFocus.unregisterBarWindow(barWindow)
|
||||||
|
|
||||||
required property var rootWindow
|
required property var rootWindow
|
||||||
required property var barConfig
|
required property var barConfig
|
||||||
property var modelData: item
|
property var modelData: item
|
||||||
@@ -18,6 +20,8 @@ PanelWindow {
|
|||||||
property var centerWidgetsModel
|
property var centerWidgetsModel
|
||||||
property var rightWidgetsModel
|
property var rightWidgetsModel
|
||||||
|
|
||||||
|
readonly property bool barRevealed: inputMask.showing
|
||||||
|
|
||||||
property var controlCenterButtonRef: null
|
property var controlCenterButtonRef: null
|
||||||
property var clockButtonRef: null
|
property var clockButtonRef: null
|
||||||
property var systemUpdateButtonRef: null
|
property var systemUpdateButtonRef: null
|
||||||
@@ -555,6 +559,7 @@ PanelWindow {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
KeyboardFocus.registerBarWindow(barWindow);
|
||||||
updateGpuTempConfig();
|
updateGpuTempConfig();
|
||||||
_updateBackgroundAlpha();
|
_updateBackgroundAlpha();
|
||||||
_updateHasMaximizedToplevel();
|
_updateHasMaximizedToplevel();
|
||||||
@@ -956,8 +961,13 @@ PanelWindow {
|
|||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const screenName = barWindow.screen?.name;
|
const screenName = barWindow.screen?.name;
|
||||||
if (screenName && PopoutManager.currentPopoutsByScreen[screenName])
|
if (!screenName)
|
||||||
|
return;
|
||||||
|
if (PopoutManager.currentPopoutsByScreen[screenName])
|
||||||
PopoutManager.closeAllPopouts();
|
PopoutManager.closeAllPopouts();
|
||||||
|
if (ModalManager.currentModalsByScreen[screenName])
|
||||||
|
ModalManager.closeAllModalsExcept(null);
|
||||||
|
TrayMenuManager.closeAllMenus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ DankPopout {
|
|||||||
|
|
||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
|
|
||||||
// mango shares dwl's layout model; route to the right service.
|
readonly property bool isMango: CompositorService.isMango
|
||||||
readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango
|
|
||||||
readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService
|
|
||||||
|
|
||||||
function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) {
|
function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) {
|
||||||
triggerX = x;
|
triggerX = x;
|
||||||
@@ -37,8 +35,8 @@ DankPopout {
|
|||||||
onScreenChanged: updateOutputState()
|
onScreenChanged: updateOutputState()
|
||||||
|
|
||||||
function updateOutputState() {
|
function updateOutputState() {
|
||||||
if (screen && root.dwlSvc.available) {
|
if (screen && MangoService.available) {
|
||||||
outputState = root.dwlSvc.getOutputState(screen.name);
|
outputState = MangoService.getOutputState(screen.name);
|
||||||
} else {
|
} else {
|
||||||
outputState = null;
|
outputState = null;
|
||||||
}
|
}
|
||||||
@@ -84,7 +82,7 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: DwlService
|
target: MangoService
|
||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
updateOutputState();
|
updateOutputState();
|
||||||
}
|
}
|
||||||
@@ -219,7 +217,7 @@ DankPopout {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.dwlSvc.layouts
|
model: MangoService.layouts
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
required property string modelData
|
required property string modelData
|
||||||
@@ -273,11 +271,11 @@ DankPopout {
|
|||||||
if (!root.triggerScreen) {
|
if (!root.triggerScreen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!root.dwlSvc.available) {
|
if (!MangoService.available) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.dwlSvc.setLayout(root.triggerScreen.name, index);
|
MangoService.setLayout(root.triggerScreen.name, index);
|
||||||
root.close();
|
root.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,15 +38,7 @@ DankPopout {
|
|||||||
|
|
||||||
backgroundInteractive: !anyModalOpen
|
backgroundInteractive: !anyModalOpen
|
||||||
|
|
||||||
customKeyboardFocus: {
|
customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null
|
||||||
if (!shouldBeVisible)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (anyModalOpen)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (CompositorService.useHyprlandFocusGrab)
|
|
||||||
return WlrKeyboardFocus.OnDemand;
|
|
||||||
return WlrKeyboardFocus.Exclusive;
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SystemUpdateService
|
target: SystemUpdateService
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ Loader {
|
|||||||
"cpuTemp": dgopAvailable,
|
"cpuTemp": dgopAvailable,
|
||||||
"gpuTemp": dgopAvailable,
|
"gpuTemp": dgopAvailable,
|
||||||
"network_speed_monitor": dgopAvailable,
|
"network_speed_monitor": dgopAvailable,
|
||||||
"layout": (CompositorService.isDwl && DwlService.dwlAvailable) || (CompositorService.isMango && MangoService.available)
|
"layout": CompositorService.isMango && MangoService.available
|
||||||
};
|
};
|
||||||
|
|
||||||
return widgetVisibility[widgetId] ?? true;
|
return widgetVisibility[widgetId] ?? true;
|
||||||
|
|||||||
@@ -13,12 +13,11 @@ BasePill {
|
|||||||
signal toggleLayoutPopup
|
signal toggleLayoutPopup
|
||||||
|
|
||||||
// mango shares dwl's tag/layout model; route to the right service.
|
// mango shares dwl's tag/layout model; route to the right service.
|
||||||
readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango
|
readonly property bool isMango: CompositorService.isMango
|
||||||
readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService
|
|
||||||
|
|
||||||
visible: layout.isDwlLike && layout.dwlSvc.available
|
visible: layout.isMango && MangoService.available
|
||||||
|
|
||||||
property var outputState: parentScreen ? layout.dwlSvc.getOutputState(parentScreen.name) : null
|
property var outputState: parentScreen ? MangoService.getOutputState(parentScreen.name) : null
|
||||||
property string currentLayoutSymbol: outputState?.layoutSymbol || ""
|
property string currentLayoutSymbol: outputState?.layoutSymbol || ""
|
||||||
property int currentLayoutIndex: outputState?.layout || 0
|
property int currentLayoutIndex: outputState?.layout || 0
|
||||||
|
|
||||||
@@ -41,9 +40,9 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: layout.dwlSvc
|
target: MangoService
|
||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
outputState = parentScreen ? layout.dwlSvc.getOutputState(parentScreen.name) : null;
|
outputState = parentScreen ? MangoService.getOutputState(parentScreen.name) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +100,13 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
if (!parentScreen || !layout.dwlSvc.available || layout.dwlSvc.layouts.length === 0) {
|
if (!parentScreen || !MangoService.available || MangoService.layouts.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentIndex = layout.currentLayoutIndex;
|
const currentIndex = layout.currentLayoutIndex;
|
||||||
const nextIndex = (currentIndex + 1) % layout.dwlSvc.layouts.length;
|
const nextIndex = (currentIndex + 1) % MangoService.layouts.length;
|
||||||
|
|
||||||
layout.dwlSvc.setLayout(parentScreen.name, nextIndex);
|
MangoService.setLayout(parentScreen.name, nextIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,8 +112,6 @@ BasePill {
|
|||||||
property string currentLayout: {
|
property string currentLayout: {
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
return NiriService.getCurrentKeyboardLayoutName();
|
return NiriService.getCurrentKeyboardLayoutName();
|
||||||
} else if (CompositorService.isDwl) {
|
|
||||||
return DwlService.currentKeyboardLayout;
|
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
return MangoService.currentKeyboardLayout;
|
return MangoService.currentKeyboardLayout;
|
||||||
}
|
}
|
||||||
@@ -209,8 +207,6 @@ BasePill {
|
|||||||
NiriService.cycleKeyboardLayout();
|
NiriService.cycleKeyboardLayout();
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
Quickshell.execDetached(["hyprctl", "switchxkblayout", root.hyprlandKeyboard, "next"]);
|
Quickshell.execDetached(["hyprctl", "switchxkblayout", root.hyprlandKeyboard, "next"]);
|
||||||
} else if (CompositorService.isDwl) {
|
|
||||||
Quickshell.execDetached(["mmsg", "dispatch", "switch_keyboard_layout"]);
|
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
MangoService.cycleKeyboardLayout();
|
MangoService.cycleKeyboardLayout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc)
|
visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc)
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
@@ -66,8 +66,6 @@ BasePill {
|
|||||||
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
||||||
} else if (CompositorService.isDwl) {
|
|
||||||
return "file://" + Theme.shellDir + "/assets/mango.png";
|
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
return "file://" + Theme.shellDir + "/assets/mango.png";
|
return "file://" + Theme.shellDir + "/assets/mango.png";
|
||||||
} else if (CompositorService.isSway) {
|
} else if (CompositorService.isSway) {
|
||||||
|
|||||||
@@ -980,21 +980,13 @@ BasePill {
|
|||||||
screen: root.parentScreen
|
screen: root.parentScreen
|
||||||
WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top
|
WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: {
|
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(root.menuOpen, null)
|
||||||
if (PopoutManager.screenshotActive)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (!root.menuOpen)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (CompositorService.useHyprlandFocusGrab)
|
|
||||||
return WlrKeyboardFocus.OnDemand;
|
|
||||||
return WlrKeyboardFocus.Exclusive;
|
|
||||||
}
|
|
||||||
WlrLayershell.namespace: "dms:tray-overflow-menu"
|
WlrLayershell.namespace: "dms:tray-overflow-menu"
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: [overflowMenu]
|
windows: [overflowMenu].concat(KeyboardFocus.barWindows)
|
||||||
active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen
|
active: root.useOverflowPopup && KeyboardFocus.wantsGrab(root.menuOpen, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -1051,32 +1043,21 @@ BasePill {
|
|||||||
"leftBar": 0,
|
"leftBar": 0,
|
||||||
"rightBar": 0
|
"rightBar": 0
|
||||||
})
|
})
|
||||||
readonly property real effectiveBarSize: root.barThickness + root.barSpacing
|
readonly property real maskX: _overflowDismissZone.x
|
||||||
|
readonly property real maskY: _overflowDismissZone.y
|
||||||
|
readonly property real maskWidth: _overflowDismissZone.width
|
||||||
|
readonly property real maskHeight: _overflowDismissZone.height
|
||||||
|
|
||||||
readonly property real maskX: {
|
DismissZone {
|
||||||
const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0;
|
id: _overflowDismissZone
|
||||||
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
|
barPosition: overflowMenu.barPosition
|
||||||
return Math.max(triggeringBarX, adjacentLeftBar);
|
barX: overflowMenu.barX
|
||||||
}
|
barY: overflowMenu.barY
|
||||||
|
barWidth: overflowMenu.barWidth
|
||||||
readonly property real maskY: {
|
barHeight: overflowMenu.barHeight
|
||||||
const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0;
|
screenWidth: overflowMenu.width
|
||||||
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
|
screenHeight: overflowMenu.height
|
||||||
return Math.max(triggeringBarY, adjacentTopBar);
|
adjacentBarInfo: overflowMenu.adjacentBarInfo
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real maskWidth: {
|
|
||||||
const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0;
|
|
||||||
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
|
|
||||||
const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar);
|
|
||||||
return Math.max(100, width - maskX - rightExclusion);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real maskHeight: {
|
|
||||||
const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0;
|
|
||||||
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
|
|
||||||
const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar);
|
|
||||||
return Math.max(100, height - maskY - bottomExclusion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
@@ -1237,13 +1218,7 @@ BasePill {
|
|||||||
fallbackOffset: 6
|
fallbackOffset: 6
|
||||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
targetRadius: Theme.cornerRadius
|
targetRadius: Theme.cornerRadius
|
||||||
sourceRect.antialiasing: true
|
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
|
||||||
sourceRect.smooth: true
|
|
||||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && !BlurService.enabled
|
|
||||||
layer.smooth: true
|
|
||||||
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
|
|
||||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
|
||||||
layer.samples: 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -1450,20 +1425,12 @@ BasePill {
|
|||||||
screen: menuRoot.parentScreen
|
screen: menuRoot.parentScreen
|
||||||
WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top
|
WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: {
|
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(menuRoot.showMenu, null)
|
||||||
if (PopoutManager.screenshotActive)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (!menuRoot.showMenu)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (CompositorService.useHyprlandFocusGrab)
|
|
||||||
return WlrKeyboardFocus.OnDemand;
|
|
||||||
return WlrKeyboardFocus.Exclusive;
|
|
||||||
}
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: [menuWindow]
|
windows: [menuWindow].concat(KeyboardFocus.barWindows)
|
||||||
active: CompositorService.useHyprlandFocusGrab && menuRoot.showMenu
|
active: KeyboardFocus.wantsGrab(menuRoot.showMenu, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
@@ -1502,32 +1469,21 @@ BasePill {
|
|||||||
"leftBar": 0,
|
"leftBar": 0,
|
||||||
"rightBar": 0
|
"rightBar": 0
|
||||||
})
|
})
|
||||||
readonly property real effectiveBarSize: root.barThickness + root.barSpacing
|
readonly property real maskX: _menuDismissZone.x
|
||||||
|
readonly property real maskY: _menuDismissZone.y
|
||||||
|
readonly property real maskWidth: _menuDismissZone.width
|
||||||
|
readonly property real maskHeight: _menuDismissZone.height
|
||||||
|
|
||||||
readonly property real maskX: {
|
DismissZone {
|
||||||
const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0;
|
id: _menuDismissZone
|
||||||
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
|
barPosition: menuWindow.barPosition
|
||||||
return Math.max(triggeringBarX, adjacentLeftBar);
|
barX: menuWindow.barX
|
||||||
}
|
barY: menuWindow.barY
|
||||||
|
barWidth: menuWindow.barWidth
|
||||||
readonly property real maskY: {
|
barHeight: menuWindow.barHeight
|
||||||
const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0;
|
screenWidth: menuWindow.width
|
||||||
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
|
screenHeight: menuWindow.height
|
||||||
return Math.max(triggeringBarY, adjacentTopBar);
|
adjacentBarInfo: menuWindow.adjacentBarInfo
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real maskWidth: {
|
|
||||||
const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0;
|
|
||||||
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
|
|
||||||
const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar);
|
|
||||||
return Math.max(100, width - maskX - rightExclusion);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real maskHeight: {
|
|
||||||
const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0;
|
|
||||||
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
|
|
||||||
const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar);
|
|
||||||
return Math.max(100, height - maskY - bottomExclusion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
@@ -1689,11 +1645,7 @@ BasePill {
|
|||||||
fallbackOffset: 6
|
fallbackOffset: 6
|
||||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
targetRadius: Theme.cornerRadius
|
targetRadius: Theme.cornerRadius
|
||||||
sourceRect.antialiasing: true
|
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
|
||||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && !BlurService.enabled
|
|
||||||
layer.smooth: true
|
|
||||||
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
|
|
||||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
@@ -22,10 +22,7 @@ Item {
|
|||||||
property var hyprlandOverviewLoader: null
|
property var hyprlandOverviewLoader: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
// mango shares dwl's tag model; route to the right service so one set of
|
readonly property bool isMango: CompositorService.isMango
|
||||||
// branches serves both.
|
|
||||||
readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango
|
|
||||||
readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService
|
|
||||||
|
|
||||||
readonly property real _leftMargin: {
|
readonly property real _leftMargin: {
|
||||||
if (isVertical)
|
if (isVertical)
|
||||||
@@ -80,9 +77,8 @@ Item {
|
|||||||
return NiriService.currentOutput || root.screenName;
|
return NiriService.currentOutput || root.screenName;
|
||||||
case "hyprland":
|
case "hyprland":
|
||||||
return Hyprland.focusedWorkspace?.monitor?.name || root.screenName;
|
return Hyprland.focusedWorkspace?.monitor?.name || root.screenName;
|
||||||
case "dwl":
|
|
||||||
case "mango":
|
case "mango":
|
||||||
return root.dwlSvc.activeOutput || root.screenName;
|
return MangoService.activeOutput || root.screenName;
|
||||||
case "sway":
|
case "sway":
|
||||||
case "scroll":
|
case "scroll":
|
||||||
case "miracle":
|
case "miracle":
|
||||||
@@ -92,6 +88,7 @@ Item {
|
|||||||
return root.screenName;
|
return root.screenName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
readonly property bool mangoOverviewActive: CompositorService.isMango && MangoService.isOutputInOverview(effectiveScreenName)
|
||||||
|
|
||||||
readonly property var extProjection: (useExtWorkspace && parentScreen) ? WindowManager.screenProjection(parentScreen) : null
|
readonly property var extProjection: (useExtWorkspace && parentScreen) ? WindowManager.screenProjection(parentScreen) : null
|
||||||
readonly property bool useExtWorkspace: {
|
readonly property bool useExtWorkspace: {
|
||||||
@@ -100,7 +97,6 @@ Item {
|
|||||||
switch (CompositorService.compositor) {
|
switch (CompositorService.compositor) {
|
||||||
case "niri":
|
case "niri":
|
||||||
case "hyprland":
|
case "hyprland":
|
||||||
case "dwl":
|
|
||||||
case "mango":
|
case "mango":
|
||||||
case "sway":
|
case "sway":
|
||||||
case "scroll":
|
case "scroll":
|
||||||
@@ -127,7 +123,6 @@ Item {
|
|||||||
return getNiriActiveWorkspace();
|
return getNiriActiveWorkspace();
|
||||||
case "hyprland":
|
case "hyprland":
|
||||||
return getHyprlandActiveWorkspace();
|
return getHyprlandActiveWorkspace();
|
||||||
case "dwl":
|
|
||||||
case "mango":
|
case "mango":
|
||||||
const activeTags = getDwlActiveTags();
|
const activeTags = getDwlActiveTags();
|
||||||
return activeTags.length > 0 ? activeTags[0] : -1;
|
return activeTags.length > 0 ? activeTags[0] : -1;
|
||||||
@@ -140,7 +135,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
property var dwlActiveTags: {
|
property var dwlActiveTags: {
|
||||||
if (root.isDwlLike) {
|
if (root.isMango) {
|
||||||
return getDwlActiveTags();
|
return getDwlActiveTags();
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
@@ -159,8 +154,9 @@ Item {
|
|||||||
case "hyprland":
|
case "hyprland":
|
||||||
baseList = getHyprlandWorkspaces();
|
baseList = getHyprlandWorkspaces();
|
||||||
break;
|
break;
|
||||||
case "dwl":
|
|
||||||
case "mango":
|
case "mango":
|
||||||
|
if (root.mangoOverviewActive)
|
||||||
|
return [];
|
||||||
baseList = getDwlTags();
|
baseList = getDwlTags();
|
||||||
break;
|
break;
|
||||||
case "sway":
|
case "sway":
|
||||||
@@ -297,7 +293,7 @@ Item {
|
|||||||
}
|
}
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
targetWorkspaceId = ws.id !== undefined ? ws.id : ws;
|
targetWorkspaceId = ws.id !== undefined ? ws.id : ws;
|
||||||
} else if (root.isDwlLike) {
|
} else if (root.isMango) {
|
||||||
if (typeof ws !== "object" || ws.tag === undefined) {
|
if (typeof ws !== "object" || ws.tag === undefined) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -317,8 +313,8 @@ Item {
|
|||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||||
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
|
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
|
||||||
} else if (root.isDwlLike) {
|
} else if (root.isMango) {
|
||||||
const output = root.dwlSvc.getOutputState(root.effectiveScreenName);
|
const output = MangoService.getOutputState(root.effectiveScreenName);
|
||||||
if (output && output.tags) {
|
if (output && output.tags) {
|
||||||
const tag = output.tags.find(t => t.tag === targetWorkspaceId);
|
const tag = output.tags.find(t => t.tag === targetWorkspaceId);
|
||||||
isActiveWs = tag ? (tag.state === 1) : false;
|
isActiveWs = tag ? (tag.state === 1) : false;
|
||||||
@@ -406,7 +402,7 @@ Item {
|
|||||||
"id": -1,
|
"id": -1,
|
||||||
"name": ""
|
"name": ""
|
||||||
};
|
};
|
||||||
} else if (root.isDwlLike) {
|
} else if (root.isMango) {
|
||||||
placeholder = {
|
placeholder = {
|
||||||
"tag": -1
|
"tag": -1
|
||||||
};
|
};
|
||||||
@@ -488,11 +484,11 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDwlTags() {
|
function getDwlTags() {
|
||||||
if (!root.dwlSvc.available)
|
if (!MangoService.available)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
const targetScreen = root.effectiveScreenName;
|
const targetScreen = root.effectiveScreenName;
|
||||||
const output = root.dwlSvc.getOutputState(targetScreen);
|
const output = MangoService.getOutputState(targetScreen);
|
||||||
if (!output || !output.tags || output.tags.length === 0)
|
if (!output || !output.tags || output.tags.length === 0)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
@@ -505,7 +501,7 @@ Item {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibleTagIndices = root.dwlSvc.getVisibleTags(targetScreen);
|
const visibleTagIndices = MangoService.getVisibleTags(targetScreen);
|
||||||
return visibleTagIndices.map(tagIndex => {
|
return visibleTagIndices.map(tagIndex => {
|
||||||
const tagData = output.tags.find(t => t.tag === tagIndex);
|
const tagData = output.tags.find(t => t.tag === tagIndex);
|
||||||
return {
|
return {
|
||||||
@@ -518,10 +514,10 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDwlActiveTags() {
|
function getDwlActiveTags() {
|
||||||
if (!root.dwlSvc.available)
|
if (!MangoService.available)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
return root.dwlSvc.getActiveTags(root.effectiveScreenName);
|
return MangoService.getActiveTags(root.effectiveScreenName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExtWorkspaceWorkspaces() {
|
function getExtWorkspaceWorkspaces() {
|
||||||
@@ -572,7 +568,7 @@ Item {
|
|||||||
return ws && ws.idx !== -1;
|
return ws && ws.idx !== -1;
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return ws && ws.id !== -1;
|
return ws && ws.id !== -1;
|
||||||
if (root.isDwlLike)
|
if (root.isMango)
|
||||||
return ws && ws.tag !== -1;
|
return ws && ws.tag !== -1;
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return ws && ws.num !== -1;
|
return ws && ws.num !== -1;
|
||||||
@@ -600,10 +596,9 @@ Item {
|
|||||||
HyprlandService.focusWorkspace(data.id);
|
HyprlandService.focusWorkspace(data.id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "dwl":
|
|
||||||
case "mango":
|
case "mango":
|
||||||
if (data.tag !== undefined)
|
if (data.tag !== undefined)
|
||||||
root.dwlSvc.switchToTag(root.screenName, data.tag);
|
MangoService.switchToTag(root.screenName, data.tag);
|
||||||
break;
|
break;
|
||||||
case "sway":
|
case "sway":
|
||||||
case "scroll":
|
case "scroll":
|
||||||
@@ -689,7 +684,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id);
|
HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id);
|
||||||
} else if (root.isDwlLike) {
|
} else if (root.isMango) {
|
||||||
const realWorkspaces = getRealWorkspaces();
|
const realWorkspaces = getRealWorkspaces();
|
||||||
if (realWorkspaces.length < 2) {
|
if (realWorkspaces.length < 2) {
|
||||||
return;
|
return;
|
||||||
@@ -703,7 +698,7 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.dwlSvc.switchToTag(root.screenName, realWorkspaces[nextIndex].tag);
|
MangoService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag);
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const realWorkspaces = getRealWorkspaces();
|
const realWorkspaces = getRealWorkspaces();
|
||||||
if (realWorkspaces.length < 2) {
|
if (realWorkspaces.length < 2) {
|
||||||
@@ -731,7 +726,7 @@ Item {
|
|||||||
return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : "";
|
return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : "";
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return modelData?.id || "";
|
return modelData?.id || "";
|
||||||
if (root.isDwlLike)
|
if (root.isMango)
|
||||||
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
|
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return modelData?.num || "";
|
return modelData?.num || "";
|
||||||
@@ -746,7 +741,7 @@ Item {
|
|||||||
isPlaceholder = modelData?.idx === -1;
|
isPlaceholder = modelData?.idx === -1;
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
isPlaceholder = modelData?.id === -1;
|
isPlaceholder = modelData?.id === -1;
|
||||||
} else if (root.isDwlLike) {
|
} else if (root.isMango) {
|
||||||
isPlaceholder = modelData?.tag === -1;
|
isPlaceholder = modelData?.tag === -1;
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
isPlaceholder = modelData?.num === -1;
|
isPlaceholder = modelData?.num === -1;
|
||||||
@@ -781,7 +776,7 @@ Item {
|
|||||||
return getWorkspaceIndexFallback(modelData, index);
|
return getWorkspaceIndexFallback(modelData, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || root.isDwlLike || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || root.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
||||||
readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
|
readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
|
||||||
readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
|
readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
|
||||||
|
|
||||||
@@ -977,7 +972,7 @@ Item {
|
|||||||
StyledText {
|
StyledText {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: !root.isVertical
|
visible: !root.isVertical
|
||||||
text: I18n.tr("OVERVIEW")
|
text: I18n.tr("Overview")
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
font.pixelSize: overviewPill.labelSize
|
font.pixelSize: overviewPill.labelSize
|
||||||
font.weight: Font.DemiBold
|
font.weight: Font.DemiBold
|
||||||
@@ -1046,7 +1041,7 @@ Item {
|
|||||||
return !!(modelData && modelData.idx === root.currentWorkspace);
|
return !!(modelData && modelData.idx === root.currentWorkspace);
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return !!(modelData && modelData.id === root.currentWorkspace);
|
return !!(modelData && modelData.id === root.currentWorkspace);
|
||||||
if (root.isDwlLike)
|
if (root.isMango)
|
||||||
return !!(modelData && root.dwlActiveTags.includes(modelData.tag));
|
return !!(modelData && root.dwlActiveTags.includes(modelData.tag));
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return !!(modelData && modelData.num === root.currentWorkspace);
|
return !!(modelData && modelData.num === root.currentWorkspace);
|
||||||
@@ -1055,7 +1050,7 @@ Item {
|
|||||||
property bool isOccupied: {
|
property bool isOccupied: {
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
|
return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
|
||||||
if (root.isDwlLike)
|
if (root.isMango)
|
||||||
return modelData.clients > 0;
|
return modelData.clients > 0;
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
|
const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
|
||||||
@@ -1070,7 +1065,7 @@ Item {
|
|||||||
return !!(modelData && modelData.idx === -1);
|
return !!(modelData && modelData.idx === -1);
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return !!(modelData && modelData.id === -1);
|
return !!(modelData && modelData.id === -1);
|
||||||
if (root.isDwlLike)
|
if (root.isMango)
|
||||||
return !!(modelData && modelData.tag === -1);
|
return !!(modelData && modelData.tag === -1);
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return !!(modelData && modelData.num === -1);
|
return !!(modelData && modelData.num === -1);
|
||||||
@@ -1087,7 +1082,7 @@ Item {
|
|||||||
return modelData?.urgent ?? false;
|
return modelData?.urgent ?? false;
|
||||||
if (CompositorService.isNiri)
|
if (CompositorService.isNiri)
|
||||||
return loadedIsUrgent;
|
return loadedIsUrgent;
|
||||||
if (root.isDwlLike)
|
if (root.isMango)
|
||||||
return modelData?.state === 2;
|
return modelData?.state === 2;
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return loadedIsUrgent;
|
return loadedIsUrgent;
|
||||||
@@ -1115,7 +1110,7 @@ Item {
|
|||||||
targetWorkspaceId = modelData?.id;
|
targetWorkspaceId = modelData?.id;
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
targetWorkspaceId = modelData?.id;
|
targetWorkspaceId = modelData?.id;
|
||||||
} else if (CompositorService.isDwl) {
|
} else if (root.isMango) {
|
||||||
targetWorkspaceId = modelData?.tag;
|
targetWorkspaceId = modelData?.tag;
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
targetWorkspaceId = modelData?.num;
|
targetWorkspaceId = modelData?.num;
|
||||||
@@ -1378,8 +1373,8 @@ Item {
|
|||||||
}
|
}
|
||||||
} else if (CompositorService.isHyprland && modelData?.id) {
|
} else if (CompositorService.isHyprland && modelData?.id) {
|
||||||
HyprlandService.focusWorkspace(modelData.id);
|
HyprlandService.focusWorkspace(modelData.id);
|
||||||
} else if (root.isDwlLike && modelData?.tag !== undefined) {
|
} else if (root.isMango && modelData?.tag !== undefined) {
|
||||||
root.dwlSvc.switchToTag(root.screenName, modelData.tag);
|
MangoService.switchToTag(root.screenName, modelData.tag);
|
||||||
} else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num) {
|
} else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num) {
|
||||||
try {
|
try {
|
||||||
I3.dispatch(`workspace number ${modelData.num}`);
|
I3.dispatch(`workspace number ${modelData.num}`);
|
||||||
@@ -1390,8 +1385,8 @@ Item {
|
|||||||
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;
|
||||||
} else if (root.isDwlLike && modelData?.tag !== undefined) {
|
} else if (root.isMango && modelData?.tag !== undefined) {
|
||||||
root.dwlSvc.toggleTag(root.screenName, modelData.tag);
|
MangoService.toggleTag(root.screenName, modelData.tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1415,7 +1410,7 @@ Item {
|
|||||||
wsData = modelData || null;
|
wsData = modelData || null;
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
wsData = modelData;
|
wsData = modelData;
|
||||||
} else if (root.isDwlLike) {
|
} else if (root.isMango) {
|
||||||
wsData = modelData;
|
wsData = modelData;
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
wsData = modelData;
|
wsData = modelData;
|
||||||
@@ -1429,7 +1424,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SettingsData.showWorkspaceApps) {
|
if (SettingsData.showWorkspaceApps) {
|
||||||
if (root.isDwlLike || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
if (root.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
|
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
|
||||||
} else if (CompositorService.isNiri) {
|
} else if (CompositorService.isNiri) {
|
||||||
delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData);
|
delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData);
|
||||||
@@ -1989,8 +1984,8 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Connections {
|
Connections {
|
||||||
target: root.dwlSvc
|
target: MangoService
|
||||||
enabled: root.isDwlLike
|
enabled: root.isMango
|
||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
delegateRoot.updateAllData();
|
delegateRoot.updateAllData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ Item {
|
|||||||
borderColor: volumePanel.border.color
|
borderColor: volumePanel.border.color
|
||||||
borderWidth: volumePanel.border.width
|
borderWidth: volumePanel.border.width
|
||||||
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
||||||
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled
|
shadowEnabled: Theme.elevationEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -272,7 +272,7 @@ Item {
|
|||||||
borderColor: audioDevicesPanel.border.color
|
borderColor: audioDevicesPanel.border.color
|
||||||
borderWidth: audioDevicesPanel.border.width
|
borderWidth: audioDevicesPanel.border.width
|
||||||
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
||||||
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled
|
shadowEnabled: Theme.elevationEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -444,7 +444,7 @@ Item {
|
|||||||
borderColor: playersPanel.border.color
|
borderColor: playersPanel.border.color
|
||||||
borderWidth: playersPanel.border.width
|
borderWidth: playersPanel.border.width
|
||||||
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
||||||
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled
|
shadowEnabled: Theme.elevationEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -105,6 +105,13 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSelectedDateChanged: updateSelectedDateEvents()
|
onSelectedDateChanged: updateSelectedDateEvents()
|
||||||
|
|
||||||
|
onShowEventDetailsChanged: {
|
||||||
|
if (showEventDetails) {
|
||||||
|
taskInput.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
loadEventsForMonth();
|
loadEventsForMonth();
|
||||||
updateSelectedDateEvents();
|
updateSelectedDateEvents();
|
||||||
@@ -176,7 +183,7 @@ Rectangle {
|
|||||||
text: {
|
text: {
|
||||||
const dateStr = Qt.formatDate(selectedDate, "MMM d");
|
const dateStr = Qt.formatDate(selectedDate, "MMM d");
|
||||||
if (selectedDateEvents && selectedDateEvents.length > 0) {
|
if (selectedDateEvents && selectedDateEvents.length > 0) {
|
||||||
const eventCount = selectedDateEvents.length === 1 ? I18n.tr("1 event") : selectedDateEvents.length + " " + I18n.tr("events");
|
const eventCount = selectedDateEvents.length === 1 ? I18n.tr("1 task", "task count next to a date") : I18n.tr("%1 tasks", "task count next to a date, %1 is the number of tasks").arg(selectedDateEvents.length);
|
||||||
return dateStr + " • " + eventCount;
|
return dateStr + " • " + eventCount;
|
||||||
}
|
}
|
||||||
return dateStr;
|
return dateStr;
|
||||||
@@ -416,9 +423,503 @@ Rectangle {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)) {
|
root.selectedDate = dayDate;
|
||||||
root.selectedDate = dayDate;
|
root.showEventDetails = true;
|
||||||
root.showEventDetails = true;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: flickableArea
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
height: parent.height - (showEventDetails ? 40 + 42 : 28 + 18) - Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: showEventDetails
|
||||||
|
clip: true
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: listViewContainer.height
|
||||||
|
interactive: listViewContainer.draggedItem === null
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: listViewContainer
|
||||||
|
width: parent.width
|
||||||
|
height: 100
|
||||||
|
|
||||||
|
property var draggedItem: null
|
||||||
|
property bool orderChanged: false
|
||||||
|
|
||||||
|
function resetAndLayout() {
|
||||||
|
for (let i = 0; i < repeater.count; i++) {
|
||||||
|
let item = repeater.itemAt(i);
|
||||||
|
if (item) {
|
||||||
|
item.visualIndex = i;
|
||||||
|
item.isDragging = false;
|
||||||
|
item.isEditing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLayout() {
|
||||||
|
let items = [];
|
||||||
|
for (let i = 0; i < repeater.count; i++) {
|
||||||
|
let item = repeater.itemAt(i);
|
||||||
|
if (item) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.sort((a, b) => a.visualIndex - b.visualIndex);
|
||||||
|
|
||||||
|
let currentY = 0;
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let item = items[i];
|
||||||
|
if (item && !item.isDragging) {
|
||||||
|
item.y = currentY;
|
||||||
|
}
|
||||||
|
if (item) {
|
||||||
|
currentY += item.height + Theme.spacingXS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listViewContainer.height = Math.max(0, currentY - Theme.spacingXS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAndReorder(dragged) {
|
||||||
|
let items = [];
|
||||||
|
for (let i = 0; i < repeater.count; i++) {
|
||||||
|
let item = repeater.itemAt(i);
|
||||||
|
if (item) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.sort((a, b) => a.visualIndex - b.visualIndex);
|
||||||
|
|
||||||
|
let swapped = false;
|
||||||
|
|
||||||
|
// Helper to get target Y position without animation offsets
|
||||||
|
function getTargetY(index) {
|
||||||
|
let y = 0;
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
y += items[i].height + Theme.spacingXS;
|
||||||
|
}
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
let draggedIdx = items.indexOf(dragged);
|
||||||
|
if (draggedIdx === -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
let didSwap = false;
|
||||||
|
|
||||||
|
// Check item above
|
||||||
|
if (draggedIdx > 0) {
|
||||||
|
let above = items[draggedIdx - 1];
|
||||||
|
let targetYAbove = getTargetY(draggedIdx - 1);
|
||||||
|
if (above && dragged.y < (targetYAbove + above.height / 2)) {
|
||||||
|
// Swap visualIndex
|
||||||
|
let temp = dragged.visualIndex;
|
||||||
|
dragged.visualIndex = above.visualIndex;
|
||||||
|
above.visualIndex = temp;
|
||||||
|
|
||||||
|
// Swap in local array
|
||||||
|
items[draggedIdx] = above;
|
||||||
|
items[draggedIdx - 1] = dragged;
|
||||||
|
|
||||||
|
listViewContainer.orderChanged = true;
|
||||||
|
swapped = true;
|
||||||
|
didSwap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check item below
|
||||||
|
if (!didSwap && draggedIdx < items.length - 1) {
|
||||||
|
let below = items[draggedIdx + 1];
|
||||||
|
let targetYBelow = getTargetY(draggedIdx + 1);
|
||||||
|
if (below && (dragged.y + dragged.height) > (targetYBelow + below.height / 2)) {
|
||||||
|
// Swap visualIndex
|
||||||
|
let temp = dragged.visualIndex;
|
||||||
|
dragged.visualIndex = below.visualIndex;
|
||||||
|
below.visualIndex = temp;
|
||||||
|
|
||||||
|
// Swap in local array
|
||||||
|
items[draggedIdx] = below;
|
||||||
|
items[draggedIdx + 1] = dragged;
|
||||||
|
|
||||||
|
listViewContainer.orderChanged = true;
|
||||||
|
swapped = true;
|
||||||
|
didSwap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!didSwap) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swapped) {
|
||||||
|
updateLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveNewOrder() {
|
||||||
|
if (!orderChanged)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let items = [];
|
||||||
|
for (let i = 0; i < repeater.count; i++) {
|
||||||
|
let item = repeater.itemAt(i);
|
||||||
|
if (item) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.sort((a, b) => a.visualIndex - b.visualIndex);
|
||||||
|
|
||||||
|
let orderedIds = [];
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let tid = items[i].taskId;
|
||||||
|
if (tid && tid.startsWith("task_")) {
|
||||||
|
orderedIds.push(tid.replace("task_", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (orderedIds.length > 0) {
|
||||||
|
CalendarService.reorderTasksForDate(root.selectedDate, orderedIds);
|
||||||
|
}
|
||||||
|
orderChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: repeater
|
||||||
|
model: selectedDateEvents
|
||||||
|
|
||||||
|
onModelChanged: {
|
||||||
|
Qt.callLater(listViewContainer.resetAndLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: taskItem
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
height: isEditing ? 34 : (eventContent.implicitHeight + Theme.spacingS)
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
|
property int modelIndex: index
|
||||||
|
property int visualIndex: index
|
||||||
|
property string taskId: (modelData && modelData.id) ? modelData.id : ""
|
||||||
|
property bool isDragging: false
|
||||||
|
property bool isEditing: false
|
||||||
|
property real dragMouseOffsetY: 0
|
||||||
|
|
||||||
|
onModelIndexChanged: {
|
||||||
|
visualIndex = modelIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
onYChanged: {
|
||||||
|
if (isDragging) {
|
||||||
|
listViewContainer.checkAndReorder(taskItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color: isDragging ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : (eventMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : Theme.nestedSurface)
|
||||||
|
border.color: isDragging ? Theme.primary : (eventMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : Theme.outlineMedium)
|
||||||
|
border.width: (isDragging || eventMouseArea.containsMouse) ? 1 : Theme.layerOutlineWidth
|
||||||
|
|
||||||
|
scale: isDragging ? 1.02 : 1.0
|
||||||
|
z: isDragging ? 100 : visualIndex
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
id: yBehavior
|
||||||
|
enabled: !taskItem.isDragging
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
visualIndex = index;
|
||||||
|
listViewContainer.updateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
onHeightChanged: {
|
||||||
|
listViewContainer.updateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
onIsEditingChanged: {
|
||||||
|
if (isEditing) {
|
||||||
|
editInput.forceActiveFocus();
|
||||||
|
editInput.selectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 3
|
||||||
|
height: parent.height - 6
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 3
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: (modelData && modelData.id && modelData.id.startsWith("task_")) ? (modelData.completed ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Theme.primary) : Theme.primary
|
||||||
|
opacity: 0.8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drag Handle
|
||||||
|
Rectangle {
|
||||||
|
id: dragHandle
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
visible: modelData && modelData.id && modelData.id.startsWith("task_") && !taskItem.isEditing
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "drag_indicator"
|
||||||
|
size: 14
|
||||||
|
color: dragMouseArea.containsMouse ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dragMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.SizeAllCursor
|
||||||
|
preventStealing: true
|
||||||
|
|
||||||
|
drag.target: taskItem
|
||||||
|
drag.axis: Drag.YAxis
|
||||||
|
drag.minimumY: 0
|
||||||
|
drag.maximumY: listViewContainer.height - taskItem.height
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
taskItem.isDragging = true;
|
||||||
|
listViewContainer.orderChanged = false;
|
||||||
|
listViewContainer.draggedItem = taskItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositionChanged: {
|
||||||
|
// Handled natively by MouseArea.drag
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: {
|
||||||
|
taskItem.isDragging = false;
|
||||||
|
listViewContainer.draggedItem = null;
|
||||||
|
if (listViewContainer.orderChanged) {
|
||||||
|
listViewContainer.saveNewOrder();
|
||||||
|
} else {
|
||||||
|
listViewContainer.updateLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCanceled: {
|
||||||
|
taskItem.isDragging = false;
|
||||||
|
listViewContainer.draggedItem = null;
|
||||||
|
listViewContainer.resetAndLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkbox status icon
|
||||||
|
Rectangle {
|
||||||
|
id: checkboxContainer
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? (taskItem.isEditing ? 8 : 32) : 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
visible: modelData && modelData.id && modelData.id.startsWith("task_")
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: (modelData && modelData.completed) ? "check_box" : "check_box_outline_blank"
|
||||||
|
size: 16
|
||||||
|
color: (modelData && modelData.completed) ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: eventContent
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? 60 : (Theme.spacingS + 6)
|
||||||
|
anchors.rightMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? 64 : Theme.spacingXS
|
||||||
|
spacing: 2
|
||||||
|
visible: !taskItem.isEditing
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: modelData ? modelData.title : ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: (modelData && modelData.id && modelData.id.startsWith("task_") && modelData.completed) ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) : Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: {
|
||||||
|
if (!modelData || modelData.allDay) {
|
||||||
|
return I18n.tr("All day", "calendar task with no specific time");
|
||||||
|
} else if (modelData.start && modelData.end) {
|
||||||
|
const timeFormat = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP";
|
||||||
|
const startTime = Qt.formatTime(modelData.start, timeFormat);
|
||||||
|
if (modelData.start.toDateString() !== modelData.end.toDateString() || modelData.start.getTime() !== modelData.end.getTime()) {
|
||||||
|
return startTime + " – " + Qt.formatTime(modelData.end, timeFormat);
|
||||||
|
}
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
|
font.weight: Font.Normal
|
||||||
|
visible: text !== "" && modelData && modelData.id && !modelData.id.startsWith("task_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inline Edit Input Box
|
||||||
|
Rectangle {
|
||||||
|
id: editInputContainer
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.leftMargin: 36
|
||||||
|
anchors.rightMargin: 64
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
height: 28
|
||||||
|
visible: taskItem.isEditing
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
TextInput {
|
||||||
|
id: editInput
|
||||||
|
anchors.fill: parent
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
selectByMouse: true
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
text: modelData ? modelData.title : ""
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
let txt = text.trim();
|
||||||
|
if (txt !== "" && modelData && modelData.id) {
|
||||||
|
CalendarService.editTask(modelData.id, txt);
|
||||||
|
}
|
||||||
|
taskItem.isEditing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEscapePressed: {
|
||||||
|
taskItem.isEditing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main body MouseArea (declared before the delete/edit buttons so they sit on top)
|
||||||
|
MouseArea {
|
||||||
|
id: eventMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? 32 : 6
|
||||||
|
anchors.rightMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? 64 : 0
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: (modelData && (modelData.url || (modelData.id && modelData.id.startsWith("task_")))) ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: modelData && (modelData.url !== "" || (modelData.id && modelData.id.startsWith("task_"))) && !taskItem.isEditing
|
||||||
|
onClicked: {
|
||||||
|
if (modelData && modelData.id && modelData.id.startsWith("task_")) {
|
||||||
|
CalendarService.toggleTask(modelData.id);
|
||||||
|
} else if (modelData && modelData.url && modelData.url !== "") {
|
||||||
|
if (Qt.openUrlExternally(modelData.url) === false) {
|
||||||
|
log.warn("Failed to open URL: " + modelData.url);
|
||||||
|
} else {
|
||||||
|
root.closeDash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete / Cancel Button
|
||||||
|
Rectangle {
|
||||||
|
id: deleteButton
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: deleteMouseArea.containsMouse ? (taskItem.isEditing ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(0.9, 0.2, 0.2, 0.15)) : "transparent"
|
||||||
|
visible: modelData && modelData.id && modelData.id.startsWith("task_")
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: taskItem.isEditing ? "close" : "delete"
|
||||||
|
size: 14
|
||||||
|
color: deleteMouseArea.containsMouse ? (taskItem.isEditing ? Theme.primary : Qt.rgba(0.9, 0.2, 0.2, 1.0)) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: deleteMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (taskItem.isEditing) {
|
||||||
|
taskItem.isEditing = false;
|
||||||
|
} else if (modelData && modelData.id) {
|
||||||
|
CalendarService.removeTask(modelData.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit / Save Button
|
||||||
|
Rectangle {
|
||||||
|
id: editButton
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
anchors.right: deleteButton.left
|
||||||
|
anchors.rightMargin: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: editMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
visible: modelData && modelData.id && modelData.id.startsWith("task_")
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: taskItem.isEditing ? "check" : "edit"
|
||||||
|
size: 14
|
||||||
|
color: editMouseArea.containsMouse ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: editMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (taskItem.isEditing) {
|
||||||
|
let txt = editInput.text.trim();
|
||||||
|
if (txt !== "" && modelData && modelData.id) {
|
||||||
|
CalendarService.editTask(modelData.id, txt);
|
||||||
|
}
|
||||||
|
taskItem.isEditing = false;
|
||||||
|
} else {
|
||||||
|
taskItem.isEditing = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -428,105 +929,40 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankListView {
|
Rectangle {
|
||||||
width: parent.width - Theme.spacingS * 2
|
width: parent.width - Theme.spacingS * 2
|
||||||
height: parent.height - (showEventDetails ? 40 : 28 + 18) - Theme.spacingS
|
height: 34
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
model: selectedDateEvents
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.nestedSurface
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
border.width: 1
|
||||||
visible: showEventDetails
|
visible: showEventDetails
|
||||||
clip: true
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
TextInput {
|
||||||
width: parent ? parent.width : 0
|
id: taskInput
|
||||||
height: eventContent.implicitHeight + Theme.spacingS
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
anchors.leftMargin: Theme.spacingS
|
||||||
color: {
|
anchors.rightMargin: Theme.spacingS
|
||||||
if (modelData.url && eventMouseArea.containsMouse) {
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
color: Theme.surfaceText
|
||||||
} else if (eventMouseArea.containsMouse) {
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06);
|
selectByMouse: true
|
||||||
}
|
clip: true
|
||||||
return Theme.nestedSurface;
|
|
||||||
}
|
|
||||||
border.color: {
|
|
||||||
if (modelData.url && eventMouseArea.containsMouse) {
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
|
||||||
} else if (eventMouseArea.containsMouse) {
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
|
|
||||||
}
|
|
||||||
return Theme.outlineMedium;
|
|
||||||
}
|
|
||||||
border.width: eventMouseArea.containsMouse ? 1 : Theme.layerOutlineWidth
|
|
||||||
|
|
||||||
Rectangle {
|
Text {
|
||||||
width: 3
|
text: I18n.tr("Add a task...", "placeholder in the new-task input field")
|
||||||
height: parent.height - 6
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||||
anchors.left: parent.left
|
visible: !taskInput.text && !taskInput.activeFocus
|
||||||
anchors.leftMargin: 3
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.primary
|
|
||||||
opacity: 0.8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
onAccepted: {
|
||||||
id: eventContent
|
let txt = text.trim();
|
||||||
|
if (txt !== "") {
|
||||||
anchors.left: parent.left
|
CalendarService.addTaskForDate(root.selectedDate, txt);
|
||||||
anchors.right: parent.right
|
text = "";
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.leftMargin: Theme.spacingS + 6
|
|
||||||
anchors.rightMargin: Theme.spacingXS
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
width: parent.width
|
|
||||||
text: modelData.title
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
width: parent.width
|
|
||||||
text: {
|
|
||||||
if (!modelData || modelData.allDay) {
|
|
||||||
return I18n.tr("All day");
|
|
||||||
} else if (modelData.start && modelData.end) {
|
|
||||||
const timeFormat = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP";
|
|
||||||
const startTime = Qt.formatTime(modelData.start, timeFormat);
|
|
||||||
if (modelData.start.toDateString() !== modelData.end.toDateString() || modelData.start.getTime() !== modelData.end.getTime()) {
|
|
||||||
return startTime + " – " + Qt.formatTime(modelData.end, timeFormat);
|
|
||||||
}
|
|
||||||
return startTime;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
font.weight: Font.Normal
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: eventMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: modelData.url ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
enabled: modelData.url !== ""
|
|
||||||
onClicked: {
|
|
||||||
if (modelData.url && modelData.url !== "") {
|
|
||||||
if (Qt.openUrlExternally(modelData.url) === false) {
|
|
||||||
log.warn("Failed to open URL: " + modelData.url);
|
|
||||||
} else {
|
|
||||||
root.closeDash();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,9 +67,6 @@ Card {
|
|||||||
return I18n.tr("on Niri");
|
return I18n.tr("on Niri");
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return I18n.tr("on Hyprland");
|
return I18n.tr("on Hyprland");
|
||||||
// technically they might not be on mangowc, but its what we support in the docs
|
|
||||||
if (CompositorService.isDwl)
|
|
||||||
return I18n.tr("on MangoWC");
|
|
||||||
if (CompositorService.isMango)
|
if (CompositorService.isMango)
|
||||||
return I18n.tr("on MangoWC");
|
return I18n.tr("on MangoWC");
|
||||||
if (CompositorService.isSway)
|
if (CompositorService.isSway)
|
||||||
@@ -101,9 +98,7 @@ Card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: DgopService.shortUptime
|
text: DgopService.shortUptime ? I18n.tr("up", "uptime prefix, e.g. 'up 4h 2m'") + DgopService.shortUptime.slice(2) : I18n.tr("up", "uptime prefix, e.g. 'up 4h 2m'")
|
||||||
? I18n.tr("up") + DgopService.shortUptime.slice(2)
|
|
||||||
: I18n.tr("up")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|||||||
@@ -20,17 +20,25 @@ Card {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: !WeatherService.weather.available
|
visible: !WeatherService.weather.available
|
||||||
|
|
||||||
|
DankSpinner {
|
||||||
|
size: 24
|
||||||
|
visible: WeatherService.weather.loading
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "cloud_off"
|
name: "cloud_off"
|
||||||
size: 24
|
size: 24
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||||
|
visible: !WeatherService.weather.loading
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: WeatherService.weather.loading ? I18n.tr("Loading...") : I18n.tr("No Weather")
|
text: I18n.tr("No Weather")
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
|
visible: !WeatherService.weather.loading
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,13 +186,36 @@ Variants {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const presented = dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps;
|
||||||
|
const phase = !presented ? "hidden" : ((!dock.reveal && (slideXAnimation.running || slideYAnimation.running)) ? "closing" : ((slideXAnimation.running || slideYAnimation.running) ? "opening" : "open"));
|
||||||
|
const bodyX = dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x;
|
||||||
|
const bodyY = dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y;
|
||||||
|
const bodyW = dock.hasApps ? dockBackground.width : 0;
|
||||||
|
const bodyH = dock.hasApps ? dockBackground.height : 0;
|
||||||
ConnectedModeState.setDockState(dock._dockScreenName, {
|
ConnectedModeState.setDockState(dock._dockScreenName, {
|
||||||
"reveal": dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps,
|
"kind": "dock",
|
||||||
|
"screenName": dock._dockScreenName,
|
||||||
|
"phase": phase,
|
||||||
|
"visible": presented,
|
||||||
|
"presented": presented,
|
||||||
|
"reveal": presented,
|
||||||
"barSide": dock.connectedBarSide,
|
"barSide": dock.connectedBarSide,
|
||||||
"bodyX": dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x,
|
"bodyRect": {
|
||||||
"bodyY": dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y,
|
"x": bodyX,
|
||||||
"bodyW": dock.hasApps ? dockBackground.width : 0,
|
"y": bodyY,
|
||||||
"bodyH": dock.hasApps ? dockBackground.height : 0,
|
"width": bodyW,
|
||||||
|
"height": bodyH
|
||||||
|
},
|
||||||
|
"animationOffset": {
|
||||||
|
"x": dockSlide.x,
|
||||||
|
"y": dockSlide.y
|
||||||
|
},
|
||||||
|
"scale": 1,
|
||||||
|
"opacity": Theme.connectedSurfaceColor.a,
|
||||||
|
"bodyX": bodyX,
|
||||||
|
"bodyY": bodyY,
|
||||||
|
"bodyW": bodyW,
|
||||||
|
"bodyH": bodyH,
|
||||||
"slideX": dockSlide.x,
|
"slideX": dockSlide.x,
|
||||||
"slideY": dockSlide.y
|
"slideY": dockSlide.y
|
||||||
});
|
});
|
||||||
@@ -724,16 +747,36 @@ Variants {
|
|||||||
onHeightChanged: dock._syncDockChromeState()
|
onHeightChanged: dock._syncDockChromeState()
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectedShape {
|
Item {
|
||||||
|
id: dockConnectedChrome
|
||||||
visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive
|
visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive
|
||||||
barSide: dock.connectedBarSide
|
readonly property real extraLeft: dock.isVertical ? 0 : Theme.connectedCornerRadius
|
||||||
bodyWidth: dockBackground.width
|
readonly property real extraTop: dock.isVertical ? Theme.connectedCornerRadius : 0
|
||||||
bodyHeight: dockBackground.height
|
readonly property real bodyRadius: dock.surfaceRadius
|
||||||
connectorRadius: Theme.connectedCornerRadius
|
readonly property bool barTop: dock.connectedBarSide === "top"
|
||||||
surfaceRadius: dock.surfaceRadius
|
readonly property bool barBottom: dock.connectedBarSide === "bottom"
|
||||||
fillColor: dock.surfaceColor
|
readonly property bool barLeft: dock.connectedBarSide === "left"
|
||||||
x: dockBackground.x - bodyX
|
readonly property bool barRight: dock.connectedBarSide === "right"
|
||||||
y: dockBackground.y - bodyY
|
|
||||||
|
x: dockBackground.x - extraLeft
|
||||||
|
y: dockBackground.y - extraTop
|
||||||
|
width: dockBackground.width + extraLeft * 2
|
||||||
|
height: dockBackground.height + extraTop * 2
|
||||||
|
|
||||||
|
ShaderEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_chrome.frag.qsb")
|
||||||
|
|
||||||
|
property real widthPx: width
|
||||||
|
property real heightPx: height
|
||||||
|
property vector4d surfaceColor: Qt.vector4d(dock.surfaceColor.r, dock.surfaceColor.g, dock.surfaceColor.b, dock.surfaceColor.a)
|
||||||
|
property vector4d shadowColor: Qt.vector4d(0, 0, 0, 0)
|
||||||
|
property vector4d shadowParam: Qt.vector4d(0, 0, 0, 0)
|
||||||
|
property vector4d ambientParam: Qt.vector4d(0, 0, 0, 0)
|
||||||
|
property vector4d bodyRect: Qt.vector4d(dockConnectedChrome.extraLeft, dockConnectedChrome.extraTop, dockBackground.width, dockBackground.height)
|
||||||
|
property vector4d cornerRadius: Qt.vector4d(dockConnectedChrome.barTop || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barTop || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius)
|
||||||
|
property vector4d edgeParam: Qt.vector4d(dockConnectedChrome.barTop ? 0 : (dockConnectedChrome.barBottom ? 1 : (dockConnectedChrome.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape {
|
Shape {
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
visible: SettingsData.dockLauncherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc)
|
visible: SettingsData.dockLauncherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc)
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||||
height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||||
@@ -247,8 +247,6 @@ Item {
|
|||||||
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
||||||
} else if (CompositorService.isDwl) {
|
|
||||||
return "file://" + Theme.shellDir + "/assets/mango.png";
|
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
return "file://" + Theme.shellDir + "/assets/mango.png";
|
return "file://" + Theme.shellDir + "/assets/mango.png";
|
||||||
} else if (CompositorService.isSway) {
|
} else if (CompositorService.isSway) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
|
// Frame perimeter ring with rounded cutout (SDF).
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
@@ -16,39 +16,14 @@ Item {
|
|||||||
required property real cutoutRadius
|
required property real cutoutRadius
|
||||||
property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity)
|
property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity)
|
||||||
|
|
||||||
Rectangle {
|
ShaderEffect {
|
||||||
id: borderRect
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
// Bake frameOpacity into the color alpha rather than using the `opacity` property
|
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/frame_arc.frag.qsb")
|
||||||
color: root.borderColor
|
|
||||||
|
|
||||||
layer.enabled: true
|
property real widthPx: width
|
||||||
layer.effect: MultiEffect {
|
property real heightPx: height
|
||||||
maskSource: cutoutMask
|
property real cutoutRadius: root.cutoutRadius
|
||||||
maskEnabled: true
|
property vector4d cutout: Qt.vector4d(root.cutoutLeftInset, root.cutoutTopInset, root.width - root.cutoutRightInset, root.height - root.cutoutBottomInset)
|
||||||
maskInverted: true
|
property vector4d surfaceColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a)
|
||||||
maskThresholdMin: 0.5
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: cutoutMask
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
layer.enabled: true
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors {
|
|
||||||
fill: parent
|
|
||||||
topMargin: root.cutoutTopInset
|
|
||||||
bottomMargin: root.cutoutBottomInset
|
|
||||||
leftMargin: root.cutoutLeftInset
|
|
||||||
rightMargin: root.cutoutRightInset
|
|
||||||
}
|
|
||||||
radius: root.cutoutRadius
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -279,9 +279,11 @@ Rectangle {
|
|||||||
break;
|
break;
|
||||||
case Qt.Key_P:
|
case Qt.Key_P:
|
||||||
if (!(event.modifiers & Qt.ControlModifier)) {
|
if (!(event.modifiers & Qt.ControlModifier)) {
|
||||||
const idx = visibleActions.indexOf("poweroff");
|
if (visibleActions.includes("poweroff")) {
|
||||||
startHold("poweroff", idx);
|
const idx = visibleActions.indexOf("poweroff");
|
||||||
event.accepted = true;
|
startHold("poweroff", idx);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
|
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
@@ -300,20 +302,28 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_R:
|
case Qt.Key_R:
|
||||||
startHold("reboot", visibleActions.indexOf("reboot"));
|
if (visibleActions.includes("reboot")) {
|
||||||
event.accepted = true;
|
startHold("reboot", visibleActions.indexOf("reboot"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_X:
|
case Qt.Key_X:
|
||||||
startHold("logout", visibleActions.indexOf("logout"));
|
if (visibleActions.includes("logout")) {
|
||||||
event.accepted = true;
|
startHold("logout", visibleActions.indexOf("logout"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_S:
|
case Qt.Key_S:
|
||||||
startHold("suspend", visibleActions.indexOf("suspend"));
|
if (visibleActions.includes("suspend")) {
|
||||||
event.accepted = true;
|
startHold("suspend", visibleActions.indexOf("suspend"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_H:
|
case Qt.Key_H:
|
||||||
startHold("hibernate", visibleActions.indexOf("hibernate"));
|
if (visibleActions.includes("hibernate")) {
|
||||||
event.accepted = true;
|
startHold("hibernate", visibleActions.indexOf("hibernate"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,9 +374,11 @@ Rectangle {
|
|||||||
break;
|
break;
|
||||||
case Qt.Key_P:
|
case Qt.Key_P:
|
||||||
if (!(event.modifiers & Qt.ControlModifier)) {
|
if (!(event.modifiers & Qt.ControlModifier)) {
|
||||||
const idx = visibleActions.indexOf("poweroff");
|
if (visibleActions.includes("poweroff")) {
|
||||||
startHold("poweroff", idx);
|
const idx = visibleActions.indexOf("poweroff");
|
||||||
event.accepted = true;
|
startHold("poweroff", idx);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
|
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
|
||||||
selectedIndex = selectedRow * gridColumns + selectedCol;
|
selectedIndex = selectedRow * gridColumns + selectedCol;
|
||||||
@@ -388,20 +400,28 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_R:
|
case Qt.Key_R:
|
||||||
startHold("reboot", visibleActions.indexOf("reboot"));
|
if (visibleActions.includes("reboot")) {
|
||||||
event.accepted = true;
|
startHold("reboot", visibleActions.indexOf("reboot"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_X:
|
case Qt.Key_X:
|
||||||
startHold("logout", visibleActions.indexOf("logout"));
|
if (visibleActions.includes("logout")) {
|
||||||
event.accepted = true;
|
startHold("logout", visibleActions.indexOf("logout"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_S:
|
case Qt.Key_S:
|
||||||
startHold("suspend", visibleActions.indexOf("suspend"));
|
if (visibleActions.includes("suspend")) {
|
||||||
event.accepted = true;
|
startHold("suspend", visibleActions.indexOf("suspend"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_H:
|
case Qt.Key_H:
|
||||||
startHold("hibernate", visibleActions.indexOf("hibernate"));
|
if (visibleActions.includes("hibernate")) {
|
||||||
event.accepted = true;
|
startHold("hibernate", visibleActions.indexOf("hibernate"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -753,9 +753,46 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextInput {
|
FocusScope {
|
||||||
id: passwordField
|
id: passwordField
|
||||||
|
|
||||||
|
property string text: root.passwordBuffer
|
||||||
|
property int cursorPosition: text.length
|
||||||
|
|
||||||
|
signal accepted()
|
||||||
|
|
||||||
|
function clampCursorPosition() {
|
||||||
|
cursorPosition = Math.max(0, Math.min(cursorPosition, text.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
text = "";
|
||||||
|
cursorPosition = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertText(value) {
|
||||||
|
if (value.length === 0)
|
||||||
|
return;
|
||||||
|
clampCursorPosition();
|
||||||
|
text = text.slice(0, cursorPosition) + value + text.slice(cursorPosition);
|
||||||
|
cursorPosition += value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function backspace() {
|
||||||
|
clampCursorPosition();
|
||||||
|
if (cursorPosition === 0)
|
||||||
|
return;
|
||||||
|
text = text.slice(0, cursorPosition - 1) + text.slice(cursorPosition);
|
||||||
|
cursorPosition -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrintableText(value) {
|
||||||
|
if (value.length === 0)
|
||||||
|
return false;
|
||||||
|
const code = value.charCodeAt(0);
|
||||||
|
return code >= 0x20 && code !== 0x7f;
|
||||||
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2
|
anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2
|
||||||
anchors.rightMargin: {
|
anchors.rightMargin: {
|
||||||
@@ -781,7 +818,6 @@ Item {
|
|||||||
focus: true
|
focus: true
|
||||||
enabled: !demoMode
|
enabled: !demoMode
|
||||||
activeFocusOnTab: !demoMode
|
activeFocusOnTab: !demoMode
|
||||||
echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (!demoMode) {
|
if (!demoMode) {
|
||||||
root.passwordBuffer = text;
|
root.passwordBuffer = text;
|
||||||
@@ -809,6 +845,8 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
clear();
|
clear();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pam.passwd.active) {
|
if (pam.passwd.active) {
|
||||||
@@ -816,6 +854,23 @@ Item {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||||
|
accepted();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Backspace) {
|
||||||
|
backspace();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrintableText(event.text)) {
|
||||||
|
insertText(event.text);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -849,6 +904,17 @@ Item {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
|
||||||
|
function onPasswordBufferChanged() {
|
||||||
|
if (passwordField.text === root.passwordBuffer)
|
||||||
|
return;
|
||||||
|
passwordField.text = root.passwordBuffer;
|
||||||
|
passwordField.cursorPosition = passwordField.text.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardController {
|
KeyboardController {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user