tquery

Interactive command query tool
git clone https://git.sinitax.com/sinitax/tquery
Log | Files | Refs | LICENSE | sfeed.txt

commit 00593807c8609425c47bc9052360bb945a4327b1
parent dcbd58ec1f3bae1d5bbeccbdb309853564f3392b
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sun,  3 Mar 2024 06:34:14 +0100

Add external hook for custom keybinds

Diffstat:
Mtquery.c | 318++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
1 file changed, 176 insertions(+), 142 deletions(-)

diff --git a/tquery.c b/tquery.c @@ -2,6 +2,7 @@ #include <sys/wait.h> #include <sys/poll.h> +#include <sys/fcntl.h> #include <limits.h> #include <signal.h> #include <dirent.h> @@ -25,8 +26,21 @@ struct nav { ssize_t sel, min, max; }; +static void sigint_handler(int sig); + +static const struct sigaction sigint_action = { + .sa_flags = SA_RESTART, + .sa_handler = sigint_handler +}; + static int child_fd = -1; static pid_t child_pid = 0; +static const char **child_argv = NULL; + +static pid_t hook_pid = -1; +static char hook_key_arg[6] = { 0 }; +static char *hook_argv[4] = { NULL, hook_key_arg, NULL, NULL }; +static bool hook_io = false; static char *loadbuf = NULL; @@ -43,13 +57,11 @@ static struct nav output_nav; static char *inputbuf = NULL; static size_t inputlen = 0; -static bool splitargs = false; -static bool single = false; -static bool childerr = false; +static bool split_args = false; +static bool oneshot = false; +static bool errlog = false; static char delim = '\n'; -static const char **child_argv = NULL; - static void die(const char *fmt, ...) { @@ -72,19 +84,6 @@ die(const char *fmt, ...) } static void -sigint(int sig) -{ - if (child_pid > 0) { - kill(child_pid, SIGKILL); - waitpid(child_pid, NULL, 0); - child_pid = 0; - signal(SIGINT, sigint); - } else { - exit(0); - } -} - -static void nav_update_win(struct nav *nav) { nav->wmin = MAX(nav->wmax - nav->wlen, nav->min); @@ -131,118 +130,37 @@ nav_update_sel(struct nav *nav, ssize_t sel) } } -static char * -findbin(const char *name) -{ - static char buf[PATH_MAX]; - char *path, *tok, *ntok, *end; - struct dirent *ent; - DIR *dir; - - path = getenv("PATH"); - if (!path) return NULL; - - ntok = tok = path; - while (ntok) { - ntok = strchr(tok, ':'); - end = ntok ? ntok : tok + strlen(tok); - strncpy(buf, tok, (size_t) (end - tok)); - buf[end - tok] = '\0'; - tok = ntok + 1; - - dir = opendir(buf); - if (!dir) continue; - - while ((ent = readdir(dir))) { - if (!strcmp(ent->d_name, name)) { - closedir(dir); - if (strlen(buf) + 1 + strlen(name) >= PATH_MAX) - return NULL; - strcat(buf, "/"); - strcat(buf, name); - return buf; - } - } - - closedir(dir); - } - - return NULL; -} - static void -spawn(const char **prefix, const char *query) +invoke(const char **argv, pid_t *pid, int *fd, bool in, bool out, bool err) { - static const char *argv[64]; - const char **arg; - const char *tok, *ntok, *end; int out_pipe[2]; - size_t argc; + int zfd; - delims_len = 0; - output_len = 0; - - if (child_fd >= 0) { - close(child_fd); - child_fd = -1; - } - - if (child_pid > 0) { - kill(child_pid, SIGKILL); - waitpid(child_pid, NULL, 0); - child_pid = 0; + if (fd && *fd >= 0) { + close(*fd); + *fd = -1; } if (pipe(out_pipe)) die("pipe:"); - child_pid = fork(); - if (child_pid < 0) die("fork:"); - if (!child_pid) { - close(0); - if (childerr) { - close(1); - } else { - close(1); - close(2); - } - dup2(out_pipe[1], 1); - close(out_pipe[0]); - - argc = 0; - for (arg = prefix; *arg && argc < 64; arg++) { - if (!strcmp(*arg, "{}")) { - arg += 1; - break; - } - argv[argc++] = *arg; - } - - if (splitargs) { - ntok = tok = query; - while (ntok && *ntok && argc < 64) { - ntok = strchr(tok, ' '); - end = ntok ? ntok : tok + strlen(tok); - argv[argc++] = strndup(tok, (size_t) (end - tok)); - tok = ntok + 1; - } - } else if (argc < 64) { - argv[argc++] = query; - } - - for (; *arg && argc < 64; arg++) { - argv[argc++] = *arg; - } - - if (argc < 64) argv[argc++] = NULL; - - if (argv[argc-1]) die("Too many arguments"); - - execv(argv[0], (char *const *) argv); + *pid = fork(); + if (*pid < 0) die("fork:"); + if (!*pid) { + zfd = open("/dev/null", O_RDWR); + if (zfd < 0) die("open /dev/null"); + if (!in) dup2(zfd, 0); + if (out && fd) dup2(out_pipe[1], 1); + else if (!out) dup2(zfd, 1); + if (!err) dup2(zfd, 2); + if (fd) close(out_pipe[0]); + close(zfd); + execvp(argv[0], (char *const *) argv); die("execv '%s':"); } else { - close(out_pipe[1]); - - child_fd = out_pipe[0]; + if (fd) { + close(out_pipe[1]); + *fd = out_pipe[0]; + } } } @@ -291,6 +209,23 @@ load(void) output_len += (size_t) nread; } +static void +unload(void) +{ + int status; + + if (!child_pid) return; + + kill(child_pid, SIGKILL); + do { + waitpid(child_pid, &status, 0); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + + close(child_fd); + child_fd = -1; + child_pid = 0; +} + static char * entry(size_t i) { @@ -335,6 +270,83 @@ update(void) } static void +spawn(const char *query) +{ + static const char *argv[64]; + const char **arg; + const char *tok, *ntok, *end; + size_t argc; + + delims_len = 0; + output_len = 0; + + argc = 0; + for (arg = child_argv; *arg && argc < 64; arg++) { + if (!strcmp(*arg, "{}")) { + arg += 1; + break; + } + argv[argc++] = *arg; + } + + if (split_args) { + ntok = tok = query; + while (ntok && *ntok && argc < 64) { + ntok = strchr(tok, ' '); + end = ntok ? ntok : tok + strlen(tok); + argv[argc++] = strndup(tok, (size_t) (end - tok)); + tok = ntok + 1; + } + } else if (argc < 64 && query && *query) { + argv[argc++] = query; + } + + for (; *arg && argc < 64; arg++) { + argv[argc++] = *arg; + } + + if (argc < 64) { + argv[argc++] = NULL; + } else { + die("Too many arguments"); + } + + if (child_pid) unload(); + invoke(argv, &child_pid, &child_fd, false, true, errlog); +} + +static void +hook(uint8_t key, const char *line) +{ + int status, rc; + + if (!hook_argv[0]) return; + + snprintf(hook_argv[1], 6, "%u", key); + free(hook_argv[2]); + hook_argv[2] = strdup(line); + + if (hook_io) { + def_prog_mode(); + endwin(); + } + + invoke((const char **)hook_argv, &hook_pid, NULL, hook_io, hook_io, errlog); + do { + rc = waitpid(hook_pid, &status, 0); + if (rc != hook_pid) die("waitpid:"); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + + if (hook_io) { + reset_prog_mode(); + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 3) + spawn(inputbuf); + update(); +} + +static void input(void) { bool dirty; @@ -394,10 +406,17 @@ input(void) nav_update_sel(&output_nav, output_nav.max-1); dirty = true; break; + case CTRL('x'): + if (output_nav.max > 0 && output_nav.sel >= 0) { + timeout(-1); + hook((uint8_t)getch(), entry((size_t)output_nav.sel)); + timeout(0); + } + break; case '\n': if (output_nav.max > 0 && output_nav.sel >= 0) { puts(entry((size_t) output_nav.sel)); - if (single) exit(0); + if (oneshot) exit(0); } break; default: @@ -413,8 +432,19 @@ input(void) } if (dirty) { + if (reload) spawn(inputbuf); + update(); + } +} + +static void +sigint_handler(int sig) +{ + if (child_pid) { + unload(); update(); - if (reload) spawn(child_argv, inputbuf); + } else { + exit(0); } } @@ -426,33 +456,38 @@ usage(int rc, bool full) fprintf(stderr, "\n"); fprintf(stderr, " -h, --help Show this message\n"); fprintf(stderr, " -d, --delim Set input entry delim\n"); - fprintf(stderr, " -s, --single Exit after single selection\n"); + fprintf(stderr, " -s, --oneshot Exit after oneshot selection\n"); fprintf(stderr, " -e, --stderr Dont close child stderr\n"); fprintf(stderr, " -a, --args Split the query into args\n"); + fprintf(stderr, " -x, --hook Program to invoke on Ctrl-x\n"); fprintf(stderr, "\n"); } exit(rc); } static void -parse(int argc, const char **argv) +parse(int argc, char **argv) { - const char **arg; - const char *cmd_path; + char **arg; for (arg = &argv[1]; *arg; arg++) { if (!strcmp(*arg, "-h") || !strcmp(*arg, "--help")) { usage(0, true); - } else if (!strcmp(*arg, "-s") || !strcmp(*arg, "--single")) { - single = true; + } else if (!strcmp(*arg, "-o") || !strcmp(*arg, "--oneshot")) { + oneshot = true; } else if (!strcmp(*arg, "-e") || !strcmp(*arg, "--stderr")) { - childerr = true; - } else if (!strcmp(*arg, "-a") || !strcmp(*arg, "--args")) { - splitargs = true; + errlog = true; + } else if (!strcmp(*arg, "-s") || !strcmp(*arg, "--split")) { + split_args = true; } else if (!strcmp(*arg, "-d") || !strcmp(*arg, "--delim")) { delim = **++arg; + } else if (!strcmp(*arg, "-x") || !strcmp(*arg, "--hook")) { + hook_argv[0] = *++arg; + } else if (!strcmp(*arg, "-X") || !strcmp(*arg, "--hook-io")) { + hook_argv[0] = *++arg; + hook_io = true; } else if (!strcmp(*arg, "--")) { - child_argv = arg + 1; + child_argv = (const char **)arg + 1; break; } else { die("unknown option '%s'", arg[0]); @@ -461,16 +496,10 @@ parse(int argc, const char **argv) if (!child_argv || !*child_argv) usage(0, false); - - if (*child_argv[0] != '/' && strncmp(child_argv[0], "./", 2)) { - cmd_path = findbin(child_argv[0]); - if (!cmd_path) die("Could not find '%s' in PATH", child_argv[0]); - child_argv[0] = cmd_path; - } } int -main(int argc, const char **argv) +main(int argc, char **argv) { struct pollfd fds[2]; int rc; @@ -479,7 +508,7 @@ main(int argc, const char **argv) nav_init(&output_nav); - signal(SIGINT, sigint); + sigaction(SIGINT, &sigint_action, NULL); atexit((void (*)()) endwin); if (!newterm(NULL, stderr, stdin)) @@ -490,7 +519,7 @@ main(int argc, const char **argv) timeout(0); - spawn(child_argv, ""); + spawn(""); inputbuf = malloc(INPUT_BUFSIZ); if (!inputbuf) die("malloc inputbuf:"); @@ -513,13 +542,18 @@ main(int argc, const char **argv) if (rc < 0 && errno == EINTR) { update(); continue; - } else if (rc < 0) die("select:"); + } else if (rc < 0) { + die("select:"); + } if (fds[0].revents & POLLIN) { input(); } else if (fds[1].revents & POLLIN) { load(); update(); + } else if (fds[1].revents & POLLHUP) { + unload(); + update(); } } }