Mercurial > projects > pwm
comparison pager.c @ 17:a08ef0674d8e
Page long output in interactive mode
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Sat, 12 Aug 2017 10:41:52 +0200 |
parents | |
children | 1e39a251cbe9 |
comparison
equal
deleted
inserted
replaced
16:a07665727c19 | 17:a08ef0674d8e |
---|---|
1 /* | |
2 * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name> | |
3 * | |
4 * Permission is hereby granted, free of charge, to any person obtaining | |
5 * a copy of this software and associated documentation files (the | |
6 * "Software"), to deal in the Software without restriction, including | |
7 * without limitation the rights to use, copy, modify, merge, publish, | |
8 * distribute, sublicense, and/or sell copies of the Software, and to | |
9 * permit persons to whom the Software is furnished to do so, subject to | |
10 * the following conditions: | |
11 * | |
12 * The above copyright notice and this permission notice shall be included | |
13 * in all copies or substantial portions of the Software. | |
14 * | |
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
22 */ | |
23 | |
24 #include "compat.h" | |
25 | |
26 #ifdef HAVE_ERR_H | |
27 #include <err.h> | |
28 #endif /* HAVE_ERR_H */ | |
29 #include <errno.h> | |
30 #include <fcntl.h> | |
31 #include <limits.h> | |
32 #include <signal.h> | |
33 #include <stdio.h> | |
34 #include <string.h> | |
35 #include <sys/ioctl.h> | |
36 #ifdef HAVE_SYS_QUEUE_H | |
37 #include <sys/queue.h> | |
38 #endif /* HAVE_SYS_QUEUE_H */ | |
39 #include <sys/stat.h> | |
40 #include <sys/types.h> | |
41 #include <termios.h> | |
42 #include <unistd.h> | |
43 #include <wchar.h> | |
44 | |
45 #include "pwm.h" | |
46 #include "pager.h" | |
47 #include "util.h" | |
48 | |
49 #ifndef TCSASOFT | |
50 #define TCSASOFT 0 | |
51 #endif /* !TCSASOFT */ | |
52 | |
53 #ifndef _NSIG | |
54 #ifdef NSIG | |
55 #define _NSIG NSIG | |
56 #else /* NSIG */ | |
57 #define _NSIG 128 | |
58 #endif /* NSIG */ | |
59 #endif /* !_NSIG */ | |
60 | |
61 #ifndef _PATH_TTY | |
62 #define _PATH_TTY "/dev/tty" | |
63 #endif | |
64 | |
65 struct pager { | |
66 TAILQ_HEAD(lines_head, lines_entry) lines_head; | |
67 FILE *fp; | |
68 size_t buf_size; | |
69 char *buf; | |
70 }; | |
71 | |
72 struct lines_entry { | |
73 TAILQ_ENTRY(lines_entry) entry; | |
74 char *line; | |
75 }; | |
76 | |
77 static volatile sig_atomic_t signals[_NSIG]; | |
78 | |
79 static void | |
80 sig_handler(int signo) | |
81 { | |
82 signals[signo] = 1; | |
83 } | |
84 | |
85 static int | |
86 getch_prompt(const char *prompt) | |
87 { | |
88 int retval = -1; | |
89 int signo; | |
90 int fd; | |
91 struct termios saved_term; | |
92 struct termios term; | |
93 struct sigaction sa; | |
94 struct sigaction saved_sa_hup; | |
95 struct sigaction saved_sa_int; | |
96 struct sigaction saved_sa_quit; | |
97 struct sigaction saved_sa_pipe; | |
98 struct sigaction saved_sa_alrm; | |
99 struct sigaction saved_sa_term; | |
100 struct sigaction saved_sa_tstp; | |
101 struct sigaction saved_sa_ttin; | |
102 struct sigaction saved_sa_ttou; | |
103 char c; | |
104 int need_restart = 0; | |
105 | |
106 printf("%s", prompt); | |
107 fflush(stdout); | |
108 | |
109 restart: | |
110 for (signo = 0; signo < _NSIG; signo++) { | |
111 signals[signo] = 0; | |
112 } | |
113 | |
114 fd = open(_PATH_TTY, O_RDONLY); | |
115 if (fd < 0) { | |
116 return (-1); | |
117 } | |
118 | |
119 if (tcgetattr(fd, &saved_term) != 0) { | |
120 close(fd); | |
121 return (-1); | |
122 } | |
123 memcpy(&term, &saved_term, sizeof (struct termios)); | |
124 | |
125 /* enter raw mode and turn echo off */ | |
126 term.c_lflag &= ~(ICANON | ECHO); | |
127 while ((tcsetattr(fd, TCSAFLUSH | TCSASOFT, &term) < 0) && | |
128 (errno != EINTR)) { | |
129 continue; | |
130 } | |
131 | |
132 /* install temporary signal handler */ | |
133 sigemptyset(&sa.sa_mask); | |
134 sa.sa_flags = 0; | |
135 sa.sa_handler = sig_handler; | |
136 sigaction(SIGHUP, &sa, &saved_sa_hup); | |
137 sigaction(SIGINT, &sa, &saved_sa_int); | |
138 sigaction(SIGQUIT, &sa, &saved_sa_quit); | |
139 sigaction(SIGPIPE, &sa, &saved_sa_pipe); | |
140 sigaction(SIGALRM, &sa, &saved_sa_alrm); | |
141 sigaction(SIGTERM, &sa, &saved_sa_term); | |
142 sigaction(SIGTSTP, &sa, &saved_sa_tstp); | |
143 sigaction(SIGTTIN, &sa, &saved_sa_ttin); | |
144 sigaction(SIGTTOU, &sa, &saved_sa_ttou); | |
145 | |
146 /* read key */ | |
147 if (read(fd, &c, 1) == sizeof (c)) { | |
148 retval = c; | |
149 } | |
150 | |
151 /* restore previous mode and echo setting */ | |
152 do { | |
153 while ((tcsetattr(fd, TCSAFLUSH | TCSASOFT, | |
154 &saved_term) < 0) && (errno != EINTR)) { | |
155 continue; | |
156 } | |
157 } while ((tcgetattr(fd, &term) == 0) && | |
158 (term.c_lflag != saved_term.c_lflag)); | |
159 | |
160 /* restore previous signal handlers */ | |
161 sigaction(SIGHUP, &saved_sa_hup, &sa); | |
162 sigaction(SIGINT, &saved_sa_int, &sa); | |
163 sigaction(SIGQUIT, &saved_sa_quit, &sa); | |
164 sigaction(SIGPIPE, &saved_sa_pipe, &sa); | |
165 sigaction(SIGALRM, &saved_sa_alrm, &sa); | |
166 sigaction(SIGTERM, &saved_sa_term, &sa); | |
167 sigaction(SIGTSTP, &saved_sa_tstp, &sa); | |
168 sigaction(SIGTTIN, &saved_sa_ttin, &sa); | |
169 sigaction(SIGTTOU, &saved_sa_ttou, &sa); | |
170 | |
171 /* erase prompt */ | |
172 printf("\r%*s\r", (int)strlen(prompt), ""); | |
173 fflush(stdout); | |
174 | |
175 while ((close(fd) < 0) && (errno != EINTR)) { | |
176 continue; | |
177 } | |
178 | |
179 /* re-raise signals received */ | |
180 for (signo = 0; signo < _NSIG; signo++) { | |
181 if (signals[signo]) { | |
182 raise(signo); | |
183 if ((signo == SIGTSTP) || (signo == SIGTTIN) || | |
184 (signo == SIGTTOU)) { | |
185 need_restart = 1; | |
186 } | |
187 } | |
188 } | |
189 if (need_restart) { | |
190 goto restart; | |
191 } | |
192 | |
193 return (retval); | |
194 } | |
195 | |
196 struct pager * | |
197 pager_create(FILE *fp) | |
198 { | |
199 struct pager *pager; | |
200 | |
201 pager = xmalloc(sizeof (struct pager)); | |
202 TAILQ_INIT(&pager->lines_head); | |
203 pager->fp = fp; | |
204 pager->buf_size = BUFSIZ; | |
205 pager->buf = xmalloc(BUFSIZ); | |
206 | |
207 return (pager); | |
208 } | |
209 | |
210 void | |
211 pager_destroy(struct pager *pager) | |
212 { | |
213 struct lines_entry *entry; | |
214 struct lines_entry *entry_tmp; | |
215 | |
216 if (pager == NULL) { | |
217 return; | |
218 } | |
219 | |
220 TAILQ_FOREACH_SAFE(entry, &pager->lines_head, entry, entry_tmp) { | |
221 TAILQ_REMOVE(&pager->lines_head, entry, entry); | |
222 free(entry->line); | |
223 free(entry); | |
224 } | |
225 free(pager->buf); | |
226 free(pager); | |
227 } | |
228 | |
229 int | |
230 pager_vprintf(struct pager *pager, const char *fmt, va_list args) | |
231 { | |
232 int len; | |
233 va_list args_new; | |
234 char *p; | |
235 size_t line_len; | |
236 struct lines_entry *entry; | |
237 size_t line_offset; | |
238 | |
239 va_copy(args_new, args); | |
240 | |
241 /* format multibyte string in buffer */ | |
242 len = vsnprintf(NULL, 0, fmt, args); | |
243 if (len < 0) { | |
244 err(1, "vsnprintf"); | |
245 } | |
246 | |
247 if (pager->buf_size < (size_t)len + 1) { | |
248 pager->buf_size = len + 1; | |
249 pager->buf = xrealloc(pager->buf, pager->buf_size); | |
250 } | |
251 | |
252 len = vsnprintf(pager->buf, pager->buf_size, fmt, args_new); | |
253 if (len < 0) { | |
254 err(1, "vsnprintf"); | |
255 } | |
256 | |
257 /* split buffer into lines */ | |
258 for (p = pager->buf; *p != '\0'; p += line_len) { | |
259 line_len = strcspn(p, "\n"); | |
260 if (p[line_len] == '\n') { | |
261 line_len++; | |
262 } | |
263 | |
264 entry = TAILQ_LAST(&pager->lines_head, lines_head); | |
265 line_offset = (entry != NULL) ? strlen(entry->line) : 0; | |
266 if ((entry != NULL) && (entry->line[line_offset - 1] != '\n')) { | |
267 /* | |
268 * append to the last line if it doesn't end with a | |
269 * newline | |
270 */ | |
271 entry->line = xrealloc(entry->line, line_offset + | |
272 line_len + 1); | |
273 memcpy(entry->line + line_offset, p, line_len); | |
274 entry->line[line_offset + line_len] = '\0'; | |
275 } else { | |
276 entry = xmalloc(sizeof (struct lines_entry)); | |
277 entry->line = xmalloc(line_len + 1); | |
278 memcpy(entry->line, p, line_len); | |
279 entry->line[line_len] = '\0'; | |
280 TAILQ_INSERT_TAIL(&pager->lines_head, entry, entry); | |
281 } | |
282 } | |
283 | |
284 va_end(args_new); | |
285 | |
286 return (len); | |
287 } | |
288 | |
289 int | |
290 pager_printf(struct pager *pager, const char *fmt, ...) | |
291 { | |
292 int len; | |
293 va_list args; | |
294 | |
295 va_start(args, fmt); | |
296 len = pager_vprintf(pager, fmt, args); | |
297 va_end(args); | |
298 | |
299 return (len); | |
300 } | |
301 | |
302 static unsigned int | |
303 mbsnprint(unsigned int cols, const char *s, FILE *fp) | |
304 { | |
305 const char *p = s; | |
306 unsigned int col = 0; | |
307 int mb_len; | |
308 wchar_t wc; | |
309 int width; | |
310 char mb_buf[MB_LEN_MAX]; | |
311 | |
312 while ((*p != '\n') && (*p != '\0') && (col < cols)) { | |
313 mb_len = mbtowc(&wc, p, MB_CUR_MAX); | |
314 if (mb_len != -1) { | |
315 width = wcwidth(wc); | |
316 if (width != -1) { | |
317 if (snprintf(mb_buf, sizeof (mb_buf), "%.*s", | |
318 mb_len, p) != mb_len) { | |
319 err(1, "snprintf"); | |
320 } | |
321 } else { | |
322 /* | |
323 * non-printable character, print | |
324 * replacement character | |
325 */ | |
326 width = 1; | |
327 if (snprintf(mb_buf, sizeof (mb_buf), | |
328 "\357\277\275") != 3) { | |
329 err(1, "snprintf"); | |
330 } | |
331 } | |
332 } else { | |
333 /* | |
334 * decoding failed, reset state and skip one | |
335 * byte | |
336 */ | |
337 mbtowc(NULL, NULL, MB_CUR_MAX); | |
338 mb_len = 1; | |
339 | |
340 /* print replacement character */ | |
341 width = 1; | |
342 if (snprintf(mb_buf, sizeof (mb_buf), | |
343 "\357\277\275") != 3) { | |
344 err(1, "snprintf"); | |
345 } | |
346 } | |
347 | |
348 p += mb_len; | |
349 col += width; | |
350 if (col <= cols) { | |
351 if (fputs(mb_buf, fp) == EOF) { | |
352 err(1, "fputs"); | |
353 } | |
354 } | |
355 } | |
356 | |
357 fputc('\n', fp); | |
358 fflush(fp); | |
359 | |
360 return (col); | |
361 } | |
362 | |
363 void | |
364 pager_show(struct pager *pager) | |
365 { | |
366 int is_interactive; | |
367 unsigned int rows = 24; | |
368 unsigned int cols = 80; | |
369 #ifdef TIOCGWINSZ | |
370 struct winsize ws; | |
371 #endif /* TIOCGWINSZ */ | |
372 unsigned int row = 0; | |
373 struct lines_entry *entry; | |
374 | |
375 is_interactive = (isatty(STDIN_FILENO) && (pager->fp == stdout)); | |
376 | |
377 #ifdef TIOCGWINSZ | |
378 if (is_interactive) { | |
379 /* determine terminal size */ | |
380 if (ioctl(fileno(pager->fp), TIOCGWINSZ, &ws) == 0) { | |
381 rows = (ws.ws_row > 0) ? ws.ws_row : rows; | |
382 cols = (ws.ws_col > 0) ? ws.ws_col : cols; | |
383 } | |
384 } | |
385 #endif /* TIOCGWINSZ */ | |
386 | |
387 TAILQ_FOREACH(entry, &pager->lines_head, entry) { | |
388 if (is_interactive) { | |
389 mbsnprint(cols, entry->line, pager->fp); | |
390 row++; | |
391 if (row == rows - 1) { | |
392 getch_prompt("--More--"); | |
393 row = 0; | |
394 } | |
395 } else { | |
396 fprintf(pager->fp, "%s", entry->line); | |
397 fflush(pager->fp); | |
398 } | |
399 } | |
400 } |