hbspbar

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

commit 7adfd6e5c641f7543fcf87e4c558579dc652fdf7
parent 1c3013dfcbbfb33cbfaf616c1b44051e35d7746c
Author: hhvn <dev@hhvn.uk>
Date:   Tue, 22 Aug 2023 12:50:03 +0100

Drawing desktops

Diffstat:
Mbar/bar.go | 146++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Abar/x.go | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbspc/bspc.go | 40+++++++++++++++++++++++++++++-----------
Mmain.go | 58+++++++++++++++++++++++++++++++---------------------------
4 files changed, 262 insertions(+), 61 deletions(-)

diff --git a/bar/bar.go b/bar/bar.go @@ -2,6 +2,7 @@ package bar import ( "fmt" + "sync" "container/list" "hhvn.uk/hbspbar/common" @@ -13,33 +14,54 @@ import ( "github.com/jezek/xgbutil" "github.com/jezek/xgbutil/icccm" "github.com/jezek/xgbutil/ewmh" + "github.com/jezek/xgbutil/xgraphics" + + "github.com/BurntSushi/freetype-go/freetype/truetype" ) var conf = struct { - H uint - Bg uint32 - Fg uint32 - Sel uint32 - Urg uint32 -} { 16, + H uint + Bg uint32 + Bg2 uint32 + Bg3 uint32 + Fg uint32 + FgDark uint32 + Sel uint32 + Urg uint32 + Font string + FontSize float64 + FontYPad int +} { 17, 0xff050a10, + 0xff0c1726, + 0xff14283c, 0xffeeeeee, + 0xff444444, 0xff1b364b, - 0xff90222b } + 0xff90222b, + "/usr/share/fonts/TTF/DejaVuSansMono.ttf", + 11.0, 1 } var bars *list.List +var font *truetype.Font type bar struct { - X *xgb.Conn - Xu *xgbutil.XUtil - Scr *xproto.ScreenInfo - Mon *bspc.Monitor - w xproto.Window + X *xgb.Conn + Xu *xgbutil.XUtil + Scr *xproto.ScreenInfo + Mon *bspc.Monitor + w xproto.Window + i *xgraphics.Image + tw, th int + tbpad int + drawing sync.Mutex } -func (b *bar) Init() error { +func (b *bar) init() error { var err error + go b.Mon.TopPadding(conf.H) + b.w, err = xproto.NewWindowId(b.X) if err != nil { return common.Perror("xproto.NewWindowId", err) @@ -48,7 +70,7 @@ func (b *bar) Init() error { 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.WindowClassInputOutput, b.Scr.RootVisual, xproto.CwBackPixel|xproto.CwEventMask, []uint32{ conf.Bg, xproto.EventMaskStructureNotify }) @@ -64,12 +86,81 @@ func (b *bar) Init() error { return common.Perror("xproto.MapWindow", err) } - go b.Mon.TopPadding(conf.H) + b.drawReInit() + b.tw, b.th, _ = b.i.Text(0, 0, int2BGRA(conf.Fg), conf.FontSize, font, "X") + b.tbpad = (int(conf.H) - b.th) / 2 return nil } -func (b *bar) Destroy() { +func (b *bar) draw() { + var bg uint32 + var filled bool + + b.drawing.Lock() + b.drawReInit() + + // Dimensions of drawing space + cx := 0 + w := int(b.Mon.Rectangle.Width) + h := int(conf.H) + + + // Draw background + b.drawRect(0, 0, w, h, conf.Bg, true) + + // Monitor + montext := b.Mon.Name + monw := 6 + b.textWidth(montext) + 8 + + if b.Mon.Focused { + bg = conf.Sel + } else { + bg = conf.Bg2 + } + + b.drawRect(cx, 0, monw, h, bg, true) + b.drawText(cx + 6, conf.Fg, montext) + cx += monw + + // Desktops + boxw := 4 + ds := len(b.Mon.Desktops) + dw := 4 + (4 + boxw + 2 + 3) * ds + 5 + + for i := 0; i < ds; i++ { + dw += b.textWidth(b.Mon.Desktops[i].Name) + } + + cx += 4 + for i := 0; i < ds; i++ { + d := b.Mon.Desktops[i] + + cx += 5 + + if d.Focused { + bg = conf.Sel + } else { + bg = conf.FgDark + } + + filled = d.Root != nil + + b.drawRect(cx, 3, boxw, h - 7, bg, filled) + cx += boxw + 2 + ax, _ := b.drawText(cx, conf.Fg, d.Name) + cx += ax + 2 + } + cx += 5 + + b.i.XSurfaceSet(b.w) + b.i.XDraw() + b.i.XPaint(b.w) + + b.drawing.Unlock() +} + +func (b *bar) destroy() { b.Mon.TopPadding(0) } @@ -95,6 +186,9 @@ func Init(state *bspc.State) (*Handle, error) { xu, err := xgbutil.NewConnXgb(x) if err != nil { return nil, err } + font, err = getFont(conf.Font) + if err != nil { return nil, err } + setup := xproto.Setup(x) screen := setup.DefaultScreen(x) @@ -105,20 +199,22 @@ func Init(state *bspc.State) (*Handle, error) { h.Err = make(chan error) events := make(chan *eventwrap) + xstop := make(chan bool, 1) go func() { for { ev, err := x.WaitForEvent() w := eventwrap{ev, err} - events <- &w select { - case <- events: return + case <- xstop: + return default: + events <- &w } }}() go func() { - defer func() { events <- nil }() + defer func() { xstop <- true }() for { select { @@ -137,6 +233,7 @@ func Init(state *bspc.State) (*Handle, error) { case xproto.DestroyNotifyEvent: h.Err <- fmt.Errorf("Window destroyed") return + default: } case id := <- h.Destroy: destroy(id) @@ -149,12 +246,12 @@ func Init(state *bspc.State) (*Handle, error) { for e := bars.Front(); e != nil; e = e.Next() { v := e.Value.(*bar) v.Mon = state.GetMon(v.Mon.ID) + go v.draw() } - default: } } }() - + return &h, nil } @@ -166,11 +263,14 @@ func create(x *xgb.Conn, xu *xgbutil.XUtil, b.Xu = xu b.Scr = scr b.Mon = m + b.i = nil - if err := b.Init(); err != nil { + if err := b.init(); err != nil { return err } + go b.draw() + bars.PushBack(&b) return nil } @@ -185,7 +285,7 @@ func destroy(monid int) (bool) { /* 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() + v.destroy() bars.Remove(e) if monid != -1 { return true } } diff --git a/bar/x.go b/bar/x.go @@ -0,0 +1,79 @@ +package bar + +import ( + "os" + "image" + + "hhvn.uk/hbspbar/common" + + "github.com/jezek/xgbutil/xgraphics" + "github.com/BurntSushi/freetype-go/freetype/truetype" +) + +func getFont(path string) (*truetype.Font, error) { + read, err := os.Open(path) + if err != nil { + return nil, common.Perror("os.Open", err) + } + + font, err := xgraphics.ParseFont(read) + if err != nil { + return nil, common.Perror("xgraphics.ParseFont", err) + } + + return font, nil +} + +func int2BGRA(argb uint32) (xgraphics.BGRA) { + return xgraphics.BGRA{ + B: uint8( argb & 0x000000ff), + G: uint8((argb & 0x0000ff00) >> 8), + R: uint8((argb & 0x00ff0000) >> 16), + A: uint8((argb & 0xff000000) >> 24) } +} + +func (b *bar) rect() image.Rectangle { + return image.Rect( 0, 0, + int(b.Mon.Rectangle.Width), int(conf.H)) +} + +func (b *bar) drawReInit() { + if b.i != nil { + b.i.Destroy() + } + + b.i = xgraphics.New(b.Xu, b.rect()) +} + +func (b *bar) drawText(x int, col uint32, text string) (int, error) { + nx, _, err := b.i.Text(x, conf.FontYPad, + int2BGRA(col), conf.FontSize, font, text) + return nx - x, err +} + +func (b *bar) textWidth(str string) int { + return len(str) * b.tw +} + +func pointInRect(px, py, x, y, w, h int) bool { + if px >= x && px <= x + w && py >= x && py <= y + h { + return true + } else { + return false + } +} + +func (b *bar) drawRect(x, y, w, h int, c uint32, fill bool) { + col := int2BGRA(c) + + var ix, iy int + + for ix = x; ix < x + w; ix++ { + for iy = y; iy < y + h; iy++ { + if fill || ix == x || ix == x + w - 1 || + iy == y || iy == y + h - 1 { + b.i.Set(ix, iy, col) + } + } + } +} diff --git a/bspc/bspc.go b/bspc/bspc.go @@ -12,7 +12,8 @@ import ( ) type State struct { - Monitors []*Monitor + Monitors []*Monitor + FocusedMonitorID int } func (s *State) GetMon(id int) (*Monitor) { @@ -26,9 +27,12 @@ func (s *State) GetMon(id int) (*Monitor) { } type Monitor struct { - ID int - Rectangle *Rect - Desktops []*Desktop + Focused bool + ID int + Name string + Rectangle *Rect + Desktops []*Desktop + FocusedDesktopID int } func (m *Monitor) TopPadding(px uint) { @@ -59,9 +63,18 @@ type Rect struct { Height uint } +type Node struct { + ID int + FirstChild *Node + SecondChild *Node +} + type Desktop struct { - Name string - Layout string + Focused bool + Name string + Layout string + ID int + Root *Node } func LoadState() (*State, error) { @@ -91,6 +104,14 @@ func LoadState() (*State, error) { 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 } @@ -128,11 +149,8 @@ func Subscribe() (*Subscriber, error) { s.scanner = bufio.NewScanner(s.pipe) go func() { for { - select { - default: - if event := s.get(); event != nil { - s.Event <- event - } + if event := s.get(); event != nil { + s.Event <- event } }}() diff --git a/main.go b/main.go @@ -38,38 +38,38 @@ func reloadState(current *bspc.State) (*bspc.State, error) { } } -func handleEvent(events *bspc.Subscriber, state *bspc.State, bar *bar.Handle) (bool) { +func handleEvents(events *bspc.Subscriber, bar *bar.Handle) (chan error, chan bool) { var event *bspc.Event - select { - case event = <-events.Event: - break - default: - return false - } + errors := make(chan error) + reload := make(chan bool) - if event.Err != nil { - common.Error("Couldn't read event: %s\n", event.Err) - return false - } + go func(){ for { + event = <-events.Event - 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 + if event.Err != nil { + errors <- event.Err + return + } + + 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 + for i := 0; i < len(reloadEvents); i++ { + if event.Name == reloadEvents[i] { + reload <- true + } } - } + }}() - return false + return errors, reload } func main() { @@ -103,24 +103,28 @@ func main() { } defer bar.Cleanup() + everr, evreload := handleEvents(events, barhandle) + for { - if reload := handleEvent(events, state, barhandle); reload { + + select { + case <- evreload: state, err = reloadState(state) if err != nil { common.Error("Couldn't reload bspwm state: %s\n", err) } barhandle.NewState <- state - } - select { + case err := <- everr: + common.Error("Couldn't read event: %s\n", err) + return case s := <- signals: common.Error("%s\n", s) return case err := <- barhandle.Err: common.Error("%s\n", err) return - default: } } }