aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorterminaldweller <devi@terminaldweller.com>2023-05-10 13:59:57 +0000
committerterminaldweller <devi@terminaldweller.com>2023-05-10 13:59:57 +0000
commit9abd509ea355d28734082bb4d25ea90004c12f0a (patch)
tree0b07e7422ba862e1eb37781977b7143870144b20
parentin-memory wip (diff)
downloadlclip-9abd509ea355d28734082bb4d25ea90004c12f0a.tar.gz
lclip-9abd509ea355d28734082bb4d25ea90004c12f0a.zip
lclipd is now basically non-blocking all the way through, no more zombie childs, the no in-memory yet, WIP
-rw-r--r--README.md33
-rwxr-xr-xlclipd.lua113
2 files changed, 95 insertions, 51 deletions
diff --git a/README.md b/README.md
index 3d55847..ef5b957 100644
--- a/README.md
+++ b/README.md
@@ -2,17 +2,13 @@
A minimal 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.</br>
-
-lclipd keeps the clipboard content database in the `tmp` directory.</br>
-
-Both X11 and wayland are supported.</br>
-
-lclipd is meant to be run as a user service. It can also be simply run by just running the script directly.</br>
-
-lclipd puts its logs in the system log.</br>
-
-lclipd does not require elevated privileges to run.</br>
+* lclipd runs the clipboard contents through `detect-secrets` and then puts the content in a sqlite3 database
+* lclipd keeps the clipboard content database in the `tmp` directory
+* Both X11 and wayland 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)
## Requirements
* lua5.3
@@ -37,15 +33,24 @@ lclipd is technically just the "backend". One way to have a frontend is to use d
#!/usr/bin/env sh
SQL_DB="$(cat /tmp/lclipd/lclipd_db_name)"
-content=$(sqlite3 "${SQL_DB}" "select replace(content,char(10),' '),id from lclipd;" | dmenu -fn "DejaVuSansMono Nerd Font Mono-11.3;antialias=true;autohint=true" -D "|" -l 20 -p "lclipd:")
+content=$(sqlite3 "${SQL_DB}" "select replace(content,char(10),' '),id from lclipd;" | dmenu -D "|" -l 20 -p "lclipd:")
sqlite3 "${SQL_DB}" "select content from lclipd where id = ${content}" | xsel -ib
```
For the above to work you have to have added the dynamic patch to dmenu.</br>
+One way to query the db through the TCP socket is like this:
+```sh
+echo 'select * from lclipd;' > ./cmd.sql
+nc -v 127.0.0.1:9999 < ./cmd.sql
+```
+The TCP server will return a JSON array as a response.</br>
+You can use `jq` or `jaq` for further processing of the returned JSON object on the shell.</br>
+
## Options
```
Usage: ./lclipd.lua [-h] [-s <hist_size>] [-d <detect_secrets_args>]
+ [-a <address>] [-p <port>]
Options:
-h, --help Show this help message and exit.
@@ -55,6 +60,10 @@ Options:
-d <detect_secrets_args>,
--detect_secrets_args <detect_secrets_args>
options that will be passed to detect secrets (default: )
+ -a <address>, address to bind to (default: 127.0.0.1)
+ --address <address>
+ -p <port>, port to bind to
+ --port <port>
```
## Supported OSes
diff --git a/lclipd.lua b/lclipd.lua
index bba5c05..6c03af5 100755
--- a/lclipd.lua
+++ b/lclipd.lua
@@ -89,6 +89,8 @@ local pid_file = "/tmp/lclipd/lclipd.pid"
--- We are not longer running.
local function remove_pid_file() if wrote_a_pidfile then os.remove(pid_file) end end
+--- exits lclipd, effectively killing all children
+-- @param n the exit status code
local function lclip_exit(n)
os.exit(n)
remove_pid_file()
@@ -116,6 +118,8 @@ local function log_to_syslog(log_str, log_priority)
posix_syslog.closelog()
end
+--- checks the uid and gid to make sure that we are the same id as the one
+-- that created the db
local function check_uid_gid()
log_to_syslog(tostring(unistd.getuid()) .. ":" .. tostring(unistd.getgid()),
posix_syslog.LOG_INFO)
@@ -145,7 +149,7 @@ end
--- Tries to determine whether another instance is running, if yes, quits
-- obvisouly doing it like this is imprecise but the chances of it failing
-- are very low unless we have a constant known way of calling the script
--- so that we can match for that exactly for the procfs cmdline check.
+-- so that we can match for that exactly in the procfs cmdline check.
local function check_pid_file()
local f = sys_stat.stat(pid_file)
if f ~= nil then
@@ -276,7 +280,6 @@ end
--- Get the sqlite DB handle.
local function get_sqlite_handle()
local clipDB = sqlite3.open("/dev/shm/lclipd")
- -- local clipDB = sqlite3.open("")
if clipDB == nil then
log_to_syslog("could not open the database", posix_syslog.LOG_CRIT)
lclip_exit(1)
@@ -285,7 +288,10 @@ local function get_sqlite_handle()
return clipDB
end
---- Callback function to get the result when we receive a query from the socket
+--- Callback function to get the result when we receive a query from the TCP port
+-- @param conn current TCP connection that we will reply to
+-- @param columns the columns of the query
+-- @param values the value of the query
local function server_query_callback(conn, columns, values, _)
local result_table = {}
for i = 1, columns do result_table[i] = values[i] end
@@ -300,10 +306,10 @@ local function server_query_callback(conn, columns, values, _)
return 0
end
---- Start the lclipd server
--- @param bind_address
--- @param bind_port
-local function run_server(bind_address, bind_port, sqlite_handle)
+--- Starts the lclipd server in a separate process
+-- @param args cli args
+-- @param sqlite_handle db handle
+local function run_server(args, sqlite_handle)
local server_pid, errmsg = unistd.fork()
if server_pid == nil then -- error
log_to_syslog(errmsg, posix_syslog.LOG_CRIT)
@@ -318,8 +324,8 @@ local function run_server(bind_address, bind_port, sqlite_handle)
end
local ret, errmsg = posix_socket.bind(sock, {
- port = bind_port,
- addr = bind_address,
+ port = args["port"],
+ addr = args["address"],
family = posix_socket.AF_INET,
socktype = posix_socket.SOCK_STREAM
})
@@ -333,14 +339,14 @@ local function run_server(bind_address, bind_port, sqlite_handle)
log_to_syslog(errmsg, posix_syslog.LOG_CRIT)
lclip_exit(1)
end
- log_to_syslog("listening on " .. bind_address .. ":" ..
- tostring(bind_port), posix_syslog.LOG_INFO)
+ log_to_syslog("listening on " .. args["address"] .. ":" ..
+ tostring(args["port"]), posix_syslog.LOG_INFO)
while true do
local conn, conn_addr = posix_socket.accept(sock)
if conn == nil then
log_to_syslog(conn_addr, posix_syslog.LOG_CRIT)
- lclip_exit(1)
+ goto server_continue
end
-- we fork on every incoming connection
@@ -378,6 +384,7 @@ local function run_server(bind_address, bind_port, sqlite_handle)
-- and wait on accept for a new incoming connection
end
unistd.close(conn)
+ ::server_continue::
end
elseif server_pid > 0 then -- parent
-- the parent process can just return at this point
@@ -387,9 +394,50 @@ local function run_server(bind_address, bind_port, sqlite_handle)
end
end
+--- handles writing of the clipboard
+-- @pram args the cli args
+-- @pram sqlite_handle db handle
+local function clipboard_writer(args, sqlite_handle)
+ local server_pid, errmsg = unistd.fork()
+ if server_pid == nil then -- error
+ log_to_syslog(errmsg, posix_syslog.LOG_CRIT)
+ lclip_exit(1)
+ elseif server_pid == 0 then
+ local return_code
+ while true do
+ local clip_content = get_clipboard_content()
+ if clip_content == nil then goto continue end
+ -- remove trailing/leading whitespace
+ clip_content = string.gsub(clip_content, '^%s*(.-)%s*$', '%1')
+
+ if clip_content == nil then goto continue end
+ local insert_string = string.format(sql_insert, clip_content)
+
+ local cpid, errmsg = unistd.fork()
+ if cpid == nil then -- error
+ 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
+ return_code = sqlite_handle:exec(insert_string)
+ if return_code ~= sqlite3.OK then
+ log_to_syslog(tostring(return_code),
+ posix_syslog.LOG_WARNING)
+ end
+ end
+ unistd._exit(0)
+ -- parent should just return to wait on the next
+ -- incoming event from clipnotify
+ end
+ ::continue::
+ end
+ elseif server_pid > 0 then
+ return
+ end
+end
+
--- The clipboard's main loop
--- @param clip_hist_size number of entries limit for the clip history file
--- @param detect_secrets_artgs args to pass to detect-secrets scan
+-- @param args the cli args
local function loop(args)
local sqlite_handle = get_sqlite_handle()
@@ -412,26 +460,15 @@ local function loop(args)
lclip_exit(1)
end
- -- fork the server component and give control back to the clipboard
- run_server(args["address"], args["port"], sqlite_handle)
+ -- run the server process
+ run_server(args, sqlite_handle)
+
+ -- run the clipboard writer process
+ clipboard_writer(args, sqlite_handle)
- log_to_syslog("starting the main loop", posix_syslog.LOG_INFO)
while true do
- local clip_content = get_clipboard_content()
- if clip_content == nil then goto continue end
- -- remove trailing/leading whitespace
- clip_content = string.gsub(clip_content, '^%s*(.-)%s*$', '%1')
-
- if clip_content == nil then goto continue end
- local insert_string = string.format(sql_insert, clip_content)
-
- if detect_secrets(clip_content, args["detect_secrets_args"]) then
- return_code = sqlite_handle:exec(insert_string)
- if return_code ~= sqlite3.OK then
- log_to_syslog(tostring(return_code), posix_syslog.LOG_WARNING)
- end
- end
- ::continue::
+ local pid = posix_wait.wait(-1)
+ while pid do pid = posix_wait.wait(-1) end
end
end
@@ -447,15 +484,13 @@ local function main()
io.write("\n")
os.exit(128 + signum)
end)
- -- we reap dead processes so we dont end up with zombies all over.
- -- in our case, we dont really care how a child is terminated as
- -- long as it terminates.
- -- signal.signal(signal.SIGCHILD, function(_)
- -- while posix_wait.wait(-1, posix_wait.WNOHANG) > 0 do end
- -- end)
+ signal.signal(signal.SIGCHLD, function()
+ local pid = posix_wait.wait(-1)
+ while pid do pid = posix_wait.wait(-1) end
+ end)
- make_tmp_dirs()
local args = parser:parse()
+ make_tmp_dirs()
check_pid_file()
write_pid_file()
check_uid_gid()