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:
parent
37c48c263a
commit
c42e1cabf8
6 changed files with 57 additions and 68 deletions
|
@ -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`
|
||||||
|
|
|
@ -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
5
go.mod
|
@ -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
4
go.sum
|
@ -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
24
main.go
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
Loading…
Reference in a new issue