Add mousebindings

This commit is contained in:
Camille Scholtz 2017-07-13 20:51:08 +02:00
parent 2bfbc2bc4a
commit 62e0f7f5f9
5 changed files with 271 additions and 175 deletions

@ -36,12 +36,20 @@ explanation of each parameter from left to right:
* The foreground color of the block in hexadecimal. (`string`) * The foreground color of the block in hexadecimal. (`string`)
* The background 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 Everything that should not be ran in a loop should of course be
specified before the `for` loop. For example setting up a connection 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 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 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 ## TODO
one being the block map key, and the second one the new string the
new foreground color of the block, in hexadecimal. * Create some kind of easy to use init function for blocks (instead of
* Update the background color of the block using the `if !init` stuff I use at the moment).
`bar.updateBlockBg()`. This function takes the same parameters as * Add popups.
`bar.updateBlockFg()`. * Drop support for `ttf` fonts and use `pcf` fonts instead if
* Update the text of the block using `bar.updateBlockTxt()`. Again, possible.
this function takes two parameters, the second one being the new * or maybe some kind of different format altogether that's easily
string the block should display. hackable, such as suckless farbfeld?
## AUTHORS ## AUTHORS

134
bar.go

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"image" "image"
"os" "os"
"strconv"
"sync" "sync"
"time"
"github.com/BurntSushi/freetype-go/freetype/truetype" "github.com/BurntSushi/freetype-go/freetype/truetype"
"github.com/BurntSushi/xgb/xproto" "github.com/BurntSushi/xgb/xproto"
@ -37,31 +37,9 @@ type Bar struct {
// A map with information about the block, see `Block`. // A map with information about the block, see `Block`.
block *sync.Map block *sync.Map
// The channel where the block name should be written to once the // The channel where the block name should be send to to once it's
// block is ready to be redrawn. // ready to be redrawn.
redraw chan string redraw chan *Block
}
// 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
} }
func initBar(x, y, w, h int, font string, fontSize float64) (*Bar, 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 return nil, err
} }
bar.win.Create(bar.xu.RootWin(), x, y, w, h, xproto.CwBackPixel| 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 // TODO: `WmStateSet` and `WmDesktopSet` are basically here to
// keep OpenBox happy, can I somehow remove them and just use // 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 { "_NET_WM_STATE_STICKY"}); err != nil {
return nil, err 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 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 return nil, err
} }
// TODO: Moving the window is again a hack to keep OpenBox happy.
// Map window. // Map window.
bar.win.Map() bar.win.Map()
bar.win.Move(x, y) 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.h = h
bar.block = new(sync.Map) 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 return bar, nil
} }
func (bar *Bar) initBlock(name, txt string, width int, align rune, func (bar *Bar) paint(block *Block) error {
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)
// Calculate the required x coordinate for the different // Calculate the required x coordinate for the different
// aligments. // aligments.
tw, _ := xgraphics.Extents(bar.font, bar.fontSize, block.txt) tw, _ := xgraphics.Extents(bar.font, bar.fontSize, block.txt)
@ -217,28 +161,26 @@ func (bar *Bar) draw(name string) error {
// Color the backround. // Color the backround.
block.img.For(func(cx, cy int) xgraphics.BGRA { block.img.For(func(cx, cy int) xgraphics.BGRA {
// TODO: Should I handle this in `initBlock()`?
// Hack for music block background. // Hack for music block background.
if name == "music" { if block.w == 660 {
if cx < x+block.xoff { if cx < x+block.xoff {
return hexToBGRA("#445967") return hexToBGRA("#445967")
} }
return block.bg return hexToBGRA(block.bg)
} }
return block.bg return hexToBGRA(block.bg)
}) })
// TODO: Center vertically automatically. // TODO: Center vertically automatically.
// Draw the text. // Draw the text.
if _, _, err := block.img.Text(x, 6, block.fg, bar.fontSize, if _, _, err := block.img.Text(x, 6, hexToBGRA(block.fg),
bar.font, block.txt); err != nil { bar.fontSize, bar.font, block.txt); err != nil {
return err return err
} }
block.img.XDraw() block.img.XDraw()
bar.img.XPaint(bar.win.Id)
return nil return nil
} }
func (bar *Bar) paint() {
bar.img.XPaint(bar.win.Id)
}

