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