1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <Qt5Instance.hxx>
21 #include <Qt5Instance.moc>
22 
23 #include <com/sun/star/lang/IllegalArgumentException.hpp>
24 
25 #include <Qt5Bitmap.hxx>
26 #include <Qt5Clipboard.hxx>
27 #include <Qt5Data.hxx>
28 #include <Qt5DragAndDrop.hxx>
29 #include <Qt5FilePicker.hxx>
30 #include <Qt5Frame.hxx>
31 #include <Qt5Menu.hxx>
32 #include <Qt5Object.hxx>
33 #include <Qt5OpenGLContext.hxx>
34 #include "Qt5SvpVirtualDevice.hxx"
35 #include <Qt5System.hxx>
36 #include <Qt5Timer.hxx>
37 #include <Qt5VirtualDevice.hxx>
38 
39 #include <headless/svpvd.hxx>
40 
41 #include <QtCore/QAbstractEventDispatcher>
42 #include <QtCore/QThread>
43 #include <QtWidgets/QApplication>
44 #include <QtWidgets/QWidget>
45 
46 #include <vclpluginapi.h>
47 #include <tools/debug.hxx>
48 #include <comphelper/flagguard.hxx>
49 #include <sal/log.hxx>
50 #include <osl/process.h>
51 #include <unx/gstsink.hxx>
52 #include <headless/svpbmp.hxx>
53 
54 #include <mutex>
55 #include <condition_variable>
56 
57 /// TODO: not much Qt5 specific here? could be generalised, esp. for OSX...
58 /// this subclass allows for the transfer of a closure for running on the main
59 /// thread, to handle all the thread affine stuff in Qt5; the SolarMutex is
60 /// "loaned" to the main thread for the execution of the closure.
61 /// @note it doesn't work to just use "emit" and signals/slots to move calls to
62 /// the main thread, because the other thread has the SolarMutex; the other
63 /// thread (typically) cannot release SolarMutex, because then the main thread
64 /// will handle all sorts of events and whatnot; this design ensures that the
65 /// main thread only runs the passed closure (unless the closure releases
66 /// SolarMutex itself, which should probably be avoided).
67 class Qt5YieldMutex : public SalYieldMutex
68 {
69 public:
70     /// flag only accessed on main thread:
71     /// main thread has "borrowed" SolarMutex from another thread
72     bool m_bNoYieldLock = false;
73     /// members for communication from non-main thread to main thread
74     std::mutex m_RunInMainMutex;
75     std::condition_variable m_InMainCondition;
76     bool m_isWakeUpMain = false;
77     std::function<void()> m_Closure; ///< code for main thread to run
78     /// members for communication from main thread to non-main thread
79     std::condition_variable m_ResultCondition;
80     bool m_isResultReady = false;
81 
82     virtual bool IsCurrentThread() const override;
83     virtual void doAcquire(sal_uInt32 nLockCount) override;
84     virtual sal_uInt32 doRelease(bool const bUnlockAll) override;
85 };
86 
IsCurrentThread() const87 bool Qt5YieldMutex::IsCurrentThread() const
88 {
89     auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
90     assert(pSalInst);
91     if (pSalInst->IsMainThread() && m_bNoYieldLock)
92     {
93         return true; // main thread has borrowed SolarMutex
94     }
95     return SalYieldMutex::IsCurrentThread();
96 }
97 
doAcquire(sal_uInt32 nLockCount)98 void Qt5YieldMutex::doAcquire(sal_uInt32 nLockCount)
99 {
100     auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
101     assert(pSalInst);
102     if (!pSalInst->IsMainThread())
103     {
104         SalYieldMutex::doAcquire(nLockCount);
105         return;
106     }
107     if (m_bNoYieldLock)
108     {
109         return; // special case for main thread: borrowed from other thread
110     }
111     do // main thread acquire...
112     {
113         std::function<void()> func; // copy of closure on thread stack
114         {
115             std::unique_lock<std::mutex> g(m_RunInMainMutex);
116             if (m_aMutex.tryToAcquire())
117             {
118                 // if there's a closure, the other thread holds m_aMutex
119                 assert(!m_Closure);
120                 m_isWakeUpMain = false;
121                 --nLockCount; // have acquired once!
122                 ++m_nCount;
123                 break;
124             }
125             m_InMainCondition.wait(g, [this]() { return m_isWakeUpMain; });
126             m_isWakeUpMain = false;
127             std::swap(func, m_Closure);
128         }
129         if (func)
130         {
131             assert(!m_bNoYieldLock);
132             m_bNoYieldLock = true; // execute closure with borrowed SolarMutex
133             func();
134             m_bNoYieldLock = false;
135             std::scoped_lock<std::mutex> g(m_RunInMainMutex);
136             assert(!m_isResultReady);
137             m_isResultReady = true;
138             m_ResultCondition.notify_all(); // unblock other thread
139         }
140     } while (true);
141     SalYieldMutex::doAcquire(nLockCount);
142 }
143 
doRelease(bool const bUnlockAll)144 sal_uInt32 Qt5YieldMutex::doRelease(bool const bUnlockAll)
145 {
146     auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
147     assert(pSalInst);
148     if (pSalInst->IsMainThread() && m_bNoYieldLock)
149     {
150         return 1; // dummy value
151     }
152 
153     std::scoped_lock<std::mutex> g(m_RunInMainMutex);
154     // read m_nCount before doRelease (it's guarded by m_aMutex)
155     bool const isReleased(bUnlockAll || m_nCount == 1);
156     sal_uInt32 nCount = SalYieldMutex::doRelease(bUnlockAll);
157     if (isReleased && !pSalInst->IsMainThread())
158     {
159         m_isWakeUpMain = true;
160         m_InMainCondition.notify_all(); // unblock main thread
161     }
162     return nCount;
163 }
164 
165 // this could be abstracted to be independent of Qt5 by passing in the
166 // event-trigger as another function parameter...
167 // it could also be a template of the return type, then it could return the
168 // result of func... but then how to handle the result in doAcquire?
RunInMainThread(std::function<void ()> func)169 void Qt5Instance::RunInMainThread(std::function<void()> func)
170 {
171     DBG_TESTSOLARMUTEX();
172     if (IsMainThread())
173     {
174         func();
175         return;
176     }
177 
178     Qt5YieldMutex* const pMutex(static_cast<Qt5YieldMutex*>(GetYieldMutex()));
179     {
180         std::scoped_lock<std::mutex> g(pMutex->m_RunInMainMutex);
181         assert(!pMutex->m_Closure);
182         pMutex->m_Closure = func;
183         // unblock main thread in case it is blocked on condition
184         pMutex->m_isWakeUpMain = true;
185         pMutex->m_InMainCondition.notify_all();
186     }
187     // wake up main thread in case it is blocked on event queue
188     // TriggerUserEventProcessing() appears to be insufficient in case the
189     // main thread does QEventLoop::WaitForMoreEvents
190     Q_EMIT ImplRunInMainSignal();
191     {
192         std::unique_lock<std::mutex> g(pMutex->m_RunInMainMutex);
193         pMutex->m_ResultCondition.wait(g, [pMutex]() { return pMutex->m_isResultReady; });
194         pMutex->m_isResultReady = false;
195     }
196 }
197 
ImplRunInMain()198 void Qt5Instance::ImplRunInMain()
199 {
200     SolarMutexGuard g; // trigger the dispatch code in Qt5YieldMutex::doAcquire
201     (void)this; // suppress unhelpful [loplugin:staticmethods]; can't be static
202 }
203 
Qt5Instance(std::unique_ptr<QApplication> & pQApp,bool bUseCairo)204 Qt5Instance::Qt5Instance(std::unique_ptr<QApplication>& pQApp, bool bUseCairo)
205     : SalGenericInstance(std::make_unique<Qt5YieldMutex>())
206     , m_postUserEventId(-1)
207     , m_bUseCairo(bUseCairo)
208     , m_pQApplication(std::move(pQApp))
209     , m_aUpdateStyleTimer("vcl::qt5 m_aUpdateStyleTimer")
210     , m_bUpdateFonts(false)
211 {
212     ImplSVData* pSVData = ImplGetSVData();
213     if (bUseCairo)
214         pSVData->maAppData.mxToolkitName = OUString("qt5+cairo");
215     else
216         pSVData->maAppData.mxToolkitName = OUString("qt5");
217 
218     m_postUserEventId = QEvent::registerEventType();
219 
220     // this one needs to be blocking, so that the handling in main thread
221     // is processed before the thread emitting the signal continues
222     connect(this, SIGNAL(ImplYieldSignal(bool, bool)), this, SLOT(ImplYield(bool, bool)),
223             Qt::BlockingQueuedConnection);
224     connect(this, &Qt5Instance::ImplRunInMainSignal, this, &Qt5Instance::ImplRunInMain,
225             Qt::QueuedConnection); // no Blocking!
226 
227     // this one needs to be queued non-blocking
228     // in order to have this event arriving to correct event processing loop
229     connect(this, &Qt5Instance::deleteObjectLaterSignal, this,
230             [](QObject* pObject) { Qt5Instance::deleteObjectLater(pObject); },
231             Qt::QueuedConnection);
232 
233     m_aUpdateStyleTimer.SetTimeout(50);
234     m_aUpdateStyleTimer.SetInvokeHandler(LINK(this, Qt5Instance, updateStyleHdl));
235 }
236 
~Qt5Instance()237 Qt5Instance::~Qt5Instance()
238 {
239     // force freeing the QApplication before freeing the arguments,
240     // as it uses references to the provided arguments!
241     m_pQApplication.reset();
242 }
243 
AfterAppInit()244 void Qt5Instance::AfterAppInit()
245 {
246     // set the default application icon via desktop file just on Wayland,
247     // as this otherwise overrides the individual desktop icons on X11.
248     if (QGuiApplication::platformName() == "wayland")
249         QGuiApplication::setDesktopFileName(QStringLiteral("libreoffice-startcenter.desktop"));
250     QGuiApplication::setLayoutDirection(AllSettings::GetLayoutRTL() ? Qt::RightToLeft
251                                                                     : Qt::LeftToRight);
252 }
253 
deleteObjectLater(QObject * pObject)254 void Qt5Instance::deleteObjectLater(QObject* pObject) { pObject->deleteLater(); }
255 
CreateChildFrame(SystemParentData *,SalFrameStyleFlags nStyle)256 SalFrame* Qt5Instance::CreateChildFrame(SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle)
257 {
258     return new Qt5Frame(nullptr, nStyle, m_bUseCairo);
259 }
260 
CreateFrame(SalFrame * pParent,SalFrameStyleFlags nStyle)261 SalFrame* Qt5Instance::CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle)
262 {
263     assert(!pParent || dynamic_cast<Qt5Frame*>(pParent));
264     SalFrame* pRet(nullptr);
265     bool bUseCairo = m_bUseCairo;
266     RunInMainThread([&pRet, pParent, nStyle, bUseCairo]() {
267         pRet = new Qt5Frame(static_cast<Qt5Frame*>(pParent), nStyle, bUseCairo);
268     });
269     assert(pRet);
270     return pRet;
271 }
272 
DestroyFrame(SalFrame * pFrame)273 void Qt5Instance::DestroyFrame(SalFrame* pFrame)
274 {
275     if (pFrame)
276     {
277         assert(dynamic_cast<Qt5Frame*>(pFrame));
278         Q_EMIT deleteObjectLaterSignal(static_cast<Qt5Frame*>(pFrame));
279     }
280 }
281 
CreateObject(SalFrame * pParent,SystemWindowData *,bool bShow)282 SalObject* Qt5Instance::CreateObject(SalFrame* pParent, SystemWindowData*, bool bShow)
283 {
284     assert(!pParent || dynamic_cast<Qt5Frame*>(pParent));
285     return new Qt5Object(static_cast<Qt5Frame*>(pParent), bShow);
286 }
287 
DestroyObject(SalObject * pObject)288 void Qt5Instance::DestroyObject(SalObject* pObject)
289 {
290     if (pObject)
291     {
292         assert(dynamic_cast<Qt5Object*>(pObject));
293         Q_EMIT deleteObjectLaterSignal(static_cast<Qt5Object*>(pObject));
294     }
295 }
296 
CreateVirtualDevice(SalGraphics * pGraphics,long & nDX,long & nDY,DeviceFormat eFormat,const SystemGraphicsData * pGd)297 std::unique_ptr<SalVirtualDevice> Qt5Instance::CreateVirtualDevice(SalGraphics* pGraphics,
298                                                                    long& nDX, long& nDY,
299                                                                    DeviceFormat eFormat,
300                                                                    const SystemGraphicsData* pGd)
301 {
302     if (m_bUseCairo)
303     {
304         SvpSalGraphics* pSvpSalGraphics = dynamic_cast<Qt5SvpGraphics*>(pGraphics);
305         assert(pSvpSalGraphics);
306         // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
307         cairo_surface_t* pPreExistingTarget
308             = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
309         std::unique_ptr<SalVirtualDevice> pVD(
310             new Qt5SvpVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget));
311         pVD->SetSize(nDX, nDY);
312         return pVD;
313     }
314     else
315     {
316         std::unique_ptr<SalVirtualDevice> pVD(new Qt5VirtualDevice(eFormat, 1));
317         pVD->SetSize(nDX, nDY);
318         return pVD;
319     }
320 }
321 
CreateMenu(bool bMenuBar,Menu * pVCLMenu)322 std::unique_ptr<SalMenu> Qt5Instance::CreateMenu(bool bMenuBar, Menu* pVCLMenu)
323 {
324     std::unique_ptr<SalMenu> pRet;
325     RunInMainThread([&pRet, bMenuBar, pVCLMenu]() {
326         Qt5Menu* pSalMenu = new Qt5Menu(bMenuBar);
327         pRet.reset(pSalMenu);
328         pSalMenu->SetMenu(pVCLMenu);
329     });
330     assert(pRet);
331     return pRet;
332 }
333 
CreateMenuItem(const SalItemParams & rItemData)334 std::unique_ptr<SalMenuItem> Qt5Instance::CreateMenuItem(const SalItemParams& rItemData)
335 {
336     return std::unique_ptr<SalMenuItem>(new Qt5MenuItem(&rItemData));
337 }
338 
CreateSalTimer()339 SalTimer* Qt5Instance::CreateSalTimer() { return new Qt5Timer(); }
340 
CreateSalSystem()341 SalSystem* Qt5Instance::CreateSalSystem() { return new Qt5System; }
342 
CreateSalBitmap()343 std::shared_ptr<SalBitmap> Qt5Instance::CreateSalBitmap()
344 {
345     if (m_bUseCairo)
346         return std::make_shared<SvpSalBitmap>();
347     else
348         return std::make_shared<Qt5Bitmap>();
349 }
350 
ImplYield(bool bWait,bool bHandleAllCurrentEvents)351 bool Qt5Instance::ImplYield(bool bWait, bool bHandleAllCurrentEvents)
352 {
353     // Re-acquire the guard for user events when called via Q_EMIT ImplYieldSignal
354     SolarMutexGuard aGuard;
355     bool wasEvent = DispatchUserEvents(bHandleAllCurrentEvents);
356     if (!bHandleAllCurrentEvents && wasEvent)
357         return true;
358 
359     /**
360      * Quoting the Qt docs: [QAbstractEventDispatcher::processEvents] processes
361      * pending events that match flags until there are no more events to process.
362      */
363     SolarMutexReleaser aReleaser;
364     QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread());
365     if (bWait && !wasEvent)
366         wasEvent = dispatcher->processEvents(QEventLoop::WaitForMoreEvents);
367     else
368         wasEvent = dispatcher->processEvents(QEventLoop::AllEvents) || wasEvent;
369     return wasEvent;
370 }
371 
DoYield(bool bWait,bool bHandleAllCurrentEvents)372 bool Qt5Instance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
373 {
374     bool bWasEvent = false;
375     if (qApp->thread() == QThread::currentThread())
376     {
377         bWasEvent = ImplYield(bWait, bHandleAllCurrentEvents);
378         if (bWasEvent)
379             m_aWaitingYieldCond.set();
380     }
381     else
382     {
383         {
384             SolarMutexReleaser aReleaser;
385             bWasEvent = Q_EMIT ImplYieldSignal(false, bHandleAllCurrentEvents);
386         }
387         if (!bWasEvent && bWait)
388         {
389             m_aWaitingYieldCond.reset();
390             SolarMutexReleaser aReleaser;
391             m_aWaitingYieldCond.wait();
392             bWasEvent = true;
393         }
394     }
395     return bWasEvent;
396 }
397 
AnyInput(VclInputFlags)398 bool Qt5Instance::AnyInput(VclInputFlags /*nType*/) { return false; }
399 
GetConnectionIdentifier()400 OUString Qt5Instance::GetConnectionIdentifier() { return OUString(); }
401 
AddToRecentDocumentList(const OUString &,const OUString &,const OUString &)402 void Qt5Instance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {}
403 
CreateOpenGLContext()404 OpenGLContext* Qt5Instance::CreateOpenGLContext() { return new Qt5OpenGLContext; }
405 
IsMainThread() const406 bool Qt5Instance::IsMainThread() const
407 {
408     return !qApp || (qApp->thread() == QThread::currentThread());
409 }
410 
TriggerUserEventProcessing()411 void Qt5Instance::TriggerUserEventProcessing()
412 {
413     QApplication::postEvent(this, new QEvent(QEvent::Type(m_postUserEventId)));
414 }
415 
ProcessEvent(SalUserEvent aEvent)416 void Qt5Instance::ProcessEvent(SalUserEvent aEvent)
417 {
418     aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
419 }
420 
421 Qt5FilePicker*
createPicker(css::uno::Reference<css::uno::XComponentContext> const & context,QFileDialog::FileMode eMode)422 Qt5Instance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
423                           QFileDialog::FileMode eMode)
424 {
425     if (!IsMainThread())
426     {
427         SolarMutexGuard g;
428         Qt5FilePicker* pPicker;
429         RunInMainThread([&pPicker, this, context, eMode]() { pPicker = createPicker(context, eMode); });
430         assert(pPicker);
431         return pPicker;
432     }
433 
434     return new Qt5FilePicker(context, eMode);
435 }
436 
437 css::uno::Reference<css::ui::dialogs::XFilePicker2>
createFilePicker(const css::uno::Reference<css::uno::XComponentContext> & context)438 Qt5Instance::createFilePicker(const css::uno::Reference<css::uno::XComponentContext>& context)
439 {
440     return css::uno::Reference<css::ui::dialogs::XFilePicker2>(
441         createPicker(context, QFileDialog::ExistingFile));
442 }
443 
444 css::uno::Reference<css::ui::dialogs::XFolderPicker2>
createFolderPicker(const css::uno::Reference<css::uno::XComponentContext> & context)445 Qt5Instance::createFolderPicker(const css::uno::Reference<css::uno::XComponentContext>& context)
446 {
447     return css::uno::Reference<css::ui::dialogs::XFolderPicker2>(
448         createPicker(context, QFileDialog::Directory));
449 }
450 
451 css::uno::Reference<css::uno::XInterface>
CreateClipboard(const css::uno::Sequence<css::uno::Any> & arguments)452 Qt5Instance::CreateClipboard(const css::uno::Sequence<css::uno::Any>& arguments)
453 {
454     OUString sel;
455     if (arguments.getLength() == 0)
456     {
457         sel = "CLIPBOARD";
458     }
459     else if (arguments.getLength() != 1 || !(arguments[0] >>= sel))
460     {
461         throw css::lang::IllegalArgumentException("bad Qt5Instance::CreateClipboard arguments",
462                                                   css::uno::Reference<css::uno::XInterface>(), -1);
463     }
464 
465     // This could also use RunInMain, but SolarMutexGuard is enough
466     // since at this point we're not accessing the clipboard, just get the
467     // accessor to the clipboard.
468     SolarMutexGuard aGuard;
469 
470     auto it = m_aClipboards.find(sel);
471     if (it != m_aClipboards.end())
472         return it->second;
473 
474     css::uno::Reference<css::uno::XInterface> xClipboard = Qt5Clipboard::create(sel);
475     if (xClipboard.is())
476         m_aClipboards[sel] = xClipboard;
477 
478     return xClipboard;
479 }
480 
CreateDragSource()481 css::uno::Reference<css::uno::XInterface> Qt5Instance::CreateDragSource()
482 {
483     return css::uno::Reference<css::uno::XInterface>(
484         static_cast<cppu::OWeakObject*>(new Qt5DragSource()));
485 }
486 
CreateDropTarget()487 css::uno::Reference<css::uno::XInterface> Qt5Instance::CreateDropTarget()
488 {
489     return css::uno::Reference<css::uno::XInterface>(
490         static_cast<cppu::OWeakObject*>(new Qt5DropTarget()));
491 }
492 
IMPL_LINK_NOARG(Qt5Instance,updateStyleHdl,Timer *,void)493 IMPL_LINK_NOARG(Qt5Instance, updateStyleHdl, Timer*, void)
494 {
495     SolarMutexGuard aGuard;
496     SalFrame* pFrame = anyFrame();
497     if (pFrame)
498     {
499         pFrame->CallCallback(SalEvent::SettingsChanged, nullptr);
500         if (m_bUpdateFonts)
501         {
502             pFrame->CallCallback(SalEvent::FontChanged, nullptr);
503             m_bUpdateFonts = false;
504         }
505     }
506 }
507 
UpdateStyle(bool bFontsChanged)508 void Qt5Instance::UpdateStyle(bool bFontsChanged)
509 {
510     if (bFontsChanged)
511         m_bUpdateFonts = true;
512     if (!m_aUpdateStyleTimer.IsActive())
513         m_aUpdateStyleTimer.Start();
514 }
515 
CreateGStreamerSink(const SystemChildWindow * pWindow)516 void* Qt5Instance::CreateGStreamerSink(const SystemChildWindow* pWindow)
517 {
518 #if ENABLE_GSTREAMER_1_0 && QT5_HAVE_GOBJECT
519     auto pSymbol = gstElementFactoryNameSymbol();
520     if (!pSymbol)
521         return nullptr;
522 
523     const SystemEnvData* pEnvData = pWindow->GetSystemData();
524     if (!pEnvData)
525         return nullptr;
526 
527     if (pEnvData->platform != SystemEnvData::Platform::Wayland)
528         return nullptr;
529 
530     GstElement* pVideosink = pSymbol("qwidget5videosink", "qwidget5videosink");
531     if (pVideosink)
532     {
533         QWidget* pQWidget = static_cast<QWidget*>(pEnvData->pWidget);
534         g_object_set(G_OBJECT(pVideosink), "widget", pQWidget, nullptr);
535     }
536     else
537     {
538         SAL_WARN("vcl.qt5", "Couldn't initialize qwidget5videosink."
539                             " Video playback might not work as expected."
540                             " Please install Qt5 packages for QtGStreamer.");
541         // with no videosink explicitly set, GStreamer will open its own (misplaced) window(s) to display video
542     }
543 
544     return pVideosink;
545 #else
546     (void*)pWindow;
547     return nullptr;
548 #endif
549 }
550 
AllocFakeCmdlineArgs(std::unique_ptr<char * []> & rFakeArgv,std::unique_ptr<int> & rFakeArgc,std::vector<FreeableCStr> & rFakeArgvFreeable)551 void Qt5Instance::AllocFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
552                                        std::unique_ptr<int>& rFakeArgc,
553                                        std::vector<FreeableCStr>& rFakeArgvFreeable)
554 {
555     OString aVersion(qVersion());
556     SAL_INFO("vcl.qt5", "qt version string is " << aVersion);
557 
558     const sal_uInt32 nParams = osl_getCommandArgCount();
559     OString aDisplay;
560     sal_uInt32 nDisplayValueIdx = 0;
561     OUString aParam, aBin;
562 
563     for (sal_uInt32 nIdx = 0; nIdx < nParams; ++nIdx)
564     {
565         osl_getCommandArg(nIdx, &aParam.pData);
566         if (aParam != "-display")
567             continue;
568         ++nIdx;
569         nDisplayValueIdx = nIdx;
570     }
571 
572     osl_getExecutableFile(&aParam.pData);
573     osl_getSystemPathFromFileURL(aParam.pData, &aBin.pData);
574     OString aExec = OUStringToOString(aBin, osl_getThreadTextEncoding());
575 
576     std::vector<FreeableCStr> aFakeArgvFreeable;
577     aFakeArgvFreeable.reserve(4);
578     aFakeArgvFreeable.emplace_back(strdup(aExec.getStr()));
579     aFakeArgvFreeable.emplace_back(strdup("--nocrashhandler"));
580     if (nDisplayValueIdx)
581     {
582         aFakeArgvFreeable.emplace_back(strdup("-display"));
583         osl_getCommandArg(nDisplayValueIdx, &aParam.pData);
584         aDisplay = OUStringToOString(aParam, osl_getThreadTextEncoding());
585         aFakeArgvFreeable.emplace_back(strdup(aDisplay.getStr()));
586     }
587     rFakeArgvFreeable.swap(aFakeArgvFreeable);
588 
589     const int nFakeArgc = rFakeArgvFreeable.size();
590     rFakeArgv.reset(new char*[nFakeArgc]);
591     for (int i = 0; i < nFakeArgc; i++)
592         rFakeArgv[i] = rFakeArgvFreeable[i].get();
593 
594     rFakeArgc.reset(new int);
595     *rFakeArgc = nFakeArgc;
596 }
597 
MoveFakeCmdlineArgs(std::unique_ptr<char * []> & rFakeArgv,std::unique_ptr<int> & rFakeArgc,std::vector<FreeableCStr> & rFakeArgvFreeable)598 void Qt5Instance::MoveFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
599                                       std::unique_ptr<int>& rFakeArgc,
600                                       std::vector<FreeableCStr>& rFakeArgvFreeable)
601 {
602     m_pFakeArgv = std::move(rFakeArgv);
603     m_pFakeArgc = std::move(rFakeArgc);
604     m_pFakeArgvFreeable.swap(rFakeArgvFreeable);
605 }
606 
CreateQApplication(int & nArgc,char ** pArgv)607 std::unique_ptr<QApplication> Qt5Instance::CreateQApplication(int& nArgc, char** pArgv)
608 {
609     QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
610 
611     FreeableCStr session_manager;
612     if (getenv("SESSION_MANAGER") != nullptr)
613     {
614         session_manager.reset(strdup(getenv("SESSION_MANAGER")));
615         unsetenv("SESSION_MANAGER");
616     }
617 
618     std::unique_ptr<QApplication> pQApp = std::make_unique<QApplication>(nArgc, pArgv);
619 
620     if (session_manager != nullptr)
621     {
622         // coverity[tainted_string] - trusted source for setenv
623         setenv("SESSION_MANAGER", session_manager.get(), 1);
624     }
625 
626     QApplication::setQuitOnLastWindowClosed(false);
627     return pQApp;
628 }
629 
630 extern "C" {
create_SalInstance()631 VCLPLUG_QT5_PUBLIC SalInstance* create_SalInstance()
632 {
633     static const bool bUseCairo = true; // (nullptr != getenv("SAL_VCL_QT5_USE_CAIRO"));
634 
635     std::unique_ptr<char* []> pFakeArgv;
636     std::unique_ptr<int> pFakeArgc;
637     std::vector<FreeableCStr> aFakeArgvFreeable;
638     Qt5Instance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
639 
640     std::unique_ptr<QApplication> pQApp
641         = Qt5Instance::CreateQApplication(*pFakeArgc, pFakeArgv.get());
642 
643     Qt5Instance* pInstance = new Qt5Instance(pQApp, bUseCairo);
644     pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
645 
646     new Qt5Data(pInstance);
647 
648     return pInstance;
649 }
650 }
651 
652 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
653