mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-07 19:59:14 -04:00
feat(mangowm): add live config reloads & misc QOL updates
- Hide workspace tags during Mango overview - Add HJKL focus/move defaults - Add Mango natural touchpad scrolling & cursor configs - Fix Mango startup
This commit is contained in:
@@ -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.")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,34 +283,63 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if len(parts) < 2 {
|
||||||
continue
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := strings.TrimSpace(parts[0])
|
||||||
|
if !m.isBindPrefix(prefix) {
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
content := strings.TrimSpace(parts[1])
|
content := strings.TrimSpace(parts[1])
|
||||||
commentParts := strings.SplitN(content, "#", 2)
|
commentParts := strings.SplitN(content, "#", 2)
|
||||||
bindContent := strings.TrimSpace(commentParts[0])
|
bindContent := strings.TrimSpace(commentParts[0])
|
||||||
|
|
||||||
var comment string
|
description := strings.TrimSpace(precedingComment)
|
||||||
|
if isMangoWCSectionComment(description) {
|
||||||
|
description = ""
|
||||||
|
}
|
||||||
if len(commentParts) > 1 {
|
if len(commentParts) > 1 {
|
||||||
comment = strings.TrimSpace(commentParts[1])
|
description = strings.TrimSpace(commentParts[1])
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(description, MangoWCHideComment) {
|
||||||
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.SplitN(bindContent, ",", 4)
|
fields := strings.SplitN(bindContent, ",", 4)
|
||||||
if len(fields) < 3 {
|
if len(fields) < 3 {
|
||||||
continue
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
mods := strings.TrimSpace(fields[0])
|
mods := strings.TrimSpace(fields[0])
|
||||||
@@ -311,21 +351,29 @@ func (m *MangoWCProvider) loadOverrideBinds() (map[string]*mangowcOverrideBind,
|
|||||||
params = strings.TrimSpace(fields[3])
|
params = strings.TrimSpace(fields[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
keyStr := m.buildKeyString(mods, keyName)
|
|
||||||
normalizedKey := strings.ToLower(keyStr)
|
|
||||||
action := command
|
action := command
|
||||||
if params != "" {
|
if params != "" {
|
||||||
action = command + " " + params
|
action = command + " " + params
|
||||||
}
|
}
|
||||||
|
|
||||||
binds[normalizedKey] = &mangowcOverrideBind{
|
return &mangowcOverrideBind{
|
||||||
Key: keyStr,
|
Key: m.buildKeyString(mods, keyName),
|
||||||
Action: action,
|
Action: action,
|
||||||
Description: comment,
|
Description: description,
|
||||||
}
|
Prefix: prefix,
|
||||||
}
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
return binds, nil
|
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 {
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ func TestMangoWCGetKeybindAtLine(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
line string
|
line string
|
||||||
|
precedingComment string
|
||||||
expected *MangoWCKeyBinding
|
expected *MangoWCKeyBinding
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,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: {
|
||||||
@@ -160,7 +161,11 @@ Item {
|
|||||||
baseList = getHyprlandWorkspaces();
|
baseList = getHyprlandWorkspaces();
|
||||||
break;
|
break;
|
||||||
case "dwl":
|
case "dwl":
|
||||||
|
baseList = getDwlTags();
|
||||||
|
break;
|
||||||
case "mango":
|
case "mango":
|
||||||
|
if (root.mangoOverviewActive)
|
||||||
|
return [];
|
||||||
baseList = getDwlTags();
|
baseList = getDwlTags();
|
||||||
break;
|
break;
|
||||||
case "sway":
|
case "sway":
|
||||||
@@ -977,7 +982,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
|
||||||
@@ -1115,7 +1120,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.isDwlLike) {
|
||||||
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;
|
||||||
|
|||||||
@@ -2432,6 +2432,17 @@ Item {
|
|||||||
onSliderValueChanged: newValue => SettingsData.setCursorSize(newValue)
|
onSliderValueChanged: newValue => SettingsData.setCursorSize(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["mango", "touchpad", "trackpad", "natural", "scrolling"]
|
||||||
|
settingKey: "mangoTrackpadNaturalScrolling"
|
||||||
|
text: I18n.tr("Natural Touchpad Scrolling")
|
||||||
|
description: I18n.tr("Invert touchpad scroll direction")
|
||||||
|
visible: CompositorService.isMango
|
||||||
|
checked: SettingsData.mangoTrackpadNaturalScrolling
|
||||||
|
onToggled: checked => SettingsData.set("mangoTrackpadNaturalScrolling", checked)
|
||||||
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
tab: "theme"
|
tab: "theme"
|
||||||
tags: ["cursor", "hide", "typing"]
|
tags: ["cursor", "hide", "typing"]
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ Item {
|
|||||||
settingKey: "dwlShowAllTags"
|
settingKey: "dwlShowAllTags"
|
||||||
tags: ["dwl", "tags", "workspace"]
|
tags: ["dwl", "tags", "workspace"]
|
||||||
text: I18n.tr("Show All Tags")
|
text: I18n.tr("Show All Tags")
|
||||||
description: I18n.tr("Show all 9 tags instead of only occupied tags (DWL only)")
|
description: I18n.tr("Show all 9 tags instead of only occupied tags")
|
||||||
checked: SettingsData.dwlShowAllTags
|
checked: SettingsData.dwlShowAllTags
|
||||||
visible: CompositorService.isDwl || CompositorService.isMango
|
visible: CompositorService.isDwl || CompositorService.isMango
|
||||||
onToggled: checked => SettingsData.set("dwlShowAllTags", checked)
|
onToggled: checked => SettingsData.set("dwlShowAllTags", checked)
|
||||||
|
|||||||
@@ -21,12 +21,18 @@ Singleton {
|
|||||||
readonly property bool available: socketPath.length > 0
|
readonly property bool available: socketPath.length > 0
|
||||||
|
|
||||||
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
readonly property string configPath: configDir + "/mango/config.conf"
|
||||||
readonly property string mangoDmsDir: configDir + "/mango/dms"
|
readonly property string mangoDmsDir: configDir + "/mango/dms"
|
||||||
|
readonly property string bindsPath: mangoDmsDir + "/binds.conf"
|
||||||
|
readonly property string colorsPath: mangoDmsDir + "/colors.conf"
|
||||||
readonly property string outputsPath: mangoDmsDir + "/outputs.conf"
|
readonly property string outputsPath: mangoDmsDir + "/outputs.conf"
|
||||||
readonly property string layoutPath: mangoDmsDir + "/layout.conf"
|
readonly property string layoutPath: mangoDmsDir + "/layout.conf"
|
||||||
readonly property string cursorPath: mangoDmsDir + "/cursor.conf"
|
readonly property string cursorPath: mangoDmsDir + "/cursor.conf"
|
||||||
|
readonly property string windowRulesPath: mangoDmsDir + "/windowrules.conf"
|
||||||
|
|
||||||
property int _lastGapValue: -1
|
property int _lastGapValue: -1
|
||||||
|
property real _ignoreWatchedReloadUntil: 0
|
||||||
|
property real _lastWatchedReloadAt: 0
|
||||||
|
|
||||||
// name -> { name, active, x, y, width, height, scale, layoutIndex,
|
// name -> { name, active, x, y, width, height, scale, layoutIndex,
|
||||||
// layoutSymbol, lastOpenSurface, kbLayout, keymode,
|
// layoutSymbol, lastOpenSurface, kbLayout, keymode,
|
||||||
@@ -47,6 +53,55 @@ Singleton {
|
|||||||
// One connection per watch target; mango streams a fresh full snapshot on
|
// One connection per watch target; mango streams a fresh full snapshot on
|
||||||
// every change, so each line is treated as the complete state.
|
// every change, so each line is treated as the complete state.
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: mangoConfigWatcher
|
||||||
|
path: CompositorService.isMango ? root.configPath : ""
|
||||||
|
watchChanges: CompositorService.isMango
|
||||||
|
onFileChanged: root.handleWatchedConfigChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: mangoBindsWatcher
|
||||||
|
path: CompositorService.isMango ? root.bindsPath : ""
|
||||||
|
watchChanges: CompositorService.isMango
|
||||||
|
onFileChanged: root.handleWatchedConfigChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: mangoColorsWatcher
|
||||||
|
path: CompositorService.isMango ? root.colorsPath : ""
|
||||||
|
watchChanges: CompositorService.isMango
|
||||||
|
onFileChanged: root.handleWatchedConfigChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: mangoLayoutWatcher
|
||||||
|
path: CompositorService.isMango ? root.layoutPath : ""
|
||||||
|
watchChanges: CompositorService.isMango
|
||||||
|
onFileChanged: root.handleWatchedConfigChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: mangoCursorWatcher
|
||||||
|
path: CompositorService.isMango ? root.cursorPath : ""
|
||||||
|
watchChanges: CompositorService.isMango
|
||||||
|
onFileChanged: root.handleWatchedConfigChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: mangoOutputsWatcher
|
||||||
|
path: CompositorService.isMango ? root.outputsPath : ""
|
||||||
|
watchChanges: CompositorService.isMango
|
||||||
|
onFileChanged: root.handleWatchedConfigChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: mangoWindowRulesWatcher
|
||||||
|
path: CompositorService.isMango ? root.windowRulesPath : ""
|
||||||
|
watchChanges: CompositorService.isMango
|
||||||
|
onFileChanged: root.handleWatchedConfigChanged()
|
||||||
|
}
|
||||||
|
|
||||||
DankSocket {
|
DankSocket {
|
||||||
id: monitorsSocket
|
id: monitorsSocket
|
||||||
path: root.socketPath
|
path: root.socketPath
|
||||||
@@ -100,12 +155,14 @@ Singleton {
|
|||||||
for (const m of monitors) {
|
for (const m of monitors) {
|
||||||
if (!m.name)
|
if (!m.name)
|
||||||
continue;
|
continue;
|
||||||
|
const activeTags = m.active_tags || [];
|
||||||
|
const inOverview = activeTags.length === 0 || activeTags.every(t => t === 0);
|
||||||
const tags = (m.tags || []).map(t => ({
|
const tags = (m.tags || []).map(t => ({
|
||||||
// 0-based to match the legacy dwl tag model used by consumers
|
// 0-based to match the legacy dwl tag model used by consumers
|
||||||
"tag": (t.index ?? 1) - 1,
|
"tag": (t.index ?? 1) - 1,
|
||||||
"state": t.is_urgent ? 2 : (t.is_active ? 1 : 0),
|
"state": t.is_urgent ? 2 : (!inOverview && t.is_active ? 1 : 0),
|
||||||
"clients": t.client_count ?? 0,
|
"clients": t.client_count ?? 0,
|
||||||
"focused": !!t.is_active,
|
"focused": !inOverview && !!t.is_active,
|
||||||
"urgent": !!t.is_urgent,
|
"urgent": !!t.is_urgent,
|
||||||
"layout": t.layout ?? ""
|
"layout": t.layout ?? ""
|
||||||
}));
|
}));
|
||||||
@@ -119,7 +176,8 @@ Singleton {
|
|||||||
"scale": m.scale ?? 1.0,
|
"scale": m.scale ?? 1.0,
|
||||||
"layoutIndex": m.layout_index ?? 0,
|
"layoutIndex": m.layout_index ?? 0,
|
||||||
"layout": m.layout_index ?? 0,
|
"layout": m.layout_index ?? 0,
|
||||||
"activeTags": m.active_tags || [],
|
"activeTags": activeTags,
|
||||||
|
"inOverview": inOverview,
|
||||||
"layoutSymbol": m.layout_symbol ?? "",
|
"layoutSymbol": m.layout_symbol ?? "",
|
||||||
"lastOpenSurface": m.last_open_surface ?? "",
|
"lastOpenSurface": m.last_open_surface ?? "",
|
||||||
"keymode": m.keymode ?? "",
|
"keymode": m.keymode ?? "",
|
||||||
@@ -179,6 +237,8 @@ Singleton {
|
|||||||
const output = getOutputState(outputName);
|
const output = getOutputState(outputName);
|
||||||
if (!output)
|
if (!output)
|
||||||
return false;
|
return false;
|
||||||
|
if (output.inOverview !== undefined)
|
||||||
|
return output.inOverview;
|
||||||
const at = output.activeTags || [];
|
const at = output.activeTags || [];
|
||||||
return at.length === 0 || at.every(t => t === 0);
|
return at.length === 0 || at.every(t => t === 0);
|
||||||
}
|
}
|
||||||
@@ -201,6 +261,8 @@ Singleton {
|
|||||||
const output = getOutputState(outputName);
|
const output = getOutputState(outputName);
|
||||||
if (!output || !output.tags)
|
if (!output || !output.tags)
|
||||||
return [];
|
return [];
|
||||||
|
if (isOutputInOverview(outputName))
|
||||||
|
return [];
|
||||||
const visibleTags = new Set();
|
const visibleTags = new Set();
|
||||||
output.tags.forEach(tag => {
|
output.tags.forEach(tag => {
|
||||||
if (tag.state === 1 || tag.clients > 0)
|
if (tag.state === 1 || tag.clients > 0)
|
||||||
@@ -336,10 +398,36 @@ Singleton {
|
|||||||
|
|
||||||
// ── Commands (mango verb IPC: mmsg dispatch <func>,<args>) ─────────────
|
// ── Commands (mango verb IPC: mmsg dispatch <func>,<args>) ─────────────
|
||||||
|
|
||||||
function reloadConfig() {
|
function suppressWatchedConfigReloads(ms) {
|
||||||
|
root._ignoreWatchedReloadUntil = Math.max(root._ignoreWatchedReloadUntil, Date.now() + (ms || 1500));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWatchedConfigChanged() {
|
||||||
|
if (!CompositorService.isMango || !root.available)
|
||||||
|
return;
|
||||||
|
const now = Date.now();
|
||||||
|
if (now < root._ignoreWatchedReloadUntil)
|
||||||
|
return;
|
||||||
|
if (now - root._lastWatchedReloadAt < 700)
|
||||||
|
return;
|
||||||
|
root._lastWatchedReloadAt = now;
|
||||||
|
root.reloadConfig(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadConfig(showToast, suppressWatch) {
|
||||||
|
const shouldShowToast = showToast !== false;
|
||||||
|
const shouldSuppressWatch = suppressWatch !== false;
|
||||||
|
if (shouldSuppressWatch)
|
||||||
|
suppressWatchedConfigReloads(1500);
|
||||||
Proc.runCommand("mango-reload", ["mmsg", "dispatch", "reload_config"], (output, exitCode) => {
|
Proc.runCommand("mango-reload", ["mmsg", "dispatch", "reload_config"], (output, exitCode) => {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0) {
|
||||||
log.warn("mmsg reload_config failed:", output);
|
log.warn("mmsg reload_config failed:", output);
|
||||||
|
if (shouldShowToast)
|
||||||
|
ToastService.showError(I18n.tr("mango: failed to reload config"), output || "", "", "mango-config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (shouldShowToast)
|
||||||
|
ToastService.showInfo(I18n.tr("mango: config reloaded"), "", "", "mango-config");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,17 +626,10 @@ borderpx=${borderSize}
|
|||||||
const themeName = settings.theme === "System Default" ? (SettingsData.systemDefaultCursorTheme || "") : settings.theme;
|
const themeName = settings.theme === "System Default" ? (SettingsData.systemDefaultCursorTheme || "") : settings.theme;
|
||||||
const size = settings.size || 24;
|
const size = settings.size || 24;
|
||||||
const hideTimeout = settings.mango?.cursorHideTimeout || 0;
|
const hideTimeout = settings.mango?.cursorHideTimeout || 0;
|
||||||
|
const naturalScrolling = SettingsData.mangoTrackpadNaturalScrolling ? 1 : 0;
|
||||||
const isDefaultConfig = !themeName && size === 24 && hideTimeout === 0;
|
|
||||||
if (isDefaultConfig) {
|
|
||||||
Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => {
|
|
||||||
if (exitCode !== 0)
|
|
||||||
log.warn("Failed to write cursor config:", output);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = `# Auto-generated by DMS - do not edit manually
|
let content = `# Auto-generated by DMS - do not edit manually
|
||||||
|
trackpad_natural_scrolling=${naturalScrolling}
|
||||||
cursor_size=${size}`;
|
cursor_size=${size}`;
|
||||||
|
|
||||||
if (themeName)
|
if (themeName)
|
||||||
|
|||||||
Reference in New Issue
Block a user