bar.go (4838B)
1 package bar // import "hhvn.uk/hbspbar/bar" 2 3 import ( 4 "fmt" 5 "image" 6 "errors" 7 "image/color" 8 9 "hhvn.uk/hbspbar/config" 10 "hhvn.uk/hbspbar/common" 11 "hhvn.uk/hbspbar/status" 12 "hhvn.uk/hbspbar/bspc" 13 "hhvn.uk/hbspbar/drw" 14 ) 15 16 17 var bars map[int]*bar 18 19 type drawinfo struct { 20 State *bspc.State 21 Blocks *status.Blocks 22 } 23 24 type bar struct { 25 id int 26 w *drw.Window 27 i *image.RGBA 28 redraw chan drawinfo 29 } 30 31 func create(state *bspc.State, id int) (error) { 32 var b bar 33 34 if err := b.init(id, state); err != nil { 35 return err 36 } 37 38 if bars == nil { 39 bars = make(map[int]*bar) 40 } 41 42 bars[b.id] = &b 43 return nil 44 } 45 46 func (b *bar) init(id int, state *bspc.State) error { 47 b.id = id 48 b.i = nil 49 50 mon, err := b.getmon(state) 51 if err != nil { return err } 52 53 go mon.TopPadding(config.H) 54 55 geom := mon.Rectangle 56 57 b.w, err = drw.WindowCreate(geom.X, geom.Y, geom.Width, config.H) 58 if err != nil { return err } 59 60 rect := image.Rect(0, 0, int(mon.Rectangle.Width), int(config.H)) 61 b.i = image.NewRGBA(rect) 62 63 b.redraw = make(chan drawinfo, 20) 64 65 go func(){ 66 for d := range b.redraw { 67 // Gobble up excess redraws 68 gobble: for { 69 select { 70 case d = <- b.redraw: 71 default: break gobble 72 } 73 } 74 75 b.draw(d) 76 } 77 }() 78 79 return nil 80 } 81 82 func (b *bar) destroy(state *bspc.State) { 83 close(b.redraw) 84 mon, _ := b.getmon(state) 85 if (mon != nil) { 86 mon.TopPadding(0) 87 } 88 } 89 90 func (b bar) getmon(state *bspc.State) (*bspc.Monitor, error) { 91 return state.GetMon(b.id) 92 } 93 94 func (b *bar) drawText(x int, col color.Color, text string) (int, error) { 95 return drw.DrawText(b.i, x, col, text) 96 } 97 98 func (b *bar) drawRect(x, y, w, h int, c color.Color, fill bool) { 99 drw.DrawRect(b.i, x, y, w, h, c, fill) 100 } 101 102 func (b bar) draw(d drawinfo) { 103 var bg color.Color 104 var filled bool 105 106 state := d.State 107 blocks := d.Blocks 108 109 mon, err := b.getmon(state) 110 if err != nil { 111 return 112 } 113 114 // Dimensions of drawing space 115 cx := 0 116 w := int(mon.Rectangle.Width) 117 h := int(config.H) 118 119 120 // Draw background 121 b.drawRect(0, 0, w, h, config.Bg, true) 122 123 // Monitor 124 montext := mon.Name 125 monw := 6 + drw.TextWidth(montext) + 8 126 127 if mon.Focused { 128 bg = config.Sel 129 } else { 130 bg = config.UnselMon 131 } 132 133 b.drawRect(cx, 0, monw, h, bg, true) 134 b.drawText(cx + 6, config.Fg, montext) 135 cx += monw 136 137 // Desktops 138 boxw := 4 139 ds := len(mon.Desktops) 140 dw := 4 + (4 + boxw + 2 + 3) * ds + 5 141 142 for i := 0; i < ds; i++ { 143 dw += drw.TextWidth(mon.Desktops[i].Name) 144 } 145 146 cx += 4 147 for i := 0; i < ds; i++ { 148 d := mon.Desktops[i] 149 150 cx += 5 151 152 if d.Focused { 153 bg = config.Sel 154 } else { 155 bg = config.FgDark 156 } 157 158 filled = d.Root != nil 159 160 b.drawRect(cx, 3, boxw, h - 7, bg, filled) 161 cx += boxw + 2 162 ax, _ := b.drawText(cx, config.Fg, d.Name) 163 cx += ax + 2 164 } 165 166 cx = int(mon.Rectangle.Width) - 6 167 for i := blocks.Len() - 1; i >= 0; i -= 1 { 168 s, ok := blocks.Get(i) 169 if ok { 170 cx -= s.W 171 cx -= config.StatusPad 172 } 173 } 174 cx += config.StatusPad 175 cx -= 6 176 177 b.drawRect(cx, 0, int(mon.Rectangle.Width) - cx, int(config.H), 178 config.Status, true) 179 180 cx = int(mon.Rectangle.Width) - 6 181 for i := blocks.Len() - 1; i >= 0; i -= 1 { 182 s, ok := blocks.Get(i) 183 if ok { 184 cx -= s.W 185 186 drw.AddImg(b.i, cx, s.W, s.I) 187 188 cx -= config.StatusPad 189 } 190 } 191 192 b.w.Paint(b.i) 193 } 194 195 var Create = make(chan int) 196 var Destroy = make(chan int) 197 var NewState = make(chan *bspc.State) 198 var Err = make(chan error) 199 200 // Store the state for when bar.Cleanup() is called 201 // This is a bit hacky but I don't really see any alternative 202 var finalstate *bspc.State = nil 203 204 func init() { 205 var state *bspc.State = nil 206 var blocks *status.Blocks = nil 207 208 go func() { 209 defer func() { 210 close(Create) 211 close(Destroy) 212 close(NewState) 213 close(Err) 214 }() 215 216 for { 217 select { 218 case e := <- drw.Events: 219 if e.Ev == nil && e.Err == nil { 220 Err <- fmt.Errorf("X connection clossed") 221 return 222 } 223 224 if e.Err != nil { 225 common.Perror("X.WaitForEvents", e.Err) 226 continue 227 } 228 229 // switch e.Ev.(type) { 230 // case xproto.DestroyNotifyEvent: 231 // Err <- fmt.Errorf("Window destroyed") 232 // return 233 // default: 234 // } 235 case id := <- Destroy: 236 bars[id].destroy(state) 237 delete(bars, id) 238 case id := <- Create: 239 if state == nil { 240 Err <- errors.New("attempted to create a bar with uninitialized state") 241 return 242 } 243 if _, ok := bars[id]; ok { break } 244 if err := create(state, id); err != nil { 245 Err <- fmt.Errorf("Couldn't create window: %s\n", err) 246 return 247 } 248 case blocks = <- status.NewBlocks: 249 for _, b := range bars { 250 b.redraw <- drawinfo{state, blocks} 251 } 252 case state = <- NewState: 253 finalstate = state 254 for _, b := range bars { 255 b.redraw <- drawinfo{state, blocks} 256 } 257 } 258 } 259 }() 260 } 261 262 func Cleanup() { 263 if finalstate != nil { 264 for _, b := range bars { 265 b.destroy(finalstate) 266 } 267 } 268 }