Introduction
Introduction Statistics Contact Development Disclaimer Help
scroll.c - scroll - scrollbackbuffer program for st
git clone git://git.suckless.org/scroll
Log
Files
Refs
README
LICENSE
---
scroll.c (12817B)
---
1 /*
2 * Based on an example code from Roberto E. Vargas Caballero.
3 *
4 * See LICENSE file for copyright and license details.
5 */
6
7 #include <sys/types.h>
8 #include <sys/ioctl.h>
9 #include <sys/wait.h>
10 #include <sys/queue.h>
11 #include <sys/resource.h>
12
13 #include <assert.h>
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <poll.h>
17 #include <pwd.h>
18 #include <signal.h>
19 #include <stdarg.h>
20 #include <stdbool.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <termios.h>
25 #include <unistd.h>
26
27 #if defined(__linux)
28 #include <pty.h>
29 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
30 #include <util.h>
31 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #include <libutil.h>
33 #endif
34
35 #define LENGTH(X) (sizeof (X) / sizeof ((X)[0]))
36
37 const char *argv0;
38
39 TAILQ_HEAD(tailhead, line) head;
40
41 struct line {
42 TAILQ_ENTRY(line) entries;
43 size_t size;
44 size_t len;
45 char *buf;
46 } *bottom;
47
48 pid_t child;
49 int mfd;
50 struct termios dfl;
51 struct winsize ws;
52 static bool altscreen = false; /* is alternative screen active */
53 static bool doredraw = false; /* redraw upon sigwinch */
54
55 struct rule {
56 const char *seq;
57 enum {SCROLL_UP, SCROLL_DOWN} event;
58 short lines;
59 };
60
61 #include "config.h"
62
63 void
64 die(const char *fmt, ...)
65 {
66 va_list ap;
67 va_start(ap, fmt);
68 vfprintf(stderr, fmt, ap);
69 va_end(ap);
70
71 if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
72 fputc(' ', stderr);
73 perror(NULL);
74 } else {
75 fputc('\n', stderr);
76 }
77
78 exit(EXIT_FAILURE);
79 }
80
81 void
82 sigwinch(int sig)
83 {
84 assert(sig == SIGWINCH);
85
86 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
87 die("ioctl:");
88 if (ioctl(mfd, TIOCSWINSZ, &ws) == -1) {
89 if (errno == EBADF) /* child already exited */
90 return;
91 die("ioctl:");
92 }
93 kill(-child, SIGWINCH);
94 doredraw = true;
95 }
96
97 void
98 reset(void)
99 {
100 if (tcsetattr(STDIN_FILENO, TCSANOW, &dfl) == -1)
101 die("tcsetattr:");
102 }
103
104 /* error avoiding remalloc */
105 void *
106 earealloc(void *ptr, size_t size)
107 {
108 void *mem;
109
110 while ((mem = realloc(ptr, size)) == NULL) {
111 struct line *line = TAILQ_LAST(&head, tailhead);
112
113 if (line == NULL)
114 die("realloc:");
115
116 TAILQ_REMOVE(&head, line, entries);
117 free(line->buf);
118 free(line);
119 }
120
121 return mem;
122 }
123
124 /* Count string length w/o ansi esc sequences. */
125 size_t
126 strelen(const char *buf, size_t size)
127 {
128 enum {CHAR, BREK, ESC} state = CHAR;
129 size_t len = 0;
130
131 for (size_t i = 0; i < size; i++) {
132 char c = buf[i];
133
134 switch (state) {
135 case CHAR:
136 if (c == '\033')
137 state = BREK;
138 else
139 len++;
140 break;
141 case BREK:
142 if (c == '[') {
143 state = ESC;
144 } else {
145 state = CHAR;
146 len++;
147 }
148 break;
149 case ESC:
150 if (c >= 64 && c <= 126)
151 state = CHAR;
152 break;
153 }
154 }
155
156 return len;
157 }
158
159 /* detect alternative screen switching and clear screen */
160 bool
161 skipesc(char c)
162 {
163 static enum {CHAR, BREK, ESC} state = CHAR;
164 static char buf[BUFSIZ];
165 static size_t i = 0;
166
167 switch (state) {
168 case CHAR:
169 if (c == '\033')
170 state = BREK;
171 break;
172 case BREK:
173 if (c == '[')
174 state = ESC;
175 else
176 state = CHAR;
177 break;
178 case ESC:
179 buf[i++] = c;
180 if (i == sizeof buf) {
181 /* TODO: find a better way to handle this situat…
182 state = CHAR;
183 i = 0;
184 } else if (c >= 64 && c <= 126) {
185 state = CHAR;
186 buf[i] = '\0';
187 i = 0;
188
189 /* esc seq. enable alternative screen */
190 if (strcmp(buf, "?1049h") == 0 ||
191 strcmp(buf, "?1047h") == 0 ||
192 strcmp(buf, "?47h" ) == 0)
193 altscreen = true;
194
195 /* esc seq. disable alternative screen */
196 if (strcmp(buf, "?1049l") == 0 ||
197 strcmp(buf, "?1047l") == 0 ||
198 strcmp(buf, "?47l" ) == 0)
199 altscreen = false;
200
201 /* don't save cursor move or clear screen */
202 /* esc sequences to log */
203 switch (c) {
204 case 'A':
205 case 'B':
206 case 'C':
207 case 'D':
208 case 'H':
209 case 'J':
210 case 'K':
211 case 'f':
212 return true;
213 }
214 }
215 break;
216 }
217
218 return altscreen;
219 }
220
221 void
222 getcursorposition(int *x, int *y)
223 {
224 char input[BUFSIZ];
225 ssize_t n;
226
227 if (write(STDOUT_FILENO, "\033[6n", 4) == -1)
228 die("requesting cursor position");
229
230 do {
231 if ((n = read(STDIN_FILENO, input, sizeof(input)-1)) == …
232 die("reading cursor position");
233 input[n] = '\0';
234 } while (sscanf(input, "\033[%d;%dR", y, x) != 2);
235
236 if (*x <= 0 || *y <= 0)
237 die("invalid cursor position: x=%d y=%d", *x, *y);
238 }
239
240 void
241 addline(char *buf, size_t size)
242 {
243 struct line *line = earealloc(NULL, sizeof *line);
244
245 line->size = size;
246 line->len = strelen(buf, size);
247 line->buf = earealloc(NULL, size);
248 memcpy(line->buf, buf, size);
249
250 TAILQ_INSERT_HEAD(&head, line, entries);
251 }
252
253 void
254 redraw()
255 {
256 int rows = 0, x, y;
257
258 if (bottom == NULL)
259 return;
260
261 getcursorposition(&x, &y);
262
263 if (y < ws.ws_row-1)
264 y--;
265
266 /* wind back bottom pointer by shown history */
267 for (; bottom != NULL && TAILQ_NEXT(bottom, entries) != NULL &&
268 rows < y - 1; rows++)
269 bottom = TAILQ_NEXT(bottom, entries);
270
271 /* clear screen */
272 dprintf(STDOUT_FILENO, "\033[2J");
273 /* set cursor position to upper left corner */
274 write(STDOUT_FILENO, "\033[0;0H", 6);
275
276 /* remove newline of first line as we are at 0,0 already */
277 if (bottom->size > 0 && bottom->buf[0] == '\n')
278 write(STDOUT_FILENO, bottom->buf + 1, bottom->size - 1);
279 else
280 write(STDOUT_FILENO, bottom->buf, bottom->size);
281
282 for (rows = ws.ws_row; rows > 0 &&
283 TAILQ_PREV(bottom, tailhead, entries) != NULL; rows--) {
284 bottom = TAILQ_PREV(bottom, tailhead, entries);
285 write(STDOUT_FILENO, bottom->buf, bottom->size);
286 }
287
288 if (bottom == TAILQ_FIRST(&head)) {
289 /* add new line in front of the shell prompt */
290 write(STDOUT_FILENO, "\n", 1);
291 write(STDOUT_FILENO, "\033[?25h", 6); /* show cur…
292 } else
293 bottom = TAILQ_NEXT(bottom, entries);
294 }
295
296 void
297 scrollup(int n)
298 {
299 int rows = 2, x, y, extra = 0;
300 struct line *scrollend = bottom;
301
302 if (bottom == NULL)
303 return;
304
305 getcursorposition(&x, &y);
306
307 if (n < 0) /* scroll by fraction of ws.ws_row, but at least one …
308 n = ws.ws_row > (-n) ? ws.ws_row / (-n) : 1;
309
310 /* wind back scrollend pointer by the current screen */
311 while (rows < y && TAILQ_NEXT(scrollend, entries) != NULL) {
312 scrollend = TAILQ_NEXT(scrollend, entries);
313 rows += (scrollend->len - 1) / ws.ws_col + 1;
314 }
315
316 if (rows <= 0)
317 return;
318
319 /* wind back scrollend pointer n lines */
320 for (rows = 0; rows + extra < n &&
321 TAILQ_NEXT(scrollend, entries) != NULL; rows++) {
322 scrollend = TAILQ_NEXT(scrollend, entries);
323 extra += (scrollend->len - 1) / ws.ws_col;
324 }
325
326 /* move the text in terminal rows lines down */
327 dprintf(STDOUT_FILENO, "\033[%dT", n);
328 /* set cursor position to upper left corner */
329 write(STDOUT_FILENO, "\033[0;0H", 6);
330 /* hide cursor */
331 write(STDOUT_FILENO, "\033[?25l", 6);
332
333 /* remove newline of first line as we are at 0,0 already */
334 if (scrollend->size > 0 && scrollend->buf[0] == '\n')
335 write(STDOUT_FILENO, scrollend->buf + 1, scrollend->size…
336 else
337 write(STDOUT_FILENO, scrollend->buf, scrollend->size);
338 if (y + n >= ws.ws_row)
339 bottom = TAILQ_NEXT(bottom, entries);
340
341 /* print rows lines and move bottom forward to the new screen bo…
342 for (; rows > 1; rows--) {
343 scrollend = TAILQ_PREV(scrollend, tailhead, entries);
344 if (y + n >= ws.ws_row)
345 bottom = TAILQ_NEXT(bottom, entries);
346 write(STDOUT_FILENO, scrollend->buf, scrollend->size);
347 }
348 /* move cursor from line n to the old bottom position */
349 if (y + n < ws.ws_row) {
350 dprintf(STDOUT_FILENO, "\033[%d;%dH", y + n, x);
351 write(STDOUT_FILENO, "\033[?25h", 6); /* show cur…
352 } else
353 dprintf(STDOUT_FILENO, "\033[%d;0H", ws.ws_row);
354 }
355
356 void
357 scrolldown(char *buf, size_t size, int n)
358 {
359 if (bottom == NULL || bottom == TAILQ_FIRST(&head))
360 return;
361
362 if (n < 0) /* scroll by fraction of ws.ws_row, but at least one …
363 n = ws.ws_row > (-n) ? ws.ws_row / (-n) : 1;
364
365 bottom = TAILQ_PREV(bottom, tailhead, entries);
366 /* print n lines */
367 while (n > 0 && bottom != NULL && bottom != TAILQ_FIRST(&head)) {
368 bottom = TAILQ_PREV(bottom, tailhead, entries);
369 write(STDOUT_FILENO, bottom->buf, bottom->size);
370 n -= (bottom->len - 1) / ws.ws_col + 1;
371 }
372 if (n > 0 && bottom == TAILQ_FIRST(&head)) {
373 write(STDOUT_FILENO, "\033[?25h", 6); /* show cur…
374 write(STDOUT_FILENO, buf, size);
375 } else if (bottom != NULL)
376 bottom = TAILQ_NEXT(bottom, entries);
377 }
378
379 void
380 jumpdown(char *buf, size_t size)
381 {
382 int rows = ws.ws_row;
383
384 /* wind back by one page starting from the latest line */
385 bottom = TAILQ_FIRST(&head);
386 for (; TAILQ_NEXT(bottom, entries) != NULL && rows > 0; rows--)
387 bottom = TAILQ_NEXT(bottom, entries);
388
389 scrolldown(buf, size, ws.ws_row);
390 }
391
392 void
393 usage(void) {
394 die("usage: %s [-Mvh] [-m mem] [program]", argv0);
395 }
396
397 int
398 main(int argc, char *argv[])
399 {
400 int ch;
401 struct rlimit rlimit;
402
403 argv0 = argv[0];
404
405 if (getrlimit(RLIMIT_DATA, &rlimit) == -1)
406 die("getrlimit");
407
408 const char *optstring = "Mm:vh";
409 while ((ch = getopt(argc, argv, optstring)) != -1) {
410 switch (ch) {
411 case 'M':
412 rlimit.rlim_cur = rlimit.rlim_max;
413 break;
414 case 'm':
415 rlimit.rlim_cur = strtoull(optarg, NULL, 0);
416 if (errno != 0)
417 die("strtoull: %s", optarg);
418 break;
419 case 'v':
420 die("%s " VERSION, argv0);
421 break;
422 case 'h':
423 default:
424 usage();
425 }
426 }
427 argc -= optind;
428 argv += optind;
429
430 TAILQ_INIT(&head);
431
432 if (isatty(STDIN_FILENO) == 0 || isatty(STDOUT_FILENO) == 0)
433 die("parent it not a tty");
434
435 /* save terminal settings for resetting after exit */
436 if (tcgetattr(STDIN_FILENO, &dfl) == -1)
437 die("tcgetattr:");
438 if (atexit(reset))
439 die("atexit:");
440
441 /* get window size of the terminal */
442 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
443 die("ioctl:");
444
445 child = forkpty(&mfd, NULL, &dfl, &ws);
446 if (child == -1)
447 die("forkpty:");
448 if (child == 0) { /* child */
449 if (argc >= 1) {
450 execvp(argv[0], argv);
451 } else {
452 struct passwd *passwd = getpwuid(getuid());
453 if (passwd == NULL)
454 die("getpwid:");
455 execlp(passwd->pw_shell, passwd->pw_shell, NULL);
456 }
457
458 perror("execvp");
459 _exit(127);
460 }
461
462 /* set maximum memory size for scrollback buffer */
463 if (setrlimit(RLIMIT_DATA, &rlimit) == -1)
464 die("setrlimit:");
465
466 #ifdef __OpenBSD__
467 if (pledge("stdio tty proc", NULL) == -1)
468 die("pledge:");
469 #endif
470
471 if (signal(SIGWINCH, sigwinch) == SIG_ERR)
472 die("signal:");
473
474 struct termios new = dfl;
475 cfmakeraw(&new);
476 new.c_cc[VMIN ] = 1; /* return read if at least one byte …
477 new.c_cc[VTIME] = 0; /* no polling time for read from ter…
478 if (tcsetattr(STDIN_FILENO, TCSANOW, &new) == -1)
479 die("tcsetattr:");
480
481 size_t size = BUFSIZ, len = 0, pos = 0;
482 char *buf = calloc(size, sizeof *buf);
483 if (buf == NULL)
484 die("calloc:");
485
486 struct pollfd pfd[2] = {
487 {STDIN_FILENO, POLLIN, 0},
488 {mfd, POLLIN, 0}
489 };
490
491 for (;;) {
492 char input[BUFSIZ];
493
494 if (poll(pfd, LENGTH(pfd), -1) == -1 && errno != EINTR)
495 die("poll:");
496
497 if (doredraw) {
498 redraw();
499 doredraw = false;
500 }
501
502 if (pfd[0].revents & POLLHUP || pfd[1].revents & POLLHUP)
503 break;
504
505 if (pfd[0].revents & POLLIN) {
506 ssize_t n = read(STDIN_FILENO, input, sizeof(inp…
507
508 if (n == -1 && errno != EINTR)
509 die("read:");
510 if (n == 0)
511 break;
512
513 input[n] = '\0';
514
515 if (altscreen)
516 goto noevent;
517
518 for (size_t i = 0; i < LENGTH(rules); i++) {
519 if (strncmp(rules[i].seq, input,
520 strlen(rules[i].seq)) == 0) {
521 if (rules[i].event == SCROLL_UP)
522 scrollup(rules[i].lines);
523 if (rules[i].event == SCROLL_DOW…
524 scrolldown(buf, len,
525 rules[i].lines);
526 goto out;
527 }
528 }
529 noevent:
530 if (write(mfd, input, n) == -1)
531 die("write:");
532
533 if (bottom != TAILQ_FIRST(&head))
534 jumpdown(buf, len);
535 }
536 out:
537 if (pfd[1].revents & POLLIN) {
538 ssize_t n = read(mfd, input, sizeof(input)-1);
539
540 if (n == -1 && errno != EINTR)
541 die("read:");
542 if (n == 0) /* on exit of child we contin…
543 continue; /* let signal handler catch SI…
544
545 input[n] = '\0';
546
547 /* don't print child output while scrolling */
548 if (bottom == TAILQ_FIRST(&head))
549 if (write(STDOUT_FILENO, input, n) == -1)
550 die("write:");
551
552 /* iterate over the input buffer */
553 for (char *c = input; n-- > 0; c++) {
554 /* don't save alternative screen and */
555 /* clear screen esc sequences to scrollb…
556 if (skipesc(*c))
557 continue;
558
559 if (*c == '\n') {
560 addline(buf, len);
561 /* only advance bottom if scroll…
562 /* at the end of the scroll back…
563 if (bottom == NULL ||
564 TAILQ_PREV(bottom, tailhead,
565 entries) == TAILQ_FIRST(&h…
566 bottom = TAILQ_FIRST(&he…
567
568 memset(buf, 0, size);
569 len = pos = 0;
570 buf[pos++] = '\r';
571 } else if (*c == '\r') {
572 pos = 0;
573 continue;
574 }
575 buf[pos++] = *c;
576 if (pos > len)
577 len = pos;
578 if (len == size) {
579 size *= 2;
580 buf = earealloc(buf, size);
581 }
582 }
583 }
584 }
585
586 if (close(mfd) == -1)
587 die("close:");
588
589 int status;
590 if (waitpid(child, &status, 0) == -1)
591 die("waitpid:");
592
593 return WEXITSTATUS(status);
594 }
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.