diff options
Diffstat (limited to 'vcl/qt5/QtInstance.cxx')
-rw-r--r-- | vcl/qt5/QtInstance.cxx | 771 |
1 files changed, 771 insertions, 0 deletions
diff --git a/vcl/qt5/QtInstance.cxx b/vcl/qt5/QtInstance.cxx new file mode 100644 index 0000000000..4880c1bdec --- /dev/null +++ b/vcl/qt5/QtInstance.cxx @@ -0,0 +1,771 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <QtInstance.hxx> +#include <QtInstance.moc> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <QtBitmap.hxx> +#include <QtClipboard.hxx> +#include <QtData.hxx> +#include <QtDragAndDrop.hxx> +#include <QtFilePicker.hxx> +#include <QtFrame.hxx> +#include <QtMenu.hxx> +#include <QtObject.hxx> +#include <QtOpenGLContext.hxx> +#include "QtSvpVirtualDevice.hxx" +#include <QtSystem.hxx> +#include <QtTimer.hxx> +#include <QtVirtualDevice.hxx> + +#include <headless/svpvd.hxx> + +#include <QtCore/QAbstractEventDispatcher> +#include <QtCore/QLibraryInfo> +#include <QtCore/QThread> +#include <QtGui/QScreen> +#include <QtWidgets/QApplication> +#include <QtWidgets/QWidget> + +#include <vclpluginapi.h> +#include <tools/debug.hxx> +#include <comphelper/flagguard.hxx> +#include <dndhelper.hxx> +#include <vcl/sysdata.hxx> +#include <sal/log.hxx> +#include <osl/process.h> +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && ENABLE_GSTREAMER_1_0 && QT5_HAVE_GOBJECT +#include <unx/gstsink.hxx> +#endif +#include <headless/svpbmp.hxx> + +#include <mutex> +#include <condition_variable> + +#ifdef EMSCRIPTEN +#include <QtCore/QtPlugin> +Q_IMPORT_PLUGIN(QWasmIntegrationPlugin) +#endif + +namespace +{ +/// TODO: not much Qt specific here? could be generalised, esp. for OSX... +/// this subclass allows for the transfer of a closure for running on the main +/// thread, to handle all the thread affine stuff in Qt; the SolarMutex is +/// "loaned" to the main thread for the execution of the closure. +/// @note it doesn't work to just use "emit" and signals/slots to move calls to +/// the main thread, because the other thread has the SolarMutex; the other +/// thread (typically) cannot release SolarMutex, because then the main thread +/// will handle all sorts of events and whatnot; this design ensures that the +/// main thread only runs the passed closure (unless the closure releases +/// SolarMutex itself, which should probably be avoided). +class QtYieldMutex : public SalYieldMutex +{ +public: + /// flag only accessed on main thread: + /// main thread has "borrowed" SolarMutex from another thread + bool m_bNoYieldLock = false; + /// members for communication from non-main thread to main thread + std::mutex m_RunInMainMutex; + std::condition_variable m_InMainCondition; + bool m_isWakeUpMain = false; + std::function<void()> m_Closure; ///< code for main thread to run + /// members for communication from main thread to non-main thread + std::condition_variable m_ResultCondition; + bool m_isResultReady = false; + + virtual bool IsCurrentThread() const override; + virtual void doAcquire(sal_uInt32 nLockCount) override; + virtual sal_uInt32 doRelease(bool const bUnlockAll) override; +}; +} + +bool QtYieldMutex::IsCurrentThread() const +{ + auto const* pSalInst(GetQtInstance()); + assert(pSalInst); + if (pSalInst->IsMainThread() && m_bNoYieldLock) + { + return true; // main thread has borrowed SolarMutex + } + return SalYieldMutex::IsCurrentThread(); +} + +void QtYieldMutex::doAcquire(sal_uInt32 nLockCount) +{ + auto const* pSalInst(GetQtInstance()); + assert(pSalInst); + if (!pSalInst->IsMainThread()) + { + SalYieldMutex::doAcquire(nLockCount); + return; + } + if (m_bNoYieldLock) + { + return; // special case for main thread: borrowed from other thread + } + do // main thread acquire... + { + std::function<void()> func; // copy of closure on thread stack + { + std::unique_lock<std::mutex> g(m_RunInMainMutex); + if (m_aMutex.tryToAcquire()) + { + // if there's a closure, the other thread holds m_aMutex + assert(!m_Closure); + m_isWakeUpMain = false; + --nLockCount; // have acquired once! + ++m_nCount; + break; + } + m_InMainCondition.wait(g, [this]() { return m_isWakeUpMain; }); + m_isWakeUpMain = false; + std::swap(func, m_Closure); + } + if (func) + { + assert(!m_bNoYieldLock); + m_bNoYieldLock = true; // execute closure with borrowed SolarMutex + func(); + m_bNoYieldLock = false; + std::scoped_lock<std::mutex> g(m_RunInMainMutex); + assert(!m_isResultReady); + m_isResultReady = true; + m_ResultCondition.notify_all(); // unblock other thread + } + } while (true); + SalYieldMutex::doAcquire(nLockCount); +} + +sal_uInt32 QtYieldMutex::doRelease(bool const bUnlockAll) +{ + auto const* pSalInst(GetQtInstance()); + assert(pSalInst); + if (pSalInst->IsMainThread() && m_bNoYieldLock) + { + return 1; // dummy value + } + + std::scoped_lock<std::mutex> g(m_RunInMainMutex); + // read m_nCount before doRelease (it's guarded by m_aMutex) + bool const isReleased(bUnlockAll || m_nCount == 1); + sal_uInt32 nCount = SalYieldMutex::doRelease(bUnlockAll); + if (isReleased && !pSalInst->IsMainThread()) + { + m_isWakeUpMain = true; + m_InMainCondition.notify_all(); // unblock main thread + } + return nCount; +} + +// this could be abstracted to be independent of Qt by passing in the +// event-trigger as another function parameter... +// it could also be a template of the return type, then it could return the +// result of func... but then how to handle the result in doAcquire? +void QtInstance::RunInMainThread(std::function<void()> func) +{ + DBG_TESTSOLARMUTEX(); + if (IsMainThread()) + { + func(); + return; + } + + QtYieldMutex* const pMutex(static_cast<QtYieldMutex*>(GetYieldMutex())); + { + std::scoped_lock<std::mutex> g(pMutex->m_RunInMainMutex); + assert(!pMutex->m_Closure); + pMutex->m_Closure = func; + // unblock main thread in case it is blocked on condition + pMutex->m_isWakeUpMain = true; + pMutex->m_InMainCondition.notify_all(); + } + + TriggerUserEventProcessing(); + { + std::unique_lock<std::mutex> g(pMutex->m_RunInMainMutex); + pMutex->m_ResultCondition.wait(g, [pMutex]() { return pMutex->m_isResultReady; }); + pMutex->m_isResultReady = false; + } +} + +OUString QtInstance::constructToolkitID(std::u16string_view sTKname) +{ + OUString sID(sTKname + OUString::Concat(u" (")); + if (m_bUseCairo) + sID += "cairo+"; + else + sID += "qfont+"; + sID += toOUString(QGuiApplication::platformName()) + OUString::Concat(u")"); + return sID; +} + +QtInstance::QtInstance(std::unique_ptr<QApplication>& pQApp) + : SalGenericInstance(std::make_unique<QtYieldMutex>()) + , m_bUseCairo(nullptr == getenv("SAL_VCL_QT_USE_QFONT")) + , m_pTimer(nullptr) + , m_bSleeping(false) + , m_pQApplication(std::move(pQApp)) + , m_aUpdateStyleTimer("vcl::qt5 m_aUpdateStyleTimer") + , m_bUpdateFonts(false) + , m_pActivePopup(nullptr) +{ + ImplSVData* pSVData = ImplGetSVData(); + const OUString sToolkit = "qt" + OUString::number(QT_VERSION_MAJOR); + pSVData->maAppData.mxToolkitName = constructToolkitID(sToolkit); + + // this one needs to be blocking, so that the handling in main thread + // is processed before the thread emitting the signal continues + connect(this, SIGNAL(ImplYieldSignal(bool, bool)), this, SLOT(ImplYield(bool, bool)), + Qt::BlockingQueuedConnection); + + // this one needs to be queued non-blocking + // in order to have this event arriving to correct event processing loop + connect(this, &QtInstance::deleteObjectLaterSignal, this, + [](QObject* pObject) { QtInstance::deleteObjectLater(pObject); }, Qt::QueuedConnection); + + m_aUpdateStyleTimer.SetTimeout(50); + m_aUpdateStyleTimer.SetInvokeHandler(LINK(this, QtInstance, updateStyleHdl)); + + QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread()); + connect(dispatcher, &QAbstractEventDispatcher::awake, this, [this]() { m_bSleeping = false; }); + connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, + [this]() { m_bSleeping = true; }); + + connect(QGuiApplication::inputMethod(), &QInputMethod::localeChanged, this, + &QtInstance::localeChanged); + + for (const QScreen* pCurScreen : QApplication::screens()) + connectQScreenSignals(pCurScreen); + connect(qApp, &QGuiApplication::primaryScreenChanged, this, &QtInstance::primaryScreenChanged); + connect(qApp, &QGuiApplication::screenAdded, this, &QtInstance::screenAdded); + connect(qApp, &QGuiApplication::screenRemoved, this, &QtInstance::screenRemoved); + +#ifndef EMSCRIPTEN + m_bSupportsOpenGL = true; +#else + ImplGetSVData()->maAppData.m_bUseSystemLoop = true; +#endif +} + +QtInstance::~QtInstance() +{ + // force freeing the QApplication before freeing the arguments, + // as it uses references to the provided arguments! + m_pQApplication.reset(); +} + +void QtInstance::AfterAppInit() +{ + // set the default application icon via desktop file just on Wayland, + // as this otherwise overrides the individual desktop icons on X11. + if (QGuiApplication::platformName() == "wayland") + QGuiApplication::setDesktopFileName(QStringLiteral("libreoffice-startcenter.desktop")); + QGuiApplication::setLayoutDirection(AllSettings::GetLayoutRTL() ? Qt::RightToLeft + : Qt::LeftToRight); +} + +void QtInstance::localeChanged() +{ + SolarMutexGuard aGuard; + const vcl::Window* pFocusWindow = Application::GetFocusWindow(); + SalFrame* const pFocusFrame = pFocusWindow ? pFocusWindow->ImplGetFrame() : nullptr; + if (!pFocusFrame) + return; + + const LanguageTag aTag( + toOUString(QGuiApplication::inputMethod()->locale().name().replace("_", "-"))); + static_cast<QtFrame*>(pFocusFrame)->setInputLanguage(aTag.getLanguageType()); +} + +void QtInstance::deleteObjectLater(QObject* pObject) { pObject->deleteLater(); } + +SalFrame* QtInstance::CreateChildFrame(SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle) +{ + SalFrame* pRet(nullptr); + RunInMainThread([&, this]() { pRet = new QtFrame(nullptr, nStyle, useCairo()); }); + assert(pRet); + return pRet; +} + +SalFrame* QtInstance::CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle) +{ + assert(!pParent || dynamic_cast<QtFrame*>(pParent)); + + SalFrame* pRet(nullptr); + RunInMainThread( + [&, this]() { pRet = new QtFrame(static_cast<QtFrame*>(pParent), nStyle, useCairo()); }); + assert(pRet); + return pRet; +} + +void QtInstance::DestroyFrame(SalFrame* pFrame) +{ + if (pFrame) + { + assert(dynamic_cast<QtFrame*>(pFrame)); + Q_EMIT deleteObjectLaterSignal(static_cast<QtFrame*>(pFrame)); + } +} + +SalObject* QtInstance::CreateObject(SalFrame* pParent, SystemWindowData*, bool bShow) +{ + assert(!pParent || dynamic_cast<QtFrame*>(pParent)); + + SalObject* pRet(nullptr); + RunInMainThread([&]() { pRet = new QtObject(static_cast<QtFrame*>(pParent), bShow); }); + assert(pRet); + return pRet; +} + +void QtInstance::DestroyObject(SalObject* pObject) +{ + if (pObject) + { + assert(dynamic_cast<QtObject*>(pObject)); + Q_EMIT deleteObjectLaterSignal(static_cast<QtObject*>(pObject)); + } +} + +std::unique_ptr<SalVirtualDevice> +QtInstance::CreateVirtualDevice(SalGraphics& rGraphics, tools::Long& nDX, tools::Long& nDY, + DeviceFormat /*eFormat*/, const SystemGraphicsData* pGd) +{ + if (m_bUseCairo) + { + SvpSalGraphics* pSvpSalGraphics = dynamic_cast<QtSvpGraphics*>(&rGraphics); + assert(pSvpSalGraphics); + // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget + cairo_surface_t* pPreExistingTarget + = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr; + std::unique_ptr<SalVirtualDevice> pVD( + new QtSvpVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget)); + pVD->SetSize(nDX, nDY); + return pVD; + } + else + { + std::unique_ptr<SalVirtualDevice> pVD(new QtVirtualDevice(/*scale*/ 1)); + pVD->SetSize(nDX, nDY); + return pVD; + } +} + +std::unique_ptr<SalMenu> QtInstance::CreateMenu(bool bMenuBar, Menu* pVCLMenu) +{ + SolarMutexGuard aGuard; + std::unique_ptr<SalMenu> pRet; + RunInMainThread([&pRet, bMenuBar, pVCLMenu]() { + QtMenu* pSalMenu = new QtMenu(bMenuBar); + pRet.reset(pSalMenu); + pSalMenu->SetMenu(pVCLMenu); + }); + assert(pRet); + return pRet; +} + +std::unique_ptr<SalMenuItem> QtInstance::CreateMenuItem(const SalItemParams& rItemData) +{ + return std::unique_ptr<SalMenuItem>(new QtMenuItem(&rItemData)); +} + +SalTimer* QtInstance::CreateSalTimer() +{ + m_pTimer = new QtTimer(); + return m_pTimer; +} + +SalSystem* QtInstance::CreateSalSystem() { return new QtSystem; } + +std::shared_ptr<SalBitmap> QtInstance::CreateSalBitmap() +{ + if (m_bUseCairo) + return std::make_shared<SvpSalBitmap>(); + else + return std::make_shared<QtBitmap>(); +} + +bool QtInstance::ImplYield(bool bWait, bool bHandleAllCurrentEvents) +{ + // Re-acquire the guard for user events when called via Q_EMIT ImplYieldSignal + SolarMutexGuard aGuard; + bool wasEvent = DispatchUserEvents(bHandleAllCurrentEvents); + if (!bHandleAllCurrentEvents && wasEvent) + return true; + + /** + * Quoting the Qt docs: [QAbstractEventDispatcher::processEvents] processes + * pending events that match flags until there are no more events to process. + */ + SolarMutexReleaser aReleaser; + QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread()); + if (bWait && !wasEvent) + wasEvent = dispatcher->processEvents(QEventLoop::WaitForMoreEvents); + else + wasEvent = dispatcher->processEvents(QEventLoop::AllEvents) || wasEvent; + return wasEvent; +} + +bool QtInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents) +{ + bool bWasEvent = false; + if (qApp->thread() == QThread::currentThread()) + { + bWasEvent = ImplYield(bWait, bHandleAllCurrentEvents); + if (bWasEvent) + m_aWaitingYieldCond.set(); + } + else + { + { + SolarMutexReleaser aReleaser; + bWasEvent = Q_EMIT ImplYieldSignal(false, bHandleAllCurrentEvents); + } + if (!bWasEvent && bWait) + { + m_aWaitingYieldCond.reset(); + SolarMutexReleaser aReleaser; + m_aWaitingYieldCond.wait(); + bWasEvent = true; + } + } + return bWasEvent; +} + +bool QtInstance::AnyInput(VclInputFlags nType) +{ + bool bResult = false; + if (nType & VclInputFlags::TIMER) + bResult |= (m_pTimer && m_pTimer->remainingTime() == 0); + if (nType & VclInputFlags::OTHER) + bResult |= !m_bSleeping; + return bResult; +} + +OUString QtInstance::GetConnectionIdentifier() { return OUString(); } + +void QtInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {} + +#ifndef EMSCRIPTEN +OpenGLContext* QtInstance::CreateOpenGLContext() { return new QtOpenGLContext; } +#endif + +bool QtInstance::IsMainThread() const +{ + return !qApp || (qApp->thread() == QThread::currentThread()); +} + +void QtInstance::TriggerUserEventProcessing() +{ + QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread()); + dispatcher->wakeUp(); +} + +void QtInstance::ProcessEvent(SalUserEvent aEvent) +{ + aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData); +} + +rtl::Reference<QtFilePicker> +QtInstance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context, + QFileDialog::FileMode eMode) +{ + if (!IsMainThread()) + { + SolarMutexGuard g; + rtl::Reference<QtFilePicker> pPicker; + RunInMainThread([&, this]() { pPicker = createPicker(context, eMode); }); + assert(pPicker); + return pPicker; + } + + return new QtFilePicker(context, eMode); +} + +css::uno::Reference<css::ui::dialogs::XFilePicker2> +QtInstance::createFilePicker(const css::uno::Reference<css::uno::XComponentContext>& context) +{ + return css::uno::Reference<css::ui::dialogs::XFilePicker2>( + createPicker(context, QFileDialog::ExistingFile)); +} + +css::uno::Reference<css::ui::dialogs::XFolderPicker2> +QtInstance::createFolderPicker(const css::uno::Reference<css::uno::XComponentContext>& context) +{ + return css::uno::Reference<css::ui::dialogs::XFolderPicker2>( + createPicker(context, QFileDialog::Directory)); +} + +css::uno::Reference<css::uno::XInterface> +QtInstance::CreateClipboard(const css::uno::Sequence<css::uno::Any>& arguments) +{ + OUString sel; + if (arguments.getLength() == 0) + { + sel = "CLIPBOARD"; + } + else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) + { + throw css::lang::IllegalArgumentException("bad QtInstance::CreateClipboard arguments", + css::uno::Reference<css::uno::XInterface>(), -1); + } + + // This could also use RunInMain, but SolarMutexGuard is enough + // since at this point we're not accessing the clipboard, just get the + // accessor to the clipboard. + SolarMutexGuard aGuard; + + auto it = m_aClipboards.find(sel); + if (it != m_aClipboards.end()) + return it->second; + + css::uno::Reference<css::uno::XInterface> xClipboard = QtClipboard::create(sel); + if (xClipboard.is()) + m_aClipboards[sel] = xClipboard; + + return xClipboard; +} + +css::uno::Reference<css::uno::XInterface> +QtInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv) +{ + return vcl::X11DnDHelper(new QtDragSource(), pSysEnv->aShellWindow); +} + +css::uno::Reference<css::uno::XInterface> +QtInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv) +{ + return vcl::X11DnDHelper(new QtDropTarget(), pSysEnv->aShellWindow); +} + +IMPL_LINK_NOARG(QtInstance, updateStyleHdl, Timer*, void) +{ + SolarMutexGuard aGuard; + SalFrame* pFrame = anyFrame(); + if (pFrame) + { + pFrame->CallCallback(SalEvent::SettingsChanged, nullptr); + if (m_bUpdateFonts) + { + pFrame->CallCallback(SalEvent::FontChanged, nullptr); + m_bUpdateFonts = false; + } + } +} + +void QtInstance::UpdateStyle(bool bFontsChanged) +{ + if (bFontsChanged) + m_bUpdateFonts = true; + if (!m_aUpdateStyleTimer.IsActive()) + m_aUpdateStyleTimer.Start(); +} + +void* QtInstance::CreateGStreamerSink(const SystemChildWindow* pWindow) +{ +// As of 2021-09, qt-gstreamer is unmaintained and there is no Qt 6 video sink +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && ENABLE_GSTREAMER_1_0 && QT5_HAVE_GOBJECT + auto pSymbol = gstElementFactoryNameSymbol(); + if (!pSymbol) + return nullptr; + + const SystemEnvData* pEnvData = pWindow->GetSystemData(); + if (!pEnvData) + return nullptr; + + if (pEnvData->platform != SystemEnvData::Platform::Wayland) + return nullptr; + + GstElement* pVideosink = pSymbol("qwidget5videosink", "qwidget5videosink"); + if (pVideosink) + { + QWidget* pQWidget = static_cast<QWidget*>(pEnvData->pWidget); + g_object_set(G_OBJECT(pVideosink), "widget", pQWidget, nullptr); + } + else + { + SAL_WARN("vcl.qt", "Couldn't initialize qwidget5videosink." + " Video playback might not work as expected." + " Please install Qt5 packages for QtGStreamer."); + // with no videosink explicitly set, GStreamer will open its own (misplaced) window(s) to display video + } + + return pVideosink; +#else + Q_UNUSED(pWindow); + return nullptr; +#endif +} + +void QtInstance::connectQScreenSignals(const QScreen* pScreen) +{ + connect(pScreen, &QScreen::orientationChanged, this, &QtInstance::orientationChanged); + connect(pScreen, &QScreen::virtualGeometryChanged, this, &QtInstance::virtualGeometryChanged); +} + +void QtInstance::notifyDisplayChanged() +{ + SolarMutexGuard aGuard; + SalFrame* pAnyFrame = anyFrame(); + if (pAnyFrame) + pAnyFrame->CallCallback(SalEvent::DisplayChanged, nullptr); +} + +void QtInstance::orientationChanged(Qt::ScreenOrientation) { notifyDisplayChanged(); } + +void QtInstance::primaryScreenChanged(QScreen*) { notifyDisplayChanged(); } + +void QtInstance::screenAdded(QScreen* pScreen) +{ + connectQScreenSignals(pScreen); + if (QApplication::screens().size() == 1) + notifyDisplayChanged(); +} + +void QtInstance::screenRemoved(QScreen*) { notifyDisplayChanged(); } + +void QtInstance::virtualGeometryChanged(const QRect&) { notifyDisplayChanged(); } + +void QtInstance::AllocFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv, + std::unique_ptr<int>& rFakeArgc, + std::vector<FreeableCStr>& rFakeArgvFreeable) +{ + OString aVersion(qVersion()); + SAL_INFO("vcl.qt", "qt version string is " << aVersion); + + const sal_uInt32 nParams = osl_getCommandArgCount(); + sal_uInt32 nDisplayValueIdx = 0; + OUString aParam, aBin; + + for (sal_uInt32 nIdx = 0; nIdx < nParams; ++nIdx) + { + osl_getCommandArg(nIdx, &aParam.pData); + if (aParam != "-display") + continue; + ++nIdx; + nDisplayValueIdx = nIdx; + } + + osl_getExecutableFile(&aParam.pData); + osl_getSystemPathFromFileURL(aParam.pData, &aBin.pData); + OString aExec = OUStringToOString(aBin, osl_getThreadTextEncoding()); + + std::vector<FreeableCStr> aFakeArgvFreeable; + aFakeArgvFreeable.reserve(4); + aFakeArgvFreeable.emplace_back(strdup(aExec.getStr())); + aFakeArgvFreeable.emplace_back(strdup("--nocrashhandler")); + if (nDisplayValueIdx) + { + aFakeArgvFreeable.emplace_back(strdup("-display")); + osl_getCommandArg(nDisplayValueIdx, &aParam.pData); + OString aDisplay = OUStringToOString(aParam, osl_getThreadTextEncoding()); + aFakeArgvFreeable.emplace_back(strdup(aDisplay.getStr())); + } + rFakeArgvFreeable.swap(aFakeArgvFreeable); + + const int nFakeArgc = rFakeArgvFreeable.size(); + rFakeArgv.reset(new char*[nFakeArgc]); + for (int i = 0; i < nFakeArgc; i++) + rFakeArgv[i] = rFakeArgvFreeable[i].get(); + + rFakeArgc.reset(new int); + *rFakeArgc = nFakeArgc; +} + +void QtInstance::MoveFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv, + std::unique_ptr<int>& rFakeArgc, + std::vector<FreeableCStr>& rFakeArgvFreeable) +{ + m_pFakeArgv = std::move(rFakeArgv); + m_pFakeArgc = std::move(rFakeArgc); + m_pFakeArgvFreeable.swap(rFakeArgvFreeable); +} + +std::unique_ptr<QApplication> QtInstance::CreateQApplication(int& nArgc, char** pArgv) +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // for Qt 6, setting Qt::AA_EnableHighDpiScaling and Qt::AA_UseHighDpiPixmaps + // is deprecated, they're always enabled + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + // for scaled icons in the native menus + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#endif + + FreeableCStr session_manager; + if (getenv("SESSION_MANAGER") != nullptr) + { + session_manager.reset(strdup(getenv("SESSION_MANAGER"))); + unsetenv("SESSION_MANAGER"); + } + + std::unique_ptr<QApplication> pQApp = std::make_unique<QApplication>(nArgc, pArgv); + + if (session_manager != nullptr) + { + // coverity[tainted_string] - trusted source for setenv + setenv("SESSION_MANAGER", session_manager.get(), 1); + } + + QApplication::setQuitOnLastWindowClosed(false); + return pQApp; +} + +bool QtInstance::DoExecute(int& nExitCode) +{ + const bool bIsOnSystemEventLoop = Application::IsOnSystemEventLoop(); + if (bIsOnSystemEventLoop) + nExitCode = QApplication::exec(); + return bIsOnSystemEventLoop; +} + +void QtInstance::DoQuit() +{ + if (Application::IsOnSystemEventLoop()) + QApplication::quit(); +} + +void QtInstance::setActivePopup(QtFrame* pFrame) +{ + assert(!pFrame || pFrame->isPopup()); + m_pActivePopup = pFrame; +} + +extern "C" { +VCLPLUG_QT_PUBLIC SalInstance* create_SalInstance() +{ + std::unique_ptr<char* []> pFakeArgv; + std::unique_ptr<int> pFakeArgc; + std::vector<FreeableCStr> aFakeArgvFreeable; + QtInstance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable); + + std::unique_ptr<QApplication> pQApp + = QtInstance::CreateQApplication(*pFakeArgc, pFakeArgv.get()); + + QtInstance* pInstance = new QtInstance(pQApp); + pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable); + + new QtData(); + + return pInstance; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |