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

/*****************************************************************************
* 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