diff --git a/README.md b/README.md index ab86056..1a699e2 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ specified before the `for` loop. For example setting up a connection 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 -immediately checking the current workspace. - use: +example of this would be not waiting for a workspace change event, but +immediately checking the current workspace - use: ```go 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 -values using for example `block.bg = value` and running +When you've gathered all information you can update the block values +using for example `block.bg = value` and running `bar.redraw <- block`. diff --git a/bar.go b/bar.go index d6f1def..7887c0e 100644 --- a/bar.go +++ b/bar.go @@ -23,27 +23,26 @@ type Bar struct { win *xwindow.Window img *xgraphics.Image - // The font and fontsize that should be used. - font *truetype.Font - fsize float64 - // 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. + // 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. + // 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, fsize float64) (*Bar, - error) { +func initBar(x, y, w, h int, font string, size float64) (*Bar, error) { bar := new(Bar) 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. 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 { + // 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. + // 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) + 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 actuale bar. - // 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? + // 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 @@ -85,29 +83,34 @@ func initBar(x, y, w, h int, font string, fsize 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 } // Map window. 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) // Create the bar image. 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.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. + // 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 @@ -116,36 +119,30 @@ func initBar(x, y, w, h int, font string, fsize float64) (*Bar, if err != nil { return nil, err } - bar.fsize = fsize - - bar.w = w - bar.h = h + 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) { + 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? + // 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.fsize, - block.txt) - if ev.EventX > int16(block.x+(block.w-tw+(block.xoff* - 2))) && ev.EventX < int16(block.x+block.w) { + 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) { + if ev.EventX > int16(block.x) && ev.EventX < int16(block.x+block. + w) { return false } 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 { - // Calculate the required x coordinate for the different - // aligments. - tw, _ := xgraphics.Extents(bar.font, bar.fsize, block.txt) + // 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': @@ -173,8 +169,7 @@ func (bar *Bar) draw(block *Block) error { case 'a': x = (bar.w / 2) - (tw / 2) default: - return fmt.Errorf("draw %#U: Not a valid aligment rune", - block.align) + return fmt.Errorf("draw %#U: Not a valid aligment rune", block.align) } x += block.xoff @@ -193,12 +188,13 @@ func (bar *Bar) draw(block *Block) error { // Draw the text. // TODO: Center text vertically automatically. - if _, _, err := block.img.Text(x, 6, hexToBGRA(block.fg), - bar.fsize, bar.font, block.txt); err != nil { + 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 } diff --git a/block.go b/block.go index 9404e38..0063381 100644 --- a/block.go +++ b/block.go @@ -14,9 +14,9 @@ type Block struct { // The x coordinate and width of the block. x, w int - /// The aligment of the text, this can be `l` for left aligment, - // `c` for center aligment, `r` for right aligment and `a` for - // absolute center aligment. + // The aligment of the text, this can be `l` for left aligment, `c` for + // center aligment, `r` for right aligment and `a` for absolute center + // aligment. align rune // 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. bg, fg string - // A map with functions to execute on button events. Accepted - // button strings are `button0` to `button5` + // A map with functions to execute on button events. Accepted button strings + // are `button0` to `button5` actions map[string]func() + // The popup. + popup *Popup + // 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 { +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() {}, @@ -51,8 +59,9 @@ func (bar *Bar) initBlock(name, txt string, w int, align rune, "button4": 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. bar.xsum += w diff --git a/blocks.go b/blocks.go index 265e67e..c9a3f1f 100644 --- a/blocks.go +++ b/blocks.go @@ -17,8 +17,7 @@ import ( ) func (bar *Bar) clockFun() { - block := bar.initBlock("clock", "?", 800, 'a', 0, "#445967", - "#CCCCCC") + block := bar.initBlock("clock", "?", 800, 'a', 0, "#445967", "#CCCCCC") init := true for { @@ -38,23 +37,36 @@ func (bar *Bar) clockFun() { } func (bar *Bar) musicFun() error { - block := bar.initBlock("music", "?", 660, 'r', -10, "#3C4F5B", - "#CCCCCC") + block := bar.initBlock("music", "?", 660, 'r', -13, "#3C4F5B", "#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() { conn, err := mpd.Dial("tcp", ":6600") if err != nil { log.Print(err) } + defer conn.Close() status, err := conn.Status() if err != nil { log.Print(err) } - if status["state"] == "pause" { - conn.Pause(false) - } else { - conn.Pause(true) + + if err := conn.Pause(status["state"] != "pause"); err != nil { + log.Print(err) } } block.actions["button4"] = func() { @@ -62,16 +74,22 @@ func (bar *Bar) musicFun() error { if err != nil { log.Print(err) } + defer conn.Close() - conn.Previous() + if err := conn.Previous(); err != nil { + log.Print(err) + } } block.actions["button5"] = func() { conn, err := mpd.Dial("tcp", ":6600") if err != nil { log.Print(err) } + defer conn.Close() - conn.Next() + if err := conn.Next(); err != nil { + log.Print(err) + } } watcher, err := mpd.NewWatcher("tcp", ":6600", "", "player") @@ -87,8 +105,7 @@ func (bar *Bar) musicFun() error { } init = false - // TODO: Is it maybe possible to not create a new connection - // 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) @@ -106,6 +123,7 @@ func (bar *Bar) musicFun() error { log.Print(err) continue } + var state string if status["state"] == "pause" { state = "[paused] " @@ -122,17 +140,16 @@ func (bar *Bar) musicFun() error { } func (bar *Bar) todoFun() { - block := bar.initBlock("todo", "?", 29, 'c', 0, "#5394C9", - "#FFFFFF") + block := bar.initBlock("todo", "?", 29, 'c', 0, "#5394C9", "#FFFFFF") watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } - if err := watcher.Add("/home/onodera/todo"); err != nil { + if err := watcher.Add("/home/onodera/.todo"); err != nil { log.Fatal(err) } - file, err := os.Open("/home/onodera/todo") + file, err := os.Open("/home/onodera/.todo") if err != nil { log.Fatal(err) } @@ -218,12 +235,11 @@ func (bar *Bar) weatherFun() { */ func (bar *Bar) windowFun() { - block := 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(_ *xgbutil.XUtil, - ev xevent.PropertyNotifyEvent) { + // TODO: I'm not sure how I can use init (to prevent a black bar) here? + xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent. + PropertyNotifyEvent) { atom, err := xprop.Atm(bar.xu, "_NET_ACTIVE_WINDOW") if ev.Atom != atom { return @@ -256,34 +272,32 @@ func (bar *Bar) windowFun() { } func (bar *Bar) workspaceFun() { - blockwww := bar.initBlock("www", "www", 74, 'c', 0, "#5394C9", - "#FFFFFF") - blockwww.actions["button1"] = func() { + blockWWW := bar.initBlock("www", "www", 74, 'c', 0, "#5394C9", "#FFFFFF") + blockWWW.actions["button1"] = func() { if err := ewmh.CurrentDesktopReq(bar.xu, 0); err != nil { log.Println(err) } } - blockirc := bar.initBlock("irc", "irc", 67, 'c', 0, "#5394C9", - "#FFFFFF") - blockirc.actions["button1"] = func() { + 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() { + blockSRC := bar.initBlock("src", "src", 70, 'c', 0, "#5394C9", "#FFFFFF") + blockSRC.actions["button1"] = func() { if err := ewmh.CurrentDesktopReq(bar.xu, 2); 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 (to prevent a black bar) here? var owsp uint - xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, - ev xevent.PropertyNotifyEvent) { + var pwsp, nwsp int + xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent. + PropertyNotifyEvent) { atom, err := xprop.Atm(bar.xu, "_NET_CURRENT_DESKTOP") if ev.Atom != atom { return @@ -298,27 +312,56 @@ func (bar *Bar) workspaceFun() { log.Print(err) 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 { return } owsp = wsp - switch wsp { - case 0: - blockwww.bg = "#72A7D3" - 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 + bar.redraw <- blockWWW + bar.redraw <- blockIRC + bar.redraw <- blockSRC }).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 } diff --git a/main.go b/main.go index 0a9ac03..c4165d9 100644 --- a/main.go +++ b/main.go @@ -6,8 +6,7 @@ import ( ) func main() { - bar, err := initBar(0, 0, 1920, 29, - "/home/onodera/.fonts/cure.ttf", 11) + bar, err := initBar(0, 0, 1920, 29, "/home/onodera/.fonts/cure.tff.bak", 11) if err != nil { log.Fatal(err) } diff --git a/popup.go b/popup.go new file mode 100644 index 0000000..595c732 --- /dev/null +++ b/popup.go @@ -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 +} diff --git a/popups.go b/popups.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/popups.go @@ -0,0 +1 @@ +package main