Files
Marlon Kranz 72c23161e7 fix(hyprland): write window rules Hyprland 0.56 accepts, keep DMS windows opaque (#3655)
* fix(hyprland): write window rules Hyprland 0.56 accepts, keep DMS windows opaque

The Lua window-rule writer emitted match keys and effects Hyprland 0.56
does not know: `floating`/`pinned`/`initialised` as match keys and
`noborder`/`norounding` as effects. It now writes `float`/`pin` and
`border_size = 0`/`rounding = 0`; the parser still reads the old
spellings, so existing files load and are rewritten on the next save.
"No Rounding" now suppresses the corner radius so a rule has one
`rounding` key.

The rule editor no longer offers fields the Hyprland writer drops
(Window Opening output/workspace, Size Constraints, the Initialised
match), and Save is disabled while only one half of a move or size
pair is filled. Every window-rule write now reloads the compositor
through one helper, which also covers Hyprland.

DMS windows draw their own translucent background, so any compositor
opacity rule or decoration opacity multiplied on top of it. DMS now
keeps a hidden rule for its own app id: `opaque = true` on Hyprland
(applied after rule and decoration opacity, independent of order, and
leaving the window's own alpha alone) and `opacity 1.0` on niri, which
the store keeps last. It is re-added on start when missing and hidden
from the rules list and the focused-window menu.

Compositor layout gains a Hyprland "Window opacity" slider writing
decoration.active_opacity/inactive_opacity; 100 leaves the user's own
config in charge.

HyprlandService.reloadConfig queues callbacks: Proc keeps one callback
per id, so a reload requested while the layout writer's reload was
pending dropped its callback and left layout generation stuck.

* fix(windowrules): cap corner radius at 20

Hyprland 0.56's Lua window_rule rejects rounding above 20 ("value 24
is more than the maximum of 20") and the rule loses its rounding. The
Hyprland writer now clamps to 20, and the editor slider stops at 20
for every compositor.
2026-10-07 14:09:49 -04:00

105 lines
2.2 KiB
Go

package windowrules
type RuleStore interface {
EnsureWritable() error
LoadDMSRules() ([]WindowRule, error)
WriteDMSRules([]WindowRule) error
}
const OpaqueRuleID = "dms-window-opaque"
// pinDMSRules puts the opaque rule last, since compositors apply rules in
// order and the last match wins.
func pinDMSRules(rules []WindowRule) []WindowRule {
var rest, last []WindowRule
for _, rule := range rules {
if rule.ID == OpaqueRuleID {
last = append(last, rule)
} else {
rest = append(rest, rule)
}
}
return append(rest, last...)
}
func Set(store RuleStore, rule WindowRule) error {
if err := store.EnsureWritable(); err != nil {
return err
}
rules, err := store.LoadDMSRules()
if err != nil {
rules = []WindowRule{}
}
for i, existing := range rules {
if existing.ID != rule.ID {
continue
}
rules[i] = rule
return store.WriteDMSRules(pinDMSRules(rules))
}
return store.WriteDMSRules(pinDMSRules(append(rules, rule)))
}
func Remove(store RuleStore, id string) error {
if err := store.EnsureWritable(); err != nil {
return err
}
rules, err := store.LoadDMSRules()
if err != nil {
return err
}
kept := make([]WindowRule, 0, len(rules))
for _, rule := range rules {
if rule.ID == id {
continue
}
kept = append(kept, rule)
}
return store.WriteDMSRules(pinDMSRules(kept))
}
func Reorder(store RuleStore, ids []string) error {
if err := store.EnsureWritable(); err != nil {
return err
}
rules, err := store.LoadDMSRules()
if err != nil {
return err
}
wanted := make(map[string]int, len(ids))
for position, id := range ids {
if _, seen := wanted[id]; seen {
continue
}
wanted[id] = position
}
ordered := make([]WindowRule, len(ids))
placed := make([]bool, len(ids))
leftover := make([]WindowRule, 0, len(rules))
for _, rule := range rules {
position, ok := wanted[rule.ID]
if !ok || placed[position] {
leftover = append(leftover, rule)
continue
}
ordered[position] = rule
placed[position] = true
}
result := make([]WindowRule, 0, len(rules))
for position, rule := range ordered {
if !placed[position] {
continue
}
result = append(result, rule)
}
return store.WriteDMSRules(pinDMSRules(append(result, leftover...)))
}