commit ecfe78e04d1f0d0b693dbffca4d218363230f86c Author: yory8 <> Date: Fri Mar 22 15:13:41 2019 +0100 first diff --git a/README.md b/README.md new file mode 100644 index 0000000..357fef6 --- /dev/null +++ b/README.md @@ -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. diff --git a/clipman.py b/clipman.py new file mode 100755 index 0000000..a30ff64 --- /dev/null +++ b/clipman.py @@ -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() diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d170492 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module clipman + +go 1.12 diff --git a/main.go b/main.go new file mode 100644 index 0000000..018c173 --- /dev/null +++ b/main.go @@ -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) + } +}