1 /*****************************************************************************
2  * main_interface.cpp : Main interface
3  ****************************************************************************
4  * Copyright (C) 2006-2011 VideoLAN and AUTHORS
5  * $Id: a57506533d635a67ca3fff0d9be35635eec11997 $
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Jean-Baptiste Kempf <jb@videolan.org>
9  *          Ilkka Ollakka <ileoo@videolan.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25 
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29 
30 #include "qt.hpp"
31 
32 #include "main_interface.hpp"
33 #include "input_manager.hpp"                    // Creation
34 #include "actions_manager.hpp"                  // killInstance
35 
36 #include "util/customwidgets.hpp"               // qtEventToVLCKey, QVLCStackedWidget
37 #include "util/qt_dirs.hpp"                     // toNativeSeparators
38 #include "util/imagehelper.hpp"
39 
40 #include "components/interface_widgets.hpp"     // bgWidget, videoWidget
41 #include "components/controller.hpp"            // controllers
42 #include "components/playlist/playlist.hpp"     // plWidget
43 #include "dialogs/firstrun.hpp"                 // First Run
44 #include "dialogs/playlist.hpp"                 // PlaylistDialog
45 
46 #include "menus.hpp"                            // Menu creation
47 #include "recents.hpp"                          // RecentItems when DnD
48 
49 #include <QCloseEvent>
50 #include <QKeyEvent>
51 
52 #include <QUrl>
53 #include <QSize>
54 #include <QDate>
55 #include <QMimeData>
56 
57 #include <QWindow>
58 #include <QMenu>
59 #include <QMenuBar>
60 #include <QStatusBar>
61 #include <QLabel>
62 #include <QStackedWidget>
63 #include <QScreen>
64 #ifdef _WIN32
65 #include <QFileInfo>
66 #endif
67 
68 #if ! HAS_QT510 && defined(QT5_HAS_X11)
69 # include <QX11Info>
70 # include <X11/Xlib.h>
71 #endif
72 
73 #include <QTimer>
74 
75 #include <vlc_actions.h>                    /* Wheel event */
76 #include <vlc_vout_display.h>               /* vout_thread_t and VOUT_ events */
77 
78 // #define DEBUG_INTF
79 
80 /* Callback prototypes */
81 static int PopupMenuCB( vlc_object_t *p_this, const char *psz_variable,
82                         vlc_value_t old_val, vlc_value_t new_val, void *param );
83 static int IntfShowCB( vlc_object_t *p_this, const char *psz_variable,
84                        vlc_value_t old_val, vlc_value_t new_val, void *param );
85 static int IntfBossCB( vlc_object_t *p_this, const char *psz_variable,
86                        vlc_value_t old_val, vlc_value_t new_val, void *param );
87 static int IntfRaiseMainCB( vlc_object_t *p_this, const char *psz_variable,
88                            vlc_value_t old_val, vlc_value_t new_val,
89                            void *param );
90 
91 const QEvent::Type MainInterface::ToolbarsNeedRebuild =
92         (QEvent::Type)QEvent::registerEventType();
93 
MainInterface(intf_thread_t * _p_intf)94 MainInterface::MainInterface( intf_thread_t *_p_intf ) : QVLCMW( _p_intf )
95 {
96     /* Variables initialisation */
97     bgWidget             = NULL;
98     videoWidget          = NULL;
99     playlistWidget       = NULL;
100     stackCentralOldWidget= NULL;
101     lastWinScreen        = NULL;
102     sysTray              = NULL;
103     fullscreenControls   = NULL;
104     cryptedLabel         = NULL;
105     controls             = NULL;
106     inputC               = NULL;
107 
108     b_hideAfterCreation  = false; // --qt-start-minimized
109     playlistVisible      = false;
110     input_name           = "";
111     b_interfaceFullScreen= false;
112     b_hasPausedWhenMinimized = false;
113     i_kc_offset          = false;
114     b_maximizedView      = false;
115     b_isWindowTiled      = false;
116 
117     /* Ask for Privacy */
118     FirstRun::CheckAndRun( this, p_intf );
119 
120     /**
121      *  Configuration and settings
122      *  Pre-building of interface
123      **/
124     /* Main settings */
125     setFocusPolicy( Qt::StrongFocus );
126     setAcceptDrops( true );
127     setWindowRole( "vlc-main" );
128     setWindowIcon( QApplication::windowIcon() );
129     setWindowOpacity( var_InheritFloat( p_intf, "qt-opacity" ) );
130 
131     /* Does the interface resize to video size or the opposite */
132     b_autoresize = var_InheritBool( p_intf, "qt-video-autoresize" );
133 
134     /* Are we in the enhanced always-video mode or not ? */
135     b_minimalView = var_InheritBool( p_intf, "qt-minimal-view" );
136 
137     /* Do we want anoying popups or not */
138     i_notificationSetting = var_InheritInteger( p_intf, "qt-notification" );
139 
140     /* */
141     b_pauseOnMinimize = var_InheritBool( p_intf, "qt-pause-minimized" );
142 
143     /* Set the other interface settings */
144     settings = getSettings();
145 
146     /* */
147     b_plDocked = getSettings()->value( "MainWindow/pl-dock-status", true ).toBool();
148 
149     /* Should the UI stays on top of other windows */
150     b_interfaceOnTop = var_InheritBool( p_intf, "video-on-top" );
151 
152 #ifdef QT5_HAS_WAYLAND
153     b_hasWayland = QGuiApplication::platformName()
154         .startsWith(QLatin1String("wayland"), Qt::CaseInsensitive);
155 #endif
156 
157     /**************************
158      *  UI and Widgets design
159      **************************/
160     setVLCWindowsTitle();
161 
162     /************
163      * Menu Bar *
164      ************/
165     VLCMenuBar::createMenuBar( this, p_intf );
166     CONNECT( THEMIM->getIM(), voutListChanged( vout_thread_t **, int ),
167              THEDP, destroyPopupMenu() );
168 
169     createMainWidget( settings );
170 
171     /**************
172      * Status Bar *
173      **************/
174     createStatusBar();
175     setStatusBarVisibility( getSettings()->value( "MainWindow/status-bar-visible", false ).toBool() );
176 
177     /*********************************
178      * Create the Systray Management *
179      *********************************/
180     initSystray();
181 
182     /*************************************************************
183      * Connect the input manager to the GUI elements it manages  *
184      * Beware initSystray did some connects on input manager too *
185      *************************************************************/
186     /**
187      * Connects on nameChanged()
188      * Those connects are different because options can impeach them to trigger.
189      **/
190     /* Main Interface statusbar */
191     CONNECT( THEMIM->getIM(), nameChanged( const QString& ),
192              this, setName( const QString& ) );
193     /* and title of the Main Interface*/
194     if( var_InheritBool( p_intf, "qt-name-in-title" ) )
195     {
196         CONNECT( THEMIM->getIM(), nameChanged( const QString& ),
197                  this, setVLCWindowsTitle( const QString& ) );
198     }
199     CONNECT( THEMIM, inputChanged( bool ), this, onInputChanged( bool ) );
200 
201     /* END CONNECTS ON IM */
202 
203     /* VideoWidget connects for asynchronous calls */
204     b_videoFullScreen = false;
205     connect( this, SIGNAL(askGetVideo(struct vout_window_t*, unsigned, unsigned, bool, bool*)),
206              this, SLOT(getVideoSlot(struct vout_window_t*, unsigned, unsigned, bool, bool*)),
207              Qt::BlockingQueuedConnection );
208     connect( this, SIGNAL(askReleaseVideo( void )),
209              this, SLOT(releaseVideoSlot( void )),
210              Qt::BlockingQueuedConnection );
211     CONNECT( this, askVideoOnTop(bool), this, setVideoOnTop(bool));
212 
213     if( videoWidget )
214     {
215         if( b_autoresize )
216         {
217             CONNECT( videoWidget, sizeChanged( int, int ),
218                      this, videoSizeChanged( int,  int ) );
219         }
220         CONNECT( this, askVideoToResize( unsigned int, unsigned int ),
221                  this, setVideoSize( unsigned int, unsigned int ) );
222 
223         CONNECT( this, askVideoSetFullScreen( bool ),
224                  this, setVideoFullScreen( bool ) );
225         CONNECT( this, askHideMouse( bool ),
226                  this, setHideMouse( bool ) );
227     }
228 
229     CONNECT( THEDP, toolBarConfUpdated(), this, toolBarConfUpdated() );
230     installEventFilter( this );
231 
232     CONNECT( this, askToQuit(), THEDP, quit() );
233 
234     CONNECT( this, askBoss(), this, setBoss() );
235     CONNECT( this, askRaise(), this, setRaise() );
236 
237 
238     connect( THEDP, &DialogsProvider::releaseMouseEvents, this, &MainInterface::voutReleaseMouseEvents ) ;
239     /** END of CONNECTS**/
240 
241 
242     /************
243      * Callbacks
244      ************/
245     var_AddCallback( p_intf->obj.libvlc, "intf-toggle-fscontrol", IntfShowCB, p_intf );
246     var_AddCallback( p_intf->obj.libvlc, "intf-boss", IntfBossCB, p_intf );
247     var_AddCallback( p_intf->obj.libvlc, "intf-show", IntfRaiseMainCB, p_intf );
248 
249     /* Register callback for the intf-popupmenu variable */
250     var_AddCallback( p_intf->obj.libvlc, "intf-popupmenu", PopupMenuCB, p_intf );
251 
252 
253     /* Final Sizing, restoration and placement of the interface */
254     if( settings->value( "MainWindow/playlist-visible", false ).toBool() )
255         togglePlaylist();
256 
257     QVLCTools::restoreWidgetPosition( settings, this, QSize(600, 420) );
258 
259     b_interfaceFullScreen = isFullScreen();
260 
261     setVisible( !b_hideAfterCreation );
262 
263     /* Switch to minimal view if needed, must be called after the show() */
264     if( b_minimalView )
265         toggleMinimalView( true );
266 
267     computeMinimumSize();
268 }
269 
~MainInterface()270 MainInterface::~MainInterface()
271 {
272     /* Unsure we hide the videoWidget before destroying it */
273     if( stackCentralOldWidget == videoWidget )
274         showTab( bgWidget );
275 
276     if( videoWidget )
277         releaseVideoSlot();
278 
279     /* Be sure to kill the actionsManager... Only used in the MI and control */
280     ActionsManager::killInstance();
281 
282     /* Delete the FSC controller */
283     delete fullscreenControls;
284 
285     /* Save states */
286 
287     settings->beginGroup("MainWindow");
288     settings->setValue( "pl-dock-status", b_plDocked );
289 
290     /* Save playlist state */
291     settings->setValue( "playlist-visible", playlistVisible );
292 
293     settings->setValue( "adv-controls",
294                         getControlsVisibilityStatus() & CONTROLS_ADVANCED );
295     settings->setValue( "status-bar-visible", b_statusbarVisible );
296 
297     /* Save the stackCentralW sizes */
298     settings->setValue( "bgSize", stackWidgetsSizes[bgWidget] );
299     settings->setValue( "playlistSize", stackWidgetsSizes[playlistWidget] );
300     settings->endGroup();
301 
302     /* Save this size */
303     QVLCTools::saveWidgetPosition(settings, this);
304 
305     /* Unregister callbacks */
306     var_DelCallback( p_intf->obj.libvlc, "intf-boss", IntfBossCB, p_intf );
307     var_DelCallback( p_intf->obj.libvlc, "intf-show", IntfRaiseMainCB, p_intf );
308     var_DelCallback( p_intf->obj.libvlc, "intf-toggle-fscontrol", IntfShowCB, p_intf );
309     var_DelCallback( p_intf->obj.libvlc, "intf-popupmenu", PopupMenuCB, p_intf );
310 
311     p_intf->p_sys->p_mi = NULL;
312 }
313 
computeMinimumSize()314 void MainInterface::computeMinimumSize()
315 {
316     int minWidth = 80;
317     if( menuBar()->isVisible() )
318         minWidth += controls->sizeHint().width();
319 
320     setMinimumWidth( minWidth );
321 }
322 
323 /*****************************
324  *   Main UI handling        *
325  *****************************/
recreateToolbars()326 void MainInterface::recreateToolbars()
327 {
328     bool b_adv = getControlsVisibilityStatus() & CONTROLS_ADVANCED;
329 
330     delete controls;
331     delete inputC;
332 
333     controls = new ControlsWidget( p_intf, b_adv, this );
334     inputC = new InputControlsWidget( p_intf, this );
335     mainLayout->insertWidget( 2, inputC );
336     mainLayout->insertWidget( settings->value( "MainWindow/ToolbarPos", false ).toBool() ? 0: 3,
337                               controls );
338 
339     if( fullscreenControls )
340     {
341         delete fullscreenControls;
342         fullscreenControls = new FullscreenControllerWidget( p_intf, this );
343         CONNECT( fullscreenControls, keyPressed( QKeyEvent * ),
344                  this, handleKeyPress( QKeyEvent * ) );
345         THEMIM->requestVoutUpdate();
346     }
347 
348     setMinimalView( b_minimalView );
349 }
350 
reloadPrefs()351 void MainInterface::reloadPrefs()
352 {
353     i_notificationSetting = var_InheritInteger( p_intf, "qt-notification" );
354     b_pauseOnMinimize = var_InheritBool( p_intf, "qt-pause-minimized" );
355     if( !var_InheritBool( p_intf, "qt-fs-controller" ) && fullscreenControls )
356     {
357         delete fullscreenControls;
358         fullscreenControls = NULL;
359     }
360 }
361 
createResumePanel(QWidget * w)362 void MainInterface::createResumePanel( QWidget *w )
363 {
364     resumePanel = new QWidget( w );
365     resumePanel->hide();
366     QHBoxLayout *resumePanelLayout = new QHBoxLayout( resumePanel );
367     resumePanelLayout->setSpacing( 0 ); resumePanelLayout->setMargin( 0 );
368 
369     QLabel *continuePixmapLabel = new QLabel();
370     continuePixmapLabel->setPixmap( ImageHelper::loadSvgToPixmap( ":/menu/help.svg" , fontMetrics().height(), fontMetrics().height()) );
371     continuePixmapLabel->setContentsMargins( 5, 0, 5, 0 );
372 
373     QLabel *continueLabel = new QLabel( qtr( "Do you want to restart the playback where left off?") );
374 
375     QToolButton *cancel = new QToolButton( resumePanel );
376     cancel->setAutoRaise( true );
377     cancel->setText( "X" );
378 
379     QPushButton *ok = new QPushButton( qtr( "&Continue" )  );
380 
381     resumePanelLayout->addWidget( continuePixmapLabel );
382     resumePanelLayout->addWidget( continueLabel );
383     resumePanelLayout->addStretch( 1 );
384     resumePanelLayout->addWidget( ok );
385     resumePanelLayout->addWidget( cancel );
386 
387     resumeTimer = new QTimer( resumePanel );
388     resumeTimer->setSingleShot( true );
389     resumeTimer->setInterval( 6000 );
390 
391     CONNECT( resumeTimer, timeout(), this, hideResumePanel() );
392     CONNECT( cancel, clicked(), this, hideResumePanel() );
393     CONNECT( THEMIM->getIM(), resumePlayback(int64_t), this, showResumePanel(int64_t) );
394     BUTTONACT( ok, resumePlayback() );
395 
396     w->layout()->addWidget( resumePanel );
397 }
398 
showResumePanel(int64_t _time)399 void MainInterface::showResumePanel( int64_t _time ) {
400     int setting = var_InheritInteger( p_intf, "qt-continue" );
401 
402     if( setting == 0 )
403         return;
404 
405     i_resumeTime = _time;
406 
407     if( setting == 2)
408         resumePlayback();
409     else
410     {
411         if( !isFullScreen() && !isMaximized() && !b_isWindowTiled )
412             resizeWindow( width(), height() + resumePanel->height() );
413         resumePanel->setVisible(true);
414         resumeTimer->start();
415     }
416 }
417 
hideResumePanel()418 void MainInterface::hideResumePanel()
419 {
420     if( resumePanel->isVisible() )
421     {
422         if( !isFullScreen() && !isMaximized() && !b_isWindowTiled )
423             resizeWindow( width(), height() - resumePanel->height() );
424         resumePanel->hide();
425         resumeTimer->stop();
426     }
427 }
428 
resumePlayback()429 void MainInterface::resumePlayback()
430 {
431     if( THEMIM->getIM()->hasInput() ) {
432         var_SetInteger( THEMIM->getInput(), "time", i_resumeTime );
433     }
434     hideResumePanel();
435 }
436 
onInputChanged(bool hasInput)437 void MainInterface::onInputChanged( bool hasInput )
438 {
439     if( hasInput == false )
440         return;
441     int autoRaise = var_InheritInteger( p_intf, "qt-auto-raise" );
442     if ( autoRaise == MainInterface::RAISE_NEVER )
443         return;
444     if( THEMIM->getIM()->hasVideo() == true )
445     {
446         if( ( autoRaise & MainInterface::RAISE_VIDEO ) == 0 )
447             return;
448     }
449     else if ( ( autoRaise & MainInterface::RAISE_AUDIO ) == 0 )
450         return;
451     emit askRaise();
452 }
453 
createMainWidget(QSettings * creationSettings)454 void MainInterface::createMainWidget( QSettings *creationSettings )
455 {
456     /* Create the main Widget and the mainLayout */
457     QWidget *main = new QWidget;
458     setCentralWidget( main );
459     mainLayout = new QVBoxLayout( main );
460     main->setContentsMargins( 0, 0, 0, 0 );
461     mainLayout->setSpacing( 0 ); mainLayout->setMargin( 0 );
462 
463     createResumePanel( main );
464     /* */
465     stackCentralW = new QVLCStackedWidget( main );
466 
467     /* Bg Cone */
468     if ( QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY
469          && var_InheritBool( p_intf, "qt-icon-change" ) )
470     {
471         bgWidget = new EasterEggBackgroundWidget( p_intf );
472         CONNECT( this, kc_pressed(), bgWidget, animate() );
473     }
474     else
475         bgWidget = new BackgroundWidget( p_intf );
476 
477     stackCentralW->addWidget( bgWidget );
478     if ( !var_InheritBool( p_intf, "qt-bgcone" ) )
479         bgWidget->setWithArt( false );
480     else
481         if ( var_InheritBool( p_intf, "qt-bgcone-expands" ) )
482             bgWidget->setExpandstoHeight( true );
483 
484     /* And video Outputs */
485     if( var_InheritBool( p_intf, "embedded-video" ) )
486     {
487         videoWidget = new VideoWidget( p_intf, stackCentralW );
488         stackCentralW->addWidget( videoWidget );
489     }
490     mainLayout->insertWidget( 1, stackCentralW );
491 
492     stackWidgetsSizes[bgWidget] =
493         creationSettings->value( "MainWindow/bgSize", QSize( 600, 0 ) ).toSize();
494     /* Resize even if no-auto-resize, because we are at creation */
495     resizeStack( stackWidgetsSizes[bgWidget].width(), stackWidgetsSizes[bgWidget].height() );
496 
497     /* Create the CONTROLS Widget */
498     controls = new ControlsWidget( p_intf,
499         creationSettings->value( "MainWindow/adv-controls", false ).toBool(), this );
500     inputC = new InputControlsWidget( p_intf, this );
501 
502     mainLayout->insertWidget( 2, inputC );
503     mainLayout->insertWidget(
504         creationSettings->value( "MainWindow/ToolbarPos", false ).toBool() ? 0: 3,
505         controls );
506 
507     /* Visualisation, disabled for now, they SUCK */
508     #if 0
509     visualSelector = new VisualSelector( p_intf );
510     mainLayout->insertWidget( 0, visualSelector );
511     visualSelector->hide();
512     #endif
513 
514 
515     /* Enable the popup menu in the MI */
516     main->setContextMenuPolicy( Qt::CustomContextMenu );
517     CONNECT( main, customContextMenuRequested( const QPoint& ),
518              THEDP, setPopupMenu() );
519 
520     if ( depth() > 8 ) /* 8bit depth has too many issues with opacity */
521         /* Create the FULLSCREEN CONTROLS Widget */
522         if( var_InheritBool( p_intf, "qt-fs-controller" ) )
523         {
524             fullscreenControls = new FullscreenControllerWidget( p_intf, this );
525             CONNECT( fullscreenControls, keyPressed( QKeyEvent * ),
526                      this, handleKeyPress( QKeyEvent * ) );
527         }
528 
529     if ( b_interfaceOnTop )
530         setWindowFlags( windowFlags() | Qt::WindowStaysOnTopHint );
531 }
532 
initSystray()533 inline void MainInterface::initSystray()
534 {
535     bool b_systrayAvailable = QSystemTrayIcon::isSystemTrayAvailable();
536     bool b_systrayWanted = var_InheritBool( p_intf, "qt-system-tray" );
537 
538     if( var_InheritBool( p_intf, "qt-start-minimized") )
539     {
540         if( b_systrayAvailable )
541         {
542             b_systrayWanted = true;
543             b_hideAfterCreation = true;
544         }
545         else
546             msg_Err( p_intf, "cannot start minimized without system tray bar" );
547     }
548 
549     if( b_systrayAvailable && b_systrayWanted )
550         createSystray();
551 }
552 
createStatusBar()553 inline void MainInterface::createStatusBar()
554 {
555     /****************
556      *  Status Bar  *
557      ****************/
558     /* Widgets Creation*/
559     QStatusBar *statusBarr = statusBar();
560 
561     TimeLabel *timeLabel = new TimeLabel( p_intf );
562     nameLabel = new ClickableQLabel();
563     nameLabel->setTextInteractionFlags( Qt::TextSelectableByMouse
564                                       | Qt::TextSelectableByKeyboard );
565     SpeedLabel *speedLabel = new SpeedLabel( p_intf, this );
566 
567     /* Styling those labels */
568     timeLabel->setFrameStyle( QFrame::Sunken | QFrame::Panel );
569     speedLabel->setFrameStyle( QFrame::Sunken | QFrame::Panel );
570     nameLabel->setFrameStyle( QFrame::Sunken | QFrame::StyledPanel);
571     timeLabel->setStyleSheet(
572             "QLabel:hover { background-color: rgba(255, 255, 255, 50%) }" );
573     speedLabel->setStyleSheet(
574             "QLabel:hover { background-color: rgba(255, 255, 255, 50%) }" );
575     /* pad both label and its tooltip */
576     nameLabel->setStyleSheet( "padding-left: 5px; padding-right: 5px;" );
577 
578     /* and adding those */
579     statusBarr->addWidget( nameLabel, 8 );
580     statusBarr->addPermanentWidget( speedLabel, 0 );
581     statusBarr->addPermanentWidget( timeLabel, 0 );
582 
583     CONNECT( nameLabel, doubleClicked(), THEDP, epgDialog() );
584     /* timeLabel behaviour:
585        - double clicking opens the goto time dialog
586        - right-clicking and clicking just toggle between remaining and
587          elapsed time.*/
588     CONNECT( timeLabel, doubleClicked(), THEDP, gotoTimeDialog() );
589 
590     CONNECT( THEMIM->getIM(), encryptionChanged( bool ),
591              this, showCryptedLabel( bool ) );
592 
593     /* This shouldn't be necessary, but for somehow reason, the statusBarr
594        starts at height of 20px and when a text is shown it needs more space.
595        But, as the QMainWindow policy doesn't allow statusBar to change QMW's
596        geometry, we need to force a height. If you have a better idea, please
597        tell me -- jb
598      */
599     statusBarr->setFixedHeight( statusBarr->sizeHint().height() + 2 );
600 }
601 
602 /**********************************************************************
603  * Handling of sizing of the components
604  **********************************************************************/
605 
debug()606 void MainInterface::debug()
607 {
608 #ifdef DEBUG_INTF
609     if( controls ) {
610         msg_Dbg( p_intf, "Controls size: %i - %i", controls->size().height(), controls->size().width() );
611         msg_Dbg( p_intf, "Controls minimumsize: %i - %i", controls->minimumSize().height(), controls->minimumSize().width() );
612         msg_Dbg( p_intf, "Controls sizeHint: %i - %i", controls->sizeHint().height(), controls->sizeHint().width() );
613     }
614 
615     msg_Dbg( p_intf, "size: %i - %i", size().height(), size().width() );
616     msg_Dbg( p_intf, "sizeHint: %i - %i", sizeHint().height(), sizeHint().width() );
617     msg_Dbg( p_intf, "minimumsize: %i - %i", minimumSize().height(), minimumSize().width() );
618 
619     msg_Dbg( p_intf, "Stack size: %i - %i", stackCentralW->size().height(), stackCentralW->size().width() );
620     msg_Dbg( p_intf, "Stack sizeHint: %i - %i", stackCentralW->sizeHint().height(), stackCentralW->sizeHint().width() );
621     msg_Dbg( p_intf, "Central size: %i - %i", centralWidget()->size().height(), centralWidget()->size().width() );
622 #endif
623 }
624 
showVideo()625 inline void MainInterface::showVideo() { showTab( videoWidget ); }
restoreStackOldWidget(bool video_closing)626 inline void MainInterface::restoreStackOldWidget( bool video_closing )
627             { showTab( stackCentralOldWidget, video_closing ); }
628 
showTab(QWidget * widget,bool video_closing)629 inline void MainInterface::showTab( QWidget *widget, bool video_closing )
630 {
631     if ( !widget ) widget = bgWidget; /* trying to restore a null oldwidget */
632 #ifdef DEBUG_INTF
633     if ( stackCentralOldWidget )
634         msg_Dbg( p_intf, "Old stackCentralOldWidget %s at index %i",
635                  stackCentralOldWidget->metaObject()->className(),
636                  stackCentralW->indexOf( stackCentralOldWidget ) );
637     msg_Dbg( p_intf, "ShowTab request for %s", widget->metaObject()->className() );
638 #endif
639     if ( stackCentralW->currentWidget() == widget )
640         return;
641 
642     /* fixing when the playlist has been undocked after being hidden.
643        restoreStackOldWidget() is called when video stops but
644        stackCentralOldWidget would still be pointing to playlist */
645     if ( widget == playlistWidget && !isPlDocked() )
646         widget = bgWidget;
647 
648     stackCentralOldWidget = stackCentralW->currentWidget();
649     if( !isFullScreen() )
650         stackWidgetsSizes[stackCentralOldWidget] = stackCentralW->size();
651 
652     /* If we are playing video, embedded */
653     if( !video_closing && videoWidget && THEMIM->getIM()->hasVideo() )
654     {
655         /* Video -> Playlist */
656         if( videoWidget == stackCentralOldWidget && widget == playlistWidget )
657         {
658             stackCentralW->removeWidget( videoWidget );
659             videoWidget->show(); videoWidget->raise();
660         }
661 
662         /* Playlist -> Video */
663         if( playlistWidget == stackCentralOldWidget && widget == videoWidget )
664         {
665             playlistWidget->artContainer->removeWidget( videoWidget );
666             videoWidget->show(); videoWidget->raise();
667             stackCentralW->addWidget( videoWidget );
668         }
669 
670         /* Embedded playlist -> Non-embedded playlist */
671         if( bgWidget == stackCentralOldWidget && widget == videoWidget )
672         {
673             /* In rare case when video is started before the interface */
674             if( playlistWidget != NULL )
675                 playlistWidget->artContainer->removeWidget( videoWidget );
676             videoWidget->show(); videoWidget->raise();
677             stackCentralW->addWidget( videoWidget );
678             stackCentralW->setCurrentWidget( videoWidget );
679         }
680     }
681 
682     stackCentralW->setCurrentWidget( widget );
683     if( b_autoresize )
684         resizeStack( stackWidgetsSizes[widget].width(), stackWidgetsSizes[widget].height() );
685 
686 #ifdef DEBUG_INTF
687     msg_Dbg( p_intf, "Stack state changed to %s, index %i",
688               stackCentralW->currentWidget()->metaObject()->className(),
689               stackCentralW->currentIndex() );
690     msg_Dbg( p_intf, "New stackCentralOldWidget %s at index %i",
691               stackCentralOldWidget->metaObject()->className(),
692               stackCentralW->indexOf( stackCentralOldWidget ) );
693 #endif
694 
695     /* This part is done later, to account for the new pl size */
696     if( !video_closing && videoWidget && THEMIM->getIM()->hasVideo() &&
697         videoWidget == stackCentralOldWidget && widget == playlistWidget )
698     {
699         playlistWidget->artContainer->addWidget( videoWidget );
700         playlistWidget->artContainer->setCurrentWidget( videoWidget );
701     }
702 }
703 
toggleFSC()704 void MainInterface::toggleFSC()
705 {
706    if( !fullscreenControls ) return;
707 
708    IMEvent *eShow = new IMEvent( IMEvent::FullscreenControlToggle );
709    QApplication::postEvent( fullscreenControls, eShow );
710 }
711 
712 /****************************************************************************
713  * Video Handling
714  ****************************************************************************/
715 
716 /**
717  * NOTE:
718  * You must not change the state of this object or other Qt UI objects,
719  * from the video output thread - only from the Qt UI main loop thread.
720  * All window provider queries must be handled through signals or events.
721  * That's why we have all those emit statements...
722  */
getVideo(struct vout_window_t * p_wnd,unsigned int i_width,unsigned int i_height,bool fullscreen)723 bool MainInterface::getVideo( struct vout_window_t *p_wnd,
724                               unsigned int i_width, unsigned int i_height,
725                               bool fullscreen )
726 {
727     bool result;
728 
729     /* This is a blocking call signal. Results are stored directly in the
730      * vout_window_t and boolean pointers. Beware of deadlocks! */
731     emit askGetVideo( p_wnd, i_width, i_height, fullscreen, &result );
732     return result;
733 }
734 
getVideoSlot(struct vout_window_t * p_wnd,unsigned i_width,unsigned i_height,bool fullscreen,bool * res)735 void MainInterface::getVideoSlot( struct vout_window_t *p_wnd,
736                                   unsigned i_width, unsigned i_height,
737                                   bool fullscreen, bool *res )
738 {
739     /* Hidden or minimized, activate */
740     if( isHidden() || isMinimized() )
741         toggleUpdateSystrayMenu();
742 
743     /* Request the videoWidget */
744     if ( !videoWidget )
745     {
746         videoWidget = new VideoWidget( p_intf, stackCentralW );
747         stackCentralW->addWidget( videoWidget );
748     }
749     *res = videoWidget->request( p_wnd );
750     if( *res ) /* The videoWidget is available */
751     {
752         setVideoFullScreen( fullscreen );
753 
754         /* Consider the video active now */
755         showVideo();
756 
757         /* Ask videoWidget to resize correctly, if we are in normal mode */
758         if( b_autoresize ) {
759 #if HAS_QT56
760             qreal factor = videoWidget->devicePixelRatioF();
761 
762             i_width = qRound( (qreal) i_width / factor );
763             i_height = qRound( (qreal) i_height / factor );
764 #endif
765 
766             videoWidget->setSize( i_width, i_height );
767         }
768     }
769 }
770 
771 /* Asynchronous call from the WindowClose function */
releaseVideo(void)772 void MainInterface::releaseVideo( void )
773 {
774     emit askReleaseVideo();
775 }
776 
777 /* Function that is CONNECTED to the previous emit */
releaseVideoSlot(void)778 void MainInterface::releaseVideoSlot( void )
779 {
780     /* This function is called when the embedded video window is destroyed,
781      * or in the rare case that the embedded window is still here but the
782      * Qt interface exits. */
783     assert( videoWidget );
784     videoWidget->release();
785     setVideoOnTop( false );
786     setVideoFullScreen( false );
787     hideResumePanel();
788 
789     if( stackCentralW->currentWidget() == videoWidget )
790         restoreStackOldWidget( true );
791     else if( playlistWidget &&
792              playlistWidget->artContainer->currentWidget() == videoWidget )
793     {
794         playlistWidget->artContainer->setCurrentIndex( 0 );
795         stackCentralW->addWidget( videoWidget );
796     }
797 
798     /* We don't want to have a blank video to popup */
799     stackCentralOldWidget = bgWidget;
800 }
801 
802 // The provided size is in physical pixels, coming from the core.
setVideoSize(unsigned int w,unsigned int h)803 void MainInterface::setVideoSize( unsigned int w, unsigned int h )
804 {
805     if (!isFullScreen() && !isMaximized() )
806     {
807         /* Resize video widget to video size, or keep it at the same
808          * size. Call setSize() either way so that vout_window_ReportSize
809          * will always get called.
810          * If the video size is too large for the screen, resize it
811          * to the screen size.
812          */
813         if (b_autoresize)
814         {
815             QRect screen = QApplication::desktop()->availableGeometry();
816 #if HAS_QT56
817             float factor = videoWidget->devicePixelRatioF();
818 #else
819             float factor = 1.0f;
820 #endif
821             if( (float)h / factor > screen.height() )
822             {
823                 w = screen.width();
824                 h = screen.height();
825                 if( !b_minimalView )
826                 {
827                     if( menuBar()->isVisible() )
828                         h -= menuBar()->height();
829                     if( controls->isVisible() )
830                         h -= controls->height();
831                     if( statusBar()->isVisible() )
832                         h -= statusBar()->height();
833                     if( inputC->isVisible() )
834                         h -= inputC->height();
835                 }
836                 h -= style()->pixelMetric(QStyle::PM_TitleBarHeight);
837                 h -= style()->pixelMetric(QStyle::PM_LayoutBottomMargin);
838                 h -= 2 * style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
839             }
840             else
841             {
842                 // Convert the size in logical pixels
843                 w = qRound( (float)w / factor );
844                 h = qRound( (float)h / factor );
845                 msg_Dbg( p_intf, "Logical video size: %ux%u", w, h );
846             }
847             videoWidget->setSize( w, h );
848         }
849         else
850             videoWidget->setSize( videoWidget->width(), videoWidget->height() );
851     }
852 }
853 
videoSizeChanged(int w,int h)854 void MainInterface::videoSizeChanged( int w, int h )
855 {
856     if( !playlistWidget || playlistWidget->artContainer->currentWidget() != videoWidget )
857         resizeStack( w, h );
858 }
859 
setVideoFullScreen(bool fs)860 void MainInterface::setVideoFullScreen( bool fs )
861 {
862     b_videoFullScreen = fs;
863     if( fs )
864     {
865         int numscreen = var_InheritInteger( p_intf, "qt-fullscreen-screennumber" );
866 
867         if ( numscreen >= 0 && numscreen < QApplication::desktop()->screenCount() )
868         {
869             if( fullscreenControls )
870                 fullscreenControls->setTargetScreen( numscreen );
871 
872             QRect screenres = QApplication::desktop()->screenGeometry( numscreen );
873             lastWinScreen = windowHandle()->screen();
874 #ifdef QT5_HAS_WAYLAND
875             if( !b_hasWayland )
876                 windowHandle()->setScreen(QGuiApplication::screens()[numscreen]);
877 #else
878             windowHandle()->setScreen(QGuiApplication::screens()[numscreen]);
879 #endif
880 
881             /* To be sure window is on proper-screen in xinerama */
882             if( !screenres.contains( pos() ) )
883             {
884                 lastWinPosition = pos();
885                 lastWinSize = size();
886                 msg_Dbg( p_intf, "Moving video to correct position");
887                 move( QPoint( screenres.x(), screenres.y() ) );
888             }
889         }
890 
891         if( playlistWidget != NULL && playlistWidget->artContainer->currentWidget() == videoWidget )
892             showTab( videoWidget );
893 
894         /* we won't be able to get its windowed sized once in fullscreen, so update it now */
895         stackWidgetsSizes[stackCentralW->currentWidget()] = stackCentralW->size();
896 
897         /* */
898         displayNormalView();
899         setInterfaceFullScreen( true );
900     }
901     else
902     {
903         setMinimalView( b_minimalView );
904         setInterfaceFullScreen( b_interfaceFullScreen );
905 #ifdef QT5_HAS_WAYLAND
906         if( lastWinScreen != NULL && !b_hasWayland )
907             windowHandle()->setScreen(lastWinScreen);
908 #else
909         if( lastWinScreen != NULL )
910             windowHandle()->setScreen(lastWinScreen);
911 #endif
912         if( lastWinPosition.isNull() == false )
913         {
914             move( lastWinPosition );
915             resizeWindow( lastWinSize.width(), lastWinSize.height() );
916             lastWinPosition = QPoint();
917             lastWinSize = QSize();
918         }
919 
920     }
921     videoWidget->sync();
922 }
923 
setHideMouse(bool hide)924 void MainInterface::setHideMouse( bool hide )
925 {
926     videoWidget->setCursor( hide ? Qt::BlankCursor : Qt::ArrowCursor );
927 }
928 
929 /* Slot to change the video always-on-top flag.
930  * Emit askVideoOnTop() to invoke this from other thread. */
setVideoOnTop(bool on_top)931 void MainInterface::setVideoOnTop( bool on_top )
932 {
933     //don't apply changes if user has already sets its interface on top
934     if ( b_interfaceOnTop )
935         return;
936 
937     Qt::WindowFlags oldflags = windowFlags(), newflags;
938 
939     if( on_top )
940         newflags = oldflags | Qt::WindowStaysOnTopHint;
941     else
942         newflags = oldflags & ~Qt::WindowStaysOnTopHint;
943     if( newflags != oldflags && !b_videoFullScreen )
944     {
945         setWindowFlags( newflags );
946         show(); /* necessary to apply window flags */
947     }
948 }
949 
setInterfaceAlwaysOnTop(bool on_top)950 void MainInterface::setInterfaceAlwaysOnTop( bool on_top )
951 {
952     b_interfaceOnTop = on_top;
953     Qt::WindowFlags oldflags = windowFlags(), newflags;
954 
955     if( on_top )
956         newflags = oldflags | Qt::WindowStaysOnTopHint;
957     else
958         newflags = oldflags & ~Qt::WindowStaysOnTopHint;
959     if( newflags != oldflags && !b_videoFullScreen )
960     {
961         setWindowFlags( newflags );
962         show(); /* necessary to apply window flags */
963     }
964 }
965 
966 /* Asynchronous call from WindowControl function */
controlVideo(int i_query,va_list args)967 int MainInterface::controlVideo( int i_query, va_list args )
968 {
969     switch( i_query )
970     {
971     case VOUT_WINDOW_SET_SIZE:
972     {
973         unsigned int i_width  = va_arg( args, unsigned int );
974         unsigned int i_height = va_arg( args, unsigned int );
975 
976         emit askVideoToResize( i_width, i_height );
977         return VLC_SUCCESS;
978     }
979     case VOUT_WINDOW_SET_STATE:
980     {
981         unsigned i_arg = va_arg( args, unsigned );
982         unsigned on_top = i_arg & VOUT_WINDOW_STATE_ABOVE;
983 
984         emit askVideoOnTop( on_top != 0 );
985         return VLC_SUCCESS;
986     }
987     case VOUT_WINDOW_SET_FULLSCREEN:
988     {
989         bool b_fs = va_arg( args, int );
990 
991         emit askVideoSetFullScreen( b_fs );
992         return VLC_SUCCESS;
993     }
994     case VOUT_WINDOW_HIDE_MOUSE:
995     {
996         bool b_hide = va_arg( args, int );
997 
998         emit askHideMouse( b_hide );
999         return VLC_SUCCESS;
1000     }
1001     default:
1002         msg_Warn( p_intf, "unsupported control query" );
1003         return VLC_EGENERIC;
1004     }
1005 }
1006 
1007 /*****************************************************************************
1008  * Playlist, Visualisation and Menus handling
1009  *****************************************************************************/
1010 /**
1011  * Toggle the playlist widget or dialog
1012  **/
createPlaylist()1013 void MainInterface::createPlaylist()
1014 {
1015     PlaylistDialog *dialog = PlaylistDialog::getInstance( p_intf );
1016 
1017     if( b_plDocked )
1018     {
1019         playlistWidget = dialog->exportPlaylistWidget();
1020         stackCentralW->addWidget( playlistWidget );
1021         stackWidgetsSizes[playlistWidget] = settings->value( "playlistSize", QSize( 600, 300 ) ).toSize();
1022     }
1023     CONNECT( dialog, visibilityChanged(bool), this, setPlaylistVisibility(bool) );
1024 }
1025 
togglePlaylist()1026 void MainInterface::togglePlaylist()
1027 {
1028     if( !playlistWidget ) createPlaylist();
1029 
1030     PlaylistDialog *dialog = PlaylistDialog::getInstance( p_intf );
1031     if( b_plDocked )
1032     {
1033         if ( dialog->hasPlaylistWidget() )
1034             playlistWidget = dialog->exportPlaylistWidget();
1035         /* Playlist is not visible, show it */
1036         if( stackCentralW->currentWidget() != playlistWidget )
1037         {
1038             if( stackCentralW->indexOf( playlistWidget ) == -1 )
1039                 stackCentralW->addWidget( playlistWidget );
1040             showTab( playlistWidget );
1041         }
1042         else /* Hide it! */
1043         {
1044             restoreStackOldWidget();
1045         }
1046         playlistVisible = ( stackCentralW->currentWidget() == playlistWidget );
1047     }
1048     else
1049     {
1050         playlistVisible = !playlistVisible;
1051         if ( ! dialog->hasPlaylistWidget() )
1052             dialog->importPlaylistWidget( playlistWidget );
1053         if ( playlistVisible )
1054             dialog->show();
1055         else
1056             dialog->hide();
1057     }
1058     debug();
1059 }
1060 
1061 const Qt::Key MainInterface::kc[10] =
1062 {
1063     Qt::Key_Up, Qt::Key_Up,
1064     Qt::Key_Down, Qt::Key_Down,
1065     Qt::Key_Left, Qt::Key_Right, Qt::Key_Left, Qt::Key_Right,
1066     Qt::Key_B, Qt::Key_A
1067 };
1068 
dockPlaylist(bool p_docked)1069 void MainInterface::dockPlaylist( bool p_docked )
1070 {
1071     if( b_plDocked == p_docked ) return;
1072     /* some extra check */
1073     if ( b_plDocked && !playlistWidget ) createPlaylist();
1074 
1075     b_plDocked = p_docked;
1076     PlaylistDialog *dialog = PlaylistDialog::getInstance( p_intf );
1077 
1078     if( !p_docked ) /* Previously docked */
1079     {
1080         playlistVisible = playlistWidget->isVisible();
1081 
1082         /* repositioning the videowidget __before__ exporting the
1083            playlistwidget into the playlist dialog avoids two unneeded
1084            calls to the server in the qt library to reparent the underlying
1085            native window back and forth.
1086            For Wayland, this is mandatory since reparenting is not implemented.
1087            For X11 or Windows, this is just an optimization. */
1088         if ( videoWidget && THEMIM->getIM()->hasVideo() )
1089             showTab(videoWidget);
1090         else
1091             showTab(bgWidget);
1092 
1093         /* playlistwidget exported into the playlist dialog */
1094         stackCentralW->removeWidget( playlistWidget );
1095         dialog->importPlaylistWidget( playlistWidget );
1096         if ( playlistVisible ) dialog->show();
1097     }
1098     else /* Previously undocked */
1099     {
1100         playlistVisible = dialog->isVisible() && !( videoWidget && THEMIM->getIM()->hasVideo() );
1101         dialog->hide();
1102         playlistWidget = dialog->exportPlaylistWidget();
1103         stackCentralW->addWidget( playlistWidget );
1104 
1105         /* If playlist is invisible don't show it */
1106         if( playlistVisible ) showTab( playlistWidget );
1107     }
1108 }
1109 
1110 /*
1111  * displayNormalView is the private function used by
1112  * the SLOT setVideoFullScreen to restore the menuBar
1113  * if minimal view is off
1114  */
displayNormalView()1115 void MainInterface::displayNormalView()
1116 {
1117     menuBar()->setVisible( false );
1118     controls->setVisible( false );
1119     statusBar()->setVisible( false );
1120     inputC->setVisible( false );
1121 }
1122 
1123 /*
1124  * setMinimalView is the private function used by
1125  * the SLOT toggleMinimalView
1126  */
setMinimalView(bool b_minimal)1127 void MainInterface::setMinimalView( bool b_minimal )
1128 {
1129     bool b_menuBarVisible = menuBar()->isVisible();
1130     bool b_controlsVisible = controls->isVisible();
1131     bool b_statusBarVisible = statusBar()->isVisible();
1132     bool b_inputCVisible = inputC->isVisible();
1133 
1134     if( !isFullScreen() && !isMaximized() && b_minimal && !b_isWindowTiled )
1135     {
1136         int i_heightChange = 0;
1137 
1138         if( b_menuBarVisible )
1139             i_heightChange += menuBar()->height();
1140         if( b_controlsVisible )
1141             i_heightChange += controls->height();
1142         if( b_statusBarVisible )
1143             i_heightChange += statusBar()->height();
1144         if( b_inputCVisible )
1145             i_heightChange += inputC->height();
1146 
1147         if( i_heightChange != 0 )
1148             resizeWindow( width(), height() - i_heightChange );
1149     }
1150 
1151     menuBar()->setVisible( !b_minimal );
1152     controls->setVisible( !b_minimal );
1153     statusBar()->setVisible( !b_minimal && b_statusbarVisible );
1154     inputC->setVisible( !b_minimal );
1155 
1156     if( !isFullScreen() && !isMaximized() && !b_minimal && !b_isWindowTiled )
1157     {
1158         int i_heightChange = 0;
1159 
1160         if( !b_menuBarVisible && menuBar()->isVisible() )
1161             i_heightChange += menuBar()->height();
1162         if( !b_controlsVisible && controls->isVisible() )
1163             i_heightChange += controls->height();
1164         if( !b_statusBarVisible && statusBar()->isVisible() )
1165             i_heightChange += statusBar()->height();
1166         if( !b_inputCVisible && inputC->isVisible() )
1167             i_heightChange += inputC->height();
1168 
1169         if( i_heightChange != 0 )
1170             resizeWindow( width(), height() + i_heightChange );
1171     }
1172 }
1173 
1174 /*
1175  * This public SLOT is used for moving to minimal View Mode
1176  *
1177  * If b_minimal is false, then we are normalView
1178  */
toggleMinimalView(bool b_minimal)1179 void MainInterface::toggleMinimalView( bool b_minimal )
1180 {
1181     if( !b_minimalView && b_autoresize ) /* Normal mode */
1182     {
1183         if( stackCentralW->currentWidget() == bgWidget )
1184         {
1185             if( stackCentralW->height() < 16 )
1186             {
1187                 resizeStack( stackCentralW->width(), 100 );
1188             }
1189         }
1190     }
1191     b_minimalView = b_minimal;
1192     if( !b_videoFullScreen )
1193     {
1194         setMinimalView( b_minimalView );
1195         computeMinimumSize();
1196     }
1197 
1198     emit minimalViewToggled( b_minimalView );
1199 }
1200 
1201 /* toggling advanced controls buttons */
toggleAdvancedButtons()1202 void MainInterface::toggleAdvancedButtons()
1203 {
1204     controls->toggleAdvanced();
1205 //    if( fullscreenControls ) fullscreenControls->toggleAdvanced();
1206 }
1207 
1208 /* Get the visibility status of the controls (hidden or not, advanced or not) */
getControlsVisibilityStatus()1209 int MainInterface::getControlsVisibilityStatus()
1210 {
1211     if( !controls ) return 0;
1212     return( (controls->isVisible() ? CONTROLS_VISIBLE : CONTROLS_HIDDEN )
1213             + CONTROLS_ADVANCED * controls->b_advancedVisible );
1214 }
1215 
getPlaylistView()1216 StandardPLPanel *MainInterface::getPlaylistView()
1217 {
1218     if( !playlistWidget ) return NULL;
1219     else return playlistWidget->mainView;
1220 }
1221 
setStatusBarVisibility(bool b_visible)1222 void MainInterface::setStatusBarVisibility( bool b_visible )
1223 {
1224     statusBar()->setVisible( b_visible );
1225     b_statusbarVisible = b_visible;
1226     if( controls ) controls->setGripVisible( !b_statusbarVisible );
1227 }
1228 
1229 
setPlaylistVisibility(bool b_visible)1230 void MainInterface::setPlaylistVisibility( bool b_visible )
1231 {
1232     if( isPlDocked() || THEDP->isDying() || (playlistWidget && playlistWidget->isMinimized() ) )
1233         return;
1234 
1235     playlistVisible = b_visible;
1236 }
1237 
1238 /************************************************************************
1239  * Other stuff
1240  ************************************************************************/
setName(const QString & name)1241 void MainInterface::setName( const QString& name )
1242 {
1243     input_name = name; /* store it for the QSystray use */
1244     /* Display it in the status bar, but also as a Tooltip in case it doesn't
1245        fit in the label */
1246     nameLabel->setText( name );
1247     nameLabel->setToolTip( name );
1248 }
1249 
1250 /**
1251  * Give the decorations of the Main Window a correct Name.
1252  * If nothing is given, set it to VLC...
1253  **/
setVLCWindowsTitle(const QString & aTitle)1254 void MainInterface::setVLCWindowsTitle( const QString& aTitle )
1255 {
1256     if( aTitle.isEmpty() )
1257     {
1258         setWindowTitle( qtr( "VLC media player" ) );
1259     }
1260     else
1261     {
1262         setWindowTitle( aTitle + " - " + qtr( "VLC media player" ) );
1263     }
1264 }
1265 
showCryptedLabel(bool b_show)1266 void MainInterface::showCryptedLabel( bool b_show )
1267 {
1268     if( cryptedLabel == NULL )
1269     {
1270         cryptedLabel = new QLabel;
1271         // The lock icon is not the right one for DRM protection/scrambled.
1272         //cryptedLabel->setPixmap( QPixmap( ":/lock.svg" ) );
1273         cryptedLabel->setText( "DRM" );
1274         statusBar()->addWidget( cryptedLabel );
1275     }
1276 
1277     cryptedLabel->setVisible( b_show );
1278 }
1279 
showBuffering(float f_cache)1280 void MainInterface::showBuffering( float f_cache )
1281 {
1282     QString amount = QString("Buffering: %1%").arg( (int)(100*f_cache) );
1283     statusBar()->showMessage( amount, 1000 );
1284 }
1285 
1286 /*****************************************************************************
1287  * Systray Icon and Systray Menu
1288  *****************************************************************************/
1289 /**
1290  * Create a SystemTray icon and a menu that would go with it.
1291  * Connects to a click handler on the icon.
1292  **/
createSystray()1293 void MainInterface::createSystray()
1294 {
1295     QIcon iconVLC;
1296     if( QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY && var_InheritBool( p_intf, "qt-icon-change" ) )
1297         iconVLC = QIcon::fromTheme( "vlc-xmas", QIcon( ":/logo/vlc128-xmas.png" ) );
1298     else
1299         iconVLC = QIcon::fromTheme( "vlc", QIcon( ":/logo/vlc256.png" ) );
1300     sysTray = new QSystemTrayIcon( iconVLC, this );
1301     sysTray->setToolTip( qtr( "VLC media player" ));
1302 
1303     systrayMenu = new QMenu( qtr( "VLC media player" ), this );
1304     systrayMenu->setIcon( iconVLC );
1305 
1306     VLCMenuBar::updateSystrayMenu( this, p_intf, true );
1307     sysTray->show();
1308 
1309     CONNECT( sysTray, activated( QSystemTrayIcon::ActivationReason ),
1310              this, handleSystrayClick( QSystemTrayIcon::ActivationReason ) );
1311 
1312     /* Connects on nameChanged() */
1313     CONNECT( THEMIM->getIM(), nameChanged( const QString& ),
1314              this, updateSystrayTooltipName( const QString& ) );
1315     /* Connect PLAY_STATUS on the systray */
1316     CONNECT( THEMIM->getIM(), playingStatusChanged( int ),
1317              this, updateSystrayTooltipStatus( int ) );
1318 }
1319 
toggleUpdateSystrayMenuWhenVisible()1320 void MainInterface::toggleUpdateSystrayMenuWhenVisible()
1321 {
1322     hide();
1323 }
1324 
resizeWindow(int w,int h)1325 void MainInterface::resizeWindow(int w, int h)
1326 {
1327 #if ! HAS_QT510 && defined(QT5_HAS_X11)
1328     if( QX11Info::isPlatformX11() )
1329     {
1330 #if HAS_QT56
1331         qreal dpr = devicePixelRatioF();
1332 #else
1333         qreal dpr = devicePixelRatio();
1334 #endif
1335         QSize size(w, h);
1336         size = size.boundedTo(maximumSize()).expandedTo(minimumSize());
1337         /* X11 window managers are not required to accept geometry changes on
1338          * the top-level window.  Unfortunately, Qt < 5.10 assumes that the
1339          * change will succeed, and resizes all sub-windows unconditionally.
1340          * By calling XMoveResizeWindow directly, Qt will not see our change
1341          * request until the ConfigureNotify event on success
1342          * and not at all if it is rejected. */
1343         XResizeWindow( QX11Info::display(), winId(),
1344                        (unsigned int)size.width() * dpr, (unsigned int)size.height() * dpr);
1345         return;
1346     }
1347 #endif
1348     resize(w, h);
1349 }
1350 
1351 /**
1352  * Updates the Systray Icon's menu and toggle the main interface
1353  */
toggleUpdateSystrayMenu()1354 void MainInterface::toggleUpdateSystrayMenu()
1355 {
1356     /* If hidden, show it */
1357     if( isHidden() )
1358     {
1359         show();
1360         activateWindow();
1361     }
1362     else if( isMinimized() )
1363     {
1364         /* Minimized */
1365         showNormal();
1366         activateWindow();
1367     }
1368     else
1369     {
1370         /* Visible (possibly under other windows) */
1371         toggleUpdateSystrayMenuWhenVisible();
1372     }
1373     if( sysTray )
1374         VLCMenuBar::updateSystrayMenu( this, p_intf );
1375 }
1376 
1377 /* First Item of the systray menu */
showUpdateSystrayMenu()1378 void MainInterface::showUpdateSystrayMenu()
1379 {
1380     if( isHidden() )
1381         show();
1382     if( isMinimized() )
1383         showNormal();
1384     activateWindow();
1385 
1386     VLCMenuBar::updateSystrayMenu( this, p_intf );
1387 }
1388 
1389 /* First Item of the systray menu */
hideUpdateSystrayMenu()1390 void MainInterface::hideUpdateSystrayMenu()
1391 {
1392     hide();
1393     VLCMenuBar::updateSystrayMenu( this, p_intf );
1394 }
1395 
1396 /* Click on systray Icon */
handleSystrayClick(QSystemTrayIcon::ActivationReason reason)1397 void MainInterface::handleSystrayClick(
1398                                     QSystemTrayIcon::ActivationReason reason )
1399 {
1400     switch( reason )
1401     {
1402         case QSystemTrayIcon::Trigger:
1403         case QSystemTrayIcon::DoubleClick:
1404 #ifdef Q_OS_MAC
1405             VLCMenuBar::updateSystrayMenu( this, p_intf );
1406 #else
1407             toggleUpdateSystrayMenu();
1408 #endif
1409             break;
1410         case QSystemTrayIcon::MiddleClick:
1411             sysTray->showMessage( qtr( "VLC media player" ),
1412                     qtr( "Control menu for the player" ),
1413                     QSystemTrayIcon::Information, 3000 );
1414             break;
1415         default:
1416             break;
1417     }
1418 }
1419 
1420 /**
1421  * Updates the name of the systray Icon tooltip.
1422  * Doesn't check if the systray exists, check before you call it.
1423  **/
updateSystrayTooltipName(const QString & name)1424 void MainInterface::updateSystrayTooltipName( const QString& name )
1425 {
1426     if( name.isEmpty() )
1427     {
1428         sysTray->setToolTip( qtr( "VLC media player" ) );
1429     }
1430     else
1431     {
1432         sysTray->setToolTip( name );
1433         if( ( i_notificationSetting == NOTIFICATION_ALWAYS ) ||
1434             ( i_notificationSetting == NOTIFICATION_MINIMIZED && (isMinimized() || isHidden()) ) )
1435         {
1436             sysTray->showMessage( qtr( "VLC media player" ), name,
1437                     QSystemTrayIcon::NoIcon, 3000 );
1438         }
1439     }
1440 
1441     VLCMenuBar::updateSystrayMenu( this, p_intf );
1442 }
1443 
1444 /**
1445  * Updates the status of the systray Icon tooltip.
1446  * Doesn't check if the systray exists, check before you call it.
1447  **/
updateSystrayTooltipStatus(int i_status)1448 void MainInterface::updateSystrayTooltipStatus( int i_status )
1449 {
1450     switch( i_status )
1451     {
1452     case PLAYING_S:
1453         sysTray->setToolTip( input_name );
1454         break;
1455     case PAUSE_S:
1456         sysTray->setToolTip( input_name + " - " + qtr( "Paused") );
1457         break;
1458     default:
1459         sysTray->setToolTip( qtr( "VLC media player" ) );
1460         break;
1461     }
1462     VLCMenuBar::updateSystrayMenu( this, p_intf );
1463 }
1464 
changeEvent(QEvent * event)1465 void MainInterface::changeEvent(QEvent *event)
1466 {
1467     if( event->type() == QEvent::WindowStateChange )
1468     {
1469         QWindowStateChangeEvent *windowStateChangeEvent = static_cast<QWindowStateChangeEvent*>(event);
1470         Qt::WindowStates newState = windowState();
1471         Qt::WindowStates oldState = windowStateChangeEvent->oldState();
1472 
1473         /* b_maximizedView stores if the window was maximized before entering fullscreen.
1474          * It is set when entering maximized mode, unset when leaving it to normal mode.
1475          * Upon leaving full screen, if b_maximizedView is set,
1476          * the window should be maximized again. */
1477         if( newState & Qt::WindowMaximized &&
1478             !( oldState & Qt::WindowMaximized ) )
1479             b_maximizedView = true;
1480 
1481         if( !( newState & Qt::WindowMaximized ) &&
1482             oldState & Qt::WindowMaximized &&
1483             !b_videoFullScreen )
1484             b_maximizedView = false;
1485 
1486         if( !( newState & Qt::WindowFullScreen ) &&
1487             oldState & Qt::WindowFullScreen &&
1488             b_maximizedView )
1489         {
1490             showMaximized();
1491             return;
1492         }
1493 
1494         if( newState & Qt::WindowMinimized )
1495         {
1496             b_hasPausedWhenMinimized = false;
1497 
1498             if( THEMIM->getIM()->playingStatus() == PLAYING_S &&
1499                 THEMIM->getIM()->hasVideo() && !THEMIM->getIM()->hasVisualisation() &&
1500                 b_pauseOnMinimize )
1501             {
1502                 b_hasPausedWhenMinimized = true;
1503                 THEMIM->pause();
1504             }
1505         }
1506         else if( oldState & Qt::WindowMinimized && !( newState & Qt::WindowMinimized ) )
1507         {
1508             if( b_hasPausedWhenMinimized )
1509             {
1510                 THEMIM->play();
1511             }
1512         }
1513     }
1514 
1515     QWidget::changeEvent(event);
1516 }
1517 
1518 /************************************************************************
1519  * D&D Events
1520  ************************************************************************/
dropEvent(QDropEvent * event)1521 void MainInterface::dropEvent(QDropEvent *event)
1522 {
1523     dropEventPlay( event, true );
1524 }
1525 
1526 /**
1527  * dropEventPlay
1528  *
1529  * Event called if something is dropped onto a VLC window
1530  * \param event the event in question
1531  * \param b_play whether to play the file immediately
1532  * \param b_playlist true to add to playlist, false to add to media library
1533  * \return nothing
1534  */
dropEventPlay(QDropEvent * event,bool b_play,bool b_playlist)1535 void MainInterface::dropEventPlay( QDropEvent *event, bool b_play, bool b_playlist )
1536 {
1537     if( event->possibleActions() & ( Qt::CopyAction | Qt::MoveAction | Qt::LinkAction ) )
1538        event->setDropAction( Qt::CopyAction );
1539     else
1540         return;
1541 
1542     const QMimeData *mimeData = event->mimeData();
1543 
1544     /* D&D of a subtitles file, add it on the fly */
1545     if( mimeData->urls().count() == 1 && THEMIM->getIM()->hasInput() )
1546     {
1547         if( !input_AddSlave( THEMIM->getInput(), SLAVE_TYPE_SPU,
1548                  qtu( mimeData->urls()[0].toString() ), true, true, true ) )
1549         {
1550             event->accept();
1551             return;
1552         }
1553     }
1554 
1555     bool first = b_play;
1556     foreach( const QUrl &url, mimeData->urls() )
1557     {
1558         if( url.isValid() )
1559         {
1560             QString mrl = toURI( url.toEncoded().constData() );
1561 #ifdef _WIN32
1562             QFileInfo info( url.toLocalFile() );
1563             if( info.exists() && info.isSymLink() )
1564             {
1565                 QString target = info.symLinkTarget();
1566                 QUrl url;
1567                 if( QFile::exists( target ) )
1568                 {
1569                     url = QUrl::fromLocalFile( target );
1570                 }
1571                 else
1572                 {
1573                     url.setUrl( target );
1574                 }
1575                 mrl = toURI( url.toEncoded().constData() );
1576             }
1577 #endif
1578             if( mrl.length() > 0 )
1579             {
1580                 Open::openMRL( p_intf, mrl, first, b_playlist );
1581                 first = false;
1582             }
1583         }
1584     }
1585 
1586     /* Browsers give content as text if you dnd the addressbar,
1587        so check if mimedata has valid url in text and use it
1588        if we didn't get any normal Urls()*/
1589     if( !mimeData->hasUrls() && mimeData->hasText() &&
1590         QUrl(mimeData->text()).isValid() )
1591     {
1592         QString mrl = toURI( mimeData->text() );
1593         Open::openMRL( p_intf, mrl, first, b_playlist );
1594     }
1595     event->accept();
1596 }
dragEnterEvent(QDragEnterEvent * event)1597 void MainInterface::dragEnterEvent(QDragEnterEvent *event)
1598 {
1599      event->acceptProposedAction();
1600 }
dragMoveEvent(QDragMoveEvent * event)1601 void MainInterface::dragMoveEvent(QDragMoveEvent *event)
1602 {
1603      event->acceptProposedAction();
1604 }
dragLeaveEvent(QDragLeaveEvent * event)1605 void MainInterface::dragLeaveEvent(QDragLeaveEvent *event)
1606 {
1607      event->accept();
1608 }
1609 
1610 /************************************************************************
1611  * Events stuff
1612  ************************************************************************/
keyPressEvent(QKeyEvent * e)1613 void MainInterface::keyPressEvent( QKeyEvent *e )
1614 {
1615     handleKeyPress( e );
1616 
1617     /* easter eggs sequence handling */
1618     if ( e->key() == kc[ i_kc_offset ] )
1619         i_kc_offset++;
1620     else
1621         i_kc_offset = 0;
1622 
1623     if ( i_kc_offset == (sizeof( kc ) / sizeof( Qt::Key )) )
1624     {
1625         i_kc_offset = 0;
1626         emit kc_pressed();
1627     }
1628 }
1629 
handleKeyPress(QKeyEvent * e)1630 void MainInterface::handleKeyPress( QKeyEvent *e )
1631 {
1632     if( ( ( e->modifiers() & Qt::ControlModifier ) && ( e->key() == Qt::Key_H ) ) ||
1633         ( b_minimalView && !b_videoFullScreen && e->key() == Qt::Key_Escape ) )
1634     {
1635         toggleMinimalView( !b_minimalView );
1636         e->accept();
1637     }
1638     else if( ( e->modifiers() & Qt::ControlModifier ) && ( e->key() == Qt::Key_K ) &&
1639         playlistWidget )
1640     {
1641         playlistWidget->setSearchFieldFocus();
1642         e->accept();
1643     }
1644 
1645     int i_vlck = qtEventToVLCKey( e );
1646     if( i_vlck > 0 )
1647     {
1648         var_SetInteger( p_intf->obj.libvlc, "key-pressed", i_vlck );
1649         e->accept();
1650     }
1651     else
1652         e->ignore();
1653 }
1654 
wheelEvent(QWheelEvent * e)1655 void MainInterface::wheelEvent( QWheelEvent *e )
1656 {
1657     int i_vlckey = qtWheelEventToVLCKey( e );
1658     var_SetInteger( p_intf->obj.libvlc, "key-pressed", i_vlckey );
1659     e->accept();
1660 }
1661 
closeEvent(QCloseEvent * e)1662 void MainInterface::closeEvent( QCloseEvent *e )
1663 {
1664 //  hide();
1665     if ( b_minimalView )
1666         setMinimalView( false );
1667     if( videoWidget )
1668         releaseVideoSlot();
1669     emit askToQuit(); /* ask THEDP to quit, so we have a unique method */
1670     /* Accept session quit. Otherwise we break the desktop mamager. */
1671     e->accept();
1672 }
1673 
eventFilter(QObject * obj,QEvent * event)1674 bool MainInterface::eventFilter( QObject *obj, QEvent *event )
1675 {
1676     if ( event->type() == MainInterface::ToolbarsNeedRebuild ) {
1677         event->accept();
1678         recreateToolbars();
1679         return true;
1680     } else {
1681         return QObject::eventFilter( obj, event );
1682     }
1683 }
1684 
toolBarConfUpdated()1685 void MainInterface::toolBarConfUpdated()
1686 {
1687     QApplication::postEvent( this, new QEvent( MainInterface::ToolbarsNeedRebuild ) );
1688 }
1689 
setInterfaceFullScreen(bool fs)1690 void MainInterface::setInterfaceFullScreen( bool fs )
1691 {
1692     if( fs )
1693         setWindowState( windowState() | Qt::WindowFullScreen );
1694     else
1695         setWindowState( windowState() & ~Qt::WindowFullScreen );
1696 }
toggleInterfaceFullScreen()1697 void MainInterface::toggleInterfaceFullScreen()
1698 {
1699     b_interfaceFullScreen = !b_interfaceFullScreen;
1700     if( !b_videoFullScreen )
1701         setInterfaceFullScreen( b_interfaceFullScreen );
1702     emit fullscreenInterfaceToggled( b_interfaceFullScreen );
1703 }
1704 
emitBoss()1705 void MainInterface::emitBoss()
1706 {
1707     emit askBoss();
1708 }
setBoss()1709 void MainInterface::setBoss()
1710 {
1711     THEMIM->pause();
1712     if( sysTray )
1713     {
1714         hide();
1715     }
1716     else
1717     {
1718         showMinimized();
1719     }
1720 }
1721 
emitRaise()1722 void MainInterface::emitRaise()
1723 {
1724     emit askRaise();
1725 }
setRaise()1726 void MainInterface::setRaise()
1727 {
1728     activateWindow();
1729     raise();
1730 }
1731 
voutReleaseMouseEvents()1732 void MainInterface::voutReleaseMouseEvents()
1733 {
1734     if (videoWidget)
1735     {
1736         QPoint pos = QCursor::pos();
1737         QPoint localpos = videoWidget->mapFromGlobal(pos);
1738         int buttons = QApplication::mouseButtons();
1739         int i_button = 1;
1740         while (buttons != 0)
1741         {
1742             if ( (buttons & 1) != 0 )
1743             {
1744                 QMouseEvent new_e( QEvent::MouseButtonRelease, localpos,
1745                                    (Qt::MouseButton)i_button, (Qt::MouseButton)i_button, Qt::NoModifier );
1746                 QApplication::sendEvent(videoWidget, &new_e);
1747             }
1748             buttons >>= 1;
1749             i_button <<= 1;
1750         }
1751 
1752     }
1753 }
1754 
1755 /*****************************************************************************
1756  * PopupMenuCB: callback triggered by the intf-popupmenu playlist variable.
1757  *  We don't show the menu directly here because we don't want the
1758  *  caller to block for a too long time.
1759  *****************************************************************************/
PopupMenuCB(vlc_object_t *,const char *,vlc_value_t,vlc_value_t new_val,void * param)1760 static int PopupMenuCB( vlc_object_t *, const char *,
1761                         vlc_value_t, vlc_value_t new_val, void *param )
1762 {
1763     intf_thread_t *p_intf = (intf_thread_t *)param;
1764 
1765     if( p_intf->pf_show_dialog )
1766     {
1767         p_intf->pf_show_dialog( p_intf, INTF_DIALOG_POPUPMENU,
1768                                 new_val.b_bool, NULL );
1769     }
1770 
1771     return VLC_SUCCESS;
1772 }
1773 
1774 /*****************************************************************************
1775  * IntfShowCB: callback triggered by the intf-toggle-fscontrol libvlc variable.
1776  *****************************************************************************/
IntfShowCB(vlc_object_t *,const char *,vlc_value_t,vlc_value_t,void * param)1777 static int IntfShowCB( vlc_object_t *, const char *,
1778                        vlc_value_t, vlc_value_t, void *param )
1779 {
1780     intf_thread_t *p_intf = (intf_thread_t *)param;
1781     p_intf->p_sys->p_mi->toggleFSC();
1782 
1783     /* Show event */
1784      return VLC_SUCCESS;
1785 }
1786 
1787 /*****************************************************************************
1788  * IntfRaiseMainCB: callback triggered by the intf-show-main libvlc variable.
1789  *****************************************************************************/
IntfRaiseMainCB(vlc_object_t *,const char *,vlc_value_t,vlc_value_t,void * param)1790 static int IntfRaiseMainCB( vlc_object_t *, const char *,
1791                             vlc_value_t, vlc_value_t, void *param )
1792 {
1793     intf_thread_t *p_intf = (intf_thread_t *)param;
1794     p_intf->p_sys->p_mi->emitRaise();
1795 
1796     return VLC_SUCCESS;
1797 }
1798 
1799 /*****************************************************************************
1800  * IntfBossCB: callback triggered by the intf-boss libvlc variable.
1801  *****************************************************************************/
IntfBossCB(vlc_object_t *,const char *,vlc_value_t,vlc_value_t,void * param)1802 static int IntfBossCB( vlc_object_t *, const char *,
1803                        vlc_value_t, vlc_value_t, void *param )
1804 {
1805     intf_thread_t *p_intf = (intf_thread_t *)param;
1806     p_intf->p_sys->p_mi->emitBoss();
1807 
1808     return VLC_SUCCESS;
1809 }
1810