summaryrefslogtreecommitdiff
path: root/files/config/suckless/scroll
diff options
context:
space:
mode:
Diffstat (limited to 'files/config/suckless/scroll')
-rw-r--r--files/config/suckless/scroll/.gitignore5
-rw-r--r--files/config/suckless/scroll/LICENSE16
-rw-r--r--files/config/suckless/scroll/Makefile45
-rw-r--r--files/config/suckless/scroll/README34
-rw-r--r--files/config/suckless/scroll/TODO3
-rw-r--r--files/config/suckless/scroll/config.def.h16
-rw-r--r--files/config/suckless/scroll/config.mk12
-rwxr-xr-xfiles/config/suckless/scroll/perf.sh29
-rw-r--r--files/config/suckless/scroll/ptty.c156
-rw-r--r--files/config/suckless/scroll/scroll.168
-rw-r--r--files/config/suckless/scroll/scroll.c594
-rw-r--r--files/config/suckless/scroll/up.log74
-rwxr-xr-xfiles/config/suckless/scroll/up.sh15
13 files changed, 1067 insertions, 0 deletions
diff --git a/files/config/suckless/scroll/.gitignore b/files/config/suckless/scroll/.gitignore
new file mode 100644
index 0000000..93b7ec2
--- /dev/null
+++ b/files/config/suckless/scroll/.gitignore
@@ -0,0 +1,5 @@
+scroll
+ptty
+*.swp
+*.core
+config.h
diff --git a/files/config/suckless/scroll/LICENSE b/files/config/suckless/scroll/LICENSE
new file mode 100644
index 0000000..2db5bfe
--- /dev/null
+++ b/files/config/suckless/scroll/LICENSE
@@ -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.
diff --git a/files/config/suckless/scroll/Makefile b/files/config/suckless/scroll/Makefile
new file mode 100644
index 0000000..cacccd2
--- /dev/null
+++ b/files/config/suckless/scroll/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/files/config/suckless/scroll/README b/files/config/suckless/scroll/README
new file mode 100644
index 0000000..a1837c2
--- /dev/null
+++ b/files/config/suckless/scroll/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/files/config/suckless/scroll/TODO b/files/config/suckless/scroll/TODO
new file mode 100644
index 0000000..84ffd33
--- /dev/null
+++ b/files/config/suckless/scroll/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/files/config/suckless/scroll/config.def.h b/files/config/suckless/scroll/config.def.h
new file mode 100644
index 0000000..536db70
--- /dev/null
+++ b/files/config/suckless/scroll/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/files/config/suckless/scroll/config.mk b/files/config/suckless/scroll/config.mk
new file mode 100644
index 0000000..5676b85
--- /dev/null
+++ b/files/config/suckless/scroll/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/files/config/suckless/scroll/perf.sh b/files/config/suckless/scroll/perf.sh
new file mode 100755
index 0000000..ab1fb21
--- /dev/null
+++ b/files/config/suckless/scroll/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/files/config/suckless/scroll/ptty.c b/files/config/suckless/scroll/ptty.c
new file mode 100644
index 0000000..bbbb99f
--- /dev/null
+++ b/files/config/suckless/scroll/ptty.c
@@ -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);
+}
diff --git a/files/config/suckless/scroll/scroll.1 b/files/config/suckless/scroll/scroll.1
new file mode 100644
index 0000000..b5524ab
--- /dev/null
+++ b/files/config/suckless/scroll/scroll.1
@@ -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 .
diff --git a/files/config/suckless/scroll/scroll.c b/files/config/suckless/scroll/scroll.c
new file mode 100644
index 0000000..8f66d54
--- /dev/null
+++ b/files/config/suckless/scroll/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 <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);
+}
diff --git a/files/config/suckless/scroll/up.log b/files/config/suckless/scroll/up.log
new file mode 100644
index 0000000..5f40226
--- /dev/null
+++ b/files/config/suckless/scroll/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/files/config/suckless/scroll/up.sh b/files/config/suckless/scroll/up.sh
new file mode 100755
index 0000000..fa28a80
--- /dev/null
+++ b/files/config/suckless/scroll/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