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