initial work - making config file

This commit is contained in:
kayos 2021-05-04 07:48:49 -07:00
parent ec0b46bf85
commit aa195431d6
10 changed files with 954 additions and 750 deletions

View File

@ -2,20 +2,13 @@
melonbar - A concurrent, hackable bar/panel for X written in Go.
![](https://punpun.xyz/54c7.png)
![](https:/camille.sh/LGxO.png)
## INSTALLATION
`go get github.com/onodera-punpun/melonbar`
Or for a binary that includes embedded static files:
`packr2 get github.com/onodera-punpun/melonbar`
`melonbar` depends on Go 1.9 or newer, gnuplot, and
[packr2](https://github.com/gobuffalo/packr/tree/master/v2).
## USAGE
@ -23,40 +16,8 @@ The idea is that this bar is very "simple" to configure by just modifying the
source code, à la suckless.
Users can configure the position, width, height and font of the bar in
`main.go`. A bar consist of various blocks that display info, these blocks are
functions definded in `blocks.go` and exectured in a goroutine in `main.go`.
## CREATING BLOCK FUNCTIONS
On top of each block function you should run `bar.initBlock()`, this generates a
block object, and is where most of the configuration happens. Here is a short
explanation of each parameter from left to right:
* The name of the block, this is gets used as the name of the block map key.
(`string`)
* The initial string the block should display. (`string`)
* The width of the block. (`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. (`rune`)
* Additional x offset to further tweak the location of the text. (`int`)
* The foreground color of the block in hexadecimal. (`string`)
* The background color of the block in hexadecimal. (`string`)
It is possible to bind mousebindings to a block using using:
```go
block.actions["buttonN"] = func() {
// Do stuff.
}
```
---
When you've gathered all information you can update the block values using for
example `block.bg = "#FF0000"` or `block.txt = "Hello World!"` and executing
`bar.redraw <- block`.
`main.go`. A bar consist of various blocks that display info, these blocks are
definded in `blocks.go`.
## AUTHORS

View File

@ -2,3 +2,6 @@
* Fix weird `'` characters.
* Fix `?` character.
* Fix `\n` character.
* Check if redrawing is necaserry.
* Test how many times a redraw is called

100
bar.go
View File

@ -4,14 +4,11 @@ import (
"fmt"
"image"
"log"
"strconv"
"sync"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/xevent"
"github.com/BurntSushi/xgbutil/xgraphics"
"github.com/BurntSushi/xgbutil/xwindow"
"github.com/elliotchance/orderedmap"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
@ -32,16 +29,19 @@ type Bar struct {
// Text drawer.
drawer *font.Drawer
// A map with information about the block, see the `Block` type.
blocks *sync.Map
// A map that stores the various blocks.
blocks *orderedmap.OrderedMap
// A map that stores the various popups.
popups *orderedmap.OrderedMap
// Store is an interface to store variables and objects to be used by other
// blocks or popups.
store map[string]interface{}
// A channel where the block should be send to to once its ready to be
// redrawn.
redraw chan *Block
// A channel where a boolean should be send once a block has initizalized,
// notifying that the next block can intialize.
ready chan bool
}
func initBar(x, y, w, h int) (*Bar, error) {
@ -75,70 +75,33 @@ func initBar(x, y, w, h int) (*Bar, error) {
}
bar.img.XDraw()
// Set bar width and height.
bar.w = w
bar.h = h
// Set bar font face.
bar.drawer = &font.Drawer{
Dst: bar.img,
Face: face,
}
bar.blocks = new(sync.Map)
// Creat blocks and popups map.
bar.blocks = orderedmap.NewOrderedMap()
bar.popups = orderedmap.NewOrderedMap()
// Create store map.
bar.store = make(map[string]interface{})
// Create redraw channel.
bar.redraw = make(chan *Block)
// Listen to mouse events and execute the required function.
xevent.ButtonPressFun(func(_ *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
// Determine what block the cursor is in.
var block *Block
bar.blocks.Range(func(name, i interface{}) bool {
block = i.(*Block)
// XXX: Hack for music block.
if name == "music" {
tw := font.MeasureString(face, block.txt).Ceil()
if ev.EventX >= int16(block.x+(block.w-tw+(block.xoff*2))) &&
ev.EventX < int16(block.x+block.w) {
return false
}
block = nil
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
}
block = nil
return true
})
// Execute the function as specified.
if block != nil {
if err := block.actions["button"+strconv.Itoa(int(ev.
Detail))](); err != nil {
log.Println(err)
}
}
}).Connect(X, bar.win.Id)
return bar, nil
}
func (bar *Bar) draw(block *Block) error {
// Calculate the required x coordinate for the different aligments.
var x int
tw := bar.drawer.MeasureString(block.txt).Ceil()
tw := bar.drawer.MeasureString(block.txt).Round()
switch block.align {
case 'l':
x = block.x
@ -152,22 +115,22 @@ func (bar *Bar) draw(block *Block) error {
return fmt.Errorf("draw %#U: Not a valid aligment rune", block.align)
}
x += block.xoff
x += 2
// Color the background.
block.img.For(func(cx, cy int) xgraphics.BGRA {
// XXX: Hack for music block.
if block.w == 660 {
if cx < x+block.xoff {
return hexToBGRA("#445967")
return xgraphics.BGRA{B: 103, G: 89, R: 68, A: 0xFF}
}
return hexToBGRA(block.bg)
}
return hexToBGRA(block.bg)
return block.bg
})
// Set text color.
bar.drawer.Src = image.NewUniform(hexToBGRA(block.fg))
// Set foreground color.
bar.drawer.Src = image.NewUniform(block.fg)
// Draw the text.
bar.drawer.Dot = fixed.P(x, 18)
@ -180,17 +143,6 @@ func (bar *Bar) draw(block *Block) error {
return nil
}
func (bar *Bar) initBlocks(blocks []func()) {
bar.ready = make(chan bool)
for _, f := range blocks {
go f()
<-bar.ready
}
close(bar.ready)
}
func (bar *Bar) listen() {
for {
if err := bar.draw(<-bar.redraw); err != nil {

114
block.go
View File

@ -3,7 +3,11 @@ package main
import (
"image"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/xevent"
"github.com/BurntSushi/xgbutil/xgraphics"
"golang.org/x/image/font"
)
// Block is a struct with information about a block.
@ -25,56 +29,84 @@ type Block struct {
// aligment.
align rune
// The foreground and background colors in hex.
bg, fg string
// The foreground and background colors.
bg, fg xgraphics.BGRA
// A map with functions to execute on button events. Accepted button strings
// are `button0` to `button5`
actions map[string]func() error
// This boolean decides if the block is an invisible "script block", that
// doesn't draw anything to the bar, only executes the `update` function.
script bool
// Block popup..
popup *Popup
// The fuction that updates the block, this will be executes as a goroutine.
update func()
// A map with functions to execute on button events.
actions map[xproto.Button]func() error
}
func (bar *Bar) initBlock(name, txt string, w int, align rune, xoff int, bg,
fg string) *Block {
block := new(Block)
func (bar *Bar) drawBlocks() {
for _, key := range bar.blocks.Keys() {
block := bar.block(key.(string))
block.img = bar.img.SubImage(image.Rect(bar.xsum, 0, bar.xsum+w, bar.
h)).(*xgraphics.Image)
block.x = bar.xsum
block.w = w
block.xoff = xoff
block.txt = txt
block.align = align
block.bg = bg
block.fg = fg
block.actions = map[string]func() error{
"button1": func() error { return nil },
"button2": func() error { return nil },
"button3": func() error { return nil },
"button4": func() error { return nil },
"button5": func() error { return nil },
// Run update function.
go block.update()
// Check if the block is a script block, if that is the case, we only
// execute the `upload` function.
if block.script {
continue
}
// Initialize block image.
block.img = bar.img.SubImage(image.Rect(bar.xsum, 0, bar.xsum+block.
w, bar.h)).(*xgraphics.Image)
// set the block location.
block.x = bar.xsum
// Add the width of this block to the xsum.
bar.xsum += block.w
// Draw block.
bar.redraw <- block
}
// Add the width of this block to the xsum.
bar.xsum += w
// Listen to mouse events and execute the required function.
xevent.ButtonPressFun(func(_ *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
for _, k := range bar.blocks.Keys() {
block := bar.block(k.(string))
// Store the block in map.
bar.blocks.Store(name, block)
// Check if clicked inside the block, if not, return.
switch k {
// XXX: Hack for music block.
case "music":
tw := font.MeasureString(face, block.txt).Round()
if ev.EventX < int16(block.x+(block.w-tw+(block.xoff*2))) || ev.
EventX > int16(block.x+block.w) {
continue
}
// XXX: Hack for clock block.
case "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) {
continue
}
default:
if ev.EventX < int16(block.x) || ev.EventX > int16(block.x+block.
w) {
continue
}
}
// Draw block.
bar.redraw <- block
return block
// Execute the function as specified.
if _, ok := block.actions[ev.Detail]; ok {
go block.actions[ev.Detail]()
}
}
}).Connect(X, bar.win.Id)
}
// 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
func (bar *Bar) block(key string) *Block {
i, _ := bar.blocks.Get(key)
return i.(*Block)
}

652
blocks.go
View File

@ -4,317 +4,409 @@ import (
"log"
"os"
"os/exec"
"os/user"
"path"
"time"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/ewmh"
"github.com/BurntSushi/xgbutil/icccm"
"github.com/BurntSushi/xgbutil/xevent"
"github.com/BurntSushi/xgbutil/xgraphics"
"github.com/BurntSushi/xgbutil/xprop"
"github.com/fhs/gompd/mpd"
"github.com/rkoesters/xdg/basedir"
"github.com/BurntSushi/xgbutil/xwindow"
// "github.com/fhs/gompd/mpd"
)
func (bar *Bar) clock() {
// Initialize block.
block := bar.initBlock("clock", "?", 799, 'a', 0, "#445967", "#CCCCCC")
func (bar *Bar) initBlocks() {
bar.blocks.Set("window", &Block{
txt: "?",
w: activeWinSize,
align: 'c',
xoff: 0,
bg: xgraphics.BGRA{B: 65, G: 0, R: 0, A: 0xFF},
fg: xgraphics.BGRA{B: 255, G: 255, R: 255, A: 0xFF},
// Notify that the next block can be initialized.
bar.ready <- true
update: func() {
block := bar.block("window")
// Show popup on clicking the left mouse button.
block.actions["button1"] = func() error {
if block.popup != nil {
block.popup = block.popup.destroy()
return nil
}
// Redraw block function.
t := func(id xproto.Window) {
// Set new block text.
txt, err := ewmh.WmNameGet(X, id)
if err != nil || len(txt) == 0 {
txt, err = icccm.WmNameGet(X, id)
if err != nil || len(txt) == 0 {
txt = "?"
}
}
txt = trim(txt, 34)
var err error
block.popup, err = initPopup((bar.w/2)-(178/2), bar.h, 178, 129,
"#EEEEEE", "#021B21")
if err != nil {
return err
}
// Return if the text is the same.
if txt == block.txt {
return
}
block.txt = txt
return block.popup.clock()
}
// Redraw block.
bar.redraw <- block
}
for {
// Compose block text.
txt := time.Now().Format("Monday, January 2th 03:04 PM")
// Variable where we store the (previous) xwindow.
var xid *xwindow.Window
// Redraw block.
if block.diff(txt) {
bar.redraw <- block
}
// Update every 45 seconds.
time.Sleep(45 * time.Second)
}
}
func (bar *Bar) music() {
// Initialize block.
block := bar.initBlock("music", "» ", 660, 'r', -12, "#3C4F5B", "#CCCCCC")
// Notify that the next block can be initialized.
bar.ready <- true
// Connect to MPD.
c, err := mpd.Dial("tcp", ":6600")
if err != nil {
log.Fatalln(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")
// Get window ID function.
f := func() {
// Get active window.
id, err := ewmh.ActiveWindowGet(X)
if err != nil {
log.Fatalln(err)
}
}
}
}()
// Show popup on clicking the left mouse button.
block.actions["button1"] = func() error {
if block.popup != nil {
block.popup = block.popup.destroy()
return nil
}
block.popup, err = initPopup(bar.w-304-29, bar.h, 304, 148, "#EEEEEE",
"#021B21")
if err != nil {
return err
}
return block.popup.music(c)
}
// Toggle play/pause on clicking the right mouse button.
block.actions["button3"] = func() error {
status, err := c.Status()
if err != nil {
return err
}
return c.Pause(status["state"] != "pause")
}
// Previous song on scrolling up.
block.actions["button4"] = func() error {
return c.Previous()
}
// Next song on on scrolling down..
block.actions["button5"] = func() error {
return c.Next()
}
// Watch MPD for events.
w, err := mpd.NewWatcher("tcp", ":6600", "", "player")
if err != nil {
log.Fatalln(err)
}
for {
cur, err := c.CurrentSong()
if err != nil {
log.Println(err)
}
sts, err := c.Status()
if err != nil {
log.Println(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
// Redraw popup.
if block.popup != nil {
if err := block.popup.music(c); err != nil {
log.Println(err)
return
}
if id == 0 {
return
}
// Stop listening to the previous window.
if xid != nil {
xid.Detach()
}
// Create xwindow from active window.
xid = xwindow.New(X, id)
// Listen to this window for window name changes.
xid.Listen(xproto.EventMaskPropertyChange)
xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent.
PropertyNotifyEvent) {
// Only listen to `_NET_WM_NAME` events.
atom, err := xprop.Atm(X, "_NET_WM_NAME")
if err != nil {
log.Println(err)
return
}
if ev.Atom != atom {
return
}
t(id)
}).Connect(X, id)
t(id)
}
}
<-w.Event
}
}
// Listen for window change event, execute `f()` accordingly.
xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent.
PropertyNotifyEvent) {
// Only listen to `_NET_ACTIVE_WINDOW` events.
//atom, err := xprop.Atm(X, "_NET_ACTIVE_WINDOW")
//if err != nil {
// log.Println(err)
// return
//}
//if ev.Atom != atom {
// return
//}
func (bar *Bar) todo() {
// Initialize block.
block := bar.initBlock("todo", "¢", bar.h, 'c', 0, "#5394C9", "#FFFFFF")
f()
}).Connect(X, X.RootWin())
// Notify that the next block can be initialized.
bar.ready <- true
// Execute `f()` one time initially.
f()
},
})
// 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()
}
bar.blocks.Set("workspace-1", &Block{
txt: "1",
w: workspaceWidth,
align: 'l',
xoff: 12,
bg: xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF},
fg: xgraphics.BGRA{B: 255, G: 255, R: 255, A: 0xFF},
// Show count popup.
var err error
block.popup, err = initPopup(bar.w-19-16, bar.h-8, 16, 12, "#EB6084",
"#FFFFFF")
if err != nil {
log.Fatalln(err)
}
if err := block.popup.todo(); err != nil {
log.Fatalln(err)
}
}
update: func() {},
func (bar *Bar) window() {
// Initialize blocks.
bar.initBlock("windowIcon", "¶", 21, 'l', 12, "#37BF8D", "#FFFFFF")
block := bar.initBlock("window", "?", 200, 'c', 0, "#37BF8D", "#FFFFFF")
actions: map[xproto.Button]func() error{
1: func() error {
return ewmh.CurrentDesktopReq(X, 0)
},
4: func() error {
return ewmh.CurrentDesktopReq(X, bar.store["prev"].(int))
},
5: func() error {
return ewmh.CurrentDesktopReq(X, bar.store["next"].(int))
},
},
})
// Notify that the next block can be initialized.
bar.ready <- true
bar.blocks.Set("workspace-2", &Block{
txt: "2",
w: workspaceWidth,
align: 'l',
xoff: 12,
bg: xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF},
fg: xgraphics.BGRA{B: 255, G: 255, R: 255, A: 0xFF},
// 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(X, "_NET_ACTIVE_WINDOW")
if err != nil {
log.Println(err)
}
if ev.Atom != atom {
return
}
update: func() {},
// Get active window.
id, err := ewmh.ActiveWindowGet(X)
if err != nil {
log.Println(err)
}
if id == 0 {
return
}
actions: map[xproto.Button]func() error{
1: func() error {
return ewmh.CurrentDesktopReq(X, 1)
},
4: func() error {
return ewmh.CurrentDesktopReq(X, bar.store["prev"].(int))
},
5: func() error {
return ewmh.CurrentDesktopReq(X, bar.store["next"].(int))
},
},
})
// Compose block text.
txt, err := ewmh.WmNameGet(X, id)
if err != nil || len(txt) == 0 {
txt, err = icccm.WmNameGet(X, id)
if err != nil || len(txt) == 0 {
txt = "?"
bar.blocks.Set("workspace-3", &Block{
txt: "3",
w: workspaceWidth,
align: 'l',
xoff: 12,
bg: xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF},
fg: xgraphics.BGRA{B: 255, G: 255, R: 255, A: 0xFF},
update: func() {},
actions: map[xproto.Button]func() error{
1: func() error {
return ewmh.CurrentDesktopReq(X, 2)
},
4: func() error {
return ewmh.CurrentDesktopReq(X, bar.store["prev"].(int))
},
5: func() error {
return ewmh.CurrentDesktopReq(X, bar.store["next"].(int))
},
},
})
bar.blocks.Set("workspace", &Block{
script: true,
update: func() {
one := bar.block("workspace-1")
two := bar.block("workspace-2")
three := bar.block("workspace-3")
f := func() {
// Get the current active desktop.
wsp, err := ewmh.CurrentDesktopGet(X)
if err != nil {
log.Println(err)
return
}
// KAYOS FIX THIS MAKE IT A CONFIG
var chosenbg xgraphics.BGRA = xgraphics.BGRA{B: 255, G: 255, R: 255, A: 0xFF}
var chosenfg xgraphics.BGRA = xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF}
// Set new block colors and the previous/next workspaces.
switch wsp {
case 0:
one.bg = chosenbg
one.fg = chosenfg
two.bg = xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF}
three.bg = xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF}
bar.store["prev"] = 2
bar.store["next"] = 1
case 1:
one.bg = xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF}
two.bg = chosenbg
two.fg = chosenfg
three.bg = xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF}
bar.store["prev"] = 0
bar.store["next"] = 2
case 2:
one.bg = xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF}
two.bg = xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF}
three.bg =chosenbg
three.fg =chosenfg
bar.store["prev"] = 1
bar.store["next"] = 0
}
// Redraw block.
bar.redraw <- one
bar.redraw <- two
bar.redraw <- three
}
}
txt = trim(txt, 34)
// Redraw block.
if block.diff(txt) {
bar.redraw <- block
}
}).Connect(X, X.RootWin())
// Listen for workspace change event, execute `f()` accordingly.
xevent.PropertyNotifyFun(func(_ *xgbutil.XUtil, ev xevent.
PropertyNotifyEvent) {
// Only listen to `_NET_ACTIVE_WINDOW` events.
atom, err := xprop.Atm(X, "_NET_CURRENT_DESKTOP")
if err != nil {
log.Println(err)
return
}
if ev.Atom != atom {
return
}
f()
}).Connect(X, X.RootWin())
// Execute `f()` one time initially.
f()
},
})
bar.blocks.Set("clock", &Block{
txt: "?",
w: 799,
align: 'a',
xoff: 0,
bg: xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF},
fg: xgraphics.BGRA{B: 255, G: 247, R: 91, A: 0xFF},
update: func() {
for {
block := bar.block("clock")
// Set new block text.
block.txt = time.Now().Format("Monday, January 2th 03:04 PM")
// Redraw block.
bar.redraw <- block
// Update every 45 seconds.
time.Sleep(45 * time.Second)
}
},
/* actions: map[xproto.Button]func() error{
1: func() error {
return bar.drawPopup("clock")
},
},*/
})
/* bar.blocks.Set("music", &Block{
txt: " Ƅ ",
w: 660,
align: 'r',
xoff: -12,
bg: xgraphics.BGRA{B: 91, G: 79, R: 60, A: 0xFF},
fg: xgraphics.BGRA{B: 204, G: 204, R: 204, A: 0xFF},
update: func() {
block := bar.block("music")
popup := bar.popup("music")
// Connect to MPD.
var err error
bar.store["mpd"], err = mpd.Dial("tcp", ":6600")
if err != nil {
log.Fatalln(err)
}
// Keep connection alive by pinging ever 45 seconds.
go func() {
for {
time.Sleep(time.Second * 45)
if err := bar.store["mpd"].(*mpd.Client).
Ping(); err != nil {
bar.store["mpd"], err = mpd.Dial("tcp", ":6600")
if err != nil {
log.Fatalln(err)
}
}
}
}()
// Watch MPD for events.
w, err := mpd.NewWatcher("tcp", ":6600", "", "player")
if err != nil {
log.Fatalln(err)
}
for {
cur, err := bar.store["mpd"].(*mpd.Client).CurrentSong()
if err != nil {
log.Println(err)
return
}
sts, err := bar.store["mpd"].(*mpd.Client).Status()
if err != nil {
log.Println(err)
return
}
// Set new block text.
var s string
if sts["state"] == "pause" {
s = "[paused] "
}
block.txt = " Ƅ " + s + cur["Artist"] + " - " + cur["Title"]
// Redraw block.
bar.redraw <- block
// Update popup if open.
if popup.open {
popup.update()
}
// Wait for next event.
<-w.Event
}
},*/
/* actions: map[xproto.Button]func() error{
1: func() error {
return bar.drawPopup("music")
},
3: func() error {
s, err := bar.store["mpd"].(*mpd.Client).Status()
if err != nil {
return err
}
return bar.store["mpd"].(*mpd.Client).
Pause(s["state"] != "pause")
},
4: func() error {
return bar.store["mpd"].(*mpd.Client).Previous()
},
5: func() error {
return bar.store["mpd"].(*mpd.Client).Next()
},
},
})*/
bar.blocks.Set("todo", &Block{
txt: "ƅ",
w: bar.h,
align: 'c',
xoff: 1,
bg: xgraphics.BGRA{B: 0, G: 0, R: 0, A: 0xFF},
fg: xgraphics.BGRA{B: 255, G: 255, R: 255, A: 0xFF},
update: func() {},
actions: map[xproto.Button]func() error{
1: func() error {
u, err := user.Current()
if err != nil {
return err
}
cmd := exec.Command("st", "nano", "-savecursor", "false", path.
Join(u.HomeDir, ".todo"))
cmd.Stdout = os.Stdout
return cmd.Start()
},
},
})
}
func (bar *Bar) workspace() {
// 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")
// Notify that the next block can be initialized.
bar.ready <- true
// Change active workspace on clicking on one of the blocks.
blockWWW.actions["button1"] = func() error {
return ewmh.CurrentDesktopReq(X, 0)
}
blockIRC.actions["button1"] = func() error {
return ewmh.CurrentDesktopReq(X, 1)
}
blockSRC.actions["button1"] = func() error {
return ewmh.CurrentDesktopReq(X, 2)
}
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(X, "_NET_CURRENT_DESKTOP")
if err != nil {
log.Println(err)
}
if ev.Atom != atom {
return
}
// Get the current active desktop.
wsp, err := ewmh.CurrentDesktopGet(X)
if err != nil {
log.Println(err)
}
// Set colors accordingly.
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 {
bar.redraw <- blockWWW
bar.redraw <- blockIRC
bar.redraw <- blockSRC
owsp = wsp
}
}).Connect(X, X.RootWin())
prevFun := func() error {
return ewmh.CurrentDesktopReq(X, pwsp)
}
nextFun := func() error {
return ewmh.CurrentDesktopReq(X, nwsp)
}
blockWWW.actions["button4"] = prevFun
blockWWW.actions["button5"] = nextFun
blockIRC.actions["button4"] = prevFun
blockIRC.actions["button5"] = nextFun
blockSRC.actions["button4"] = prevFun
blockSRC.actions["button5"] = nextFun
}

31
main.go
View File

@ -2,20 +2,21 @@ package main
import (
"log"
"embed"
"github.com/AndreKR/multiface"
"github.com/BurntSushi/xgbutil"
"github.com/gobuffalo/packr/v2"
"golang.org/x/image/font"
)
var (
box = packr.New("box", "./box")
//go:embed runtime/*
runtime embed.FS
// Connection to the X server.
X *xgbutil.XUtil
// The font that should be used.
face font.Face
// The multifont face that should be used.
face *multiface.Face
)
func main() {
@ -24,25 +25,23 @@ func main() {
log.Fatalln(err)
}
// Initialize font.
if err := initFont(); err != nil {
// Initialize font face.
if err := initFace(); err != nil {
log.Fatalln(err)
}
// Initialize bar.
bar, err := initBar(0, 0, 1920, 29)
bar, err := initBar(0, 965, 1366, 29)
if err != nil {
log.Fatalln(err)
}
// Initialize blocks.
go bar.initBlocks([]func(){
bar.window,
bar.workspace,
bar.clock,
bar.music,
bar.todo,
})
// Initialize blocks and popups.
bar.initBlocks()
//bar.initPopups()
// Draw blocks.
go bar.drawBlocks()
// Listen for redraw events.
bar.listen()

View File

@ -15,79 +15,86 @@ type Popup struct {
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
// The position, width and height of the popup.
x, y, w, h int
// Text drawer.
drawer *font.Drawer
// If the popup is currently open or not.
open bool
// The fuction that updates the block, this will be executes as a goroutine.
update func()
}
func initPopup(x, y, w, h int, bg, fg string) (*Popup, error) {
popup := new(Popup)
var err error
func (bar *Bar) drawPopup(key string) error {
popup := bar.popup(key)
// Create a window for the bar. This window listens to button press events
// in order to respond to them.
// If the popup is already open, we destroy it.
if popup.open {
popup.destroy()
return nil
}
// Create a window for the popup. This window listens to button press
// events in order to respond to them.
var err error
popup.win, err = xwindow.Generate(X)
if err != nil {
return nil, err
return err
}
popup.win.Create(X.RootWin(), x, y, w, h, xproto.CwBackPixel|xproto.
CwEventMask, 0x000000, xproto.EventMaskButtonPress)
popup.win.Create(X.RootWin(), popup.x, popup.y, popup.w, popup.h, xproto.
CwBackPixel|xproto.CwEventMask, 0x000000, xproto.EventMaskButtonPress)
// EWMH stuff.
if err := initEWMH(popup.win.Id); err != nil {
return nil, err
return err
}
// Map window.
popup.win.Map()
// TODO: Moving the window is again a hack to keep OpenBox happy.
popup.win.Move(x, y)
// XXX: Moving the window is again a hack to keep OpenBox happy.
popup.win.Move(popup.x, popup.y)
// Create the bar image.
popup.img = xgraphics.New(X, image.Rect(0, 0, w, h))
// Create the popup image.
popup.img = xgraphics.New(X, image.Rect(0, 0, popup.w, popup.h))
if err := popup.img.XSurfaceSet(popup.win.Id); err != nil {
panic(err)
}
popup.img.XDraw()
popup.w = w
popup.h = h
popup.bg = bg
popup.fg = fg
// Set popup font face.
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)
})
// Run update function.
popup.update()
// Draw the popup.
popup.draw()
return nil
}
return popup, nil
func (bar *Bar) popup(key string) *Popup {
i, _ := bar.popups.Get(key)
return i.(*Popup)
}
func (popup *Popup) draw() {
popup.img.XDraw()
popup.img.XPaint(popup.win.Id)
// Set popup status to open.
popup.open = true
}
// TODO: I don't know if this actually frees memory and shit.
func (popup *Popup) destroy() *Popup {
func (popup *Popup) destroy() {
popup.win.Destroy()
popup.img.Destroy()
popup = nil
return popup
// Set popup status to closed.
popup.open = false
}

600
popups.go
View File

@ -1,272 +1,398 @@
package main
import (
"bufio"
"bytes"
"fmt"
"image"
"io"
"io/ioutil"
"log"
"math"
"net/http"
"os"
"os/exec"
"path"
"strconv"
"time"
// "image"
// "log"
// "math"
// "os"
// "path"
// "strconv"
// "time"
"github.com/BurntSushi/xgbutil/xgraphics"
"github.com/fhs/gompd/mpd"
"github.com/fsnotify/fsnotify"
"github.com/rkoesters/xdg/basedir"
"github.com/rkoesters/xdg/userdirs"
"github.com/thedevsaddam/gojsonq"
"golang.org/x/image/math/fixed"
// "github.com/BurntSushi/xgbutil/xgraphics"
// "github.com/IvanMenshykov/MoonPhase"
// "github.com/RadhiFadlillah/go-prayer"
// "github.com/elliotchance/orderedmap"
// "github.com/fhs/gompd/mpd"
// "github.com/rkoesters/xdg/userdirs"
// "golang.org/x/image/math/fixed"
)
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()
func (bar *Bar) initPopups() {
/*bar.popups.Set("clock", &Popup{
x: (bar.w / 2) - (184 / 2),
y: bar.h,
w: 184,
h: 118,
// Draw the background.
bg, _, err := image.Decode(f.(io.Reader))
if err != nil {
return err
}
xgraphics.Blend(popup.img, xgraphics.NewConvert(X, bg), image.Point{0, 0})
update: func() {
popup := bar.popup("clock")
// Redraw the popup.
popup.draw()
// Color the background.
popup.img.For(func(cx, cy int) xgraphics.BGRA {
return xgraphics.BGRA{B: 238, G: 238, R: 238, A: 0xFF}
})
// Creat http client with a timeout.
c := &http.Client{Timeout: time.Duration(2 * time.Second)}
// Set foreground color.
popup.drawer.Src = image.NewUniform(xgraphics.BGRA{B: 33, G: 27,
R: 2, A: 0xFF})
// Get weather information.
r, err := c.Get("https://api.buienradar.nl/data/public/2.0/jsonfeed")
if err != nil {
return err
}
defer r.Body.Close()
jq := gojsonq.New().Reader(r.Body).From("actual.stationmeasurements.[0]").
WhereEqual("stationid", "6260")
// Get the current time. Present Day, heh... Present Time! Hahahaha!
n := time.Now()
// Draw weather information.
popup.drawer.Src = image.NewUniform(hexToBGRA(popup.fg))
popup.drawer.Dot = fixed.P(11, 101)
popup.drawer.DrawString("Rainfall graph, it's " + fmt.Sprint(jq.Find(
"feeltemperature")) + "°C.")
// Get moon phase.
/* mc := MoonPhase.New(n)
mn := map[int]string{
0: "Ɔ",
1: "Ƈ",
2: "ƈ",
3: "Ɖ",
4: "Ɗ",
5: "Ƌ",
6: "ƌ",
7: "ƍ",
8: "Ƈ",
}
// Redraw the popup.
popup.draw()
// Draw moon text.
popup.drawer.Dot = fixed.P(19, 48)
popup.drawer.DrawString("The moon currently looks like: " + mn[int(
math.Floor((mc.Phase()+0.0625)*8))])
// Set location.
lat := "52.0646"
lon := "5.2065"
// Get the prayers.
pm := (&prayer.Calculator{
Latitude: 52.1277,
Longitude: 5.6686,
Elevation: 21,
CalculationMethod: prayer.MWL,
AsrConvention: prayer.Hanafi,
PreciseToSeconds: false,
}).Init().SetDate(n).Calculate()
// Get rainfall information.
r, err = c.Get("https://gpsgadget.buienradar.nl/data/raintext?lat=" + lat +
"&lon=" + lon)
if err != nil {
return err
}
defer r.Body.Close()
// The prayers we want to track.
pom := orderedmap.NewOrderedMap()
pom.Set("Fajr", pm[prayer.Fajr])
pom.Set("Zuhr", pm[prayer.Zuhr])
pom.Set("Asr", pm[prayer.Asr])
pom.Set("Maghrib", pm[prayer.Maghrib])
pom.Set("Isha", pm[prayer.Isha])
// 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())
// Calculate the dot lenght, this is the length of the line (164px)
// divided by 2400 (minutes in a day).
d := 164.00 / 2400.00
// 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")...)
}
// Calculate elapsed line length.
e := int(math.Round(d*float64(n.Hour()*100+n.Minute()))) + 10
// Write rainfall data tmp file.
if _, err = td.Write(d); err != nil {
return err
}
if err := td.Close(); err != nil {
return err
}
// Draw line.
popup.img.SubImage(image.Rect(10, 101, 10+164, 102)).(*xgraphics.
Image).For(func(x, y int) xgraphics.BGRA {
// Make the line look dashed.
if x%5 == 4 {
return xgraphics.BGRA{B: 238, G: 238, R: 238, A: 0xFF}
}
// 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
}
if x < e {
return xgraphics.BGRA{B: 33, G: 27, R: 2, A: 0xFF}
}
return xgraphics.BGRA{B: 211, G: 167, R: 114, A: 0xFF}
})
// 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})
// Loop over these prayers and draw stuff for each one.
tm := false
np := false
for p := pom.Front(); p != nil; p = p.Next() {
k := p.Key.(string)
v := p.Value.(time.Time)
// Redraw the popup.
popup.draw()
// Calculate arrow position.
pd := int(math.Round(d*float64(v.Hour()*100+v.Minute()))) + 9
return nil
}
// Set arrow color.
popup.drawer.Src = image.NewUniform(xgraphics.BGRA{B: 211,
G: 167, R: 114, A: 0xFF})
// TODO: Make progressbar clickable.
// TODO: Make progressbar update every X milliseconds.
func (popup *Popup) music(c *mpd.Client) error {
// Color the background.
popup.img.For(func(cx, cy int) xgraphics.BGRA {
return hexToBGRA(popup.bg)
if tm || (!np && v.Unix() > n.Add(-time.Hour).Unix()) {
np = true
// Set arrow color for next prayer.
popup.drawer.Src = image.NewUniform(xgraphics.BGRA{B: 33,
G: 27, R: 2, A: 0xFF})
// Compose arrow text.
s := k + ", " + v.Format("03:04 PM")
// Calculate X offset, we use some smart logic in order to
// always have nice padding, even with longer strings.
sl := popup.drawer.MeasureString(s).Round()
x := pd + 2 - (sl / 2)
if x < 10 {
x = 10
} else if x > 184-8-sl {
x = 184 - 8 - sl
}
// Draw arrow text.
popup.drawer.Dot = fixed.P(x, 85)
popup.drawer.DrawString(s)
}
// Workaround if the next prayer is tomorrow.
if p.Next() == nil && !np {
tm = true
pom.Delete("Fajr")
pom.Set("Fajr", pm[prayer.Fajr])
}
// Draw arrow.
popup.drawer.Dot = fixed.P(pd+2, 98)
popup.drawer.DrawString("↓")
}
// Redraw the popup.
popup.draw()
},
})
cur, err := c.CurrentSong()
if err != nil {
return err
}
sts, err := c.Status()
if err != nil {
return err
}
bar.popups.Set("music", &Popup{
x: bar.w - 327 - bar.h,
y: bar.h,
w: 327,
h: 148,
// Set text color.
popup.drawer.Src = image.NewUniform(hexToBGRA(popup.fg))
update: func() {
popup := bar.popup("music")
// 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)
cur, err := bar.store["mpd"].(*mpd.Client).CurrentSong()
if err != nil {
log.Println(err)
return
}
sts, err := bar.store["mpd"].(*mpd.Client).Status()
if err != nil {
log.Println(err)
return
}
// 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)
// Color the background.
popup.img.For(func(cx, cy int) xgraphics.BGRA {
return xgraphics.BGRA{B: 238, G: 238, R: 238, A: 0xFF}
})
// 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)
// Set foreground color.
popup.drawer.Src = image.NewUniform(xgraphics.BGRA{B: 33, G: 27,
R: 2, A: 0xFF})
// 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 text.
album := trim(cur["Album"], 32)
popup.drawer.Dot = fixed.P(-(popup.drawer.MeasureString(album).
Ceil()/2)+90, 48)
popup.drawer.DrawString(album)
// 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!")
}
// Draw artist text.
artist := trim("Artist: "+cur["AlbumArtist"], 32)
popup.drawer.Dot = fixed.P(-(popup.drawer.MeasureString(artist).
Ceil()/2)+90, 58+16)
popup.drawer.DrawString(artist)
// Calculate progressbar lengths.
e, err := strconv.ParseFloat(sts["elapsed"], 32)
if err != nil {
return err
}
t, err := strconv.ParseFloat(sts["duration"], 32)
if err != nil {
return err
}
pf := int(math.Round(e / t * 29))
pu := 29 - pf
// Draw rlease date text.
date := trim("Release date: "+cur["Date"], 32)
popup.drawer.Dot = fixed.P(-(popup.drawer.MeasureString(date).
Ceil()/2)+90, 58+16+16)
popup.drawer.DrawString(date)
// Draw progressbar.
popup.drawer.Dot = fixed.P(10, 132)
for i := 1; i <= pf; i++ {
popup.drawer.DrawString("-")
}
popup.drawer.Src = image.NewUniform(hexToBGRA("#72A7D3"))
for i := 1; i <= pu; i++ {
popup.drawer.DrawString("-")
}
// Check if the cover 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 {
log.Println(err)
return
}
defer f.Close()
// Redraw the popup.
popup.draw()
// Draw cover art.
img, _, err := image.Decode(f)
if err != nil {
log.Println(err)
return
}
xgraphics.Blend(popup.img, xgraphics.NewConvert(X, img), image.
Point{-179, -0})
} else {
popup.drawer.Dot = fixed.P(218, 78)
popup.drawer.DrawString("No cover found!")
}
return nil
}
func (popup *Popup) todo() error {
// Watch file for events.
w, err := fsnotify.NewWatcher()
if err != nil {
return err
}
if err := w.Add(path.Join(basedir.Home, ".todo")); err != nil {
return err
}
f, err := os.Open(path.Join(basedir.Home, ".todo"))
if err != nil {
return err
}
// Set text color.
popup.drawer.Src = image.NewUniform(hexToBGRA(popup.fg))
for {
// Count file lines.
s := bufio.NewScanner(f)
var c int
for s.Scan() {
c++
}
// Rewind file.
if _, err := f.Seek(0, 0); err != nil {
log.Println(err)
}
// Color the background.
popup.img.For(func(cx, cy int) xgraphics.BGRA {
return hexToBGRA(popup.bg)
})
// Draw text.
popup.drawer.Dot = fixed.P(5, 11)
popup.drawer.DrawString(strconv.Itoa(c))
// Redraw block.
popup.draw()
// Listen for next write event.
ev := <-w.Events
if ev.Op&fsnotify.Write != fsnotify.Write {
continue
}
}
// Get elapsed and duration times.
se, err := strconv.ParseFloat(sts["elapsed"], 32)
if err != nil {
log.Println(err)
return
}
//e := int(math.Round(se))
// Calculate the dot lenght, this is the length of the line divided
// by the length of the song.
sd, err := strconv.ParseFloat(sts["duration"], 32)
if err != nil {
log.Println(err)
return
}
d := 159.00 / sd
// Calculate elapsed line length.
e := int(math.Round(d*se)) + 10
// Draw line.
popup.img.SubImage(image.Rect(10, 131, 10+159, 132)).(*xgraphics.
Image).For(func(x, y int) xgraphics.BGRA {
// Make the line look dashed.
if x%5 == 4 {
return xgraphics.BGRA{B: 238, G: 238, R: 238, A: 0xFF}
}
if x < e {
return xgraphics.BGRA{B: 33, G: 27, R: 2, A: 0xFF}
}
return xgraphics.BGRA{B: 211, G: 167, R: 114, A: 0xFF}
})
// Redraw the popup.
popup.draw()
},
})
bar.popups.Set("clock", &Popup{
x: (bar.w / 2) - (178 / 2),
y: bar.h,
w: 178,
h: 129,
update: func() {
popup := bar.popup("clock")
// Color the background.
f, err := runtime.Open("/images/clock-popup-bg.png")
if err != nil {
log.Println(err)
return
}
defer f.Close()
bg, _, err := image.Decode(f.(io.Reader))
if err != nil {
log.Println(err)
return
}
xgraphics.Blend(popup.img, xgraphics.NewConvert(X, bg), image.Point{
0, 0})
// Redraw the popup.
popup.draw()
// Set foreground color.
popup.drawer.Src = image.NewUniform(xgraphics.BGRA{B: 33, G: 27,
R: 2, A: 0xFF})
// Create http client with a timeout.
/*c := &http.Client{Timeout: time.Duration(2 * time.Second)}
// Get weather information.
r, err := c.Get(
"https://api.buienradar.nl/data/public/2.0/jsonfeed")
if err != nil {
log.Println(err)
return
}
defer r.Body.Close()
jq := gojsonq.New().Reader(r.Body).From(
"actual.stationmeasurements.[0]").WhereEqual("stationid",
"6260")
// Draw weather information.
popup.drawer.Dot = fixed.P(11, 101)
popup.drawer.DrawString("Rainfall graph, it's " + fmt.Sprint(jq.
Find("feeltemperature")) + "°C.")
// Redraw the popup.
popup.draw()
// Set location.
lat := "52.0646"
lon := "5.2065"
// Get rainfall information.
r, err = c.Get(
"https://gpsgadget.buienradar.nl/data/raintext?lat=" + lat +
"&lon=" + lon)
if err != nil {
log.Println(err)
return
}
defer r.Body.Close()
// Create rainfall tmp files.
td, err := ioutil.TempFile(os.TempDir(), "melonbar-rain-*.dat")
if err != nil {
log.Println(err)
return
}
defer os.Remove(td.Name())
ti, err := ioutil.TempFile(os.TempDir(), "melonbar-rain-*.png")
if err != nil {
log.Println(err)
return
}
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 {
log.Println(err)
return
}
if err := td.Close(); err != nil {
log.Println(err)
return
}
// Create rainfall graph.
cmd := exec.Command("gnuplot", "-e", `
set terminal png transparent size 205,107;
set output '`+ti.Name()+`';
set yrange [0:255];
set noborder;
set nolabel;
set nokey;
set notics;
set notitle;
plot '`+td.Name()+`' smooth csplines w filledcu x1 fc rgb '#72A7D3', '`+td.Name()+`' smooth csplines w line lc rgb '#5394C9';
`)
if err := cmd.Run(); err != nil {
log.Println(err)
return
}
// Draw rainfall graph.
img, _, err := image.Decode(ti)
if err != nil {
log.Println(err)
return
}
xgraphics.Blend(popup.img, xgraphics.NewConvert(X, img), image.
Point{11, 2})
// Redraw the popup.
popup.draw()
},
})*/
}

45
util.go
View File

@ -1,19 +1,5 @@
package main
import (
"encoding/hex"
"strings"
"github.com/BurntSushi/xgbutil/xgraphics"
)
func hexToBGRA(h string) xgraphics.BGRA {
h = strings.Replace(h, "#", "", 1)
d, _ := hex.DecodeString(h)
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 {
@ -21,3 +7,34 @@ func trim(txt string, l int) string {
}
return txt
}
type Hex string
type RGB struct {
Red uint8
Green uint8
Blue uint8
}
func (h Hex) toRGB() (RGB, error) {
return Hex2RGB(h)
}
func Hex2RGB(hex Hex) (RGB, error) {
var rgb RGB
values, err := strconv.ParseUint(string(hex), 16, 32)
if err != nil {
return RGB{}, err
}
rgb = RGB{
Red: uint8(values >> 16),
Green: uint8((values >> 8) & 0xFF),
Blue: uint8(values & 0xFF),
}
return rgb, nil
}

39
x.go
View File

@ -1,17 +1,23 @@
package main
import (
"path"
"io/ioutil"
"log"
"github.com/AndreKR/multiface"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/ewmh"
"github.com/BurntSushi/xgbutil/xevent"
"github.com/BurntSushi/xgbutil/xwindow"
"golang.org/x/image/font/plan9font"
"github.com/zachomedia/go-bdf"
)
func initX() error {
// Disable logging messages.
xgb.Logger = log.New(ioutil.Discard, "", 0)
// Set up a connection to the X server.
var err error
X, err = xgbutil.NewConn()
@ -45,17 +51,26 @@ func initEWMH(w xproto.Window) error {
return ewmh.WmNameSet(X, w, "melonbar")
}
func initFont() error {
fr := func(name string) ([]byte, error) {
return box.Find(path.Join("fonts", name))
func initFace() error {
face = new(multiface.Face)
fpl := []string{
"runtime/fonts/cure.punpun.bdf",
"runtime/fonts/kochi.small.bdf",
"runtime/fonts/baekmuk.small.bdf",
}
fp, err := box.Find("fonts/cure.font")
if err != nil {
return err
}
face, err = plan9font.ParseFont(fp, fr)
if err != nil {
return err
for _, fp := range fpl {
fb, err := runtime.ReadFile(fp)
if err != nil {
return err
}
ff, err := bdf.Parse(fb)
if err != nil {
return err
}
face.AddFace(ff.NewFace())
}
return nil