Did I really write this 2 years ago??? Anyways, some small updates

This commit is contained in:
Camille Scholtz 2018-12-23 18:03:56 +01:00
parent a9daf23fbe
commit 67c68f8a98
7 changed files with 263 additions and 117 deletions

@ -55,8 +55,8 @@ 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 change event, but
immediately checking the current workspace. - use: immediately checking the current workspace - use:
```go ```go
init := true init := true
@ -75,8 +75,8 @@ the user changes his workspace for the first time.
--- ---
When you've gathered all needed information you can update the block When you've gathered all information you can update the block values
values using for example `block.bg = value` and running using for example `block.bg = value` and running
`bar.redraw <- block`. `bar.redraw <- block`.

100
bar.go

@ -23,27 +23,26 @@ type Bar struct {
win *xwindow.Window win *xwindow.Window
img *xgraphics.Image img *xgraphics.Image
// The font and fontsize that should be used.
font *truetype.Font
fsize float64
// The width and height of the bar. // The width and height of the bar.
w, h int w, h int
// This is a sum of all of the block widths, used to draw a block // This is a sum of all of the block widths, used to draw a block to the
// to the right of the last block. // right of the last block.
xsum int xsum int
// The font that should be used.
font *truetype.Font
size float64
// A map with information about the block, see the `Block` type. // A map with information about the block, see the `Block` type.
block *sync.Map block *sync.Map
// A channel where the block should be send to to once its ready // A channel where the block should be send to to once its ready to be
// to be redrawn. // redrawn.
redraw chan *Block redraw chan *Block
} }
func initBar(x, y, w, h int, font string, fsize float64) (*Bar, func initBar(x, y, w, h int, font string, size float64) (*Bar, error) {
error) {
bar := new(Bar) bar := new(Bar)
var err error var err error
@ -56,27 +55,26 @@ func initBar(x, y, w, h int, font string, fsize float64) (*Bar,
// Run the main X event loop, this is uses to catch events. // Run the main X event loop, this is uses to catch events.
go xevent.Main(bar.xu) go xevent.Main(bar.xu)
// Listen to the root window for property change events, used to // Listen to the root window for property change events, used to check if
// check if the user changed the focused window or active // the user changed the focused window or active workspace for example.
// workspace for example. if err := xwindow.New(bar.xu, bar.xu.RootWin()).Listen(xproto.
if err := xwindow.New(bar.xu, bar.xu.RootWin()).Listen( EventMaskPropertyChange); err != nil {
xproto.EventMaskPropertyChange); err != nil {
return nil, err return nil, err
} }
// Create a window for the bar. This window listens to button // Create a window for the bar. This window listens to button press events
// press events in order to respond to them. // in order to respond to them.
bar.win, err = xwindow.Generate(bar.xu) bar.win, err = xwindow.Generate(bar.xu)
if err != nil { if err != nil {
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.
xproto.CwEventMask, 0x000000, xproto.EventMaskButtonPress) CwEventMask, 0x000000, xproto.EventMaskButtonPress)
// EWMH stuff to make the window behave like an actuale bar. // EWMH stuff to make the window behave like an actual bar.
// TODO: `WmStateSet` and `WmDesktopSet` are basically here to // XXX: `WmStateSet` and `WmDesktopSet` are basically here to keep OpenBox
// keep OpenBox happy, can I somehow remove them and just use // happy, can I somehow remove them and just use `_NET_WM_WINDOW_TYPE_DOCK`
// `_NET_WM_WINDOW_TYPE_DOCK` like I can with WindowChef? // like I can with WindowChef?
if err := ewmh.WmWindowTypeSet(bar.xu, bar.win.Id, []string{ if err := ewmh.WmWindowTypeSet(bar.xu, bar.win.Id, []string{
"_NET_WM_WINDOW_TYPE_DOCK"}); err != nil { "_NET_WM_WINDOW_TYPE_DOCK"}); err != nil {
return nil, err return nil, err
@ -85,29 +83,34 @@ func initBar(x, y, w, h int, font string, fsize 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 != if err := ewmh.WmDesktopSet(bar.xu, bar.win.Id, ^uint(0)); err != nil {
nil {
return nil, err return nil, err
} }
if err := ewmh.WmNameSet(bar.xu, bar.win.Id, "melonbar"); err != if err := ewmh.WmNameSet(bar.xu, bar.win.Id, "melonbar"); err != nil {
nil {
return nil, err return nil, err
} }
// Map window. // Map window.
bar.win.Map() bar.win.Map()
// TODO: Moving the window is again a hack to keep OpenBox happy. // XXX: Moving the window is again a hack to keep OpenBox happy.
bar.win.Move(x, y) bar.win.Move(x, y)
// Create the bar image. // Create the bar image.
bar.img = xgraphics.New(bar.xu, image.Rect(0, 0, w, h)) bar.img = xgraphics.New(bar.xu, image.Rect(0, 0, w, h))
bar.img.XSurfaceSet(bar.win.Id) if err := bar.img.XSurfaceSet(bar.win.Id); err != nil {
return nil, err
}
bar.img.XDraw() bar.img.XDraw()
bar.w = w
bar.h = h
// Load font. // Load font.
// TODO: I don't *really* want to use `ttf` fonts but there // TODO: I don't *really* want to use `ttf` fonts but there doesn't seem to
// doesn't seem to be any `pcf` Go library at the moment. // 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) f, err := os.Open(font)
if err != nil { if err != nil {
return nil, err return nil, err
@ -116,36 +119,30 @@ func initBar(x, y, w, h int, font string, fsize float64) (*Bar,
if err != nil { if err != nil {
return nil, err return nil, err
} }
bar.fsize = fsize bar.size = size
bar.w = w
bar.h = h
bar.block = new(sync.Map) bar.block = new(sync.Map)
bar.redraw = make(chan *Block) bar.redraw = make(chan *Block)
// Listen to mouse events and execute the required function. // Listen to mouse events and execute the required function.
xevent.ButtonPressFun(func(_ *xgbutil.XUtil, xevent.ButtonPressFun(func(_ *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
ev xevent.ButtonPressEvent) {
// Determine what block the cursor is in. // Determine what block the cursor is in.
// TODO: This feels a bit slow at the moment, can I improve // TODO: This feels a bit slow at the moment, can I improve it?
// it?
var block *Block var block *Block
bar.block.Range(func(name, i interface{}) bool { bar.block.Range(func(name, i interface{}) bool {
block = i.(*Block) block = i.(*Block)
// XXX: Hack for music block. // XXX: Hack for music block.
if name == "music" { if name == "music" {
tw, _ := xgraphics.Extents(bar.font, bar.fsize, tw, _ := xgraphics.Extents(bar.font, bar.size, block.txt)
block.txt) if ev.EventX > int16(block.x+(block.w-tw+(block.xoff*2))) && ev.
if ev.EventX > int16(block.x+(block.w-tw+(block.xoff* EventX < int16(block.x+block.w) {
2))) && ev.EventX < int16(block.x+block.w) {
return false return false
} }
return true return true
} }
if ev.EventX > int16(block.x) && ev.EventX < int16( if ev.EventX > int16(block.x) && ev.EventX < int16(block.x+block.
block.x+block.w) { w) {
return false return false
} }
return true return true
@ -159,9 +156,8 @@ func initBar(x, y, w, h int, font string, fsize float64) (*Bar,
} }
func (bar *Bar) draw(block *Block) error { func (bar *Bar) draw(block *Block) error {
// Calculate the required x coordinate for the different // Calculate the required x coordinate for the different aligments.
// aligments. tw, _ := xgraphics.Extents(bar.font, bar.size, block.txt)
tw, _ := xgraphics.Extents(bar.font, bar.fsize, block.txt)
var x int var x int
switch block.align { switch block.align {
case 'l': case 'l':
@ -173,8 +169,7 @@ func (bar *Bar) draw(block *Block) error {
case 'a': case 'a':
x = (bar.w / 2) - (tw / 2) x = (bar.w / 2) - (tw / 2)
default: default:
return fmt.Errorf("draw %#U: Not a valid aligment rune", return fmt.Errorf("draw %#U: Not a valid aligment rune", block.align)
block.align)
} }
x += block.xoff x += block.xoff
@ -193,12 +188,13 @@ func (bar *Bar) draw(block *Block) error {
// Draw the text. // Draw the text.
// TODO: Center text vertically automatically. // TODO: Center text vertically automatically.
if _, _, err := block.img.Text(x, 6, hexToBGRA(block.fg), if _, _, err := block.img.Text(x, 6, hexToBGRA(block.fg), bar.size, bar.
bar.fsize, bar.font, block.txt); err != nil { font, block.txt); err != nil {
return err return err
} }
block.img.XDraw() block.img.XDraw()
bar.img.XPaint(bar.win.Id) bar.img.XPaint(bar.win.Id)
return nil return nil
} }

@ -14,9 +14,9 @@ type Block struct {
// The x coordinate and width of the block. // The x coordinate and width of the block.
x, w int x, w int
/// The aligment of the text, this can be `l` for left aligment, // The aligment of the text, this can be `l` for left aligment, `c` for
// `c` for center aligment, `r` for right aligment and `a` for // center aligment, `r` for right aligment and `a` for absolute center
// absolute center aligment. // aligment.
align rune align rune
// Additional x offset to further tweak the location of the text. // Additional x offset to further tweak the location of the text.
@ -25,25 +25,33 @@ type Block struct {
// The foreground and background colors in hex. // The foreground and background colors in hex.
bg, fg string bg, fg string
// A map with functions to execute on button events. Accepted // A map with functions to execute on button events. Accepted button strings
// button strings are `button0` to `button5` // are `button0` to `button5`
actions map[string]func() actions map[string]func()
// The popup.
popup *Popup
// The sub-image that represents the block. // The sub-image that represents the block.
img *xgraphics.Image img *xgraphics.Image
} }
func (bar *Bar) initBlock(name, txt string, w int, align rune, func (bar *Bar) initBlock(name, txt string, w int, align rune, xoff int, bg,
xoff int, bg, fg string) *Block { fg string) *Block {
block := new(Block) block := new(Block)
block.txt = txt block.txt = txt
block.x = bar.xsum block.x = bar.xsum
block.w = w block.w = w
block.align = align block.align = align
block.xoff = xoff block.xoff = xoff
block.bg = bg block.bg = bg
block.fg = fg block.fg = fg
block.actions = map[string]func(){ block.actions = map[string]func(){
"button1": func() {}, "button1": func() {},
"button2": func() {}, "button2": func() {},
@ -51,8 +59,9 @@ func (bar *Bar) initBlock(name, txt string, w int, align rune,
"button4": func() {}, "button4": func() {},
"button5": func() {}, "button5": func() {},
} }
block.img = bar.img.SubImage(image.Rect(bar.xsum, 0, bar.xsum+w,
bar.h)).(*xgraphics.Image) 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. // Add the width of this block to the xsum.
bar.xsum += w bar.xsum += w

143
blocks.go

@ -17,8 +17,7 @@ import (
) )
func (bar *Bar) clockFun() { func (bar *Bar) clockFun() {
block := bar.initBlock("clock", "?", 800, 'a', 0, "#445967", block := bar.initBlock("clock", "?", 800, 'a', 0, "#445967", "#CCCCCC")
"#CCCCCC")
init := true init := true
for { for {
@ -38,23 +37,36 @@ func (bar *Bar) clockFun() {
} }
func (bar *Bar) musicFun() error { func (bar *Bar) musicFun() error {
block := bar.initBlock("music", "?", 660, 'r', -10, "#3C4F5B", block := bar.initBlock("music", "?", 660, 'r', -13, "#3C4F5B", "#CCCCCC")
"#CCCCCC")
block.actions["button1"] = func() {
if block.popup == nil {
var err error
block.popup, err = bar.initPopup(1920-304-29, 29, 304, 148,
"#3C4F5B", "#CCCCCC")
if err != nil {
log.Print(err)
}
//popup.draw()
} else {
block.popup = block.popup.destroy()
}
}
block.actions["button3"] = func() { block.actions["button3"] = func() {
conn, err := mpd.Dial("tcp", ":6600") conn, err := mpd.Dial("tcp", ":6600")
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }
defer conn.Close()
status, err := conn.Status() status, err := conn.Status()
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }
if status["state"] == "pause" {
conn.Pause(false) if err := conn.Pause(status["state"] != "pause"); err != nil {
} else { log.Print(err)
conn.Pause(true)
} }
} }
block.actions["button4"] = func() { block.actions["button4"] = func() {
@ -62,16 +74,22 @@ func (bar *Bar) musicFun() error {
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }
defer conn.Close()
conn.Previous() if err := conn.Previous(); err != nil {
log.Print(err)
}
} }
block.actions["button5"] = func() { block.actions["button5"] = func() {
conn, err := mpd.Dial("tcp", ":6600") conn, err := mpd.Dial("tcp", ":6600")
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }
defer conn.Close()
conn.Next() if err := conn.Next(); err != nil {
log.Print(err)
}
} }
watcher, err := mpd.NewWatcher("tcp", ":6600", "", "player") watcher, err := mpd.NewWatcher("tcp", ":6600", "", "player")
@ -87,8 +105,7 @@ func (bar *Bar) musicFun() error {
} }
init = false init = false
// TODO: Is it maybe possible to not create a new connection // TODO: Is it maybe possible to not create a new connection each 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)
@ -106,6 +123,7 @@ func (bar *Bar) musicFun() error {
log.Print(err) log.Print(err)
continue continue
} }
var state string var state string
if status["state"] == "pause" { if status["state"] == "pause" {
state = "[paused] " state = "[paused] "
@ -122,17 +140,16 @@ func (bar *Bar) musicFun() error {
} }
func (bar *Bar) todoFun() { func (bar *Bar) todoFun() {
block := bar.initBlock("todo", "?", 29, 'c', 0, "#5394C9", block := bar.initBlock("todo", "?", 29, 'c', 0, "#5394C9", "#FFFFFF")
"#FFFFFF")
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := watcher.Add("/home/onodera/todo"); err != nil { if err := watcher.Add("/home/onodera/.todo"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
file, err := os.Open("/home/onodera/todo") file, err := os.Open("/home/onodera/.todo")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -218,12 +235,11 @@ func (bar *Bar) weatherFun() {
*/ */
func (bar *Bar) windowFun() { func (bar *Bar) windowFun() {
block := bar.initBlock("window", "?", 220, 'c', 0, "#37BF8D", block := bar.initBlock("window", "?", 220, 'c', 0, "#37BF8D", "#FFFFFF")
"#FFFFFF")
// TODO: I'm not sure how I can use init here? // TODO: I'm not sure how I can use init (to prevent a black bar) here?
xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent.
ev xevent.PropertyNotifyEvent) { 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 {
return return
@ -256,34 +272,32 @@ func (bar *Bar) windowFun() {
} }
func (bar *Bar) workspaceFun() { func (bar *Bar) workspaceFun() {
blockwww := bar.initBlock("www", "www", 74, 'c', 0, "#5394C9", blockWWW := bar.initBlock("www", "www", 74, 'c', 0, "#5394C9", "#FFFFFF")
"#FFFFFF") blockWWW.actions["button1"] = func() {
blockwww.actions["button1"] = func() {
if err := ewmh.CurrentDesktopReq(bar.xu, 0); err != nil { if err := ewmh.CurrentDesktopReq(bar.xu, 0); err != nil {
log.Println(err) log.Println(err)
} }
} }
blockirc := bar.initBlock("irc", "irc", 67, 'c', 0, "#5394C9", blockIRC := bar.initBlock("irc", "irc", 67, 'c', 0, "#5394C9", "#FFFFFF")
"#FFFFFF") blockIRC.actions["button1"] = func() {
blockirc.actions["button1"] = func() {
if err := ewmh.CurrentDesktopReq(bar.xu, 1); err != nil { if err := ewmh.CurrentDesktopReq(bar.xu, 1); err != nil {
log.Println(err) log.Println(err)
} }
} }
blocksrc := bar.initBlock("src", "src", 70, 'c', 0, "#5394C9", blockSRC := bar.initBlock("src", "src", 70, 'c', 0, "#5394C9", "#FFFFFF")
"#FFFFFF") blockSRC.actions["button1"] = func() {
blocksrc.actions["button1"] = func() {
if err := ewmh.CurrentDesktopReq(bar.xu, 2); err != nil { if err := ewmh.CurrentDesktopReq(bar.xu, 2); err != nil {
log.Println(err) log.Println(err)
} }
} }
// TODO: I'm not sure how I can use init here? // TODO: I'm not sure how I can use init (to prevent a black bar) here?
var owsp uint var owsp uint
xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, var pwsp, nwsp int
ev xevent.PropertyNotifyEvent) { xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, 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 {
return return
@ -298,27 +312,56 @@ func (bar *Bar) workspaceFun() {
log.Print(err) log.Print(err)
return return
} }
switch wsp {
case 0:
blockWWW.bg = "#72A7D3"
blockIRC.bg = "#5394C9"
blockSRC.bg = "#5394C9"
pwsp = 2
nwsp = 1
case 1:
blockWWW.bg = "#5394C9"
blockIRC.bg = "#72A7D3"
blockSRC.bg = "#5394C9"
pwsp = 0
nwsp = 2
case 2:
blockWWW.bg = "#5394C9"
blockIRC.bg = "#5394C9"
blockSRC.bg = "#72A7D3"
pwsp = 1
nwsp = 0
}
if owsp == wsp { if owsp == wsp {
return return
} }
owsp = wsp owsp = wsp
switch wsp { bar.redraw <- blockWWW
case 0: bar.redraw <- blockIRC
blockwww.bg = "#72A7D3" bar.redraw <- blockSRC
blockirc.bg = "#5394C9"
blocksrc.bg = "#5394C9"
case 1:
blockwww.bg = "#5394C9"
blockirc.bg = "#72A7D3"
blocksrc.bg = "#5394C9"
case 2:
blockwww.bg = "#5394C9"
blockirc.bg = "#5394C9"
blocksrc.bg = "#72A7D3"
}
bar.redraw <- blockwww
bar.redraw <- blockirc
bar.redraw <- blocksrc
}).Connect(bar.xu, bar.xu.RootWin()) }).Connect(bar.xu, bar.xu.RootWin())
prevFun := func() {
if err := ewmh.CurrentDesktopReq(bar.xu, pwsp); err != nil {
log.Println(err)
}
}
nextFun := func() {
if err := ewmh.CurrentDesktopReq(bar.xu, nwsp); err != nil {
log.Println(err)
}
}
blockWWW.actions["button4"] = prevFun
blockWWW.actions["button5"] = nextFun
blockIRC.actions["button4"] = prevFun
blockIRC.actions["button5"] = nextFun
blockSRC.actions["button4"] = prevFun
blockSRC.actions["button5"] = nextFun
} }

@ -6,8 +6,7 @@ import (
) )
func main() { func main() {
bar, err := initBar(0, 0, 1920, 29, bar, err := initBar(0, 0, 1920, 29, "/home/onodera/.fonts/cure.tff.bak", 11)
"/home/onodera/.fonts/cure.ttf", 11)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

98
popup.go Normal file

@ -0,0 +1,98 @@
package main
import (
"image"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil/ewmh"
"github.com/BurntSushi/xgbutil/xgraphics"
"github.com/BurntSushi/xgbutil/xwindow"
)
// Popup is a struct with information about the popup.
type Popup struct {
// The popup window and image.
win *xwindow.Window
img *xgraphics.Image
// The width and height of the popup.
w, h int
// The foreground and background colors in hex.
bg, fg string
// A channel where the popup should be send to to once its ready
// to be redrawn.
redraw chan *Popup
}
func (bar *Bar) initPopup(x, y, w, h int, bg, fg string) (*Popup,
error) {
popup := new(Popup)
var err error
// Create a window for the bar. This window listens to button
// press events in order to respond to them.
popup.win, err = xwindow.Generate(bar.xu)
if err != nil {
return nil, err
}
popup.win.Create(bar.xu.RootWin(), x, y, w, h, xproto.CwBackPixel|
xproto.CwEventMask, 0x000000, xproto.EventMaskButtonPress)
// EWMH stuff.
// TODO: `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, popup.win.Id, []string{
"_NET_WM_WINDOW_TYPE_DOCK"}); err != nil {
return nil, err
}
if err := ewmh.WmStateSet(bar.xu, popup.win.Id, []string{
"_NET_WM_STATE_STICKY"}); err != nil {
return nil, err
}
if err := ewmh.WmDesktopSet(bar.xu, popup.win.Id, ^uint(
0)); err != nil {
return nil, err
}
if err := ewmh.WmNameSet(bar.xu, popup.win.Id, "melonbar"); err !=
nil {
return nil, err
}
// Map window.
popup.win.Map()
// TODO: Moving the window is again a hack to keep OpenBox happy.
popup.win.Move(x, y)
// Create the bar image.
popup.img = xgraphics.New(bar.xu, image.Rect(0, 0, w, h))
popup.img.XSurfaceSet(popup.win.Id)
popup.img.XDraw()
popup.w = w
popup.h = h
popup.bg = bg
popup.fg = fg
popup.redraw = make(chan *Popup)
return popup, nil
}
//func (popup *Popup) draw() error {
//}
// TODO: I don't know if this actually frees memory and shit.
func (popup *Popup) destroy() *Popup {
popup.win.Destroy()
popup.img.Destroy()
close(popup.redraw)
return nil
}

1
popups.go Normal file

@ -0,0 +1 @@
package main