1 /* main.c
2  *
3  * Omnitty SSH Multiplexer
4  * Copyright (c) 2004 Bruno Takahashi C. de Oliveira
5  * All rights reserved.
6  *
7  * LICENSE INFORMATION:
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public
19  * License along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
21  * Copyright (c) 2002 Bruno T. C. de Oliveira
22  */
23 
24 #include <ncurses.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 #include <signal.h>
28 #include <string.h>
29 
30 #include "curutil.h"
31 #include "machine.h"
32 #include "machmgr.h"
33 #include "minibuf.h"
34 #include "menu.h"
35 
36 /* minimum terminal dimensions to run program */
37 #define MIN_REQUIRED_WIDTH 80
38 #define MIN_REQUIRED_HEIGHT 25
39 #define MAX_HOSTNAME_LENGTH 64
40 
41 #define REMINDER_LINE "OmNiTTY-R v" OMNITTY_VERSION \
42                       "  \007F1\007:menu  \006F2/3\007:sel  \003F4\007:tag" \
43                       "  \002F5\007:add  \001F6\007:del" \
44                       "  \005F7\007:mcast"
45 
46 #define SPLASH_LINE_1 "OmNiTTY Reborn v" OMNITTY_VERSION
47 #define SPLASH_LINE_2 "Copyright (c) 2004 Bruno T. C. de Oliveira"
48 
49 /* how many characters wide the list window will be, by default */
50 #define LISTWIN_DEFAULT_CHARS 8
51 #define TERMWIN_DEFAULT_CHARS 80
52 #define TERMWIN_MIN 80
53 
54 #define RTFM "Syntax: omnitty [-W list_width] [-T term_width]\n" \
55              "\n" \
56              "     -W        specifies width of machine list area\n" \
57              "               (default is 8 characters)\n" \
58              "\n" \
59              "     -T        specifies width of terminal area\n" \
60              "               (default is 80 characters)\n" \
61              "\n"
62 
63 static WINDOW *listwin   = NULL;
64 static WINDOW *sumwin    = NULL;
65 static WINDOW *vtwin     = NULL;
66 static WINDOW *minibuf = NULL;
67 static volatile int zombie_count = 0;
68 
sigchld_handler(int signo)69 void sigchld_handler(int signo) { zombie_count++; }
70 
curses_init()71 void curses_init() {
72    int w, h, i = 0;
73 
74    initscr();
75    start_color();
76    noecho();
77    keypad(stdscr, TRUE);
78    timeout(200);
79    raw();
80    curutil_colorpair_init();
81    clear();
82 
83    /* register some alternate escape sequences for the function keys,
84     * to improve compatibility with several types of terminal emulators */
85    define_key("\eOP",   KEY_F(1)); define_key("\eOQ",   KEY_F(2));
86    define_key("\eOR",   KEY_F(3)); define_key("\eOS",   KEY_F(4));
87    define_key("\e[11~", KEY_F(1)); define_key("\e[12~", KEY_F(2));
88    define_key("\e[13~", KEY_F(3)); define_key("\e[14~", KEY_F(4));
89    define_key("\e[15~", KEY_F(5)); define_key("\e[17~", KEY_F(6));
90    define_key("\e[18~", KEY_F(7)); define_key("\e[19~", KEY_F(8));
91    define_key("\e[20~", KEY_F(9)); define_key("\e[21~", KEY_F(10));
92    /* If "Sun Function-Keys" is enabled in your Xterm: */
93    define_key("\e[224z", KEY_F(1)); define_key("\e[225z", KEY_F(2));
94    define_key("\e[226z", KEY_F(3)); define_key("\e[227z", KEY_F(4));
95    define_key("\e[228z", KEY_F(5)); define_key("\e[229z", KEY_F(6));
96    define_key("\e[230z", KEY_F(7)); define_key("\e[231z", KEY_F(8));
97    define_key("\e[232z", KEY_F(9)); define_key("\e[233z", KEY_F(10));
98 
99    getmaxyx(stdscr, h, w);
100    if (h < MIN_REQUIRED_HEIGHT || w < MIN_REQUIRED_WIDTH) {
101       endwin();
102       fprintf(stderr, "ERROR: omnitty requires a %d x %d terminal to run.\n",
103                         MIN_REQUIRED_WIDTH, MIN_REQUIRED_HEIGHT);
104       exit(1);
105    }
106 
107    wmove(stdscr, h/2, (w - strlen(SPLASH_LINE_1))/2);
108    curutil_attrset(stdscr, 0x40);
109    waddstr(stdscr, SPLASH_LINE_1);
110 
111    curutil_attrset(stdscr, 0x70);
112    wmove(stdscr, h/2 + 1, (w - strlen(SPLASH_LINE_2))/2);
113    waddstr(stdscr, SPLASH_LINE_2);
114 
115    wrefresh(stdscr);
116    while (getch() < 0 && i < 10) i++;
117 
118    wclear(stdscr);
119    wrefresh(stdscr);
120 }
121 
122 /* Window layout:
123  *
124  *      list    summary     terminal window
125  *     window   window
126  *    |-------|--------X|--------------------------------|
127  *    0       A        BC                             termcols-1
128  *
129  * A = list_win_chars + 2
130  */
wins_init(int * vtrows,int * vtcols,int list_win_chars,int term_win_chars)131 void wins_init(int *vtrows, int *vtcols, int list_win_chars,
132                                                 int term_win_chars) {
133    int termcols, termrows, A, B, C;
134    const char *p;
135 
136    /* obtain terminal dimensions */
137    getmaxyx(stdscr, termrows, termcols);
138 
139    /* the geometry is hard-coded here, but nowhere else... so I don't
140     * see a lot of point using #defines or anything any more sophisticated */
141    A = list_win_chars + 2;
142    C = termcols - term_win_chars;
143    B = C-1;
144    if (B < A) B = A, C = B + 1;
145 
146    *vtcols = termcols - C;
147    *vtrows = termrows - 3;
148 
149    /* actually create the windows */
150    listwin = newwin(termrows-3, A - 0, 1, 0);
151    sumwin  = (B - A >= 3) ? newwin(termrows-3, B - A, 1, A) : NULL;
152    vtwin   = newwin(termrows-3, *vtcols, 1, C);
153    minibuf = newwin(1, termcols, termrows-1, 0);
154 
155    /* draw the top decoration line */
156    wattrset(stdscr, COLOR_PAIR(3) | A_BOLD);
157    wmove(stdscr, 0, 0);
158    whline(stdscr, ACS_HLINE | A_NORMAL, termcols);
159 
160    /* draw instruction line */
161    wattrset(stdscr, COLOR_PAIR(4*8) | A_BOLD);
162    wmove(stdscr, termrows-2, 0);
163    whline(stdscr, ' ', termcols);
164    wmove(stdscr, termrows-2, 0);
165    p = REMINDER_LINE;
166    while (*p) {
167       if (*p >= 0 && *p <= 7)
168          wattrset(stdscr, COLOR_PAIR(4*8 + 7 - *p) | A_BOLD);
169       else
170          waddch(stdscr, *p);
171       p++;
172    }
173 
174    /* draw the separator at column B */
175    wattrset(stdscr, COLOR_PAIR(3) | A_BOLD);
176    wmove(stdscr, 0, B);
177    wvline(stdscr, ACS_VLINE | A_NORMAL, termrows - 2);
178    wmove(stdscr, 0, B);          waddch(stdscr, ACS_TTEE);
179    wrefresh(stdscr);
180 
181    /* draw window titles */
182    if (termcols > 90) {
183       wmove(stdscr, 0, 2);
184       waddstr(stdscr, "[Machines]");
185       wmove(stdscr, 0, B+2);
186       waddstr(stdscr, "[Terminal]");
187    }
188 
189    /* make the cursor position be irrelevant for all windows except
190     * the terminal window */
191    leaveok(listwin, TRUE);
192    if (sumwin) leaveok(sumwin, TRUE);
193 
194    /* draw all windows */
195    touchwin(listwin);   wclear(listwin);
196    touchwin(vtwin);     wclear(vtwin);
197    if (sumwin) { touchwin(sumwin); wclear(sumwin); }
198    touchwin(minibuf); wclear(minibuf);
199 }
200 
update_cast_label()201 void update_cast_label() {
202    /* draws the label that says 'single cast' or 'multicast' on minibuffer */
203    int termwidth, termheight;
204    const char *msg;
205    getmaxyx(minibuf, termheight, termwidth);
206 
207    if (machmgr_is_multicast()) {
208       curutil_attrset(minibuf, 0xF9); /* bright blinking white over red */
209       msg = "!!! MULTICAST MODE !!!";
210    }
211    else {
212       curutil_attrset(minibuf, 0x40);
213       msg = "singlecast mode";
214    }
215 
216    werase(minibuf);
217    wmove(minibuf, 0, termwidth - strlen(msg));
218    waddstr(minibuf, msg);
219 
220    leaveok(minibuf, TRUE);  /* prevent cursor movement */
221    wrefresh(minibuf);
222    leaveok(minibuf, FALSE);
223 }
224 
redraw(bool force_full_redraw)225 void redraw(bool force_full_redraw) {
226    if (force_full_redraw) {
227       touchwin(stdscr);
228       wrefresh(stdscr);
229    }
230 
231    /* draw machine list */
232    machmgr_draw_list();
233    if (force_full_redraw) touchwin(listwin);
234    wrefresh(listwin);
235 
236    /* draw summary window, if there is one */
237    if (sumwin) {
238       machmgr_draw_summary(sumwin);
239       if (force_full_redraw) touchwin(sumwin);
240       wrefresh(sumwin);
241    }
242 
243    /* draw vt window */
244    machmgr_draw_vt(vtwin);
245    if (force_full_redraw) touchwin(vtwin);
246    wrefresh(vtwin);
247 
248    /* draw the 'multicast/singlecast' label */
249    update_cast_label();
250 }
251 
add_machines_from_file(const char * file)252 static void add_machines_from_file(const char *file) {
253    static char buf[128];
254    bool pipe = false;
255    FILE *f;
256 
257    if (getenv("OMNITTY_AT_COMMAND")) {
258       /* popen() a command */
259       pipe = true;
260       strcpy(buf, getenv("OMNITTY_AT_COMMAND"));
261       strcat(buf, " ");
262       strcat(buf, file);
263       strcat(buf, " 2>/dev/null");
264       f = popen(buf, "r");
265    }
266    else f = fopen(file, "r");
267 
268    if (!f) {
269       minibuf_msg(minibuf, pipe ?
270          "Can't execute command specified by OMNITTY_AT_COMMAND" :
271          "Can't read that file.", 0xF1);
272       return;
273    }
274 
275    minibuf_put(minibuf, pipe ? "Adding machines supplied by command..." :
276                                "Adding machines from file...", 0x70);
277 
278    while (1 == fscanf(f, "%s", buf)) machmgr_add(buf);
279 
280    if (pipe) {
281       if (0 != pclose(f))
282          minibuf_msg(minibuf, "Command given by OMNITTY_AT_COMMAND exited "
283                               "with error.", 0xF1);
284       /* at this point SIGCHLD will have caused zombie_count to be one more
285        * than it should, since the child command has already been reaped
286        * by pclose(). If we don't correct zombie_count, wait() will block
287        * in the main loop, since it will try to reap a zombie that does not yet
288        * exist. */
289       zombie_count--;
290    }
291    else
292       fclose(f);
293 
294    minibuf_put(minibuf, NULL, 0x70);
295 }
296 
add_machine()297 static void add_machine() {
298    static char buf[MAX_HOSTNAME_LENGTH];
299 
300    *buf = 0;
301    if (minibuf_prompt(minibuf, "Add: ", 0xE0, buf, sizeof(buf))) {
302       if (*buf == '@') add_machines_from_file(buf+1);
303       else machmgr_add(buf);
304    }
305 }
306 
delete_machine()307 static void delete_machine() {
308    static char buf[2];
309    *buf = 0;
310    if (minibuf_prompt(minibuf, "Really delete it [y/n]?", 0x90, buf, 2)
311        && (*buf == 'y' || *buf == 'Y')) machmgr_delete_current();
312 }
313 
main(int argc,char ** argv)314 int main(int argc, char **argv) {
315    int vtcols, vtrows, ch = 0;
316    int list_win_chars = LISTWIN_DEFAULT_CHARS;
317    int term_win_chars = TERMWIN_DEFAULT_CHARS;
318    bool quit = false;
319    pid_t chldpid;
320 
321    /* process command-line options */
322    while ( 0 < (ch = getopt(argc, argv, "W:T:")) ) {
323       switch (ch) {
324          case 'W': list_win_chars = atoi(optarg); break;
325 	 case 'T': term_win_chars = atoi(optarg);
326 		   if( term_win_chars < TERMWIN_MIN ) {
327 		       fprintf(stderr, " terminal area too narrow: %d\n",
328                                                         term_win_chars);
329 		       fputs(RTFM, stderr);
330 		       exit(2);
331 		   }
332 		   break;
333          default: fputs(RTFM, stderr); exit(2);
334       }
335    }
336    signal(SIGCHLD, sigchld_handler);
337    curses_init();
338    wins_init(&vtrows, &vtcols, list_win_chars, term_win_chars);
339    menu_init(minibuf);
340 
341    machmgr_init(listwin, vtrows, vtcols);
342 
343    while (!quit) {
344       if (zombie_count) {
345          if ((chldpid = waitpid(-1, NULL, WNOHANG)) > 0) {
346             zombie_count--;
347             machmgr_handle_death(chldpid);
348          }
349       }
350 
351       machmgr_update();
352       redraw(false);
353 
354       ch = getch();
355       if (ch < 0) continue;
356 
357       switch (ch) {
358          case KEY_F(1): menu_show(); redraw(true); break;
359          case KEY_F(2): machmgr_prev_machine(); break;
360          case KEY_F(3): machmgr_next_machine(); break;
361          case KEY_F(4): machmgr_toggle_tag_current(); break;
362          case KEY_F(5): add_machine(); break;
363          case KEY_F(6): delete_machine(); break;
364          case KEY_F(7): machmgr_toggle_multicast(); break;
365          default: machmgr_forward_keypress(ch); break;
366       }
367    }
368 
369    endwin();
370    return 0;
371 }
372 
373