feat: add desktop notifications

This commit is contained in:
yory8 2020-02-25 22:24:56 +01:00
parent f58fb5133e
commit 4240d6f017
5 changed files with 71 additions and 18 deletions

View file

@ -1,3 +1,13 @@
# 1.4.0
**New features**
- optional desktop notifications on errors
**Notable bug fixes**
- the toolArgs option now understands complex patterns (spaces, quotes)
# 1.3.0 # 1.3.0
**Breaking changes** **Breaking changes**

View file

@ -1,4 +0,0 @@
- upgrade version number
- in main
- in man page
- update changelog

View file

@ -1,4 +1,4 @@
.TH clipman 1 1.3.0 "" .TH clipman 1 1.4.0 ""
.SH "NAME" .SH "NAME"
clipman clipman
.SH "SYNOPSIS" .SH "SYNOPSIS"
@ -15,6 +15,9 @@ Show context-sensitive help (also try --help-long and --help-man).
\fB--histpath="~/.local/share/clipman.json"\fR \fB--histpath="~/.local/share/clipman.json"\fR
Path of history file Path of history file
.TP .TP
\fB--notify\fR
Send desktop notifications on errors
.TP
\fB-v, --version\fR \fB-v, --version\fR
Show application version. Show application version.
.SH "COMMANDS" .SH "COMMANDS"

26
main.go
View file

@ -7,7 +7,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
@ -15,11 +14,12 @@ import (
"gopkg.in/alecthomas/kingpin.v2" "gopkg.in/alecthomas/kingpin.v2"
) )
const version = "1.3.0" const version = "1.4.0"
var ( var (
app = kingpin.New("clipman", "A clipboard manager for Wayland") app = kingpin.New("clipman", "A clipboard manager for Wayland")
histpath = app.Flag("histpath", "Path of history file").Default("~/.local/share/clipman.json").String() histpath = app.Flag("histpath", "Path of history file").Default("~/.local/share/clipman.json").String()
alert = app.Flag("notify", "Send desktop notifications on errors").Bool()
storer = app.Command("store", "Record clipboard events (run as argument to `wl-paste --watch`)") storer = app.Command("store", "Record clipboard events (run as argument to `wl-paste --watch`)")
maxDemon = storer.Flag("max-items", "history size").Default("15").Int() maxDemon = storer.Flag("max-items", "history size").Default("15").Int()
@ -47,7 +47,7 @@ func main() {
histfile, history, err := getHistory(*histpath) histfile, history, err := getHistory(*histpath)
if err != nil { if err != nil {
log.Fatal(err) smartLog(err.Error(), "critical", *alert)
} }
switch action { switch action {
@ -59,18 +59,18 @@ func main() {
stdin = append(stdin, scanner.Text()) stdin = append(stdin, scanner.Text())
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
log.Fatal("Error getting input from stdin.") smartLog("Couldn't get input from stdin.", "critical", *alert)
} }
text := strings.Join(stdin, "\n") text := strings.Join(stdin, "\n")
persist := !*noPersist persist := !*noPersist
if err := store(text, history, histfile, *maxDemon, persist); err != nil { if err := store(text, history, histfile, *maxDemon, persist); err != nil {
log.Fatal(err) smartLog(err.Error(), "critical", *alert)
} }
case "pick": case "pick":
selection, err := selector(history, *maxPicker, *pickTool, "pick", *pickToolArgs) selection, err := selector(history, *maxPicker, *pickTool, "pick", *pickToolArgs)
if err != nil { if err != nil {
log.Fatal(err) smartLog(err.Error(), "normal", *alert)
} }
if selection != "" { if selection != "" {
@ -79,7 +79,7 @@ func main() {
} }
case "restore": case "restore":
if len(history) == 0 { if len(history) == 0 {
log.Println("Nothing to restore") fmt.Println("Nothing to restore")
return return
} }
@ -88,7 +88,7 @@ func main() {
// remove all history // remove all history
if *clearAll { if *clearAll {
if err := wipeAll(histfile); err != nil { if err := wipeAll(histfile); err != nil {
log.Fatal(err) smartLog(err.Error(), "normal", *alert)
} }
return return
} }
@ -100,7 +100,7 @@ func main() {
selection, err := selector(history, *maxClearer, *clearTool, "clear", *clearToolArgs) selection, err := selector(history, *maxClearer, *clearTool, "clear", *clearToolArgs)
if err != nil { if err != nil {
log.Fatal(err) smartLog(err.Error(), "normal", *alert)
} }
if selection == "" { if selection == "" {
@ -111,7 +111,7 @@ func main() {
// there was only one possible item we could select, and we selected it, // there was only one possible item we could select, and we selected it,
// so wipe everything // so wipe everything
if err := wipeAll(histfile); err != nil { if err := wipeAll(histfile); err != nil {
log.Fatal(err) smartLog(err.Error(), "normal", *alert)
} }
return return
} }
@ -123,7 +123,7 @@ func main() {
} }
if err := write(filter(history, selection), histfile); err != nil { if err := write(filter(history, selection), histfile); err != nil {
log.Fatal(err) smartLog(err.Error(), "critical", *alert)
} }
} }
} }
@ -171,12 +171,12 @@ func getHistory(rawPath string) (string, []string, error) {
func serveTxt(s string) { func serveTxt(s string) {
bin, err := exec.LookPath("wl-copy") bin, err := exec.LookPath("wl-copy")
if err != nil { if err != nil {
log.Printf("couldn't find wl-copy: %v\n", err) smartLog(fmt.Sprintf("couldn't find wl-copy: %v\n", err), "low", *alert)
} }
// we mandate the mime type because we know we can only serve text; not doing this leads to weird bugs like #35 // we mandate the mime type because we know we can only serve text; not doing this leads to weird bugs like #35
cmd := exec.Cmd{Path: bin, Args: []string{bin, "-t", "TEXT"}, Stdin: strings.NewReader(s)} cmd := exec.Cmd{Path: bin, Args: []string{bin, "-t", "TEXT"}, Stdin: strings.NewReader(s)}
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
log.Printf("error running wl-copy: %s\n", err) smartLog(fmt.Sprintf("error running wl-copy: %s\n", err), "low", *alert)
} }
} }

44
notify.go Normal file
View file

@ -0,0 +1,44 @@
package main
import (
"fmt"
"log"
"os/exec"
"time"
)
func smartLog(message, urgency string, alert bool) {
if alert {
if err := notify(message, urgency); err != nil {
log.Printf("failure sending notification: %s\n", err)
}
}
switch urgency {
case "critical", "normal":
log.Fatal(message)
default:
log.Println(message)
}
}
func notify(message string, urgency string) error {
var timeout time.Duration
switch urgency {
// cases accepted by notify-send: low, normal, critical
case "critical":
timeout = 5 * time.Second
case "low":
timeout = 2 * time.Second
default:
timeout = 3 * time.Second
}
// notify-send only accepts milliseconds
millisec := fmt.Sprintf("%v", timeout.Seconds()*1000)
args := []string{"-a", "Clipman", "-u", urgency, "-t", millisec, message}
return exec.Command("notify-send", args...).Run()
}