diff --git a/bar.go b/bar.go index bbaee2a..76c9b2f 100644 --- a/bar.go +++ b/bar.go @@ -54,7 +54,7 @@ func initBar(x, y, w, h int, fp string) (*Bar, error) { return nil, err } - // Run the main X event loop, this is uses to catch events. + // Run the main X event loop, this is used to catch events. go xevent.Main(bar.xu) // Listen to the root window for property change events, used to check if diff --git a/block.go b/block.go index 0063381..5ce0510 100644 --- a/block.go +++ b/block.go @@ -71,3 +71,14 @@ func (bar *Bar) initBlock(name, txt string, w int, align rune, xoff int, bg, return block } + +// TODO: Make this function more versatile by allowing different and multiple +// properties to be checked. +func (block *Block) diff(txt string) bool { + if block.txt == txt { + return false + } + + block.txt = txt + return true +} diff --git a/blocks.go b/blocks.go index 3c5f8ef..8315151 100644 --- a/blocks.go +++ b/blocks.go @@ -3,6 +3,7 @@ package main import ( "bufio" "os" + "path" "strconv" "time" @@ -13,31 +14,54 @@ import ( "github.com/BurntSushi/xgbutil/xprop" "github.com/fhs/gompd/mpd" "github.com/fsnotify/fsnotify" + homedir "github.com/mitchellh/go-homedir" ) func (bar *Bar) clockFun() { + // Initialize block. block := bar.initBlock("clock", "?", 800, 'a', 0, "#445967", "#CCCCCC") + bar.redraw <- block - init := true for { - if !init { - time.Sleep(20 * time.Second) - } - init = false - + // Compose block text. txt := time.Now().Format("Monday, January 2th 03:04 PM") - if block.txt == txt { - continue + + // Redraw block. + if block.diff(txt) { + bar.redraw <- block } - block.txt = txt - bar.redraw <- block + // Update every 45 seconds. + time.Sleep(45 * time.Second) } } func (bar *Bar) musicFun() { - block := bar.initBlock("music", "?", 660, 'r', -12, "#3C4F5B", "#CCCCCC") + // Initialize block. + block := bar.initBlock("music", "» ", 660, 'r', -12, "#3C4F5B", "#CCCCCC") + bar.redraw <- block + // Connect to MPD. + c, err := mpd.Dial("tcp", ":6600") + if err != nil { + panic(err) + } + + // Keep connection alive by pinging ever 45 seconds. + go func() { + for { + time.Sleep(time.Second * 45) + + if err := c.Ping(); err != nil { + c, err = mpd.Dial("tcp", ":6600") + if err != nil { + panic(err) + } + } + } + }() + + // Show popup on clicking the left mouse button. /*block.actions["button1"] = func() { if block.popup == nil { var err error @@ -52,191 +76,131 @@ func (bar *Bar) musicFun() { block.popup = block.popup.destroy() } }*/ + + // Toggle play/pause on clicking the right mouse button. block.actions["button3"] = func() { - conn, err := mpd.Dial("tcp", ":6600") - if err != nil { - panic(err) - } - defer conn.Close() - - status, err := conn.Status() + status, err := c.Status() if err != nil { panic(err) } - if err := conn.Pause(status["state"] != "pause"); err != nil { + if err := c.Pause(status["state"] != "pause"); err != nil { panic(err) } } + + // Previous song on scrolling up. block.actions["button4"] = func() { - conn, err := mpd.Dial("tcp", ":6600") - if err != nil { - panic(err) - } - defer conn.Close() - - if err := conn.Previous(); err != nil { + if err := c.Previous(); err != nil { panic(err) } } + + // Next song on on scrolling down.. block.actions["button5"] = func() { - conn, err := mpd.Dial("tcp", ":6600") - if err != nil { - panic(err) - } - defer conn.Close() - - if err := conn.Next(); err != nil { + if err := c.Next(); err != nil { panic(err) } } - watcher, err := mpd.NewWatcher("tcp", ":6600", "", "player") + // Watch MPD for events. + w, err := mpd.NewWatcher("tcp", ":6600", "", "player") if err != nil { panic(err) } - var conn *mpd.Client - init := true + for { - if !init { - conn.Close() - <-watcher.Event + cur, err := c.CurrentSong() + if err != nil { + panic(err) } - init = false - - // TODO: Is it maybe possible to not create a new connection each loop? - conn, err = mpd.Dial("tcp", ":6600") + sts, err := c.Status() if err != nil { panic(err) } - cur, err := conn.CurrentSong() - if err != nil { - panic(err) + // Compose text. + var s string + if sts["state"] == "pause" { + s = "[paused] " + } + txt := "» " + s + cur["Artist"] + " - " + cur["Title"] + + // Redraw block. + if block.diff(txt) { + bar.redraw <- block } - status, err := conn.Status() - if err != nil { - panic(err) - } - - var state string - if status["state"] == "pause" { - state = "[paused] " - } - - txt := "» " + state + cur["Artist"] + " - " + cur["Title"] - if block.txt == txt { - continue - } - - block.txt = txt - bar.redraw <- block + <-w.Event } } func (bar *Bar) todoFun() { - block := bar.initBlock("todo", "?", 29, 'c', 0, "#5394C9", "#FFFFFF") + // Initialize block. + block := bar.initBlock("todo", "¢", 29, 'c', 0, "#5394C9", "#FFFFFF") + bar.redraw <- block - watcher, err := fsnotify.NewWatcher() + // Find `.todo` file. + hd, err := homedir.Dir() if err != nil { panic(err) } - if err := watcher.Add("/home/onodera/.todo"); err != nil { - panic(err) - } - file, err := os.Open("/home/onodera/.todo") + fp := path.Join(hd, ".todo") + + // Watch file for events. + w, err := fsnotify.NewWatcher() if err != nil { panic(err) } - init := true + if err := w.Add(fp); err != nil { + panic(err) + } + f, err := os.Open(fp) + if err != nil { + panic(err) + } + for { - if !init { - ev := <-watcher.Events - if ev.Op&fsnotify.Write != fsnotify.Write { - continue - } - } - init = false - - s := bufio.NewScanner(file) + // Count file lines. + s := bufio.NewScanner(f) s.Split(bufio.ScanLines) var c int for s.Scan() { c++ } - if _, err := file.Seek(0, 0); err != nil { + + // Rewind file. + if _, err := f.Seek(0, 0); err != nil { panic(err) } + // Compose block text. txt := "¢ " + strconv.Itoa(c) - if block.txt == txt { + + // Redraw block. + if block.diff(txt) { + bar.redraw <- block + } + + // Listen for next write event. + ev := <-w.Events + if ev.Op&fsnotify.Write != fsnotify.Write { continue } - - block.txt = txt - bar.redraw <- block } } -/* -func (bar *Bar) weatherFun() { - 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 { - time.Sleep(200 * time.Second) - } - init = false - - if err := w.CurrentByID(2758106); err != nil { - log.Print(err) - 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 - } - - block.txt = strconv.FormatFloat(w.Main.Temp, 'f', 0, 64) + - " °C" - bar.redraw <- block - } -} -*/ - func (bar *Bar) windowFun() { + // Initialize blocks. blockIcon := bar.initBlock("window", "º", 21, 'l', 12, "#37BF8D", "#FFFFFF") block := bar.initBlock("window", "?", 200, 'c', 0, "#37BF8D", "#FFFFFF") + bar.redraw <- blockIcon + bar.redraw <- block - // TODO: I'm not sure how I can use init (to prevent a black bar) here? // TODO: This doesn't check for window title changes. xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent. PropertyNotifyEvent) { + // Only listen to `_NET_ACTIVE_WINDOW` events. atom, err := xprop.Atm(bar.xu, "_NET_ACTIVE_WINDOW") if err != nil { panic(err) @@ -245,6 +209,7 @@ func (bar *Bar) windowFun() { return } + // Get active window. id, err := ewmh.ActiveWindowGet(bar.xu) if err != nil { panic(err) @@ -253,6 +218,7 @@ func (bar *Bar) windowFun() { return } + // Compose block text. txt, err := ewmh.WmNameGet(bar.xu, id) if err != nil || len(txt) == 0 { txt, err = icccm.WmNameGet(bar.xu, id) @@ -263,47 +229,48 @@ func (bar *Bar) windowFun() { if len(txt) > 38 { txt = txt[0:38] + "..." } - if block.txt == txt { - return + + // Redraw block. + if block.diff(txt) { + bar.redraw <- block } - - block.txt = txt - bar.redraw <- block }).Connect(bar.xu, bar.xu.RootWin()) - - bar.redraw <- blockIcon } func (bar *Bar) workspaceFun() { + // Initialize block. blockWWW := bar.initBlock("www", "¼ www", 74, 'l', 10, "#5394C9", "#FFFFFF") + blockIRC := bar.initBlock("irc", "½ irc", 67, 'l', 10, "#5394C9", + "#FFFFFF") + blockSRC := bar.initBlock("src", "¾ src", 70, 'l', 10, "#5394C9", + "#FFFFFF") + bar.redraw <- blockWWW + bar.redraw <- blockIRC + bar.redraw <- blockSRC + + // Change active workspace on clicking on one of the blocks. blockWWW.actions["button1"] = func() { if err := ewmh.CurrentDesktopReq(bar.xu, 0); err != nil { panic(err) } } - - blockIRC := bar.initBlock("irc", "½ irc", 67, 'l', 10, "#5394C9", - "#FFFFFF") blockIRC.actions["button1"] = func() { if err := ewmh.CurrentDesktopReq(bar.xu, 1); err != nil { panic(err) } } - - blockSRC := bar.initBlock("src", "¾ src", 70, 'l', 10, "#5394C9", - "#FFFFFF") blockSRC.actions["button1"] = func() { if err := ewmh.CurrentDesktopReq(bar.xu, 2); err != nil { panic(err) } } - // TODO: I'm not sure how I can use init (to prevent a black bar) here? var owsp uint var pwsp, nwsp int xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent. PropertyNotifyEvent) { + // Only listen to `_NET_ACTIVE_WINDOW` events. atom, err := xprop.Atm(bar.xu, "_NET_CURRENT_DESKTOP") if err != nil { panic(err) @@ -312,11 +279,13 @@ func (bar *Bar) workspaceFun() { return } + // Get the current active desktop. wsp, err := ewmh.CurrentDesktopGet(bar.xu) if err != nil { panic(err) } + // Set colors accordingly. switch wsp { case 0: blockWWW.bg = "#72A7D3" @@ -341,14 +310,13 @@ func (bar *Bar) workspaceFun() { nwsp = 0 } - if owsp == wsp { - return - } - owsp = wsp + if owsp != wsp { + bar.redraw <- blockWWW + bar.redraw <- blockIRC + bar.redraw <- blockSRC - bar.redraw <- blockWWW - bar.redraw <- blockIRC - bar.redraw <- blockSRC + owsp = wsp + } }).Connect(bar.xu, bar.xu.RootWin()) prevFun := func() { diff --git a/main.go b/main.go index 8f92a4d..adc9b3e 100644 --- a/main.go +++ b/main.go @@ -1,36 +1,28 @@ package main -import ( - "log" - "time" -) +import "time" func main() { bar, err := initBar(0, 0, 1920, 29, "./vendor/font/cure.font") if err != nil { - log.Fatal(err) + panic(err) } - // Run bar block functions. Make sure to sleep a millisecond after each - // block, else they won't appear in the right order. - go bar.windowFun() - time.Sleep(time.Millisecond * 5) - - go bar.workspaceFun() - time.Sleep(time.Millisecond * 5) - - go bar.clockFun() - time.Sleep(time.Millisecond * 5) - - go bar.musicFun() - time.Sleep(time.Millisecond * 5) - - go bar.todoFun() - time.Sleep(time.Millisecond * 5) + // Run bar block functions. + runBlock(bar.windowFun) + runBlock(bar.workspaceFun) + runBlock(bar.clockFun) + runBlock(bar.musicFun) + runBlock(bar.todoFun) for { if err := bar.draw(<-bar.redraw); err != nil { - log.Fatal(err) + panic(err) } } } + +func runBlock(f func()) { + go f() + time.Sleep(time.Millisecond * 10) +}