libyui-qt  2.47.1
YQUI.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: YQUI.cc
20 
21  Author: Stefan Hundhammer <sh@suse.de>
22 
23 /-*/
24 
25 #include <sys/param.h> // MAXHOSTNAMELEN
26 #include <dlfcn.h>
27 #include <libintl.h>
28 #include <algorithm>
29 #include <stdio.h>
30 
31 #include <QWidget>
32 #include <QThread>
33 #include <QSocketNotifier>
34 #include <QDesktopWidget>
35 #include <QEvent>
36 #include <QCursor>
37 #include <QLocale>
38 #include <QMessageLogContext>
39 
40 
41 #define YUILogComponent "qt-ui"
42 #include <yui/YUILog.h>
43 #include <yui/Libyui_config.h>
44 
45 #include "YQUI.h"
46 
47 #include <yui/YEvent.h>
48 #include <yui/YCommandLine.h>
49 #include <yui/YButtonBox.h>
50 #include <yui/YUISymbols.h>
51 
52 #include "QY2Styler.h"
53 #include "YQApplication.h"
54 #include "YQDialog.h"
55 #include "YQWidgetFactory.h"
56 #include "YQOptionalWidgetFactory.h"
57 
58 #include "YQi18n.h"
59 #include "utf8.h"
60 
61 // Include low-level X headers AFTER Qt headers:
62 // X.h pollutes the global namespace (!!!) with pretty useless #defines
63 // like "Above", "Below" etc. that clash with some Qt headers.
64 #include <X11/Xlib.h>
65 
66 
67 using std::max;
68 
69 #define BUSY_CURSOR_TIMEOUT 200 // milliseconds
70 #define VERBOSE_EVENT_LOOP 0
71 
72 
73 
74 static void qMessageHandler( QtMsgType type, const QMessageLogContext &, const QString & msg );
75 YQUI * YQUI::_ui = 0;
76 
77 
78 YUI * createUI( bool withThreads )
79 {
80  if ( ! YQUI::ui() )
81  {
82  YQUI * ui = new YQUI( withThreads );
83 
84  if ( ui && ! withThreads )
85  ui->initUI();
86  }
87 
88  return YQUI::ui();
89 }
90 
91 
92 YQUI::YQUI( bool withThreads )
93  : YUI( withThreads )
94 #if 0
95  , _main_win( NULL )
96 #endif
97  , _do_exit_loop( false )
98 {
99  yuiDebug() << "YQUI constructor start" << std::endl;
100  yuiMilestone() << "This is libyui-qt " << VERSION << std::endl;
101 
102  _ui = this;
103  _uiInitialized = false;
104  _fatalError = false;
105  _fullscreen = false;
106  _noborder = false;
107  screenShotNameTemplate = "";
108  _blockedLevel = 0;
109 
110  qInstallMessageHandler( qMessageHandler );
111 
112  yuiDebug() << "YQUI constructor finished" << std::endl;
113 
114  topmostConstructorHasFinished();
115 }
116 
117 
119 {
120  if ( _uiInitialized )
121  return;
122 
123  _uiInitialized = true;
124  yuiDebug() << "Initializing Qt part" << std::endl;
125 
126  YCommandLine cmdLine; // Retrieve command line args from /proc/<pid>/cmdline
127  std::string progName;
128 
129  if ( cmdLine.argc() > 0 )
130  {
131  progName = cmdLine[0];
132  std::size_t lastSlashPos = progName.find_last_of( '/' );
133 
134  if ( lastSlashPos != std::string::npos )
135  progName = progName.substr( lastSlashPos+1 );
136 
137  // Qt will display argv[0] as the window manager title.
138  // For YaST2, display "YaST2" instead of "y2base".
139  // For other applications, leave argv[0] alone.
140 
141  if ( progName == "y2base" )
142  cmdLine.replace( 0, "YaST2" );
143  }
144 
145  _ui_argc = cmdLine.argc();
146  char ** argv = cmdLine.argv();
147 
148  // Probe X11 display for better error handling if it can't be opened
149  probeX11Display( cmdLine );
150 
151  yuiDebug() << "Creating QApplication" << std::endl;
152  new QApplication( _ui_argc, argv );
153  Q_CHECK_PTR( qApp );
154  // Qt keeps track to a global QApplication in qApp.
155 
156  _signalReceiver = new YQUISignalReceiver();
157  _busyCursorTimer = new QTimer( _signalReceiver );
158  _busyCursorTimer->setSingleShot( true );
159 
160  (void) QY2Styler::styler(); // Make sure QY2Styler singleton is created
161 
162  setButtonOrderFromEnvironment();
163  processCommandLineArgs( _ui_argc, argv );
164  calcDefaultSize();
165 
166  _do_exit_loop = false;
167 
168 #if 0
169  // Create main window for `opt(`defaultsize) dialogs.
170  //
171  // We have to use something else than QWidgetStack since QWidgetStack
172  // doesn't accept a WFlags arg which we badly need here.
173 
174  _main_win = new QWidget( 0, Qt::Window ); // parent, wflags
175  _main_win->setFocusPolicy( Qt::StrongFocus );
176  _main_win->setObjectName( "main_window" );
177 
178  _main_win->resize( _defaultSize );
179 
180  if ( _fullscreen )
181  _main_win->move( 0, 0 );
182 #endif
183 
184 
185  //
186  // Set application title (used by YQDialog and YQWizard)
187  //
188 
189  // for YaST2, display "YaST2" instead of "y2base"
190  if ( progName == "y2base" )
191  _applicationTitle = QString( "YaST2" );
192  else
193  _applicationTitle = fromUTF8( progName );
194 
195  // read x11 display from commandline or environment variable
196  int displayArgPos = cmdLine.find( "-display" );
197  QString displayName;
198 
199  if ( displayArgPos > 0 && displayArgPos+1 < cmdLine.argc() )
200  displayName = cmdLine[ displayArgPos+1 ].c_str();
201  else
202  displayName = getenv( "DISPLAY" );
203 
204  // identify hostname
205  char hostname[ MAXHOSTNAMELEN+1 ];
206  if ( gethostname( hostname, sizeof( hostname )-1 ) == 0 )
207  hostname[ sizeof( hostname ) -1 ] = '\0'; // make sure it's terminated
208  else
209  hostname[0] = '\0';
210 
211  // add hostname to the window title if it's not a local display
212  if ( !displayName.startsWith( ":" ) && strlen( hostname ) > 0 )
213  {
214  _applicationTitle += QString( "@" );
215  _applicationTitle += fromUTF8( hostname );
216  }
217 
218 
219 #if 0
220  // Hide the main window for now. The first call to UI::OpenDialog() on an
221  // `opt(`defaultSize) dialog will trigger a dialog->open() call that shows
222  // the main window - there is nothing to display yet.
223 
224  _main_win->hide();
225 #endif
226 
227  YButtonBoxMargins buttonBoxMargins;
228  buttonBoxMargins.left = 8;
229  buttonBoxMargins.right = 8;
230  buttonBoxMargins.top = 6;
231  buttonBoxMargins.bottom = 6;
232 
233  buttonBoxMargins.spacing = 4;
234  buttonBoxMargins.helpButtonExtraSpacing = 16;
235  YButtonBox::setDefaultMargins( buttonBoxMargins );
236 
237 
238 
239  // Ugly hack as a workaround of bug #121872 (Segfault at program exit
240  // if no Qt style defined):
241  //
242  // Qt does not seem to be designed for use in plugin libs. It loads some
243  // add-on libs dynamically with dlopen() and unloads them at program exit
244  // (QGPluginManager). Unfortunately, since they all depend on the Qt master
245  // lib (libqt-mt) themselves, when they are unloading the last call to
246  // dlclose() for them causes the last reference to libqt-mt to vanish as
247  // well. Since libqt-mt is already in the process of destruction there is
248  // no more reference from the caller of libqt-mt, and the GLIBC decides
249  // that libqt-mt is not needed any more (zero references) and unmaps
250  // libqt-mt. When the static destructor of libqt-mt that triggered the
251  // cleanup in QGPluginManager returns, the code it is to return to is
252  // already unmapped, causing a segfault.
253  //
254  // Workaround: Keep one more reference to libqt-mt open - dlopen() it here
255  // and make sure there is no corresponding dlclose().
256 
257  QString qt_lib_name = QString( QTLIBDIR "/libQtGui.so.%1" ).arg( QT_VERSION >> 16 );;
258  void * qt_lib = dlopen( qt_lib_name.toUtf8().constData(), RTLD_LAZY | RTLD_GLOBAL );
259  if (qt_lib)
260  yuiMilestone() << "Forcing " << qt_lib_name.toUtf8().constData() << " open successful" << std::endl;
261  else
262  yuiError() << "Forcing " << qt_lib_name.toUtf8().constData() << " open failed" << std::endl;
263 
264  // Init other stuff
265 
266  qApp->setFont( yqApp()->currentFont() );
267  busyCursor();
268 
269 
270  QObject::connect( _busyCursorTimer, &pclass(_busyCursorTimer)::timeout,
271  _signalReceiver, &pclass(_signalReceiver)::slotBusyCursor );
272 
273  yuiMilestone() << "YQUI initialized. Thread ID: 0x"
274  << hex << QThread::currentThreadId () << dec
275  << std::endl;
276 
277  qApp->processEvents();
278 }
279 
280 
283 {
284  return static_cast<YQApplication *>( app() );
285 }
286 
287 
288 void YQUI::processCommandLineArgs( int argc, char **argv )
289 {
290  if ( argv )
291  {
292  for( int i=0; i < argc; i++ )
293  {
294  QString opt = argv[i];
295 
296  yuiMilestone() << "Qt argument: " << argv[i] << std::endl;
297 
298  // Normalize command line option - accept "--xy" as well as "-xy"
299 
300  if ( opt.startsWith( "--" ) )
301  opt.remove(0, 1);
302 
303  if ( opt == QString( "-fullscreen" ) ) _fullscreen = true;
304  else if ( opt == QString( "-noborder" ) ) _noborder = true;
305  else if ( opt == QString( "-auto-font" ) ) yqApp()->setAutoFonts( true );
306  else if ( opt == QString( "-auto-fonts" ) ) yqApp()->setAutoFonts( true );
307  else if ( opt == QString( "-gnome-button-order" ) ) YButtonBox::setLayoutPolicy( YButtonBox::gnomeLayoutPolicy() );
308  else if ( opt == QString( "-kde-button-order" ) ) YButtonBox::setLayoutPolicy( YButtonBox::kdeLayoutPolicy() );
309  // --macro is handled by YUI_component
310  else if ( opt == QString( "-help" ) )
311  {
312  fprintf( stderr,
313  "Command line options for the YaST2 Qt UI:\n"
314  "\n"
315  "--nothreads run without additional UI threads\n"
316  "--fullscreen use full screen for `opt(`defaultsize) dialogs\n"
317  "--noborder no window manager border for `opt(`defaultsize) dialogs\n"
318  "--auto-fonts automatically pick fonts, disregard Qt standard settings\n"
319  "--help this help text\n"
320  "\n"
321  "--macro <macro-file> play a macro right on startup\n"
322  "\n"
323  "-no-wm, -noborder etc. are accepted as well as --no-wm, --noborder\n"
324  "to maintain backwards compatibility.\n"
325  "\n"
326  );
327 
328  raiseFatalError();
329  }
330  }
331  }
332 
333  // Qt handles command line option "-reverse" for Arabic / Hebrew
334 }
335 
336 
337 
339 {
340  yuiDebug() <<"Closing down Qt UI." << std::endl;
341 
342  // Intentionally NOT calling dlclose() to libqt-mt
343  // (see constructor for explanation)
344 
345  if ( qApp ) // might already be reset to 0 internally from Qt
346  {
347  qApp->exit();
348  qApp->deleteLater();
349  }
350 
351  delete _signalReceiver;
352 }
353 
354 void
356 {
357  if ( qApp ) // might already be reset to 0 internally from Qt
358  {
359  qApp->exit();
360  qApp->deleteLater();
361  }
362 }
363 
364 
365 YWidgetFactory *
367 {
368  YQWidgetFactory * factory = new YQWidgetFactory();
369  YUI_CHECK_NEW( factory );
370 
371  return factory;
372 }
373 
374 
375 
376 YOptionalWidgetFactory *
378 {
380  YUI_CHECK_NEW( factory );
381 
382  return factory;
383 }
384 
385 
386 YApplication *
387 YQUI::createApplication()
388 {
389  YQApplication * app = new YQApplication();
390  YUI_CHECK_NEW( app );
391 
392  return app;
393 }
394 
395 
397 {
398  QSize primaryScreenSize = qApp->desktop()->screenGeometry( qApp->desktop()->primaryScreen() ).size();
399  QSize availableSize = qApp->desktop()->availableGeometry( qApp->desktop()->primaryScreen() ).size();
400 
401  if ( _fullscreen )
402  {
403  _defaultSize = availableSize;
404 
405  yuiMilestone() << "-fullscreen: using "
406  << _defaultSize.width() << " x " << _defaultSize.height()
407  << "for `opt(`defaultsize)"
408  << std::endl;
409  }
410  else
411  {
412  // Get _defaultSize via -geometry command line option (if set)
413 
414  // Set min defaultsize or figure one out if -geometry was not used
415 
416  if ( _defaultSize.width() < 800 ||
417  _defaultSize.height() < 600 )
418  {
419  if ( primaryScreenSize.width() >= 1024 && primaryScreenSize.height() >= 768 )
420  {
421  // Scale down to 70% of screen size
422 
423  _defaultSize.setWidth ( max( (int) (availableSize.width() * 0.7), 800 ) );
424  _defaultSize.setHeight( max( (int) (availableSize.height() * 0.7), 600 ) );
425  }
426  else
427  {
428  _defaultSize = availableSize;
429  }
430  }
431  else
432  {
433  yuiMilestone() << "Forced size (via -geometry): "
434  << _defaultSize.width() << " x " << _defaultSize.height()
435  << std::endl;
436  }
437  }
438 
439  yuiMilestone() << "Default size: "
440  << _defaultSize.width() << " x " << _defaultSize.height()
441  << std::endl;
442 }
443 
444 
445 void YQUI::idleLoop( int fd_ycp )
446 {
447  initUI();
448 
449  _received_ycp_command = false;
450  QSocketNotifier * notifier = new QSocketNotifier( fd_ycp, QSocketNotifier::Read );
451  QObject::connect( notifier, &pclass(notifier)::activated,
452  _signalReceiver, &pclass(_signalReceiver)::slotReceivedYCPCommand );
453 
454  notifier->setEnabled( true );
455 
456 
457  //
458  // Process Qt events until fd_ycp is readable
459  //
460 
461 #if VERBOSE_EVENT_LOOP
462  yuiDebug() << "Entering idle loop" << std::endl;
463 #endif
464 
465  QEventLoop eventLoop( qApp );
466 
467  while ( !_received_ycp_command )
468  eventLoop.processEvents( QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents );
469 
470 #if VERBOSE_EVENT_LOOP
471  yuiDebug() << "Leaving idle loop" << std::endl;
472 #endif
473 
474  delete notifier;
475 }
476 
477 
479 {
480  _received_ycp_command = true;
481 }
482 
483 
484 void YQUI::sendEvent( YEvent * event )
485 {
486  if ( event )
487  {
488  _eventHandler.sendEvent( event );
489  YQDialog * dialog = (YQDialog *) YDialog::currentDialog( false ); // don't throw
490 
491  if ( dialog )
492  {
493  if ( dialog->eventLoop()->isRunning() )
494  dialog->eventLoop()->exit( 0 );
495  }
496  else
497  {
498  yuiError() << "No dialog" << std::endl;
499  }
500  }
501 }
502 
503 
504 void YQUI::setTextdomain( const char * domain )
505 {
506  bindtextdomain( domain, YSettings::localeDir().c_str() );
507  bind_textdomain_codeset( domain, "utf8" );
508  textdomain( domain );
509 
510  // Make change known.
511  {
512  extern int _nl_msg_cat_cntr;
513  ++_nl_msg_cat_cntr;
514  }
515 }
516 
517 
518 void YQUI::blockEvents( bool block )
519 {
520  initUI();
521 
522  if ( block )
523  {
524  if ( ++_blockedLevel == 1 )
525  {
526  _eventHandler.blockEvents( true );
527 
528  YQDialog * dialog = (YQDialog *) YDialog::currentDialog( false ); // don't throw
529 
530  if ( dialog && dialog->eventLoop()->isRunning() )
531  {
532  yuiWarning() << "blocking events in active event loop of " << dialog << std::endl;
533  dialog->eventLoop()->exit();
534  }
535  }
536  }
537  else
538  {
539  if ( --_blockedLevel == 0 )
540  {
541  _eventHandler.blockEvents( false );
542 
543  YQDialog * dialog = (YQDialog *) YDialog::currentDialog( false ); // don't throw
544 
545  if ( dialog )
546  dialog->eventLoop()->wakeUp();
547  }
548  }
549 }
550 
551 
553 {
554  initUI();
555  _blockedLevel = 0;
556  _eventHandler.blockEvents( false );
557 }
558 
559 
561 {
562  return _eventHandler.eventsBlocked();
563 }
564 
565 
567 {
568  qApp->setOverrideCursor( Qt::BusyCursor );
569 }
570 
571 
573 {
574  if ( _busyCursorTimer->isActive() )
575  _busyCursorTimer->stop();
576 
577  while ( qApp->overrideCursor() )
578  qApp->restoreOverrideCursor();
579 }
580 
581 
583 {
584  // Display a busy cursor, but only if there is no other activity within
585  // BUSY_CURSOR_TIMEOUT milliseconds: Avoid cursor flicker.
586 
587  _busyCursorTimer->start( BUSY_CURSOR_TIMEOUT ); // single shot
588 }
589 
590 
591 int YQUI::defaultSize(YUIDimension dim) const
592 {
593  return dim == YD_HORIZ ? _defaultSize.width() : _defaultSize.height();
594 }
595 
596 
597 void YQUI::probeX11Display( const YCommandLine & cmdLine )
598 {
599  int displayArgPos = cmdLine.find( "-display" );
600  std::string displayNameStr;
601 
602  if ( displayArgPos > 0 && displayArgPos+1 < cmdLine.argc() )
603  {
604  displayNameStr = cmdLine[ displayArgPos+1 ];
605  yuiMilestone() << "Using X11 display \"" << displayNameStr << "\"" << std::endl;
606  }
607 
608  const char * displayName = ( displayNameStr.empty() ? 0 : displayNameStr.c_str() );
609  Display * display = XOpenDisplay( displayName );
610 
611  if ( display )
612  {
613  yuiDebug() << "Probing X11 display successful" << std::endl;
614  XCloseDisplay( display );
615  }
616  else
617  {
618  string msg = "Can't open display " + displayNameStr;
619  YUI_THROW( YUIException( msg ) );
620  }
621 }
622 
623 
624 void YQUI::deleteNotify( YWidget * widget )
625 {
626  _eventHandler.deletePendingEventsFor( widget );
627 }
628 
629 // FIXME: Does this still do anything now that YQUI is no longer a QObject?
631 {
632  yuiMilestone() << "Closing application" << std::endl;
633  sendEvent( new YCancelEvent() );
634  return true;
635 }
636 
637 
638 
639 
640 YQUISignalReceiver::YQUISignalReceiver()
641  : QObject()
642 {
643 }
644 
645 
646 void YQUISignalReceiver::slotBusyCursor()
647 {
648  YQUI::ui()->busyCursor();
649 }
650 
651 
652 void YQUISignalReceiver::slotReceivedYCPCommand()
653 {
655 }
656 
657 
658 
659 static void
660 qMessageHandler( QtMsgType type, const QMessageLogContext &, const QString & msg )
661 {
662  switch (type)
663  {
664  case QtDebugMsg:
665  yuiMilestone() << "<libqt-debug> " << msg << std::endl;
666  break;
667 
668 #if QT_VERSION >= 0x050500
669  case QtInfoMsg:
670  yuiMilestone() << "<libqt-info> " << msg << std::endl;
671  break;
672 #endif
673 
674  case QtWarningMsg:
675  yuiWarning() << "<libqt-warning> " << msg << std::endl;
676  break;
677 
678  case QtCriticalMsg:
679  yuiError() << "<libqt-critical>" << msg << std::endl;
680  break;
681 
682  case QtFatalMsg:
683  yuiError() << "<libqt-fatal> " << msg << std::endl;
684  abort();
685  exit(1); // Qt does the same
686  }
687 
688  if ( QString( msg ).contains( "Fatal IO error", Qt::CaseInsensitive ) &&
689  QString( msg ).contains( "client killed", Qt::CaseInsensitive ) )
690  yuiError() << "Client killed. Possibly caused by X server shutdown or crash." << std::endl;
691 }
692 
693 
694 
695 #include "YQUI.moc"
int defaultSize(YUIDimension dim) const
Returns size for opt(defaultsize) dialogs (in one dimension).
Definition: YQUI.cc:591
void receivedYCPCommand()
Notification that a YCP command has been received on fd_ycp to leave idleLoop()
Definition: YQUI.cc:478
static YQApplication * yqApp()
Return the global YApplication object as YQApplication.
Definition: YQUI.cc:282
void forceUnblockEvents()
Force unblocking all events, no matter how many times blockEvents() has This returns 0 if there is no...
Definition: YQUI.cc:552
void setAutoFonts(bool useAutoFonts)
Set whether or not fonts should automatically be picked.
virtual YOptionalWidgetFactory * createOptionalWidgetFactory()
Create the widget factory that provides all the createXY() methods for optional ("special") widgets a...
Definition: YQUI.cc:377
void calcDefaultSize()
Calculate size of opt(defaultsize) dialogs.
Definition: YQUI.cc:396
QEventLoop * eventLoop()
Access to this dialog's event loop.
Definition: YQDialog.h:201
Helper class that acts as a Qt signal receiver for YQUI.
Definition: YQUI.h:368
void sendEvent(YEvent *event)
Widget event handlers (slots) call this when an event occured that should be the answer to a UserInpu...
Definition: YQUI.cc:484
YQUI(bool withThreads)
Constructor.
Definition: YQUI.cc:92
virtual void idleLoop(int fd_ycp)
Idle around until fd_ycp is readable and handle repaints.
Definition: YQUI.cc:445
void probeX11Display(const YCommandLine &cmdLine)
Probe the X11 display.
Definition: YQUI.cc:597
virtual void deleteNotify(YWidget *widget)
Notification that a widget is being deleted.
Definition: YQUI.cc:624
void busyCursor()
Show mouse cursor indicating busy state.
Definition: YQUI.cc:566
Definition: YQUI.h:61
void processCommandLineArgs(int argc, char **argv)
Handle command line args.
Definition: YQUI.cc:288
Concrete widget factory for mandatory widgets.
virtual void uiThreadDestructor()
Destroy whatever needs to be destroyed within the UI thread.
Definition: YQUI.cc:355
virtual void blockEvents(bool block=true)
Block (or unblock) events.
Definition: YQUI.cc:518
void timeoutBusyCursor()
Show mouse cursor indicating busy state if the UI is unable to respond to user input for more than a ...
Definition: YQUI.cc:582
bool close()
Application shutdown.
Definition: YQUI.cc:630
void normalCursor()
Show normal mouse cursor not indicating busy status.
Definition: YQUI.cc:572
void initUI()
Post-constructor initialization.
Definition: YQUI.cc:118
virtual ~YQUI()
Destructor.
Definition: YQUI.cc:338
void raiseFatalError()
Raise a fatal UI error.
Definition: YQUI.h:184
static void setTextdomain(const char *domain)
Initialize and set a textdomain for gettext()
Definition: YQUI.cc:504
static YQUI * ui()
Access the global Qt-UI.
Definition: YQUI.h:80
Widget factory for optional ("special") widgets.
virtual bool eventsBlocked() const
Returns 'true' if events are currently blocked.
Definition: YQUI.cc:560
virtual YWidgetFactory * createWidgetFactory()
Create the widget factory that provides all the createXY() methods for standard (mandatory,...
Definition: YQUI.cc:366