diff --git a/core/cmd/dms/shell.go b/core/cmd/dms/shell.go index 998db7142..41df6ea11 100644 --- a/core/cmd/dms/shell.go +++ b/core/cmd/dms/shell.go @@ -92,6 +92,14 @@ func appendLogEnv(env []string) []string { return env } +func withDMSExecutable(env []string) []string { + selfPath, err := os.Executable() + if err != nil { + return env + } + return append(env, "DMS_EXECUTABLE="+selfPath) +} + func hasSystemdRun() bool { _, err := exec.LookPath("systemd-run") return err == nil @@ -207,7 +215,7 @@ func runShellInteractive(session bool) { log.Infof("Spawning quickshell with -p %s", configPath) cmd := exec.CommandContext(ctx, "qs", "-p", configPath) - cmd.Env = append(os.Environ(), "DMS_SOCKET="+socketPath) + cmd.Env = withDMSExecutable(append(os.Environ(), "DMS_SOCKET="+socketPath)) if os.Getenv("QT_LOGGING_RULES") == "" { if qtRules := log.GetQtLoggingRules(); qtRules != "" { cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules) @@ -461,7 +469,7 @@ func runShellDaemon(session bool) { log.Infof("Spawning quickshell with -p %s", configPath) cmd := exec.CommandContext(ctx, "qs", "-p", configPath) - cmd.Env = append(os.Environ(), "DMS_SOCKET="+socketPath) + cmd.Env = withDMSExecutable(append(os.Environ(), "DMS_SOCKET="+socketPath)) if os.Getenv("QT_LOGGING_RULES") == "" { if qtRules := log.GetQtLoggingRules(); qtRules != "" { cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules) diff --git a/core/internal/windowrules/providers/hyprland_parser.go b/core/internal/windowrules/providers/hyprland_parser.go index aa4c65823..b378591b1 100644 --- a/core/internal/windowrules/providers/hyprland_parser.go +++ b/core/internal/windowrules/providers/hyprland_parser.go @@ -384,10 +384,6 @@ func applyHyprlandRuleAction(actions *windowrules.Actions, rule, value string) { if f, err := strconv.ParseFloat(value, 64); err == nil { actions.Opacity = &f } - case "size": - actions.Size = value - case "move": - actions.Move = value case "monitor": actions.Monitor = value case "workspace": @@ -564,6 +560,13 @@ func hyprLuaBoolStr(b bool) string { return "false" } +func hyprLuaExprWrap(v string) string { + if _, err := strconv.ParseFloat(v, 64); err == nil { + return v + } + return strconv.Quote(v) +} + func luaAppendMatch(mc windowrules.MatchCriteria, dst *[]string) { if mc.AppID != "" { *dst = append(*dst, fmt.Sprintf(`class = %s`, strconv.Quote(mc.AppID))) @@ -634,11 +637,11 @@ func luaAppendActions(a windowrules.Actions, dst *[]string) { if a.Opacity != nil { *dst = append(*dst, fmt.Sprintf(`opacity = %s`, strconv.FormatFloat(*a.Opacity, 'g', -1, 64))) } - if a.Size != "" { - *dst = append(*dst, fmt.Sprintf(`size = %s`, strconv.Quote(a.Size))) + if a.SizeWidth != "" && a.SizeHeight != "" { + *dst = append(*dst, fmt.Sprintf(`size = { %s, %s }`, hyprLuaExprWrap(a.SizeWidth), hyprLuaExprWrap(a.SizeHeight))) } - if a.Move != "" { - *dst = append(*dst, fmt.Sprintf(`move = %s`, strconv.Quote(a.Move))) + if a.MoveX != "" && a.MoveY != "" { + *dst = append(*dst, fmt.Sprintf(`move = { %s, %s }`, hyprLuaExprWrap(a.MoveX), hyprLuaExprWrap(a.MoveY))) } if a.Monitor != "" { *dst = append(*dst, fmt.Sprintf(`monitor = %s`, strconv.Quote(a.Monitor))) @@ -1194,7 +1197,11 @@ func luaStringValue(s string) string { } } } - return strings.Trim(strings.TrimSpace(s), `"'`) + v := strings.Trim(strings.TrimSpace(s), `"'`) + if len(v) >= 2 && v[0] == '(' && v[len(v)-1] == ')' { + v = strings.TrimSpace(v[1 : len(v)-1]) + } + return v } func luaBoolLike(s string) (val bool, ok bool) { @@ -1349,11 +1356,29 @@ func applyLuaActionKey(a *windowrules.Actions, key, raw string) bool { } } case "size": - a.Size = strings.TrimSpace(luaStringValue(raw)) - return true + v := strings.TrimSpace(luaStringValue(raw)) + if strings.HasPrefix(v, "{") && strings.HasSuffix(v, "}") { + inner := trimOuterBraces(v) + parts := splitTopLevelCommaLua(inner) + if len(parts) == 2 { + a.SizeWidth = strings.TrimSpace(luaStringValue(parts[0])) + a.SizeHeight = strings.TrimSpace(luaStringValue(parts[1])) + return true + } + } + return false case "move": - a.Move = strings.TrimSpace(luaStringValue(raw)) - return true + v := strings.TrimSpace(luaStringValue(raw)) + if strings.HasPrefix(v, "{") && strings.HasSuffix(v, "}") { + inner := trimOuterBraces(v) + parts := splitTopLevelCommaLua(inner) + if len(parts) == 2 { + a.MoveX = strings.TrimSpace(luaStringValue(parts[0])) + a.MoveY = strings.TrimSpace(luaStringValue(parts[1])) + return true + } + } + return false case "monitor": a.Monitor = strings.TrimSpace(luaStringValue(raw)) return true diff --git a/core/internal/windowrules/providers/hyprland_parser_test.go b/core/internal/windowrules/providers/hyprland_parser_test.go index 92a8d3813..8107ffe13 100644 --- a/core/internal/windowrules/providers/hyprland_parser_test.go +++ b/core/internal/windowrules/providers/hyprland_parser_test.go @@ -400,3 +400,196 @@ func TestBoolToInt(t *testing.T) { t.Error("boolToInt(false) should be 0") } } + +func TestLuaAppendActionsTableSyntax(t *testing.T) { + actions := windowrules.Actions{ + SizeWidth: "800", + SizeHeight: "600", + MoveX: "100", + MoveY: "200", + } + + var out []string + luaAppendActions(actions, &out) + joined := strings.Join(out, "\n") + for _, want := range []string{ + `size = { 800, 600 }`, + `move = { 100, 200 }`, + } { + if !strings.Contains(joined, want) { + t.Errorf("expected output to contain %q, got:\n%s", want, joined) + } + } +} + +func TestLuaAppendActionsExprWrap(t *testing.T) { + actions := windowrules.Actions{ + SizeWidth: "window_w * 0.5", + SizeHeight: "window_h - 50", + MoveX: "100", + MoveY: "(monitor_h / 2) + 17", + } + + var out []string + luaAppendActions(actions, &out) + joined := strings.Join(out, "\n") + for _, want := range []string{ + `size = { "window_w * 0.5", "window_h - 50" }`, + `move = { 100, "(monitor_h / 2) + 17" }`, + } { + if !strings.Contains(joined, want) { + t.Errorf("expected output to contain %q, got:\n%s", want, joined) + } + } +} + +func TestApplyLuaActionKeyTableSyntax(t *testing.T) { + tests := []struct { + name string + key string + raw string + wantSizeW string + wantSizeH string + wantMoveX string + wantMoveY string + }{ + { + name: "size table syntax", + key: "size", + raw: `{ 800, 600 }`, + wantSizeW: "800", + wantSizeH: "600", + }, + { + name: "move table syntax", + key: "move", + raw: `{ 100, 200 }`, + wantMoveX: "100", + wantMoveY: "200", + }, + { + name: "size string syntax returns false", + key: "size", + raw: `"800x600"`, + }, + { + name: "move string syntax returns false", + key: "move", + raw: `"100 200"`, + }, + { + name: "size expressions", + key: "size", + raw: `{ "window_w * 0.5", "window_h - 50" }`, + wantSizeW: "window_w * 0.5", + wantSizeH: "window_h - 50", + }, + { + name: "move expressions", + key: "move", + raw: `{ 100, "(monitor_h / 2) + 17" }`, + wantMoveX: "100", + wantMoveY: "(monitor_h / 2) + 17", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var a windowrules.Actions + result := applyLuaActionKey(&a, tt.key, tt.raw) + if tt.wantSizeW == "" && tt.wantSizeH == "" && tt.wantMoveX == "" && tt.wantMoveY == "" { + if result { + t.Errorf("expected applyLuaActionKey to return false for string syntax, got true") + } + return + } + if !result { + t.Fatal("applyLuaActionKey returned false") + } + if tt.wantSizeW != "" && a.SizeWidth != tt.wantSizeW { + t.Errorf("SizeWidth = %q, want %q", a.SizeWidth, tt.wantSizeW) + } + if tt.wantSizeH != "" && a.SizeHeight != tt.wantSizeH { + t.Errorf("SizeHeight = %q, want %q", a.SizeHeight, tt.wantSizeH) + } + if tt.wantMoveX != "" && a.MoveX != tt.wantMoveX { + t.Errorf("MoveX = %q, want %q", a.MoveX, tt.wantMoveX) + } + if tt.wantMoveY != "" && a.MoveY != tt.wantMoveY { + t.Errorf("MoveY = %q, want %q", a.MoveY, tt.wantMoveY) + } + }) + } +} + +func TestLuaRoundTripTableSyntax(t *testing.T) { + original := windowrules.Actions{ + SizeWidth: "800", + SizeHeight: "600", + MoveX: "100", + MoveY: "200", + } + + var out []string + luaAppendActions(original, &out) + + var parsed windowrules.Actions + for _, line := range out { + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + val := strings.TrimSpace(parts[1]) + applyLuaActionKey(&parsed, key, val) + } + + if parsed.SizeWidth != original.SizeWidth { + t.Errorf("SizeWidth = %q, want %q", parsed.SizeWidth, original.SizeWidth) + } + if parsed.SizeHeight != original.SizeHeight { + t.Errorf("SizeHeight = %q, want %q", parsed.SizeHeight, original.SizeHeight) + } + if parsed.MoveX != original.MoveX { + t.Errorf("MoveX = %q, want %q", parsed.MoveX, original.MoveX) + } + if parsed.MoveY != original.MoveY { + t.Errorf("MoveY = %q, want %q", parsed.MoveY, original.MoveY) + } +} + +func TestLuaRoundTripTableSyntaxExpressions(t *testing.T) { + original := windowrules.Actions{ + SizeWidth: "window_w * 0.5", + SizeHeight: "window_h - 50", + MoveX: "100", + MoveY: "(monitor_h / 2) + 17", + } + + var out []string + luaAppendActions(original, &out) + + var parsed windowrules.Actions + for _, line := range out { + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + val := strings.TrimSpace(parts[1]) + applyLuaActionKey(&parsed, key, val) + } + + if parsed.SizeWidth != original.SizeWidth { + t.Errorf("SizeWidth = %q, want %q", parsed.SizeWidth, original.SizeWidth) + } + if parsed.SizeHeight != original.SizeHeight { + t.Errorf("SizeHeight = %q, want %q", parsed.SizeHeight, original.SizeHeight) + } + if parsed.MoveX != original.MoveX { + t.Errorf("MoveX = %q, want %q", parsed.MoveX, original.MoveX) + } + if parsed.MoveY != original.MoveY { + t.Errorf("MoveY = %q, want %q", parsed.MoveY, original.MoveY) + } +} diff --git a/core/internal/windowrules/providers/mango_parser.go b/core/internal/windowrules/providers/mango_parser.go index 923cdc9f7..66f163090 100644 --- a/core/internal/windowrules/providers/mango_parser.go +++ b/core/internal/windowrules/providers/mango_parser.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" "regexp" - "strconv" "strings" "github.com/AvengeMedia/DankMaterialShell/core/internal/windowrules" @@ -168,7 +167,8 @@ func ConvertMangoRulesToWindowRules(mangoRules []MangoWindowRule) []windowrules. } if w, ok := f["width"]; ok { if h, ok2 := f["height"]; ok2 { - actions.Size = w + "x" + h + actions.SizeWidth = w + actions.SizeHeight = h } } @@ -200,11 +200,9 @@ func formatMangoRule(rule windowrules.WindowRule) string { add("tags", rule.Actions.Workspace) add("monitor", rule.Actions.Monitor) - if rule.Actions.Size != "" { - if w, h, ok := splitSize(rule.Actions.Size); ok { - add("width", w) - add("height", h) - } + if rule.Actions.SizeWidth != "" && rule.Actions.SizeHeight != "" { + add("width", rule.Actions.SizeWidth) + add("height", rule.Actions.SizeHeight) } addBool := func(k string, b *bool) { @@ -223,19 +221,6 @@ func formatMangoRule(rule windowrules.WindowRule) string { return "windowrule=" + strings.Join(parts, ",") } -func splitSize(size string) (w, h string, ok bool) { - for _, sep := range []string{"x", "X", " "} { - if parts := strings.Split(size, sep); len(parts) == 2 { - w = strings.TrimSpace(parts[0]) - h = strings.TrimSpace(parts[1]) - if _, err := strconv.ParseFloat(w, 64); err == nil { - return w, h, true - } - } - } - return "", "", false -} - type MangoWritableProvider struct { configDir string } diff --git a/core/internal/windowrules/providers/mango_parser_test.go b/core/internal/windowrules/providers/mango_parser_test.go index 2581ccfb5..1702425ef 100644 --- a/core/internal/windowrules/providers/mango_parser_test.go +++ b/core/internal/windowrules/providers/mango_parser_test.go @@ -65,7 +65,8 @@ func TestMangoSetAndLoadRoundTrip(t *testing.T) { Actions: windowrules.Actions{ OpenFloating: &floating, Workspace: "9", - Size: "1000x900", + SizeWidth: "1000", + SizeHeight: "900", }, } @@ -98,8 +99,11 @@ func TestMangoSetAndLoadRoundTrip(t *testing.T) { if got.Actions.Workspace != "9" { t.Errorf("Workspace = %q, want 9", got.Actions.Workspace) } - if got.Actions.Size != "1000x900" { - t.Errorf("Size = %q, want 1000x900", got.Actions.Size) + if got.Actions.SizeWidth != "1000" { + t.Errorf("SizeWidth = %q, want 1000", got.Actions.SizeWidth) + } + if got.Actions.SizeHeight != "900" { + t.Errorf("SizeHeight = %q, want 900", got.Actions.SizeHeight) } if got.Actions.OpenFloating == nil || !*got.Actions.OpenFloating { t.Errorf("OpenFloating = %v, want true", got.Actions.OpenFloating) @@ -114,3 +118,40 @@ func TestMangoSetAndLoadRoundTrip(t *testing.T) { t.Errorf("after remove got %d rules, want 0", len(loaded)) } } + +func TestMangoRoundTripWithSizeWidthHeight(t *testing.T) { + tmpDir := t.TempDir() + provider := NewMangoWritableProvider(tmpDir) + + rule := windowrules.WindowRule{ + ID: "rule_roundtrip", + Name: "Size Test", + Enabled: true, + MatchCriteria: windowrules.MatchCriteria{ + AppID: "testapp", + }, + Actions: windowrules.Actions{ + SizeWidth: "800", + SizeHeight: "600", + }, + } + + if err := provider.SetRule(rule); err != nil { + t.Fatalf("SetRule: %v", err) + } + + loaded, err := provider.LoadDMSRules() + if err != nil { + t.Fatalf("LoadDMSRules: %v", err) + } + if len(loaded) != 1 { + t.Fatalf("got %d rules, want 1", len(loaded)) + } + got := loaded[0] + if got.Actions.SizeWidth != "800" { + t.Errorf("SizeWidth = %q, want 800", got.Actions.SizeWidth) + } + if got.Actions.SizeHeight != "600" { + t.Errorf("SizeHeight = %q, want 600", got.Actions.SizeHeight) + } +} diff --git a/core/internal/windowrules/types.go b/core/internal/windowrules/types.go index 5d3fdb71c..1b6b9c904 100644 --- a/core/internal/windowrules/types.go +++ b/core/internal/windowrules/types.go @@ -51,8 +51,10 @@ type Actions struct { DefaultFloatingX *int `json:"defaultFloatingX,omitempty"` DefaultFloatingY *int `json:"defaultFloatingY,omitempty"` DefaultFloatingRelativeTo string `json:"defaultFloatingRelativeTo,omitempty"` - Size string `json:"size,omitempty"` - Move string `json:"move,omitempty"` + MoveX string `json:"moveX,omitempty"` + MoveY string `json:"moveY,omitempty"` + SizeWidth string `json:"sizeWidth,omitempty"` + SizeHeight string `json:"sizeHeight,omitempty"` Monitor string `json:"monitor,omitempty"` Workspace string `json:"workspace,omitempty"` Tile *bool `json:"tile,omitempty"` diff --git a/quickshell/Common/Proc.qml b/quickshell/Common/Proc.qml index b38f51401..3a6e2d1ae 100644 --- a/quickshell/Common/Proc.qml +++ b/quickshell/Common/Proc.qml @@ -11,6 +11,7 @@ Singleton { readonly property var log: Log.scoped("Proc") readonly property int noTimeout: -1 + readonly property string dmsBin: Quickshell.env("DMS_EXECUTABLE") || "dms" property int defaultDebounceMs: 50 property int defaultTimeoutMs: 10000 property var _procDebouncers: ({}) diff --git a/quickshell/Modals/DankColorPickerModal.qml b/quickshell/Modals/DankColorPickerModal.qml index f7cbbb126..61135c4e6 100644 --- a/quickshell/Modals/DankColorPickerModal.qml +++ b/quickshell/Modals/DankColorPickerModal.qml @@ -104,7 +104,7 @@ DankModal { function pickColorFromScreen() { hideInstant(); - Proc.runCommand("dms-color-pick", ["dms", "color", "pick", "--json"], (output, exitCode) => { + Proc.runCommand("dms-color-pick", [Proc.dmsBin, "color", "pick", "--json"], (output, exitCode) => { if (exitCode !== 0) { log.warn("dms color pick exited with code:", exitCode); root.show(); diff --git a/quickshell/Modals/WindowRuleModal.qml b/quickshell/Modals/WindowRuleModal.qml index b8c4769e8..765e3c81c 100644 --- a/quickshell/Modals/WindowRuleModal.qml +++ b/quickshell/Modals/WindowRuleModal.qml @@ -94,8 +94,10 @@ FloatingWindow { noRoundingToggle.checked = false; pinToggle.checked = false; opaqueToggle.checked = false; - sizeInput.text = ""; - moveInput.text = ""; + moveXInput.text = ""; + moveYInput.text = ""; + sizeWInput.text = ""; + sizeHInput.text = ""; monitorInput.text = ""; hyprWorkspaceInput.text = ""; mangoTagsInput.text = ""; @@ -219,14 +221,16 @@ FloatingWindow { noRoundingToggle.checked = actions.norounding || false; pinToggle.checked = actions.pin || false; opaqueToggle.checked = actions.opaque || false; - sizeInput.text = actions.size || ""; - moveInput.text = actions.move || ""; + moveXInput.text = actions.moveX || ""; + moveYInput.text = actions.moveY || ""; + sizeWInput.text = actions.sizeWidth || ""; + sizeHInput.text = actions.sizeHeight || ""; monitorInput.text = actions.monitor || ""; hyprWorkspaceInput.text = actions.workspace || ""; mangoTagsInput.text = actions.workspace || ""; mangoMonitorInput.text = actions.monitor || ""; - mangoSizeInput.text = actions.size || ""; + mangoSizeInput.text = (actions.sizeWidth && actions.sizeHeight) ? actions.sizeWidth + "x" + actions.sizeHeight : ""; mangoNoBlurToggle.checked = actions.noblur || false; mangoNoBorderToggle.checked = actions.noborder || false; mangoNoShadowToggle.checked = actions.noshadow || false; @@ -400,10 +404,14 @@ FloatingWindow { actions.pin = true; if (opaqueToggle.checked) actions.opaque = true; - if (sizeInput.text.trim()) - actions.size = sizeInput.text.trim(); - if (moveInput.text.trim()) - actions.move = moveInput.text.trim(); + if (sizeWInput.text.trim()) + actions.sizeWidth = sizeWInput.text.trim(); + if (sizeHInput.text.trim()) + actions.sizeHeight = sizeHInput.text.trim(); + if (moveXInput.text.trim()) + actions.moveX = moveXInput.text.trim(); + if (moveYInput.text.trim()) + actions.moveY = moveYInput.text.trim(); if (monitorInput.text.trim()) actions.monitor = monitorInput.text.trim(); if (hyprWorkspaceInput.text.trim()) @@ -415,8 +423,13 @@ FloatingWindow { actions.workspace = mangoTagsInput.text.trim(); if (mangoMonitorInput.text.trim()) actions.monitor = mangoMonitorInput.text.trim(); - if (mangoSizeInput.text.trim()) - actions.size = mangoSizeInput.text.trim(); + if (mangoSizeInput.text.trim()) { + const parts = mangoSizeInput.text.trim().split(/x/i); + if (parts.length === 2) { + actions.sizeWidth = parts[0].trim(); + actions.sizeHeight = parts[1].trim(); + } + } if (mangoNoBlurToggle.checked) actions.noblur = true; if (mangoNoBorderToggle.checked) @@ -447,7 +460,7 @@ FloatingWindow { if (isEditMode) { const ruleJson = JSON.stringify(ruleData); - Proc.runCommand("update-windowrule", ["dms", "config", "windowrules", "update", compositor, editingRule.id, ruleJson], (output, exitCode) => { + Proc.runCommand("update-windowrule", [Proc.dmsBin, "config", "windowrules", "update", compositor, editingRule.id, ruleJson], (output, exitCode) => { root.submitting = false; if (exitCode !== 0) return; @@ -460,7 +473,7 @@ FloatingWindow { }); } else { const ruleJson = JSON.stringify(ruleData); - Proc.runCommand("add-windowrule", ["dms", "config", "windowrules", "add", compositor, ruleJson], (output, exitCode) => { + Proc.runCommand("add-windowrule", [Proc.dmsBin, "config", "windowrules", "add", compositor, ruleJson], (output, exitCode) => { root.submitting = false; if (exitCode !== 0) return; @@ -1580,11 +1593,11 @@ FloatingWindow { visible: isHyprland Column { - width: (parent.width - Theme.spacingM) / 2 + width: (parent.width - Theme.spacingM * 3) / 4 spacing: Theme.spacingXS StyledText { - text: I18n.tr("Size") + text: I18n.tr("X") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText width: parent.width @@ -1593,13 +1606,13 @@ FloatingWindow { InputField { width: parent.width - hasFocus: sizeInput.activeFocus + hasFocus: moveXInput.activeFocus DankTextField { - id: sizeInput + id: moveXInput anchors.fill: parent font.pixelSize: Theme.fontSizeSmall textColor: Theme.surfaceText - placeholderText: "800 600" + placeholderText: "0" backgroundColor: "transparent" enabled: root.visible } @@ -1607,11 +1620,11 @@ FloatingWindow { } Column { - width: (parent.width - Theme.spacingM) / 2 + width: (parent.width - Theme.spacingM * 3) / 4 spacing: Theme.spacingXS StyledText { - text: I18n.tr("Move") + text: I18n.tr("Y") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText width: parent.width @@ -1620,13 +1633,67 @@ FloatingWindow { InputField { width: parent.width - hasFocus: moveInput.activeFocus + hasFocus: moveYInput.activeFocus DankTextField { - id: moveInput + id: moveYInput anchors.fill: parent font.pixelSize: Theme.fontSizeSmall textColor: Theme.surfaceText - placeholderText: "100 100" + placeholderText: "0" + backgroundColor: "transparent" + enabled: root.visible + } + } + } + + Column { + width: (parent.width - Theme.spacingM * 3) / 4 + spacing: Theme.spacingXS + + StyledText { + text: I18n.tr("W") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + horizontalAlignment: Text.AlignLeft + } + + InputField { + width: parent.width + hasFocus: sizeWInput.activeFocus + DankTextField { + id: sizeWInput + anchors.fill: parent + font.pixelSize: Theme.fontSizeSmall + textColor: Theme.surfaceText + placeholderText: "800" + backgroundColor: "transparent" + enabled: root.visible + } + } + } + + Column { + width: (parent.width - Theme.spacingM * 3) / 4 + spacing: Theme.spacingXS + + StyledText { + text: I18n.tr("H") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + horizontalAlignment: Text.AlignLeft + } + + InputField { + width: parent.width + hasFocus: sizeHInput.activeFocus + DankTextField { + id: sizeHInput + anchors.fill: parent + font.pixelSize: Theme.fontSizeSmall + textColor: Theme.surfaceText + placeholderText: "600" backgroundColor: "transparent" enabled: root.visible } diff --git a/quickshell/Modules/Settings/CompositorLayoutTab.qml b/quickshell/Modules/Settings/CompositorLayoutTab.qml index d0c042b39..397bcced9 100644 --- a/quickshell/Modules/Settings/CompositorLayoutTab.qml +++ b/quickshell/Modules/Settings/CompositorLayoutTab.qml @@ -68,7 +68,7 @@ Item { const compositorArg = compositor === "mango" ? "mangowc" : compositor; checkingInclude = true; - Proc.runCommand("check-layout-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => { + Proc.runCommand("check-layout-include", [Proc.dmsBin, "config", "resolve-include", compositorArg, filename], (output, exitCode) => { checkingInclude = false; if (exitCode !== 0) { layoutIncludeStatus = { diff --git a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml index 5b7776508..7cba71cbb 100644 --- a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml +++ b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml @@ -1443,7 +1443,7 @@ Singleton { const compositorArg = (compositor === "mango") ? "mangowc" : compositor; checkingInclude = true; - Proc.runCommand("check-outputs-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => { + Proc.runCommand("check-outputs-include", [Proc.dmsBin, "config", "resolve-include", compositorArg, filename], (output, exitCode) => { checkingInclude = false; if (exitCode !== 0) { includeStatus = { diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml index 808cedabb..0860c6620 100644 --- a/quickshell/Modules/Settings/ThemeColorsTab.qml +++ b/quickshell/Modules/Settings/ThemeColorsTab.qml @@ -117,7 +117,7 @@ Item { const compositorArg = (compositor === "mango") ? "mangowc" : compositor; checkingCursorInclude = true; - Proc.runCommand("check-cursor-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => { + Proc.runCommand("check-cursor-include", [Proc.dmsBin, "config", "resolve-include", compositorArg, filename], (output, exitCode) => { checkingCursorInclude = false; if (exitCode !== 0) { cursorIncludeStatus = { @@ -238,7 +238,7 @@ Item { return; matugenPreviewRequestKey = requestKey; - Proc.runCommand("", ["dms", "matugen", "preview", "--source-color", sourceColor, "--contrast", contrast.toString()], (output, exitCode) => { + Proc.runCommand("", [Proc.dmsBin, "matugen", "preview", "--source-color", sourceColor, "--contrast", contrast.toString()], (output, exitCode) => { if (requestKey !== themeColorsTab.matugenPreviewRequestKey) return; if (exitCode !== 0) { @@ -262,7 +262,7 @@ Item { DMSService.listInstalledThemes(); if (PopoutService.pendingThemeInstall) Qt.callLater(() => showThemeBrowser()); - Proc.runCommand("template-check", ["dms", "matugen", "check"], (output, exitCode) => { + Proc.runCommand("template-check", [Proc.dmsBin, "matugen", "check"], (output, exitCode) => { if (exitCode !== 0) return; try { diff --git a/quickshell/Modules/Settings/WindowRulesTab.qml b/quickshell/Modules/Settings/WindowRulesTab.qml index 7cc3829b3..f1cc88ee7 100644 --- a/quickshell/Modules/Settings/WindowRulesTab.qml +++ b/quickshell/Modules/Settings/WindowRulesTab.qml @@ -122,8 +122,10 @@ Item { "norounding": I18n.tr("No Round"), "pin": I18n.tr("Pin"), "opaque": I18n.tr("Opaque"), - "size": I18n.tr("Size"), - "move": I18n.tr("Move"), + "sizeWidth": I18n.tr("W"), + "sizeHeight": I18n.tr("H"), + "moveX": I18n.tr("X"), + "moveY": I18n.tr("Y"), "monitor": I18n.tr("Monitor"), "workspace": I18n.tr("Workspace"), "drawBorderWithBackground": I18n.tr("Border w/ Bg"), @@ -198,7 +200,7 @@ Item { } checkingInclude = true; - Proc.runCommand("load-windowrules", ["dms", "config", "windowrules", "list", compositor], (output, exitCode) => { + Proc.runCommand("load-windowrules", [Proc.dmsBin, "config", "windowrules", "list", compositor], (output, exitCode) => { checkingInclude = false; if (exitCode !== 0) { windowRules = []; @@ -234,7 +236,7 @@ Item { if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") return; - Proc.runCommand("remove-windowrule", ["dms", "config", "windowrules", "remove", compositor, ruleId], (output, exitCode) => { + Proc.runCommand("remove-windowrule", [Proc.dmsBin, "config", "windowrules", "remove", compositor, ruleId], (output, exitCode) => { if (exitCode === 0) { if (CompositorService.isMango) MangoService.reloadConfig(); @@ -260,7 +262,7 @@ Item { const [moved] = ids.splice(fromIndex, 1); ids.splice(toIndex, 0, moved); - Proc.runCommand("reorder-windowrules", ["dms", "config", "windowrules", "reorder", compositor, JSON.stringify(ids)], (output, exitCode) => { + Proc.runCommand("reorder-windowrules", [Proc.dmsBin, "config", "windowrules", "reorder", compositor, JSON.stringify(ids)], (output, exitCode) => { if (exitCode === 0) { if (CompositorService.isMango) MangoService.reloadConfig(); diff --git a/quickshell/Services/CompositorService.qml b/quickshell/Services/CompositorService.qml index 976cfc52c..c521d6491 100644 --- a/quickshell/Services/CompositorService.qml +++ b/quickshell/Services/CompositorService.qml @@ -46,7 +46,7 @@ Singleton { signal toplevelsChanged function fetchRandrData() { - Proc.runCommand("randr", ["dms", "randr", "--json"], (output, exitCode) => { + Proc.runCommand("randr", [Proc.dmsBin, "randr", "--json"], (output, exitCode) => { if (exitCode === 0 && output) { try { const data = JSON.parse(output.trim()); diff --git a/quickshell/Services/HyprlandService.qml b/quickshell/Services/HyprlandService.qml index 40b4d5ad5..61e29dab0 100644 --- a/quickshell/Services/HyprlandService.qml +++ b/quickshell/Services/HyprlandService.qml @@ -128,7 +128,7 @@ Singleton { return; luaConfigStatusLoading = true; - Proc.runCommand("hypr-lua-config-status", ["dms", "config", "resolve-include", "hyprland", "outputs.lua"], (output, exitCode) => { + Proc.runCommand("hypr-lua-config-status", [Proc.dmsBin, "config", "resolve-include", "hyprland", "outputs.lua"], (output, exitCode) => { luaConfigStatusLoading = false; luaConfigStatusReady = true; if (exitCode !== 0) { diff --git a/quickshell/Services/TrashService.qml b/quickshell/Services/TrashService.qml index 967f386d9..ea31d925c 100644 --- a/quickshell/Services/TrashService.qml +++ b/quickshell/Services/TrashService.qml @@ -58,7 +58,7 @@ Singleton { } function refreshCount() { - Proc.runCommand("trash-count", ["dms", "trash", "count"], (output, exitCode) => { + Proc.runCommand("trash-count", [Proc.dmsBin, "trash", "count"], (output, exitCode) => { if (exitCode !== 0) { root.count = homeTrashModel.count; return; @@ -74,7 +74,7 @@ Singleton { callback(false, "empty path"); return; } - Proc.runCommand(null, ["dms", "trash", "put", path], (output, exitCode) => { + Proc.runCommand(null, [Proc.dmsBin, "trash", "put", path], (output, exitCode) => { const ok = exitCode === 0; if (!ok) ToastService.showError(I18n.tr("Failed to move to trash"), path); @@ -121,7 +121,7 @@ Singleton { } function emptyTrash() { - Proc.runCommand("trash-empty", ["dms", "trash", "empty"], (output, exitCode) => { + Proc.runCommand("trash-empty", [Proc.dmsBin, "trash", "empty"], (output, exitCode) => { if (exitCode !== 0) ToastService.showError(I18n.tr("Failed to empty trash"), output || ""); refreshCount(); diff --git a/quickshell/Widgets/DankLocationSearch.qml b/quickshell/Widgets/DankLocationSearch.qml index 7c4309229..31875495d 100644 --- a/quickshell/Widgets/DankLocationSearch.qml +++ b/quickshell/Widgets/DankLocationSearch.qml @@ -59,7 +59,7 @@ Item { root.currentSearchText = searchLocation; const encodedLocation = encodeURIComponent(searchLocation); const searchUrl = "https://nominatim.openstreetmap.org/search?q=" + encodedLocation + "&format=json&limit=5&addressdetails=1"; - Proc.runCommand("locationSearch", ["dms", "dl", "-4", "--timeout", "10", searchUrl], (output, exitCode) => { + Proc.runCommand("locationSearch", [Proc.dmsBin, "dl", "-4", "--timeout", "10", searchUrl], (output, exitCode) => { root.isLoading = false; if (exitCode !== 0) { searchResultsModel.clear();