]> ncurses.scripts.mit.edu Git - ncurses.git/blob - test/ditto.c
ncurses 6.2 - patch 20210905
[ncurses.git] / test / ditto.c
1 /****************************************************************************
2  * Copyright 2018-2020,2021 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29
30 /*
31  * Author: Thomas E. Dickey (1998-on)
32  *
33  * $Id: ditto.c,v 1.52 2021/08/15 20:07:11 tom Exp $
34  *
35  * The program illustrates how to set up multiple screens from a single
36  * program.
37  *
38  * If openpty() is supported, the command line parameters are titles for
39  * the windows showing each screen's data.
40  *
41  * If openpty() is not supported, you must invoke the program by specifying
42  * another terminal on the same machine by specifying its device, e.g.,
43  *      ditto /dev/ttyp1
44  */
45 #include <test.priv.h>
46 #include <sys/stat.h>
47
48 #if HAVE_DELSCREEN
49
50 #ifdef USE_PTHREADS
51 #include <pthread.h>
52 #endif
53
54 #ifdef USE_XTERM_PTY
55 #include USE_OPENPTY_HEADER
56 #endif
57
58 #define MAX_FIFO 256
59
60 #define THIS_FIFO(n) ((n) % MAX_FIFO)
61 #define NEXT_FIFO(n) THIS_FIFO((n) + 1)
62
63 typedef struct {
64     unsigned long sequence;
65     int head;
66     int tail;
67     int data[MAX_FIFO];
68 } FIFO;
69
70 typedef struct {
71     unsigned long sequence;
72 } PEEK;
73
74 /*
75  * Data "owned" for a single screen.  Each screen is divided into windows that
76  * show the text read from each terminal.  Input from a given screen will also
77  * be read into one window per screen.
78  */
79 typedef struct {
80     FILE *input;
81     FILE *output;
82     SCREEN *screen;             /* this screen - curses internal data */
83     int which1;                 /* this screen's index in DITTO[] array */
84     int length;                 /* length of windows[] and peeks[] */
85     char **titles;              /* per-window titles */
86     WINDOW **parents;           /* display boxes around each screen's data */
87     WINDOW **windows;           /* display data from each screen */
88     PEEK *peeks;                /* indices for each screen's fifo */
89     FIFO fifo;                  /* fifo for this screen */
90 #ifdef USE_PTHREADS
91     pthread_t thread;
92 #endif
93 } DITTO;
94
95 /*
96  * Structure used to pass multiple parameters via the use_screen()
97  * single-parameter interface.
98  */
99 typedef struct {
100     int source;                 /* which screen did character come from */
101     int target;                 /* which screen is character going to */
102     DITTO *ditto;               /* data for all screens */
103 } DDATA;
104
105 static GCC_NORETURN void failed(const char *);
106 static GCC_NORETURN void usage(void);
107
108 static void
109 failed(const char *s)
110 {
111     perror(s);
112     ExitProgram(EXIT_FAILURE);
113 }
114
115 static void
116 usage(void)
117 {
118     fprintf(stderr, "Usage: ditto [terminal1 ...]\n");
119     ExitProgram(EXIT_FAILURE);
120 }
121
122 /* Add to the head of the fifo, checking for overflow. */
123 static void
124 put_fifo(FIFO * fifo, int value)
125 {
126     int next = NEXT_FIFO(fifo->head);
127     if (next == fifo->tail)
128         fifo->tail = NEXT_FIFO(fifo->tail);
129     fifo->data[next] = value;
130     fifo->head = next;
131     fifo->sequence += 1;
132 }
133
134 /* Get data from the tail (oldest part) of the fifo, returning -1 if no data.
135  * Since each screen can peek into the fifo, we do not update the tail index,
136  * but modify the peek-index.
137  *
138  * FIXME - test/workaround for case where fifo gets more than a buffer
139  * ahead of peek.
140  */
141 static int
142 peek_fifo(FIFO * fifo, PEEK * peek)
143 {
144     int result = -1;
145     if (peek->sequence < fifo->sequence) {
146         result = fifo->data[THIS_FIFO(peek->sequence)];
147         peek->sequence += 1;
148     }
149     return result;
150 }
151
152 static FILE *
153 open_tty(char *path)
154 {
155     FILE *fp;
156 #ifdef USE_XTERM_PTY
157     int amaster;
158     int aslave;
159     char slave_name[1024];
160     char s_option[sizeof(slave_name) + 80];
161     const char *xterm_prog = 0;
162
163     if ((xterm_prog = getenv("XTERM_PROG")) == 0)
164         xterm_prog = "xterm";
165
166     if (openpty(&amaster, &aslave, slave_name, 0, 0) != 0
167         || strlen(slave_name) > sizeof(slave_name) - 1)
168         failed("openpty");
169     if (strrchr(slave_name, '/') == 0) {
170         errno = EISDIR;
171         failed(slave_name);
172     }
173     _nc_SPRINTF(s_option, _nc_SLIMIT(sizeof(s_option))
174                 "-S%s/%d", slave_name, aslave);
175     if (fork()) {
176         execlp(xterm_prog, xterm_prog, s_option, "-title", path, (char *) 0);
177         _exit(0);
178     }
179     fp = fdopen(amaster, "r+");
180     if (fp == 0)
181         failed(path);
182 #else
183     struct stat sb;
184
185     if (stat(path, &sb) == -1)
186         failed(path);
187     if ((sb.st_mode & S_IFMT) != S_IFCHR) {
188         errno = ENOTTY;
189         failed(path);
190     }
191     fp = fopen(path, "r+");
192     if (fp == 0)
193         failed(path);
194     printf("opened %s\n", path);
195 #endif
196     assert(fp != 0);
197     return fp;
198 }
199
200 static int
201 init_screen(
202 #if HAVE_USE_WINDOW
203                SCREEN *sp GCC_UNUSED,
204 #endif
205                void *arg)
206 {
207     DITTO *target = (DITTO *) arg;
208     int high, wide;
209     int k;
210
211     cbreak();
212     noecho();
213     scrollok(stdscr, TRUE);
214     box(stdscr, 0, 0);
215
216     target->parents = typeCalloc(WINDOW *, (size_t) target->length);
217     target->windows = typeCalloc(WINDOW *, (size_t) target->length);
218     target->peeks = typeCalloc(PEEK, (size_t) target->length);
219
220     high = (LINES - 2) / target->length;
221     wide = (COLS - 2);
222     for (k = 0; k < target->length; ++k) {
223         WINDOW *outer = newwin(high, wide, 1 + (high * k), 1);
224         WINDOW *inner = derwin(outer, high - 2, wide - 2, 1, 1);
225
226         box(outer, 0, 0);
227         MvWAddStr(outer, 0, 2, target->titles[k]);
228         wnoutrefresh(outer);
229
230         scrollok(inner, TRUE);
231         keypad(inner, TRUE);
232 #ifndef USE_PTHREADS
233         nodelay(inner, TRUE);
234 #endif
235
236         target->parents[k] = outer;
237         target->windows[k] = inner;
238     }
239     doupdate();
240     return TRUE;
241 }
242
243 static void
244 open_screen(DITTO * target, char **source, int length, int which1)
245 {
246     if (which1 != 0) {
247         target->input =
248             target->output = open_tty(source[which1]);
249     } else {
250         target->input = stdin;
251         target->output = stdout;
252     }
253
254     target->which1 = which1;
255     target->titles = source;
256     target->length = length;
257     target->fifo.head = -1;
258     target->screen = newterm((char *) 0,        /* assume $TERM is the same */
259                              target->output,
260                              target->input);
261
262     if (target->screen == 0)
263         failed("newterm");
264
265     (void) USING_SCREEN(target->screen, init_screen, target);
266 }
267
268 static int
269 close_screen(
270 #if HAVE_USE_WINDOW
271                 SCREEN *sp GCC_UNUSED,
272 #endif
273                 void *arg GCC_UNUSED)
274 {
275 #if HAVE_USE_WINDOW
276     (void) sp;
277 #endif
278     (void) arg;
279     return endwin();
280 }
281
282 /*
283  * Read data from the 'source' screen.
284  */
285 static int
286 read_screen(
287 #if HAVE_USE_WINDOW
288                SCREEN *sp GCC_UNUSED,
289 #endif
290                void *arg)
291 {
292     DDATA *data = (DDATA *) arg;
293     DITTO *ditto = &(data->ditto[data->source]);
294     WINDOW *win = ditto->windows[data->source];
295     int ch = wgetch(win);
296
297     if (ch > 0 && ch < 256)
298         put_fifo(&(ditto->fifo), ch);
299     else
300         ch = ERR;
301
302     return ch;
303 }
304
305 /*
306  * Write all of the data that's in fifos for the 'target' screen.
307  */
308 static int
309 write_screen(
310 #if HAVE_USE_WINDOW
311                 SCREEN *sp GCC_UNUSED,
312 #endif
313                 void *arg GCC_UNUSED)
314 {
315     DDATA *data = (DDATA *) arg;
316     DITTO *ditto = &(data->ditto[data->target]);
317     bool changed = FALSE;
318     int which;
319
320     for (which = 0; which < ditto->length; ++which) {
321         WINDOW *win = ditto->windows[which];
322         FIFO *fifo = &(data->ditto[which].fifo);
323         PEEK *peek = &(ditto->peeks[which]);
324         int ch;
325
326         while ((ch = peek_fifo(fifo, peek)) > 0) {
327             changed = TRUE;
328
329             waddch(win, (chtype) ch);
330             wnoutrefresh(win);
331         }
332     }
333
334     if (changed)
335         doupdate();
336     return OK;
337 }
338
339 static void
340 show_ditto(DITTO * data, int count, DDATA * ddata)
341 {
342     int n;
343
344     (void) data;
345     for (n = 0; n < count; n++) {
346         ddata->target = n;
347         USING_SCREEN(data[n].screen, write_screen, (void *) ddata);
348     }
349 }
350
351 #ifdef USE_PTHREADS
352 static void *
353 handle_screen(void *arg)
354 {
355     DDATA ddata;
356
357     memset(&ddata, 0, sizeof(ddata));
358     ddata.ditto = (DITTO *) arg;
359     ddata.source = ddata.ditto->which1;
360     ddata.ditto -= ddata.source;        /* -> base of array */
361
362     for (;;) {
363         int ch = read_screen(ddata.ditto->screen, &ddata);
364         if (ch == CTRL('D')) {
365             int later = (ddata.source ? ddata.source : -1);
366             int j;
367
368             for (j = ddata.ditto->length - 1; j > 0; --j) {
369                 if (j != later) {
370                     pthread_cancel(ddata.ditto[j].thread);
371                 }
372             }
373             if (later > 0) {
374                 pthread_cancel(ddata.ditto[later].thread);
375             }
376             break;
377         }
378         show_ditto(ddata.ditto, ddata.ditto->length, &ddata);
379     }
380     return NULL;
381 }
382 #endif
383
384 int
385 main(int argc, char *argv[])
386 {
387     int j;
388     DITTO *data;
389 #ifndef USE_PTHREADS
390     int count;
391 #endif
392
393     if (argc <= 1)
394         usage();
395
396     if ((data = typeCalloc(DITTO, (size_t) argc)) == 0)
397         failed("calloc data");
398
399     assert(data != 0);
400
401     for (j = 0; j < argc; j++) {
402         open_screen(&data[j], argv, argc, j);
403     }
404
405 #ifdef USE_PTHREADS
406     /*
407      * For multi-threaded operation, set up a reader for each of the screens.
408      * That uses blocking I/O rather than polling for input, so no calls to
409      * napms() are needed.
410      */
411     for (j = 0; j < argc; j++) {
412         (void) pthread_create(&(data[j].thread), NULL, handle_screen, &data[j]);
413     }
414     pthread_join(data[1].thread, NULL);
415 #else
416     /*
417      * Loop, reading characters from any of the inputs and writing to all
418      * of the screens.
419      */
420     for (count = 0;; ++count) {
421         DDATA ddata;
422         int ch;
423         int which = (count % argc);
424
425         napms(20);
426
427         ddata.source = which;
428         ddata.ditto = data;
429
430         ch = USING_SCREEN(data[which].screen, read_screen, &ddata);
431         if (ch == CTRL('D')) {
432             break;
433         } else if (ch != ERR) {
434             show_ditto(data, argc, &ddata);
435         }
436     }
437 #endif
438
439     /*
440      * Cleanup and exit
441      */
442     for (j = argc - 1; j >= 0; j--) {
443         USING_SCREEN(data[j].screen, close_screen, 0);
444         fprintf(data[j].output, "**Closed\r\n");
445
446         /*
447          * Closing before a delscreen() helps ncurses determine that there
448          * is no valid output buffer, and can remove the setbuf() data.
449          */
450         fflush(data[j].output);
451         fclose(data[j].output);
452         delscreen(data[j].screen);
453     }
454     ExitProgram(EXIT_SUCCESS);
455 }
456 #else
457 int
458 main(void)
459 {
460     printf("This program requires the curses delscreen function\n");
461     ExitProgram(EXIT_FAILURE);
462 }
463 #endif