This commit is contained in:
yory8 2019-03-22 15:13:41 +01:00
commit ecfe78e04d
4 changed files with 168 additions and 0 deletions

17
README.md Normal file
View file

@ -0,0 +1,17 @@
# Clipman
A basic clipboard manager for Wayland.
## Requirements
- a windows manager that uses `wlr-data-control`, like Sway and other wlroots-bases WMs.
- wl-clipboard from latest master (NOT v1.0)
- dmenu
## Usage
The demon that collects history is written in Go. Install it in your path, then run it in your Sway session by adding `exec clipman` at the beginning of your config.
You can configure how many history items to preserve (default: 15) by editing directly the source.
To query the history and select items, run the provided python script (`clipman.py`). You can assign it to a keybinding.

45
clipman.py Executable file
View file

@ -0,0 +1,45 @@
#!/usr/bin/python
import json
import os
import subprocess
import sys
MAX = 15
def selector(input_, max_):
"""Send list to dmenu for selection. Display max items."""
cmd = [
"dmenu",
"-b",
"-fn",
"-misc-dejavu sans mono-medium-r-normal--17-120-100-100-m-0-iso8859-16",
"-l",
str(max_),
]
chosen = (
subprocess.run(
cmd, input="\n".join(input_).encode("utf-8"), capture_output=True
)
.stdout.decode()
.strip()
)
return chosen
def main():
try:
with open(os.path.expanduser("~/.local/share/clipman.json")) as f:
history = json.load(f)
except FileNotFoundError:
sys.exit("No history available")
history = [repr(x) for x in reversed(history)] # don't expand newlines
selected = selector(history, MAX)
subprocess.run(["wl-copy", selected])
if __name__ == "__main__":
main()

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module clipman
go 1.12

103
main.go Normal file
View file

@ -0,0 +1,103 @@
package main
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"time"
)
const max = 15
const sleep = 1 * time.Second
func write(history []string, histf string) error {
histlog, err := json.Marshal(history)
if err != nil {
return err
}
err = ioutil.WriteFile(histf, histlog, 0644)
if err != nil {
return err
}
return nil
}
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 main() {
h, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
histf := path.Join(h, ".local/share/clipman.json")
var history []string
b, err := ioutil.ReadFile(histf)
if err == nil {
if err := json.Unmarshal(b, &history); err != nil {
log.Fatal(err)
}
}
for {
t, err := exec.Command("wl-paste", []string{"-n", "-t", "text"}...).Output()
text := string(t)
if err != nil || text == "" {
// there's nothing to select, so we sleep.
time.Sleep(sleep)
continue
}
l := len(history)
// wl-paste will always give back the last copied text
// (as long as the place we copied from is still running)
if history[l-1] == text {
time.Sleep(sleep)
continue
}
if l == max {
// we know that at any given time len(history) cannot be bigger than max,
// so it's enough to drop the first element
history = history[1:]
}
// remove duplicates
history = filter(history, text)
history = append(history, text)
// dump history to file so that other apps can query it
err = write(history, histf)
if err != nil {
log.Fatal(err)
}
time.Sleep(sleep)
}
}