hbspbar

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

commit 79a80fbbf278e0eb743a79e2c0c252817537434f
parent 0543020038edb5de1162863e7401ea36f8a8584c
Author: hhvn <dev@hhvn.uk>
Date:   Mon, 14 Aug 2023 14:04:41 +0100

Creating and destroying the windows

Diffstat:
A.gitignore | 1+
AMakefile | 2++
Abar/bar.go | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbspc/bspc.go | 61++++++++++++++++++++++++++++++++++++++++++++-----------------
Mcommon/common.go | 6+-----
Mgo.mod | 4++--
Mgo.sum | 10++++++----
Mmain.go | 54+++++++++++++++++++++++++++++++++++++++++++++---------
8 files changed, 301 insertions(+), 37 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +hbspbar diff --git a/Makefile b/Makefile @@ -0,0 +1,2 @@ +build: + go build diff --git a/bar/bar.go b/bar/bar.go @@ -0,0 +1,200 @@ +package bar + +import ( + "fmt" + "container/list" + + "hhvn.uk/hbspbar/common" + "hhvn.uk/hbspbar/bspc" + + "github.com/jezek/xgb" + "github.com/jezek/xgb/xproto" + + "github.com/jezek/xgbutil" + "github.com/jezek/xgbutil/icccm" + "github.com/jezek/xgbutil/ewmh" +) + +var conf = struct { + H uint + Bg uint32 + Fg uint32 + Sel uint32 + Urg uint32 +} { 16, + 0xff050a10, + 0xffeeeeee, + 0xff1b364b, + 0xff90222b } + +var bars *list.List + +type bar struct { + X *xgb.Conn + Xu *xgbutil.XUtil + Scr *xproto.ScreenInfo + Mon *bspc.Monitor + w xproto.Window +} + +func (b *bar) Init() error { + var err error + + b.w, err = xproto.NewWindowId(b.X) + if err != nil { + return common.Perror("xproto.NewWindowId", err) + } + + geom := b.Mon.Rectangle + xproto.CreateWindow(b.X, b.Scr.RootDepth, b.w, b.Scr.Root, + int16(geom.X), int16(geom.Y), uint16(geom.Width), uint16(conf.H), 0, + xproto.WindowClassInputOutput, b.Scr.RootVisual, + xproto.CwBackPixel|xproto.CwEventMask, []uint32{ + conf.Bg, + xproto.EventMaskStructureNotify }) + + class := icccm.WmClass{"hbspbar", "hbspbar"} + icccm.WmClassSet(b.Xu, b.w, &class) + + ewmh.WmWindowTypeSet(b.Xu, b.w, []string{"_NET_WM_WINDOW_TYPE_DOCK"}) + ewmh.WmStateSet(b.Xu, b.w, []string{"_NET_WM_STATE_STICKY", "_NET_WM_STATE_ABOVE"}) + + err = xproto.MapWindowChecked(b.X, b.w).Check() + if err != nil { + return common.Perror("xproto.MapWindow", err) + } + + go b.Mon.TopPadding(conf.H) + + return nil +} + +func (b *bar) Destroy() { + b.Mon.TopPadding(0) +} + +type eventwrap struct { + Ev xgb.Event + Err error +} + +// External interface +type Handle struct { + Create chan int + Destroy chan int + NewState chan *bspc.State + Err chan error +} + +func Init(state *bspc.State) (*Handle, error) { + bars = list.New() + + x, err := xgb.NewConn() + if err != nil { return nil, err } + + xu, err := xgbutil.NewConnXgb(x) + if err != nil { return nil, err } + + setup := xproto.Setup(x) + screen := setup.DefaultScreen(x) + + var h Handle + h.Create = make(chan int) + h.Destroy = make(chan int) + h.NewState = make(chan *bspc.State) + h.Err = make(chan error) + + events := make(chan *eventwrap) + + go func() { for { + ev, err := x.WaitForEvent() + w := eventwrap{ev, err} + events <- &w + + select { + case <- events: return + default: + } + }}() + + go func() { + defer func() { events <- nil }() + + for { + select { + case e := <- events: + if e.Ev == nil && e.Err == nil { + h.Err <- fmt.Errorf("X connection clossed") + return + } + + if e.Err != nil { + common.Perror("x.WaitForEvents", err) + continue + } + + switch e.Ev.(type) { + case xproto.DestroyNotifyEvent: + h.Err <- fmt.Errorf("Window destroyed") + return + } + case id := <- h.Destroy: + destroy(id) + case id := <- h.Create: + if err := create(x, xu, screen, state.GetMon(id)); err != nil { + h.Err <- fmt.Errorf("Couldn't create window: %s\n", err) + return + } + case state := <- h.NewState: + for e := bars.Front(); e != nil; e = e.Next() { + v := e.Value.(*bar) + v.Mon = state.GetMon(v.Mon.ID) + } + default: + } + } + }() + + return &h, nil +} + +func create(x *xgb.Conn, xu *xgbutil.XUtil, + scr *xproto.ScreenInfo, m *bspc.Monitor) (error) { + var b bar + + b.X = x + b.Xu = xu + b.Scr = scr + b.Mon = m + + if err := b.Init(); err != nil { + return err + } + + bars.PushBack(&b) + return nil +} + +func destroy(monid int) (bool) { + var next *list.Element + + for e := bars.Front(); e != nil; e = next { + next = e.Next() + v := e.Value.(*bar) + + /* A monid of -1 means that we're doing + the bar.Cleanup schermoodle today */ + if monid == -1 || monid > 0 && v.Mon.ID == int(monid) { + v.Destroy() + bars.Remove(e) + if monid != -1 { return true } + } + } + + if monid == -1 { return true } + return false +} + +func Cleanup() { + destroy(-1) +} diff --git a/bspc/bspc.go b/bspc/bspc.go @@ -1,6 +1,7 @@ package bspc import ( + "fmt" "io" "bufio" "strings" @@ -14,17 +15,48 @@ type State struct { Monitors []*Monitor } +func (s *State) GetMon(id int) (*Monitor) { + for _, m := range s.Monitors { + if m.ID == id { + return m + } + } + + return nil +} + type Monitor struct { ID int Rectangle *Rect Desktops []*Desktop } +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 int - Y int - Width int - Height int + X uint + Y uint + Width uint + Height uint } type Desktop struct { @@ -95,25 +127,20 @@ func Subscribe() (*Subscriber, error) { s.pipe = pipe s.scanner = bufio.NewScanner(s.pipe) - go func() { - for { - select { - case <- s.cleanup: - s.cmd.Wait() - return - default: - if event := s.get(); event != nil { - s.Event <- event - } + go func() { for { + select { + default: + if event := s.get(); event != nil { + s.Event <- event } } - }() + }}() return &s, nil } func (s *Subscriber) Close() { - s.cleanup <- true + s.cmd.Process.Kill() } func (s *Subscriber) getLine() (string, error) { @@ -133,7 +160,7 @@ func (s *Subscriber) get() (*Event) { var err error for { line, err = s.getLine() - if line[0] != 'W' { break } + if len(line) > 0 && line[0] != 'W' { break } } if line == "" { return nil } diff --git a/common/common.go b/common/common.go @@ -12,11 +12,7 @@ func Perror(function string, err error) error { func Error(format string, a ... any) (int, error) { fmt.Fprintf(os.Stderr, "error: ") - if (len(a) == 0) { - return fmt.Fprintf(os.Stderr, format) - } else { - return fmt.Fprintf(os.Stderr, format, a) - } + return fmt.Fprintf(os.Stderr, format, a...) } func Intify(s string) (int, error) { diff --git a/go.mod b/go.mod @@ -3,6 +3,6 @@ module hhvn.uk/hbspbar go 1.20 require ( - github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect - github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect + github.com/jezek/xgb v1.1.0 // indirect + github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 // indirect ) diff --git a/go.sum b/go.sum @@ -1,4 +1,6 @@ -github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk= -github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA= -github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= +github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= +github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= +github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk= +github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 h1:Pf/0BAbppEOq4azPH6fnvUX2dycAwZdGkdxFn25j44c= +github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0/go.mod h1:AHecLyFNy6AN9f/+0AH/h1MI7X1+JL5bmCz4XlVZk7Y= diff --git a/main.go b/main.go @@ -1,16 +1,14 @@ package main import ( + "os" "strings" + "syscall" + "os/signal" "hhvn.uk/hbspbar/common" "hhvn.uk/hbspbar/bspc" "hhvn.uk/hbspbar/bar" - - /* - "github.com/BurntSushi/xgb" - "github.com/BurntSushi/xgb/xproto" - */ ) var reloadEvents = []string{ @@ -40,7 +38,7 @@ func reloadState(current *bspc.State) (*bspc.State, error) { } } -func handleEvent(events *bspc.Subscriber) (bool) { +func handleEvent(events *bspc.Subscriber, state *bspc.State, bar *bar.Handle) (bool) { var event *bspc.Event select { @@ -55,6 +53,16 @@ func handleEvent(events *bspc.Subscriber) (bool) { return false } + if strings.HasPrefix(event.Name, "monitor_") { + id, err := common.Intify(event.Tokens[0]) + if err == nil { + switch (event.Name) { + case "monitor_add": bar.Create <- id + case "monitor_remove": bar.Destroy <- id + } + } + } + for i := 0; i < len(reloadEvents); i++ { if event.Name == reloadEvents[i] { return true @@ -65,26 +73,54 @@ func handleEvent(events *bspc.Subscriber) (bool) { } func main() { + signals := make(chan os.Signal, 1) + signal.Notify(signals, + syscall.SIGINT, + syscall.SIGHUP, + syscall.SIGTERM) + state, err := bspc.LoadState() - if (err != nil) { + if err != nil { common.Error("Couldn't load bspwm state: %s\n", err) return } events, err := bspc.Subscribe() - if (err != nil) { + if err != nil { common.Error("Couldn't subscribe to bspwm: %s\n", err) return } defer events.Close() + barhandle, err := bar.Init(state) + if err != nil { + common.Error("Couldn't initialize X: %s\n", err) + return + } + + for _, m := range state.Monitors { + barhandle.Create <- m.ID + } + defer bar.Cleanup() for { - if reload := handleEvent(events); reload { + if reload := handleEvent(events, state, barhandle); reload { state, err = reloadState(state) if err != nil { common.Error("Couldn't reload bspwm state: %s\n", err) } + + barhandle.NewState <- state + } + + select { + case s := <- signals: + common.Error("%s\n", s) + return + case err := <- barhandle.Err: + common.Error("%s\n", err) + return + default: } } }