63
block.go Normal file

@ -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
}

210
blocks.go

@ -12,13 +12,13 @@ import (
"github.com/BurntSushi/xgbutil/icccm" "github.com/BurntSushi/xgbutil/icccm"
"github.com/BurntSushi/xgbutil/xevent" "github.com/BurntSushi/xgbutil/xevent"
"github.com/BurntSushi/xgbutil/xprop" "github.com/BurntSushi/xgbutil/xprop"
owm "github.com/briandowns/openweathermap"
"github.com/fhs/gompd/mpd" "github.com/fhs/gompd/mpd"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
) )
func (bar *Bar) clockFun() { func (bar *Bar) clockFun() {
bar.initBlock("clock", "?", 800, 'c', 300, "#445967", "#CCCCCC") block := bar.initBlock("clock", "?", 800, 'c', 300, "#445967",
"#CCCCCC")
init := true init := true
for { for {
@ -27,21 +27,57 @@ func (bar *Bar) clockFun() {
} }
init = false 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( block.txt = txt
"Monday, January 2th 03:04 PM")) bar.redraw <- block
} }
} }
func (bar *Bar) musicFun() error { 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") watcher, err := mpd.NewWatcher("tcp", ":6600", "", "player")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
var conn *mpd.Client var conn *mpd.Client
init := true init := true
for { for {
@ -51,8 +87,8 @@ func (bar *Bar) musicFun() error {
} }
init = false init = false
// TODO: Is it maybe possible to not create a new conn each // TODO: Is it maybe possible to not create a new connection
// loop? // each loop?
conn, err = mpd.Dial("tcp", ":6600") conn, err = mpd.Dial("tcp", ":6600")
if err != nil { if err != nil {
log.Print(err) log.Print(err)
@ -65,23 +101,29 @@ func (bar *Bar) musicFun() error {
continue continue
} }
s, err := conn.Status() status, err := conn.Status()
if err != nil { if err != nil {
log.Print(err) log.Print(err)
continue continue
} }
var state string var state string
if s["state"] == "pause" { if status["state"] == "pause" {
state = "[paused] " state = "[paused] "
} }
bar.updateBlockTxt("music", state+cur["Artist"]+" - "+ txt := state + cur["Artist"] + " - " + cur["Title"]
cur["Title"]) if block.txt == txt {
continue
}
block.txt = txt
bar.redraw <- block
} }
} }
func (bar *Bar) todoFun() { 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() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
@ -94,7 +136,6 @@ func (bar *Bar) todoFun() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
init := true init := true
for { for {
if !init { if !init {
@ -116,18 +157,25 @@ func (bar *Bar) todoFun() {
continue 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() { 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") w, err := owm.NewCurrent("C", "en")
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
init := true init := true
for { for {
if !init { if !init {
@ -140,40 +188,41 @@ func (bar *Bar) weatherFun() {
continue continue
} }
/* var state uint
var state uint switch w.Weather[0].Icon[0:2] {
switch w.Weather[0].Icon[0:2] { case "01":
case "01": state = 0
state = 0 case "02":
case "02": state = 1
state = 1 case "03":
case "03": state = 2
state = 2 case "04":
case "04": state = 3
state = 3 case "09":
case "09": state = 4
state = 4 case "10":
case "10": state = 5
state = 5 case "11":
case "11": state = 6
state = 6 case "13":
case "13": state = 7
state = 7 case "50":
case "50": state = 8
state = 8 }
}
*/
bar.updateBlockTxt("weather", strconv.FormatFloat(w.Main.Temp, block.txt = strconv.FormatFloat(w.Main.Temp, 'f', 0, 64) +
'f', 0, 64)+" °C") " °C"
bar.redraw <- block
} }
} }
*/
func (bar *Bar) windowFun() { 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? // 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) { ev xevent.PropertyNotifyEvent) {
atom, err := xprop.Atm(bar.xu, "_NET_ACTIVE_WINDOW") atom, err := xprop.Atm(bar.xu, "_NET_ACTIVE_WINDOW")
if ev.Atom != atom { if ev.Atom != atom {
@ -189,25 +238,51 @@ func (bar *Bar) windowFun() {
log.Print(err) log.Print(err)
return return
} }
win, err := ewmh.WmNameGet(bar.xu, id)
if err != nil || len(win) == 0 { txt, err := ewmh.WmNameGet(bar.xu, id)
win, err = icccm.WmNameGet(bar.xu, id) if err != nil || len(txt) == 0 {
if err != nil || len(win) == 0 { txt, err = icccm.WmNameGet(bar.xu, id)
win = "?" 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()) }).Connect(bar.xu, bar.xu.RootWin())
} }
func (bar *Bar) workspaceFun() { func (bar *Bar) workspaceFun() {
bar.initBlock("www", "www", 74, 'c', 0, "#5394C9", "#FFFFFF") blockwww := bar.initBlock("www", "www", 74, 'c', 0, "#5394C9",
bar.initBlock("irc", "irc", 67, 'c', 0, "#5394C9", "#FFFFFF") "#FFFFFF")
bar.initBlock("src", "src", 70, '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? // 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) { ev xevent.PropertyNotifyEvent) {
atom, err := xprop.Atm(bar.xu, "_NET_CURRENT_DESKTOP") atom, err := xprop.Atm(bar.xu, "_NET_CURRENT_DESKTOP")
if ev.Atom != atom { if ev.Atom != atom {
@ -223,20 +298,27 @@ func (bar *Bar) workspaceFun() {
log.Print(err) log.Print(err)
return return
} }
if owsp == wsp {
return
}
owsp = wsp
switch wsp { switch wsp {
case 0: case 0:
bar.updateBlockBg("www", "#72A7D3") blockwww.bg = "#72A7D3"
bar.updateBlockBg("irc", "#5394C9") blockirc.bg = "#5394C9"
bar.updateBlockBg("src", "#5394C9") blocksrc.bg = "#5394C9"
case 1: case 1:
bar.updateBlockBg("www", "#5394C9") blockwww.bg = "#5394C9"
bar.updateBlockBg("irc", "#72A7D3") blockirc.bg = "#72A7D3"
bar.updateBlockBg("src", "#5394C9") blocksrc.bg = "#5394C9"
case 2: case 2:
bar.updateBlockBg("www", "#5394C9") blockwww.bg = "#5394C9"
bar.updateBlockBg("irc", "#5394C9") blockirc.bg = "#5394C9"
bar.updateBlockBg("src", "#72A7D3") blocksrc.bg = "#72A7D3"
} }
bar.redraw <- blockwww
bar.redraw <- blockirc
bar.redraw <- blocksrc
}).Connect(bar.xu, bar.xu.RootWin()) }).Connect(bar.xu, bar.xu.RootWin())
} }

@ -18,7 +18,7 @@ func main() {
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
go bar.workspaceFun() go bar.workspaceFun()
time.Sleep(time.Millisecond * 3) time.Sleep(time.Millisecond)
go bar.clockFun() go bar.clockFun()
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
@ -30,9 +30,8 @@ func main() {
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
for { for {
if err := bar.draw(<-bar.redraw); err != nil { if err := bar.paint(<-bar.redraw); err != nil {
log.Fatal(err) log.Fatal(err)
} }
bar.paint()
} }
} }