mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 23:34:01 -04:00
217 lines
6.3 KiB
Go
217 lines
6.3 KiB
Go
// Package mangoconf maps Mango config keys between the run-together names of
|
|
// Mango <= 0.17.5 and the snake_case names of upstream 8adc6bc3 (mangowm/mango#1491).
|
|
// Neither parser accepts the other's names.
|
|
package mangoconf
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type Dialect int
|
|
|
|
const (
|
|
Legacy Dialect = iota
|
|
Snake
|
|
)
|
|
|
|
// Upstream migration order: where two legacy names share a snake name, the
|
|
// window-rule spelling comes first and wins the reverse lookup.
|
|
var renames = [][2]string{
|
|
{"windowrule-once", "window_rule_once"},
|
|
{"windowrule", "window_rule"},
|
|
{"monitorrule", "monitor_rule"},
|
|
{"tagrule", "tag_rule"},
|
|
{"layerrule", "layer_rule"},
|
|
{"devicerule", "device_rule"},
|
|
{"source-optional", "source_optional"},
|
|
{"exec-once", "exec_once"},
|
|
{"keymode", "key_mode"},
|
|
{"isnoborder", "no_border"},
|
|
{"isnoshadow", "no_shadow"},
|
|
{"isnoradius", "no_radius"},
|
|
{"isnoanimation", "no_animation"},
|
|
{"isnosizehint", "no_size_hint"},
|
|
{"noblur", "no_blur"},
|
|
{"noshadow", "no_shadow"},
|
|
{"noanim", "no_animation"},
|
|
{"nofocus", "no_focus"},
|
|
{"nofadein", "no_fade_in"},
|
|
{"nofadeout", "no_fade_out"},
|
|
{"noswallow", "no_swallow"},
|
|
{"special_gappih", "special_gap_inner_horizontal"},
|
|
{"special_gappiv", "special_gap_inner_vertical"},
|
|
{"special_gappoh", "special_gap_outer_horizontal"},
|
|
{"special_gappov", "special_gap_outer_vertical"},
|
|
{"gappih", "gap_inner_horizontal"},
|
|
{"gappiv", "gap_inner_vertical"},
|
|
{"gappoh", "gap_outer_horizontal"},
|
|
{"gappov", "gap_outer_vertical"},
|
|
{"overviewgappi", "overview_gap_inner"},
|
|
{"overviewgappo", "overview_gap_outer"},
|
|
{"default_mfact", "default_master_factor"},
|
|
{"default_nmaster", "default_master_count"},
|
|
{"animation_curve_opafadein", "animation_curve_opacity_fade_in"},
|
|
{"animation_curve_opafadeout", "animation_curve_opacity_fade_out"},
|
|
{"dwindle_hsplit", "dwindle_horizontal_split"},
|
|
{"dwindle_vsplit", "dwindle_vertical_split"},
|
|
{"fadein_begin_opacity", "fade_in_begin_opacity"},
|
|
{"fadeout_begin_opacity", "fade_out_begin_opacity"},
|
|
{"focusdir_only_zone_overlap", "focus_direction_only_zone_overlap"},
|
|
{"force_fakemaximize", "force_fake_maximize"},
|
|
{"idleinhibit_ignore_visible", "idle_inhibit_ignore_visible"},
|
|
{"idleinhibit_when_focus", "idle_inhibit_when_focus"},
|
|
{"idleinhibit_when_fullscreen", "idle_inhibit_when_fullscreen"},
|
|
{"syncobj_enable", "sync_obj_enable"},
|
|
{"isfakefullscreen", "is_fake_fullscreen"},
|
|
{"isfloating", "is_floating"},
|
|
{"isfullscreen", "is_fullscreen"},
|
|
{"isunglobal", "is_unmanaged_global"},
|
|
{"isglobal", "is_global"},
|
|
{"isnamedscratchpad", "is_named_scratchpad"},
|
|
{"isopensilent", "is_open_silent"},
|
|
{"isoverlay", "is_overlay"},
|
|
{"istagsilent", "is_tag_silent"},
|
|
{"isterm", "is_term"},
|
|
{"appid", "app_id"},
|
|
{"bordercolor", "border_color"},
|
|
{"borderpx", "border_px"},
|
|
{"dropcolor", "drop_color"},
|
|
{"focuscolor", "focus_color"},
|
|
{"globalcolor", "global_color"},
|
|
{"globalkeybinding", "global_key_binding"},
|
|
{"maximizescreencolor", "maximized_screen_color"},
|
|
{"mfact", "master_factor"},
|
|
{"nmaster", "master_count"},
|
|
{"numlockon", "numlock_on"},
|
|
{"offsetx", "offset_x"},
|
|
{"offsety", "offset_y"},
|
|
{"overlaycolor", "overlay_color"},
|
|
{"rootcolor", "root_color"},
|
|
{"scratchpadcolor", "scratchpad_color"},
|
|
{"shadowscolor", "shadows_color"},
|
|
{"sloppyfocus", "sloppy_focus"},
|
|
{"smartgaps", "smart_gaps"},
|
|
{"splitcolor", "split_color"},
|
|
{"urgentcolor", "urgent_color"},
|
|
{"warpcursor", "warp_cursor"},
|
|
}
|
|
|
|
var toSnake, toLegacy = func() (map[string]string, map[string]string) {
|
|
fwd := make(map[string]string, len(renames))
|
|
rev := make(map[string]string, len(renames))
|
|
for _, r := range renames {
|
|
fwd[r[0]] = r[1]
|
|
if _, ok := rev[r[1]]; !ok {
|
|
rev[r[1]] = r[0]
|
|
}
|
|
}
|
|
return fwd, rev
|
|
}()
|
|
|
|
// Normalize returns the snake_case name for a key in either dialect.
|
|
func Normalize(key string) string {
|
|
if s, ok := toSnake[key]; ok {
|
|
return s
|
|
}
|
|
return key
|
|
}
|
|
|
|
// Key spells a snake_case key in this dialect.
|
|
func (d Dialect) Key(snake string) string {
|
|
if d == Snake {
|
|
return snake
|
|
}
|
|
if l, ok := toLegacy[snake]; ok {
|
|
return l
|
|
}
|
|
return snake
|
|
}
|
|
|
|
var keyLine = regexp.MustCompile(`^(\s*)([A-Za-z][A-Za-z_-]*)(\s*=\s*)(.*)$`)
|
|
|
|
// Translate respells every line key, and the field keys of rule lines, in this dialect.
|
|
func (d Dialect) Translate(text string) string {
|
|
lines := strings.Split(text, "\n")
|
|
for i, line := range lines {
|
|
m := keyLine.FindStringSubmatch(line)
|
|
if m == nil {
|
|
continue
|
|
}
|
|
key := Normalize(m[2])
|
|
value := m[4]
|
|
switch key {
|
|
case "window_rule", "window_rule_once", "tag_rule":
|
|
value = d.translateFields(value, nil)
|
|
case "layer_rule":
|
|
value = d.translateFields(value, legacyLayerFields)
|
|
}
|
|
lines[i] = m[1] + d.Key(key) + m[3] + value
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
// Layer rules spell these without the window rules' "is" prefix.
|
|
var legacyLayerFields = map[string]string{"no_shadow": "noshadow", "no_animation": "noanim"}
|
|
|
|
func (d Dialect) translateFields(value string, legacyOverride map[string]string) string {
|
|
parts := strings.Split(value, ",")
|
|
for i, part := range parts {
|
|
k, v, ok := strings.Cut(part, ":")
|
|
if !ok {
|
|
continue
|
|
}
|
|
trimmed := strings.TrimSpace(k)
|
|
key := d.Key(Normalize(trimmed))
|
|
if l, ok := legacyOverride[Normalize(trimmed)]; ok && d == Legacy {
|
|
key = l
|
|
}
|
|
parts[i] = strings.Replace(k, trimmed, key, 1) + ":" + v
|
|
}
|
|
return strings.Join(parts, ",")
|
|
}
|
|
|
|
// Legacy builds carry exec_once as a symbol name, so it cannot tell the dialects apart.
|
|
var snakeMarker = []byte("gap_inner_horizontal")
|
|
|
|
var (
|
|
detectOnce sync.Once
|
|
detected Dialect
|
|
)
|
|
|
|
// Detect reads the mango binary: snake_case shipped on master builds still reporting 0.17.5, before the 0.18.0 release.
|
|
func Detect() Dialect {
|
|
detectOnce.Do(func() {
|
|
path, err := exec.LookPath("mango")
|
|
if err != nil {
|
|
return
|
|
}
|
|
detected = DetectBinary(path)
|
|
})
|
|
return detected
|
|
}
|
|
|
|
func DetectBinary(path string) Dialect {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil || !bytes.Contains(data, snakeMarker) {
|
|
return Legacy
|
|
}
|
|
return Snake
|
|
}
|
|
|
|
// Mango reads each line into a 512-byte buffer and the value into 255 bytes; longer is cut silently.
|
|
const maxValueLen = 255
|
|
|
|
func CheckLine(line string) error {
|
|
_, value, ok := strings.Cut(line, "=")
|
|
if !ok || len(strings.TrimSpace(value)) <= maxValueLen {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("line is %d characters after '='; Mango truncates past %d", len(strings.TrimSpace(value)), maxValueLen)
|
|
}
|