Squashed 'suckless/scroll/' content from commit 51ee387

git-subtree-dir: suckless/scroll
git-subtree-split: 51ee387dd3860b13baaca5ef23392f7282e9bbdc
This commit is contained in:
Alejandro Guerrero 2026-09-27 17:52:02 +02:00
commit 112255cfb0
13 changed files with 1067 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
scroll
ptty
*.swp
*.core
config.h

16
LICENSE Normal file
View file

@ -0,0 +1,16 @@
ISC License (ISC)
Copyright (c) 2020 Jan Klemkow <j.klemkow@wemelug.de>
Copyright (c) 2020 Jochen Sprickerhof <git@jochen.sprickerhof.de>
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.

45
Makefile Normal file
View file

@ -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

34
README Normal file
View file

@ -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

3
TODO Normal file
View file

@ -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)

16
config.def.h Normal file
View file

@ -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 */
};

12
config.mk Normal file
View file

@ -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

29
perf.sh Executable file
View file

@ -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

156
ptty.c Normal file
View file

@ -0,0 +1,156 @@
#include <sys/wait.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <poll.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#if defined(__linux)
#include <pty.h>
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
#include <util.h>
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#include <libutil.h>
#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);
}

68
scroll.1 Normal file
View file

@ -0,0 +1,68 @@
.\"
.\" Copyright (c) 2020 Jan Klemkow <j.klemkow@wemelug.de>
.\"
.\" 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 .

594
scroll.c Normal file
View file

@ -0,0 +1,594 @@
/*
* Based on an example code from Roberto E. Vargas Caballero.
*
* See LICENSE file for copyright and license details.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/queue.h>
#include <sys/resource.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#if defined(__linux)
#include <pty.h>
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
#include <util.h>
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#include <libutil.h>
#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);
}

74
up.log Normal file
View file

@ -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

15
up.sh Executable file
View file

@ -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