You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1171 lines
35 KiB
1171 lines
35 KiB
/*****************************************************************************
|
|
* main_interface.cpp : Main interface
|
|
****************************************************************************
|
|
* Copyright (C) 2006-2011 VideoLAN and AUTHORS
|
|
*
|
|
* Authors: Clément Stenac <zorglub@videolan.org>
|
|
* Jean-Baptiste Kempf <jb@videolan.org>
|
|
* Ilkka Ollakka <ileoo@videolan.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "qt.hpp"
|
|
|
|
#include "mainctx.hpp"
|
|
#include "mainctx_submodels.hpp"
|
|
#include "medialibrary/mlhelper.hpp"
|
|
|
|
#include "compositor.hpp"
|
|
#include "util/renderer_manager.hpp"
|
|
#include "util/csdbuttonmodel.hpp"
|
|
|
|
#include "util/vlchotkeyconverter.hpp"
|
|
#include "util/qt_dirs.hpp" // toNativeSeparators
|
|
|
|
#include "util/color_scheme_model.hpp"
|
|
|
|
#include "widgets/native/interface_widgets.hpp" // bgWidget, videoWidget
|
|
|
|
#include "playlist/playlist_controller.hpp"
|
|
#include "player/player_controller.hpp"
|
|
|
|
#include "dialogs/dialogs_provider.hpp"
|
|
#include "dialogs/systray/systray.hpp"
|
|
|
|
#include "videosurface.hpp"
|
|
|
|
#include "menus/menus.hpp" // Menu creation
|
|
|
|
#include "dialogs/toolbar/controlbar_profile_model.hpp"
|
|
#include "dialogs/help/help.hpp"
|
|
|
|
#include <QKeyEvent>
|
|
|
|
#include <QUrl>
|
|
#include <QDate>
|
|
#include <QMimeData>
|
|
#include <QClipboard>
|
|
#include <QInputDialog>
|
|
|
|
#include <QQmlProperty>
|
|
|
|
#include <QWindow>
|
|
#include <QScreen>
|
|
|
|
#include <QOperatingSystemVersion>
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
|
#include <QStyleHints>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#include <QFileInfo>
|
|
#endif
|
|
|
|
#include <vlc_interface.h>
|
|
#include <vlc_preparser.h>
|
|
|
|
#define VLC_REFERENCE_SCALE_FACTOR 96.
|
|
|
|
using namespace vlc::playlist;
|
|
|
|
// #define DEBUG_INTF
|
|
|
|
/* Callback prototypes */
|
|
static int PopupMenuCB( vlc_object_t *p_this, const char *psz_variable,
|
|
vlc_value_t old_val, vlc_value_t new_val, void *param );
|
|
static int IntfShowCB( vlc_object_t *p_this, const char *psz_variable,
|
|
vlc_value_t old_val, vlc_value_t new_val, void *param );
|
|
static int IntfBossCB( vlc_object_t *p_this, const char *psz_variable,
|
|
vlc_value_t old_val, vlc_value_t new_val, void *param );
|
|
static int IntfRaiseMainCB( vlc_object_t *p_this, const char *psz_variable,
|
|
vlc_value_t old_val, vlc_value_t new_val,
|
|
void *param );
|
|
|
|
const QEvent::Type MainCtx::ToolbarsNeedRebuild =
|
|
(QEvent::Type)QEvent::registerEventType();
|
|
|
|
namespace
|
|
{
|
|
|
|
template <typename T>
|
|
T loadVLCOption(vlc_object_t *obj, const char *name);
|
|
|
|
template <>
|
|
int loadVLCOption<int>(vlc_object_t *obj, const char *name)
|
|
{
|
|
return var_InheritInteger(obj, name);
|
|
}
|
|
|
|
template <>
|
|
float loadVLCOption<float>(vlc_object_t *obj, const char *name)
|
|
{
|
|
return var_InheritFloat(obj, name);
|
|
}
|
|
|
|
template <>
|
|
bool loadVLCOption<bool>(vlc_object_t *obj, const char *name)
|
|
{
|
|
return var_InheritBool(obj, name);
|
|
}
|
|
|
|
}
|
|
|
|
MainCtx::MainCtx(qt_intf_t *_p_intf)
|
|
: p_intf(_p_intf)
|
|
, m_csdButtonModel {std::make_unique<CSDButtonModel>(this, this)}
|
|
{
|
|
/**
|
|
* Configuration and settings
|
|
* Pre-building of interface
|
|
**/
|
|
|
|
settings = getSettings();
|
|
m_colorScheme = new ColorSchemeModel(this);
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
|
connect(m_colorScheme, &ColorSchemeModel::currentChanged, qGuiApp, [colorScheme = m_colorScheme]() {
|
|
QStyleHints *const styleHints = qGuiApp->styleHints();
|
|
if (unlikely(!styleHints))
|
|
return;
|
|
|
|
Qt::ColorScheme scheme;
|
|
switch (colorScheme->currentScheme())
|
|
{
|
|
case ColorSchemeModel::ColorScheme::Day:
|
|
scheme = Qt::ColorScheme::Light;
|
|
break;
|
|
case ColorSchemeModel::ColorScheme::Night:
|
|
scheme = Qt::ColorScheme::Dark;
|
|
break;
|
|
case ColorSchemeModel::ColorScheme::System:
|
|
default:
|
|
styleHints->unsetColorScheme();
|
|
return;
|
|
}
|
|
|
|
styleHints->setColorScheme(scheme);
|
|
});
|
|
#endif
|
|
|
|
m_sort = new SortCtx(this);
|
|
m_search = new SearchCtx(this);
|
|
|
|
// getOSInfo();
|
|
QOperatingSystemVersion currentOS = QOperatingSystemVersion::current();
|
|
switch (currentOS.type()) {
|
|
case QOperatingSystemVersion::OSType::Windows:
|
|
m_osName = Windows;
|
|
break;
|
|
|
|
default:
|
|
m_osName = Unknown;
|
|
break;
|
|
}
|
|
m_osVersion = (currentOS.majorVersion());
|
|
|
|
loadPrefs(false);
|
|
loadFromSettingsImpl(false);
|
|
|
|
/* Get the available interfaces */
|
|
m_extraInterfaces = new VLCVarChoiceModel(VLC_OBJECT(p_intf->intf), "intf-add", this);
|
|
|
|
vlc_medialibrary_t* ml = vlc_ml_instance_get( p_intf );
|
|
b_hasMedialibrary = (ml != NULL);
|
|
if (b_hasMedialibrary) {
|
|
m_medialib = new MediaLib(p_intf, p_intf->p_mainPlaylistController);
|
|
}
|
|
|
|
/* Controlbar Profile Model Creation */
|
|
m_controlbarProfileModel = new ControlbarProfileModel(p_intf->mainSettings, this);
|
|
|
|
m_dialogFilepath = getSettings()->value( "filedialog-path", QVLCUserDir( VLC_HOME_DIR ) ).toString();
|
|
|
|
QString platformName = QGuiApplication::platformName();
|
|
|
|
b_hasWayland = platformName.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive);
|
|
|
|
/*********************************
|
|
* Create the Systray Management *
|
|
*********************************/
|
|
//postpone systray initialisation to speedup starting time
|
|
QMetaObject::invokeMethod(this, &MainCtx::initSystray, Qt::QueuedConnection);
|
|
|
|
/*************************************************************
|
|
* Connect the input manager to the GUI elements it manages *
|
|
* Beware initSystray did some connects on input manager too *
|
|
*************************************************************/
|
|
/**
|
|
* Connects on nameChanged()
|
|
* Those connects are different because options can impeach them to trigger.
|
|
**/
|
|
/* Main Interface statusbar */
|
|
/* and title of the Main Interface*/
|
|
connect( THEMIM, &PlayerController::inputChanged, this, &MainCtx::onInputChanged );
|
|
|
|
/* END CONNECTS ON IM */
|
|
|
|
/* VideoWidget connects for asynchronous calls */
|
|
connect( this, &MainCtx::askToQuit, THEDP, &DialogsProvider::quit, Qt::QueuedConnection );
|
|
|
|
/** END of CONNECTS**/
|
|
|
|
|
|
/************
|
|
* Callbacks
|
|
************/
|
|
libvlc_int_t* libvlc = vlc_object_instance(p_intf);
|
|
var_AddCallback( libvlc, "intf-toggle-fscontrol", IntfShowCB, p_intf );
|
|
var_AddCallback( libvlc, "intf-boss", IntfBossCB, p_intf );
|
|
var_AddCallback( libvlc, "intf-show", IntfRaiseMainCB, p_intf );
|
|
|
|
/* Register callback for the intf-popupmenu variable */
|
|
var_AddCallback( libvlc, "intf-popupmenu", PopupMenuCB, p_intf );
|
|
|
|
if( config_GetInt("qt-privacy-ask") )
|
|
{
|
|
//postpone dialog call, as composition might not be ready yet
|
|
QMetaObject::invokeMethod(this, [](){
|
|
THEDP->firstRunDialog();
|
|
}, Qt::QueuedConnection);
|
|
}
|
|
else if (m_medialib)
|
|
{
|
|
QMetaObject::invokeMethod(m_medialib, &MediaLib::reload, Qt::QueuedConnection);
|
|
}
|
|
|
|
const struct vlc_preparser_cfg cfg = []{
|
|
struct vlc_preparser_cfg cfg{};
|
|
cfg.types = VLC_PREPARSER_TYPE_PARSE;
|
|
cfg.max_parser_threads = 1;
|
|
cfg.timeout = 0;
|
|
return cfg;
|
|
}();
|
|
m_network_preparser = vlc_preparser_New(VLC_OBJECT(libvlc), &cfg);
|
|
|
|
#ifdef UPDATE_CHECK
|
|
/* Checking for VLC updates */
|
|
if( var_InheritBool( p_intf, "qt-updates-notif" ) &&
|
|
!var_InheritBool( p_intf, "qt-privacy-ask" ) )
|
|
{
|
|
int interval = var_InheritInteger( p_intf, "qt-updates-days" );
|
|
if( QDate::currentDate() >
|
|
getSettings()->value( "updatedate" ).toDate().addDays( interval ) )
|
|
{
|
|
/* check for update at startup */
|
|
m_updateModel = std::make_unique<UpdateModel>(p_intf);
|
|
connect(m_updateModel.get(), &UpdateModel::updateStatusChanged, this, [this](){
|
|
switch (m_updateModel->updateStatus())
|
|
{
|
|
case UpdateModel::Checking:
|
|
case UpdateModel::Unchecked:
|
|
break;
|
|
case UpdateModel::NeedUpdate:
|
|
qWarning() << "Need Udpate";
|
|
THEDP->updateDialog();
|
|
[[fallthrough]];
|
|
case UpdateModel::UpToDate:
|
|
case UpdateModel::CheckFailed:
|
|
disconnect(m_updateModel.get(), nullptr, this, nullptr);
|
|
break;
|
|
}
|
|
});
|
|
m_updateModel->checkUpdate();
|
|
getSettings()->setValue( "updatedate", QDate::currentDate() );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
m_threadRunner = new ThreadRunner();
|
|
}
|
|
|
|
MainCtx::~MainCtx()
|
|
{
|
|
/* Save states */
|
|
|
|
m_threadRunner->destroy();
|
|
|
|
settings->beginGroup("MainWindow");
|
|
settings->setValue( "pl-dock-status", b_playlistDocked );
|
|
settings->setValue( "ShowRemainingTime", m_showRemainingTime );
|
|
settings->setValue( "interface-scale", QString::number( m_intfUserScaleFactor ) );
|
|
|
|
/* Save playlist state */
|
|
settings->setValue( "playlist-visible", m_playlistVisible );
|
|
settings->setValue( "playlist-width-factor", QString::number( m_playlistWidthFactor ) );
|
|
settings->setValue( "player-playlist-width-factor", QString::number( m_playerPlaylistWidthFactor ) );
|
|
|
|
settings->setValue( "artist-albums-width-factor", QString::number( m_artistAlbumsWidthFactor ) );
|
|
|
|
settings->setValue( "grid-view", m_gridView );
|
|
settings->setValue( "grouping", m_grouping );
|
|
|
|
settings->setValue( "color-scheme-index", m_colorScheme->currentIndex() );
|
|
/* Save the stackCentralW sizes */
|
|
settings->endGroup();
|
|
|
|
if( var_InheritBool( p_intf, "save-recentplay" ) )
|
|
getSettings()->setValue( "filedialog-path", m_dialogFilepath );
|
|
else
|
|
getSettings()->remove( "filedialog-path" );
|
|
|
|
/* Unregister callbacks */
|
|
libvlc_int_t* libvlc = vlc_object_instance(p_intf);
|
|
var_DelCallback( libvlc, "intf-boss", IntfBossCB, p_intf );
|
|
var_DelCallback( libvlc, "intf-show", IntfRaiseMainCB, p_intf );
|
|
var_DelCallback( libvlc, "intf-toggle-fscontrol", IntfShowCB, p_intf );
|
|
var_DelCallback( libvlc, "intf-popupmenu", PopupMenuCB, p_intf );
|
|
|
|
if (m_medialib)
|
|
delete m_medialib;
|
|
|
|
if (m_network_preparser)
|
|
vlc_preparser_Delete(m_network_preparser);
|
|
|
|
p_intf->p_mi = NULL;
|
|
}
|
|
|
|
bool MainCtx::hasVLM() const {
|
|
#ifdef ENABLE_VLM
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool MainCtx::useClientSideDecoration() const
|
|
{
|
|
//don't show CSD when interface is fullscreen
|
|
return !m_windowTitlebar;
|
|
}
|
|
|
|
bool MainCtx::hasFirstrun() const {
|
|
return config_GetInt( "qt-privacy-ask" );
|
|
}
|
|
|
|
void MainCtx::setUseGlobalShortcuts( bool useShortcuts )
|
|
{
|
|
if (m_useGlobalShortcuts == useShortcuts)
|
|
return;
|
|
m_useGlobalShortcuts = useShortcuts;
|
|
emit useGlobalShortcutsChanged(m_useGlobalShortcuts);
|
|
}
|
|
|
|
void MainCtx::setWindowSuportExtendedFrame(bool support) {
|
|
if (m_windowSuportExtendedFrame == support)
|
|
return;
|
|
m_windowSuportExtendedFrame = support;
|
|
emit windowSuportExtendedFrameChanged();
|
|
}
|
|
|
|
void MainCtx::setWindowExtendedMargin(unsigned int margin) {
|
|
if (margin == m_windowExtendedMargin)
|
|
return;
|
|
m_windowExtendedMargin = margin;
|
|
emit windowExtendedMarginChanged(margin);
|
|
}
|
|
|
|
/*****************************
|
|
* Main UI handling *
|
|
*****************************/
|
|
|
|
void MainCtx::loadPrefs(const bool callSignals)
|
|
{
|
|
const auto loadFromVLCOption = [this, callSignals](auto &variable, const char *name
|
|
, const std::function<void(MainCtx *)> signal)
|
|
{
|
|
using variableType = std::remove_reference_t<decltype(variable)>;
|
|
|
|
const auto value = loadVLCOption<variableType>(VLC_OBJECT(p_intf), name);
|
|
if (value == variable)
|
|
return;
|
|
|
|
variable = value;
|
|
if (callSignals && signal)
|
|
signal(this);
|
|
};
|
|
|
|
/* Are we in the enhanced always-video mode or not ? */
|
|
loadFromVLCOption(m_minimalView, "qt-minimal-view", &MainCtx::minimalViewChanged);
|
|
|
|
loadFromVLCOption(m_bgCone, "qt-bgcone", &MainCtx::bgConeToggled);
|
|
|
|
/* Should the UI stays on top of other windows */
|
|
loadFromVLCOption(b_interfaceOnTop, "video-on-top", [this](MainCtx *)
|
|
{
|
|
emit interfaceAlwaysOnTopChanged(b_interfaceOnTop);
|
|
});
|
|
|
|
loadFromVLCOption(m_hasToolbarMenu, "qt-menubar", &MainCtx::hasToolbarMenuChanged);
|
|
|
|
loadFromVLCOption(m_windowTitlebar, "qt-titlebar" , &MainCtx::useClientSideDecorationChanged);
|
|
|
|
loadFromVLCOption(m_smoothScroll, "qt-smooth-scrolling", &MainCtx::smoothScrollChanged);
|
|
|
|
loadFromVLCOption(m_maxVolume, "qt-max-volume", &MainCtx::maxVolumeChanged);
|
|
|
|
loadFromVLCOption(m_pinVideoControls, "qt-pin-controls", &MainCtx::pinVideoControlsChanged);
|
|
|
|
loadFromVLCOption(m_pinOpacity, "qt-fs-opacity", &MainCtx::pinOpacityChanged);
|
|
|
|
loadFromVLCOption(m_safeArea, "qt-safe-area", &MainCtx::safeAreaChanged);
|
|
|
|
loadFromVLCOption(m_mouseHideTimeout, "mouse-hide-timeout", &MainCtx::mouseHideTimeoutChanged);
|
|
}
|
|
|
|
void MainCtx::loadFromSettingsImpl(const bool callSignals)
|
|
{
|
|
const auto loadFromSettings = [this, callSignals](auto &variable, const char *name
|
|
, const auto defaultValue, auto signal)
|
|
{
|
|
using variableType = std::remove_reference_t<decltype(variable)>;
|
|
|
|
const auto value = getSettings()->value(name, defaultValue).template value<variableType>();
|
|
if (value == variable)
|
|
return;
|
|
|
|
variable = value;
|
|
if (callSignals && signal)
|
|
(this->*signal)(variable);
|
|
};
|
|
|
|
loadFromSettings(b_playlistDocked, "MainWindow/pl-dock-status", true, &MainCtx::playlistDockedChanged);
|
|
|
|
loadFromSettings(m_playlistVisible, "MainWindow/playlist-visible", false, &MainCtx::playlistVisibleChanged);
|
|
|
|
loadFromSettings(m_playlistWidthFactor, "MainWindow/playlist-width-factor", 4.0 , &MainCtx::playlistWidthFactorChanged);
|
|
|
|
loadFromSettings(m_playerPlaylistWidthFactor, "MainWindow/player-playlist-width-factor", 4.0 , &MainCtx::playerPlaylistFactorChanged);
|
|
|
|
loadFromSettings(m_artistAlbumsWidthFactor, "MainWindow/artist-albums-width-factor"
|
|
, 4.0 , &MainCtx::artistAlbumsWidthFactorChanged);
|
|
|
|
loadFromSettings(m_gridView, "MainWindow/grid-view", true, &MainCtx::gridViewChanged);
|
|
|
|
loadFromSettings(m_grouping, "MainWindow/grouping", GROUPING_NONE, &MainCtx::groupingChanged);
|
|
|
|
loadFromSettings(m_showRemainingTime, "MainWindow/ShowRemainingTime", false, &MainCtx::showRemainingTimeChanged);
|
|
|
|
const auto colorSchemeIndex = getSettings()->value( "MainWindow/color-scheme-index", 0 ).toInt();
|
|
m_colorScheme->setCurrentIndex(colorSchemeIndex);
|
|
|
|
/* user interface scale factor */
|
|
auto userIntfScaleFactor = var_InheritFloat(p_intf, "qt-interface-scale");
|
|
if (userIntfScaleFactor == -1)
|
|
userIntfScaleFactor = getSettings()->value( "MainWindow/interface-scale", 1.0).toDouble();
|
|
if (m_intfUserScaleFactor != userIntfScaleFactor)
|
|
{
|
|
m_intfUserScaleFactor = userIntfScaleFactor;
|
|
updateIntfScaleFactor();
|
|
}
|
|
}
|
|
|
|
void MainCtx::reloadPrefs()
|
|
{
|
|
loadPrefs(true);
|
|
}
|
|
|
|
void MainCtx::onInputChanged( bool hasInput )
|
|
{
|
|
if( hasInput == false )
|
|
return;
|
|
int autoRaise = var_InheritInteger( p_intf, "qt-auto-raise" );
|
|
if ( autoRaise == MainCtx::RAISE_NEVER )
|
|
return;
|
|
if( THEMIM->hasVideoOutput() == true )
|
|
{
|
|
if( ( autoRaise & MainCtx::RAISE_VIDEO ) == 0 )
|
|
return;
|
|
}
|
|
else if ( ( autoRaise & MainCtx::RAISE_AUDIO ) == 0 )
|
|
return;
|
|
emit askRaise();
|
|
}
|
|
|
|
#ifdef KeyPress
|
|
#undef KeyPress
|
|
#endif
|
|
void MainCtx::sendHotkey(Qt::Key key , Qt::KeyboardModifiers modifiers)
|
|
{
|
|
QKeyEvent event(QEvent::KeyPress, key, modifiers );
|
|
int vlckey = qtEventToVLCKey(&event);
|
|
var_SetInteger(vlc_object_instance(p_intf), "key-pressed", vlckey);
|
|
}
|
|
|
|
void MainCtx::sendVLCHotkey(int vlcHotkey)
|
|
{
|
|
var_SetInteger(vlc_object_instance(p_intf), "key-pressed", vlcHotkey);
|
|
}
|
|
|
|
void MainCtx::updateIntfScaleFactor()
|
|
{
|
|
auto newValue = m_intfUserScaleFactor;
|
|
if (QWindow* window = p_intf->p_compositor ? p_intf->p_compositor->interfaceMainWindow() : nullptr)
|
|
{
|
|
QScreen* screen = window->screen();
|
|
if (screen)
|
|
{
|
|
qreal dpi = screen->logicalDotsPerInch();
|
|
newValue = m_intfUserScaleFactor * dpi / VLC_REFERENCE_SCALE_FACTOR;
|
|
}
|
|
}
|
|
|
|
const int FACTOR_RESOLUTION = 100;
|
|
if ( static_cast<int>(m_intfScaleFactor * FACTOR_RESOLUTION)
|
|
== static_cast<int>(newValue * FACTOR_RESOLUTION) ) return;
|
|
|
|
m_intfScaleFactor = newValue;
|
|
emit intfScaleFactorChanged();
|
|
}
|
|
|
|
void MainCtx::onWindowVisibilityChanged(QWindow::Visibility visibility)
|
|
{
|
|
m_windowVisibility = visibility;
|
|
}
|
|
|
|
void MainCtx::setHasAcrylicSurface(const bool v)
|
|
{
|
|
if (m_hasAcrylicSurface == v)
|
|
return;
|
|
|
|
m_hasAcrylicSurface = v;
|
|
emit hasAcrylicSurfaceChanged(v);
|
|
}
|
|
|
|
void MainCtx::incrementIntfUserScaleFactor(bool increment)
|
|
{
|
|
if (increment)
|
|
setIntfUserScaleFactor(m_intfUserScaleFactor + 0.1);
|
|
else
|
|
setIntfUserScaleFactor(m_intfUserScaleFactor - 0.1);
|
|
}
|
|
|
|
void MainCtx::setIntfUserScaleFactor(double newValue)
|
|
{
|
|
m_intfUserScaleFactor = qBound(getMinIntfUserScaleFactor(), newValue, getMaxIntfUserScaleFactor());
|
|
updateIntfScaleFactor();
|
|
}
|
|
|
|
void MainCtx::setHasToolbarMenu( bool hasToolbarMenu )
|
|
{
|
|
if (m_hasToolbarMenu == hasToolbarMenu)
|
|
return;
|
|
|
|
m_hasToolbarMenu = hasToolbarMenu;
|
|
|
|
config_PutInt("qt-menubar", (int) hasToolbarMenu);
|
|
|
|
emit hasToolbarMenuChanged();
|
|
}
|
|
|
|
void MainCtx::setPinVideoControls(bool pinVideoControls)
|
|
{
|
|
if (m_pinVideoControls == pinVideoControls)
|
|
return;
|
|
|
|
m_pinVideoControls = pinVideoControls;
|
|
emit pinVideoControlsChanged();
|
|
}
|
|
|
|
void MainCtx::setPinOpacity(float pinOpacity)
|
|
{
|
|
if (m_pinOpacity == pinOpacity)
|
|
return;
|
|
|
|
m_pinOpacity = pinOpacity;
|
|
|
|
emit pinOpacityChanged();
|
|
}
|
|
|
|
inline void MainCtx::initSystray()
|
|
{
|
|
bool b_systrayAvailable = QSystemTrayIcon::isSystemTrayAvailable();
|
|
bool b_systrayWanted = var_InheritBool( p_intf, "qt-system-tray" );
|
|
|
|
if( var_InheritBool( p_intf, "qt-start-minimized") )
|
|
{
|
|
if( b_systrayAvailable )
|
|
{
|
|
b_systrayWanted = true;
|
|
b_hideAfterCreation = true;
|
|
}
|
|
else
|
|
msg_Err( p_intf, "cannot start minimized without system tray bar" );
|
|
}
|
|
|
|
if( b_systrayAvailable && b_systrayWanted )
|
|
m_systray = std::make_unique<VLCSystray>(this);
|
|
}
|
|
|
|
ThreadRunner* MainCtx::threadRunner() const
|
|
{
|
|
return m_threadRunner;
|
|
}
|
|
|
|
QUrl MainCtx::folderMRL(const QString &fileMRL) const
|
|
{
|
|
return folderMRL(QUrl::fromUserInput(fileMRL));
|
|
}
|
|
|
|
QUrl MainCtx::folderMRL(const QUrl &fileMRL) const
|
|
{
|
|
if (fileMRL.isLocalFile())
|
|
{
|
|
const QString f = fileMRL.toLocalFile();
|
|
return QUrl::fromLocalFile(QFileInfo(f).absoluteDir().absolutePath());
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QString MainCtx::displayMRL(const QUrl &mrl) const
|
|
{
|
|
return urlToDisplayString(mrl);
|
|
}
|
|
|
|
void MainCtx::setPlaylistDocked( bool docked )
|
|
{
|
|
b_playlistDocked = docked;
|
|
|
|
emit playlistDockedChanged(docked);
|
|
}
|
|
|
|
void MainCtx::setPlaylistVisible( bool visible )
|
|
{
|
|
m_playlistVisible = visible;
|
|
|
|
emit playlistVisibleChanged(visible);
|
|
}
|
|
|
|
void MainCtx::setPlaylistWidthFactor( double factor )
|
|
{
|
|
if (factor > 0.0)
|
|
{
|
|
m_playlistWidthFactor = factor;
|
|
emit playlistWidthFactorChanged(factor);
|
|
}
|
|
}
|
|
|
|
void MainCtx::setPlayerPlaylistWidthFactor( double factor )
|
|
{
|
|
if (factor > 0.0)
|
|
{
|
|
m_playerPlaylistWidthFactor = factor;
|
|
emit playerPlaylistFactorChanged(factor);
|
|
}
|
|
}
|
|
|
|
void MainCtx::setbgCone(bool bgCone)
|
|
{
|
|
if (m_bgCone == bgCone)
|
|
return;
|
|
|
|
m_bgCone = bgCone;
|
|
|
|
emit bgConeToggled();
|
|
}
|
|
|
|
void MainCtx::setMinimalView(bool minimalView)
|
|
{
|
|
if (m_minimalView == minimalView)
|
|
return;
|
|
|
|
m_minimalView = minimalView;
|
|
emit minimalViewChanged();
|
|
}
|
|
|
|
void MainCtx::setShowRemainingTime( bool show )
|
|
{
|
|
m_showRemainingTime = show;
|
|
emit showRemainingTimeChanged(show);
|
|
}
|
|
|
|
void MainCtx::setGridView(bool asGrid)
|
|
{
|
|
m_gridView = asGrid;
|
|
emit gridViewChanged( asGrid );
|
|
}
|
|
|
|
void MainCtx::setGrouping(Grouping grouping)
|
|
{
|
|
m_grouping = grouping;
|
|
|
|
emit groupingChanged(grouping);
|
|
}
|
|
|
|
void MainCtx::setInterfaceAlwaysOnTop( bool on_top )
|
|
{
|
|
if (b_interfaceOnTop == on_top)
|
|
return;
|
|
|
|
b_interfaceOnTop = on_top;
|
|
emit interfaceAlwaysOnTopChanged(on_top);
|
|
}
|
|
|
|
bool MainCtx::hasEmbededVideo() const
|
|
{
|
|
return m_videoSurfaceProvider && m_videoSurfaceProvider->hasVideoEmbed();
|
|
}
|
|
|
|
void MainCtx::setVideoSurfaceProvider(VideoSurfaceProvider* videoSurfaceProvider)
|
|
{
|
|
if (m_videoSurfaceProvider)
|
|
disconnect(m_videoSurfaceProvider, &VideoSurfaceProvider::hasVideoEmbedChanged, this, &MainCtx::hasEmbededVideoChanged);
|
|
m_videoSurfaceProvider = videoSurfaceProvider;
|
|
if (m_videoSurfaceProvider)
|
|
connect(m_videoSurfaceProvider, &VideoSurfaceProvider::hasVideoEmbedChanged,
|
|
this, &MainCtx::hasEmbededVideoChanged,
|
|
Qt::QueuedConnection);
|
|
emit hasEmbededVideoChanged(m_videoSurfaceProvider && m_videoSurfaceProvider->hasVideoEmbed());
|
|
}
|
|
|
|
QJSValue MainCtx::urlListToMimeData(const QJSValue &array) {
|
|
// NOTE: Due to a Qt regression since 17318c4
|
|
// (Nov 11, 2022), it is not possible to
|
|
// use RFC-2483 compliant string here.
|
|
// This regression was later corrected by
|
|
// c25f53b (Jul 31, 2024).
|
|
// NOTE: Qt starts supporting string list since
|
|
// 17318c4, so starting from 6.5.0 a string
|
|
// list can be used which is not affected
|
|
// by the said issue. For Qt versions below
|
|
// 6.5.0, use byte array which is used as is
|
|
// by Qt.
|
|
assert(array.property("length").toInt() > 0);
|
|
|
|
QJSEngine* const engine = qjsEngine(this);
|
|
assert(engine);
|
|
|
|
QJSValue data;
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
|
|
QString string;
|
|
for (int i = 0; i < array.property(QStringLiteral("length")).toInt(); ++i)
|
|
{
|
|
QString decodedUrl;
|
|
const QJSValue element = array.property(i);
|
|
if (element.isUrl())
|
|
// QJSValue does not have `toUrl()`
|
|
decodedUrl = QJSManagedValue(element, engine).toUrl().toString(QUrl::FullyEncoded);
|
|
else if (element.isString())
|
|
// If the element is string, we assume it is already encoded
|
|
decodedUrl = element.toString();
|
|
else if (element.isVariant())
|
|
{
|
|
const QVariant variant = element.toVariant();
|
|
if (variant.typeId() == QMetaType::QUrl)
|
|
decodedUrl = variant.toUrl().toString(QUrl::FullyEncoded);
|
|
else if (variant.typeId() == QMetaType::QString)
|
|
decodedUrl = variant.toString();
|
|
else
|
|
Q_UNREACHABLE();
|
|
}
|
|
else
|
|
Q_UNREACHABLE(); // Assertion failure in debug builds
|
|
string += decodedUrl + QStringLiteral("\r\n");
|
|
}
|
|
string.chop(2);
|
|
data = engine->toScriptValue(string);
|
|
#else
|
|
data = array;
|
|
#endif
|
|
QJSValue ret = engine->newObject();
|
|
ret.setProperty(QStringLiteral("text/uri-list"), data);
|
|
return ret;
|
|
}
|
|
|
|
VideoSurfaceProvider* MainCtx::getVideoSurfaceProvider() const
|
|
{
|
|
return m_videoSurfaceProvider;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Events stuff
|
|
************************************************************************/
|
|
|
|
void MainCtx::onWindowClose( QWindow* )
|
|
{
|
|
PlaylistController* playlistController = p_intf->p_mainPlaylistController;
|
|
PlayerController* playerController = p_intf->p_mainPlayerController;
|
|
|
|
if (m_videoSurfaceProvider)
|
|
m_videoSurfaceProvider->onWindowClosed();
|
|
//We need to make sure that noting is playing anymore otherwise the vout will be closed
|
|
//after the main interface, and it requires (at least with OpenGL) that the OpenGL context
|
|
//from the main window is still valid.
|
|
//vlc_window_ReportClose is currently stubbed
|
|
if (playerController && playerController->hasVideoOutput()) {
|
|
connect(playerController, &PlayerController::playingStateChanged, [this](PlayerController::PlayingState state){
|
|
if (state == PlayerController::PLAYING_STATE_STOPPED) {
|
|
emit askToQuit();
|
|
}
|
|
});
|
|
playlistController->stop();
|
|
}
|
|
else
|
|
{
|
|
emit askToQuit(); /* ask THEDP to quit, so we have a unique method */
|
|
}
|
|
}
|
|
|
|
void MainCtx::toggleToolbarMenu()
|
|
{
|
|
setHasToolbarMenu(!m_hasToolbarMenu);
|
|
}
|
|
|
|
void MainCtx::toggleInterfaceFullScreen()
|
|
{
|
|
emit setInterfaceFullScreen( m_windowVisibility != QWindow::FullScreen );
|
|
}
|
|
|
|
void MainCtx::emitBoss()
|
|
{
|
|
emit askBoss();
|
|
}
|
|
|
|
void MainCtx::emitShow()
|
|
{
|
|
emit askShow();
|
|
}
|
|
|
|
void MainCtx::emitRaise()
|
|
{
|
|
emit askRaise();
|
|
}
|
|
|
|
VLCVarChoiceModel* MainCtx::getExtraInterfaces()
|
|
{
|
|
return m_extraInterfaces;
|
|
}
|
|
|
|
bool MainCtx::pasteFromClipboard()
|
|
{
|
|
assert(qApp);
|
|
const QClipboard *const clipboard = qApp->clipboard();
|
|
if (Q_UNLIKELY(!clipboard))
|
|
return false;
|
|
const QMimeData *mimeData = clipboard->mimeData(QClipboard::Selection);
|
|
if (!mimeData || !mimeData->hasUrls())
|
|
mimeData = clipboard->mimeData(QClipboard::Clipboard);
|
|
|
|
if (Q_UNLIKELY(!mimeData))
|
|
return false;
|
|
|
|
QList<QUrl> urlList = mimeData->urls();
|
|
QString text = mimeData->text();
|
|
|
|
if (urlList.count() > 1 || text.contains('\n'))
|
|
{
|
|
// NOTE: The reason that mime data for `text/uri-list` is not used
|
|
// directly as the placeholder of the input dialog instead of
|
|
// re-constructing is to decode the urls in the list.
|
|
QString placeholder;
|
|
|
|
if (urlList.isEmpty())
|
|
{
|
|
placeholder = std::move(text);
|
|
}
|
|
else
|
|
{
|
|
for (const auto& i : urlList)
|
|
placeholder += i.toString(QUrl::PrettyDecoded) + '\n';
|
|
placeholder.chop(1);
|
|
}
|
|
|
|
bool ok = false;
|
|
const QString ret = QInputDialog::getMultiLineText(nullptr,
|
|
qtr("Paste from clipboard"),
|
|
qtr("Do you want to enqueue the following URLs into the playlist?"),
|
|
placeholder,
|
|
&ok);
|
|
if (!ok)
|
|
return false;
|
|
|
|
for (const auto& i : QStringView(ret).split('\n'))
|
|
{
|
|
if (i.length() > 0)
|
|
THEMPL->append(i.trimmed().toString(), false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if ((urlList.count() == 1) || mimeData->hasText())
|
|
{
|
|
THEDP->openUrlDialog();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* PopupMenuCB: callback triggered by the intf-popupmenu playlist variable.
|
|
* We don't show the menu directly here because we don't want the
|
|
* caller to block for a too long time.
|
|
*****************************************************************************/
|
|
static int PopupMenuCB( vlc_object_t *, const char *,
|
|
vlc_value_t, vlc_value_t new_val, void *param )
|
|
{
|
|
qt_intf_t *p_intf = (qt_intf_t *)param;
|
|
|
|
if( p_intf->pf_show_dialog )
|
|
{
|
|
p_intf->pf_show_dialog( p_intf->intf, INTF_DIALOG_POPUPMENU,
|
|
new_val.b_bool, NULL );
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* IntfShowCB: callback triggered by the intf-toggle-fscontrol libvlc variable.
|
|
*****************************************************************************/
|
|
static int IntfShowCB( vlc_object_t *, const char *,
|
|
vlc_value_t, vlc_value_t, void *param )
|
|
{
|
|
qt_intf_t *p_intf = (qt_intf_t *)param;
|
|
p_intf->p_mi->emitShow();
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* IntfRaiseMainCB: callback triggered by the intf-show-main libvlc variable.
|
|
*****************************************************************************/
|
|
static int IntfRaiseMainCB( vlc_object_t *, const char *,
|
|
vlc_value_t, vlc_value_t, void *param )
|
|
{
|
|
qt_intf_t *p_intf = (qt_intf_t *)param;
|
|
p_intf->p_mi->emitRaise();
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* IntfBossCB: callback triggered by the intf-boss libvlc variable.
|
|
*****************************************************************************/
|
|
static int IntfBossCB( vlc_object_t *, const char *,
|
|
vlc_value_t, vlc_value_t, void *param )
|
|
{
|
|
qt_intf_t *p_intf = (qt_intf_t *)param;
|
|
p_intf->p_mi->emitBoss();
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
bool MainCtx::acrylicActive() const
|
|
{
|
|
return m_acrylicActive;
|
|
}
|
|
|
|
void MainCtx::setAcrylicActive(bool newAcrylicActive)
|
|
{
|
|
if (m_acrylicActive == newAcrylicActive)
|
|
return;
|
|
|
|
m_acrylicActive = newAcrylicActive;
|
|
emit acrylicActiveChanged();
|
|
}
|
|
|
|
bool MainCtx::preferHotkeys() const
|
|
{
|
|
return m_preferHotkeys;
|
|
}
|
|
|
|
void MainCtx::setPreferHotkeys(bool enable)
|
|
{
|
|
if (m_preferHotkeys == enable)
|
|
return;
|
|
|
|
m_preferHotkeys = enable;
|
|
|
|
emit preferHotkeysChanged();
|
|
}
|
|
|
|
QWindow *MainCtx::intfMainWindow() const
|
|
{
|
|
if (p_intf->p_compositor)
|
|
return p_intf->p_compositor->interfaceMainWindow();
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
QVariant MainCtx::settingValue(const QString &key, const QVariant &defaultValue) const
|
|
{
|
|
return settings->value(key, defaultValue);
|
|
}
|
|
|
|
void MainCtx::setSettingValue(const QString &key, const QVariant &value)
|
|
{
|
|
settings->setValue(key, value);
|
|
}
|
|
|
|
void MainCtx::setAttachedToolTip(QObject *toolTip)
|
|
{
|
|
// See QQuickToolTipAttachedPrivate::instance(bool create)
|
|
assert(toolTip);
|
|
|
|
// Prevent possible invalid down-casting:
|
|
assert(toolTip->inherits("QQuickToolTip"));
|
|
|
|
QQmlEngine* const engine = qmlEngine(toolTip);
|
|
assert(engine);
|
|
assert(engine->objectOwnership(toolTip) == QQmlEngine::ObjectOwnership::JavaScriptOwnership);
|
|
|
|
// Dynamic internal property:
|
|
static const char* const name = "_q_QQuickToolTip";
|
|
|
|
if (const auto obj = engine->property(name).value<QObject *>())
|
|
{
|
|
if (engine->objectOwnership(obj) == QQmlEngine::ObjectOwnership::CppOwnership)
|
|
obj->deleteLater();
|
|
}
|
|
|
|
// setProperty() will return false, so there is no
|
|
// need to check the return value:
|
|
engine->setProperty(name, QVariant::fromValue(toolTip));
|
|
|
|
// Check if the attached tooltip is actually the
|
|
// one that is set
|
|
#ifndef NDEBUG
|
|
QQmlComponent component(engine);
|
|
component.setData(QByteArrayLiteral("import QtQuick; import QtQuick.Controls; Item { }"), {});
|
|
QObject* const obj = component.create();
|
|
assert(obj);
|
|
// Consider disabling setting of custom attached
|
|
// tooltip if the following assertion fails:
|
|
if (QQmlProperty::read(obj, QStringLiteral("ToolTip.toolTip"), qmlContext(obj)).value<QObject*>() != toolTip)
|
|
qmlWarning(obj) << "Could not set self as custom ToolTip!";
|
|
obj->deleteLater();
|
|
#endif
|
|
}
|
|
|
|
double MainCtx::dp(const double px, const double scale)
|
|
{
|
|
return std::round(px * scale);
|
|
}
|
|
|
|
double MainCtx::dp(const double px) const
|
|
{
|
|
return dp(px, m_intfScaleFactor);
|
|
}
|
|
|
|
bool MainCtx::useXmasCone() const
|
|
{
|
|
return (QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY)
|
|
&& var_InheritBool( p_intf, "qt-icon-change" );
|
|
}
|
|
|
|
bool WindowStateHolder::holdFullscreen(QWindow *window, Source source, bool hold)
|
|
{
|
|
QVariant prop = window->property("__windowFullScreen");
|
|
bool ok = false;
|
|
unsigned fullscreenCounter = prop.toUInt(&ok);
|
|
if (!ok)
|
|
fullscreenCounter = 0;
|
|
|
|
if (hold)
|
|
fullscreenCounter |= source;
|
|
else
|
|
fullscreenCounter &= ~source;
|
|
|
|
Qt::WindowStates oldflags = window->windowStates();
|
|
Qt::WindowStates newflags;
|
|
|
|
if( fullscreenCounter != 0 )
|
|
newflags = oldflags | Qt::WindowFullScreen;
|
|
else
|
|
newflags = oldflags & ~Qt::WindowFullScreen;
|
|
|
|
window->setProperty("__windowFullScreen", QVariant::fromValue(fullscreenCounter));
|
|
|
|
if( newflags != oldflags )
|
|
{
|
|
window->setWindowStates( newflags );
|
|
}
|
|
|
|
return fullscreenCounter != 0;
|
|
}
|
|
|
|
bool WindowStateHolder::holdOnTop(QWindow *window, Source source, bool hold)
|
|
{
|
|
QVariant prop = window->property("__windowOnTop");
|
|
bool ok = false;
|
|
unsigned onTopCounter = prop.toUInt(&ok);
|
|
if (!ok)
|
|
onTopCounter = 0;
|
|
|
|
if (hold)
|
|
onTopCounter |= source;
|
|
else
|
|
onTopCounter &= ~source;
|
|
|
|
#ifdef _WIN32
|
|
if (!window->handle()) // do not call hold on top if there is no platform window
|
|
return false;
|
|
#endif
|
|
|
|
if( onTopCounter != 0 )
|
|
{
|
|
#ifdef _WIN32
|
|
// Qt::WindowStaysOnTopHint is broken on Windows: QTBUG-133034, QTBUG-132522.
|
|
// Reported to be fixed by Qt 6.9, but needs additional checking if the solution does not interfere with `CSDWin32EventHandler`.
|
|
SetWindowPos(reinterpret_cast<HWND>(window->winId()), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
|
#else
|
|
window->setFlag(Qt::WindowStaysOnTopHint, true);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef _WIN32
|
|
// Qt::WindowStaysOnTopHint is broken on Windows: QTBUG-133034, QTBUG-132522.
|
|
// Reported to be fixed by Qt 6.9, but needs additional checking if the solution does not interfere with `CSDWin32EventHandler`.
|
|
SetWindowPos(reinterpret_cast<HWND>(window->winId()), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
|
#else
|
|
window->setFlag(Qt::WindowStaysOnTopHint, false);
|
|
#endif
|
|
}
|
|
|
|
window->setProperty("__windowOnTop", QVariant::fromValue(onTopCounter));
|
|
|
|
return onTopCounter != 0;
|
|
}
|
|
|
|
double MainCtx::artistAlbumsWidthFactor() const
|
|
{
|
|
return m_artistAlbumsWidthFactor;
|
|
}
|
|
|
|
void MainCtx::setArtistAlbumsWidthFactor(double newArtistAlbumsWidthFactor)
|
|
{
|
|
if (qFuzzyCompare(m_artistAlbumsWidthFactor, newArtistAlbumsWidthFactor))
|
|
return;
|
|
|
|
m_artistAlbumsWidthFactor = newArtistAlbumsWidthFactor;
|
|
emit artistAlbumsWidthFactorChanged( m_artistAlbumsWidthFactor );
|
|
}
|
|
|
|
#ifdef UPDATE_CHECK
|
|
UpdateModel* MainCtx::getUpdateModel() const
|
|
{
|
|
if (!m_updateModel)
|
|
m_updateModel = std::make_unique<UpdateModel>(p_intf);
|
|
return m_updateModel.get();
|
|
}
|
|
#endif
|
|
|