refac!: move from demon to oneshot storer

Breaking change: requires calling with
`wl-paste -t text --watch clipman store`.

As a side-effect, enable primary clipboard support:
`wl-paste -p -t text --watch clipman store
--histpath="~/.local/share/clipman-primary.json`!

Closes #12
This commit is contained in:
yory8 2019-09-16 19:39:53 +02:00
parent 37c48c263a
commit c42e1cabf8
6 changed files with 57 additions and 68 deletions

View file

@ -2,5 +2,6 @@
**Breaking changes**: **Breaking changes**:
- switch from flags to subcommands: `clipman listen` instead than `clipman -d` and `clipman pick` instead than `clipman -s` - switch from flags to subcommands: `wl-paste -t text --watch clipman store` instead than `clipman -d` and `clipman pick` instead than `clipman -s`
- switch demon from polling to event-driven: requires wl-clipboard 2.0 (or latest git HEAD) - switch demon from polling to event-driven: requires wl-clipboard >= 2.0
- primary clipboard support: `wl-paste -p -t text --watch clipman store --histpath="~/.local/share/clipman-primary.json` and `clipman pick --histpath="~/.local/share/clipman-primary.json`

View file

@ -16,9 +16,11 @@ Archlinux users can find a PKGBUILD [here](https://aur.archlinux.org/packages/cl
## Usage ## Usage
Run the binary in your Sway session by adding `exec clipman -d` (or `exec clipman -d 1>> PATH/TO/LOGFILE 2>&1 &` to log errors) at the beginning of your config. Run the binary in your Sway session by adding `exec wl-paste -t text --watch clipman store` (or `exec wl-paste -t text --watch clipman store 1>> PATH/TO/LOGFILE 2>&1 &` to log errors) at the beginning of your config.
For primary clipboard support, also add `exec wl-paste -p -t text --watch clipman store --histpath="~/.local/share/clipman-primary.json`.
To query the history and select items, run the binary as `clipman -s`. You can assign it to a keybinding: `bindsym $mod+h exec clipman -s`. To query the history and select items, run the binary as `clipman pick`. You can assign it to a keybinding: `bindsym $mod+h exec clipman pick`.
For primary clipboard support, `clipman pick --histpath="~/.local/share/clipman-primary.json`.
For more options: `clipman -h`. For more options: `clipman -h`.

5
go.mod
View file

@ -1,13 +1,12 @@
module github.com/yory8/clipman module github.com/yory8/clipman
go 1.12 go 1.13
require ( require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.1.0 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.4.0 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
) )

4
go.sum
View file

@ -1,7 +1,7 @@
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117 h1:aUo+WrWZtRRfc6WITdEKzEczFRlEpfW15NhNeLRc17U=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=

24
main.go
View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -14,10 +15,10 @@ import (
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()
demon = app.Command("listen", "Run as a demon to record clipboard events") storer = app.Command("store", "Run from `wl-paste --watch` to record clipboard events")
picker = app.Command("pick", "Pick an item from clipboard history") picker = app.Command("pick", "Pick an item from clipboard history")
noPersist = demon.Flag("no-persist", "Don't persist a copy buffer after a program exits").Short('P').Default("false").Bool() noPersist = storer.Flag("no-persist", "Don't persist a copy buffer after a program exits").Short('P').Default("false").Bool()
maxDemon = demon.Flag("max-items", "history size").Default("15").Int() maxDemon = storer.Flag("max-items", "history size").Default("15").Int()
maxPicker = picker.Flag("max-items", "scrollview length").Default("15").Int() maxPicker = picker.Flag("max-items", "scrollview length").Default("15").Int()
tool = picker.Flag("selector", "Which selector to use: dmenu/rofi/-").Default("dmenu").String() tool = picker.Flag("selector", "Which selector to use: dmenu/rofi/-").Default("dmenu").String()
) )
@ -25,13 +26,26 @@ var (
func main() { func main() {
app.HelpFlag.Short('h') app.HelpFlag.Short('h')
switch kingpin.MustParse(app.Parse(os.Args[1:])) { switch kingpin.MustParse(app.Parse(os.Args[1:])) {
case "listen": case "store":
persist := !*noPersist persist := !*noPersist
histfile, history, err := getHistory() histfile, history, err := getHistory()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
listen(history, histfile, persist, *maxDemon)
// read copy from stdin
var stdin []string
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
stdin = append(stdin, scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal("Error getting input from stdin.")
}
text := strings.Join(stdin, "\n")
store(text, history, histfile, *maxDemon, persist)
case "pick": case "pick":
_, history, err := getHistory() _, history, err := getHistory()
if err != nil { if err != nil {

View file

@ -4,55 +4,9 @@ import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"log" "log"
"os"
"os/exec" "os/exec"
) )
type historyBuf struct {
buf []string // field name as required by io.Writer, don't change
histfile string
max int
persist bool
}
func (hb *historyBuf) Write(p []byte) (n int, err error) {
hb.buf = store(string(p), hb.buf, hb.histfile, hb.max, hb.persist)
return len(p), err // signature as required by io.Writer, don't change
}
func write(history []string, histfile string) error {
histlog, err := json.Marshal(history)
if err != nil {
return err
}
err = ioutil.WriteFile(histfile, histlog, 0644)
return err
}
func filter(history []string, text string) []string {
var (
found bool
idx int
)
for i, el := range history {
if el == text {
found = true
idx = i
break
}
}
if found {
// we know that idx can't be the last element, because
// we never get to call this function if that's the case
history = append(history[:idx], history[idx+1:]...)
}
return history
}
func store(text string, history []string, histfile string, max int, persist bool) []string { func store(text string, history []string, histfile string, max int, persist bool) []string {
l := len(history) l := len(history)
if l > 0 { if l > 0 {
@ -87,16 +41,35 @@ func store(text string, history []string, histfile string, max int, persist bool
return history return history
} }
func listen(history []string, histfile string, persist bool, max int) { func filter(history []string, text string) []string {
cmd := exec.Command("wl-paste", "-t", "text", "--watch", "cat") var (
cmd.Stdout = &historyBuf{history, histfile, max, persist} found bool
cmd.Stderr = os.Stderr idx int
)
if err := cmd.Start(); err != nil { for i, el := range history {
log.Fatalf("Error running wl-paste (cmd.Start): %s", err) if el == text {
found = true
idx = i
break
}
} }
if err := cmd.Wait(); err != nil { if found {
log.Fatalf("Error running wl-paste (cmd.Wait): %s", err) // we know that idx can't be the last element, because
// we never get to call this function if that's the case
history = append(history[:idx], history[idx+1:]...)
} }
return history
}
func write(history []string, histfile string) error {
histlog, err := json.Marshal(history)
if err != nil {
return err
}
err = ioutil.WriteFile(histfile, histlog, 0644)
return err
} }