libyui-ncurses
2.44.1
|
00001 /* 00002 Copyright (C) 2000-2012 Novell, Inc 00003 This library is free software; you can redistribute it and/or modify 00004 it under the terms of the GNU Lesser General Public License as 00005 published by the Free Software Foundation; either version 2.1 of the 00006 License, or (at your option) version 3.0 of the License. This library 00007 is distributed in the hope that it will be useful, but WITHOUT ANY 00008 WARRANTY; without even the implied warranty of MERCHANTABILITY or 00009 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 00010 License for more details. You should have received a copy of the GNU 00011 Lesser General Public License along with this library; if not, write 00012 to the Free Software Foundation, Inc., 51 Franklin Street, Fifth 00013 Floor, Boston, MA 02110-1301 USA 00014 */ 00015 00016 00017 /*-/ 00018 00019 File: NCurses.cc 00020 00021 Author: Michael Andres <ma@suse.de> 00022 00023 /-*/ 00024 00025 #include <unistd.h> 00026 #include <string.h> // strcmp(), strerror() 00027 00028 #include <cstdarg> 00029 #include <fstream> 00030 #include <list> 00031 #include <set> 00032 00033 #include <yui/Libyui_config.h> 00034 00035 #define YUILogComponent "ncurses" 00036 #include <yui/YUILog.h> 00037 #include "NCurses.h" 00038 #include "NCDialog.h" 00039 #include "NCi18n.h" 00040 00041 #include "stdutil.h" 00042 #include <signal.h> 00043 00044 #include <sys/types.h> 00045 #include <sys/stat.h> 00046 #include <fcntl.h> 00047 #include <fnmatch.h> 00048 00049 using stdutil::vform; 00050 using stdutil::form; 00051 00052 /* 00053 Textdomain "ncurses" 00054 */ 00055 00056 NCurses * NCurses::myself = 0; 00057 std::set<NCDialog*> NCurses::_knownDlgs; 00058 const NCursesEvent NCursesEvent::Activated( NCursesEvent::button, YEvent::Activated ); 00059 const NCursesEvent NCursesEvent::SelectionChanged( NCursesEvent::button, YEvent::SelectionChanged ); 00060 const NCursesEvent NCursesEvent::ValueChanged( NCursesEvent::button, YEvent::ValueChanged ); 00061 00062 00063 00064 00065 #define CONVERR(n,p) \ 00066 va_list ap; \ 00067 va_list ap1; \ 00068 va_start( ap, p ); \ 00069 va_start( ap1, p );\ 00070 errval_i = n; \ 00071 errmsg_t = vform( p, ap, ap1 ); \ 00072 va_end( ap ); \ 00073 va_end( ap1 ) 00074 00075 NCursesError::NCursesError( const char * msg, ... ) 00076 : errval_i( ERR ) 00077 { 00078 CONVERR( ERR, msg ); 00079 } 00080 00081 NCursesError::NCursesError( int val, const char * msg, ... ) 00082 : errval_i( val ) 00083 { 00084 CONVERR( val, msg ); 00085 } 00086 00087 NCursesError & NCursesError::NCError( const char * msg, ... ) 00088 { 00089 CONVERR( ERR, msg ); 00090 return *this; 00091 } 00092 00093 NCursesError & NCursesError::NCError( int val, const char * msg, ... ) 00094 { 00095 CONVERR( val, msg ); 00096 return *this; 00097 } 00098 00099 #undef CONVERR 00100 00101 00102 std::ostream & operator<<( std::ostream & STREAM, const NCursesError & OBJ ) 00103 { 00104 STREAM << form( "%s: (%d) %s" 00105 , OBJ.location() 00106 , OBJ.errval_i 00107 , OBJ.errmsg_t.c_str() ); 00108 return STREAM; 00109 } 00110 00111 00112 std::ostream & operator<<( std::ostream & STREAM, const NCursesEvent & OBJ ) 00113 { 00114 #define ENUM_OUT(v) case NCursesEvent::v: return STREAM << "Ev::" << #v 00115 00116 switch ( OBJ.type ) 00117 { 00118 ENUM_OUT( none ); 00119 ENUM_OUT( handled ); 00120 ENUM_OUT( cancel ); 00121 ENUM_OUT( button ); 00122 ENUM_OUT( menu ); 00123 ENUM_OUT( timeout ); 00124 ENUM_OUT( key ); 00125 } 00126 00127 #undef ENUM_OUT 00128 return STREAM << "Ev::unknown"; 00129 } 00130 00131 00132 00133 NCurses::NCurses() 00134 : theTerm( 0 ) 00135 , title_w( 0 ) 00136 , status_w( 0 ) 00137 , styleset( 0 ) 00138 , stdpan( 0 ) 00139 { 00140 const char * term = getenv( "TERM" ); 00141 00142 if ( term && *term ) 00143 envTerm = term; 00144 } 00145 00146 00147 00148 NCurses::~NCurses() 00149 { 00150 yuiMilestone() << "Shutdown NCurses..." << std::endl; 00151 myself = 0; 00152 00153 //restore env. variable - might have been changed by NCurses::init() 00154 setenv( "TERM", envTerm.c_str(), 1 ); 00155 delete styleset; 00156 delete stdpan; 00157 00158 if ( title_w ) 00159 ::delwin( title_w ); 00160 00161 if ( status_w ) 00162 ::delwin( status_w ); 00163 00164 ::endwin(); 00165 00166 if ( theTerm ) 00167 ::delscreen( theTerm ); 00168 00169 yuiMilestone() << "NCurses down" << std::endl; 00170 } 00171 00172 00173 00174 WINDOW * NCurses::ripped_w_top = 0; 00175 WINDOW * NCurses::ripped_w_bottom = 0; 00176 00177 int NCurses::ripinit_top( WINDOW * w, int c ) 00178 { 00179 ripped_w_top = w; 00180 return OK; 00181 } 00182 00183 00184 int NCurses::ripinit_bottom( WINDOW * w, int c ) 00185 { 00186 ripped_w_bottom = w; 00187 return OK; 00188 } 00189 00190 00191 void NCurses::init() 00192 { 00193 yuiMilestone() << "Launch NCurses..." 00194 #ifdef VERSION 00195 << "(ui-ncurses-" << VERSION << ")" 00196 #endif 00197 << std::endl; 00198 yuiMilestone() << "TERM=" << envTerm << std::endl; 00199 00200 signal( SIGINT, SIG_IGN ); // ignore Ctrl C 00201 00202 //rip off the top line 00203 00204 if ( title_line() && ::ripoffline( 1, ripinit_top ) != OK ) 00205 throw NCursesError( "ripoffline() failed" ); 00206 00207 //and bottom line (-1 means 1st from the bottom) 00208 if ( ::ripoffline( -1, ripinit_bottom ) != OK ) 00209 throw NCursesError( "ripoffline() failed" ); 00210 00211 yuiMilestone() << "isatty(stdin)" << ( isatty( 0 ) ? "yes" : "no" ) << std::endl; 00212 00213 if ( isatty( 0 ) ) 00214 { 00215 char * mytty = ttyname( 0 ); 00216 00217 if ( mytty ) 00218 { 00219 yuiMilestone() << "mytty: " << mytty << std::endl; 00220 FILE * fdi = fopen( mytty, "r" ); 00221 00222 if ( !fdi ) 00223 { 00224 yuiError() << "fdi: (" << errno << ") " << strerror( errno ) << std::endl; 00225 } 00226 00227 FILE * fdo = fopen( mytty, "w" ); 00228 00229 if ( !fdo ) 00230 { 00231 yuiError() << "fdo: (" << errno << ") " << strerror( errno ) << std::endl; 00232 } 00233 00234 if ( fdi && fdo ) 00235 { 00236 theTerm = newterm( 0, fdo, fdi ); 00237 00238 //initialization failed 00239 00240 if ( theTerm == NULL ) 00241 { 00242 //bug #235954: workaround missing terminfos in inst-sys 00243 //let's close the first term 00244 ::endwin(); 00245 00246 std::string fallbackTerm = ""; 00247 //try generic xterm for xterm-like terminals, otherwise use vt100 00248 00249 if ( ! fnmatch( "xterm*", envTerm.c_str(), 0 ) ) 00250 fallbackTerm = "xterm"; 00251 else 00252 fallbackTerm = "vt100"; 00253 00254 yuiWarning() << "newterm() failed, using generic " << fallbackTerm << " as a fallback" << std::endl; 00255 00256 //overwrite environment variable 00257 setenv( "TERM", fallbackTerm.c_str(), 1 ); 00258 00259 //.. and try again 00260 theTerm = newterm( 0, fdo, fdi ); 00261 00262 if ( theTerm == NULL ) 00263 throw NCursesError( "fallback newterm() failed" ); 00264 } 00265 00266 if ( set_term( theTerm ) == NULL ) 00267 throw NCursesError( "set_term() failed" ); 00268 00269 myTerm = mytty; 00270 } 00271 } 00272 } 00273 00274 //duplicate stdout and stderr before redirecting them to log 00275 //so that they can be regenerated before system() call 00276 stdout_save = dup( 1 ); 00277 stderr_save = dup( 2 ); 00278 00279 RedirectToLog(); 00280 00281 if ( !theTerm ) 00282 { 00283 yuiMilestone() << "no term so fall back to initscr" << std::endl; 00284 00285 if ( ::initscr() == NULL ) 00286 throw NCursesError( "initscr() failed" ); 00287 } 00288 00289 yuiMilestone() << "have color = " << ::has_colors() << std::endl; 00290 00291 if ( want_colors() && ::has_colors() ) 00292 { 00293 if ( ::start_color() != OK ) 00294 throw NCursesError( "start_color() failed" ); 00295 00296 NCattribute::init_colors(); 00297 } 00298 00299 if ( title_line() ) 00300 { 00301 if ( !ripped_w_top ) 00302 throw NCursesError( "ripinit_top() failed" ); 00303 00304 title_w = ripped_w_top; 00305 } 00306 00307 if ( !ripped_w_bottom ) 00308 throw NCursesError( "ripinit_bottom() failed" ); 00309 00310 status_w = ripped_w_bottom; 00311 00312 setup_screen(); 00313 00314 yuiMilestone() << form( "screen size %d x %d\n", lines(), cols() ); 00315 00316 myself = this; 00317 styleset = new NCstyle( envTerm ); 00318 stdpan = new NCursesPanel(); 00319 stdpan->bkgd( style()( NCstyle::AppText ) ); 00320 00321 if ( title_line() ) 00322 init_title(); 00323 SetStatusLine( myself->status_line ); 00324 00325 init_screen(); 00326 yuiMilestone() << "NCurses ready" << std::endl; 00327 } 00328 00329 00330 00331 void NCurses::setup_screen() 00332 { 00333 ::cbreak(); 00334 ::noecho(); 00335 ::keypad( ::stdscr, true ); 00336 ::meta( ::stdscr, true ); 00337 ::leaveok( ::stdscr, true ); 00338 ::curs_set( 0 ); 00339 00340 ::define_key( "\e[Z", KEY_BTAB ); 00341 ::define_key( "\e\t", KEY_BTAB ); 00342 ::define_key( "\030\t", KEY_BTAB ); 00343 } 00344 00345 00346 00347 void NCurses::init_title() 00348 { 00349 ::wbkgd( title_w, style()( NCstyle::AppTitle ) ); 00350 ::wnoutrefresh( title_w ); 00351 ::wbkgd( status_w, style()( NCstyle::AppTitle ) ); 00352 ::wnoutrefresh( status_w ); 00353 } 00354 00355 00356 00357 void NCurses::init_screen() 00358 { 00359 bool redefine = false; 00360 00361 char *value = getenv( "Y2NCPSEUDO" ); 00362 00363 // The 9.0 workaround for missing ACS chars (bug #30512) is not necessary 00364 // any longer (a patch is provided for ncurses-5.4). 00365 00366 // Redefine ACS chars if Y2NCPSEUDO is set to "1" (just in case of ...) 00367 00368 if ( value != NULL && strcmp( value, "1" ) == 0 ) 00369 { 00370 redefine = true; 00371 } 00372 00373 if ( redefine ) 00374 { 00375 chtype cch = 0; 00376 00377 NCattribute::setChar( cch, '+' ); 00378 ACS_ULCORNER = cch; 00379 ACS_LLCORNER = cch; 00380 ACS_URCORNER = cch; 00381 ACS_LRCORNER = cch; 00382 ACS_LTEE = cch; 00383 ACS_RTEE = cch; 00384 ACS_BTEE = cch; 00385 ACS_TTEE = cch; 00386 ACS_PLUS = cch; 00387 00388 NCattribute::setChar( cch, '|' ); 00389 ACS_VLINE = cch; 00390 00391 NCattribute::setChar( cch, '-' ); 00392 ACS_HLINE = cch; 00393 00394 NCattribute::setChar( cch, '#' ); 00395 ACS_DIAMOND = cch; 00396 ACS_CKBOARD = cch; 00397 ACS_BOARD = cch; 00398 00399 NCattribute::setChar( cch, '<' ); 00400 ACS_LARROW = cch; 00401 00402 NCattribute::setChar( cch, '>' ); 00403 ACS_RARROW = cch; 00404 00405 NCattribute::setChar( cch, 'v' ); 00406 ACS_DARROW = cch; 00407 00408 NCattribute::setChar( cch, '^' ); 00409 ACS_UARROW = cch; 00410 } 00411 } 00412 00413 00414 const NCstyle & NCurses::style() 00415 { 00416 return *myself->styleset; 00417 } 00418 00419 00420 void NCurses::Update() 00421 { 00422 if ( myself && myself->initialized() ) 00423 { 00424 //myself->stdpan->refresh(); 00425 myself->stdpan->redraw(); 00426 } 00427 } 00428 00429 00430 void NCurses::Refresh() 00431 { 00432 if ( myself && myself->initialized() ) 00433 { 00434 yuiMilestone() << "start refresh ..." << std::endl; 00435 SetTitle( myself->title_t ); 00436 SetStatusLine( myself->status_line ); 00437 ::clearok( ::stdscr, true ); 00438 myself->stdpan->refresh(); 00439 yuiMilestone() << "done refresh ..." << std::endl; 00440 } 00441 } 00442 00443 00444 void NCurses::Redraw() 00445 { 00446 if ( myself && myself->initialized() ) 00447 { 00448 yuiMilestone() << "start redraw ..." << std::endl; 00449 00450 // initialize all dialogs rewdraw 00451 PANEL * pan = ::panel_above( NULL ); 00452 00453 while ( pan ) 00454 { 00455 NCDialog * dlg = NCursesUserPanel<NCDialog>::UserDataOf( *pan ); 00456 00457 if ( dlg ) 00458 { 00459 dlg->Recoded(); 00460 } 00461 00462 pan = ::panel_above( pan ); 00463 } 00464 00465 // TBD: initialize all dialogs rewdraw 00466 Refresh(); 00467 00468 yuiMilestone() << "done redraw ..." << std::endl; 00469 } 00470 } 00471 00472 00473 void NCurses::SetTitle( const std::string & str ) 00474 { 00475 if ( myself && myself->title_w ) 00476 { 00477 myself->title_t = str; 00478 ::wbkgd( myself->title_w, myself->style()( NCstyle::AppTitle ) ); 00479 ::wclear( myself->title_w ); 00480 00481 yuiMilestone() << "Draw title called" << std::endl; 00482 00483 #if 0 00484 setTextdomain( "ncurses" ); 00485 // part of title (headline) of the textmode yast 00486 NCstring helpF1( _( "Press F1 for Help" ) ); 00487 NCtext textF1( helpF1 ); 00488 00489 int s = myself->title_w->_maxx - textF1.Columns(); 00490 00491 if ( NCstring::terminalEncoding() != "UTF-8" ) 00492 { 00493 std::string out; 00494 NCstring::RecodeFromWchar( helpF1.str(), NCstring::terminalEncoding(), &out ); 00495 ::mvwaddstr( myself->title_w, 0, s, out.c_str() ); 00496 } 00497 else 00498 { 00499 ::mvwaddwstr( myself->title_w, 0, s, ( wchar_t * )helpF1.str().c_str() ); 00500 } 00501 00502 #endif 00503 00504 ::mvwaddstr( myself->title_w, 0, 1, myself->title_t.c_str() ); 00505 ::wnoutrefresh( myself->title_w ); 00506 } 00507 00508 } 00509 00510 void NCurses::SetStatusLine( std::map <int, std::string> fkeys ) 00511 { 00512 00513 if ( myself && myself->status_w ) 00514 { 00515 myself->status_line = fkeys; 00516 ::wbkgd( myself->status_w, myself->style()( NCstyle::AppTitle ) ); 00517 ::werase( myself->status_w ); 00518 00519 char key[10]; 00520 char value[100]; 00521 00522 std::map<int, std::string>::iterator it; 00523 00524 for ( it = fkeys.begin(); it != fkeys.end(); ++it ) 00525 { 00526 sprintf( key, " F%d ", ( *it ).first ); 00527 //reverse F-key to make it more visible 00528 ::wattron( myself->status_w, A_REVERSE ); 00529 ::waddstr( myself->status_w, key ); 00530 ::wattroff( myself->status_w, A_REVERSE ); 00531 00532 sprintf( value, "%s ", ( *it ).second.c_str() ); 00533 ::waddstr( myself->status_w, value ); 00534 } 00535 00536 ::wnoutrefresh( myself->status_w ); 00537 } 00538 } 00539 00540 00541 00542 void NCurses::drawTitle() 00543 { 00544 if ( myself && myself->title_w ) 00545 { 00546 SetTitle( myself->title_t ); 00547 } 00548 } 00549 00550 00551 00552 void NCurses::RememberDlg( NCDialog * dlg_r ) 00553 { 00554 if ( dlg_r ) 00555 { 00556 _knownDlgs.insert( dlg_r ); 00557 } 00558 } 00559 00560 00561 00562 void NCurses::ForgetDlg( NCDialog * dlg_r ) 00563 { 00564 if ( dlg_r ) 00565 { 00566 _knownDlgs.erase( dlg_r ); 00567 } 00568 } 00569 00570 00571 00572 void NCurses::RedirectToLog() 00573 { 00574 std::string log = "/dev/null"; // this used to be get_log_filename() 00575 00576 yuiMilestone() << "isatty(stderr)" << ( isatty( 2 ) ? "yes" : "no" ) << std::endl; 00577 00578 if ( isatty( 2 ) && theTerm ) 00579 { 00580 // redirect stderr to log 00581 close( 2 ); 00582 open( log.c_str(), O_APPEND | O_CREAT, 0666 ); 00583 } 00584 00585 yuiMilestone() << "isatty(stdout)" << ( isatty( 1 ) ? "yes" : "no" ) << std::endl; 00586 00587 if ( isatty( 1 ) && theTerm ) 00588 { 00589 // redirect stdout to log 00590 close( 1 ); 00591 open( log.c_str(), O_APPEND | O_CREAT, 0666 ); 00592 } 00593 } 00594 00595 00596 00597 void NCurses::ResizeEvent() 00598 { 00599 if ( myself && myself->initialized() ) 00600 { 00601 yuiMilestone() << "start resize to " << NCurses::lines() << 'x' << NCurses::cols() << "..." << std::endl; 00602 00603 // remember stack of visible dialogs. 00604 // don't hide on the fly, as it will mess up stacking order. 00605 std::list<NCDialog*> dlgStack; 00606 00607 for ( PANEL * pan = ::panel_above( NULL ); pan; pan = ::panel_above( pan ) ) 00608 { 00609 NCDialog * dlg = NCursesUserPanel<NCDialog>::UserDataOf( *pan ); 00610 00611 if ( dlg ) 00612 { 00613 dlgStack.push_back( dlg ); 00614 } 00615 } 00616 00617 // hide all visible dialogs. 00618 for ( std::list<NCDialog*>::iterator it = dlgStack.begin(); it != dlgStack.end(); ++it ) 00619 { 00620 ( *it )->getInvisible(); 00621 } 00622 00623 drawTitle(); 00624 Update(); 00625 00626 // relayout all dialogs 00627 00628 for ( std::set<NCDialog*>::iterator it = _knownDlgs.begin(); it != _knownDlgs.end(); ++it ) 00629 { 00630 ( *it )->resizeEvent(); 00631 } 00632 00633 // recreate stack of visible dialogs 00634 for ( std::list<NCDialog*>::iterator it = dlgStack.begin(); it != dlgStack.end(); ++it ) 00635 { 00636 ( *it )->getVisible(); 00637 } 00638 00639 Update(); 00640 00641 //FIXME: remove this once libncurses is upgraded to 20080105 patchlevel 00642 //after the resize, status line window needs to be moved to the new pos. 00643 ::mvwin( myself->status_w, NCurses::lines(), 0 ); 00644 SetStatusLine( myself->status_line ); 00645 //update the screen 00646 ::touchwin( myself->status_w ); 00647 ::doupdate(); 00648 00649 yuiMilestone() << "done resize ..." << std::endl; 00650 } 00651 } 00652 00653 00654 00655 void NCurses::ScreenShot( const std::string & name ) 00656 { 00657 if ( !myself ) 00658 return; 00659 00660 //ofstream out( name.c_str(), ios::out|ios::app ); 00661 std::ostream & out( yuiMilestone() ); 00662 00663 int curscrlines = myself->title_line() ? lines() + 1 : lines(); 00664 00665 for ( int l = 0; l < curscrlines; ++l ) 00666 { 00667 for ( int c = 0; c < cols(); ++c ) 00668 { 00669 00670 chtype al = ::mvwinch( ::curscr, l, c ) & ( A_ALTCHARSET | A_CHARTEXT ); 00671 00672 if ( al & A_ALTCHARSET ) 00673 { 00674 if ( al == ACS_ULCORNER 00675 || al == ACS_LLCORNER 00676 || al == ACS_URCORNER 00677 || al == ACS_LRCORNER 00678 || al == ACS_LTEE 00679 || al == ACS_RTEE 00680 || al == ACS_BTEE 00681 || al == ACS_TTEE 00682 || al == ACS_PLUS ) 00683 { 00684 out << '+'; 00685 } 00686 else if ( al == ACS_HLINE ) 00687 { 00688 out << '-'; 00689 } 00690 else if ( al == ACS_VLINE ) 00691 { 00692 out << '|'; 00693 } 00694 else if ( al == ACS_DIAMOND 00695 || al == ACS_CKBOARD 00696 || al == ACS_BOARD ) 00697 { 00698 out << '#'; 00699 } 00700 else if ( al == ACS_LARROW ) 00701 { 00702 out << '<'; 00703 } 00704 else if ( al == ACS_RARROW ) 00705 { 00706 out << '>'; 00707 } 00708 else if ( al == ACS_DARROW ) 00709 { 00710 out << 'v'; 00711 } 00712 else if ( al == ACS_UARROW ) 00713 { 00714 out << '^'; 00715 } 00716 else 00717 { 00718 out << ( char )( al & A_CHARTEXT ); 00719 } 00720 } 00721 else 00722 { 00723 out << ( char )( al & A_CHARTEXT ); 00724 } 00725 00726 } 00727 00728 out << std::endl; 00729 } 00730 } 00731 00732 00733 std::ostream & operator<<( std::ostream & STREAM, const NCurses & OBJ ) 00734 { 00735 STREAM << form( "NC - %d x %d - colors %d - pairs %d\n" 00736 , OBJ.lines(), OBJ.cols() 00737 , NCattribute::colors(), NCattribute::color_pairs() ); 00738 00739 WINDOW * cw = ::stdscr; 00740 STREAM << form( "NC - rootw %p", cw ); 00741 00742 if ( cw ) 00743 STREAM << form( " - (%2hd,%2hd)%2hdx%2hd - {%p - (%2d,%2d)}\n" 00744 , cw->_begy, cw->_begx 00745 , cw->_maxy, cw->_maxx 00746 , cw->_parent 00747 , cw->_pary, cw->_parx 00748 ); 00749 else 00750 STREAM << std::endl; 00751 00752 cw = OBJ.title_w; 00753 00754 STREAM << form( "NC - title %p", cw ); 00755 00756 if ( cw ) 00757 STREAM << form( " - (%2hd,%2hd)%2hdx%2hd - {%p - (%2d,%2d)}\n" 00758 , cw->_begy, cw->_begx 00759 , cw->_maxy, cw->_maxx 00760 , cw->_parent 00761 , cw->_pary, cw->_parx 00762 ); 00763 else 00764 STREAM << std::endl; 00765 00766 return STREAM; 00767 }