mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
Feature/split move size hyprland windowrules (#2824)
* windowrules: add split move/size fields for Lua table syntax * windowrules: remove deprecated Move/Size fields, switch QML to split fields * fix: use resolved dms binary path in Proc.runCommand
This commit is contained in:
+10
-2
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
Reference in New Issue
Block a user