hlctl/cmdlets/init/init.go

320 lines
7.6 KiB
Go

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()
}