1 | // **************************************************************************
|
---|
2 | /** @class Shell
|
---|
3 |
|
---|
4 | @brief Implementation of a console based user shell with an input and output window
|
---|
5 |
|
---|
6 | Shell is based on the ReadlineWindow class. It creates two windows
|
---|
7 | (panels) using ncurses which are used for input and output. The output
|
---|
8 | window panel is displayed on top of the input panel, but can be hidden
|
---|
9 | for example by pressing F1.
|
---|
10 |
|
---|
11 | The idea is that continous messages like logging messages do not interfere
|
---|
12 | with the input area, although one still has both diplayed in the same
|
---|
13 | console window.
|
---|
14 |
|
---|
15 | To get a list of the command and functions supported by Shell
|
---|
16 | type 'h' or 'help' at a command line prompt.
|
---|
17 |
|
---|
18 | The usage is quite simple. Instantiate an object of Shell with the
|
---|
19 | programname as an argument. For its meaning see the base class
|
---|
20 | documentation Readline::Readline(). The created input- and output-
|
---|
21 | stream can be accessed through GetStreamIn() and GetStreamOut()
|
---|
22 | whihc are both ostreams, one redirected to the input window and the
|
---|
23 | other one redirected to the output window. Especially, GetStreamIn()
|
---|
24 | should not be used while the Readline prompt is in progress, but can for
|
---|
25 | example be used to display errors about what was entered.
|
---|
26 |
|
---|
27 | The recommanded way of usage is:
|
---|
28 |
|
---|
29 | \code
|
---|
30 |
|
---|
31 | static Shell shell("myprog"); // readline will read the myprog.his history file
|
---|
32 |
|
---|
33 | while (1)
|
---|
34 | {
|
---|
35 | string txt = shell.Prompt("prompt> ");
|
---|
36 | if (txt=="quit)
|
---|
37 | break;
|
---|
38 |
|
---|
39 | // ... do something ...
|
---|
40 |
|
---|
41 | shell.AddHistory(txt);
|
---|
42 | }
|
---|
43 |
|
---|
44 | // On destruction redline will write the current history to the file
|
---|
45 | // By declaring the Shell static the correct terminal is restored even
|
---|
46 | // the program got killed (not if killed with SIGABRT)
|
---|
47 |
|
---|
48 | \endcode
|
---|
49 |
|
---|
50 | If for some reason the terminal is not correctly restored type (maybe blindly)
|
---|
51 | <I>reset</I> in your console. This should restor everything back to normal.
|
---|
52 |
|
---|
53 | @section References
|
---|
54 |
|
---|
55 | - <A HREF="http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html">GNU Readline</A>
|
---|
56 | - <A HREF="http://www.gnu.org/software/ncurses">GNU Ncurses</A>
|
---|
57 |
|
---|
58 |
|
---|
59 | @todo
|
---|
60 | - Introduce the possibility to scroll in both windows
|
---|
61 | - Add redisplay functionality for both panels if the console was resized
|
---|
62 |
|
---|
63 | */
|
---|
64 | // **************************************************************************
|
---|
65 | #include "Shell.h"
|
---|
66 |
|
---|
67 | #include <fstream>
|
---|
68 | #include <iostream>
|
---|
69 |
|
---|
70 | #include <signal.h> // SIGWINCH
|
---|
71 | #include <sys/wait.h> // waitpid
|
---|
72 | #include <sys/ioctl.h> // ioctl
|
---|
73 |
|
---|
74 | #include <panel.h>
|
---|
75 |
|
---|
76 | #define FORK_ALLOWED
|
---|
77 |
|
---|
78 | using namespace std;
|
---|
79 |
|
---|
80 | Shell *Shell::This = 0;
|
---|
81 |
|
---|
82 | // --------------------------------------------------------------------------
|
---|
83 | //
|
---|
84 | //! This initializes the Ncurses environment. Since the ncurses environment
|
---|
85 | //! is global only one instance of this class is allowed.
|
---|
86 | //!
|
---|
87 | //! The first 8 color pairs (COLOR_PAIR) are set to the first 8 color
|
---|
88 | //! with default background.
|
---|
89 | //!
|
---|
90 | //! The shells windows and panels are created. And their pointers are
|
---|
91 | //! propagated to the two associated WindowLog streams.
|
---|
92 | //!
|
---|
93 | //! Also some key bindings are initialized.
|
---|
94 | //
|
---|
95 | Shell::Shell(const char *prgname) : ReadlineWindow(prgname),
|
---|
96 | fPanelHeight(13), fIsVisible(1)
|
---|
97 | {
|
---|
98 | if (stdscr!=0)
|
---|
99 | {
|
---|
100 | endwin();
|
---|
101 | cout << "ERROR - Only one instance of class Shell is allowed." << endl;
|
---|
102 | exit(-1);
|
---|
103 | }
|
---|
104 |
|
---|
105 | This = this;
|
---|
106 |
|
---|
107 | // ---------------------- Setup ncurses -------------------------
|
---|
108 |
|
---|
109 | initscr(); // Start curses mode
|
---|
110 |
|
---|
111 | cbreak(); // Line buffering disabled, Pass on
|
---|
112 | noecho(); // Switch off echo mode
|
---|
113 | nonl(); // Associate return with CR
|
---|
114 |
|
---|
115 | intrflush(stdscr, FALSE);
|
---|
116 | keypad(stdscr, FALSE); // Switch off keymapping for function keys
|
---|
117 |
|
---|
118 | start_color(); // Initialize ncurses colors
|
---|
119 | use_default_colors(); // Assign terminal default colors to -1
|
---|
120 | //assume_default_colors(-1, -1); // standard terminal colors assigned to pair 0
|
---|
121 |
|
---|
122 | // Setup colors
|
---|
123 | for (int i=1; i<8; i++)
|
---|
124 | init_pair(i, i, -1); // -1: def background
|
---|
125 |
|
---|
126 | signal(SIGWINCH, HandleResizeImp); // Attach HandleResize to SIGWINCH signal
|
---|
127 |
|
---|
128 | // ---------------------- Setup pansl --------------------------
|
---|
129 |
|
---|
130 | // Create the necessary windows
|
---|
131 | WINDOW *wins[4];
|
---|
132 | CreateWindows(wins);
|
---|
133 |
|
---|
134 | // Initialize the panels
|
---|
135 | fPanelIn = new_panel(wins[0]);
|
---|
136 | fPanelFrame = new_panel(wins[1]);
|
---|
137 | fPanelOut = new_panel(wins[2]);
|
---|
138 |
|
---|
139 | win.SetWindow(wins[0]);
|
---|
140 | wout.SetWindow(wins[2]);
|
---|
141 |
|
---|
142 | // Get the panels into the right order for startup
|
---|
143 | ShowHide(1);
|
---|
144 |
|
---|
145 | // Setup Readline
|
---|
146 | SetWindow(wins[0]);
|
---|
147 | SetColorPrompt(COLOR_PAIR(COLOR_BLUE));
|
---|
148 |
|
---|
149 | // ------------------- Setup key bindings -----------------------
|
---|
150 | BindKeySequence("\033OP", rl_proc_F1);
|
---|
151 | BindKeySequence("\033[1;5B", rl_scroll_top);
|
---|
152 | BindKeySequence("\033[1;5A", rl_scroll_top);
|
---|
153 | BindKeySequence("\033[1;3A", rl_scroll_bot);
|
---|
154 | BindKeySequence("\033[1;3B", rl_scroll_bot);
|
---|
155 | BindKeySequence("\033[5;3~", rl_top_inc);
|
---|
156 | BindKeySequence("\033[6;3~", rl_top_dec);
|
---|
157 | BindKeySequence("\033+", rl_top_resize);
|
---|
158 | BindKeySequence("\033-", rl_top_resize);
|
---|
159 |
|
---|
160 | /*
|
---|
161 | rl_bind_keyseq("\033\t", rl_complete); // Meta-Tab
|
---|
162 | rl_bind_keyseq("\033[1~", home); // Home (console)
|
---|
163 | rl_bind_keyseq("\033[H", home); // Home (x)
|
---|
164 | rl_bind_keyseq("\033[4~", end); // End (console)
|
---|
165 | rl_bind_keyseq("\033[F", end); // End (x)
|
---|
166 | rl_bind_keyseq("\033[A", up); // Up
|
---|
167 | rl_bind_keyseq("\033[B", down); // Down
|
---|
168 | rl_bind_keyseq("\033[[B", accept); // F2 (console)
|
---|
169 | rl_bind_keyseq("\033OQ", accept); // F2 (x)
|
---|
170 | rl_bind_keyseq("\033[21~", cancel); // F10
|
---|
171 | */
|
---|
172 |
|
---|
173 | // Ctrl+dn: \033[1;5B
|
---|
174 | // Ctrl+up: \033[1;5A
|
---|
175 | // Alt+up: \033[1;3A
|
---|
176 | // Alt+dn: \033[1;3B
|
---|
177 | // Alt+pg up: \033[5;3~
|
---|
178 | // Alt+pg dn: \033[6;3~
|
---|
179 | }
|
---|
180 |
|
---|
181 | // --------------------------------------------------------------------------
|
---|
182 | //
|
---|
183 | //! Ends the ncurses environment by calling endwin().
|
---|
184 | //
|
---|
185 | Shell::~Shell()
|
---|
186 | {
|
---|
187 | // Maybe not needed because the window is more or less valid until the
|
---|
188 | // object is destructed anyway.
|
---|
189 | //win.SetWindow(0);
|
---|
190 | //wout.SetWindow(0);
|
---|
191 | //SetWindow(0);
|
---|
192 |
|
---|
193 | endwin();
|
---|
194 | cout << "The end." << endl;
|
---|
195 | }
|
---|
196 |
|
---|
197 | // --------------------------------------------------------------------------
|
---|
198 | //
|
---|
199 | //! This function gets the windows into the expected order which is:
|
---|
200 | //!
|
---|
201 | //! @param v
|
---|
202 | //! - \b 0: Do not show the output panel
|
---|
203 | //! - \b 1: Show the output panel
|
---|
204 | //! - \b -1: Toggle the visibility of the output panel
|
---|
205 | //! - \b -2: Just update the panels, do not change their visibility
|
---|
206 | //
|
---|
207 | void Shell::ShowHide(int v)
|
---|
208 | {
|
---|
209 | if (v>-2)
|
---|
210 | fIsVisible = v==-1 ? !fIsVisible : v;
|
---|
211 |
|
---|
212 | if (fIsVisible)
|
---|
213 | {
|
---|
214 | show_panel(fPanelIn);
|
---|
215 | show_panel(fPanelFrame);
|
---|
216 | show_panel(fPanelOut);
|
---|
217 | }
|
---|
218 | else
|
---|
219 | {
|
---|
220 | show_panel(fPanelIn);
|
---|
221 | hide_panel(fPanelFrame);
|
---|
222 | hide_panel(fPanelOut);
|
---|
223 | }
|
---|
224 |
|
---|
225 | update_panels();
|
---|
226 | doupdate();
|
---|
227 | }
|
---|
228 |
|
---|
229 |
|
---|
230 | // --------------------------------------------------------------------------
|
---|
231 | //
|
---|
232 | //! Creates the windows to be used as panels and draws a frame around one
|
---|
233 | //!
|
---|
234 | //! @param w
|
---|
235 | //! pointers to the three windows which have been created are returned
|
---|
236 | //!
|
---|
237 | //! @param all
|
---|
238 | //! If all is false do not (re-)create the bottom or input-window
|
---|
239 | //
|
---|
240 | void Shell::CreateWindows(WINDOW *w[3], int all)
|
---|
241 | {
|
---|
242 | int maxx, maxy;
|
---|
243 | getmaxyx(stdscr, maxy, maxx);
|
---|
244 |
|
---|
245 | const int separator = maxy-fPanelHeight;
|
---|
246 |
|
---|
247 | WINDOW *new_in = all ? newwin(maxy, maxx, 0, 0) : 0;
|
---|
248 | WINDOW *new_frame = newwin(separator-1, maxx, 0, 0);
|
---|
249 | WINDOW *new_out = newwin(separator-1-2, maxx-2, 1, 1);
|
---|
250 |
|
---|
251 | box(new_frame, 0,0);
|
---|
252 | wmove(new_frame, 0, 1);
|
---|
253 | waddch(new_frame, ACS_RTEE);
|
---|
254 | wprintw(new_frame, " F1 ");
|
---|
255 | waddch(new_frame, ACS_LTEE);
|
---|
256 |
|
---|
257 | scrollok(new_out, true);
|
---|
258 | leaveok (new_out, true);
|
---|
259 |
|
---|
260 | if (new_in)
|
---|
261 | {
|
---|
262 | scrollok(new_in, true); // Allow scrolling
|
---|
263 | leaveok (new_in, false); // Move the cursor with the output
|
---|
264 |
|
---|
265 | wmove(new_in, maxy-1, 0);
|
---|
266 | }
|
---|
267 |
|
---|
268 | w[0] = new_in;
|
---|
269 | w[1] = new_frame;
|
---|
270 | w[2] = new_out;
|
---|
271 | }
|
---|
272 |
|
---|
273 | // --------------------------------------------------------------------------
|
---|
274 | //
|
---|
275 | //! Key binding for F1. Toggles upper panel by calling ShowHide(-1)
|
---|
276 | //
|
---|
277 | int Shell::rl_proc_F1(int /*cnt*/, int /*key*/)
|
---|
278 | {
|
---|
279 | This->ShowHide(-1); // toggle
|
---|
280 | return 0;
|
---|
281 | }
|
---|
282 |
|
---|
283 | int Shell::rl_scroll_top(int, int key)
|
---|
284 | {
|
---|
285 | This->win << "Scroll " << key << endl;
|
---|
286 | return 0;
|
---|
287 | }
|
---|
288 |
|
---|
289 | int Shell::rl_scroll_bot(int, int key)
|
---|
290 | {
|
---|
291 | This->win << "Scroll " << key << endl;
|
---|
292 | return 0;
|
---|
293 | }
|
---|
294 |
|
---|
295 | int Shell::rl_top_inc(int, int key)
|
---|
296 | {
|
---|
297 | This->win << "Increase " << key << endl;
|
---|
298 | return 0;
|
---|
299 | }
|
---|
300 |
|
---|
301 | int Shell::rl_top_dec(int, int key)
|
---|
302 | {
|
---|
303 | This->win << "Increase " << key << endl;
|
---|
304 | return 0;
|
---|
305 | }
|
---|
306 |
|
---|
307 | int Shell::rl_top_resize(int, int key)
|
---|
308 | {
|
---|
309 | This->Resize(key=='+' ? This->fPanelHeight-1 : This->fPanelHeight+1);
|
---|
310 | return 0;
|
---|
311 | }
|
---|
312 |
|
---|
313 |
|
---|
314 | // --------------------------------------------------------------------------
|
---|
315 | //
|
---|
316 | //! Signal handler for SIGWINCH, calls HandleResize
|
---|
317 | //
|
---|
318 | void Shell::HandleResizeImp(int)
|
---|
319 | {
|
---|
320 | This->HandleResize();
|
---|
321 | }
|
---|
322 |
|
---|
323 | // --------------------------------------------------------------------------
|
---|
324 | //
|
---|
325 | //! Signal handler for SIGWINCH. It resizes the terminal and all panels
|
---|
326 | //! according to the new terminal size and redisplay the backlog buffer
|
---|
327 | //! in both windows
|
---|
328 | //!
|
---|
329 | //! @todo
|
---|
330 | //! Maybe there are more efficient ways than to display the whole buffers
|
---|
331 | //
|
---|
332 | void Shell::HandleResize()
|
---|
333 | {
|
---|
334 | // Get the new terminal size
|
---|
335 | struct winsize w;
|
---|
336 | ioctl(0, TIOCGWINSZ, &w);
|
---|
337 |
|
---|
338 | // propagate it to the terminal
|
---|
339 | resize_term(w.ws_row, w.ws_col);
|
---|
340 |
|
---|
341 | // Store the pointer to the old windows
|
---|
342 | WINDOW *w_in = panel_window(fPanelIn);
|
---|
343 | WINDOW *w_frame = panel_window(fPanelFrame);
|
---|
344 | WINDOW *w_out = panel_window(fPanelOut);
|
---|
345 |
|
---|
346 | // Create new windows
|
---|
347 | WINDOW *wins[3];
|
---|
348 | CreateWindows(wins);
|
---|
349 |
|
---|
350 | // Redirect the streams and the readline output to the new windows
|
---|
351 | win.SetWindow(wins[0]);
|
---|
352 | wout.SetWindow(wins[2]);
|
---|
353 |
|
---|
354 | SetWindow(wins[0]);
|
---|
355 |
|
---|
356 | // Replace windows in the panels
|
---|
357 | replace_panel(fPanelIn, wins[0]);
|
---|
358 | replace_panel(fPanelFrame, wins[1]);
|
---|
359 | replace_panel(fPanelOut, wins[2]);
|
---|
360 |
|
---|
361 | // delete the old obsolete windows
|
---|
362 | delwin(w_in);
|
---|
363 | delwin(w_out);
|
---|
364 | delwin(w_frame);
|
---|
365 |
|
---|
366 | // FIXME: NEEDED also in Redisplay panel
|
---|
367 | //Redisplay();
|
---|
368 |
|
---|
369 | // Redisplay their contents
|
---|
370 | win.Display();
|
---|
371 | wout.Display();
|
---|
372 | }
|
---|
373 |
|
---|
374 | // --------------------------------------------------------------------------
|
---|
375 | //
|
---|
376 | //! This resized the top panel or output panel as requested by the argument.
|
---|
377 | //! The argument is the number of lines which are kept free for the input
|
---|
378 | //! panel below the top panel
|
---|
379 | //!
|
---|
380 | //! @returns
|
---|
381 | //! always true
|
---|
382 | //
|
---|
383 | bool Shell::Resize(int h)
|
---|
384 | {
|
---|
385 | // Get curretn terminal size
|
---|
386 | int lines, cols;
|
---|
387 | getmaxyx(stdscr, lines, cols);
|
---|
388 |
|
---|
389 | // Check if we are in a valid range
|
---|
390 | if (h<1 || h>lines-5)
|
---|
391 | return false;
|
---|
392 |
|
---|
393 | // Set new height for panel to be kept free
|
---|
394 | fPanelHeight = h;
|
---|
395 |
|
---|
396 | // Store the pointers of the old windows associated with the panels
|
---|
397 | // which should be resized
|
---|
398 | WINDOW *w_frame = panel_window(fPanelFrame);
|
---|
399 | WINDOW *w_out = panel_window(fPanelOut);
|
---|
400 |
|
---|
401 | // Create new windows
|
---|
402 | WINDOW *wins[3];
|
---|
403 | CreateWindows(wins, false);
|
---|
404 |
|
---|
405 | // Redirect the output stream to the new window
|
---|
406 | wout.SetWindow(wins[2]);
|
---|
407 |
|
---|
408 | // Replace the windows associated with the panels
|
---|
409 | replace_panel(fPanelFrame, wins[1]);
|
---|
410 | replace_panel(fPanelOut, wins[2]);
|
---|
411 |
|
---|
412 | // delete the ols windows
|
---|
413 | delwin(w_out);
|
---|
414 | delwin(w_frame);
|
---|
415 |
|
---|
416 | // FIXME: NEEDED also in Redisplay panel
|
---|
417 | //Redisplay();
|
---|
418 |
|
---|
419 | // Redisplay the contents
|
---|
420 | wout.Display();
|
---|
421 |
|
---|
422 | return true;
|
---|
423 | }
|
---|
424 |
|
---|
425 | bool Shell::PrintKeyBindings()
|
---|
426 | {
|
---|
427 | ReadlineColor::PrintKeyBindings(win);
|
---|
428 | win << " " << kUnderline << "Special key bindings:" << endl << endl;;
|
---|
429 | win << kBold << " F1 " << kReset << "Toggle visibility of upper panel" << endl;
|
---|
430 | win << endl;
|
---|
431 | return true;
|
---|
432 | }
|
---|
433 |
|
---|
434 | bool Shell::PrintGeneralHelp()
|
---|
435 | {
|
---|
436 | ReadlineColor::PrintGeneralHelp(win, GetName());
|
---|
437 | win << kBold << " hide " << kReset << "Hide upper panel." << endl;
|
---|
438 | win << kBold << " show " << kReset << "Show upper panel." << endl;
|
---|
439 | win << kBold << " height <h> " << kReset << "Set height of upper panel to h." << endl;
|
---|
440 | win << endl;
|
---|
441 | return true;
|
---|
442 | }
|
---|
443 |
|
---|
444 | // --------------------------------------------------------------------------
|
---|
445 | //
|
---|
446 | //! Processes the command provided by the Shell-class.
|
---|
447 | //!
|
---|
448 | //! @returns
|
---|
449 | //! whether a command was successfully processed or could not be found
|
---|
450 | //
|
---|
451 | bool Shell::Process(const string &str)
|
---|
452 | {
|
---|
453 | // Implement readline commands:
|
---|
454 | // rl set (rl_variable_bind(..))
|
---|
455 | // rl_read_init_file(filename)
|
---|
456 | // int rl_add_defun (const char *name, rl_command_func_t *function, int key)
|
---|
457 |
|
---|
458 | if (ReadlineColor::Process(win, str))
|
---|
459 | return true;
|
---|
460 |
|
---|
461 | if (Readline::Process(str))
|
---|
462 | return true;
|
---|
463 |
|
---|
464 | // ----------- ReadlineNcurses -----------
|
---|
465 |
|
---|
466 | if (string(str)=="hide")
|
---|
467 | {
|
---|
468 | ShowHide(0);
|
---|
469 | return true;
|
---|
470 | }
|
---|
471 | if (string(str)=="show")
|
---|
472 | {
|
---|
473 | ShowHide(1);
|
---|
474 | return true;
|
---|
475 | }
|
---|
476 |
|
---|
477 | if (str.substr(0, 7)=="height ")
|
---|
478 | {
|
---|
479 | int h;
|
---|
480 | sscanf(str.c_str()+7, "%d", &h);
|
---|
481 | return Resize(h);
|
---|
482 | }
|
---|
483 |
|
---|
484 | if (str=="d")
|
---|
485 | {
|
---|
486 | wout.Display();
|
---|
487 | return true;
|
---|
488 | }
|
---|
489 |
|
---|
490 | return false;
|
---|
491 | }
|
---|
492 |
|
---|
493 | // --------------------------------------------------------------------------
|
---|
494 | //
|
---|
495 | //! Overwrites Shutdown. It's main purpose is to re-output
|
---|
496 | //! the prompt and the buffer using the WindowLog stream so that it is
|
---|
497 | //! buffered in its backlog.
|
---|
498 | //!
|
---|
499 | //! @param buf
|
---|
500 | //! A pointer to the buffer returned by readline
|
---|
501 | //!
|
---|
502 | void Shell::Shutdown(const char *buf)
|
---|
503 | {
|
---|
504 | ReadlineWindow::Shutdown(buf);
|
---|
505 |
|
---|
506 | // Now move the cursor to the start of the prompt
|
---|
507 | RewindCursor();
|
---|
508 |
|
---|
509 | // Output the text ourself to get it into the backlog
|
---|
510 | // buffer of win. We cannot use GetBuffer() because rl_end
|
---|
511 | // is not updated finally.
|
---|
512 | win << kBlue << GetPrompt() << kReset << buf << endl;
|
---|
513 | }
|
---|