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