package init import ( "fmt" "os/exec" "strconv" "strings" "sectorinf.com/emilis/hlctl/cmdlets" "sectorinf.com/emilis/hlctl/config" "sectorinf.com/emilis/hlctl/ctllog" "sectorinf.com/emilis/hlctl/groupctl" "sectorinf.com/emilis/hlctl/hlcl" "sectorinf.com/emilis/hlctl/hlcl/cmd" "sectorinf.com/emilis/hlctl/panelctl" "sectorinf.com/emilis/hlctl/svcctl" ) const ( Return = "Return" Shift = "Shift" Tab = "Tab" Left = "Left" Right = "Right" Up = "Up" Down = "Down" Space = "space" Control = "Control" Backtick = "grave" ) var log = ctllog.Logger{}.New("init", ctllog.Green) type command struct{} var Command command func (command) Name() string { return "init" } func (command) Usage() cmdlets.Usage { return cmdlets.Usage{ Name: Command.Name(), Short: "Initialize herbstluftwm, should be run from " + "the autostart script", Long: []string{ "`init` starts the configuration of herbstluftwm with the", "default configuration, setting up keybinds, hlctl groups,", "and other commands.", }, Flags: map[string]string{}, Examples: []string{ "hcctl init", }, } } func logError(err error) { if err != nil { log.Error(err.Error()) } } type Keybind struct { Keys []string Command cmd.Command Args []string } func (k Keybind) Set() { err := hlcl.Keybind(k.Keys, k.Command, k.Args...) if err != nil { log.Errorf( "Keybind [%s]: %s", strings.Join(k.Args, "-"), err, ) } else { binds := strings.Join(k.Keys, "-") command := strings.Join( append([]string{k.Command.String()}, k.Args...), " ", ) log.Printf("%s -> [%s]", binds, command) } } func Keybinds(cfg config.HlwmConfig) []Keybind { // convenience for setting []string c := func(v ...string) []string { return v } key := func(keys []string, cm cmd.Command, args ...string) Keybind { return Keybind{ Keys: keys, Command: cm, Args: args, } } Mod := cfg.Mod step := "+0.05" base := []Keybind{ key(c(Mod, Shift, "r"), cmd.Reload), key(c(Mod, Shift, "c"), cmd.Close), key(c(Mod, Return), cmd.Spawn, "dmenu_run"), key(c(Mod, "s"), cmd.Spawn, "flameshot", "gui"), key(c(Mod, cfg.TermSpawnKey), cmd.Spawn, cfg.Term), // Focus key(c(Mod, Left), cmd.Focus, "left"), key(c(Mod, Right), cmd.Focus, "right"), key(c(Mod, Up), cmd.Focus, "up"), key(c(Mod, Down), cmd.Focus, "down"), key(c(Mod, Tab), cmd.Cycle), key(c(Mod, "i"), cmd.JumpTo, "urgent"), // Move frames key(c(Mod, Shift, Left), cmd.Shift, "left"), key(c(Mod, Shift, Right), cmd.Shift, "right"), key(c(Mod, Shift, Up), cmd.Shift, "up"), key(c(Mod, Shift, Down), cmd.Shift, "down"), // Frames key(c(Mod, "u"), cmd.Split, "bottom", "0.7"), key(c(Mod, "o"), cmd.Split, "right", "0.5"), key(c(Mod, "r"), cmd.Remove), key(c(Mod, "f"), cmd.Fullscreen, "toggle"), key(c(Mod, Space), cmd.CycleLayout), // Resizing key(c(Mod, Control, Left), cmd.Resize, "left", step), key(c(Mod, Control, Right), cmd.Resize, "right", step), key(c(Mod, Control, Up), cmd.Resize, "up", step), key(c(Mod, Control, Down), cmd.Resize, "down", step), // Commands key(c(Mod, Backtick), cmd.Spawn, "hlctl", "group", "+"), } for i := 0; i < int(cfg.Groups.Groups); i++ { base = append( base, key(c(Mod, fmt.Sprintf("F%d", i+1)), cmd.Spawn, "hlctl", "group", strconv.Itoa(i+1)), ) } return base } type set struct { name string value any } func (s set) String() string { return fmt.Sprint(s.value) } func Theme(cfg config.HlwmConfig) { s := func(name string, value any) set { return set{ name: name, value: value, } } col := cfg.Theme.Colors // Theme sets for _, row := range [...]set{ s("frame_border_active_color", col.Active), s("frame_border_normal_color", "black"), s("frame_bg_active_color", col.Active), s("frame_border_width", 2), s("show_frame_decorations", "focused_if_multiple"), s("tree_style", "╾│ ├└╼─┐"), s("frame_bg_transparent", "on"), s("smart_window_surroundings", "off"), // TODO s("smart_frame_surroundings", "on"), s("frame_transparent_width", 5), s("frame_gap", 3), s("window_gap", 0), s("frame_padding", 0), s("mouse_recenter_gap", 0), } { value := row.String() if err := hlcl.Set(row.name, value); err != nil { log.Errorf("Set [%s] -> [%s]: %s", row.name, value, err) } } // Theme attrs for _, row := range [...]set{ s("reset", 1), s("tiling.reset", 1), s("floating.reset", 1), s("title_height", 15), s("title_when", "multiple_tabs"), s("title_font", cfg.FontBold), s("title_depth", 3), s("inner_width", 0), s("inner_color", "black"), s("border_width", 1), s("floating.border_width", 4), s("floating.outer_width", 1), s("tiling.outer_width", 1), s("background_color", "black"), s("active.color", col.Active), s("active.inner_color", col.Active), s("active.outer_color", col.Active), s("normal.color", col.Normal), s("normal.inner_color", col.Normal), s("normal.outer_color", col.Normal), s("urgent.color", col.Urgent), s("urgent.inner_color", col.Urgent), s("urgent.outer_color", col.Urgent), s("title_color", "white"), s("normal.title_color", col.Text), } { attr := fmt.Sprintf("theme.%s", row.name) v := row.String() log.Printf("Setting [%s] -> [%s]", attr, v) if err := hlcl.SetAttr(attr, v); err != nil { log.Errorf("Attr [%s] -> [%s]: %s", attr, v, err) } } } func SetRules() { sFmt := "windowtype~_NET_WM_WINDOW_TYPE_%s" logError(hlcl.Rule("focus=on")) logError(hlcl.Rule("floatplacement=smart")) logError(hlcl.Rule("fixedsize", "floating=on")) logError(hlcl.Rule( fmt.Sprintf(sFmt, "(DIALOG|UTILITY|SPLASH)"), "floating=on", )) logError(hlcl.Rule( fmt.Sprintf(sFmt, "(NOTIFICATION|DOCK|DESKTOP)"), "manage=off", )) } func AddTags(groupCount, tagsPerGroup uint8) error { for group := 0; group < int(groupCount); group++ { for tag := 0; tag < int(tagsPerGroup); tag++ { err := hlcl.AddTag(fmt.Sprintf("%d|%d", group+1, tag+1)) if err != nil { return fmt.Errorf( "Adding tag [%d] for group [%d]: %s", tag+1, group+1, err, ) } } } defaultAttr, err := hlcl.GetAttr("tags.by-name.default.index") if err != nil { // no default log.Print("No 'default' tag, continuing...") return nil } focus, err := config.GetFocusedTag() if err != nil { return fmt.Errorf("(removing default) getting current tag: %s", err) } def, err := strconv.ParseUint(defaultAttr, 10, 8) if err != nil { return fmt.Errorf("Bug? default index not uint: %s", err) } if uint8(def) == focus { if err := hlcl.UseIndex(focus + 1); err != nil { log.Warnf("Tried switching away from default: %s", err) } } if err := hlcl.MergeTag("default"); err != nil { return fmt.Errorf("merging default: %s", err) } return nil } func (command) Invoke(args ...string) error { log.Print("begining herbstluftwm setup via hlctl") log.Print("loading config") cfg := config.Load() if err := cfg.Validate(); err != nil { return fmt.Errorf("validating config: %s", err) } if err := cfg.SetAll(); err != nil { return fmt.Errorf("setting cfg values to hlwm: %s", err) } exec.Command("xsetroot", "-solid", "black").Run() logError(hlcl.KeyUnbind()) log.Print("Adding tags") logError(AddTags(cfg.Groups.Groups, cfg.Groups.Tags)) logError(groupctl.ResetActive()) for _, key := range Keybinds(cfg) { key.Set() } log.Print("Resetting groups") logError(groupctl.ResetActive()) log.Print("Theming...") Theme(cfg) log.Print("Rules") SetRules() log.Print("Unlocking") if err := hlcl.Unlock(); err != nil { return fmt.Errorf("failed unlock: %w", err) } svcctl.RestartServices(cfg.Services) log.Print("Running panels") return panelctl.Run() }