bspc.go (5009B)
1 package bspc // import "hhvn.uk/hbspbar/bspc" 2 3 import ( 4 "io" 5 "fmt" 6 "time" 7 "sync" 8 "bufio" 9 "errors" 10 "strings" 11 "os/exec" 12 "encoding/json" 13 14 "hhvn.uk/hbspbar/common" 15 ) 16 17 var ErrMonNotFound = errors.New("monitor not found") 18 19 type State struct { 20 Monitors []*Monitor 21 FocusedMonitorID int 22 } 23 24 func (s *State) GetMon(id int) (*Monitor, error) { 25 for _, m := range s.Monitors { 26 if m.ID == id { 27 return m, nil 28 } 29 } 30 31 return nil, ErrMonNotFound 32 } 33 34 type Monitor struct { 35 Focused bool 36 ID int 37 Name string 38 Rectangle *Rect 39 Desktops []*Desktop 40 FocusedDesktopID int 41 } 42 43 func (m *Monitor) TopPadding(px uint) { 44 Cmd("config", "-m", fmt.Sprintf("%d", m.ID), 45 "top_padding", fmt.Sprintf("%d", px)) 46 } 47 48 type Rect struct { 49 X uint 50 Y uint 51 Width uint 52 Height uint 53 } 54 55 type Desktop struct { 56 Focused bool 57 Name string 58 Layout string 59 ID int 60 Root *Node 61 } 62 63 type Node struct { 64 ID int 65 FirstChild *Node 66 SecondChild *Node 67 Client *Client 68 } 69 70 type Client struct { 71 Urgent bool 72 } 73 74 func urgentWalk(n *Node) bool { 75 switch { 76 case n == nil: 77 return false 78 case n.Client != nil && n.Client.Urgent: 79 return true 80 } 81 82 if urgentWalk(n.FirstChild) { 83 return true 84 } 85 if urgentWalk(n.SecondChild) { 86 return true 87 } 88 89 return false 90 } 91 92 func (d *Desktop) Urgent() bool { 93 return urgentWalk(d.Root) 94 } 95 96 var reloadEvents = []string{ 97 "monitor_rename", 98 "monitor_swap", 99 "monitor_focus", 100 "monitor_remove", 101 "monitor_geometry", // only event we get for monitors on restart 102 "desktop_add", 103 "desktop_rename", 104 "desktop_remove", 105 "desktop_swap", 106 "desktop_transfer", 107 "desktop_focus", 108 "desktop_activate", 109 "desktop_layout", 110 "node_add", 111 "node_remove", 112 "node_swap", 113 "node_transfer", 114 "node_flag", // for urgency 115 } 116 117 func getState() (*State, error) { 118 cmd := exec.Command("bspc", "wm", "-d") 119 120 out, err := cmd.StdoutPipe() 121 if err != nil { 122 return nil, common.Perror("cmd.StdoutPipe", err) 123 } 124 125 if err = cmd.Start(); err != nil { 126 return nil, common.Perror("cmd.Start", err) 127 } 128 129 data, err := io.ReadAll(out) 130 if err != nil { 131 return nil, common.Perror("io.ReadAll", err) 132 } 133 134 if err = cmd.Wait(); err != nil { 135 return nil, common.Perror("cmd.Wait", err) 136 } 137 138 var state State 139 err = json.Unmarshal(data, &state) 140 if err != nil { 141 return nil, common.Perror("json.Unmarshal", err) 142 } 143 144 for _, m := range state.Monitors { 145 m.Focused = m.ID == state.FocusedMonitorID 146 147 for _, d := range m.Desktops { 148 d.Focused = d.ID == m.FocusedDesktopID 149 } 150 } 151 152 return &state, nil 153 } 154 155 type event struct { 156 Name string 157 Tokens []string 158 } 159 160 var InitErr error 161 var NewState = make(chan *State, 1) // Buffered to prevent blocking on first send 162 var StErr = make(chan error) 163 var Event = make(chan *event) 164 var EvErr = make(chan error) 165 166 func init() { 167 cmd := exec.Command("bspc", "subscribe", "all") 168 cmdRegister(cmd) 169 170 pipe, err := cmd.StdoutPipe() 171 if err != nil { 172 InitErr = common.Perror("cmd.StdoutPipe", err) 173 return 174 } 175 176 if err = cmd.Start(); err != nil { 177 InitErr = common.Perror("cmd.Start", err) 178 return 179 } 180 181 istate, err := getState() 182 if err != nil { 183 InitErr = common.Perror("getState", err) 184 return 185 } 186 187 NewState <- istate 188 189 scanner := bufio.NewScanner(pipe) 190 191 go func() { for { 192 event, err := get(scanner) 193 194 if event != nil { 195 for i := 0; i < len(reloadEvents); i++ { 196 if event.Name == reloadEvents[i] { 197 state, serr := getState() 198 if serr != nil { 199 StErr <- serr 200 return 201 } 202 NewState <- state 203 } 204 } 205 Event <- event 206 } 207 208 if err != nil { 209 EvErr <- err 210 return 211 } 212 }}() 213 } 214 215 func getLine(s *bufio.Scanner) (string, error) { 216 if s.Scan() { 217 return s.Text(), nil 218 } 219 220 if err := s.Err(); err != nil { 221 return "", common.Perror("scanner.Err", err) 222 } 223 224 return "", nil 225 } 226 227 func get(s *bufio.Scanner) (*event, error) { 228 var line string 229 var err error 230 for { 231 line, err = getLine(s) 232 if len(line) > 0 && line[0] != 'W' { break } 233 } 234 235 if line == "" { return nil, nil } 236 if err != nil { return nil, err } 237 238 name, tokens, _ := strings.Cut(line, " ") 239 240 return &event{name, strings.Split(tokens, " ")}, nil 241 } 242 243 var cmdlist []*exec.Cmd 244 var cmdlock sync.Mutex 245 246 func cmdRegister(c *exec.Cmd) int { 247 cmdlock.Lock() 248 defer cmdlock.Unlock() 249 250 for i, v := range cmdlist { 251 if v == nil { 252 cmdlist[i] = c 253 return i 254 } 255 } 256 257 cmdlist = append(cmdlist, c) 258 i := len(cmdlist) - 1 259 return i 260 } 261 262 func cmdDelete(i int) { 263 cmdlock.Lock() 264 defer cmdlock.Unlock() 265 cmdlist[i] = nil 266 } 267 268 func Cmd(args ...string) { 269 cmd := exec.Command("bspc", args...) 270 271 cmdi := cmdRegister(cmd) 272 pipe, _ := cmd.StderrPipe() 273 scan := bufio.NewScanner(pipe) 274 startt := time.Now() 275 276 cmd.Start() 277 278 for scan.Scan() { 279 common.Error("[Started %v] `bspc %v`: %v\n", 280 startt.Format("15:04:05.00"), args, scan.Text()) 281 } 282 283 if err := scan.Err(); err != nil { 284 common.Perror("scan.Err", err) 285 } 286 287 cmd.Wait() 288 cmdDelete(cmdi) 289 } 290 291 func Cleanup() { 292 for _, c := range cmdlist { 293 if c != nil { 294 c.Process.Kill() 295 } 296 } 297 }