libyui-ncurses  2.46.6
 All Classes Functions Variables
NCurses.cc
1 /*
2  Copyright (C) 2000-2012 Novell, Inc
3  This library is free software; you can redistribute it and/or modify
4  it under the terms of the GNU Lesser General Public License as
5  published by the Free Software Foundation; either version 2.1 of the
6  License, or (at your option) version 3.0 of the License. This library
7  is distributed in the hope that it will be useful, but WITHOUT ANY
8  WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  License for more details. You should have received a copy of the GNU
11  Lesser General Public License along with this library; if not, write
12  to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  Floor, Boston, MA 02110-1301 USA
14 */
15 
16 
17 /*-/
18 
19  File: NCurses.cc
20 
21  Author: Michael Andres <ma@suse.de>
22 
23 /-*/
24 
25 #include <unistd.h>
26 #include <string.h> // strcmp(), strerror()
27 
28 #include <cstdarg>
29 #include <fstream>
30 #include <list>
31 #include <set>
32 
33 #include <yui/Libyui_config.h>
34 
35 #define YUILogComponent "ncurses"
36 #include <yui/YUILog.h>
37 #include "NCurses.h"
38 #include "NCDialog.h"
39 
40 #include "stdutil.h"
41 #include <signal.h>
42 
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <fcntl.h>
46 #include <fnmatch.h>
47 
48 using stdutil::vform;
49 using stdutil::form;
50 
51 NCurses * NCurses::myself = 0;
52 std::set<NCDialog*> NCurses::_knownDlgs;
53 const NCursesEvent NCursesEvent::Activated( NCursesEvent::button, YEvent::Activated );
54 const NCursesEvent NCursesEvent::SelectionChanged( NCursesEvent::button, YEvent::SelectionChanged );
55 const NCursesEvent NCursesEvent::ValueChanged( NCursesEvent::button, YEvent::ValueChanged );
56 
57 
58 
59 
60 #define CONVERR(n,p) \
61  va_list ap; \
62  va_list ap1; \
63  va_start( ap, p ); \
64  va_start( ap1, p );\
65  errval_i = n; \
66  errmsg_t = vform( p, ap, ap1 ); \
67  va_end( ap ); \
68  va_end( ap1 )
69 
70 NCursesError::NCursesError( const char * msg, ... )
71  : errval_i( ERR )
72 {
73  CONVERR( ERR, msg );
74 }
75 
76 NCursesError::NCursesError( int val, const char * msg, ... )
77  : errval_i( val )
78 {
79  CONVERR( val, msg );
80 }
81 
82 NCursesError & NCursesError::NCError( const char * msg, ... )
83 {
84  CONVERR( ERR, msg );
85  return *this;
86 }
87 
88 NCursesError & NCursesError::NCError( int val, const char * msg, ... )
89 {
90  CONVERR( val, msg );
91  return *this;
92 }
93 
94 #undef CONVERR
95 
96 
97 std::ostream & operator<<( std::ostream & STREAM, const NCursesError & OBJ )
98 {
99  STREAM << form( "%s: (%d) %s"
100  , OBJ.location()
101  , OBJ.errval_i
102  , OBJ.errmsg_t.c_str() );
103  return STREAM;
104 }
105 
106 
107 std::ostream & operator<<( std::ostream & STREAM, const NCursesEvent & OBJ )
108 {
109 #define ENUM_OUT(v) case NCursesEvent::v: return STREAM << "Ev::" << #v
110 
111  switch ( OBJ.type )
112  {
113  ENUM_OUT( none );
114  ENUM_OUT( handled );
115  ENUM_OUT( cancel );
116  ENUM_OUT( button );
117  ENUM_OUT( menu );
118  ENUM_OUT( timeout );
119  ENUM_OUT( key );
120  }
121 
122 #undef ENUM_OUT
123  return STREAM << "Ev::unknown";
124 }
125 
126 
127 
128 NCurses::NCurses()
129  : theTerm( 0 )
130  , title_w( 0 )
131  , status_w( 0 )
132  , styleset( 0 )
133  , stdpan( 0 )
134 {
135  const char * term = getenv( "TERM" );
136 
137  if ( term && *term )
138  envTerm = term;
139 }
140 
141 
142 
143 NCurses::~NCurses()
144 {
145  yuiMilestone() << "Shutdown NCurses..." << std::endl;
146  myself = 0;
147 
148  //restore env. variable - might have been changed by NCurses::init()
149  setenv( "TERM", envTerm.c_str(), 1 );
150  delete styleset;
151  delete stdpan;
152 
153  if ( title_w )
154  ::delwin( title_w );
155 
156  if ( status_w )
157  ::delwin( status_w );
158 
159  ::endwin();
160 
161  if ( theTerm )
162  ::delscreen( theTerm );
163 
164  yuiMilestone() << "NCurses down" << std::endl;
165 }
166 
167 
168 
169 WINDOW * NCurses::ripped_w_top = 0;
170 WINDOW * NCurses::ripped_w_bottom = 0;
171 
172 int NCurses::ripinit_top( WINDOW * w, int c )
173 {
174  ripped_w_top = w;
175  return OK;
176 }
177 
178 
179 int NCurses::ripinit_bottom( WINDOW * w, int c )
180 {
181  ripped_w_bottom = w;
182  return OK;
183 }
184 
185 
186 void NCurses::init()
187 {
188  yuiMilestone() << "Launch NCurses..."
189 #ifdef VERSION
190  << "(ui-ncurses-" << VERSION << ")"
191 #endif
192  << std::endl;
193  yuiMilestone() << "TERM=" << envTerm << std::endl;
194 
195  signal( SIGINT, SIG_IGN ); // ignore Ctrl C
196 
197  //rip off the top line
198 
199  if ( title_line() && ::ripoffline( 1, ripinit_top ) != OK )
200  throw NCursesError( "ripoffline() failed" );
201 
202  //and bottom line (-1 means 1st from the bottom)
203  if ( ::ripoffline( -1, ripinit_bottom ) != OK )
204  throw NCursesError( "ripoffline() failed" );
205 
206  yuiMilestone() << "isatty(stdin)" << ( isatty( 0 ) ? "yes" : "no" ) << std::endl;
207 
208  if ( isatty( 0 ) )
209  {
210  char * mytty = ttyname( 0 );
211 
212  if ( mytty )
213  {
214  yuiMilestone() << "mytty: " << mytty << std::endl;
215  FILE * fdi = fopen( mytty, "r" );
216 
217  if ( !fdi )
218  {
219  yuiError() << "fdi: (" << errno << ") " << strerror( errno ) << std::endl;
220  }
221 
222  FILE * fdo = fopen( mytty, "w" );
223 
224  if ( !fdo )
225  {
226  yuiError() << "fdo: (" << errno << ") " << strerror( errno ) << std::endl;
227  }
228 
229  if ( fdi && fdo )
230  {
231  theTerm = newterm( 0, fdo, fdi );
232 
233  //initialization failed
234 
235  if ( theTerm == NULL )
236  {
237  //bug #235954: workaround missing terminfos in inst-sys
238  //let's close the first term
239  ::endwin();
240 
241  std::string fallbackTerm = "";
242  //try generic xterm for xterm-like terminals, otherwise use vt100
243 
244  if ( ! fnmatch( "xterm*", envTerm.c_str(), 0 ) )
245  fallbackTerm = "xterm";
246  else
247  fallbackTerm = "vt100";
248 
249  yuiWarning() << "newterm() failed, using generic " << fallbackTerm << " as a fallback" << std::endl;
250 
251  //overwrite environment variable
252  setenv( "TERM", fallbackTerm.c_str(), 1 );
253 
254  //.. and try again
255  theTerm = newterm( 0, fdo, fdi );
256 
257  if ( theTerm == NULL )
258  throw NCursesError( "fallback newterm() failed" );
259  }
260 
261  if ( set_term( theTerm ) == NULL )
262  throw NCursesError( "set_term() failed" );
263 
264  myTerm = mytty;
265  }
266  }
267  }
268 
269  //duplicate stdout and stderr before redirecting them to log
270  //so that they can be regenerated before system() call
271  stdout_save = dup( 1 );
272  stderr_save = dup( 2 );
273 
274  RedirectToLog();
275 
276  if ( !theTerm )
277  {
278  yuiMilestone() << "no term so fall back to initscr" << std::endl;
279 
280  if ( ::initscr() == NULL )
281  throw NCursesError( "initscr() failed" );
282  }
283 
284  yuiMilestone() << "have color = " << ::has_colors() << std::endl;
285 
286  if ( want_colors() && ::has_colors() )
287  {
288  if ( ::start_color() != OK )
289  throw NCursesError( "start_color() failed" );
290 
291  NCattribute::init_colors();
292  }
293 
294  if ( title_line() )
295  {
296  if ( !ripped_w_top )
297  throw NCursesError( "ripinit_top() failed" );
298 
299  title_w = ripped_w_top;
300  }
301 
302  if ( !ripped_w_bottom )
303  throw NCursesError( "ripinit_bottom() failed" );
304 
305  status_w = ripped_w_bottom;
306 
307  setup_screen();
308 
309  yuiMilestone() << form( "screen size %d x %d\n", lines(), cols() );
310 
311  myself = this;
312  styleset = new NCstyle( envTerm );
313  stdpan = new NCursesPanel();
314  stdpan->bkgd( style()( NCstyle::AppText ) );
315 
316  if ( title_line() )
317  init_title();
318  SetStatusLine( myself->status_line );
319 
320  init_screen();
321  yuiMilestone() << "NCurses ready" << std::endl;
322 }
323 
324 
325 
326 void NCurses::setup_screen()
327 {
328  ::cbreak();
329  ::noecho();
330  ::keypad( ::stdscr, true );
331  ::meta( ::stdscr, true );
332  ::leaveok( ::stdscr, true );
333  ::curs_set( 0 );
334 
335  ::define_key( "\e[Z", KEY_BTAB );
336  ::define_key( "\e\t", KEY_BTAB );
337  ::define_key( "\030\t", KEY_BTAB );
338 }
339 
340 
341 
342 void NCurses::init_title()
343 {
344  ::wbkgd( title_w, style()( NCstyle::AppTitle ) );
345  ::wnoutrefresh( title_w );
346  ::wbkgd( status_w, style()( NCstyle::AppTitle ) );
347  ::wnoutrefresh( status_w );
348 }
349 
350 
351 
352 void NCurses::init_screen()
353 {
354  bool redefine = false;
355 
356  char *value = getenv( "Y2NCPSEUDO" );
357 
358  // The 9.0 workaround for missing ACS chars (bug #30512) is not necessary
359  // any longer (a patch is provided for ncurses-5.4).
360 
361  // Redefine ACS chars if Y2NCPSEUDO is set to "1" (just in case of ...)
362 
363  if ( value != NULL && strcmp( value, "1" ) == 0 )
364  {
365  redefine = true;
366  }
367 
368  if ( redefine )
369  {
370  chtype cch = 0;
371 
372  NCattribute::setChar( cch, '+' );
373  ACS_ULCORNER = cch;
374  ACS_LLCORNER = cch;
375  ACS_URCORNER = cch;
376  ACS_LRCORNER = cch;
377  ACS_LTEE = cch;
378  ACS_RTEE = cch;
379  ACS_BTEE = cch;
380  ACS_TTEE = cch;
381  ACS_PLUS = cch;
382 
383  NCattribute::setChar( cch, '|' );
384  ACS_VLINE = cch;
385 
386  NCattribute::setChar( cch, '-' );
387  ACS_HLINE = cch;
388 
389  NCattribute::setChar( cch, '#' );
390  ACS_DIAMOND = cch;
391  ACS_CKBOARD = cch;
392  ACS_BOARD = cch;
393 
394  NCattribute::setChar( cch, '<' );
395  ACS_LARROW = cch;
396 
397  NCattribute::setChar( cch, '>' );
398  ACS_RARROW = cch;
399 
400  NCattribute::setChar( cch, 'v' );
401  ACS_DARROW = cch;
402 
403  NCattribute::setChar( cch, '^' );
404  ACS_UARROW = cch;
405  }
406 }
407 
408 
409 const NCstyle & NCurses::style()
410 {
411  return *myself->styleset;
412 }
413 
414 
415 void NCurses::Update()
416 {
417  if ( myself && myself->initialized() )
418  {
419  //myself->stdpan->refresh();
420  myself->stdpan->redraw();
421  }
422 }
423 
424 
425 void NCurses::Refresh()
426 {
427  if ( myself && myself->initialized() )
428  {
429  yuiMilestone() << "start refresh ..." << std::endl;
430  SetTitle( myself->title_t );
431  SetStatusLine( myself->status_line );
432  ::clearok( ::stdscr, true );
433  myself->stdpan->refresh();
434  yuiMilestone() << "done refresh ..." << std::endl;
435  }
436 }
437 
438 
439 void NCurses::Redraw()
440 {
441  if ( myself && myself->initialized() )
442  {
443  yuiMilestone() << "start redraw ..." << std::endl;
444 
445  // initialize all dialogs rewdraw
446  PANEL * pan = ::panel_above( NULL );
447 
448  while ( pan )
449  {
451 
452  if ( dlg )
453  {
454  dlg->Recoded();
455  }
456 
457  pan = ::panel_above( pan );
458  }
459 
460  // TBD: initialize all dialogs rewdraw
461  Refresh();
462 
463  yuiMilestone() << "done redraw ..." << std::endl;
464  }
465 }
466 
467 
468 void NCurses::SetTitle( const std::string & str )
469 {
470  if ( myself && myself->title_w )
471  {
472  myself->title_t = str;
473  ::wbkgd( myself->title_w, myself->style()( NCstyle::AppTitle ) );
474  ::wclear( myself->title_w );
475 
476  yuiMilestone() << "Draw title called" << std::endl;
477 
478  ::mvwaddstr( myself->title_w, 0, 1, myself->title_t.c_str() );
479  ::wnoutrefresh( myself->title_w );
480  }
481 
482 }
483 
484 void NCurses::SetStatusLine( std::map <int, NCstring> fkeys )
485 {
486 
487  if ( myself && myself->status_w )
488  {
489  myself->status_line = fkeys;
490  ::wbkgd( myself->status_w, myself->style()( NCstyle::AppTitle ) );
491  ::werase( myself->status_w );
492 
493  char key[10];
494  std::wstring value;
495 
496  std::map<int, NCstring>::iterator it;
497 
498  for ( it = fkeys.begin(); it != fkeys.end(); ++it )
499  {
500  sprintf( key, " F%d ", ( *it ).first );
501  //reverse F-key to make it more visible
502  ::wattron( myself->status_w, A_REVERSE );
503  ::waddstr( myself->status_w, key );
504  ::wattroff( myself->status_w, A_REVERSE );
505 
506  value = (*it ).second.str();
507  if ( NCstring::terminalEncoding() != "UTF-8" )
508  {
509  std::string out;
510  NCstring::RecodeFromWchar( value, NCstring::terminalEncoding(), &out );
511  ::waddstr( myself->status_w, (char *)out.c_str() );
512  }
513  else
514  {
515  ::waddwstr( myself->status_w, (wchar_t *)value.c_str() );
516  }
517  ::waddch( myself->status_w, ' ' );
518  }
519 
520  ::wnoutrefresh( myself->status_w );
521  }
522 }
523 
524 
525 
526 void NCurses::drawTitle()
527 {
528  if ( myself && myself->title_w )
529  {
530  SetTitle( myself->title_t );
531  }
532 }
533 
534 
535 
536 void NCurses::RememberDlg( NCDialog * dlg_r )
537 {
538  if ( dlg_r )
539  {
540  _knownDlgs.insert( dlg_r );
541  }
542 }
543 
544 
545 
546 void NCurses::ForgetDlg( NCDialog * dlg_r )
547 {
548  if ( dlg_r )
549  {
550  _knownDlgs.erase( dlg_r );
551  }
552 }
553 
554 
555 
556 void NCurses::RedirectToLog()
557 {
558  std::string log = "/dev/null"; // this used to be get_log_filename()
559 
560  yuiMilestone() << "isatty(stderr)" << ( isatty( 2 ) ? "yes" : "no" ) << std::endl;
561 
562  if ( isatty( 2 ) && theTerm )
563  {
564  // redirect stderr to log
565  close( 2 );
566  open( log.c_str(), O_APPEND | O_CREAT, 0666 );
567  }
568 
569  yuiMilestone() << "isatty(stdout)" << ( isatty( 1 ) ? "yes" : "no" ) << std::endl;
570 
571  if ( isatty( 1 ) && theTerm )
572  {
573  // redirect stdout to log
574  close( 1 );
575  open( log.c_str(), O_APPEND | O_CREAT, 0666 );
576  }
577 }
578 
579 
580 
581 void NCurses::ResizeEvent()
582 {
583  if ( myself && myself->initialized() )
584  {
585  yuiMilestone() << "start resize to " << NCurses::lines() << 'x' << NCurses::cols() << "..." << std::endl;
586 
587  // remember stack of visible dialogs.
588  // don't hide on the fly, as it will mess up stacking order.
589  std::list<NCDialog*> dlgStack;
590 
591  for ( PANEL * pan = ::panel_above( NULL ); pan; pan = ::panel_above( pan ) )
592  {
594 
595  if ( dlg )
596  {
597  dlgStack.push_back( dlg );
598  }
599  }
600 
601  // hide all visible dialogs.
602  for ( std::list<NCDialog*>::iterator it = dlgStack.begin(); it != dlgStack.end(); ++it )
603  {
604  ( *it )->getInvisible();
605  }
606 
607  drawTitle();
608  Update();
609 
610  // relayout all dialogs
611 
612  for ( std::set<NCDialog*>::iterator it = _knownDlgs.begin(); it != _knownDlgs.end(); ++it )
613  {
614  ( *it )->resizeEvent();
615  }
616 
617  // recreate stack of visible dialogs
618  for ( std::list<NCDialog*>::iterator it = dlgStack.begin(); it != dlgStack.end(); ++it )
619  {
620  ( *it )->getVisible();
621  }
622 
623  Update();
624 
625  //FIXME: remove this once libncurses is upgraded to 20080105 patchlevel
626  //after the resize, status line window needs to be moved to the new pos.
627  ::mvwin( myself->status_w, NCurses::lines(), 0 );
628  SetStatusLine( myself->status_line );
629  //update the screen
630  ::touchwin( myself->status_w );
631  ::doupdate();
632 
633  yuiMilestone() << "done resize ..." << std::endl;
634  }
635 }
636 
637 
638 
639 void NCurses::ScreenShot( const std::string & name )
640 {
641  if ( !myself )
642  return;
643 
644  //ofstream out( name.c_str(), ios::out|ios::app );
645  std::ostream & out( yuiMilestone() );
646 
647  int curscrlines = myself->title_line() ? lines() + 1 : lines();
648 
649  for ( int l = 0; l < curscrlines; ++l )
650  {
651  for ( int c = 0; c < cols(); ++c )
652  {
653 
654  chtype al = ::mvwinch( ::curscr, l, c ) & ( A_ALTCHARSET | A_CHARTEXT );
655 
656  if ( al & A_ALTCHARSET )
657  {
658  if ( al == ACS_ULCORNER
659  || al == ACS_LLCORNER
660  || al == ACS_URCORNER
661  || al == ACS_LRCORNER
662  || al == ACS_LTEE
663  || al == ACS_RTEE
664  || al == ACS_BTEE
665  || al == ACS_TTEE
666  || al == ACS_PLUS )
667  {
668  out << '+';
669  }
670  else if ( al == ACS_HLINE )
671  {
672  out << '-';
673  }
674  else if ( al == ACS_VLINE )
675  {
676  out << '|';
677  }
678  else if ( al == ACS_DIAMOND
679  || al == ACS_CKBOARD
680  || al == ACS_BOARD )
681  {
682  out << '#';
683  }
684  else if ( al == ACS_LARROW )
685  {
686  out << '<';
687  }
688  else if ( al == ACS_RARROW )
689  {
690  out << '>';
691  }
692  else if ( al == ACS_DARROW )
693  {
694  out << 'v';
695  }
696  else if ( al == ACS_UARROW )
697  {
698  out << '^';
699  }
700  else
701  {
702  out << ( char )( al & A_CHARTEXT );
703  }
704  }
705  else
706  {
707  out << ( char )( al & A_CHARTEXT );
708  }
709 
710  }
711 
712  out << std::endl;
713  }
714 }
715 
716 
717 std::ostream & operator<<( std::ostream & STREAM, const NCurses & OBJ )
718 {
719  STREAM << form( "NC - %d x %d - colors %d - pairs %d\n"
720  , OBJ.lines(), OBJ.cols()
721  , NCattribute::colors(), NCattribute::color_pairs() );
722 
723  WINDOW * cw = ::stdscr;
724  STREAM << form( "NC - rootw %p", cw );
725 
726  if ( cw )
727  STREAM << form( " - (%2hd,%2hd)%2hdx%2hd - {%p - (%2d,%2d)}\n"
728  , cw->_begy, cw->_begx
729  , cw->_maxy, cw->_maxx
730  , cw->_parent
731  , cw->_pary, cw->_parx
732  );
733  else
734  STREAM << std::endl;
735 
736  cw = OBJ.title_w;
737 
738  STREAM << form( "NC - title %p", cw );
739 
740  if ( cw )
741  STREAM << form( " - (%2hd,%2hd)%2hdx%2hd - {%p - (%2d,%2d)}\n"
742  , cw->_begy, cw->_begx
743  , cw->_maxy, cw->_maxx
744  , cw->_parent
745  , cw->_pary, cw->_parx
746  );
747  else
748  STREAM << std::endl;
749 
750  return STREAM;
751 }
static void redraw()
Redraw all panels.
Definition: ncursesp.cc:94
int bkgd(const chtype ch)
Set the background property and apply it to the window.
Definition: ncursesw.h:1443
virtual int refresh()
Propagate all panel changes to the virtual screen and update the physical screen. ...
Definition: ncursesp.cc:112
static T * UserDataOf(const PANEL &pan)
Retrieve the user data if associated with the PANEL.
Definition: ncursesp.h:340