From 112255cfb09cc360a896392fbf6dc796b205c6e5 Mon Sep 17 00:00:00 2001 From: alejandrogs73 Date: Sun, 27 Sep 2026 17:52:02 +0200 Subject: [PATCH] Squashed 'suckless/scroll/' content from commit 51ee387 git-subtree-dir: suckless/scroll git-subtree-split: 51ee387dd3860b13baaca5ef23392f7282e9bbdc --- .gitignore | 5 + LICENSE | 16 ++ Makefile | 45 ++++ README | 34 +++ TODO | 3 + config.def.h | 16 ++ config.mk | 12 ++ perf.sh | 29 +++ ptty.c | 156 ++++++++++++++ scroll.1 | 68 ++++++ scroll.c | 594 +++++++++++++++++++++++++++++++++++++++++++++++++++ up.log | 74 +++++++ up.sh | 15 ++ 13 files changed, 1067 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 config.def.h create mode 100644 config.mk create mode 100755 perf.sh create mode 100644 ptty.c create mode 100644 scroll.1 create mode 100644 scroll.c create mode 100644 up.log create mode 100755 up.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93b7ec2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +scroll +ptty +*.swp +*.core +config.h diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2db5bfe --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +ISC License (ISC) + +Copyright (c) 2020 Jan Klemkow +Copyright (c) 2020 Jochen Sprickerhof + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cacccd2 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.POSIX: + +include config.mk + +all: scroll + +config.h: + cp config.def.h config.h + +scroll: scroll.c config.h + +install: scroll + mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1 + cp -f scroll $(DESTDIR)$(BINDIR) + cp -f scroll.1 $(DESTDIR)$(MANDIR)/man1 + +uninstall: + rm -f $(DESTDIR)$(BINDIR)/scroll $(DESTDIR)$(MANDIR)/man1/scroll.1 + +test: scroll ptty + # check usage + if ./ptty ./scroll -h; then exit 1; fi + # check exit passthrough of child + if ! ./ptty ./scroll true; then exit 1; fi + if ./ptty ./scroll false; then exit 1; fi + ./up.sh + +clean: + rm -f scroll ptty + +distclean: clean + rm -f config.h scroll-$(VERSION).tar.gz + +dist: clean + mkdir -p scroll-$(VERSION) + cp -R README scroll.1 TODO Makefile config.mk config.def.h \ + ptty.c scroll.c up.sh up.log \ + scroll-$(VERSION) + tar -cf - scroll-$(VERSION) | gzip > scroll-$(VERSION).tar.gz + rm -rf scroll-$(VERSION) + +.c: + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< -lutil + +.PHONY: all install test clean distclean dist diff --git a/README b/README new file mode 100644 index 0000000..a1837c2 --- /dev/null +++ b/README @@ -0,0 +1,34 @@ +This program provides a scroll back buffer for a terminal like st(1). It +should run on any Unix-like system. + +At the moment it is in an experimental state. Its not recommended for +productive use. + +The initial version of this program is from Roberto E. Vargas Caballero: + https://lists.suckless.org/dev/1703/31256.html + +What is the state of scroll? + +The project is faced with some hard facts, that our original plan is not doable +as we thought in the first place: + + 1. [crtl]+[e] is used in emacs mode (default) on the shell to jump to the end + of the line. But, its also used so signal a scroll down mouse event from + terminal emulators to the shell an other programs. + + - A workaround is to use vi mode in the shell. + - Or to give up mouse support (default behavior) + + 2. scroll could not handle backward cursor jumps and editing of old lines + properly. We just handle current line editing and switching between + alternative screens (curses mode). For a proper end user experience we + would need to write a completely new terminal emulator like screen or tmux. + +What is the performance impact of scroll? + + indirect OpenBSD +------------------------------- + 0x 7.53 s + 1x 10.10 s + 2x 12.00 s + 3x 13.73 s diff --git a/TODO b/TODO new file mode 100644 index 0000000..84ffd33 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ + * strlen function which is aware of unicode + * handle wrapping lines in scrolling line count correctly + * hotkey to dump buffer to file (like screen hardcopy) diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..536db70 --- /dev/null +++ b/config.def.h @@ -0,0 +1,16 @@ +/* + * Define ESC sequences to use for scroll events. + * Use "cat -v" to figure out favorite key combination. + * + * lines is the number of lines scrolled up or down. + * If lines is negative, it's the fraction of the terminal size. + */ + +struct rule rules[] = { + /* sequence event lines */ + {"\033[5;2~", SCROLL_UP, -1}, /* [Shift] + [PageUP] */ + {"\033[6;2~", SCROLL_DOWN, -1}, /* [Shift] + [PageDown] */ + /* mouse binding shadows ^E and ^Y, so it's disabled by default */ + //{"\031", SCROLL_UP, 1}, /* mouse wheel up */ + //{"\005", SCROLL_DOWN, 1}, /* mouse wheel Down */ +}; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..5676b85 --- /dev/null +++ b/config.mk @@ -0,0 +1,12 @@ +# scroll version +VERSION = 0.1 + +# paths +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin +MANDIR = $(PREFIX)/share/man + +CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE +# if your system is not POSIX, add -std=c99 to CFLAGS +CFLAGS = -Os +LDFLAGS = -s diff --git a/perf.sh b/perf.sh new file mode 100755 index 0000000..ab1fb21 --- /dev/null +++ b/perf.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +set -eu + +export POSIXLY_CORRECT=1 +num=1000000 +seq=seq + +if [ -x /usr/bin/jot ]; then + seq=jot +fi + +rm -f perf_*.log + +for i in `$seq 10`; do + /usr/bin/time st -e $seq $num 2>>perf_0.log +done + +for i in `$seq 10`; do + /usr/bin/time st -e ./ptty $seq $num 2>>perf_1.log +done + +for i in `$seq 10`; do + /usr/bin/time st -e ./ptty ./ptty $seq $num 2>>perf_2.log +done + +for i in `$seq 10`; do + /usr/bin/time st -e ./ptty ./ptty ./ptty $seq $num 2>>perf_3.log +done diff --git a/ptty.c b/ptty.c new file mode 100644 index 0000000..bbbb99f --- /dev/null +++ b/ptty.c @@ -0,0 +1,156 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +void +die(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(EXIT_FAILURE); +} + +void +usage(void) +{ + fputs("ptty [-C] [-c cols] [-r rows] cmd\n", stderr); + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[]) +{ + struct winsize ws = {.ws_row = 25, .ws_col = 80, 0, 0}; + int ch; + bool closeflag = false; + + while ((ch = getopt(argc, argv, "c:r:Ch")) != -1) { + switch (ch) { + case 'c': /* cols */ + ws.ws_col = strtoimax(optarg, NULL, 10); + if (errno != 0) + die("strtoimax: %s", optarg); + break; + case 'r': /* lines */ + ws.ws_row = strtoimax(optarg, NULL, 10); + if (errno != 0) + die("strtoimax: %s", optarg); + break; + case 'C': + closeflag = true; + break; + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + int mfd; + pid_t child = forkpty(&mfd, NULL, NULL, &ws); + switch (child) { + case -1: + die("forkpty"); + case 0: /* child */ + execvp(argv[0], argv); + die("exec"); + } + + /* parent */ + + if (closeflag && close(mfd) == -1) + die("close:"); + + int pfds = 2; + struct pollfd pfd[2] = { + { STDIN_FILENO, POLLIN, 0}, + { mfd, POLLIN, 0} + }; + + for (;;) { + char buf[BUFSIZ]; + ssize_t n; + int r; + + if ((r = poll(pfd, pfds, -1)) == -1) + die("poll:"); + + if (pfd[0].revents & POLLIN) { + if ((n = read(STDIN_FILENO, buf, sizeof buf)) == -1) + die("read:"); + if (n == 0) { + pfd[0].fd = -1; + if (close(mfd) == -1) + die("close:"); + break; + } + if (write(mfd, buf, n) == -1) + die("write:"); + } + + if (pfd[1].revents & POLLIN) { + if ((n = read(mfd, buf, sizeof(buf)-1)) == -1) + die("read:"); + + if (n == 0) break; + + buf[n] = '\0'; + + /* handle cursor position request */ + if (strcmp("\033[6n", buf) == 0) { + dprintf(mfd, "\033[25;1R"); + continue; + } + + if (write(STDOUT_FILENO, buf, n) == -1) + die("write:"); + } + + if (pfd[0].revents & POLLHUP) { + pfd[0].fd = -1; + if (close(mfd) == -1) + die("close:"); + break; + } + if (pfd[1].revents & POLLHUP) + break; + } + + int status; + if (waitpid(child, &status, 0) != child) + die("waitpid:"); + + return WEXITSTATUS(status); +} diff --git a/scroll.1 b/scroll.1 new file mode 100644 index 0000000..b5524ab --- /dev/null +++ b/scroll.1 @@ -0,0 +1,68 @@ +.\" +.\" Copyright (c) 2020 Jan Klemkow +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd April 9, 2020 +.Dt SCROLL 1 +.Os +.Sh NAME +.Nm scroll +.Nd scrollback buffer +.Sh SYNOPSIS +.Nm +.Op Fl Mh +.Op Fl m Ar size +.Op program Op arg ... +.Sh DESCRIPTION +The +.Nm +utility saves output lines from the child +.Ar program +to use them for scrollback. +If +.Ar program +is not set, +.Nm +starts the users default shell. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl h +Shows usage of +.Nm . +.It Fl M +Set memory limit used for scrollbackbuffer to maximum. +.It Fl m Ar size +Set memory limit used for scrollbackbuffer to +.Ar size . +.El +.Sh EXIT STATUS +.Nm +exits with the status code of its the child +.Ar program . +.Sh EXAMPLES +.Nm st +.Fl e +.Nm scroll +.Nm /bin/sh +.Sh SEE ALSO +.Xr screen 1 , +.Xr st 1 , +.Xr tmux 1 +.Sh AUTHORS +.Nm +was written by +.An Jan Klemkow Aq Mt j.klemkow@wemelug.de +and +.An Jochen Sprickerhof Aq Mt git@jochen.sprickerhof.de . diff --git a/scroll.c b/scroll.c new file mode 100644 index 0000000..8f66d54 --- /dev/null +++ b/scroll.c @@ -0,0 +1,594 @@ +/* + * Based on an example code from Roberto E. Vargas Caballero. + * + * See LICENSE file for copyright and license details. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +#define LENGTH(X) (sizeof (X) / sizeof ((X)[0])) + +const char *argv0; + +TAILQ_HEAD(tailhead, line) head; + +struct line { + TAILQ_ENTRY(line) entries; + size_t size; + size_t len; + char *buf; +} *bottom; + +pid_t child; +int mfd; +struct termios dfl; +struct winsize ws; +static bool altscreen = false; /* is alternative screen active */ +static bool doredraw = false; /* redraw upon sigwinch */ + +struct rule { + const char *seq; + enum {SCROLL_UP, SCROLL_DOWN} event; + short lines; +}; + +#include "config.h" + +void +die(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(EXIT_FAILURE); +} + +void +sigwinch(int sig) +{ + assert(sig == SIGWINCH); + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) + die("ioctl:"); + if (ioctl(mfd, TIOCSWINSZ, &ws) == -1) { + if (errno == EBADF) /* child already exited */ + return; + die("ioctl:"); + } + kill(-child, SIGWINCH); + doredraw = true; +} + +void +reset(void) +{ + if (tcsetattr(STDIN_FILENO, TCSANOW, &dfl) == -1) + die("tcsetattr:"); +} + +/* error avoiding remalloc */ +void * +earealloc(void *ptr, size_t size) +{ + void *mem; + + while ((mem = realloc(ptr, size)) == NULL) { + struct line *line = TAILQ_LAST(&head, tailhead); + + if (line == NULL) + die("realloc:"); + + TAILQ_REMOVE(&head, line, entries); + free(line->buf); + free(line); + } + + return mem; +} + +/* Count string length w/o ansi esc sequences. */ +size_t +strelen(const char *buf, size_t size) +{ + enum {CHAR, BREK, ESC} state = CHAR; + size_t len = 0; + + for (size_t i = 0; i < size; i++) { + char c = buf[i]; + + switch (state) { + case CHAR: + if (c == '\033') + state = BREK; + else + len++; + break; + case BREK: + if (c == '[') { + state = ESC; + } else { + state = CHAR; + len++; + } + break; + case ESC: + if (c >= 64 && c <= 126) + state = CHAR; + break; + } + } + + return len; +} + +/* detect alternative screen switching and clear screen */ +bool +skipesc(char c) +{ + static enum {CHAR, BREK, ESC} state = CHAR; + static char buf[BUFSIZ]; + static size_t i = 0; + + switch (state) { + case CHAR: + if (c == '\033') + state = BREK; + break; + case BREK: + if (c == '[') + state = ESC; + else + state = CHAR; + break; + case ESC: + buf[i++] = c; + if (i == sizeof buf) { + /* TODO: find a better way to handle this situation */ + state = CHAR; + i = 0; + } else if (c >= 64 && c <= 126) { + state = CHAR; + buf[i] = '\0'; + i = 0; + + /* esc seq. enable alternative screen */ + if (strcmp(buf, "?1049h") == 0 || + strcmp(buf, "?1047h") == 0 || + strcmp(buf, "?47h" ) == 0) + altscreen = true; + + /* esc seq. disable alternative screen */ + if (strcmp(buf, "?1049l") == 0 || + strcmp(buf, "?1047l") == 0 || + strcmp(buf, "?47l" ) == 0) + altscreen = false; + + /* don't save cursor move or clear screen */ + /* esc sequences to log */ + switch (c) { + case 'A': + case 'B': + case 'C': + case 'D': + case 'H': + case 'J': + case 'K': + case 'f': + return true; + } + } + break; + } + + return altscreen; +} + +void +getcursorposition(int *x, int *y) +{ + char input[BUFSIZ]; + ssize_t n; + + if (write(STDOUT_FILENO, "\033[6n", 4) == -1) + die("requesting cursor position"); + + do { + if ((n = read(STDIN_FILENO, input, sizeof(input)-1)) == -1) + die("reading cursor position"); + input[n] = '\0'; + } while (sscanf(input, "\033[%d;%dR", y, x) != 2); + + if (*x <= 0 || *y <= 0) + die("invalid cursor position: x=%d y=%d", *x, *y); +} + +void +addline(char *buf, size_t size) +{ + struct line *line = earealloc(NULL, sizeof *line); + + line->size = size; + line->len = strelen(buf, size); + line->buf = earealloc(NULL, size); + memcpy(line->buf, buf, size); + + TAILQ_INSERT_HEAD(&head, line, entries); +} + +void +redraw() +{ + int rows = 0, x, y; + + if (bottom == NULL) + return; + + getcursorposition(&x, &y); + + if (y < ws.ws_row-1) + y--; + + /* wind back bottom pointer by shown history */ + for (; bottom != NULL && TAILQ_NEXT(bottom, entries) != NULL && + rows < y - 1; rows++) + bottom = TAILQ_NEXT(bottom, entries); + + /* clear screen */ + dprintf(STDOUT_FILENO, "\033[2J"); + /* set cursor position to upper left corner */ + write(STDOUT_FILENO, "\033[0;0H", 6); + + /* remove newline of first line as we are at 0,0 already */ + if (bottom->size > 0 && bottom->buf[0] == '\n') + write(STDOUT_FILENO, bottom->buf + 1, bottom->size - 1); + else + write(STDOUT_FILENO, bottom->buf, bottom->size); + + for (rows = ws.ws_row; rows > 0 && + TAILQ_PREV(bottom, tailhead, entries) != NULL; rows--) { + bottom = TAILQ_PREV(bottom, tailhead, entries); + write(STDOUT_FILENO, bottom->buf, bottom->size); + } + + if (bottom == TAILQ_FIRST(&head)) { + /* add new line in front of the shell prompt */ + write(STDOUT_FILENO, "\n", 1); + write(STDOUT_FILENO, "\033[?25h", 6); /* show cursor */ + } else + bottom = TAILQ_NEXT(bottom, entries); +} + +void +scrollup(int n) +{ + int rows = 2, x, y, extra = 0; + struct line *scrollend = bottom; + + if (bottom == NULL) + return; + + getcursorposition(&x, &y); + + if (n < 0) /* scroll by fraction of ws.ws_row, but at least one line */ + n = ws.ws_row > (-n) ? ws.ws_row / (-n) : 1; + + /* wind back scrollend pointer by the current screen */ + while (rows < y && TAILQ_NEXT(scrollend, entries) != NULL) { + scrollend = TAILQ_NEXT(scrollend, entries); + rows += (scrollend->len - 1) / ws.ws_col + 1; + } + + if (rows <= 0) + return; + + /* wind back scrollend pointer n lines */ + for (rows = 0; rows + extra < n && + TAILQ_NEXT(scrollend, entries) != NULL; rows++) { + scrollend = TAILQ_NEXT(scrollend, entries); + extra += (scrollend->len - 1) / ws.ws_col; + } + + /* move the text in terminal rows lines down */ + dprintf(STDOUT_FILENO, "\033[%dT", n); + /* set cursor position to upper left corner */ + write(STDOUT_FILENO, "\033[0;0H", 6); + /* hide cursor */ + write(STDOUT_FILENO, "\033[?25l", 6); + + /* remove newline of first line as we are at 0,0 already */ + if (scrollend->size > 0 && scrollend->buf[0] == '\n') + write(STDOUT_FILENO, scrollend->buf + 1, scrollend->size - 1); + else + write(STDOUT_FILENO, scrollend->buf, scrollend->size); + if (y + n >= ws.ws_row) + bottom = TAILQ_NEXT(bottom, entries); + + /* print rows lines and move bottom forward to the new screen bottom */ + for (; rows > 1; rows--) { + scrollend = TAILQ_PREV(scrollend, tailhead, entries); + if (y + n >= ws.ws_row) + bottom = TAILQ_NEXT(bottom, entries); + write(STDOUT_FILENO, scrollend->buf, scrollend->size); + } + /* move cursor from line n to the old bottom position */ + if (y + n < ws.ws_row) { + dprintf(STDOUT_FILENO, "\033[%d;%dH", y + n, x); + write(STDOUT_FILENO, "\033[?25h", 6); /* show cursor */ + } else + dprintf(STDOUT_FILENO, "\033[%d;0H", ws.ws_row); +} + +void +scrolldown(char *buf, size_t size, int n) +{ + if (bottom == NULL || bottom == TAILQ_FIRST(&head)) + return; + + if (n < 0) /* scroll by fraction of ws.ws_row, but at least one line */ + n = ws.ws_row > (-n) ? ws.ws_row / (-n) : 1; + + bottom = TAILQ_PREV(bottom, tailhead, entries); + /* print n lines */ + while (n > 0 && bottom != NULL && bottom != TAILQ_FIRST(&head)) { + bottom = TAILQ_PREV(bottom, tailhead, entries); + write(STDOUT_FILENO, bottom->buf, bottom->size); + n -= (bottom->len - 1) / ws.ws_col + 1; + } + if (n > 0 && bottom == TAILQ_FIRST(&head)) { + write(STDOUT_FILENO, "\033[?25h", 6); /* show cursor */ + write(STDOUT_FILENO, buf, size); + } else if (bottom != NULL) + bottom = TAILQ_NEXT(bottom, entries); +} + +void +jumpdown(char *buf, size_t size) +{ + int rows = ws.ws_row; + + /* wind back by one page starting from the latest line */ + bottom = TAILQ_FIRST(&head); + for (; TAILQ_NEXT(bottom, entries) != NULL && rows > 0; rows--) + bottom = TAILQ_NEXT(bottom, entries); + + scrolldown(buf, size, ws.ws_row); +} + +void +usage(void) { + die("usage: %s [-Mvh] [-m mem] [program]", argv0); +} + +int +main(int argc, char *argv[]) +{ + int ch; + struct rlimit rlimit; + + argv0 = argv[0]; + + if (getrlimit(RLIMIT_DATA, &rlimit) == -1) + die("getrlimit"); + + const char *optstring = "Mm:vh"; + while ((ch = getopt(argc, argv, optstring)) != -1) { + switch (ch) { + case 'M': + rlimit.rlim_cur = rlimit.rlim_max; + break; + case 'm': + rlimit.rlim_cur = strtoull(optarg, NULL, 0); + if (errno != 0) + die("strtoull: %s", optarg); + break; + case 'v': + die("%s " VERSION, argv0); + break; + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + TAILQ_INIT(&head); + + if (isatty(STDIN_FILENO) == 0 || isatty(STDOUT_FILENO) == 0) + die("parent it not a tty"); + + /* save terminal settings for resetting after exit */ + if (tcgetattr(STDIN_FILENO, &dfl) == -1) + die("tcgetattr:"); + if (atexit(reset)) + die("atexit:"); + + /* get window size of the terminal */ + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) + die("ioctl:"); + + child = forkpty(&mfd, NULL, &dfl, &ws); + if (child == -1) + die("forkpty:"); + if (child == 0) { /* child */ + if (argc >= 1) { + execvp(argv[0], argv); + } else { + struct passwd *passwd = getpwuid(getuid()); + if (passwd == NULL) + die("getpwid:"); + execlp(passwd->pw_shell, passwd->pw_shell, NULL); + } + + perror("execvp"); + _exit(127); + } + + /* set maximum memory size for scrollback buffer */ + if (setrlimit(RLIMIT_DATA, &rlimit) == -1) + die("setrlimit:"); + +#ifdef __OpenBSD__ + if (pledge("stdio tty proc", NULL) == -1) + die("pledge:"); +#endif + + if (signal(SIGWINCH, sigwinch) == SIG_ERR) + die("signal:"); + + struct termios new = dfl; + cfmakeraw(&new); + new.c_cc[VMIN ] = 1; /* return read if at least one byte in buffer */ + new.c_cc[VTIME] = 0; /* no polling time for read from terminal */ + if (tcsetattr(STDIN_FILENO, TCSANOW, &new) == -1) + die("tcsetattr:"); + + size_t size = BUFSIZ, len = 0, pos = 0; + char *buf = calloc(size, sizeof *buf); + if (buf == NULL) + die("calloc:"); + + struct pollfd pfd[2] = { + {STDIN_FILENO, POLLIN, 0}, + {mfd, POLLIN, 0} + }; + + for (;;) { + char input[BUFSIZ]; + + if (poll(pfd, LENGTH(pfd), -1) == -1 && errno != EINTR) + die("poll:"); + + if (doredraw) { + redraw(); + doredraw = false; + } + + if (pfd[0].revents & POLLHUP || pfd[1].revents & POLLHUP) + break; + + if (pfd[0].revents & POLLIN) { + ssize_t n = read(STDIN_FILENO, input, sizeof(input)-1); + + if (n == -1 && errno != EINTR) + die("read:"); + if (n == 0) + break; + + input[n] = '\0'; + + if (altscreen) + goto noevent; + + for (size_t i = 0; i < LENGTH(rules); i++) { + if (strncmp(rules[i].seq, input, + strlen(rules[i].seq)) == 0) { + if (rules[i].event == SCROLL_UP) + scrollup(rules[i].lines); + if (rules[i].event == SCROLL_DOWN) + scrolldown(buf, len, + rules[i].lines); + goto out; + } + } + noevent: + if (write(mfd, input, n) == -1) + die("write:"); + + if (bottom != TAILQ_FIRST(&head)) + jumpdown(buf, len); + } + out: + if (pfd[1].revents & POLLIN) { + ssize_t n = read(mfd, input, sizeof(input)-1); + + if (n == -1 && errno != EINTR) + die("read:"); + if (n == 0) /* on exit of child we continue here */ + continue; /* let signal handler catch SIGCHLD */ + + input[n] = '\0'; + + /* don't print child output while scrolling */ + if (bottom == TAILQ_FIRST(&head)) + if (write(STDOUT_FILENO, input, n) == -1) + die("write:"); + + /* iterate over the input buffer */ + for (char *c = input; n-- > 0; c++) { + /* don't save alternative screen and */ + /* clear screen esc sequences to scrollback */ + if (skipesc(*c)) + continue; + + if (*c == '\n') { + addline(buf, len); + /* only advance bottom if scroll is */ + /* at the end of the scroll back */ + if (bottom == NULL || + TAILQ_PREV(bottom, tailhead, + entries) == TAILQ_FIRST(&head)) + bottom = TAILQ_FIRST(&head); + + memset(buf, 0, size); + len = pos = 0; + buf[pos++] = '\r'; + } else if (*c == '\r') { + pos = 0; + continue; + } + buf[pos++] = *c; + if (pos > len) + len = pos; + if (len == size) { + size *= 2; + buf = earealloc(buf, size); + } + } + } + } + + if (close(mfd) == -1) + die("close:"); + + int status; + if (waitpid(child, &status, 0) == -1) + die("waitpid:"); + + return WEXITSTATUS(status); +} diff --git a/up.log b/up.log new file mode 100644 index 0000000..5f40226 --- /dev/null +++ b/up.log @@ -0,0 +1,74 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +[?25l1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 \ No newline at end of file diff --git a/up.sh b/up.sh new file mode 100755 index 0000000..fa28a80 --- /dev/null +++ b/up.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +set -eu +export POSIXLY_CORRECT=1 + +i=1 +while test "$i" -lt 50; do + echo "$i" + i=$((i + 1)) +done > tmp.log + +(sleep 1; printf '\033[5;2~'; sleep 1; ) \ + | ./ptty ./scroll tail -fn 50 tmp.log > out.log + +cmp out.log up.log