Initial commit
This commit is contained in:
commit
23bb7a61ba
|
@ -0,0 +1,2 @@
|
||||||
|
# Openbsd core dumps
|
||||||
|
*.core
|
|
@ -0,0 +1,47 @@
|
||||||
|
package cmdlets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Usage struct {
|
||||||
|
Name string
|
||||||
|
Short string
|
||||||
|
Long []string
|
||||||
|
Flags map[string]string
|
||||||
|
Examples []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Usage) String() string {
|
||||||
|
str := strings.Builder{}
|
||||||
|
str.WriteString(
|
||||||
|
fmt.Sprintf("%s SUBCOMMAND\n\n", strings.ToUpper(u.Name)),
|
||||||
|
)
|
||||||
|
str.WriteString(fmt.Sprintf("\t%s\t%s\n\n", u.Name, u.Short))
|
||||||
|
for _, line := range u.Long {
|
||||||
|
str.WriteString(fmt.Sprintf("\t%s\n", line))
|
||||||
|
}
|
||||||
|
str.WriteRune('\n')
|
||||||
|
if len(u.Flags) != 0 {
|
||||||
|
for flag, desc := range u.Flags {
|
||||||
|
str.WriteString(fmt.Sprintf("\t%s\n\t\t%s\n", flag, desc))
|
||||||
|
}
|
||||||
|
str.WriteRune('\n')
|
||||||
|
}
|
||||||
|
if len(u.Examples) != 0 {
|
||||||
|
str.WriteString("EXAMPLES\n\n")
|
||||||
|
for _, example := range u.Examples {
|
||||||
|
str.WriteString(fmt.Sprintf("\t%s\n", example))
|
||||||
|
}
|
||||||
|
str.WriteRune('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Commandlet interface {
|
||||||
|
Name() string
|
||||||
|
Usage() Usage
|
||||||
|
Invoke(args ...string) error
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,323 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := hlcl.Lock(); err != nil {
|
||||||
|
return fmt.Errorf("failed lock: %w", 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()
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sectorinf.com/emilis/hlctl/cmdlets"
|
||||||
|
"sectorinf.com/emilis/hlctl/config"
|
||||||
|
"sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
"sectorinf.com/emilis/hlctl/notifyctl"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = ctllog.Logger{}.New("notify", ctllog.ColorRGB{}.FromHex("ff003e"))
|
||||||
|
|
||||||
|
type command struct{}
|
||||||
|
|
||||||
|
var Command command
|
||||||
|
|
||||||
|
func (command) Name() string {
|
||||||
|
return "notify"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (command) Usage() cmdlets.Usage {
|
||||||
|
return cmdlets.Usage{
|
||||||
|
Name: Command.Name(),
|
||||||
|
Short: "Draws notifications using dzen2",
|
||||||
|
Long: []string{
|
||||||
|
"`notify` Will create notifications using the theme colors",
|
||||||
|
"that hlwm was set over",
|
||||||
|
},
|
||||||
|
Flags: map[string]string{
|
||||||
|
"-s": "Amount of seconds to persist for, default 1",
|
||||||
|
},
|
||||||
|
Examples: []string{
|
||||||
|
"notify -s 3 \"Hello world\"",
|
||||||
|
"notify Hey what's up chicken head",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (command) Invoke(args ...string) error {
|
||||||
|
var (
|
||||||
|
secs string = "1"
|
||||||
|
)
|
||||||
|
cleanArgs := make([]string, 0, len(args))
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
arg := args[i]
|
||||||
|
if len(arg) > 1 && arg[0] == '-' {
|
||||||
|
var next string
|
||||||
|
if i+1 < len(args) {
|
||||||
|
next = args[i+1]
|
||||||
|
}
|
||||||
|
switch arg[1] {
|
||||||
|
case 's':
|
||||||
|
secs = next
|
||||||
|
i++
|
||||||
|
default:
|
||||||
|
cleanArgs = append(cleanArgs, arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cleanArgs = append(cleanArgs, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(cleanArgs) == 0 {
|
||||||
|
return errors.New("no message")
|
||||||
|
}
|
||||||
|
message := strings.Join(cleanArgs, " ")
|
||||||
|
cfg := config.Load()
|
||||||
|
secsNum, err := strconv.ParseUint(secs, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("seconds[%s]: %w", secs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := cfg.Theme.Colors
|
||||||
|
return notifyctl.Display(
|
||||||
|
message,
|
||||||
|
uint(secsNum),
|
||||||
|
c.Text,
|
||||||
|
c.Normal,
|
||||||
|
cfg.Font,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package save
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sectorinf.com/emilis/hlctl/cmdlets"
|
||||||
|
"sectorinf.com/emilis/hlctl/config"
|
||||||
|
"sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = ctllog.Logger{}.New("save", ctllog.Green)
|
||||||
|
|
||||||
|
type command struct{}
|
||||||
|
|
||||||
|
var Command command
|
||||||
|
|
||||||
|
func (command) Name() string {
|
||||||
|
return "save"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (command) Usage() cmdlets.Usage {
|
||||||
|
return cmdlets.Usage{
|
||||||
|
Name: Command.Name(),
|
||||||
|
Short: "Save the currently loaded configuration to file",
|
||||||
|
Long: []string{
|
||||||
|
"`save` simply saves the configurable state of hlctl",
|
||||||
|
"to a configuration file located at",
|
||||||
|
"$HOME/.config/herbstluftwm/hlctl.toml",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (command) Invoke(args ...string) error {
|
||||||
|
return config.Collect().Save()
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl"
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Term = "Term"
|
||||||
|
Font = "Font"
|
||||||
|
FontBold = "FontBold"
|
||||||
|
ModKey = "ModKey"
|
||||||
|
TermSpawnKey = "TermSpawnKey"
|
||||||
|
GroupCount = "GroupCount"
|
||||||
|
TagsPerGroup = "TagsPerGroup"
|
||||||
|
ActiveGroup = "ActiveGroup"
|
||||||
|
Services = "Services"
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
ColorActive = "ColorActive"
|
||||||
|
ColorNormal = "ColorNormal"
|
||||||
|
ColorUrgent = "ColorUrgent"
|
||||||
|
ColorText = "ColorText"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
settingFmt = "settings.my_%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidType = errors.New("invalid type")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Setting struct {
|
||||||
|
raw string
|
||||||
|
valueType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Setting) String() string {
|
||||||
|
return s.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Setting) Uint() (uint8, error) {
|
||||||
|
if s.valueType != "uint" {
|
||||||
|
return 0, fmt.Errorf("%s: %w", s.valueType, ErrInvalidType)
|
||||||
|
}
|
||||||
|
val, err := strconv.ParseUint(s.raw, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf(
|
||||||
|
"hc uint setting[%s] failed to parse: %s",
|
||||||
|
s.raw,
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return uint8(val), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFocusedTag() (uint8, error) {
|
||||||
|
attr, err := hlcl.GetAttr("tags.focus.index")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
val, err := strconv.ParseUint(attr, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("parsing [%s] to uint8: %w", attr, err)
|
||||||
|
}
|
||||||
|
return uint8(val), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(name string) (Setting, error) {
|
||||||
|
attrPath := fmt.Sprintf(settingFmt, name)
|
||||||
|
attr, err := hlcl.GetAttr(attrPath)
|
||||||
|
if err != nil {
|
||||||
|
return Setting{}, err
|
||||||
|
}
|
||||||
|
attrType, err := hlcl.AttrType(attrPath)
|
||||||
|
if err != nil {
|
||||||
|
// Default to string, silently
|
||||||
|
attrType = "string"
|
||||||
|
}
|
||||||
|
return Setting{
|
||||||
|
raw: attr,
|
||||||
|
valueType: attrType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set(name string, value string) error {
|
||||||
|
return hlcl.SetAttr(fmt.Sprintf(settingFmt, name), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(name string, value string, attrType cmd.AttributeType) error {
|
||||||
|
if _, err := Get(name); err == nil {
|
||||||
|
// Already exists, just set value
|
||||||
|
return Set(name, value)
|
||||||
|
}
|
||||||
|
return hlcl.NewAttr(fmt.Sprintf(settingFmt, name), value, attrType)
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
"sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfgPath = filepath.Join(
|
||||||
|
os.Getenv("HOME"),
|
||||||
|
".config",
|
||||||
|
"herbstluftwm",
|
||||||
|
"hlctl.toml",
|
||||||
|
)
|
||||||
|
|
||||||
|
log = ctllog.Logger{}.New("config", ctllog.ColorRGB{}.FromHex("ff5aff"))
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
HlwmConfig struct {
|
||||||
|
Font string
|
||||||
|
FontBold string
|
||||||
|
Mod string
|
||||||
|
TermSpawnKey string
|
||||||
|
Term string
|
||||||
|
Groups GroupConfig
|
||||||
|
Theme Theme
|
||||||
|
Services ServicesConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
ServicesConfig struct {
|
||||||
|
Services []string
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupConfig struct {
|
||||||
|
Groups uint8
|
||||||
|
Tags uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
Theme struct {
|
||||||
|
Colors Colors
|
||||||
|
}
|
||||||
|
|
||||||
|
Colors struct {
|
||||||
|
Active string
|
||||||
|
Normal string
|
||||||
|
Urgent string
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h HlwmConfig) Save() error {
|
||||||
|
cfg, err := os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("opening cfg path: [%s]: %w", cfgPath, err)
|
||||||
|
}
|
||||||
|
defer cfg.Close()
|
||||||
|
err = toml.NewEncoder(cfg).Encode(h)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encoding config to toml: %w", err)
|
||||||
|
}
|
||||||
|
return cfg.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HlwmConfig) Validate() error {
|
||||||
|
if h.Font == "" ||
|
||||||
|
h.FontBold == "" ||
|
||||||
|
h.Mod == "" ||
|
||||||
|
h.TermSpawnKey == "" ||
|
||||||
|
h.Term == "" ||
|
||||||
|
h.Groups.Groups == 0 ||
|
||||||
|
h.Groups.Tags == 0 ||
|
||||||
|
len(h.Theme.Colors.Active) < 6 ||
|
||||||
|
len(h.Theme.Colors.Normal) < 6 ||
|
||||||
|
len(h.Theme.Colors.Urgent) < 6 ||
|
||||||
|
len(h.Theme.Colors.Text) < 6 {
|
||||||
|
return errors.New("some configuration options are missing")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the config from loaded attributes. Or get default
|
||||||
|
// if that fails.
|
||||||
|
func Collect() HlwmConfig {
|
||||||
|
getOrNothing := func(settingName string, value *string) {
|
||||||
|
v, err := Get(settingName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*value = v.String()
|
||||||
|
}
|
||||||
|
getUints := func(settingName string, value *uint8) {
|
||||||
|
v, err := Get(settingName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val, err := v.Uint()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*value = uint8(val)
|
||||||
|
}
|
||||||
|
cfg := Default
|
||||||
|
getOrNothing(Font, &cfg.Font)
|
||||||
|
getOrNothing(FontBold, &cfg.FontBold)
|
||||||
|
getOrNothing(ModKey, &cfg.Mod)
|
||||||
|
getOrNothing(TermSpawnKey, &cfg.TermSpawnKey)
|
||||||
|
getOrNothing(Term, &cfg.Term)
|
||||||
|
getUints(GroupCount, &cfg.Groups.Groups)
|
||||||
|
getUints(TagsPerGroup, &cfg.Groups.Tags)
|
||||||
|
getOrNothing(ColorActive, &cfg.Theme.Colors.Active)
|
||||||
|
getOrNothing(ColorNormal, &cfg.Theme.Colors.Normal)
|
||||||
|
getOrNothing(ColorUrgent, &cfg.Theme.Colors.Urgent)
|
||||||
|
getOrNothing(ColorText, &cfg.Theme.Colors.Text)
|
||||||
|
svcsString, err := Get(Services)
|
||||||
|
if err == nil {
|
||||||
|
cfg.Services.Services = strings.Split(svcsString.String(), ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HlwmConfig) SetAll() error {
|
||||||
|
err := New(GroupCount, strconv.Itoa(int(h.Groups.Groups)), cmd.Uint)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", GroupCount, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = New(TagsPerGroup, strconv.Itoa(int(h.Groups.Tags)), cmd.Uint)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", TagsPerGroup, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sets := map[string]string{
|
||||||
|
Font: h.Font,
|
||||||
|
FontBold: h.FontBold,
|
||||||
|
ModKey: h.Mod,
|
||||||
|
TermSpawnKey: h.TermSpawnKey,
|
||||||
|
Term: h.Term,
|
||||||
|
ColorActive: h.Theme.Colors.Active,
|
||||||
|
ColorNormal: h.Theme.Colors.Normal,
|
||||||
|
ColorUrgent: h.Theme.Colors.Urgent,
|
||||||
|
ColorText: h.Theme.Colors.Text,
|
||||||
|
Services: strings.Join(h.Services.Services, ";"),
|
||||||
|
}
|
||||||
|
for cfg, val := range sets {
|
||||||
|
if err := New(cfg, val, cmd.String); err != nil {
|
||||||
|
return fmt.Errorf("%s[%s]: %w", cfg, val, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() HlwmConfig {
|
||||||
|
cfg := HlwmConfig{}
|
||||||
|
if _, err := toml.DecodeFile(cfgPath, &cfg); err != nil {
|
||||||
|
log.Warnf(
|
||||||
|
"could not read file [%s] for config, using default",
|
||||||
|
cfgPath,
|
||||||
|
)
|
||||||
|
return Default
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
Default = HlwmConfig{
|
||||||
|
Font: "-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*",
|
||||||
|
FontBold: "-*-fixed-bold-*-*-*-12-*-*-*-*-*-*-*",
|
||||||
|
Mod: "Mod4",
|
||||||
|
TermSpawnKey: "Home",
|
||||||
|
Term: "alacritty",
|
||||||
|
Groups: GroupConfig{
|
||||||
|
Groups: 3,
|
||||||
|
Tags: 5,
|
||||||
|
},
|
||||||
|
Theme: Theme{
|
||||||
|
Colors: Colors{
|
||||||
|
Active: "#800080",
|
||||||
|
Normal: "#330033",
|
||||||
|
Urgent: "#7811A1",
|
||||||
|
Text: "#898989",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: ServicesConfig{
|
||||||
|
Services: []string{"fcitx5"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,102 @@
|
||||||
|
package ctllog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// \x1b[38;2;255;100;0mTRUECOLOR\x1b[0m
|
||||||
|
// ESC[ 38;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB foreground color
|
||||||
|
|
||||||
|
const (
|
||||||
|
escape byte = 0x1B
|
||||||
|
csi byte = '['
|
||||||
|
separator byte = ';'
|
||||||
|
csiEnd byte = 'm'
|
||||||
|
fgColor byte = '3'
|
||||||
|
bgColor byte = '4'
|
||||||
|
trueColor byte = '8'
|
||||||
|
rgb byte = '2'
|
||||||
|
reset byte = '0'
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
trueColorFMT = string([]byte{
|
||||||
|
escape,
|
||||||
|
csi,
|
||||||
|
'%', 'c',
|
||||||
|
trueColor,
|
||||||
|
separator,
|
||||||
|
rgb,
|
||||||
|
separator,
|
||||||
|
'%', 'd',
|
||||||
|
separator,
|
||||||
|
'%', 'd',
|
||||||
|
separator,
|
||||||
|
'%', 'd',
|
||||||
|
csiEnd,
|
||||||
|
'%', 's',
|
||||||
|
escape,
|
||||||
|
csi,
|
||||||
|
reset,
|
||||||
|
csiEnd,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
type Variant byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
Foreground = Variant(fgColor)
|
||||||
|
Background = Variant(bgColor)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ColorRGB struct {
|
||||||
|
r uint8
|
||||||
|
g uint8
|
||||||
|
b uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ColorRGB) FromHex(hexString string) ColorRGB {
|
||||||
|
if len(hexString) < 6 {
|
||||||
|
return White
|
||||||
|
}
|
||||||
|
if hexString[0] == '#' {
|
||||||
|
hexString = hexString[1:]
|
||||||
|
}
|
||||||
|
if len(hexString) != 6 {
|
||||||
|
return White
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := hex.DecodeString(hexString)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return White
|
||||||
|
}
|
||||||
|
|
||||||
|
return ColorRGB{
|
||||||
|
r: res[0],
|
||||||
|
g: res[1],
|
||||||
|
b: res[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStr(str string, typeByte Variant, color ColorRGB) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
trueColorFMT,
|
||||||
|
typeByte,
|
||||||
|
color.r,
|
||||||
|
color.g,
|
||||||
|
color.b,
|
||||||
|
str,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
Black ColorRGB
|
||||||
|
White = ColorRGB{255, 255, 255}
|
||||||
|
Green = ColorRGB{0, 255, 0}
|
||||||
|
Red = ColorRGB{255, 0, 0}
|
||||||
|
Blue = ColorRGB{0, 0, 255}
|
||||||
|
Purple = ColorRGB{255, 0, 255}
|
||||||
|
Yellow = ColorRGB{255, 255, 0}
|
||||||
|
)
|
|
@ -0,0 +1,22 @@
|
||||||
|
package ctllog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
// "sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestColorFromHex(t *testing.T) {
|
||||||
|
testCases := map[string]ColorRGB{
|
||||||
|
"000000": {},
|
||||||
|
"0c2238": {12, 34, 56},
|
||||||
|
}
|
||||||
|
|
||||||
|
for hexColor, expected := range testCases {
|
||||||
|
t.Run(hexColor, func(t *testing.T) {
|
||||||
|
actual := ColorRGB{}.FromHex(hexColor)
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected %+v got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package ctllog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
coloredName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Logger) New(name string, color ColorRGB) Logger {
|
||||||
|
coloredName := getStr("[ "+name+" ]", Foreground, color)
|
||||||
|
return Logger{
|
||||||
|
coloredName: coloredName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) print(str string) {
|
||||||
|
lines := strings.Split(str, "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
lines[i] = fmt.Sprintf("%s: %s", l.coloredName, line)
|
||||||
|
}
|
||||||
|
str = strings.Join(lines, "\n")
|
||||||
|
fmt.Fprintln(os.Stderr, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) colored(color ColorRGB, args ...any) {
|
||||||
|
l.print(getStr(fmt.Sprint(args...), Foreground, color))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) coloredf(color ColorRGB, format string, args ...any) {
|
||||||
|
l.print(getStr(fmt.Sprintf(format, args...), Foreground, color))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) fatal(str string) {
|
||||||
|
l.print(getStr(str, Background, Red))
|
||||||
|
debug.PrintStack()
|
||||||
|
os.Exit(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Print(args ...any) {
|
||||||
|
l.print(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Printf(format string, args ...any) {
|
||||||
|
l.print(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Warn(args ...any) {
|
||||||
|
l.colored(Yellow, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Warnf(format string, args ...any) {
|
||||||
|
l.coloredf(Yellow, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Error(args ...any) {
|
||||||
|
l.colored(Red, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Errorf(format string, args ...any) {
|
||||||
|
l.coloredf(Red, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Fatal(args ...any) {
|
||||||
|
l.fatal(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Fatalf(format string, args ...any) {
|
||||||
|
l.fatal(fmt.Sprintf(format, args...))
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module sectorinf.com/emilis/hlctl
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require github.com/BurntSushi/toml v1.2.1
|
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
|
@ -0,0 +1,106 @@
|
||||||
|
package groupctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"sectorinf.com/emilis/hlctl/config"
|
||||||
|
"sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl"
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = ctllog.Logger{}.New("group", ctllog.ColorRGB{}.FromHex("27ee7e"))
|
||||||
|
|
||||||
|
func ResetActive() error {
|
||||||
|
err := config.New(config.ActiveGroup, "1", cmd.Uint)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("new active group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update switches to the correct tag based on the active group
|
||||||
|
func Update() error {
|
||||||
|
activeCfg, err := config.Get(config.ActiveGroup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", config.ActiveGroup, err)
|
||||||
|
}
|
||||||
|
active, err := activeCfg.Uint()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", config.ActiveGroup, err)
|
||||||
|
}
|
||||||
|
focus, err := config.GetFocusedTag()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("focus: %w", err)
|
||||||
|
}
|
||||||
|
numCfg, err := config.Get(config.TagsPerGroup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get %s: %w", config.TagsPerGroup, err)
|
||||||
|
}
|
||||||
|
num, err := numCfg.Uint()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", config.TagsPerGroup, err)
|
||||||
|
}
|
||||||
|
relativeTag := focus % uint8(num)
|
||||||
|
absTag := ((active - 1) * num) + relativeTag
|
||||||
|
if focus != absTag {
|
||||||
|
log.Printf("Tag %d -> %d", focus, absTag)
|
||||||
|
}
|
||||||
|
if err := hlcl.UseIndex(absTag); err != nil {
|
||||||
|
return fmt.Errorf("UseIndex[%d]: %w", absTag, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SetKeybinds(active, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MoveTo(group uint8) error {
|
||||||
|
numCfg, err := config.Get(config.TagsPerGroup)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get %s: %w", config.TagsPerGroup, err)
|
||||||
|
}
|
||||||
|
num, err := numCfg.Uint()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", config.TagsPerGroup, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
focus, err := config.GetFocusedTag()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("focus: %w", err)
|
||||||
|
}
|
||||||
|
relative := focus % num
|
||||||
|
newTag := ((group - 1) * num) + relative
|
||||||
|
log.Printf("Moving window to tag -> %d", newTag)
|
||||||
|
return hlcl.MoveIndex(newTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetKeybinds(group uint8, num uint8) error {
|
||||||
|
offset := int((group - 1) * num)
|
||||||
|
mod, err := config.Get(config.ModKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", config.ModKey, err)
|
||||||
|
}
|
||||||
|
log.Printf("Setting %d keybinds for group %d", num, group)
|
||||||
|
for i := 0; i < int(num); i++ {
|
||||||
|
actualTag := strconv.Itoa(offset + i)
|
||||||
|
tag := strconv.Itoa(i + 1)
|
||||||
|
err = hlcl.Keybind(
|
||||||
|
[]string{mod.String(), tag},
|
||||||
|
cmd.UseIndex,
|
||||||
|
actualTag,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Setting use_index for [%d]: %w", i+1, err)
|
||||||
|
}
|
||||||
|
err = hlcl.Keybind(
|
||||||
|
[]string{mod.String(), "Shift", tag},
|
||||||
|
cmd.MoveIndex,
|
||||||
|
actualTag,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Setting move_index for [%d]: %w", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Command string
|
||||||
|
|
||||||
|
func (c Command) String() string {
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Command) Pretty() string {
|
||||||
|
parts := strings.Split(string(c), "_")
|
||||||
|
for i, part := range parts {
|
||||||
|
// Set the first character to be upper case
|
||||||
|
if len(part) == 0 {
|
||||||
|
continue
|
||||||
|
} else if len(part) == 1 {
|
||||||
|
parts[i] = strings.ToUpper(part)
|
||||||
|
}
|
||||||
|
var first rune
|
||||||
|
for _, r := range part {
|
||||||
|
first = r
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[i] = fmt.Sprintf("%c%s", unicode.ToUpper(first), part[1:])
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Reload Command = "reload"
|
||||||
|
Close Command = "close"
|
||||||
|
Spawn Command = "spawn"
|
||||||
|
ListMonitors Command = "list_monitors"
|
||||||
|
Lock Command = "lock"
|
||||||
|
Unlock Command = "unlock"
|
||||||
|
|
||||||
|
// Attr family
|
||||||
|
GetAttr Command = "get_attr"
|
||||||
|
SetAttr Command = "set_attr"
|
||||||
|
Attr Command = "attr"
|
||||||
|
NewAttr Command = "new_attr"
|
||||||
|
AttrType Command = "attr_type"
|
||||||
|
RemoveAttr Command = "remove_attr"
|
||||||
|
|
||||||
|
Set Command = "set"
|
||||||
|
EmitHook Command = "emit_hook"
|
||||||
|
Rule Command = "rule"
|
||||||
|
Unrule Command = "unrule"
|
||||||
|
Substitute Command = "substitute"
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
Keybind Command = "keybind"
|
||||||
|
Keyunbind Command = "keyunbind"
|
||||||
|
Mousebind Command = "mousebind"
|
||||||
|
Mouseunbind Command = "mouseunbind"
|
||||||
|
UseIndex Command = "use_index"
|
||||||
|
MoveIndex Command = "move_index"
|
||||||
|
JumpTo Command = "jumpto"
|
||||||
|
|
||||||
|
// Views/Frames
|
||||||
|
AddTag Command = "add"
|
||||||
|
MergeTag Command = "merge_tag"
|
||||||
|
Cycle Command = "cycle"
|
||||||
|
Focus Command = "focus"
|
||||||
|
Shift Command = "shift"
|
||||||
|
Split Command = "split"
|
||||||
|
Remove Command = "remove"
|
||||||
|
Fullscreen Command = "fullscreen"
|
||||||
|
CycleLayout Command = "cycle_layout"
|
||||||
|
Resize Command = "resize"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MouseButton string
|
||||||
|
|
||||||
|
func (m MouseButton) String() string {
|
||||||
|
return string(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Mouse1 MouseButton = "Mouse1"
|
||||||
|
Mouse2 MouseButton = "Mouse2"
|
||||||
|
Mouse3 MouseButton = "Mouse3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AttributeType string
|
||||||
|
|
||||||
|
func (a AttributeType) String() string {
|
||||||
|
return string(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
String AttributeType = "string"
|
||||||
|
Bool AttributeType = "bool"
|
||||||
|
Color AttributeType = "color"
|
||||||
|
Int AttributeType = "int"
|
||||||
|
Uint AttributeType = "uint"
|
||||||
|
)
|
|
@ -0,0 +1,190 @@
|
||||||
|
// package hlcl contains logic for interacting with the
|
||||||
|
// herbstclient
|
||||||
|
package hlcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = ctllog.Logger{}.New("hlcl", ctllog.Purple)
|
||||||
|
|
||||||
|
func runGeneric(command string, stdin string, args ...string) (string, error) {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
// log.Printf("Running command [%s] with args: %v", command, args)
|
||||||
|
var stdout, stderr = bytes.Buffer{}, bytes.Buffer{}
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
if stdin != "" {
|
||||||
|
pipe, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("stdin pipe: %w", err)
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return "", fmt.Errorf("start: %w", err)
|
||||||
|
}
|
||||||
|
io.WriteString(pipe, stdin+"\n")
|
||||||
|
_, err = pipe.Write([]byte(stdin))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("write stdin: %w", err)
|
||||||
|
}
|
||||||
|
pipe.Close()
|
||||||
|
} else {
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return "", fmt.Errorf("start: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
status, ok := err.(*exec.ExitError)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("%s exec error: %w", command, err)
|
||||||
|
}
|
||||||
|
exitCode := status.ExitCode()
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"%s error (%d): %s",
|
||||||
|
command,
|
||||||
|
exitCode,
|
||||||
|
strings.TrimSpace(stderr.String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(stdout.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(command cmd.Command, args ...string) (string, error) {
|
||||||
|
args = append([]string{command.String()}, args...)
|
||||||
|
return runGeneric("herbstclient", "", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query executes a command and pretty formats and error message
|
||||||
|
// based on the command's Pretty(), and returns the output
|
||||||
|
func query(c cmd.Command, args ...string) (string, error) {
|
||||||
|
value, err := run(c, args...)
|
||||||
|
if err != nil {
|
||||||
|
return value, fmt.Errorf(
|
||||||
|
"%s(%s): %w",
|
||||||
|
c.Pretty(),
|
||||||
|
strings.Join(args, ", "),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute: wrapper for [query], discarding the stdout result
|
||||||
|
func execute(c cmd.Command, args ...string) error {
|
||||||
|
_, err := query(c, args...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttr(path string, value string, aType cmd.AttributeType) error {
|
||||||
|
return execute(cmd.NewAttr, aType.String(), path, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetAttr(path string, value string) error {
|
||||||
|
return execute(cmd.SetAttr, path, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAttr(path string) (string, error) {
|
||||||
|
return query(cmd.GetAttr, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AttrType(path string) (string, error) {
|
||||||
|
return query(cmd.AttrType, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyUnbind() error {
|
||||||
|
return execute(cmd.Keyunbind, "--all")
|
||||||
|
}
|
||||||
|
|
||||||
|
func MouseUnbind() error {
|
||||||
|
return execute(cmd.Mouseunbind, "--all")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListMonitors() ([]Screen, error) {
|
||||||
|
out, err := query(cmd.ListMonitors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outs := strings.Split(out, "\n")
|
||||||
|
scrs := make([]Screen, len(outs))
|
||||||
|
for i, line := range outs {
|
||||||
|
scrs[i] = Screen{}.FromString(line)
|
||||||
|
}
|
||||||
|
return scrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindCMD(
|
||||||
|
bindCommand cmd.Command,
|
||||||
|
keys []string,
|
||||||
|
command cmd.Command,
|
||||||
|
args ...string,
|
||||||
|
) error {
|
||||||
|
bindKeys := strings.Join(keys, "-")
|
||||||
|
args = append([]string{bindKeys, command.String()}, args...)
|
||||||
|
return execute(bindCommand, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Keybind(keys []string, command cmd.Command, args ...string) error {
|
||||||
|
return bindCMD(cmd.Keybind, keys, command, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mousebind(keys []string, command cmd.Command, args ...string) error {
|
||||||
|
return bindCMD(cmd.Mousebind, keys, command, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set(name, value string) error {
|
||||||
|
return execute(cmd.Set, name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UseIndex(index uint8) error {
|
||||||
|
return execute(cmd.UseIndex, strconv.Itoa(int(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeTag(name string) error {
|
||||||
|
return execute(cmd.MergeTag, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MoveIndex(index uint8) error {
|
||||||
|
return execute(cmd.MoveIndex, strconv.Itoa(int(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TextWidth(font, message string) (string, error) {
|
||||||
|
return runGeneric("textwidth", "", font, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rule(name string, args ...string) error {
|
||||||
|
return execute(cmd.Rule, append([]string{name}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unrule() error {
|
||||||
|
return execute(cmd.Unrule, "-F")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SilentSpawn(command string, args ...string) error {
|
||||||
|
return execute(cmd.Spawn, append([]string{command}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lock() error {
|
||||||
|
return execute(cmd.Lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unlock() error {
|
||||||
|
return execute(cmd.Unlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTag(name string) error {
|
||||||
|
return execute(cmd.AddTag, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Dzen2(message string, args ...string) error {
|
||||||
|
_, err := runGeneric("dzen2", message, args...)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package hlcl
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type Screen struct {
|
||||||
|
ID string
|
||||||
|
Resolution string
|
||||||
|
WithTag string
|
||||||
|
Focused bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Screen) FromString(v string) Screen {
|
||||||
|
parts := strings.Split(v, " ")
|
||||||
|
if len(parts) < 5 {
|
||||||
|
return Screen{
|
||||||
|
ID: v[:1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, part := range parts {
|
||||||
|
parts[i] = strings.TrimSpace(
|
||||||
|
strings.TrimRight(
|
||||||
|
strings.Trim(part, "\""), ":"))
|
||||||
|
}
|
||||||
|
return Screen{
|
||||||
|
ID: parts[0],
|
||||||
|
Resolution: parts[1],
|
||||||
|
// Maybe later
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sectorinf.com/emilis/hlctl/cmdlets"
|
||||||
|
"sectorinf.com/emilis/hlctl/cmdlets/group"
|
||||||
|
initcmd "sectorinf.com/emilis/hlctl/cmdlets/init"
|
||||||
|
"sectorinf.com/emilis/hlctl/cmdlets/notify"
|
||||||
|
"sectorinf.com/emilis/hlctl/cmdlets/save"
|
||||||
|
"sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = ctllog.Logger{}.New("hlctl", ctllog.Green)
|
||||||
|
|
||||||
|
var cmds = []cmdlets.Commandlet{
|
||||||
|
initcmd.Command,
|
||||||
|
group.Command,
|
||||||
|
notify.Command,
|
||||||
|
save.Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCmdlet(name string) (cmdlets.Commandlet, error) {
|
||||||
|
for _, c := range cmds {
|
||||||
|
if c.Name() == name {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%s: %w", name, ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func help() {
|
||||||
|
commands := make([]string, len(cmds))
|
||||||
|
for i, c := range cmds {
|
||||||
|
commands[i] = c.Name()
|
||||||
|
}
|
||||||
|
fmt.Fprint(os.Stderr, "USAGE: hlctl [command] [arguments]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\nAvailable commands: %v\n", commands)
|
||||||
|
fmt.Fprint(os.Stderr, "\nSee hlctl help [command] for details\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// omit the program name
|
||||||
|
args := os.Args[1:]
|
||||||
|
if len(args) == 0 {
|
||||||
|
help()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
command := strings.ToLower(args[0])
|
||||||
|
if command == "help" {
|
||||||
|
if len(args) == 1 {
|
||||||
|
help()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := args[1]
|
||||||
|
c, err := getCmdlet(name)
|
||||||
|
if err != nil {
|
||||||
|
help()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println(c.Usage())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, err := getCmdlet(command)
|
||||||
|
if err != nil {
|
||||||
|
help()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Invoke(args[1:]...); err != nil {
|
||||||
|
log.Fatalf(" %s > %s", c.Name(), err.Error())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package notifyctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = ctllog.Logger{}.New("notify", ctllog.ColorRGB{}.FromHex("ff003e"))
|
||||||
|
|
||||||
|
func Display(message string, duration uint, fg, bg, font string) error {
|
||||||
|
width, err := hlcl.TextWidth(font, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%s; defaulting to width 100", err.Error())
|
||||||
|
width = "100"
|
||||||
|
}
|
||||||
|
widthNum, err := strconv.ParseUint(width, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hlcl.Dzen2(
|
||||||
|
message,
|
||||||
|
"-p", strconv.Itoa(int(duration)),
|
||||||
|
"-fg", fg,
|
||||||
|
"-bg", bg,
|
||||||
|
"-fn", font,
|
||||||
|
"-ta", "c",
|
||||||
|
"-w", strconv.Itoa(int(widthNum)+30),
|
||||||
|
"-h", "16",
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package panelctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run() error {
|
||||||
|
screens, err := hlcl.ListMonitors()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
panel := func(mon string) error {
|
||||||
|
return exec.Command(
|
||||||
|
"bash",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%s/.config/herbstluftwm/panel.sh",
|
||||||
|
os.Getenv("HOME"),
|
||||||
|
),
|
||||||
|
mon,
|
||||||
|
).Run()
|
||||||
|
}
|
||||||
|
for i := 1; i < len(screens); i++ {
|
||||||
|
id := screens[i].ID
|
||||||
|
go func() {
|
||||||
|
if err := panel(id); err != nil {
|
||||||
|
log.Fatalf("Monitor [%s] panel: %s", id, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return panel(screens[0].ID)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package svcctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"sectorinf.com/emilis/hlctl/config"
|
||||||
|
"sectorinf.com/emilis/hlctl/ctllog"
|
||||||
|
"sectorinf.com/emilis/hlctl/hlcl"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = ctllog.Logger{}.New("config", ctllog.ColorRGB{}.FromHex("2e8647"))
|
||||||
|
|
||||||
|
func RestartServices(cfg config.ServicesConfig) {
|
||||||
|
for _, svc := range cfg.Services {
|
||||||
|
log.Printf("Restarting [%s]", svc)
|
||||||
|
if err := exec.Command("pkill", "-9", svc).Run(); err != nil {
|
||||||
|
log.Errorf("Killing [%s]: %s", svc, err)
|
||||||
|
}
|
||||||
|
if err := hlcl.SilentSpawn(svc); err != nil {
|
||||||
|
log.Errorf("Starting [%s]: %s", svc, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue