From 62e0f7f5f94801daa441db332659f9840bb2b7b0 Mon Sep 17 00:00:00 2001 From: Camille Scholtz Date: Thu, 13 Jul 2017 20:51:08 +0200 Subject: [PATCH] Add mousebindings --- README.md | 34 +++++---- bar.go | 134 ++++++++++------------------------ block.go | 63 ++++++++++++++++ blocks.go | 210 +++++++++++++++++++++++++++++++++++++----------------- main.go | 5 +- 5 files changed, 271 insertions(+), 175 deletions(-) create mode 100644 block.go diff --git a/README.md b/README.md index fb72afc..fbea680 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,20 @@ explanation of each parameter from left to right: * The foreground color of the block in hexadecimal. (`string`) * The background color of the block in hexadecimal. (`string`) +You can also additionally specify mousebindings using: + +```go +block.actions["buttonN"] = func() { + // Do stuff. +} +``` + --- Everything that should not be ran in a loop should of course be specified before the `for` loop. For example setting up a connection -to mpd. +to `mpd`. If you want something to only be done *after* the very first loop - an example of this would be not waiting for a workspace chance event, but @@ -64,18 +72,20 @@ the user changes his workspace for the first time. --- -When you've gathered all needed information you can: +When you've gathered all needed information you can update the block +values using for example `block.bg = value` and running +`bar.redraw <- block`. -* Update the foreground color of the block using - `bar.updateBlockFg()`. This function takes two parameters, the first - one being the block map key, and the second one the new string the - new foreground color of the block, in hexadecimal. -* Update the background color of the block using - `bar.updateBlockBg()`. This function takes the same parameters as - `bar.updateBlockFg()`. -* Update the text of the block using `bar.updateBlockTxt()`. Again, - this function takes two parameters, the second one being the new - string the block should display. + +## TODO + +* Create some kind of easy to use init function for blocks (instead of + the `if !init` stuff I use at the moment). +* Add popups. +* Drop support for `ttf` fonts and use `pcf` fonts instead if + possible. +* or maybe some kind of different format altogether that's easily + hackable, such as suckless farbfeld? ## AUTHORS diff --git a/bar.go b/bar.go index 33fa229..eff9ca6 100644 --- a/bar.go +++ b/bar.go @@ -4,8 +4,8 @@ import ( "fmt" "image" "os" + "strconv" "sync" - "time" "github.com/BurntSushi/freetype-go/freetype/truetype" "github.com/BurntSushi/xgb/xproto" @@ -37,31 +37,9 @@ type Bar struct { // A map with information about the block, see `Block`. block *sync.Map - // The channel where the block name should be written to once the - // block is ready to be redrawn. - redraw chan string -} - -// Block is a struct with information about a block. -type Block struct { - // The text the block should display. - txt string - - // The x coordinate and width of the bar. - x, w int - - /// The aligment of the text, this can be `'l'` for left aligment, - // `'c'` for center aligment and `'r'` for right aligment. - align rune - - // Additional x offset to further tweak the location of the text. - xoff int - - // The foreground and background colors. - bg, fg xgraphics.BGRA - - // The sub-image that represents the block. - img *xgraphics.Image + // The channel where the block name should be send to to once it's + // ready to be redrawn. + redraw chan *Block } func initBar(x, y, w, h int, font string, fontSize float64) (*Bar, @@ -90,7 +68,7 @@ func initBar(x, y, w, h int, font string, fontSize float64) (*Bar, return nil, err } bar.win.Create(bar.xu.RootWin(), x, y, w, h, xproto.CwBackPixel| - xproto.CwEventMask, 0x000000, xproto.EventMaskPropertyChange) + xproto.CwEventMask, 0x000000, xproto.EventMaskButtonPress) // TODO: `WmStateSet` and `WmDesktopSet` are basically here to // keep OpenBox happy, can I somehow remove them and just use @@ -104,13 +82,16 @@ func initBar(x, y, w, h int, font string, fontSize float64) (*Bar, "_NET_WM_STATE_STICKY"}); err != nil { return nil, err } - if err := ewmh.WmDesktopSet(bar.xu, bar.win.Id, ^uint(0)); err != nil { + 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 { + if err := ewmh.WmNameSet(bar.xu, bar.win.Id, "melonbar"); err != + nil { return nil, err } + // TODO: Moving the window is again a hack to keep OpenBox happy. // Map window. bar.win.Map() bar.win.Move(x, y) @@ -137,68 +118,31 @@ func initBar(x, y, w, h int, font string, fontSize float64) (*Bar, bar.h = h bar.block = new(sync.Map) - bar.redraw = make(chan string) + bar.redraw = make(chan *Block) + + // Listen to mouse events and execute requires function. + xevent.ButtonPressFun(func(_ *xgbutil.XUtil, + ev xevent.ButtonPressEvent) { + // Determine what block the cursor is in. + var block *Block + bar.block.Range(func(val, i interface{}) bool { + block = i.(*Block) + fmt.Println(val) + 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) initBlock(name, txt string, width int, align rune, - xoff int, bg, fg string) { - bar.block.Store(name, &Block{txt, bar.xsum, width, align, xoff, - hexToBGRA(bg), hexToBGRA(fg), bar.img.SubImage(image.Rect( - bar.xsum, 0, bar.xsum+width, bar.h)).(*xgraphics.Image)}) - bar.xsum += width -} - -func (bar *Bar) updateBlockBg(name, bg string) { - i, _ := bar.block.Load(name) - block := i.(*Block) - - nbg := hexToBGRA(bg) - if block.bg == nbg { - return - } - - block.bg = nbg - bar.redraw <- name - return -} - -func (bar *Bar) updateBlockFg(name, fg string) { - i, _ := bar.block.Load(name) - block := i.(*Block) - - nfg := hexToBGRA(fg) - if block.fg == nfg { - return - } - - block.fg = nfg - bar.redraw <- name - return -} - -func (bar *Bar) updateBlockTxt(name, txt string) { - i, _ := bar.block.Load(name) - block := i.(*Block) - - if block.txt == txt { - return - } - - block.txt = txt - bar.redraw <- name - return -} - -func (bar *Bar) draw(name string) error { - // Needed to prevent an `interface conversion: interface {} is - // nil, not *main.Block` panic for some reason... - time.Sleep(time.Nanosecond) - - i, _ := bar.block.Load(name) - block := i.(*Block) - +func (bar *Bar) paint(block *Block) error { // Calculate the required x coordinate for the different // aligments. tw, _ := xgraphics.Extents(bar.font, bar.fontSize, block.txt) @@ -217,28 +161,26 @@ func (bar *Bar) draw(name string) error { // Color the backround. block.img.For(func(cx, cy int) xgraphics.BGRA { + // TODO: Should I handle this in `initBlock()`? // Hack for music block background. - if name == "music" { + if block.w == 660 { if cx < x+block.xoff { return hexToBGRA("#445967") } - return block.bg + return hexToBGRA(block.bg) } - return block.bg + return hexToBGRA(block.bg) }) // TODO: Center vertically automatically. // Draw the text. - if _, _, err := block.img.Text(x, 6, block.fg, bar.fontSize, - bar.font, block.txt); err != nil { + if _, _, err := block.img.Text(x, 6, hexToBGRA(block.fg), + bar.fontSize, bar.font, block.txt); err != nil { return err } block.img.XDraw() + bar.img.XPaint(bar.win.Id) return nil } - -func (bar *Bar) paint() { - bar.img.XPaint(bar.win.Id) -} diff --git a/block.go b/block.go new file mode 100644 index 0000000..b70972c --- /dev/null +++ b/block.go @@ -0,0 +1,63 @@ +package main + +import ( + "image" + + "github.com/BurntSushi/xgbutil/xgraphics" +) + +// Block is a struct with information about a block. +type Block struct { + // The text the block should display. + txt string + + // The x coordinate and width of the bar. + x, w int + + /// The aligment of the text, this can be `l` for left aligment, + // `c` for center aligment (the default) and `r` for right + // aligment. + align rune + + // Additional x offset to further tweak the location of the text. + xoff int + + // The foreground and background colors. + bg, fg string + + // Commands to execute on button events. + actions map[string]func() + + // The sub-image that represents the block. + img *xgraphics.Image +} + +func (bar *Bar) initBlock(name, txt string, w int, align rune, + xoff int, bg, fg string) *Block { + block := new(Block) + + block.txt = txt + block.x = bar.xsum + block.w = w + block.align = align + block.xoff = xoff + block.bg = bg + block.fg = fg + block.actions = map[string]func(){ + "button1": func() {}, + "button2": func() {}, + "button3": func() {}, + "button4": func() {}, + "button5": func() {}, + } + block.img = bar.img.SubImage(image.Rect(bar.xsum, 0, bar.xsum+w, + bar.h)).(*xgraphics.Image) + + // Add the width of this block to the xsum. + bar.xsum += w + + // Store the block in map. + bar.block.Store(name, block) + + return block +} diff --git a/blocks.go b/blocks.go index 30489ae..10ac305 100644 --- a/blocks.go +++ b/blocks.go @@ -12,13 +12,13 @@ import ( "github.com/BurntSushi/xgbutil/icccm" "github.com/BurntSushi/xgbutil/xevent" "github.com/BurntSushi/xgbutil/xprop" - owm "github.com/briandowns/openweathermap" "github.com/fhs/gompd/mpd" "github.com/fsnotify/fsnotify" ) func (bar *Bar) clockFun() { - bar.initBlock("clock", "?", 800, 'c', 300, "#445967", "#CCCCCC") + block := bar.initBlock("clock", "?", 800, 'c', 300, "#445967", + "#CCCCCC") init := true for { @@ -27,21 +27,57 @@ func (bar *Bar) clockFun() { } init = false - t := time.Now() + txt := time.Now().Format("Monday, January 2th 03:04 PM") + if block.txt == txt { + continue + } - bar.updateBlockTxt("clock", t.Format( - "Monday, January 2th 03:04 PM")) + block.txt = txt + bar.redraw <- block } } func (bar *Bar) musicFun() error { - bar.initBlock("music", "?", 660, 'r', -10, "#3C4F5B", "#CCCCCC") + block := bar.initBlock("music", "?", 660, 'r', -10, "#3C4F5B", + "#CCCCCC") + + block.actions["button3"] = func() { + conn, err := mpd.Dial("tcp", ":6600") + if err != nil { + log.Print(err) + } + + status, err := conn.Status() + if err != nil { + log.Print(err) + } + if status["state"] == "pause" { + conn.Pause(false) + } else { + conn.Pause(true) + } + } + block.actions["button4"] = func() { + conn, err := mpd.Dial("tcp", ":6600") + if err != nil { + log.Print(err) + } + + conn.Previous() + } + block.actions["button5"] = func() { + conn, err := mpd.Dial("tcp", ":6600") + if err != nil { + log.Print(err) + } + + conn.Next() + } watcher, err := mpd.NewWatcher("tcp", ":6600", "", "player") if err != nil { log.Fatal(err) } - var conn *mpd.Client init := true for { @@ -51,8 +87,8 @@ func (bar *Bar) musicFun() error { } init = false - // TODO: Is it maybe possible to not create a new conn each - // loop? + // TODO: Is it maybe possible to not create a new connection + // each loop? conn, err = mpd.Dial("tcp", ":6600") if err != nil { log.Print(err) @@ -65,23 +101,29 @@ func (bar *Bar) musicFun() error { continue } - s, err := conn.Status() + status, err := conn.Status() if err != nil { log.Print(err) continue } var state string - if s["state"] == "pause" { + if status["state"] == "pause" { state = "[paused] " } - bar.updateBlockTxt("music", state+cur["Artist"]+" - "+ - cur["Title"]) + txt := state + cur["Artist"] + " - " + cur["Title"] + if block.txt == txt { + continue + } + + block.txt = txt + bar.redraw <- block } } func (bar *Bar) todoFun() { - bar.initBlock("todo", "?", 29, 'c', 0, "#5394C9", "#FFFFFF") + block := bar.initBlock("todo", "?", 29, 'c', 0, "#5394C9", + "#FFFFFF") watcher, err := fsnotify.NewWatcher() if err != nil { @@ -94,7 +136,6 @@ func (bar *Bar) todoFun() { if err != nil { log.Fatal(err) } - init := true for { if !init { @@ -116,18 +157,25 @@ func (bar *Bar) todoFun() { continue } - bar.updateBlockTxt("todo", strconv.Itoa(c)) + txt := strconv.Itoa(c) + if block.txt == txt { + continue + } + + block.txt = txt + bar.redraw <- block } } +/* func (bar *Bar) weatherFun() { - bar.initBlock("weather", "?", 29, 'r', 0, "#5394C9", "#FFFFFF") + block := bar.initBlock("weather", "?", 29, 'r', 0, "#5394C9", + "#FFFFFF") w, err := owm.NewCurrent("C", "en") if err != nil { log.Fatalln(err) } - init := true for { if !init { @@ -140,40 +188,41 @@ func (bar *Bar) weatherFun() { continue } - /* - var state uint - switch w.Weather[0].Icon[0:2] { - case "01": - state = 0 - case "02": - state = 1 - case "03": - state = 2 - case "04": - state = 3 - case "09": - state = 4 - case "10": - state = 5 - case "11": - state = 6 - case "13": - state = 7 - case "50": - state = 8 - } - */ + var state uint + switch w.Weather[0].Icon[0:2] { + case "01": + state = 0 + case "02": + state = 1 + case "03": + state = 2 + case "04": + state = 3 + case "09": + state = 4 + case "10": + state = 5 + case "11": + state = 6 + case "13": + state = 7 + case "50": + state = 8 + } - bar.updateBlockTxt("weather", strconv.FormatFloat(w.Main.Temp, - 'f', 0, 64)+" °C") + block.txt = strconv.FormatFloat(w.Main.Temp, 'f', 0, 64) + + " °C" + bar.redraw <- block } } +*/ func (bar *Bar) windowFun() { - bar.initBlock("window", "?", 220, 'c', 0, "#37BF8D", "#FFFFFF") + block := bar.initBlock("window", "?", 220, 'c', 0, "#37BF8D", + "#FFFFFF") // TODO: I'm not sure how I can use init here? - xevent.PropertyNotifyFun(func(xu *xgbutil.XUtil, + xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent.PropertyNotifyEvent) { atom, err := xprop.Atm(bar.xu, "_NET_ACTIVE_WINDOW") if ev.Atom != atom { @@ -189,25 +238,51 @@ func (bar *Bar) windowFun() { log.Print(err) return } - win, err := ewmh.WmNameGet(bar.xu, id) - if err != nil || len(win) == 0 { - win, err = icccm.WmNameGet(bar.xu, id) - if err != nil || len(win) == 0 { - win = "?" + + txt, err := ewmh.WmNameGet(bar.xu, id) + if err != nil || len(txt) == 0 { + txt, err = icccm.WmNameGet(bar.xu, id) + if err != nil || len(txt) == 0 { + txt = "?" } } + if block.txt == txt { + return + } - bar.updateBlockTxt("window", win) + block.txt = txt + bar.redraw <- block }).Connect(bar.xu, bar.xu.RootWin()) } func (bar *Bar) workspaceFun() { - bar.initBlock("www", "www", 74, 'c', 0, "#5394C9", "#FFFFFF") - bar.initBlock("irc", "irc", 67, 'c', 0, "#5394C9", "#FFFFFF") - bar.initBlock("src", "src", 70, 'c', 0, "#5394C9", "#FFFFFF") + blockwww := bar.initBlock("www", "www", 74, 'c', 0, "#5394C9", + "#FFFFFF") + blockwww.actions["button1"] = func() { + if err := ewmh.CurrentDesktopReq(bar.xu, 1); err != nil { + log.Println(err) + } + } + + blockirc := bar.initBlock("irc", "irc", 67, 'c', 0, "#5394C9", + "#FFFFFF") + blockirc.actions["button1"] = func() { + if err := ewmh.CurrentDesktopReq(bar.xu, 1); err != nil { + log.Println(err) + } + } + + blocksrc := bar.initBlock("src", "src", 70, 'c', 0, "#5394C9", + "#FFFFFF") + blocksrc.actions["button1"] = func() { + if err := ewmh.CurrentDesktopReq(bar.xu, 1); err != nil { + log.Println(err) + } + } // TODO: I'm not sure how I can use init here? - xevent.PropertyNotifyFun(func(xu *xgbutil.XUtil, + var owsp uint + xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent.PropertyNotifyEvent) { atom, err := xprop.Atm(bar.xu, "_NET_CURRENT_DESKTOP") if ev.Atom != atom { @@ -223,20 +298,27 @@ func (bar *Bar) workspaceFun() { log.Print(err) return } + if owsp == wsp { + return + } + owsp = wsp switch wsp { case 0: - bar.updateBlockBg("www", "#72A7D3") - bar.updateBlockBg("irc", "#5394C9") - bar.updateBlockBg("src", "#5394C9") + blockwww.bg = "#72A7D3" + blockirc.bg = "#5394C9" + blocksrc.bg = "#5394C9" case 1: - bar.updateBlockBg("www", "#5394C9") - bar.updateBlockBg("irc", "#72A7D3") - bar.updateBlockBg("src", "#5394C9") + blockwww.bg = "#5394C9" + blockirc.bg = "#72A7D3" + blocksrc.bg = "#5394C9" case 2: - bar.updateBlockBg("www", "#5394C9") - bar.updateBlockBg("irc", "#5394C9") - bar.updateBlockBg("src", "#72A7D3") + blockwww.bg = "#5394C9" + blockirc.bg = "#5394C9" + blocksrc.bg = "#72A7D3" } + bar.redraw <- blockwww + bar.redraw <- blockirc + bar.redraw <- blocksrc }).Connect(bar.xu, bar.xu.RootWin()) } diff --git a/main.go b/main.go index 7cc85c9..06e35d7 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,7 @@ func main() { time.Sleep(time.Millisecond) go bar.workspaceFun() - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond) go bar.clockFun() time.Sleep(time.Millisecond) @@ -30,9 +30,8 @@ func main() { time.Sleep(time.Millisecond) for { - if err := bar.draw(<-bar.redraw); err != nil { + if err := bar.paint(<-bar.redraw); err != nil { log.Fatal(err) } - bar.paint() } }