5bar5/bar.go

201 lines
5.1 KiB
Go

package main
import (
"fmt"
"image"
"os"
"strconv"
"sync"
"github.com/BurntSushi/freetype-go/freetype/truetype"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/ewmh"
"github.com/BurntSushi/xgbutil/xevent"
"github.com/BurntSushi/xgbutil/xgraphics"
"github.com/BurntSushi/xgbutil/xwindow"
)
// Bar is a struct with information about the bar.
type Bar struct {
// Connection to the X server, the bar window, and the bar image.
xu *xgbutil.XUtil
win *xwindow.Window
img *xgraphics.Image
// The width and height of the bar.
w, h int
// This is a sum of all of the block widths, used to draw a block to the
// right of the last block.
xsum int
// The font that should be used.
font *truetype.Font
size float64
// A map with information about the block, see the `Block` type.
block *sync.Map
// A channel where the block should be send to to once its ready to be
// redrawn.
redraw chan *Block
}
func initBar(x, y, w, h int, font string, size float64) (*Bar, error) {
bar := new(Bar)
var err error
// Set up a connection to the X server.
bar.xu, err = xgbutil.NewConn()
if err != nil {
return nil, err
}
// Run the main X event loop, this is uses to catch events.
go xevent.Main(bar.xu)
// Listen to the root window for property change events, used to check if
// the user changed the focused window or active workspace for example.
if err := xwindow.New(bar.xu, bar.xu.RootWin()).Listen(xproto.
EventMaskPropertyChange); err != nil {
return nil, err
}
// Create a window for the bar. This window listens to button press events
// in order to respond to them.
bar.win, err = xwindow.Generate(bar.xu)
if err != nil {
return nil, err
}
bar.win.Create(bar.xu.RootWin(), x, y, w, h, xproto.CwBackPixel|xproto.
CwEventMask, 0x000000, xproto.EventMaskButtonPress)
// EWMH stuff to make the window behave like an actual bar.
// XXX: `WmStateSet` and `WmDesktopSet` are basically here to keep OpenBox
// happy, can I somehow remove them and just use `_NET_WM_WINDOW_TYPE_DOCK`
// like I can with WindowChef?
if err := ewmh.WmWindowTypeSet(bar.xu, bar.win.Id, []string{
"_NET_WM_WINDOW_TYPE_DOCK"}); err != nil {
return nil, err
}
if err := ewmh.WmStateSet(bar.xu, bar.win.Id, []string{
"_NET_WM_STATE_STICKY"}); err != nil {
return nil, err
}
if err := ewmh.WmDesktopSet(bar.xu, bar.win.Id, ^uint(0)); err != nil {
return nil, err
}
if err := ewmh.WmNameSet(bar.xu, bar.win.Id, "melonbar"); err != nil {
return nil, err
}
// Map window.
bar.win.Map()
// XXX: Moving the window is again a hack to keep OpenBox happy.
bar.win.Move(x, y)
// Create the bar image.
bar.img = xgraphics.New(bar.xu, image.Rect(0, 0, w, h))
if err := bar.img.XSurfaceSet(bar.win.Id); err != nil {
return nil, err
}
bar.img.XDraw()
bar.w = w
bar.h = h
// Load font.
// TODO: I don't *really* want to use `ttf` fonts but there doesn't seem to
// be any `pcf` Go library at the moment. I have tried the plan9 fonts,
// which do work, but honestly it's a pain in the ass (read: impossible) to
// convert muh cure font.
f, err := os.Open(font)
if err != nil {
return nil, err
}
bar.font, err = xgraphics.ParseFont(f)
if err != nil {
return nil, err
}
bar.size = size
bar.block = new(sync.Map)
bar.redraw = make(chan *Block)
// Listen to mouse events and execute the required function.
xevent.ButtonPressFun(func(_ *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
// Determine what block the cursor is in.
// TODO: This feels a bit slow at the moment, can I improve it?
var block *Block
bar.block.Range(func(name, i interface{}) bool {
block = i.(*Block)
// XXX: Hack for music block.
if name == "music" {
tw, _ := xgraphics.Extents(bar.font, bar.size, block.txt)
if ev.EventX > int16(block.x+(block.w-tw+(block.xoff*2))) && ev.
EventX < int16(block.x+block.w) {
return false
}
return true
}
if ev.EventX > int16(block.x) && ev.EventX < int16(block.x+block.
w) {
return false
}
return true
})
// Execute the function as specified.
block.actions["button"+strconv.Itoa(int(ev.Detail))]()
}).Connect(bar.xu, bar.win.Id)
return bar, nil
}
func (bar *Bar) draw(block *Block) error {
// Calculate the required x coordinate for the different aligments.
tw, _ := xgraphics.Extents(bar.font, bar.size, block.txt)
var x int
switch block.align {
case 'l':
x = block.x
case 'c':
x = block.x + ((block.w / 2) - (tw / 2))
case 'r':
x = (block.x + block.w) - tw
case 'a':
x = (bar.w / 2) - (tw / 2)
default:
return fmt.Errorf("draw %#U: Not a valid aligment rune", block.align)
}
x += block.xoff
// Color the background.
block.img.For(func(cx, cy int) xgraphics.BGRA {
// XXX: Hack for music block.
if block.w == 660 {
if cx < x+block.xoff {
return hexToBGRA("#445967")
}
return hexToBGRA(block.bg)
}
return hexToBGRA(block.bg)
})
// Draw the text.
// TODO: Center text vertically automatically.
if _, _, err := block.img.Text(x, 6, hexToBGRA(block.fg), bar.size, bar.
font, block.txt); err != nil {
return err
}
block.img.XDraw()
bar.img.XPaint(bar.win.Id)
return nil
}