package group import ( "errors" "fmt" "strconv" "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/notifyctl" ) var log = ctllog.Logger{}.New("group", ctllog.ColorRGB{}.FromHex("27ee7e")) type command struct{} var Command command func (command) Name() string { return "group" } func (command) Usage() cmdlets.Usage { return cmdlets.Usage{ Name: Command.Name(), Short: "Controls tag grouping and behavior", Long: []string{ "`group` is responsible for controlling how groups behave", "in hlwm, and switching between them", }, Flags: map[string]string{ "+": "Jump to next group", "-": "Jump to previous group", "[number]": "Jump to [number] group", "move [number]": "Move the currently focused window to group [number]", }, Examples: []string{ "hcctl group +", "hcctl group -", "hcctl group 2", "hcctl group move 2", }, } } func (command) Invoke(args ...string) error { if len(args) == 0 { return errors.New("group requires arguments") } arg := args[0] if arg == "move" && len(args) < 2 { return errors.New("group move requires an argument") } var active uint8 currentActiveCfg, err := config.Get(config.ActiveGroup) if err != nil { log.Warnf( "error getting active group, defaulting to 1: %s", err.Error(), ) active = 1 } else { val, err := currentActiveCfg.Uint() mustUint(config.ActiveGroup, err) active = uint8(val) } log.Print("Loading num of tags per group") numCfg, err := config.Get(config.TagsPerGroup) if err != nil { return fmt.Errorf("%s: %w", config.TagsPerGroup, err) } num, err := numCfg.Uint() mustUint(config.TagsPerGroup, err) log.Print(num, "tags per group") focus, err := config.GetFocusedTag() if err != nil { return fmt.Errorf("get focused tag: %w", err) } log.Print("Currently in", focus, "tag") maxCfg, err := config.Get(config.GroupCount) if err != nil { return fmt.Errorf("%s: %w", config.GroupCount, err) } max, err := maxCfg.Uint() mustUint(config.GroupCount, err) var ( newActive uint8 ) switch arg { case "+": newActive = shiftGroup(false, active, uint8(max)) case "-": newActive = shiftGroup(true, active, uint8(max)) case "move": a, err := strconv.ParseUint(arg, 10, 8) target := uint8(a) if err != nil || target < 1 || target > max { return fmt.Errorf("out of range, use [1:%d]", max) } return groupctl.MoveTo(uint8(a)) default: a, err := strconv.ParseUint(arg, 10, 8) target := uint8(a) if err != nil || target < 1 || target > max { return fmt.Errorf("out of range, use [1:%d]", max) } newActive = uint8(a) } if active != newActive { log.Printf("Group %d -> %d", active, newActive) config.Set(config.ActiveGroup, strconv.Itoa(int(newActive))) // shift the tag index if err := groupctl.Update(); err != nil { return fmt.Errorf( "updating groups [%d -> %d]: %w", active, newActive, err, ) } } if err := groupctl.SetKeybinds(newActive, uint8(num)); err != nil { return fmt.Errorf("SetKeybinds: %w", err) } fg, _ := config.Get(config.ColorText) bg, _ := config.Get(config.ColorNormal) font, _ := config.Get(config.Font) return notifyctl.Display( fmt.Sprintf("Group %d", newActive), 2, fg.String(), bg.String(), font.String(), ) } func moveTo(currentTag, currentGroup, newGroup, num uint8) error { relTag := (currentTag - ((currentGroup - 1) * num)) newTag := ((newGroup - 1) * num) + relTag return hlcl.MoveIndex(uint8(newTag)) } func mustUint(cfg string, err error) { if err != nil { panic(fmt.Sprintf( "%s must be set as uint: %s", cfg, err.Error(), )) } } func shiftGroup(backwards bool, active, num uint8) uint8 { // Maintain the logic of the fish scripts, // 0 == 1, act as if groups are 1 indexed if backwards && active <= 1 { active = num } else if backwards { active-- } else if active >= num { active = 1 } else { active++ } return uint8(active) }