package bspc // import "hhvn.uk/hbspbar/bspc" import ( "fmt" "io" "bufio" "strings" "os/exec" "encoding/json" "hhvn.uk/hbspbar/common" ) type State struct { Monitors []*Monitor FocusedMonitorID int } func (s *State) GetMon(id int) (*Monitor) { for _, m := range s.Monitors { if m.ID == id { return m } } return nil } type Monitor struct { Focused bool ID int Name string Rectangle *Rect Desktops []*Desktop FocusedDesktopID int } func (m *Monitor) TopPadding(px uint) { cmd := exec.Command("bspc", "config", "-m", fmt.Sprintf("%d", m.ID), "top_padding", fmt.Sprintf("%d", px)) pipe, _ := cmd.StderrPipe() scan := bufio.NewScanner(pipe) cmd.Start() for scan.Scan() { common.Error("%s\n", scan.Text()) } if err := scan.Err(); err != nil { common.Perror("scan.Err", err) } cmd.Wait() } type Rect struct { X uint Y uint Width uint Height uint } type Node struct { ID int FirstChild *Node SecondChild *Node } type Desktop struct { Focused bool Name string Layout string ID int Root *Node } var reloadEvents = []string{ "monitor_rename", "monitor_swap", "monitor_focus", "monitor_remove", "monitor_geometry", // only event we get for monitors on restart "desktop_add", "desktop_rename", "desktop_remove", "desktop_swap", "desktop_transfer", "desktop_focus", "desktop_activate", "desktop_layout", "node_add", "node_remove", "node_swap", "node_transfer" } func getState() (*State, error) { cmd := exec.Command("bspc", "wm", "-d") out, err := cmd.StdoutPipe() if err != nil { return nil, common.Perror("cmd.StdoutPipe", err) } if err = cmd.Start(); err != nil { return nil, common.Perror("cmd.Start", err) } data, err := io.ReadAll(out) if err != nil { return nil, common.Perror("io.ReadAll", err) } if err = cmd.Wait(); err != nil { return nil, common.Perror("cmd.Wait", err) } var state State err = json.Unmarshal(data, &state) if err != nil { return nil, common.Perror("json.Unmarshal", err) } for _, m := range state.Monitors { m.Focused = m.ID == state.FocusedMonitorID for _, d := range m.Desktops { d.Focused = d.ID == m.FocusedDesktopID } } return &state, nil } type subscriber struct { State *State StateUpdate chan bool StErr chan error Event chan *Event EvErr chan error cmd *exec.Cmd pipe io.ReadCloser scanner *bufio.Scanner } type Event struct { Name string Tokens []string } var InitErr error var Handle subscriber func init() { cmd := exec.Command("bspc", "subscribe", "all") pipe, err := cmd.StdoutPipe() if err != nil { InitErr = common.Perror("cmd.StdoutPipe", err) return } if err = cmd.Start(); err != nil { InitErr = common.Perror("cmd.Start", err) return } istate, err := getState() if err != nil { InitErr = common.Perror("getState", err) return } Handle.State = istate Handle.StateUpdate = make(chan bool) Handle.StErr = make(chan error) Handle.Event = make(chan *Event) Handle.EvErr = make(chan error) Handle.cmd = cmd Handle.pipe = pipe Handle.scanner = bufio.NewScanner(Handle.pipe) go func() { for { event, err := get() if event != nil { for i := 0; i < len(reloadEvents); i++ { if event.Name == reloadEvents[i] { state, serr := getState() if serr != nil { Handle.StErr <- serr return } Handle.State = state Handle.StateUpdate <- true } } Handle.Event <- event } if err != nil { Handle.EvErr <- err return } }}() } func Cleanup() { Handle.cmd.Process.Kill() } func getLine() (string, error) { if Handle.scanner.Scan() { return Handle.scanner.Text(), nil } if err := Handle.scanner.Err(); err != nil { return "", common.Perror("scanner.Err", err) } return "", nil } func get() (*Event, error) { var line string var err error for { line, err = getLine() if len(line) > 0 && line[0] != 'W' { break } } if line == "" { return nil, nil } if err != nil { return nil, err } name, tokens, _ := strings.Cut(line, " ") return &Event{name, strings.Split(tokens, " ")}, nil }