From 30451238df36d238fea816f01ca987853c6562a5 Mon Sep 17 00:00:00 2001 From: Robert Günzler Date: Thu, 11 Feb 2021 13:45:25 +0100 Subject: waybar: update 2021-02-11T12:46:11Z --- .config/waybar/modules/gammastep.go | 94 ++++++++++++++ .config/waybar/modules/go.mod | 5 + .config/waybar/modules/go.sum | 4 + .config/waybar/modules/redshift.go | 89 ------------- .config/waybar/modules/todo.go | 246 ++++++++++++++++++++++++++++++++++++ 5 files changed, 349 insertions(+), 89 deletions(-) create mode 100644 .config/waybar/modules/gammastep.go create mode 100644 .config/waybar/modules/go.mod create mode 100644 .config/waybar/modules/go.sum delete mode 100644 .config/waybar/modules/redshift.go create mode 100644 .config/waybar/modules/todo.go (limited to '.config/waybar/modules') diff --git a/.config/waybar/modules/gammastep.go b/.config/waybar/modules/gammastep.go new file mode 100644 index 0000000..c855e1b --- /dev/null +++ b/.config/waybar/modules/gammastep.go @@ -0,0 +1,94 @@ +// usage: +// go build -o waybar-gammastep gammastep.go +package main + +import ( + "encoding/json" + "log" + "os" + "os/exec" + "os/signal" + "strings" + + . "syscall" +) + +func main() { + var ( + update = make(chan os.Signal, 1) + cancel = make(chan os.Signal, 1) + ) + signal.Notify(update, SIGUSR1, SIGUSR2, SIGHUP) + signal.Notify(cancel, SIGTERM, SIGINT) + + // could use golang.org/x/sys/unix.Prctl here + // but I want to avoid the dependency + if _, _, e := Syscall6(SYS_PRCTL, + uintptr(PR_SET_PDEATHSIG), + uintptr(SIGTERM), + 0, 0, 0, 0); e != 0 { + log.Printf("error: %v", e) + os.Exit(1) + } + // if err := Prctl(PR_SET_PDEATHSIG, + // SIGTERM, + // 0, 0, 0); err != nil { + // fmt.Fprintln(os.Stderr, err) + // os.Exit(1) + // } + + lookup() + for { + select { + case sig := <-update: + switch sig { + case SIGUSR1: + // redshift off + printJson("day", "day") + case SIGUSR2: + // redshift on + printJson("night", "night") + case SIGHUP: + lookup() + } + case <-cancel: + log.Printf("%v exiting\n", os.Args[0]) + os.Exit(0) + } + } +} + +func printJson(state, tooltip string) { + _ = json.NewEncoder(os.Stdout).Encode(map[string]interface{}{ + "class": state, + "alt": state, + "tooltip": "gammastep: " + tooltip, + "text": state, + }) +} + +func lookup() { + b, err := exec.Command("gammastep", "-p").CombinedOutput() + if err != nil { + log.Printf("error: %v", err) + return + } + var period string + var fds = strings.Fields(string(b)) + for idx, f := range fds { + if f == "Period:" { + period = fds[idx+1] + break + } + } + switch period { + case "Night": + printJson("night", "night") + case "Transition": + printJson("night", "transition") + case "Daytime": + printJson("day", "day") + default: + printJson("unknown", "unknown") + } +} diff --git a/.config/waybar/modules/go.mod b/.config/waybar/modules/go.mod new file mode 100644 index 0000000..fdeae95 --- /dev/null +++ b/.config/waybar/modules/go.mod @@ -0,0 +1,5 @@ +module git.sr.ht/~robertgzr/dotfiles/.config/waybar/modules + +go 1.15 + +require github.com/fsnotify/fsnotify v1.4.9 // indirect diff --git a/.config/waybar/modules/go.sum b/.config/waybar/modules/go.sum new file mode 100644 index 0000000..b12c1ed --- /dev/null +++ b/.config/waybar/modules/go.sum @@ -0,0 +1,4 @@ +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/.config/waybar/modules/redshift.go b/.config/waybar/modules/redshift.go deleted file mode 100644 index b336748..0000000 --- a/.config/waybar/modules/redshift.go +++ /dev/null @@ -1,89 +0,0 @@ -// usage: -// go build -o waybar-redshift redshift.go -package main - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "os/signal" - "strings" - - . "syscall" -) - -func main() { - var ( - update = make(chan os.Signal, 1) - cancel = make(chan os.Signal, 1) - ) - signal.Notify(update, SIGUSR1, SIGUSR2, SIGHUP) - signal.Notify(cancel, SIGTERM, SIGINT) - - // could use golang.org/x/sys/unix.Prctl here - // but I want to avoid the dependency - if _, _, e := Syscall6(SYS_PRCTL, - uintptr(PR_SET_PDEATHSIG), - uintptr(SIGTERM), - 0, 0, 0, 0); e != 0 { - os.Exit(1) - } - // if err := Prctl(PR_SET_PDEATHSIG, - // SIGTERM, - // 0, 0, 0); err != nil { - // fmt.Fprintln(os.Stderr, err) - // os.Exit(1) - // } - - lookup() - for { - select { - case sig := <-update: - switch sig { - case SIGUSR1: - // redshift off - printJson("day", "day") - case SIGUSR2: - // redshift on - printJson("night", "night") - case SIGHUP: - lookup() - } - case <-cancel: - fmt.Fprintf(os.Stderr, "%v exiting\n", os.Args[0]) - os.Exit(0) - } - } -} - -func printJson(state, tooltip string) { - _ = json.NewEncoder(os.Stdout).Encode(map[string]interface{}{ - "class": state, - "alt": state, - "tooltip": "redshift: " + tooltip, - "text": state, - }) -} - -func lookup() { - b, err := exec.Command("redshift", "-p").Output() - if err != nil { - return - } - fds := strings.Fields(string(b)) - for idx, f := range fds { - if f == "Period:" { - fds = fds[idx+1:] - break - } - } - switch fds[0] { - case "Night": - printJson("night", "night") - case "Transition": - printJson("night", "transition") - case "Daytime": - printJson("day", "day") - } -} diff --git a/.config/waybar/modules/todo.go b/.config/waybar/modules/todo.go new file mode 100644 index 0000000..449f991 --- /dev/null +++ b/.config/waybar/modules/todo.go @@ -0,0 +1,246 @@ +// # Documentation +// we try to read a file pointed to by WAYBAR_TODO_FILE +// or `nb show --path todo`. +// While no particular format is assumed, we recommend +// Markdown. +// We extract the todos from the top-level list items +// (listings start with a `*` character). +// +// # Usage: +// go build -o waybar-todo todo.go +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "os/signal" + "regexp" + "strings" + "text/scanner" + "time" + + "github.com/fsnotify/fsnotify" + "golang.org/x/sys/unix" +) + +const ( + TODO_LINE_CHAR rune = '*' + TODO_INACTIVE_STRING string = "" + TODO_LABEL_REGEX string = `\[\[!(.*)\]\]` +) + +var ( + active = true + todofile = os.Getenv("WAYBAR_TODO_FILE") + cookiefile = os.ExpandEnv("${XDG_RUNTIME_DIR}/waybar-todo.cookie") + labelFilter = os.Getenv("WAYBAR_TODO_FILTER") +) + +func printJson(text, tooltip string, class []string, alt string, percentage int) error { + return json.NewEncoder(os.Stdout).Encode(map[string]interface{}{ + "text": text, + "tooltip": tooltip, + "class": class, + "alt": alt, + "percentage": percentage, + }) +} + +type todoItem struct { + text string + label string +} + +func readTodofile(path string) ([]todoItem, error) { + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading file: %v", err) + } + + var ( + sc scanner.Scanner + validLine bool + todos []todoItem + ) + sc.Init(bytes.NewReader(content)) + sc.Filename = path + sc.Whitespace ^= 1<<'\n' | 1<<' ' // don't skip newlines or spaces + sc.Mode ^= scanner.GoTokens + for tok := sc.Scan(); tok != scanner.EOF; tok = sc.Scan() { + switch { + // a valid line stats with TODO_LINE_CHAR + // followed by a space + // we strip them before appending the line array + case tok == TODO_LINE_CHAR && sc.Peek() == ' ': + validLine = true + _ = sc.Scan() // swallow the space + todos = append(todos, todoItem{}) // grow the array + + case tok == '\n': + validLine = false + if len(todos) > 0 { + idx := len(todos) - 1 + matches := labelre.FindAllStringSubmatch(todos[idx].text, -1) + if matches != nil && len(matches) > 0 { + todos[idx].label = matches[0][1] + todos[idx].text = strings.TrimSpace( + strings.Replace(todos[idx].text, matches[0][0], "", 1)) + } + } + + case validLine: + todos[len(todos)-1].text = todos[len(todos)-1].text + sc.TokenText() + } + + } + return todos, nil +} + +func setCookie(active bool) error { + if !active { + _, err := os.Create(cookiefile) + return err + } + return os.Remove(cookiefile) +} + +func update() error { + if _, err := os.Stat(cookiefile); err == nil { + log.Printf("cookie set, we're inactive") + active = false + } + if !active { + return printJson(TODO_INACTIVE_STRING, "* inactive *", []string{"off"}, "todo-off", 0) + } + todos, err := readTodofile(todofile) + if err != nil { + return err + } + var ( + text string + tooltip string + extraClass string + ) + for _, item := range todos { + if labelFilter != "" && strings.Contains(labelFilter, item.label) { + continue + } + if text == "" { + text = item.text + extraClass = item.label + continue + } + // render out the rest of the todos into the tooltip + tooltip = strings.Join([]string{tooltip, item.text}, "\n") + } + return printJson(text, tooltip, []string{"on", extraClass}, "todo-on", 1) +} + +var ( + labelre *regexp.Regexp +) + +func init() { + labelre = regexp.MustCompile(TODO_LABEL_REGEX) +} + +func main() { + var ( + updateC = make(chan os.Signal, 1) + cancelC = make(chan os.Signal, 1) + ) + signal.Notify(updateC, unix.SIGUSR1, unix.SIGUSR2, unix.SIGHUP) + signal.Notify(cancelC, unix.SIGTERM, unix.SIGINT) + + var err error + // we will receive a SIGTERM if our parent dies + err = unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(unix.SIGTERM), 0, 0, 0) + if err != nil { + log.Fatalf("error: setting prctl: %v", err) + } + + if todofile == "" { + // get from nb(1) + nb := exec.Command("sh", "-c", "nb show --no-color --path todo") + nb_output, err := nb.Output() + if err != nil { + e := err.(*exec.ExitError) + log.Fatalf("error: finding todofile via nb: %s", e.Stderr) + } + todofile = strings.TrimSpace(string(nb_output)) + } + + log.Printf("watching %q", todofile) + + w, err := fsnotify.NewWatcher() + if err != nil { + log.Fatalf("error: creating watcher: %v", err) + } + defer w.Close() + + if err := w.Add(todofile); err != nil { + log.Fatalf("error: adding watch: %v", err) + } + + if err := update(); err != nil { + // fail here because it's the first time we + // try to read the todofile. + log.Fatalf("error: %v", err) + } + for { + select { + case evt, ok := <-w.Events: + if !ok { + return + } + // see https://github.com/fsnotify/fsnotify/issues/94 + // https://github.com/gohugoio/hugo/pull/4720 + if evt.Op&fsnotify.Remove == fsnotify.Remove { + counter := 0 + for w.Add(todofile) != nil { + counter++ + if counter >= 1000 { + log.Panic("error: unable to add file watcher") + } + time.Sleep(100 * time.Millisecond) + } + } + goto force_update + case err, ok := <-w.Errors: + if !ok { + return + } + log.Printf("error: from watcher: %v", err) + + case sig := <-updateC: + switch sig { + case unix.SIGUSR1: // on + active = !active + if err := setCookie(active); err != nil { + log.Printf("err: %v", err) + } + goto force_update + case unix.SIGUSR2: // + if strings.Contains(labelFilter, "work") { + labelFilter = strings.Trim(strings.ReplaceAll(labelFilter, "work", ""), ",") + } else { + labelFilter = strings.Join([]string{labelFilter, "work"}, ",") + } + continue + case unix.SIGHUP: // force update + log.Println("force update") + goto force_update + } + case <-cancelC: + log.Printf("%s exiting", os.Args[0]) + os.Exit(0) + } + force_update: + _ = update() + } +} -- cgit 1.4.1