Many minor popup improvements, add weather popup
This commit is contained in:
parent
0b8a9d2e01
commit
9d5cf20d28
@ -13,7 +13,8 @@ Or for a binary that includes embedded static files:
|
|||||||
|
|
||||||
`packr2 get github.com/onodera-punpun/melonbar`
|
`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
|
## USAGE
|
||||||
|
1
TODO.md
1
TODO.md
@ -2,3 +2,4 @@
|
|||||||
* Fix weird `'` characters.
|
* Fix weird `'` characters.
|
||||||
* Fix `?` character.
|
* Fix `?` character.
|
||||||
* Fix `\n` character.
|
* Fix `\n` character.
|
||||||
|
* Fix and add `º` character.
|
||||||
|
34
bar.go
34
bar.go
@ -30,6 +30,9 @@ type Bar struct {
|
|||||||
// right of the last block.
|
// right of the last block.
|
||||||
xsum int
|
xsum int
|
||||||
|
|
||||||
|
// Text drawer.
|
||||||
|
drawer *font.Drawer
|
||||||
|
|
||||||
// A map with information about the block, see the `Block` type.
|
// A map with information about the block, see the `Block` type.
|
||||||
blocks *sync.Map
|
blocks *sync.Map
|
||||||
|
|
||||||
@ -90,6 +93,11 @@ func initBar(x, y, w, h int) (*Bar, error) {
|
|||||||
bar.w = w
|
bar.w = w
|
||||||
bar.h = h
|
bar.h = h
|
||||||
|
|
||||||
|
bar.drawer = &font.Drawer{
|
||||||
|
Dst: bar.img,
|
||||||
|
Face: face,
|
||||||
|
}
|
||||||
|
|
||||||
bar.blocks = new(sync.Map)
|
bar.blocks = new(sync.Map)
|
||||||
bar.redraw = make(chan *Block)
|
bar.redraw = make(chan *Block)
|
||||||
|
|
||||||
@ -111,6 +119,17 @@ func initBar(x, y, w, h int) (*Bar, error) {
|
|||||||
return true
|
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.
|
if ev.EventX >= int16(block.x) && ev.EventX < int16(block.x+block.
|
||||||
w) {
|
w) {
|
||||||
return false
|
return false
|
||||||
@ -132,15 +151,9 @@ func initBar(x, y, w, h int) (*Bar, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bar *Bar) draw(block *Block) 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.
|
// Calculate the required x coordinate for the different aligments.
|
||||||
var x int
|
var x int
|
||||||
tw := d.MeasureString(block.txt).Ceil()
|
tw := bar.drawer.MeasureString(block.txt).Ceil()
|
||||||
switch block.align {
|
switch block.align {
|
||||||
case 'l':
|
case 'l':
|
||||||
x = block.x
|
x = block.x
|
||||||
@ -168,9 +181,12 @@ func (bar *Bar) draw(block *Block) error {
|
|||||||
return hexToBGRA(block.bg)
|
return hexToBGRA(block.bg)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Set text color.
|
||||||
|
bar.drawer.Src = image.NewUniform(hexToBGRA(block.fg))
|
||||||
|
|
||||||
// Draw the text.
|
// Draw the text.
|
||||||
d.Dot = fixed.P(x, 18)
|
bar.drawer.Dot = fixed.P(x, 18)
|
||||||
d.DrawString(block.txt)
|
bar.drawer.DrawString(block.txt)
|
||||||
|
|
||||||
// Redraw the bar.
|
// Redraw the bar.
|
||||||
block.img.XDraw()
|
block.img.XDraw()
|
||||||
|
30
blocks.go
30
blocks.go
@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@ -25,6 +26,23 @@ func (bar *Bar) clock() {
|
|||||||
// Notify that the next block can be initialized.
|
// Notify that the next block can be initialized.
|
||||||
bar.ready <- true
|
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 {
|
for {
|
||||||
// Compose block text.
|
// Compose block text.
|
||||||
txt := time.Now().Format("Monday, January 2th 03:04 PM")
|
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.
|
// Notify that the next block can be initialized.
|
||||||
bar.ready <- true
|
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.
|
// Watch file for events.
|
||||||
w, err := fsnotify.NewWatcher()
|
w, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -226,9 +252,7 @@ func (bar *Bar) window() {
|
|||||||
txt = "?"
|
txt = "?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(txt) > 34 {
|
txt = trim(txt, 34)
|
||||||
txt = txt[0:34] + "..."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redraw block.
|
// Redraw block.
|
||||||
if block.diff(txt) {
|
if block.diff(txt) {
|
||||||
|
BIN
box/images/clock-popup-bg.png
Normal file
BIN
box/images/clock-popup-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 241 B |
Binary file not shown.
Before Width: | Height: | Size: 240 B |
4
main.go
4
main.go
@ -73,11 +73,11 @@ func initFont() error {
|
|||||||
fr := func(name string) ([]byte, error) {
|
fr := func(name string) ([]byte, error) {
|
||||||
return box.Find(path.Join("fonts", name))
|
return box.Find(path.Join("fonts", name))
|
||||||
}
|
}
|
||||||
font, err := box.Find("fonts/cure.font")
|
fp, err := box.Find("fonts/cure.font")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
face, err = plan9font.ParseFont(font, fr)
|
face, err = plan9font.ParseFont(fp, fr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
25
popup.go
25
popup.go
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/BurntSushi/xgbutil/ewmh"
|
"github.com/BurntSushi/xgbutil/ewmh"
|
||||||
"github.com/BurntSushi/xgbutil/xgraphics"
|
"github.com/BurntSushi/xgbutil/xgraphics"
|
||||||
"github.com/BurntSushi/xgbutil/xwindow"
|
"github.com/BurntSushi/xgbutil/xwindow"
|
||||||
|
"golang.org/x/image/font"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Popup is a struct with information about the popup.
|
// Popup is a struct with information about the popup.
|
||||||
@ -21,9 +22,8 @@ type Popup struct {
|
|||||||
// The foreground and background colors in hex.
|
// The foreground and background colors in hex.
|
||||||
bg, fg string
|
bg, fg string
|
||||||
|
|
||||||
// A channel where the popup should be send to to once its ready
|
// Text drawer.
|
||||||
// to be redrawn.
|
drawer *font.Drawer
|
||||||
redraw chan *Popup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initPopup(x, y, w, h int, bg, fg string) (*Popup, error) {
|
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.bg = bg
|
||||||
popup.fg = fg
|
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
|
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.
|
// TODO: I don't know if this actually frees memory and shit.
|
||||||
func (popup *Popup) destroy() *Popup {
|
func (popup *Popup) destroy() *Popup {
|
||||||
popup.win.Destroy()
|
popup.win.Destroy()
|
||||||
popup.img.Destroy()
|
popup.img.Destroy()
|
||||||
close(popup.redraw)
|
|
||||||
popup = nil
|
popup = nil
|
||||||
|
|
||||||
return popup
|
return popup
|
||||||
|
187
popups.go
187
popups.go
@ -1,27 +1,29 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/BurntSushi/xgbutil/xgraphics"
|
"github.com/BurntSushi/xgbutil/xgraphics"
|
||||||
|
"github.com/antchfx/xmlquery"
|
||||||
"github.com/fhs/gompd/mpd"
|
"github.com/fhs/gompd/mpd"
|
||||||
"github.com/rkoesters/xdg/userdirs"
|
"github.com/rkoesters/xdg/userdirs"
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/math/fixed"
|
"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 {
|
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.
|
// Color the background.
|
||||||
popup.img.For(func(cx, cy int) xgraphics.BGRA {
|
popup.img.For(func(cx, cy int) xgraphics.BGRA {
|
||||||
return hexToBGRA(popup.bg)
|
return hexToBGRA(popup.bg)
|
||||||
@ -36,31 +38,47 @@ func (popup *Popup) music(c *mpd.Client) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw album info text.
|
// Set text color.
|
||||||
d.Dot = fixed.P(10, 20)
|
popup.drawer.Src = image.NewUniform(hexToBGRA(popup.fg))
|
||||||
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"])
|
|
||||||
|
|
||||||
// Find album art.
|
// Draw album text.
|
||||||
var f interface{}
|
album := trim(cur["Album"], 32)
|
||||||
f, err = os.Open(path.Join(userdirs.Music, path.Dir(
|
popup.drawer.Dot = fixed.P(-(popup.drawer.MeasureString(album).Ceil()/2)+82,
|
||||||
cur["file"]), "cover_popup.png"))
|
48)
|
||||||
if err != nil {
|
popup.drawer.DrawString(album)
|
||||||
f, err = box.Open("images/cover.png")
|
|
||||||
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
defer f.Close()
|
||||||
|
|
||||||
// Draw album art.
|
// Draw album art.
|
||||||
img, _, err := image.Decode(f.(io.Reader))
|
img, _, err := image.Decode(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// Calculate progressbar lengths.
|
||||||
e, err := strconv.ParseFloat(sts["elapsed"], 32)
|
e, err := strconv.ParseFloat(sts["elapsed"], 32)
|
||||||
@ -75,19 +93,120 @@ func (popup *Popup) music(c *mpd.Client) error {
|
|||||||
pu := 29 - pf
|
pu := 29 - pf
|
||||||
|
|
||||||
// Draw progressbar.
|
// Draw progressbar.
|
||||||
d.Dot = fixed.P(10, 132)
|
popup.drawer.Dot = fixed.P(10, 132)
|
||||||
d.Src = image.NewUniform(hexToBGRA("#5394C9"))
|
|
||||||
for i := 1; i <= pf; i++ {
|
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++ {
|
for i := 1; i <= pu; i++ {
|
||||||
d.DrawString("-")
|
popup.drawer.DrawString("-")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the popup.
|
// Redraw the bar.
|
||||||
popup.img.XDraw()
|
popup.draw()
|
||||||
popup.img.XPaint(popup.win.Id)
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
8
util.go
8
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}
|
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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user