hbspbar

bspwm status bar
git clone https://hhvn.uk/hbspbar
git clone git://hhvn.uk/hbspbar
Log | Files | Refs

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 }