commit 7adfd6e5c641f7543fcf87e4c558579dc652fdf7
parent 1c3013dfcbbfb33cbfaf616c1b44051e35d7746c
Author: hhvn <dev@hhvn.uk>
Date: Tue, 22 Aug 2023 12:50:03 +0100
Drawing desktops
Diffstat:
M | bar/bar.go | | | 146 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
A | bar/x.go | | | 79 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | bspc/bspc.go | | | 40 | +++++++++++++++++++++++++++++----------- |
M | main.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:
}
}
}