From 0ab9be934fad7db7e66acef0829900fbee95d813 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Thu, 26 May 2022 02:57:50 +0500 Subject: [PATCH] Add ueberzug support --- Makefile | 12 +++-- clear.sh | 7 +++ ctpv.c | 49 ++++++++++++++---- ctpvclear | 3 ++ end.sh | 6 +++ error.h | 12 +++++ helpers.sh | 10 ++++ prev/image.sh | 13 +++++ preview.c | 24 ++++----- previews.h | 1 + server.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++ server.h | 8 +++ shell.h | 7 +++ utils.c | 33 +++++++----- utils.h | 3 ++ 15 files changed, 284 insertions(+), 41 deletions(-) create mode 100644 clear.sh create mode 100755 ctpvclear create mode 100644 end.sh create mode 100644 prev/image.sh create mode 100644 server.c create mode 100644 server.h create mode 100644 shell.h diff --git a/Makefile b/Makefile index a140fdc..5a40ffd 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ SRC := $(wildcard *.c) OBJ := $(SRC:.c=.o) DEP := $(OBJ:.o=.d) PRE := $(wildcard prev/*.sh) -GEN := gen/prev/scripts.h +GEN := gen/prev/scripts.h gen/server.h CFLAGS += -Os -MD -Wall -Wextra -Wno-unused-parameter LDFLAGS += -lmagic @@ -17,9 +17,9 @@ options: @echo "CFLAGS = $(CFLAGS)" @echo "LDFLAGS = $(LDFLAGS)" -install: ctpv +install: ctpv ctpvclear install -d $(BINPREFIX) - install $< $(BINPREFIX) + install $^ $(BINPREFIX) uninstall: $(RM) $(BINPREFIX)/ctpv @@ -35,10 +35,14 @@ ctpv: $(OBJ) ctpv.c: $(GEN) -gen/prev/scripts.h: embed/embed $(PRE) +gen/prev/scripts.h: $(PRE) embed/embed helpers.sh @mkdir -p $(@D) embed/embed -p prev_scr_ -h helpers.sh $(PRE) > $@ +gen/server.h: clear.sh end.sh embed/embed helpers.sh + @mkdir -p $(@D) + embed/embed -p scr_ -h helpers.sh clear.sh end.sh > $@ + embed/embed: make_embed @: diff --git a/clear.sh b/clear.sh new file mode 100644 index 0000000..a788662 --- /dev/null +++ b/clear.sh @@ -0,0 +1,7 @@ +fifo="$(get_fifo "$id")" + +[ -e "$fifo" ] || exit 1 + +fifo_open "$fifo" && { + printf '{"action": "remove", "identifier": "preview"}\n' > "$fifo" +} diff --git a/ctpv.c b/ctpv.c index 0d62a2c..cca262a 100644 --- a/ctpv.c +++ b/ctpv.c @@ -1,10 +1,13 @@ +#include +#include #include #include -#include +#include #include -#include #include "error.h" +#include "server.h" +#include "preview.h" #include "previews.h" #define ANY_TYPE "*" @@ -17,10 +20,13 @@ static struct { enum { MODE_PREVIEW, MODE_SERVER, + MODE_CLEAR, + MODE_END, MODE_LIST, MODE_MIME, } mode; -} ctpv = { MODE_PREVIEW }; + char *server_id_s; +} ctpv = { .mode = MODE_PREVIEW }; static void cleanup(void) { cleanup_previews(); @@ -108,15 +114,22 @@ static int preview(int argc, char *argv[]) PreviewArgs args = { .f = f, .w = w, .h = h, .x = x, .y = y }; - ERRCHK_RET_OK(run_preview(get_ext(f), mimetype, &args)); - - return OK; + return run_preview(get_ext(f), mimetype, &args); } -static int server(void) +static int server(char const *id_s) { - /* TODO */ - return OK; + return server_listen(id_s); +} + +static int clear(void) +{ + return server_clear(); +} + +static int end(char const *id_s) +{ + return server_end(id_s); } static int list(void) @@ -179,10 +192,18 @@ int main(int argc, char *argv[]) program = argc > 0 ? argv[0] : "ctpv"; int c; - while ((c = getopt(argc, argv, "slm")) != -1) { + while ((c = getopt(argc, argv, "s:ce:lm")) != -1) { switch (c) { case 's': ctpv.mode = MODE_SERVER; + ctpv.server_id_s = optarg; + break; + case 'c': + ctpv.mode = MODE_CLEAR; + break; + case 'e': + ctpv.mode = MODE_END; + ctpv.server_id_s = optarg; break; case 'l': ctpv.mode = MODE_LIST; @@ -204,7 +225,13 @@ int main(int argc, char *argv[]) ret = preview(argc, argv); break; case MODE_SERVER: - ret = server(); + ret = server(ctpv.server_id_s); + break; + case MODE_CLEAR: + ret = clear(); + break; + case MODE_END: + ret = end(ctpv.server_id_s); break; case MODE_LIST: ret = list(); diff --git a/ctpvclear b/ctpvclear new file mode 100755 index 0000000..8e8f715 --- /dev/null +++ b/ctpvclear @@ -0,0 +1,3 @@ +#!/bin/sh + +exec ctpv -c diff --git a/end.sh b/end.sh new file mode 100644 index 0000000..32135ba --- /dev/null +++ b/end.sh @@ -0,0 +1,6 @@ +fifo="$(get_fifo "$1")" + +[ -e "$fifo" ] || exit 1 + +# sending zero byte tells listener to stop +fifo_open "$fifo" && printf '\0' > "$fifo" diff --git a/error.h b/error.h index 02cb894..68f1e94 100644 --- a/error.h +++ b/error.h @@ -33,12 +33,24 @@ } \ } while (0) +#define ERRCHK_GOTO(cond, ret, label, ...) \ + do { \ + if (cond) { \ + __VA_OPT__(PRINTINTERR(__VA_ARGS__);) \ + ret = ERR; \ + goto label; \ + } \ + } while (0) + /* * Shortcut for ERRCHK_RET(expr != OK) */ #define ERRCHK_RET_OK(expr, ...) \ ERRCHK_RET((expr) != OK __VA_OPT__(, ) __VA_ARGS__) +#define ERRCHK_GOTO_OK(expr, ...) \ + ERRCHK_GOTO((expr) != OK __VA_OPT__(, ) __VA_ARGS__) + enum { OK, ERR, diff --git a/helpers.sh b/helpers.sh index ca80e25..40bb00a 100644 --- a/helpers.sh +++ b/helpers.sh @@ -1,3 +1,13 @@ +get_fifo() { + printf '/tmp/ctpvfifo.%s' "$1" +} + exists() { command -v "$1" > /dev/null } + +fifo_open() { + # https://unix.stackexchange.com/a/522940/183147 + dd oflag=nonblock conv=notrunc,nocreat count=0 of="$1" \ + >/dev/null 2>/dev/null +} diff --git a/prev/image.sh b/prev/image.sh new file mode 100644 index 0000000..973a153 --- /dev/null +++ b/prev/image.sh @@ -0,0 +1,13 @@ +fifo="$(get_fifo "$id")" + +# tell ctpv to fallback to another preview +[ -e "$fifo" ] || exit 127 + +path="$(printf '%s' "$f" | sed 's/\\/\\\\/g; s/"/\\"/g')" + +fifo_open "$fifo" && { + printf '{ "action": "add", "identifier": "preview", "x": %d, "y": %d, "width": %d, "height": %d, "scaler": "contain", "scaling_position_x": 1, "scaling_position_y": 1, "path": "%s"}\n' "$x" "$y" "$w" "$h" "$path" > "$fifo" +} + +# tell lf to disable preview caching +exit 1 diff --git a/preview.c b/preview.c index f273d8e..99d5d90 100644 --- a/preview.c +++ b/preview.c @@ -3,14 +3,13 @@ #include "utils.h" #include "error.h" +#include "shell.h" #include "preview.h" -#define FAILED_PREVIEW_EC 127 +#define FAILED_PREVIEW_EC NOTEXIST_EC #define PREVP_SIZE sizeof(Preview *) -static char shell[] = "sh"; - static struct { size_t len; Preview **list; @@ -119,21 +118,20 @@ static void check_init_previews(void) static int run(Preview *p, int *exitcode) { - int pipe_fd[2]; - ERRCHK_RET(pipe(pipe_fd) == -1, FUNCFAILED("pipe"), ERRNOS); + int pipe_fds[2]; + ERRCHK_RET(pipe(pipe_fds) == -1, FUNCFAILED("pipe"), ERRNOS); - int fd = STDERR_FILENO; - int *sp_arg[] = { pipe_fd, &fd }; + int sp_arg[] = { pipe_fds[0], pipe_fds[1], STDERR_FILENO }; - char *args[] = { shell, "-c", p->script, shell, NULL }; + char *args[] = SHELL_ARGS(p->script); int ret = spawn(args, NULL, exitcode, spawn_redirect, sp_arg); - close(pipe_fd[1]); + close(pipe_fds[1]); if (*exitcode != FAILED_PREVIEW_EC) { char buf[CMD_ERR_BUF]; int len; - while ((len = read(pipe_fd[0], buf, CMD_ERR_BUF)) > 0) { + while ((len = read(pipe_fds[0], buf, CMD_ERR_BUF)) > 0) { write(STDOUT_FILENO, buf, len); } @@ -143,7 +141,7 @@ static int run(Preview *p, int *exitcode) } } - close(pipe_fd[0]); + close(pipe_fds[0]); return ret; } @@ -176,7 +174,7 @@ run: p = find_preview(mimetype, ext, &i); if (!p) { puts("ctpv: no previews found"); - return OK; + return ERR; } ERRCHK_RET_OK(run(p, &exitcode)); @@ -185,7 +183,7 @@ run: goto run; } - return OK; + return exitcode == 0 ? OK : ERR; } Preview **get_previews_list(size_t *len) diff --git a/previews.h b/previews.h index ce7f596..e358449 100644 --- a/previews.h +++ b/previews.h @@ -17,4 +17,5 @@ Preview previews[] = { PR(NULL, NULL, NULL, any), PR("md", NULL, NULL, markdown), PR(NULL, "application", "json", json), + PR(NULL, "image", NULL, image), }; diff --git a/server.c b/server.c new file mode 100644 index 0000000..9d22473 --- /dev/null +++ b/server.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "utils.h" +#include "shell.h" +#include "gen/server.h" + +static pid_t ueberzug_pid; + +static void kill_ueberzug(void) +{ + if (kill(ueberzug_pid, SIGTERM) == -1) { + if (errno == ESRCH) + print_error("ueberzug is not running"); + else + PRINTINTERR(FUNCFAILED("kill"), ERRNOS); + } + + spawn_wait(ueberzug_pid, NULL); +} + +static void sig_int_handler(int s) +{ + /* Do nothing */ +} + +static int listen(int fifo_fd) +{ + int ret = OK; + + ERRCHK_GOTO(signal(SIGINT, sig_int_handler) == SIG_ERR, ret, exit, + FUNCFAILED("signal"), ERRNOS); + + int pipe_fds[2]; + ERRCHK_GOTO(pipe(pipe_fds) == -1, ret, signal, FUNCFAILED("pipe"), ERRNOS); + + char *args[] = { "ueberzug", "layer", NULL }; + int sp_arg[] = { pipe_fds[1], pipe_fds[0], STDIN_FILENO }; + if (spawn(args, &ueberzug_pid, NULL, spawn_redirect, sp_arg) != OK) + ret = ERR; + + close(pipe_fds[0]); + + /* If spawn() failed */ + if (ret != OK) + goto close; + + struct pollfd pollfd = { .fd = fifo_fd, .events = POLLIN }; + + int poll_ret, len; + while ((poll_ret = poll(&pollfd, 1, -1) > 0)) { + static char buf[1024]; + while ((len = read(fifo_fd, buf, LEN(buf))) > 0) { + /* First zero byte means that ctpv -e $id was run */ + if (buf[0] == 0) + goto close; + write(pipe_fds[1], buf, len); + } + } + + ERRCHK_GOTO(poll_ret < 0, ret, close, FUNCFAILED("poll"), ERRNOS); + +close: + close(pipe_fds[1]); + kill_ueberzug(); + +signal: + signal(SIGINT, SIG_DFL); + +exit: + return ret; +} + +static int check_ueberzug(int *exitcode) +{ + char *args[] = SHELL_ARGS("command -v ueberzug > /dev/null"); + return spawn(args, NULL, exitcode, NULL, NULL); +} + +int server_listen(char const *id_s) +{ + int ret = OK; + + int exitcode; + ERRCHK_GOTO_OK(check_ueberzug(&exitcode), ret, exit); + + if (exitcode == 127) { + print_error("ueberzug is not installed"); + goto exit; + } + + char fifo[256]; + snprintf(fifo, LEN(fifo)-1, "/tmp/ctpvfifo.%s", id_s); + + ERRCHK_GOTO(mkfifo(fifo, 0600) == -1 && errno != EEXIST, ret, exit, + FUNCFAILED("mkfifo"), ERRNOS); + + int fifo_fd; + ERRCHK_GOTO((fifo_fd = open(fifo, O_RDONLY | O_NONBLOCK)) == -1, ret, fifo, + FUNCFAILED("open"), ERRNOS); + + ERRCHK_GOTO_OK(listen(fifo_fd), ret, fifo_fd); + +fifo_fd: + close(fifo_fd); + +fifo: + if (remove(fifo) == -1) + PRINTINTERR(FUNCFAILED("remove"), ERRNOS); + +exit: + return ret; +} + +int server_clear(void) +{ + char *args[] = SHELL_ARGS(scr_clear_sh); + int exitcode; + ERRCHK_RET_OK(spawn(args, NULL, &exitcode, NULL, NULL)); + + return OK; +} + +int server_end(const char *id_s) +{ + char *args[] = SHELL_ARGS(scr_end_sh, (char *)id_s); + int exitcode; + ERRCHK_RET_OK(spawn(args, NULL, &exitcode, NULL, NULL)); + + return OK; +} diff --git a/server.h b/server.h new file mode 100644 index 0000000..4eb286a --- /dev/null +++ b/server.h @@ -0,0 +1,8 @@ +#ifndef SERVER_H +#define SERVER_H + +int server_listen(char const *id_s); +int server_clear(void); +int server_end(const char *id_s); + +#endif diff --git a/shell.h b/shell.h new file mode 100644 index 0000000..faa0743 --- /dev/null +++ b/shell.h @@ -0,0 +1,7 @@ +#ifndef SHELL_H +#define SHELL_H + +#define SHELL_ARGS(script, ...) \ + { "sh", "-c", script, "sh", __VA_ARGS__ __VA_OPT__(,) NULL } + +#endif diff --git a/utils.c b/utils.c index bc31669..c468761 100644 --- a/utils.c +++ b/utils.c @@ -10,12 +10,21 @@ char *program; int spawn_redirect(const void *arg) { - int **fds = (int **)arg; - int *pipe = fds[0]; - int *fd = fds[1]; + int *fds = (int *)arg; - ERRCHK_RET(close(pipe[0]) == -1, FUNCFAILED("close"), ERRNOS); - ERRCHK_RET(dup2(pipe[1], *fd) == -1, FUNCFAILED("dup2"), ERRNOS); + ERRCHK_RET(close(fds[0]) == -1, FUNCFAILED("close"), ERRNOS); + ERRCHK_RET(dup2(fds[1], fds[2]) == -1, FUNCFAILED("dup2"), ERRNOS); + + return OK; +} + +int spawn_wait(pid_t pid, int *exitcode) +{ + int stat; + ERRCHK_RET(waitpid(pid, &stat, 0) == -1, FUNCFAILED("waitpid"), ERRNOS); + + if (exitcode && WIFEXITED(stat)) + *exitcode = WEXITSTATUS(stat); return OK; } @@ -43,20 +52,18 @@ int spawn(char *args[], pid_t *cpid, int *exitcode, int (*cfunc)(const void *), exit(EXIT_FAILURE); execvp(args[0], args); + if (errno == ENOENT) + exit(NOTEXIST_EC); + PRINTINTERR(FUNCFAILED("exec"), ERRNOS); exit(EXIT_FAILURE); } - if (cpid) { + if (cpid) *cpid = pid; - } else { - int stat; - ERRCHK_RET(waitpid(pid, &stat, 0) == -1, FUNCFAILED("waitpid"), - ERRNOS); - if (exitcode && WIFEXITED(stat)) - *exitcode = WEXITSTATUS(stat); - } + if (exitcode) + ERRCHK_RET_OK(spawn_wait(pid, exitcode)); return OK; } diff --git a/utils.h b/utils.h index 28bd736..860e987 100644 --- a/utils.h +++ b/utils.h @@ -4,6 +4,8 @@ #include #include +#define NOTEXIST_EC 127 + #define LEN(a) (sizeof(a) / sizeof((a)[0])) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) @@ -27,6 +29,7 @@ typedef struct { extern char *program; int spawn_redirect(const void *arg); +int spawn_wait(pid_t pid, int *exitcode); int spawn(char *args[], pid_t *cpid, int *exitcode, int (*cfunc)(const void *), const void *carg);