diff options
-rw-r--r-- | README.md | 47 | ||||
-rwxr-xr-x | lclipd.lua | 67 |
2 files changed, 91 insertions, 23 deletions
@@ -3,31 +3,30 @@ A small clipboard manager in lua.</br> # How it works * lclipd runs the clipboard contents through `detect-secrets` and then puts the content in a sqlite3 database -* Both X11 and wayland are supported +* X11, Wayland, Tmux and custom clipboard commands are supported * it is meant to be run as a user service. It can also be simply run by just running the script directly * the logs are put in the system log * lclipd does not require elevated privileges to run nor does it need to have extra capabilities -* exposes a TCP server which you can use to query the db(on localhost:9999 by default) +* exposes a TCP server(yes a TCP server, not an HTTP server) which you can use to query the db(on localhost:9999 by default) ## Requirements * lua5.3 * [luaposix](https://github.com/luaposix/luaposix) * [argparse](https://github.com/mpeterv/argparse) -* [luasqlite3](http://lua.sqlite.org/index.cgi/home): luasqlite3 comes in two flavours. One includes sqlite3 in the lua rock itself and one does not. If you choose to install the rock without sqlite3 you need to have that installed on your system. +* [luasqlite3](http://lua.sqlite.org/index.cgi/home): luasqlite3 comes in two flavours. One includes sqlite3 in the lua rock itself and one does not. If you choose to install the rock without sqlite3 you need to have sqlite3 installed on your system. * [detect-secrets](https://github.com/Yelp/detect-secrets) -* xclip -* wl-clipboard +* `xclip` ot `wl-clipboard` or whatever clipboard command you use ```sh luarocks install --local luaposix luarocks install --local argparse luarocks install --local lsqlite3 -pip install detect-secrets +pipx install detect-secrets ``` ## Usage -lclipd is technically just the "backend". One way to have a "frontend" is to use dmenu:</br> +lclipd is technically just the "backend". One way to have a "frontend" is to use something like dmenu:</br> ```sh #!/usr/bin/env sh @@ -43,7 +42,7 @@ The TCP server will return a JSON array as a response.</br> You can use something like `jq` for further processing of the returned JSON object on the shell.</br> An example of a terminal-oriented "frontend": ```sh -tmux set-buffer $(echo 'select * from lclipd;' | nc 127.0.0.1 9999 | jq '.[1]' | awk '{print substr($0, 2, length($0) - 2)}' | fzf-tmux) +tmux set-buffer $(echo 'select * from lclipd;' | nc 127.0.0.1 9999 | jq '.[1]' | awk '{print substr($0, 2, length($0) - 2)}' | fzf) ``` The author has this setup in their `.zshrc`: @@ -57,18 +56,25 @@ fzf_lclipd() { zle -N fzf_lclipd bindkey '' fzf_lclipd ``` +You can also put the db on a network share and then have different instanecs on different hosts use the same common db, effectively sharing your clipboard between different devices on the same subnet.</br> ## Options ``` -Usage: ./lclipd.lua [-h] [-s <hist_size>] [-d <detect_secrets_args>] - [-a <address>] [-p <port>] +Usage: ./lclipd.lua [-h] [-s <hist_size>] [-e <detect_secrets_exe>] + [-d <detect_secrets_args>] [-a <address>] [-p <port>] + [-c <custom_clip_command>] [--x_clip_cmd <x_clip_cmd>] + [--wayland_clip_cmd <wayland_clip_cmd>] + [--tmux_clip_cmd <tmux_clip_cmd>] [--db_path <db_path>] Options: -h, --help Show this help message and exit. -s <hist_size>, --hist_size <hist_size> number of distinct entries for clipboard history + -e <detect_secrets_exe>, + --detect_secrets_exe <detect_secrets_exe> + the command used to call detect-secrets (default: detect-secrets) -d <detect_secrets_args>, --detect_secrets_args <detect_secrets_args> options that will be passed to detect secrets (default: ) @@ -76,7 +82,28 @@ Options: --address <address> -p <port>, port to bind to --port <port> + -c <custom_clip_command>, + --custom_clip_command <custom_clip_command> + custom clipboard read command (default: ) + --x_clip_cmd <x_clip_cmd> + the command used to get the X clipboard content (default: xsel -ob) + --wayland_clip_cmd <wayland_clip_cmd> + the command used to get the wayland clipboard content (default: wl-paste) + --tmux_clip_cmd <tmux_clip_cmd> + the command used to get the tmux paste-buffer content (default: tmux show-buffer) + --db_path <db_path> path to the db location,currently :memory: and ''(empty) is not supported (default: /dev/shm/lclipd) ``` ## Supported OSes lcilpd uses luaposix so any POSIX-half-compliant OS will do.</br> + +## Acknowledgements +* [luaposix](https://github.com/luaposix/luaposix) - pretty much all the "heavy lifting" is done using luaposix +* [detect-secrets](https://github.com/Yelp/detect-secrets) +* [luasqlite3](http://lua.sqlite.org/index.cgi/home) +* [json.lua](https://github.com/rxi/json.lua) - used as a vendored dependency +* [argparse](https://github.com/mpeterv/argparse) + +## TODO +* support `in-memory` and `temporary` databases. +* add IPv6 support @@ -76,9 +76,11 @@ local sql_insert = [=[ insert into lclipd(content,dateAdded) values('%s', unixepoch()); ]=] --- using a heredoc string without expansion bypasses the need for escaping +-- the shell command used to call detect-secrets. +-- we are using a heredoc string without expansion to bypass the +-- need for escaping. local detect_secrets_cmd = [=[ -detect-secrets scan %s --string <<- STR | grep True +%s scan %s --string <<- STR | grep True %s STR ]=] @@ -99,10 +101,24 @@ end local parser = argparse() parser:option("-s --hist_size", "number of distinct entries for clipboard history", 200) +parser:option("-e --detect_secrets_exe", + "the command used to call detect-secrets", "detect-secrets") parser:option("-d --detect_secrets_args", "options that will be passed to detect secrets", "") parser:option("-a --address", "address to bind to", "127.0.0.1") parser:option("-p --port", "port to bind to", 9999) +parser:option("-c --custom_clip_command", "custom clipboard read command", "") +parser:option("--x_clip_cmd", "the command used to get the X clipboard content", + "xsel -ob") +parser:option("--wayland_clip_cmd", + "the command used to get the wayland clipboard content", + "wl-paste") +parser:option("--tmux_clip_cmd", + "the command used to get the tmux paste-buffer content", + "tmux show-buffer") +parser:option("--db_path", + "path to the db location,currently :memory: and ''(empty) is not supported", + "/dev/shm/lclipd") --- Log the given string to syslog with the given priority. -- @param log_str the string passed to the logging facility @@ -190,7 +206,7 @@ end -- returns true if the string is not a secret -- @param clipboard_content the content that will be checked against detect-secrets -- @param detect_secrets_arg extra args that will be passed to detect-secrets scan -local function detect_secrets(clipboard_content, detect_secrets_args) +local function detect_secrets(clipboard_content, args) if clipboard_content == nil or clipboard_content == "" then return false end local pipe_read, pipe_write = unistd.pipe() if pipe_read == nil then @@ -209,8 +225,9 @@ local function detect_secrets(clipboard_content, detect_secrets_args) lclip_exit(1) elseif pid == 0 then -- child unistd.close(pipe_read) - local cmd = string.format(detect_secrets_cmd, detect_secrets_args, - clipboard_content) + local cmd = string.format(detect_secrets_cmd, + args["detect_secrets_exe"], + args["detect_secrets_args"], clipboard_content) local ret = os.execute(cmd) if ret == 0 then unistd.write(pipe_write, "0") @@ -235,7 +252,7 @@ local function detect_secrets(clipboard_content, detect_secrets_args) end --- Get the clipboard content from X or wayland. -local function get_clipboard_content() +local function get_clipboard_content(args) -- if we use a plain os.execute for clipnotify the parent wont get the -- SIGINT when it is passed.clipnotify will end up getting it. -- if we fork though, the parent receives the SIGINT just fine. @@ -255,7 +272,8 @@ local function get_clipboard_content() -- we dont care whether all the calls to the different clipboard apps -- succeed or not so we just ignore the errors. - local _, handle_x = pcall(io.popen, "xsel -ob") + -- X + local _, handle_x = pcall(io.popen, args["x_clip_cmd"]) if handle_x ~= nil then local last_clip_entry_x = handle_x:read("*a") handle_x:close() @@ -264,7 +282,8 @@ local function get_clipboard_content() end end - local _, handle_w = pcall(io.popen, "wl-paste") + -- wayland + local _, handle_w = pcall(io.popen, args["wayland_clip_cmd"]) if handle_w ~= nil then local last_clip_entry_w = handle_w:read("*a") handle_w:close() @@ -273,13 +292,35 @@ local function get_clipboard_content() end end + -- tmux + local _, handle_t = pcall(io.popen, args["tmux_clip_cmd"]) + if handle_t ~= nil then + local last_clip_entry_t = handle_t:read("*a") + handle_t:close() + if last_clip_entry_t ~= "" and last_clip_entry_t ~= nil then + return last_clip_entry_t + end + end + + -- custom + if args["custom_clip_command"] ~= "" then + local _, handle_c = pcall(io.popen, args["custom_clip_command"]) + if handle_c ~= nil then + local last_clip_entry_c = handle_c:read("*a") + handle_c:close() + if last_clip_entry_c ~= "" and last_clip_entry_c ~= nil then + return last_clip_entry_c + end + end + end + return nil end end --- Get the sqlite DB handle. -local function get_sqlite_handle() - local clipDB = sqlite3.open("/dev/shm/lclipd") +local function get_sqlite_handle(db_path) + local clipDB = sqlite3.open(db_path) if clipDB == nil then log_to_syslog("could not open the database", posix_syslog.LOG_CRIT) lclip_exit(1) @@ -405,7 +446,7 @@ local function clipboard_writer(args, sqlite_handle) elseif server_pid == 0 then local return_code while true do - local clip_content = get_clipboard_content() + local clip_content = get_clipboard_content(args) if clip_content == nil then goto continue end -- remove trailing/leading whitespace clip_content = string.gsub(clip_content, '^%s*(.-)%s*$', '%1') @@ -418,7 +459,7 @@ local function clipboard_writer(args, sqlite_handle) log_to_syslog(errmsg, posix_syslog.LOG_CRIT) lclip_exit(1) elseif cpid == 0 then -- child - if detect_secrets(clip_content, args["detect_secrets_args"]) then + if detect_secrets(clip_content, args) then return_code = sqlite_handle:exec(insert_string) if return_code ~= sqlite3.OK then log_to_syslog(tostring(return_code), @@ -439,7 +480,7 @@ end --- The clipboard's main loop -- @param args the cli args local function loop(args) - local sqlite_handle = get_sqlite_handle() + local sqlite_handle = get_sqlite_handle(args["db_path"]) -- create the table if it does not exist local return_code = sqlite_handle:exec(sql_create_table) |