Author: Kevin Schoon [kevinschoon@gmail.com]
Hash: 85c4f20f8c71d4aa2971ca7b09b7d2d7ea1218f5
Timestamp: Sun, 21 Jan 2018 10:35:03 +0000 (6 years ago)

+454 -21 +/-8 browse
refactor run loop, add simple libnotify support, improve notifications, binpack tomato image
1diff --git a/Makefile b/Makefile
2new file mode 100644
3index 0000000..90f1f99
4--- /dev/null
5+++ b/Makefile
6 @@ -0,0 +1,15 @@
7+
8+ .PHONY: \
9+ all
10+
11+ all: bin/pomo
12+
13+ clean:
14+ rm -v bin/pomo bindata.go
15+
16+ bindata.go:
17+ go-bindata -pkg main -o $@ tomato-icon.png
18+
19+ bin/pomo: bindata.go
20+ mkdir bin 2>/dev/null
21+ go build -o bin/pomo
22 diff --git a/bindata.go b/bindata.go
23new file mode 100644
24index 0000000..8f4bcd9
25--- /dev/null
26+++ b/bindata.go
27 @@ -0,0 +1,235 @@
28+ // Code generated by go-bindata.
29+ // sources:
30+ // tomato-icon.png
31+ // DO NOT EDIT!
32+
33+ package main
34+
35+ import (
36+ "bytes"
37+ "compress/gzip"
38+ "fmt"
39+ "io"
40+ "io/ioutil"
41+ "os"
42+ "path/filepath"
43+ "strings"
44+ "time"
45+ )
46+
47+ func bindataRead(data []byte, name string) ([]byte, error) {
48+ gz, err := gzip.NewReader(bytes.NewBuffer(data))
49+ if err != nil {
50+ return nil, fmt.Errorf("Read %q: %v", name, err)
51+ }
52+
53+ var buf bytes.Buffer
54+ _, err = io.Copy(&buf, gz)
55+ clErr := gz.Close()
56+
57+ if err != nil {
58+ return nil, fmt.Errorf("Read %q: %v", name, err)
59+ }
60+ if clErr != nil {
61+ return nil, err
62+ }
63+
64+ return buf.Bytes(), nil
65+ }
66+
67+ type asset struct {
68+ bytes []byte
69+ info os.FileInfo
70+ }
71+
72+ type bindataFileInfo struct {
73+ name string
74+ size int64
75+ mode os.FileMode
76+ modTime time.Time
77+ }
78+
79+ func (fi bindataFileInfo) Name() string {
80+ return fi.name
81+ }
82+ func (fi bindataFileInfo) Size() int64 {
83+ return fi.size
84+ }
85+ func (fi bindataFileInfo) Mode() os.FileMode {
86+ return fi.mode
87+ }
88+ func (fi bindataFileInfo) ModTime() time.Time {
89+ return fi.modTime
90+ }
91+ func (fi bindataFileInfo) IsDir() bool {
92+ return false
93+ }
94+ func (fi bindataFileInfo) Sys() interface{} {
95+ return nil
96+ }
97+
98+ var _tomatoIconPng = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x00\x5c\x0f\xa3\xf0\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x30\x00\x00\x00\x2b\x08\x06\x00\x00\x00\x3e\x13\x0b\xdf\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x21\x38\x00\x00\x21\x38\x01\x45\x96\x31\x60\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe2\x01\x15\x08\x10\x11\xe3\x9e\xfd\x2f\x00\x00\x0e\xe9\x49\x44\x41\x54\x68\xde\xbd\x99\x7b\xb0\x5f\x55\x75\xc7\x3f\x6b\xef\xf3\xf8\x3d\xee\xcd\xcd\xbd\x24\x04\x12\xc2\x0d\x24\x24\x40\x54\x10\x82\x48\x40\x8c\x3c\x1a\xc1\x57\xcb\x8c\xd8\x8e\x56\xb1\x6a\x47\xc4\x57\xa1\x0e\x95\x69\xad\x45\x6a\xd5\xce\x50\xc5\x0a\x4c\x61\x46\x45\xab\xad\x45\xda\x29\x1d\x6d\xb4\xf1\x0f\xd4\x0a\x02\xa6\x3c\x42\x0c\x90\xc7\x0d\x49\x48\xb8\x79\xdd\xdc\xdf\xeb\x3c\xf6\x5e\xfd\x63\x9f\xdf\xbd\x37\x0f\x2c\x81\xb6\x67\xe6\xcc\x39\x73\xce\xde\xfb\xac\xef\x7a\x7c\xd7\x5a\xfb\xc0\xcb\x38\xde\xf9\xe1\xf3\xb8\xfe\xf3\x97\xcb\xe1\xcf\xef\xbe\xff\xa3\xe7\xf2\xff\x7c\x98\x97\x33\xe9\xf4\xe5\xc7\x9f\x76\xeb\x4d\x3f\xd6\x99\xcf\xee\xfc\x97\x3f\x34\xe0\xdf\x75\xf8\xd8\xb3\x2e\x58\xf4\x92\xd6\xfc\xe2\x5d\x57\xfd\xdf\x03\xb8\xe5\x8e\x77\xbc\xfa\x73\x77\xbe\xfd\xc2\xa7\x37\xec\xd9\xf4\x27\xb7\xbe\xf9\xd0\x97\x4e\x4f\x2c\x8b\xe2\x75\x33\x1f\x5d\x7c\xe5\x19\x3c\xf6\x8b\xad\x9c\x7f\xc9\xd2\x5b\x56\xbd\x75\xf9\xd6\x37\xbe\xfd\xcc\xe3\x01\x3e\xfd\x57\x57\x02\xf0\xf9\xdb\x7f\xc7\xdc\x72\xfb\x6f\x37\xbe\xf8\x77\x57\x99\x1b\x3f\x74\xdf\x51\xbf\x79\xdd\x9f\xad\xb2\xbf\x49\x26\x79\xa9\xc2\x7f\xe5\xdb\xef\xe2\xc0\xfe\xee\x95\xdb\x77\x1c\x58\x70\xd7\x17\x1e\xb8\xeb\x9a\x1b\x56\x32\x7f\xeb\x04\x9f\xff\xfe\x7a\x00\xbe\xfc\xed\xf7\xcd\x01\xc6\xbd\xf3\x0b\xaf\x7f\xdf\xb7\xb6\xf7\xe7\x5d\xf4\xe6\xd3\x3f\x10\x27\xe6\xee\xa4\x66\x37\xad\xf9\xde\x13\x4b\xfa\xcf\x6f\xfa\xc2\x95\x75\x1b\x9b\xd1\xcf\xdd\xf0\x6f\xbf\x3e\xda\xf7\x3e\x70\xc3\x1b\x68\x34\x92\x18\xc1\x7c\xf5\xe6\xb5\xd9\x2b\x06\xd0\x3f\x6e\xf8\xdc\xe5\xef\x9f\x68\xf5\xce\xb9\xfb\x8b\x3f\xfd\x18\xc0\x37\x41\x46\x41\x57\x01\xb7\x7c\xed\xea\xcd\xde\xf9\x9d\x9f\xf9\xf8\xbd\x17\x01\x5c\x79\xd5\xab\x86\x34\x36\x9b\xe3\xd4\x8e\x44\xc2\x79\xf7\x7d\x73\xdd\x23\x00\x9f\xf8\xf4\xa5\x57\xa7\xa9\xdd\xf3\xa5\xcf\xfe\xe8\x27\x3b\x41\x9e\x04\x3d\x01\x78\x4d\xf5\x8d\x3b\x2e\x3d\x95\x6b\xd7\x6e\xe6\x83\x9f\xba\x78\x71\x6c\xec\x58\x5c\x33\xe5\x6d\x7f\xb1\xf6\xe5\x03\x78\x16\x58\x02\xdc\xfe\xee\x73\xf9\xc8\xdf\x3f\xca\x87\x6f\x5c\xf5\xd5\xdc\x95\xe7\x0d\xb5\xf3\x2b\x6f\xbd\xe3\x97\xfb\x5f\x58\x7e\x72\x74\xfc\xfa\x6d\xee\xcf\xff\xf4\x8a\xd7\xb7\x2d\x3f\xf7\x5e\x9f\x59\xb0\xa3\x75\xfe\x83\xae\x7d\x95\x8f\xcc\xdd\xd6\xc8\xee\x8b\xc6\xf3\xa5\x1f\xff\xe1\xee\xce\x27\x7e\x7f\xf1\xf9\x79\x2d\xba\xf6\x8e\xbf\x7d\xe0\xbd\xe3\xe7\x2e\x4b\xf0\x28\xbe\x54\x2d\x32\xcf\xf8\x7e\x9d\x9c\x2c\x74\x49\x2f\xf7\xef\xbd\x6e\xe5\x1c\x63\xcd\xaa\x6f\xdc\xf6\xb3\x7b\x5f\x91\x0b\x3d\x0d\x2c\x05\x9e\x02\x99\x9d\x20\x66\xe9\x22\x99\xf7\x64\xce\x87\xaf\x3b\xe5\x33\x45\x5d\xae\x1f\x2c\xf4\xfa\x2f\x7f\x65\xc3\x77\x38\x7f\xc4\x72\xa0\xe7\x3f\xf0\xc6\xd1\x3b\x4a\x2f\xef\x51\xef\x77\x76\x6d\x9e\x02\xc7\x59\xe5\xf1\x9b\x9f\x9e\xbc\xec\x9e\x39\x03\xaf\xdf\xd4\x8c\xef\x3b\x2d\x2f\xdf\x72\xf3\xd8\xbe\x87\x11\x51\xac\xf7\xa8\x78\xd4\x78\x9e\xee\x79\xd9\xb3\xbd\xf7\xbb\xef\x7d\xdd\xb5\x02\xbb\xbe\x7b\xcf\x2f\xff\xd9\x01\x7b\x80\x79\xc7\x0a\x60\x0c\x58\x87\xe1\xf5\x78\xca\xe3\x1a\x26\x9e\x3b\x62\xa2\xfa\x40\x34\x62\x0b\xf3\x5c\xcf\xcc\x6e\x77\x38\xf5\xde\xd3\x9a\x5f\xda\xbc\xa4\x7e\x41\x33\x36\x0f\x5d\xf2\xd0\x44\xf7\xe4\xdd\x45\xbd\x1d\x61\x6f\x5b\x3e\xb0\xc2\xc5\x10\xcd\x16\x50\x18\xee\xfa\xbd\xef\x79\xb8\xb3\xee\x2b\x4b\xea\x17\x78\xd1\xe6\x35\x5b\x32\xd2\xd2\x33\x52\x82\xa8\xd2\xa9\xd9\xf1\x9d\x0d\xf3\xf8\xd8\x70\xf4\xd8\x84\x70\xe2\x85\xe3\xdd\x4f\xbe\x75\xdd\x96\x3d\xcf\xbf\xe6\x24\x6b\x77\xee\x76\xcf\xef\xe9\xf8\xb3\x81\xfd\xc0\xf0\xb1\x58\x60\x1b\x48\xed\xec\x53\x2c\x3e\x8e\x53\xa3\xf1\xbe\x82\x39\xbb\x7a\xe6\x26\x2f\xbc\x33\x86\xba\x15\xa2\x71\x8b\xdc\x73\x46\x9d\xc6\x69\x03\x0c\xf4\x3c\xbf\xf5\xab\x83\xfc\xa2\x9b\xf3\xc8\x29\x4d\x9a\x23\x36\x00\xe8\x79\x0e\x3e\x97\xf3\x42\x04\x83\x85\x52\x02\xef\x9e\x74\x14\x0b\xea\xac\x9b\x93\xb0\x6f\xbc\xc7\x3b\xc6\x32\x16\x64\x0a\x8e\xd2\x29\x7b\x53\xab\x1f\x5b\x31\xe2\x7e\x30\xde\xb5\x4e\x7d\xa7\x48\x9e\xdc\xee\x87\x41\x5f\x92\x05\x36\x01\xb5\xb9\x75\x13\x9f\x30\x3f\x9a\xbb\x70\x48\x1e\xdf\xd4\x3e\xbb\x5d\x72\x3d\xc2\xd5\xb1\x80\xb5\x50\xa0\x6c\x3a\xb1\xce\x9e\x0b\x47\x71\x93\x05\x3f\xd8\xb0\x03\xaf\x8a\x4b\x84\x46\xe6\x29\x13\x43\x63\x24\x22\xaa\x1b\xbc\x42\xb6\xcf\x91\xd6\x2c\xc9\x40\x00\x35\x99\x7b\x16\xef\xca\x58\xb5\x3b\x63\x45\xe6\x29\x10\x72\x85\x42\x21\xf7\x42\xee\xc1\x2b\x0f\xa7\x46\x6f\x5c\xb1\x32\x7b\xe0\x85\x47\x0d\xf1\xfa\x2d\x7e\x04\xf4\x39\x60\xe1\x8b\x01\xd8\x04\xd2\x9c\x9d\x4a\xf4\x9a\xb3\x63\xe6\xcf\x1f\xda\xbc\x61\xec\x8f\xcb\x4e\xf7\x53\x89\x78\x62\x03\xa9\x35\x48\x22\xfc\xe4\xcc\x59\x8c\xc4\x09\xe7\x3f\x33\xc1\x3f\xfa\x92\x35\x0d\xa1\x4f\xd8\x3a\xe3\x3a\x70\x42\x42\x3a\x68\xa9\xa5\x09\x07\x77\x75\x69\x8f\x67\x24\xc0\x15\x07\x4a\xce\x29\x94\x93\x44\xb0\x0a\x8a\xe2\x14\x4a\x85\xc2\x43\xee\xa1\xe7\x85\xdc\x81\x08\x37\x9e\xd6\x74\x5f\xc3\xb9\x2c\x5b\xbf\xc5\x2d\x9c\x61\x89\x29\x00\x7b\x80\x39\xc0\xce\xe1\xc4\xd4\xce\x59\x91\x74\x47\x97\x2c\xd8\xf1\xeb\xb1\xef\x49\xb7\x7d\x4e\xdc\x6b\x93\x88\x52\x4b\x0c\x71\x2d\xc6\x0d\x24\x34\xa2\x88\x24\xb6\x6c\x68\x75\x69\x95\x9e\x8e\x01\xa7\x50\xa8\x67\x20\x89\x88\x54\x69\xe7\x05\x59\x59\x82\x15\x4c\xcf\x31\x0b\x38\x4e\x84\x05\x6a\x49\xa3\x88\xae\x31\xf4\xbc\xc7\xbb\x92\x86\x73\x44\x4e\xf1\xaa\x53\x20\x32\x0f\x5d\x17\x80\x78\xe5\x6f\x2e\xde\xbc\xf1\xfa\x17\xce\x9c\x6f\xf7\x3f\xb5\xd3\x0d\x02\xf3\x81\x08\xe0\xd1\x4a\xf8\xf1\x54\x24\x7a\xd5\xab\xa3\xf6\xc9\x4b\x16\xee\x1a\xdb\xfd\xa3\xb4\x16\x2d\xb2\xed\x82\xd8\x28\xb5\x48\x88\x1b\x31\xf1\xec\x26\x76\x78\x00\x33\x50\x47\x93\x88\xe1\x76\x97\x7a\x5e\x52\x38\x8f\xa8\xb2\x70\x78\x90\xba\x2a\xae\xd5\x41\xf7\xb7\xd1\x89\x36\xda\xcd\xd0\x52\x41\x04\x92\x04\x33\x38\x08\x8d\x06\xcd\x24\xa1\xe9\x3c\xf4\xba\xb8\xc9\x49\xb4\xdb\xc1\x38\x87\xf1\x60\x6c\x28\x13\xa4\xb2\x65\xcf\xc9\x1f\xfd\x6c\xf1\xb2\x2d\x26\xd2\xbb\x66\xcd\xad\xeb\x83\xe3\x3d\x0f\x1a\x00\x9c\x0b\x6c\x04\xf1\xcb\x4e\xb5\x43\x97\x5c\xd2\x7c\xf6\xe7\x8f\xff\xb0\xd6\xac\x2f\xb2\xe3\xbb\x88\xcb\x8c\xc4\x28\x49\x64\x89\x6a\x31\x76\x56\x03\x3b\x32\x80\x0c\x35\x21\x8d\x39\x29\x1a\xe1\x60\xa7\x47\xb7\x93\xd3\xa8\x27\x34\x6a\x31\xda\x2b\x90\xd8\x42\xe9\xf1\xdd\x1c\xcd\x0a\x54\x14\xac\xc5\xd4\xeb\xc8\xf0\x30\x32\x34\x8c\x24\x09\x78\x87\xb6\x5b\x18\x55\x7c\x51\x80\xf7\x58\xf1\xa0\x20\x36\x38\x89\x56\xee\xd8\x75\xfc\x75\xcf\xf9\xef\xd6\x8e\x9f\x3b\xb1\x60\x7c\x9b\x02\x6a\x00\x7e\x88\x30\x30\xb7\x26\xb5\xa5\x67\xd4\xd6\x3d\xf8\xd4\x5f\xd6\x46\x86\x17\x47\x79\x46\xdc\x69\x91\xe2\x49\x8c\x62\xc4\x23\xde\x43\x51\xa0\x79\x81\xf6\x72\xc8\xc3\xfd\x60\x1c\x33\x54\x4f\x68\xc6\x16\xcd\x0a\xc8\x8a\xf0\xae\x70\xe0\x3d\x68\xe5\xb2\xaa\xe0\x3d\x5a\x3a\x70\x05\x94\x05\x94\x25\x5a\x3a\x54\x3d\x33\x49\xc6\x08\x58\x20\xb6\x90\x1a\x48\x8d\x12\x0b\xe9\xa6\xae\xfd\xce\x71\xcb\x22\x3f\xda\x40\x66\xf7\x2d\xf4\x04\xc8\x82\xb3\x17\x47\xf9\x05\x97\x8e\x3e\xbf\x73\xef\x33\x8d\x81\x3a\x66\xf3\x33\x24\x93\xfb\x89\x45\xb1\x22\x88\x15\x24\x89\x91\x66\x0d\x33\x58\x47\x1a\x29\x92\x44\x10\x9b\x60\x6f\xd1\x10\x04\xa5\x43\xb3\x12\xdf\xe9\xa1\xad\x2e\xda\xca\xd0\xbc\x0c\x40\x44\x90\x28\x42\x1a\x4d\xa4\xd1\x40\xe2\x18\xf5\x1e\xcd\x72\xb4\xdb\x41\x7b\x3d\x70\x65\x00\x5a\x81\x76\x0a\xb9\x0a\x5d\x07\xad\x12\xba\x4e\x7c\x62\x58\xb1\x28\xe9\x3d\xd1\x79\x6a\xac\x8c\x6e\x03\x86\x07\x90\xe1\x0f\xbd\x8b\x47\xee\x7d\xe4\xeb\x03\x27\x2f\xc0\xec\xdf\x43\xd4\x6d\x13\x09\x58\xa9\x8c\xe8\x09\x9a\x57\x8f\x66\x05\x12\x9b\xe0\x26\x91\x0d\xea\x12\xc0\x87\xe8\xd3\xa2\x44\x0b\x0f\x45\x19\xac\xa0\x0a\x62\x40\x40\xbd\x42\xa7\x83\xe6\x19\x12\xc5\x60\x4d\xa5\x78\x45\x22\x0b\x28\xea\xfa\xd6\x50\x0c\x1a\x2c\x61\xc2\x99\x2b\xa6\xf0\xac\xaa\xc5\xc9\xfa\x6d\x31\x12\x5d\x0a\xd8\x79\xf3\xcc\x53\xf7\xff\xea\x55\x76\x70\x60\x65\x64\x0d\x32\x71\x80\xc8\x15\xd8\x99\x34\xa5\x01\x04\x85\x47\x5d\x06\x85\x81\x58\x82\x8d\x8d\x09\x1f\xec\x03\x28\xc3\x89\x56\x0b\x88\x41\x4c\x7f\x5c\xdf\x3f\x22\x88\x63\x48\x12\xc4\x58\xc4\x3b\x34\xcf\x03\xb0\xbc\x40\xbd\x03\xaf\x88\x2a\x56\x15\xeb\x95\xb8\x72\x2b\x27\x5c\x36\x6b\x56\xfe\xd5\xfa\xec\x21\x89\xa2\x08\x89\x06\x87\x4d\xe6\xe5\xa2\x74\x68\xd0\x48\xd6\xc3\x76\x5a\x18\x14\x11\x3d\x32\xcb\xa9\x0f\x52\x59\x50\x6b\x90\x58\x90\x48\x40\x25\x08\xee\x05\xdc\x34\x4b\x8b\x11\x88\x62\x24\x4e\x20\x8a\x10\x63\x02\x1b\x45\x31\x34\xea\x98\x7a\x13\x92\x38\xb8\x5e\xb7\x8d\xb6\x5a\x68\xaf\x87\x14\x39\xea\x1c\x38\x1f\x98\x49\x3d\x46\x35\x18\xac\x64\x05\xc3\xc2\x70\x14\x49\x64\x63\x2b\x73\xce\x38\xc5\x3c\xb7\xdf\xbe\x31\xaa\xd5\x90\xf1\xe7\xb1\x65\x5e\x79\x85\x4c\x07\x96\x4c\x47\x97\x44\xc1\x9e\xa6\x6e\x91\x7a\x15\x07\x0a\xe4\x0e\x28\x50\x27\x88\x17\x54\x4d\x10\x34\xad\x05\xbf\x4f\x93\x90\xc6\xc5\x20\x49\x82\x0c\xce\x0a\x94\x9a\xa4\x50\x16\xf8\xc9\x49\x34\x9d\xc0\xb7\x26\x21\xcf\x2a\xc2\xc8\x91\x3c\xc7\x6a\x81\xf5\x0e\x83\x82\x70\x3c\x13\xc6\x26\x43\x49\x19\xc5\xd6\x08\xe7\x9e\x25\xfc\xe4\xa9\x73\x8d\x11\xa4\xd3\x01\xe7\x2a\xf2\x92\x43\xab\x0e\x21\x04\x73\x64\x20\xb5\x48\x33\x42\x9a\x31\x92\x58\xd4\x29\x74\xa5\x4a\xa5\x8a\x3a\x10\x0c\x92\xa4\x48\xb3\x89\x99\x35\x04\xf5\x06\x12\x47\x60\x0c\x52\xab\x23\x43\x43\x98\xa1\xd9\x48\x92\xa2\x65\x89\x1c\x9c\xc0\xa7\x29\xd2\xa8\xa3\xdd\x2e\xe4\x19\xda\xe9\xe0\xda\xed\x40\xab\x4e\x11\xe3\x31\xa5\xb2\x7e\x57\x7c\xd2\xe2\x86\xdf\x1a\xd5\xe6\x0c\xc0\xbc\x13\x2c\xb2\x61\x91\x38\x07\x59\x2f\xb8\xcf\x4c\xed\x4f\x61\x90\xe0\xbf\x91\x41\x52\x8b\xd4\x63\xa4\x19\x43\x62\x90\x22\x14\x2f\xf4\x4a\xd4\x84\x71\x82\x85\x24\x46\x1a\x0d\x18\x1c\xc4\x0c\x0c\x42\x1c\x57\x4c\xd4\x40\x66\x0f\x07\x00\x69\x0d\x2d\x0b\x7c\x9a\x62\xa2\x08\xad\xd5\xd0\x6e\x17\xed\x75\x21\x4e\x30\x08\xb6\xf4\x98\xdc\x05\xb7\x16\xa5\xe7\x19\xb6\xea\xc7\xa2\x7a\xa3\x2e\x3b\xfe\xe3\x91\xe3\xc5\x5a\x28\x4b\x28\xf2\xa9\xe0\x3b\x02\x82\x04\x10\x81\x52\x6d\x00\x51\xb3\x15\x95\x0a\xf4\x1c\xbe\x02\x29\xc6\xa0\x62\x31\x71\x8c\xa4\x35\x4c\xbd\x01\x8d\x3a\x92\xd4\x30\x49\x02\x8d\x66\x10\x7e\xd6\x10\x52\xaf\x23\x79\x0e\x22\x78\xf5\x61\xad\x34\x85\x76\x1c\x34\x5f\xe4\xd0\xed\x81\xcd\x10\x09\xc5\x91\xaa\x46\x46\x3d\x51\x1a\x45\x3a\xb1\xf7\xe0\x5c\x13\xa7\x68\x59\x4c\xb9\xcf\x51\x85\xef\x5f\x6d\xc5\xaf\x91\x40\x6c\x90\xc8\x86\xd8\xb6\x21\x68\xa7\xe6\x19\x83\x58\x8b\xc4\x31\x12\xc7\x10\xa7\x21\x0e\xd2\x5a\x10\xba\x56\x0b\x96\x48\x6b\x68\x14\x21\x45\x81\xa9\xd5\xf0\x79\x11\x58\xcf\x79\xc8\xf3\x30\x2f\x9a\xc1\x62\x80\x35\xb4\xbd\x58\x35\xd6\x1a\xf1\xae\xac\x85\xac\x51\x65\xce\x17\x2b\x55\x65\xe6\x29\xd3\x8c\x22\x87\x0f\x96\xca\xe3\x24\xf0\xbf\x39\xda\x69\x43\x40\x57\x6b\x48\xf5\x5c\x4d\x3f\xaf\x54\x56\xad\xd6\xf7\x48\xc8\x6f\xd5\xda\x23\x91\xdf\xdd\x2d\x55\x0c\x06\x35\x51\xd4\x53\xf5\xa8\x57\x54\xa7\xf5\x7e\x04\x89\xf6\x8b\x12\x1f\x92\x96\x7a\x5f\x5d\xab\x1c\xa0\x87\xce\xd4\x2a\xa3\xaa\xf7\xa8\x2a\xd2\x1f\xe7\x5d\xc8\xb8\x45\x28\x25\x70\x25\x5a\x84\xd2\x42\x8a\x02\xf2\xbc\xca\x09\x39\x5a\xe4\x68\x59\x82\x73\xa8\x06\xf9\xbc\xc2\xe8\x68\xb1\x3f\xcb\x32\x8d\x26\x5b\x6d\xe6\x2e\x3c\x61\xfb\xf6\xb1\xdd\x68\x62\x0f\x91\xd1\x1c\x92\xc8\x2a\xdd\x6a\xc8\xa6\x52\x7a\xb4\xf0\x48\xee\x10\x05\x5f\x56\x0a\xf0\xd3\xdd\x80\x54\xc2\x8b\x73\xa1\xe6\x29\xf2\xe0\x62\x55\x66\xf6\x69\x82\x89\x93\x00\x20\x2f\xd0\x76\x0b\xdf\x9a\x44\x5b\x93\x68\xbb\x0d\xdd\x0e\xda\x6e\xa3\xdd\x2e\x3e\x2f\x70\xce\xe1\x54\x91\xd0\xd3\xd0\x9d\xc8\x88\x5a\x7b\xbb\x9c\xf4\x96\x0b\xf6\x6d\xbb\xed\xde\xcc\xf9\x34\x15\x31\x95\x32\x15\x3d\xdc\x87\x7c\xf0\x4b\x29\x3c\x9a\x39\xe8\x96\x78\x2b\x48\xaa\x50\x7a\x28\x3c\xe2\x74\xda\x92\xce\x05\x2d\x67\x59\xa0\x45\x6b\x43\x01\x67\x2d\x9a\xe7\x18\x25\x58\x27\x4d\x21\x2f\xf0\x13\x07\xd0\xbd\x7b\xf1\x13\x07\xf0\x9d\x36\x74\xbb\x68\xbb\x8d\x6b\xb5\x70\x59\x86\x2f\x1d\x2e\x78\xf8\x63\x4c\x2a\xde\xab\x46\x9d\x89\x96\xf2\xc4\x13\x1e\xe7\x1f\xf5\xce\xaf\x74\x18\x4a\x95\x6a\xcb\x4e\x31\x32\x03\x85\x0a\xea\x41\x4a\x45\x72\x8f\x74\xca\xc0\xac\x55\xff\xe7\x7b\x65\xc8\xc6\xce\x87\x2a\x0c\x87\xe6\x19\xbe\xd3\x09\x81\xec\x1c\x12\x45\x28\x60\xe2\x18\xdf\xeb\xa2\xdd\x4e\x95\x07\x0a\x74\xe2\x00\x7e\xdf\x5e\x74\x62\x02\xed\x74\xf0\x79\x86\xf6\x32\x7c\x2f\xa3\xcc\x73\x4a\xe7\xf1\xde\x63\xe0\xa7\x93\xad\xc4\xec\x9e\x98\x28\xa3\x7d\x05\xda\x5e\xfb\xa0\xa2\x83\x3f\x2a\xb2\x6c\xa5\x8a\xc5\x68\x70\x1f\x45\xa6\xdd\xa8\x9f\xd7\x9c\x22\xa5\x43\x7a\xa1\xc8\x13\x07\x3e\xae\x8a\xaf\xdc\xa1\x3d\x87\x96\x5a\xd5\x42\x82\x98\x1c\x6d\xb7\x43\x9c\xf7\x7a\xa1\x26\xc2\xe3\x4d\x84\x69\xb5\x60\xb2\x85\x24\x31\x14\x21\x13\xfb\x89\x03\x68\x6b\x32\x58\xad\x28\x70\xa5\xc3\x15\x25\x65\xe1\x29\x4a\xa5\x54\x7c\x04\x0f\x75\xf3\xd2\x0d\x17\x68\x34\x0f\x74\x72\xeb\x33\x3e\x1a\x7d\xed\xda\x7c\xb2\xfd\x59\x05\xf0\x02\xa2\x81\xde\x75\x06\x83\x1a\x05\x27\x53\x5d\x92\x78\x17\xac\x61\x2b\x7a\x2b\xb5\xaa\x42\x83\x05\x14\x8f\x64\x05\xe2\x3b\x48\xe9\x42\x2d\xd4\xb7\xa8\x18\x24\x9d\xc4\xd4\x0e\x06\x1a\x76\x2e\x24\xaf\x4e\x3b\x94\xd5\x45\x81\xf7\x0e\xe7\x94\xdc\x29\x59\xa9\x14\x1e\xbc\x67\xcf\xeb\x8e\x73\x0f\xbd\xb0\x7d\x97\xdf\x0e\xc8\xfd\xc0\x59\x20\x0b\x55\xe5\xe7\xaf\x3d\xff\x09\xab\xee\xcc\xb8\xdb\x21\x15\x37\x95\x9f\xa6\x58\xb2\x4f\x9f\x80\xa9\x72\x81\x58\x99\xe6\xe7\x2a\x46\x70\x15\xdb\x54\x33\xc5\x9a\xd0\x8d\x19\x13\xe2\x4a\x41\x4c\xe8\x0d\x88\xe2\x90\xf4\x34\x34\x4b\xfd\xfa\x27\x30\x17\x94\x5e\xe9\x39\xe8\x39\xa5\x1b\x94\xf7\x7b\x2b\x17\xb4\xfe\x69\xec\xa7\x3b\x74\x11\xf8\xe8\x6d\xc0\x66\x40\x2f\x5c\x2e\x43\x83\xc7\xbd\x7d\xdf\xae\x7d\xcf\x7a\xaf\xa8\x11\x1c\x60\xd1\x29\x10\x7d\x2b\x08\x02\x1a\xdc\x47\x44\xc1\xf8\x69\x86\xd2\x50\x7a\x8b\x07\x24\xb8\x51\x18\xeb\xa7\xb5\x4f\xe8\x8f\x45\x0a\x30\x59\xb8\x57\x42\x7b\xe9\xdd\x94\xf0\xde\x2b\x85\x42\xcf\x43\xe6\x05\x07\xeb\xce\x1b\x2a\xff\x75\xf7\xd6\x8c\x6b\x02\x51\x22\x5b\x80\x53\x80\xed\x35\x64\xf6\xf2\x65\xd1\xc3\xfb\xe5\x56\x54\x3f\x1a\x87\x92\x07\x2b\x15\x9d\x4a\x00\x32\x6d\x88\xca\x95\x4c\xbf\x6a\x0d\x31\xa1\x3a\xdd\x88\x33\xc3\xfd\x10\x39\x2c\x39\x56\xc5\xa1\xc8\x94\x55\x50\x0d\xec\xe7\x15\xad\xba\xb1\xb0\xc5\x12\xf6\x8c\x2c\x5c\xb1\x74\x96\x5f\xdb\xfe\xaf\x67\xca\x53\x2b\x62\x97\xfe\x46\x56\x01\xcc\x1e\xb0\x66\xde\xaa\x45\xac\x7d\x32\x7a\xce\x08\xf3\xfb\xc2\xf7\x2d\x30\xe5\x4e\x87\xb9\xd5\x21\xd7\xbe\x82\x5f\x6c\xf7\xec\x45\xf6\x02\x75\x1a\x73\xc8\x75\x15\x00\xe7\xa1\x0c\xd3\x3e\x79\xf1\x65\x93\xb7\xbd\xf0\xf5\xe7\x65\x7d\xa1\x7e\x3e\x70\xfa\xe1\xcb\x1d\x00\x86\x46\x90\xff\x1c\x5e\x36\xdc\x29\x59\x2f\x86\x13\xcc\x74\x37\x18\xa8\x55\x42\x60\x73\x58\x05\x71\x48\x9c\x4c\x2b\xfc\x48\xb9\x95\x23\x32\xbd\xce\x00\xd0\x6f\x26\xfb\x89\xdd\x85\x41\xdf\x7a\xd3\x3c\xff\x07\x3b\xb6\xed\xf5\xed\x9d\xfb\xfc\xb2\x17\xd3\xc7\x58\xb5\x95\x7e\x16\xc8\x63\xa3\x4b\xe6\x3a\xb1\xcf\x09\x24\x1c\x5a\x02\xcd\x2c\x4c\x8f\xa8\xf3\x8e\x26\x7c\xff\x9d\xfe\x06\xad\xeb\x8c\xce\x55\xa7\x3d\x0a\xe0\x1b\x97\x6d\xdd\xf8\xfe\x5d\x16\x53\x73\xa1\x52\x1b\x79\xa9\x9b\xbb\x3f\x3e\x79\xe9\x5c\x2f\xb2\x16\x78\xf5\x4c\xa1\x84\xa3\xbb\x88\x1c\xe3\x6f\x13\x7d\x11\x6b\xcc\x48\x3b\xd7\x5d\xbe\x75\xe3\xed\xbf\x8c\x22\x29\xcb\x52\x57\xbe\xdc\x1f\x1c\x6b\x46\x97\xdd\x0d\x5c\x53\x15\xd2\x47\xcc\xfc\x9f\x16\x39\x9a\xf6\x8f\x1a\x03\xd3\x37\x5b\x54\x79\xdb\xea\x6d\x1b\xd7\xf7\x1f\x3f\x00\x5c\x7c\xac\x3f\xf9\xd6\x8c\x2e\x63\xcd\xe8\x32\x56\x8f\x6d\xfc\x20\xb0\x1c\x78\x76\xa6\x79\xfb\xa7\x3f\xca\x79\xf8\xfb\xdf\x34\xf6\x90\xf1\xf0\x6e\x0f\x67\xae\xde\xb6\x71\xfd\xbf\xbf\xed\xfd\xfc\xb8\xff\xc3\xf0\x95\xfc\x23\xab\x40\xf4\xef\xaf\x03\x3e\x02\x9c\xf9\xbf\xf8\xbb\x77\x03\xf0\x0f\xab\xc7\x36\xde\x7c\xf8\xf7\x78\x09\xd6\x3d\xe6\x63\xcd\xe8\x32\x53\xfd\x36\xbb\x13\x78\xd3\x2b\x10\xfc\xfb\xc0\x4d\xc0\xa6\xd5\x63\x1b\xdd\xb1\x0a\x4f\x7f\x77\xfa\x65\x1c\x7e\xf5\xd8\xc6\xa7\x81\x4b\xd6\x8c\x2e\x9b\x05\xbc\x01\x78\x6d\x45\xcd\x27\x03\x73\x81\x05\x40\x13\xd8\x05\x8c\x03\xbb\x81\x2d\x95\xb6\xd7\x01\xbf\x58\x3d\xb6\xb1\xe8\x0b\xdd\x3f\x8e\x45\x78\x80\xff\x06\x26\x17\xf9\x29\x2d\x26\x91\x99\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\x01\x00\x00\xff\xff\x8e\x30\x0d\x01\x5c\x0f\x00\x00")
99+
100+ func tomatoIconPngBytes() ([]byte, error) {
101+ return bindataRead(
102+ _tomatoIconPng,
103+ "tomato-icon.png",
104+ )
105+ }
106+
107+ func tomatoIconPng() (*asset, error) {
108+ bytes, err := tomatoIconPngBytes()
109+ if err != nil {
110+ return nil, err
111+ }
112+
113+ info := bindataFileInfo{name: "tomato-icon.png", size: 3932, mode: os.FileMode(420), modTime: time.Unix(1516522577, 0)}
114+ a := &asset{bytes: bytes, info: info}
115+ return a, nil
116+ }
117+
118+ // Asset loads and returns the asset for the given name.
119+ // It returns an error if the asset could not be found or
120+ // could not be loaded.
121+ func Asset(name string) ([]byte, error) {
122+ cannonicalName := strings.Replace(name, "\\", "/", -1)
123+ if f, ok := _bindata[cannonicalName]; ok {
124+ a, err := f()
125+ if err != nil {
126+ return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
127+ }
128+ return a.bytes, nil
129+ }
130+ return nil, fmt.Errorf("Asset %s not found", name)
131+ }
132+
133+ // MustAsset is like Asset but panics when Asset would return an error.
134+ // It simplifies safe initialization of global variables.
135+ func MustAsset(name string) []byte {
136+ a, err := Asset(name)
137+ if err != nil {
138+ panic("asset: Asset(" + name + "): " + err.Error())
139+ }
140+
141+ return a
142+ }
143+
144+ // AssetInfo loads and returns the asset info for the given name.
145+ // It returns an error if the asset could not be found or
146+ // could not be loaded.
147+ func AssetInfo(name string) (os.FileInfo, error) {
148+ cannonicalName := strings.Replace(name, "\\", "/", -1)
149+ if f, ok := _bindata[cannonicalName]; ok {
150+ a, err := f()
151+ if err != nil {
152+ return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
153+ }
154+ return a.info, nil
155+ }
156+ return nil, fmt.Errorf("AssetInfo %s not found", name)
157+ }
158+
159+ // AssetNames returns the names of the assets.
160+ func AssetNames() []string {
161+ names := make([]string, 0, len(_bindata))
162+ for name := range _bindata {
163+ names = append(names, name)
164+ }
165+ return names
166+ }
167+
168+ // _bindata is a table, holding each asset generator, mapped to its name.
169+ var _bindata = map[string]func() (*asset, error){
170+ "tomato-icon.png": tomatoIconPng,
171+ }
172+
173+ // AssetDir returns the file names below a certain
174+ // directory embedded in the file by go-bindata.
175+ // For example if you run go-bindata on data/... and data contains the
176+ // following hierarchy:
177+ // data/
178+ // foo.txt
179+ // img/
180+ // a.png
181+ // b.png
182+ // then AssetDir("data") would return []string{"foo.txt", "img"}
183+ // AssetDir("data/img") would return []string{"a.png", "b.png"}
184+ // AssetDir("foo.txt") and AssetDir("notexist") would return an error
185+ // AssetDir("") will return []string{"data"}.
186+ func AssetDir(name string) ([]string, error) {
187+ node := _bintree
188+ if len(name) != 0 {
189+ cannonicalName := strings.Replace(name, "\\", "/", -1)
190+ pathList := strings.Split(cannonicalName, "/")
191+ for _, p := range pathList {
192+ node = node.Children[p]
193+ if node == nil {
194+ return nil, fmt.Errorf("Asset %s not found", name)
195+ }
196+ }
197+ }
198+ if node.Func != nil {
199+ return nil, fmt.Errorf("Asset %s not found", name)
200+ }
201+ rv := make([]string, 0, len(node.Children))
202+ for childName := range node.Children {
203+ rv = append(rv, childName)
204+ }
205+ return rv, nil
206+ }
207+
208+ type bintree struct {
209+ Func func() (*asset, error)
210+ Children map[string]*bintree
211+ }
212+ var _bintree = &bintree{nil, map[string]*bintree{
213+ "tomato-icon.png": &bintree{tomatoIconPng, map[string]*bintree{}},
214+ }}
215+
216+ // RestoreAsset restores an asset under the given directory
217+ func RestoreAsset(dir, name string) error {
218+ data, err := Asset(name)
219+ if err != nil {
220+ return err
221+ }
222+ info, err := AssetInfo(name)
223+ if err != nil {
224+ return err
225+ }
226+ err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
227+ if err != nil {
228+ return err
229+ }
230+ err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
231+ if err != nil {
232+ return err
233+ }
234+ err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
235+ if err != nil {
236+ return err
237+ }
238+ return nil
239+ }
240+
241+ // RestoreAssets restores an asset under the given directory recursively
242+ func RestoreAssets(dir, name string) error {
243+ children, err := AssetDir(name)
244+ // File
245+ if err != nil {
246+ return RestoreAsset(dir, name)
247+ }
248+ // Dir
249+ for _, child := range children {
250+ err = RestoreAssets(dir, filepath.Join(name, child))
251+ if err != nil {
252+ return err
253+ }
254+ }
255+ return nil
256+ }
257+
258+ func _filePath(dir, name string) string {
259+ cannonicalName := strings.Replace(name, "\\", "/", -1)
260+ return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
261+ }
262+
263 diff --git a/libnotify/libnotify.go b/libnotify/libnotify.go
264new file mode 100644
265index 0000000..60d91fd
266--- /dev/null
267+++ b/libnotify/libnotify.go
268 @@ -0,0 +1,47 @@
269+ /*
270+ libnotify is a lightweight client for libnotify https://developer.gnome.org/notification-spec/.
271+ For now this just shells out to "notify-send".
272+ TODO: Move this into it's own repository as time permits.
273+ */
274+ package libnotify
275+
276+ import (
277+ "fmt"
278+ "os/exec"
279+ "time"
280+ )
281+
282+ type Notification struct {
283+ Urgency string
284+ Expire time.Duration
285+ Title string
286+ Body string
287+ Icon string
288+ }
289+
290+ type Client struct {
291+ Path string
292+ }
293+
294+ func NewClient() *Client {
295+ return &Client{
296+ Path: "/bin/notify-send",
297+ }
298+ }
299+
300+ func (c Client) Notify(n Notification) error {
301+ var args []string
302+ if n.Urgency != "" {
303+ args = append(args, fmt.Sprintf("--urgency=%s", n.Urgency))
304+ }
305+ if n.Icon != "" {
306+ args = append(args, fmt.Sprintf("--icon=%s", n.Icon))
307+ }
308+ if n.Expire > 0 {
309+ args = append(args, fmt.Sprintf("--expire=%s", n.Expire.Truncate(time.Millisecond)))
310+ }
311+ args = append(args, n.Title)
312+ args = append(args, n.Body)
313+ _, err := exec.Command(c.Path, args...).Output()
314+ return err
315+ }
316 diff --git a/main.go b/main.go
317index 0b2cd8c..519d1ec 100644
318--- a/main.go
319+++ b/main.go
320 @@ -23,13 +23,15 @@ func start(path *string) func(*cli.Cmd) {
321 db, err := NewStore(*path)
322 maybe(err)
323 defer db.Close()
324- task := Task{
325+ task := &Task{
326 Message: *message,
327 Tags: *tags,
328 NPomodoros: *pomodoros,
329 Duration: parsed,
330 }
331- run(task, &I3{}, db)
332+ runner, err := NewTaskRunner(task, db)
333+ maybe(err)
334+ maybe(runner.Run())
335 }
336 }
337 }
338 diff --git a/task.go b/task.go
339index 685717c..c3f8bfe 100644
340--- a/task.go
341+++ b/task.go
342 @@ -3,10 +3,98 @@ package main
343 import (
344 "fmt"
345 "github.com/gosuri/uilive"
346- "io"
347 "time"
348 )
349
350+ type TaskRunner struct {
351+ count int
352+ task *Task
353+ store *Store
354+ writer *uilive.Writer
355+ timer *time.Timer
356+ ticker *time.Ticker
357+ notifier Notifier
358+ }
359+
360+ func NewTaskRunner(task *Task, store *Store) (*TaskRunner, error) {
361+ taskID, err := store.CreateTask(*task)
362+ if err != nil {
363+ return nil, err
364+ }
365+ task.ID = taskID
366+ tr := &TaskRunner{
367+ task: task,
368+ store: store,
369+ notifier: NewLibNotifier(),
370+ writer: uilive.New(),
371+ timer: time.NewTimer(task.Duration),
372+ ticker: time.NewTicker(RefreshInterval),
373+ }
374+ tr.writer.Start()
375+ return tr, nil
376+ }
377+
378+ func (t *TaskRunner) Run() error {
379+ for t.count < t.task.NPomodoros {
380+ // ASCII spinner
381+ wheel := &Wheel{}
382+ // This pomodoro
383+ pomodoro := &Pomodoro{}
384+ prompt("press enter to begin")
385+ // Emit a desktop notification
386+ // that the task is beginning.
387+ t.notifier.Begin(t.count, *t.task)
388+ // Record task as started
389+ pomodoro.Start = time.Now()
390+ // Reset the timer
391+ t.timer.Reset(t.task.Duration)
392+ // Wait for either a tick to update
393+ // the UI for the timer to complete
394+ loop:
395+ select {
396+ case <-t.ticker.C:
397+ t.updateUI(Message{
398+ Start: pomodoro.Start,
399+ Duration: t.task.Duration,
400+ Pomodoros: t.task.NPomodoros,
401+ Wheel: wheel,
402+ CurrentPomodoro: t.count,
403+ })
404+ goto loop
405+ case <-t.timer.C:
406+ // Send a notification for the
407+ // user to take a break. We record
408+ // how long it actually takes for
409+ // them to initiate the break.
410+ t.notifier.Break(*t.task)
411+ prompt("press enter to take a break")
412+ // Record the task as complete
413+ pomodoro.End = time.Now()
414+ // Record the session in the db
415+ err := t.store.CreatePomodoro(t.task.ID, *pomodoro)
416+ if err != nil {
417+ return err
418+ }
419+ // Increment the count of completed pomodoros
420+ t.count++
421+ }
422+ }
423+ return nil
424+ }
425+
426+ func (t *TaskRunner) updateUI(msg Message) {
427+ fmt.Fprintf(
428+ t.writer,
429+ "%s %s remaining [ pomodoro %d/%d ]\n",
430+ msg.Wheel,
431+ (msg.Duration - time.Since(msg.Start)).Truncate(time.Second),
432+ msg.CurrentPomodoro,
433+ msg.Pomodoros,
434+ )
435+ }
436+
437+ /*
438+
439 // Task Starting..
440 //
441 // 20min remaning [pomodoro 1/4]
442 @@ -25,7 +113,7 @@ func display(writer io.Writer, msg Message) {
443 )
444 }
445
446- func run(task Task, prompter Prompter, db *Store) {
447+ func run(task Task, notifier Notifier, db *Store) {
448 taskID, err := db.CreateTask(task)
449 maybe(err)
450 writer := uilive.New()
451 @@ -36,7 +124,7 @@ func run(task Task, prompter Prompter, db *Store) {
452 var p int
453 for p < task.NPomodoros {
454 pomodoro := &Pomodoro{}
455- maybe(prompter.Prompt("Begin working!"))
456+ maybe(notifier.Begin(task))
457 pomodoro.Start = time.Now()
458 timer.Reset(task.Duration)
459 loop:
460 @@ -51,11 +139,14 @@ func run(task Task, prompter Prompter, db *Store) {
461 })
462 goto loop
463 case <-timer.C:
464- maybe(prompter.Prompt("Take a break!"))
465+ maybe(notifier.Break(task))
466+ fmt.Println("press enter to take a break")
467 pomodoro.End = time.Now()
468 maybe(db.CreatePomodoro(taskID, *pomodoro))
469 p++
470 }
471 }
472+ maybe(notifier.Finish(task))
473 writer.Stop()
474 }
475+ */
476 diff --git a/tomato-icon.png b/tomato-icon.png
477new file mode 100644
478index 0000000..a4ed47e
479 Binary files /dev/null and b/tomato-icon.png differ
480 diff --git a/types.go b/types.go
481index 60c2961..6fdbf84 100644
482--- a/types.go
483+++ b/types.go
484 @@ -5,8 +5,9 @@ import (
485 "fmt"
486 "github.com/fatih/color"
487 "io/ioutil"
488- "os/exec"
489 "time"
490+
491+ "github.com/kevinschoon/pomo/libnotify"
492 )
493
494 // RefreshInterval is the frequency at which
495 @@ -115,22 +116,57 @@ type Pomodoro struct {
496 End time.Time `json:"end"`
497 }
498
499- // Prompter prompts a user with a message.
500- type Prompter interface {
501- Prompt(string) error
502+ // Notifier implements a system specific
503+ // notification. On Linux this libnotify.
504+ // TODO: OSX, Windows(?)
505+ type Notifier interface {
506+ Begin(int, Task) error
507+ Break(Task) error
508+ Finish(Task) error
509 }
510
511- // I3 implements a prompter for i3
512- type I3 struct{}
513+ // LibNotifier implements a Linux
514+ // notifier client.
515+ type LibNotifier struct {
516+ client *libnotify.Client
517+ iconPath string
518+ }
519
520- func (i *I3) Prompt(message string) error {
521- _, err := exec.Command(
522- "/bin/i3-nagbar",
523- "-m",
524- message,
525- ).Output()
526- if err != nil {
527- return err
528+ func NewLibNotifier() Notifier {
529+ ln := &LibNotifier{
530+ client: libnotify.NewClient(),
531 }
532- return nil
533+ // Write the tomato icon to a temp path
534+ raw := MustAsset("tomato-icon.png")
535+ fp, _ := ioutil.TempFile("", "pomo")
536+ fp.Write(raw)
537+ ln.iconPath = fp.Name()
538+ fp.Close()
539+ return ln
540+ }
541+
542+ func (ln LibNotifier) Begin(count int, t Task) error {
543+ return ln.client.Notify(libnotify.Notification{
544+ Title: t.Message,
545+ Body: fmt.Sprintf("Task is starting (%d/%d pomodoros)", count, t.NPomodoros),
546+ Icon: ln.iconPath,
547+ })
548+ }
549+
550+ func (ln LibNotifier) Break(t Task) error {
551+ return ln.client.Notify(libnotify.Notification{
552+ Title: t.Message,
553+ Urgency: "critical",
554+ Body: fmt.Sprintf("Time to take a break!\nPress enter at the console to initiate the break."),
555+ Icon: ln.iconPath,
556+ })
557+ }
558+
559+ func (ln LibNotifier) Finish(t Task) error {
560+ return ln.client.Notify(libnotify.Notification{
561+ Title: t.Message,
562+ Urgency: "critical",
563+ Body: fmt.Sprintf("This task session is complete!"),
564+ Icon: ln.iconPath,
565+ })
566 }
567 diff --git a/util.go b/util.go
568index 99aa249..6ff64eb 100644
569--- a/util.go
570+++ b/util.go
571 @@ -1,6 +1,7 @@
572 package main
573
574 import (
575+ "bufio"
576 "fmt"
577 "github.com/fatih/color"
578 "os"
579 @@ -21,6 +22,12 @@ func defaultConfigPath() string {
580 return u.HomeDir + "/.pomo"
581 }
582
583+ func prompt(text string) {
584+ reader := bufio.NewReader(os.Stdin)
585+ fmt.Println(text)
586+ reader.ReadString('\n')
587+ }
588+
589 func summerizeTasks(config *Config, tasks []*Task) {
590 for _, task := range tasks {
591 fmt.Printf("%d: [%s] ", task.ID, task.Duration.Truncate(time.Second))