package bar // import "hhvn.uk/hbspbar/bar" import ( "fmt" "image" "errors" "image/color" "hhvn.uk/hbspbar/config" "hhvn.uk/hbspbar/common" "hhvn.uk/hbspbar/status" "hhvn.uk/hbspbar/bspc" "hhvn.uk/hbspbar/drw" ) var bars map[int]*bar type drawinfo struct { State *bspc.State Blocks *status.Blocks } type bar struct { id int w *drw.Window i *image.RGBA redraw chan drawinfo } func (b *bar) init(state *bspc.State) error { mon, err := b.getmon(state) if err != nil { return err } go mon.TopPadding(config.H) geom := mon.Rectangle b.w, err = drw.WindowCreate(geom.X, geom.Y, geom.Width, config.H) if err != nil { return err } rect := image.Rect(0, 0, int(mon.Rectangle.Width), int(config.H)) b.i = image.NewRGBA(rect) b.redraw = make(chan drawinfo, 20) go func(){ for d := range b.redraw { // Gobble up excess redraws gobble: for { select { case d = <- b.redraw: default: break gobble } } b.draw(d) } }() return nil } func (b bar) getmon(state *bspc.State) (*bspc.Monitor, error) { return state.GetMon(b.id) } func (b *bar) drawText(x int, col color.Color, text string) (int, error) { return drw.DrawText(b.i, x, col, text) } func (b *bar) drawRect(x, y, w, h int, c color.Color, fill bool) { drw.DrawRect(b.i, x, y, w, h, c, fill) } func (b bar) draw(d drawinfo) { var bg color.Color var filled bool state := d.State blocks := d.Blocks mon, err := b.getmon(state) if err != nil { return } // Dimensions of drawing space cx := 0 w := int(mon.Rectangle.Width) h := int(config.H) // Draw background b.drawRect(0, 0, w, h, config.Bg, true) // Monitor montext := mon.Name monw := 6 + drw.TextWidth(montext) + 8 if mon.Focused { bg = config.Sel } else { bg = config.UnselMon } b.drawRect(cx, 0, monw, h, bg, true) b.drawText(cx + 6, config.Fg, montext) cx += monw // Desktops boxw := 4 ds := len(mon.Desktops) dw := 4 + (4 + boxw + 2 + 3) * ds + 5 for i := 0; i < ds; i++ { dw += drw.TextWidth(mon.Desktops[i].Name) } cx += 4 for i := 0; i < ds; i++ { d := mon.Desktops[i] cx += 5 if d.Focused { bg = config.Sel } else { bg = config.FgDark } filled = d.Root != nil b.drawRect(cx, 3, boxw, h - 7, bg, filled) cx += boxw + 2 ax, _ := b.drawText(cx, config.Fg, d.Name) cx += ax + 2 } cx = int(mon.Rectangle.Width) - 6 for i := blocks.Len() - 1; i >= 0; i -= 1 { s, ok := blocks.Get(i) if ok { cx -= s.W cx -= config.StatusPad } } cx += config.StatusPad cx -= 6 b.drawRect(cx, 0, int(mon.Rectangle.Width) - cx, int(config.H), config.Status, true) cx = int(mon.Rectangle.Width) - 6 for i := blocks.Len() - 1; i >= 0; i -= 1 { s, ok := blocks.Get(i) if ok { cx -= s.W drw.AddImg(b.i, cx, s.W, s.I) cx -= config.StatusPad } } b.w.Paint(b.i) } func (b *bar) destroy(state *bspc.State) { close(b.redraw) mon, _ := b.getmon(state) if (mon != nil) { mon.TopPadding(0) } } var Create = make(chan int) var Destroy = make(chan int) var NewState = make(chan *bspc.State) var Err = make(chan error) // Store the state for when bar.Cleanup() is called // This is a bit hacky but I don't really see any alternative var finalstate *bspc.State = nil func init() { var state *bspc.State = nil var blocks *status.Blocks = nil go func() { defer func() { close(Create) close(Destroy) close(NewState) close(Err) }() for { select { case e := <- drw.Events: if e.Ev == nil && e.Err == nil { Err <- fmt.Errorf("X connection clossed") return } if e.Err != nil { common.Perror("X.WaitForEvents", e.Err) continue } // switch e.Ev.(type) { // case xproto.DestroyNotifyEvent: // Err <- fmt.Errorf("Window destroyed") // return // default: // } case id := <- Destroy: bars[id].destroy(state) delete(bars, id) case id := <- Create: if state == nil { Err <- errors.New("attempted to create a bar with uninitialized state") return } if _, ok := bars[id]; ok { break } if err := create(state, id); err != nil { Err <- fmt.Errorf("Couldn't create window: %s\n", err) return } case blocks = <- status.NewBlocks: for _, b := range bars { b.redraw <- drawinfo{state, blocks} } case state = <- NewState: finalstate = state for _, b := range bars { b.redraw <- drawinfo{state, blocks} } } } }() } func create(state *bspc.State, id int) (error) { var b bar b.id = id b.i = nil if err := b.init(state); err != nil { return err } if bars == nil { bars = make(map[int]*bar) } bars[b.id] = &b return nil } func Cleanup() { if finalstate != nil { for _, b := range bars { b.destroy(finalstate) } } }