first
This commit is contained in:
commit
ecfe78e04d
4 changed files with 168 additions and 0 deletions
17
README.md
Normal file
17
README.md
Normal 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
45
clipman.py
Executable 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
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module clipman
|
||||||
|
|
||||||
|
go 1.12
|
103
main.go
Normal file
103
main.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue