diff --git a/README.md b/README.md index 95df19a..4d13aea 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ Or for a binary that includes embedded static files: `packr2 get github.com/onodera-punpun/melonbar` -`melonbar` depends on Go 1.9 or newer and [packr2](https://github.com/gobuffalo/packr/tree/master/v2). +`melonbar` depends on Go 1.9 or newer, gnuplot, and +[packr2](https://github.com/gobuffalo/packr/tree/master/v2). ## USAGE diff --git a/TODO.md b/TODO.md index 2d36d16..7add46e 100644 --- a/TODO.md +++ b/TODO.md @@ -2,3 +2,4 @@ * Fix weird `'` characters. * Fix `?` character. * Fix `\n` character. +* Fix and add `º` character. diff --git a/bar.go b/bar.go index 7262336..cb308b1 100644 --- a/bar.go +++ b/bar.go @@ -30,6 +30,9 @@ type Bar struct { // right of the last block. xsum int + // Text drawer. + drawer *font.Drawer + // A map with information about the block, see the `Block` type. blocks *sync.Map @@ -90,6 +93,11 @@ func initBar(x, y, w, h int) (*Bar, error) { bar.w = w bar.h = h + bar.drawer = &font.Drawer{ + Dst: bar.img, + Face: face, + } + bar.blocks = new(sync.Map) bar.redraw = make(chan *Block) @@ -111,6 +119,17 @@ func initBar(x, y, w, h int) (*Bar, error) { return true } + // XXX: Hack for clock block. + if name == "clock" { + tw := bar.drawer.MeasureString(block.txt).Ceil() + if ev.EventX >= int16(((bar.w/2)-(tw/2))-13) && ev. + EventX < int16(((bar.w/2)+(tw/2))+13) { + return false + } + block = nil + return true + } + if ev.EventX >= int16(block.x) && ev.EventX < int16(block.x+block. w) { return false @@ -132,15 +151,9 @@ func initBar(x, y, w, h int) (*Bar, error) { } func (bar *Bar) draw(block *Block) error { - d := &font.Drawer{ - Dst: block.img, - Src: image.NewUniform(hexToBGRA(block.fg)), - Face: face, - } - // Calculate the required x coordinate for the different aligments. var x int - tw := d.MeasureString(block.txt).Ceil() + tw := bar.drawer.MeasureString(block.txt).Ceil() switch block.align { case 'l': x = block.x @@ -168,9 +181,12 @@ func (bar *Bar) draw(block *Block) error { return hexToBGRA(block.bg) }) + // Set text color. + bar.drawer.Src = image.NewUniform(hexToBGRA(block.fg)) + // Draw the text. - d.Dot = fixed.P(x, 18) - d.DrawString(block.txt) + bar.drawer.Dot = fixed.P(x, 18) + bar.drawer.DrawString(block.txt) // Redraw the bar. block.img.XDraw() diff --git a/blocks.go b/blocks.go index 706b1ba..1fe032a 100644 --- a/blocks.go +++ b/blocks.go @@ -4,6 +4,7 @@ import ( "bufio" "log" "os" + "os/exec" "path" "strconv" "time" @@ -25,6 +26,23 @@ func (bar *Bar) clock() { // Notify that the next block can be initialized. bar.ready <- true + // Show popup on clicking the left mouse button. + block.actions["button1"] = func() error { + if block.popup != nil { + block.popup = block.popup.destroy() + return nil + } + + var err error + block.popup, err = initPopup((bar.w/2)-(178/2), 29, 178, 129, "#EEEEEE", + "#021B21") + if err != nil { + return err + } + + return block.popup.clock() + } + for { // Compose block text. txt := time.Now().Format("Monday, January 2th 03:04 PM") @@ -147,6 +165,14 @@ func (bar *Bar) todo() { // Notify that the next block can be initialized. bar.ready <- true + // Show popup on clicking the left mouse button. + block.actions["button1"] = func() error { + cmd := exec.Command("st", "micro", "-savecursor", "false", path.Join( + basedir.Home, ".todo")) + cmd.Stdout = os.Stdout + return cmd.Run() + } + // Watch file for events. w, err := fsnotify.NewWatcher() if err != nil { @@ -226,9 +252,7 @@ func (bar *Bar) window() { txt = "?" } } - if len(txt) > 34 { - txt = txt[0:34] + "..." - } + txt = trim(txt, 34) // Redraw block. if block.diff(txt) { diff --git a/box/images/clock-popup-bg.png b/box/images/clock-popup-bg.png new file mode 100644 index 0000000..98e23ad Binary files /dev/null and b/box/images/clock-popup-bg.png differ diff --git a/box/images/cover.png b/box/images/cover.png deleted file mode 100644 index 4d31ff7..0000000 Binary files a/box/images/cover.png and /dev/null differ diff --git a/main.go b/main.go index 71dccac..6715efe 100644 --- a/main.go +++ b/main.go @@ -73,11 +73,11 @@ func initFont() error { fr := func(name string) ([]byte, error) { return box.Find(path.Join("fonts", name)) } - font, err := box.Find("fonts/cure.font") + fp, err := box.Find("fonts/cure.font") if err != nil { return err } - face, err = plan9font.ParseFont(font, fr) + face, err = plan9font.ParseFont(fp, fr) if err != nil { return err } diff --git a/popup.go b/popup.go index 66da8e7..d4224c7 100644 --- a/popup.go +++ b/popup.go @@ -7,6 +7,7 @@ import ( "github.com/BurntSushi/xgbutil/ewmh" "github.com/BurntSushi/xgbutil/xgraphics" "github.com/BurntSushi/xgbutil/xwindow" + "golang.org/x/image/font" ) // Popup is a struct with information about the popup. @@ -21,9 +22,8 @@ type Popup struct { // 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 + // Text drawer. + drawer *font.Drawer } func initPopup(x, y, w, h int, bg, fg string) (*Popup, error) { @@ -77,16 +77,31 @@ func initPopup(x, y, w, h int, bg, fg string) (*Popup, error) { popup.bg = bg popup.fg = fg - popup.redraw = make(chan *Popup) + popup.drawer = &font.Drawer{ + Dst: popup.img, + Face: face, + } + + // Color the background. + popup.img.For(func(cx, cy int) xgraphics.BGRA { + return hexToBGRA(popup.bg) + }) + + // Draw the popup. + popup.draw() return popup, nil } +func (popup *Popup) draw() { + popup.img.XDraw() + popup.img.XPaint(popup.win.Id) +} + // 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) popup = nil return popup diff --git a/popups.go b/popups.go index 256e473..8d6156c 100644 --- a/popups.go +++ b/popups.go @@ -1,27 +1,29 @@ package main import ( + "bufio" + "bytes" + "fmt" "image" "io" + "io/ioutil" "math" + "net/http" "os" + "os/exec" "path" "strconv" "github.com/BurntSushi/xgbutil/xgraphics" + "github.com/antchfx/xmlquery" "github.com/fhs/gompd/mpd" "github.com/rkoesters/xdg/userdirs" - "golang.org/x/image/font" "golang.org/x/image/math/fixed" ) +// TODO: Make progressbar clickable. +// TODO: Make progressbar update every X milliseconds. func (popup *Popup) music(c *mpd.Client) error { - d := &font.Drawer{ - Dst: popup.img, - Src: image.NewUniform(hexToBGRA(popup.fg)), - Face: face, - } - // Color the background. popup.img.For(func(cx, cy int) xgraphics.BGRA { return hexToBGRA(popup.bg) @@ -36,31 +38,47 @@ func (popup *Popup) music(c *mpd.Client) error { return err } - // Draw album info text. - d.Dot = fixed.P(10, 20) - d.DrawString("Album: " + cur["Album"]) - d.Dot = fixed.P(10, 20+16) - d.DrawString("Artist: " + cur["AlbumArtist"]) - d.Dot = fixed.P(10, 20+16+16) - d.DrawString("Date: " + cur["Date"]) + // Set text color. + popup.drawer.Src = image.NewUniform(hexToBGRA(popup.fg)) - // Find album art. - var f interface{} - f, err = os.Open(path.Join(userdirs.Music, path.Dir( - cur["file"]), "cover_popup.png")) - if err != nil { - f, err = box.Open("images/cover.png") + // Draw album text. + album := trim(cur["Album"], 32) + popup.drawer.Dot = fixed.P(-(popup.drawer.MeasureString(album).Ceil()/2)+82, + 48) + popup.drawer.DrawString(album) + + // Draw artist text. + artist := trim("Artist: "+cur["AlbumArtist"], 32) + popup.drawer.Dot = fixed.P(-(popup.drawer.MeasureString(artist).Ceil()/2)+ + 82, 58+16) + popup.drawer.DrawString(artist) + + // Draw rlease date text. + date := trim("Release date: "+cur["Date"], 32) + popup.drawer.Dot = fixed.P(-(popup.drawer.MeasureString(date).Ceil()/2)+82, + 58+16+16) + popup.drawer.DrawString(date) + + // Check if album art file exists. + fp := path.Join(userdirs.Music, path.Dir(cur["file"]), "cover_popup.png") + if _, err := os.Stat(fp); !os.IsNotExist(err) { + f, err := os.Open(fp) if err != nil { return err } - } + defer f.Close() - // Draw album art. - img, _, err := image.Decode(f.(io.Reader)) - if err != nil { - return err + // Draw album art. + img, _, err := image.Decode(f) + if err != nil { + return err + } + xgraphics.Blend(popup.img, xgraphics.NewConvert(X, img), image.Point{ + -166, -10}) + } else { + popup.drawer.Dot = fixed.P(200, 78) + popup.drawer.DrawString("No cover found!") } - xgraphics.Blend(popup.img, img, image.Point{-166, -10}) // Calculate progressbar lengths. e, err := strconv.ParseFloat(sts["elapsed"], 32) @@ -75,19 +93,120 @@ func (popup *Popup) music(c *mpd.Client) error { pu := 29 - pf // Draw progressbar. - d.Dot = fixed.P(10, 132) - d.Src = image.NewUniform(hexToBGRA("#5394C9")) + popup.drawer.Dot = fixed.P(10, 132) for i := 1; i <= pf; i++ { - d.DrawString("-") + popup.drawer.DrawString("-") } - d.Src = image.NewUniform(hexToBGRA(popup.fg)) + popup.drawer.Src = image.NewUniform(hexToBGRA("#72A7D3")) for i := 1; i <= pu; i++ { - d.DrawString("-") + popup.drawer.DrawString("-") } - // Draw the popup. - popup.img.XDraw() - popup.img.XPaint(popup.win.Id) + // Redraw the bar. + popup.draw() + + return nil +} + +func (popup *Popup) clock() error { + // Color the background. + f, err := box.Open("images/clock-popup-bg.png") + if err != nil { + return err + } + defer f.Close() + + // Draw album art. + bg, _, err := image.Decode(f.(io.Reader)) + if err != nil { + return err + } + xgraphics.Blend(popup.img, xgraphics.NewConvert(X, bg), image.Point{0, 0}) + + // Redraw the popup. + popup.draw() + + // Set location. + lat := "52.0646" + lon := "5.2065" + + // Get rainfall information. + r, err := http.Get("https://gps.buienradar.nl/getrr.php?lat=" + lat + + "&lon=" + lon) + if err != nil { + return err + } + defer r.Body.Close() + + // Create rainfall tmp files. + td, err := ioutil.TempFile(os.TempDir(), "melonbar-rain-*.dat") + if err != nil { + return err + } + defer os.Remove(td.Name()) + ti, err := ioutil.TempFile(os.TempDir(), "melonbar-rain-*.png") + if err != nil { + return err + } + defer os.Remove(ti.Name()) + + // Compose rainfall data tmp file contents. + var d []byte + s := bufio.NewScanner(r.Body) + for s.Scan() { + d = append(d, bytes.Split(s.Bytes(), []byte("|"))[0]...) + d = append(d, []byte("\n")...) + } + + // Write rainfall data tmp file. + if _, err = td.Write(d); err != nil { + return err + } + if err := td.Close(); err != nil { + return err + } + + // Create rainfall graph. + cmd := exec.Command("gnuplot", "-e", ` + set terminal png transparent size 211,107; + set output '`+ti.Name()+`'; + set yrange [0:255]; + set noborder; + set nolabel; + set nokey; + set notics; + set notitle; + set style fill solid border rgb '#5394C9'; + plot '`+td.Name()+`' smooth csplines with filledcurve x1 lc rgb '#72A7D3' + `) + if err := cmd.Run(); err != nil { + return err + } + + // Draw rainfall graph. + img, _, err := image.Decode(ti) + if err != nil { + return err + } + xgraphics.Blend(popup.img, xgraphics.NewConvert(X, img), image.Point{14, 0}) + + // Redraw the popup. + popup.draw() + + // Get weather information. + x, err := xmlquery.LoadURL("https://xml.buienradar.nl") + if err != nil { + return err + } + w := xmlquery.FindOne(x, "//weerstation[@id=6260]") + fmt.Println() + popup.drawer.Src = image.NewUniform(hexToBGRA(popup.fg)) + popup.drawer.Dot = fixed.P(10, 100) + popup.drawer.DrawString("Rainfall graph, it's " + w.SelectElement( + "temperatuurGC").InnerText() + "ºC") + + // Redraw the popup. + popup.draw() return nil } diff --git a/util.go b/util.go index 53a3e28..78632af 100644 --- a/util.go +++ b/util.go @@ -13,3 +13,11 @@ func hexToBGRA(h string) xgraphics.BGRA { return xgraphics.BGRA{B: d[2], G: d[1], R: d[0], A: 0xFF} } + +// TODO: Instead of doing this using rune-count, do this using pixel-count. +func trim(txt string, l int) string { + if len(txt) > l { + return txt[0:l] + "..." + } + return txt +}