From: "Ted Unangst" Subject: Re: responsive watch wip To: "Job Snijders" Cc: tech@openbsd.org Date: Sat, 14 Jun 2025 13:50:21 -0400 I think I've figured out most of the interactions. Pause, update, etc. Also should support unicode now. Index: watch.c =================================================================== RCS file: /home/cvs/src/usr.bin/watch/watch.c,v diff -u -p -r1.28 watch.c --- watch.c 31 May 2025 08:26:26 -0000 1.28 +++ watch.c 14 Jun 2025 17:41:41 -0000 @@ -72,6 +72,7 @@ int start_line = 0, start_column = 0; /* int pause_on_error = 0; int paused = 0; +int want_update; int last_exitcode = 0; time_t lastupdate; int xflag = 0; @@ -83,6 +84,15 @@ static char *cmdstr; static size_t cmdlen; static char **cmdv; +struct child { + char buf[65000]; + size_t bufsiz; + size_t pos; + pid_t pid; + int fd; + struct event evin; +}; + typedef wchar_t BUFFER[MAXLINE][MAXCOLUMN + 1]; BUFFER buf0, buf1; BUFFER *cur_buf, *prev_buf; @@ -93,17 +103,22 @@ struct event ev_timer; #define ctrl(c) ((c) & 037) int display(BUFFER *, BUFFER *, highlight_mode_t); -void read_result(BUFFER *); kbd_result_t kbd_command(int); void show_help(void); void untabify(wchar_t *, int); void on_signal(int, short, void *); +void on_sigchild(int, short, void *); void timer(int, short, void *); void input(int, short, void *); void quit(void); void usage(void); void swap_buffers(void); +struct child *running_child; +void start_child(); +void child_input(int, short, void *); +void child_done(struct child *); + int set_interval(const char *str) { @@ -124,6 +139,7 @@ int main(int argc, char *argv[]) { struct event ev_sigint, ev_sighup, ev_sigterm, ev_sigwinch, ev_stdin; + struct event ev_sigchild; size_t len, rem; int i, ch; char *p; @@ -216,19 +232,19 @@ main(int argc, char *argv[]) signal_set(&ev_sighup, SIGHUP, on_signal, NULL); signal_set(&ev_sigterm, SIGTERM, on_signal, NULL); signal_set(&ev_sigwinch, SIGWINCH, on_signal, NULL); + signal_set(&ev_sigchild, SIGCHLD, on_sigchild, NULL); signal_add(&ev_sigint, NULL); signal_add(&ev_sighup, NULL); signal_add(&ev_sigterm, NULL); signal_add(&ev_sigwinch, NULL); + signal_add(&ev_sigchild, NULL); event_set(&ev_stdin, STDIN_FILENO, EV_READ | EV_PERSIST, input, NULL); event_add(&ev_stdin, NULL); evtimer_set(&ev_timer, timer, NULL); - evtimer_add(&ev_timer, &opt_interval.tv); cur_buf = &buf0; prev_buf = &buf1; - read_result(cur_buf); - display(cur_buf, prev_buf, highlight_mode); + start_child(); event_dispatch(); @@ -379,21 +395,21 @@ display(BUFFER * cur, BUFFER * prev, hig } void -read_result(BUFFER *buf) +start_child() { - FILE *fp; - int i, st, fds[2]; - pid_t pipe_pid, pid; + struct child *child; + int fds[2]; - /* Clear buffer */ - memset(buf, 0, sizeof(*buf)); + child = calloc(1, sizeof(*child)); + child->bufsiz = sizeof(child->buf); if (pipe(fds) == -1) err(1, "pipe()"); - if ((pipe_pid = vfork()) == -1) - err(1, "vfork()"); - else if (pipe_pid == 0) { + child->pid = vfork(); + if (child->pid == -1) + err(1, "vfork"); + if (child->pid == 0) { close(fds[0]); if (fds[1] != STDOUT_FILENO) { dup2(fds[1], STDOUT_FILENO); @@ -407,21 +423,56 @@ read_result(BUFFER *buf) /* use warn(3) + _exit(2) not to call exit(3) */ warn("exec(%s)", cmdstr); _exit(1); - /* NOTREACHED */ } - if ((fp = fdopen(fds[0], "r")) == NULL) - err(1, "fdopen()"); close(fds[1]); + child->fd = fds[0]; + + event_set(&child->evin, child->fd, EV_READ | EV_PERSIST, child_input, child); + event_add(&child->evin, NULL); + + running_child = child; +} + +void +update() +{ + if (running_child) { + /* not yet */ + want_update = 1; + return; + } + want_update = 0; + swap_buffers(); + start_child(); +} + +void +child_done(struct child *child) +{ + event_del(&child->evin); + close(child->fd); + free(child); + // assert(running_child == child); + if (running_child == child) + running_child = NULL; + if (want_update) + update(); + else if (!paused) + evtimer_add(&ev_timer, &opt_interval.tv); +} + +void +on_sigchild(int sig, short event, void *arg) +{ + pid_t pid; + int st; - /* Read command output and convert tab to spaces * */ - for (i = 0; i < MAXLINE && fgetws((*buf)[i], MAXCOLUMN, fp) != NULL; - i++) - untabify((*buf)[i], sizeof((*buf)[i])); - fclose(fp); do { - pid = waitpid(pipe_pid, &st, 0); + pid = waitpid(WAIT_ANY, &st, 0); } while (pid == -1 && errno == EINTR); + if (!running_child || running_child->pid != pid) + return; /* Remember update time */ time(&lastupdate); @@ -430,6 +481,45 @@ read_result(BUFFER *buf) last_exitcode = WEXITSTATUS(st); if (pause_on_error && last_exitcode) paused = 1; + + child_done(running_child); +} + +void +child_input(int sig, short event, void *arg) +{ + struct child *child = arg; + ssize_t n; + + n = read(child->fd, child->buf + child->pos, child->bufsiz - child->pos); + if (n == -1) + return; + child->pos += n; + + size_t l = 0, c = 0; + BUFFER *buf = cur_buf; + memset(*buf, 0, sizeof(*buf)); + for (size_t i = 0; i < child->pos;/* i += len */) { + wchar_t wc; + int len = mbtowc(&wc, &child->buf[i], MB_CUR_MAX); + if (len == -1) { + wc = '?'; + i += 1; + } else { + i += len; + } + if (wc == '\n') { + l++; + c = 0; + if (l == MAXLINE) + break; + continue; + } + if (c == MAXCOLUMN) + continue; + (*buf)[l][c++] = wc; + } + display(buf, prev_buf, highlight_mode); } kbd_result_t @@ -534,12 +624,13 @@ kbd_command(int ch) case 'p': if (paused == 1) { paused = 0; - evtimer_del(&ev_timer); - return (RSLT_REDRAW); - } else { - paused = 1; evtimer_add(&ev_timer, &opt_interval.tv); return (RSLT_UPDATE); + } else { + paused = 1; + want_update = 0; + evtimer_del(&ev_timer); + return (RSLT_REDRAW); } case 's': @@ -687,11 +778,7 @@ on_signal(int sig, short event, void *ar void timer(int sig, short event, void *arg) { - swap_buffers(); - read_result(cur_buf); - display(cur_buf, prev_buf, highlight_mode); - if (!paused) - evtimer_add(&ev_timer, &opt_interval.tv); + update(); } void @@ -704,11 +791,7 @@ input(int sig, short event, void *arg) kbd_result_t result = kbd_command(ch); switch (result) { case RSLT_UPDATE: /* update buffer */ - swap_buffers(); - read_result(cur_buf); - display(cur_buf, prev_buf, highlight_mode); - if (!paused) - evtimer_add(&ev_timer, &opt_interval.tv); + update(); break; case RSLT_REDRAW: /* scroll with current buffer */ display(cur_buf, prev_buf, highlight_mode);