summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2017-11-15 22:23:31 +0100
committerStefan Majewsky <majewsky@gmx.net>2017-11-15 22:23:39 +0100
commitd47c8f45f11b283b5648eef5f15e15ff7ce27d4b (patch)
tree842191a4d951699621605da175b12e7f84486a54
parent28115d8776a952d9a0c32cf639cdffb2eff19ca2 (diff)
downloadgofu-d47c8f45f11b283b5648eef5f15e15ff7ce27d4b.tar.gz
add i3status applet, initially with a clock only
-rw-r--r--Makefile2
-rw-r--r--README.md4
-rw-r--r--main.go3
-rw-r--r--pkg/i3status/main.go137
4 files changed, 145 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 55e4a22..e2767b3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
PKG = github.com/majewsky/gofu
PREFIX = /usr
-APPLETS = rtree prettyprompt
+APPLETS = i3status rtree prettyprompt
all: build/gofu $(addprefix build/,$(APPLETS))
diff --git a/README.md b/README.md
index 469ca2c..ea04307 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,10 @@ $ make && make install
# List of applets
+## `i3status`
+
+My own variant of [`i3status`](https://i3wm.org/i3status/).
+
## `prettyprompt`
This renders my shell prompt. Among other things, it identifies the current
diff --git a/main.go b/main.go
index 1942c8e..4a79ed9 100644
--- a/main.go
+++ b/main.go
@@ -23,6 +23,7 @@ import (
"os"
"path/filepath"
+ "github.com/majewsky/gofu/pkg/i3status"
"github.com/majewsky/gofu/pkg/prompt"
"github.com/majewsky/gofu/pkg/rtree"
)
@@ -46,6 +47,8 @@ func execApplet(applet string, args []string, allowGofu bool) int {
return prompt.Exec(args)
case "rtree":
return rtree.Exec(args)
+ case "i3status":
+ return i3status.Exec(args)
default:
fmt.Fprintln(os.Stderr, "ERROR: unknown applet: "+applet)
return 255
diff --git a/pkg/i3status/main.go b/pkg/i3status/main.go
new file mode 100644
index 0000000..1b16f21
--- /dev/null
+++ b/pkg/i3status/main.go
@@ -0,0 +1,137 @@
+/*******************************************************************************
+*
+* Copyright 2017 Stefan Majewsky <majewsky@gmx.net>
+*
+* This program is free software: you can redistribute it and/or modify it under
+* the terms of the GNU General Public License as published by the Free Software
+* Foundation, either version 3 of the License, or (at your option) any later
+* version.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along with
+* this program. If not, see <http://www.gnu.org/licenses/>.
+*
+*******************************************************************************/
+
+package i3status
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "sort"
+ "time"
+)
+
+//Exec executes the i3status applet and returns an exit code (0 for
+//success, >0 for error).
+func Exec(args []string) int {
+ //start protocol (we handle errors here for once because if stdout is
+ //working, we have nothing left to do)
+ _, err := os.Stdout.Write([]byte("{\"version\":1}\n[\n"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, err.Error())
+ return 1
+ }
+
+ //main loop
+ currentBlocks := make(map[string][]Block)
+ for {
+ //prepare clock (this is inline instead of split into a separate function
+ //because the wallclock also drives the main loop's clock, see below)
+ now := time.Now()
+ currentBlocks["clock"] = []Block{
+ {
+ Name: "clock",
+ Instance: "date",
+ Position: PositionClock,
+ FullText: now.Format("2006-01-02"),
+ ShortText: " ",
+ Color: "#AAAAAA",
+ },
+ {
+ Name: "clock",
+ Instance: "time",
+ Position: PositionClock,
+ FullText: now.Format("15:04:05"),
+ },
+ }
+
+ //put blocks in rendering order
+ var allBlocks []Block
+ for _, blocks := range currentBlocks {
+ allBlocks = append(allBlocks, blocks...)
+ }
+ sort.Sort(byPositionAndInstance(allBlocks))
+
+ //write output
+ buf, err := json.Marshal(allBlocks)
+ if err == nil {
+ os.Stdout.Write(append(buf, ',', '\n'))
+ } else {
+ //this should not happen, but if it does, fall back to just encoding the
+ //error string (which always works, otherwise wtf)
+ buf, _ = json.Marshal(err.Error())
+ fmt.Printf(
+ `[{"name":"error","urgent":true,"color":"#FF0000","full_text":%s}],`,
+ string(buf),
+ )
+ }
+
+ //sleep until the next second starts, so that the clock is always on time
+ nsec := 1000000000 - now.Nanosecond()
+ time.Sleep(time.Duration(nsec) * time.Nanosecond)
+ }
+
+ return 0
+}
+
+//Block is a block of text as used in the i3status protocol.
+//(Not all attributes are represented.)
+type Block struct {
+ Position Position `json:"-"`
+ Name string `json:"name"` //REQUIRED
+ Instance string `json:"instance,omitempty"`
+ FullText string `json:"full_text"` //REQUIRED
+ ShortText string `json:"short_text"`
+ Color string `json:"color,omitempty"` //CSS hex syntax, e.g. #123456
+ BackgroundColor string `json:"background,omitempty"`
+ MinWidth uint `json:"min_width,omitempty"`
+ Alignment Alignment `json:"align,omitempty"` //only plausible with MinWidth
+ Urgent bool `json:"urgent,omitempty"`
+ Separator bool `json:"separator"`
+ SeparatorBlockWidth int `json:"separator_block_width,omitempty"`
+}
+
+type byPositionAndInstance []Block
+
+func (b byPositionAndInstance) Len() int { return len(b) }
+func (b byPositionAndInstance) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
+func (b byPositionAndInstance) Less(i, j int) bool {
+ if b[i].Position == b[j].Position {
+ return b[i].Instance < b[j].Instance
+ }
+ return b[i].Position < b[j].Position
+}
+
+//Alignment is the alignment of a Block.
+type Alignment string
+
+//Acceptable values for Alignment.
+const (
+ AlignmentLeft Alignment = "left"
+ AlignmentCenter Alignment = "center"
+ AlignmentRight Alignment = "right"
+)
+
+//Position defines how blocks are ordered.
+type Position int
+
+//Acceptable values for Position, from left to right.
+const (
+ PositionNone Position = iota
+ PositionClock
+)