Squashed 'suckless/scroll/' content from commit 51ee387
git-subtree-dir: suckless/scroll git-subtree-split: 51ee387dd3860b13baaca5ef23392f7282e9bbdc
This commit is contained in:
commit
112255cfb0
13 changed files with 1067 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
scroll
|
||||||
|
ptty
|
||||||
|
*.swp
|
||||||
|
*.core
|
||||||
|
config.h
|
||||||
16
LICENSE
Normal file
16
LICENSE
Normal 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
45
Makefile
Normal 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
34
README
Normal 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
3
TODO
Normal 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
16
config.def.h
Normal 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
12
config.mk
Normal 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
29
perf.sh
Executable 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
156
ptty.c
Normal 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
68
scroll.1
Normal 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
594
scroll.c
Normal 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
74
up.log
Normal 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
|
||||||
|
[25T[0;0H[?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[25;0H
|
||||||
15
up.sh
Executable file
15
up.sh
Executable 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue