2017-07-11 01:17:27 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-07-12 16:21:51 +00:00
|
|
|
"fmt"
|
2017-07-11 01:17:27 +00:00
|
|
|
"image"
|
2018-12-24 19:16:42 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"path"
|
2017-07-13 18:51:08 +00:00
|
|
|
"strconv"
|
2017-07-11 01:17:27 +00:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/BurntSushi/xgb/xproto"
|
|
|
|
"github.com/BurntSushi/xgbutil"
|
|
|
|
"github.com/BurntSushi/xgbutil/ewmh"
|
2017-07-12 16:21:51 +00:00
|
|
|
"github.com/BurntSushi/xgbutil/xevent"
|
2017-07-11 01:17:27 +00:00
|
|
|
"github.com/BurntSushi/xgbutil/xgraphics"
|
|
|
|
"github.com/BurntSushi/xgbutil/xwindow"
|
2018-12-24 19:16:42 +00:00
|
|
|
"golang.org/x/image/font"
|
|
|
|
"golang.org/x/image/font/plan9font"
|
|
|
|
"golang.org/x/image/math/fixed"
|
2017-07-11 01:17:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Bar is a struct with information about the bar.
|
|
|
|
type Bar struct {
|
2017-07-13 19:22:19 +00:00
|
|
|
// Connection to the X server, the bar window, and the bar image.
|
2017-07-11 01:17:27 +00:00
|
|
|
xu *xgbutil.XUtil
|
|
|
|
win *xwindow.Window
|
|
|
|
img *xgraphics.Image
|
|
|
|
|
2017-07-11 15:25:19 +00:00
|
|
|
// The width and height of the bar.
|
|
|
|
w, h int
|
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
// This is a sum of all of the block widths, used to draw a block to the
|
|
|
|
// right of the last block.
|
2017-07-11 15:25:19 +00:00
|
|
|
xsum int
|
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
// The font that should be used.
|
2018-12-24 19:16:42 +00:00
|
|
|
face font.Face
|
2018-12-23 17:03:56 +00:00
|
|
|
|
2017-07-13 19:22:19 +00:00
|
|
|
// A map with information about the block, see the `Block` type.
|
2017-07-11 01:17:27 +00:00
|
|
|
block *sync.Map
|
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
// A channel where the block should be send to to once its ready to be
|
|
|
|
// redrawn.
|
2017-07-13 18:51:08 +00:00
|
|
|
redraw chan *Block
|
2017-07-11 01:17:27 +00:00
|
|
|
}
|
|
|
|
|
2018-12-24 19:16:42 +00:00
|
|
|
func initBar(x, y, w, h int, fp string) (*Bar, error) {
|
2017-07-11 01:17:27 +00:00
|
|
|
bar := new(Bar)
|
|
|
|
var err error
|
|
|
|
|
2017-07-13 19:22:19 +00:00
|
|
|
// Set up a connection to the X server.
|
2017-07-11 01:17:27 +00:00
|
|
|
bar.xu, err = xgbutil.NewConn()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-13 19:22:19 +00:00
|
|
|
|
2018-12-26 14:00:07 +00:00
|
|
|
// Run the main X event loop, this is used to catch events.
|
2017-07-12 16:21:51 +00:00
|
|
|
go xevent.Main(bar.xu)
|
2017-07-11 19:58:23 +00:00
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
// 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 {
|
2017-07-12 16:21:51 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-11 01:17:27 +00:00
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
// Create a window for the bar. This window listens to button press events
|
|
|
|
// in order to respond to them.
|
2017-07-11 01:17:27 +00:00
|
|
|
bar.win, err = xwindow.Generate(bar.xu)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-12-23 17:03:56 +00:00
|
|
|
bar.win.Create(bar.xu.RootWin(), x, y, w, h, xproto.CwBackPixel|xproto.
|
|
|
|
CwEventMask, 0x000000, xproto.EventMaskButtonPress)
|
2017-07-11 19:58:23 +00:00
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
// 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?
|
2017-07-11 01:17:27 +00:00
|
|
|
if err := ewmh.WmWindowTypeSet(bar.xu, bar.win.Id, []string{
|
|
|
|
"_NET_WM_WINDOW_TYPE_DOCK"}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-12 16:21:51 +00:00
|
|
|
if err := ewmh.WmStateSet(bar.xu, bar.win.Id, []string{
|
|
|
|
"_NET_WM_STATE_STICKY"}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-12-23 17:03:56 +00:00
|
|
|
if err := ewmh.WmDesktopSet(bar.xu, bar.win.Id, ^uint(0)); err != nil {
|
2017-07-12 16:21:51 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-12-23 17:03:56 +00:00
|
|
|
if err := ewmh.WmNameSet(bar.xu, bar.win.Id, "melonbar"); err != nil {
|
2017-07-12 16:21:51 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-11 19:58:23 +00:00
|
|
|
|
|
|
|
// Map window.
|
2017-07-11 01:17:27 +00:00
|
|
|
bar.win.Map()
|
2017-07-13 19:22:19 +00:00
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
// XXX: Moving the window is again a hack to keep OpenBox happy.
|
2017-07-12 16:21:51 +00:00
|
|
|
bar.win.Move(x, y)
|
2017-07-11 01:17:27 +00:00
|
|
|
|
2017-07-13 19:22:19 +00:00
|
|
|
// Create the bar image.
|
2017-07-11 01:17:27 +00:00
|
|
|
bar.img = xgraphics.New(bar.xu, image.Rect(0, 0, w, h))
|
2018-12-23 17:03:56 +00:00
|
|
|
if err := bar.img.XSurfaceSet(bar.win.Id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-11 19:58:23 +00:00
|
|
|
bar.img.XDraw()
|
2017-07-11 01:17:27 +00:00
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
bar.w = w
|
|
|
|
bar.h = h
|
|
|
|
|
2017-07-13 19:22:19 +00:00
|
|
|
// Load font.
|
2018-12-24 19:16:42 +00:00
|
|
|
fr := func(name string) ([]byte, error) {
|
|
|
|
return ioutil.ReadFile(path.Join(path.Dir(fp), name))
|
|
|
|
}
|
|
|
|
fd, err := fr(path.Base(fp))
|
2017-07-11 01:17:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-12-24 19:16:42 +00:00
|
|
|
bar.face, err = plan9font.ParseFont(fd, fr)
|
2017-07-11 01:17:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-11 15:25:19 +00:00
|
|
|
|
2017-07-11 01:17:27 +00:00
|
|
|
bar.block = new(sync.Map)
|
2017-07-13 18:51:08 +00:00
|
|
|
bar.redraw = make(chan *Block)
|
|
|
|
|
2017-07-13 21:06:10 +00:00
|
|
|
// Listen to mouse events and execute the required function.
|
2018-12-23 17:03:56 +00:00
|
|
|
xevent.ButtonPressFun(func(_ *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
|
2017-07-13 18:51:08 +00:00
|
|
|
// Determine what block the cursor is in.
|
2018-12-23 17:03:56 +00:00
|
|
|
// TODO: This feels a bit slow at the moment, can I improve it?
|
2017-07-13 18:51:08 +00:00
|
|
|
var block *Block
|
2017-07-13 21:06:10 +00:00
|
|
|
bar.block.Range(func(name, i interface{}) bool {
|
2017-07-13 18:51:08 +00:00
|
|
|
block = i.(*Block)
|
2017-07-13 21:06:10 +00:00
|
|
|
// XXX: Hack for music block.
|
|
|
|
if name == "music" {
|
2018-12-24 19:16:42 +00:00
|
|
|
tw := font.MeasureString(bar.face, block.txt).Ceil()
|
2018-12-23 17:03:56 +00:00
|
|
|
if ev.EventX > int16(block.x+(block.w-tw+(block.xoff*2))) && ev.
|
|
|
|
EventX < int16(block.x+block.w) {
|
2017-07-13 21:06:10 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
if ev.EventX > int16(block.x) && ev.EventX < int16(block.x+block.
|
|
|
|
w) {
|
2017-07-13 18:51:08 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2017-07-11 01:17:27 +00:00
|
|
|
|
2017-07-13 18:51:08 +00:00
|
|
|
// Execute the function as specified.
|
|
|
|
block.actions["button"+strconv.Itoa(int(ev.Detail))]()
|
|
|
|
}).Connect(bar.xu, bar.win.Id)
|
2017-07-11 19:58:23 +00:00
|
|
|
|
2017-07-13 18:51:08 +00:00
|
|
|
return bar, nil
|
2017-07-11 01:17:27 +00:00
|
|
|
}
|
|
|
|
|
2017-07-13 18:57:42 +00:00
|
|
|
func (bar *Bar) draw(block *Block) error {
|
2018-12-24 19:16:42 +00:00
|
|
|
d := &font.Drawer{
|
|
|
|
Dst: block.img,
|
|
|
|
Src: image.NewUniform(hexToBGRA(block.fg)),
|
|
|
|
Face: bar.face,
|
|
|
|
}
|
|
|
|
|
2018-12-23 17:03:56 +00:00
|
|
|
// Calculate the required x coordinate for the different aligments.
|
2017-07-11 01:17:27 +00:00
|
|
|
var x int
|
2018-12-24 19:16:42 +00:00
|
|
|
tw := d.MeasureString(block.txt).Ceil()
|
2017-07-11 01:17:27 +00:00
|
|
|
switch block.align {
|
|
|
|
case 'l':
|
2017-07-13 21:06:10 +00:00
|
|
|
x = block.x
|
2017-07-11 01:17:27 +00:00
|
|
|
case 'c':
|
2017-07-13 21:06:10 +00:00
|
|
|
x = block.x + ((block.w / 2) - (tw / 2))
|
2017-07-11 01:17:27 +00:00
|
|
|
case 'r':
|
2017-07-13 21:06:10 +00:00
|
|
|
x = (block.x + block.w) - tw
|
|
|
|
case 'a':
|
|
|
|
x = (bar.w / 2) - (tw / 2)
|
2017-07-12 16:21:51 +00:00
|
|
|
default:
|
2018-12-23 17:03:56 +00:00
|
|
|
return fmt.Errorf("draw %#U: Not a valid aligment rune", block.align)
|
2017-07-11 01:17:27 +00:00
|
|
|
}
|
2017-07-13 21:06:10 +00:00
|
|
|
x += block.xoff
|
2017-07-11 01:17:27 +00:00
|
|
|
|
2017-07-13 21:06:10 +00:00
|
|
|
// Color the background.
|
2017-07-12 16:21:51 +00:00
|
|
|
block.img.For(func(cx, cy int) xgraphics.BGRA {
|
2017-07-13 21:06:10 +00:00
|
|
|
// XXX: Hack for music block.
|
2017-07-13 18:51:08 +00:00
|
|
|
if block.w == 660 {
|
2017-07-12 16:21:51 +00:00
|
|
|
if cx < x+block.xoff {
|
|
|
|
return hexToBGRA("#445967")
|
|
|
|
}
|
2017-07-13 18:51:08 +00:00
|
|
|
return hexToBGRA(block.bg)
|
2017-07-12 16:21:51 +00:00
|
|
|
}
|
|
|
|
|
2017-07-13 18:51:08 +00:00
|
|
|
return hexToBGRA(block.bg)
|
2017-07-12 16:21:51 +00:00
|
|
|
})
|
|
|
|
|
2017-07-11 01:17:27 +00:00
|
|
|
// Draw the text.
|
2018-12-24 19:16:42 +00:00
|
|
|
d.Dot = fixed.P(x, bar.face.Metrics().Ascent.Ceil()+9)
|
|
|
|
d.DrawString(block.txt)
|
2017-07-11 01:17:27 +00:00
|
|
|
|
2018-12-24 19:16:42 +00:00
|
|
|
// Redraw the bar.
|
2017-07-11 01:17:27 +00:00
|
|
|
block.img.XDraw()
|
|
|
|
bar.img.XPaint(bar.win.Id)
|
2018-12-23 17:03:56 +00:00
|
|
|
|
2017-07-13 18:51:08 +00:00
|
|
|
return nil
|
2017-07-11 01:17:27 +00:00
|
|
|
}
|