Experimental Emscripten Qt6 --enable-emscripten-proxy-to-pthread ersatz
With Qt5, the (enabled by default) --enable-emscripten-proxy-to-pthread (i.e., leveraging Emscripten's -sPROXY_TO_PTHREAD) works reasonably well with the handful of hacks we added to our Qt5 5.15.2+wasm branch. However, for Qt6 any necessary hacking there would need to be excessive and looks unrealistic (see the mailing list thread starting at <https://lists.qt-project.org/pipermail/development/2024-December/045960.html> "[Development] Wasm: Support for Emscripten PROXY_TO_PTHREAD?" and the presentation at <https://fosdem.org/2025/schedule/event/fosdem-2025-5169-lowa-in-need-of-a-vcl-plug/>). But when leveraging the upcoming browser support for JSPI (through Emscripten's -sJSPI, Qt6's -feature-wasm-jspi, and our --enable-emscripten-jspi), it looks feasible to leave the Qt6 code alone and instead of proxying the complete application main thread (including the Qt6 event handling) off the browser main thread to an additional thread (automatically set up through Emscripten -sPROXY_TO_PTHREAD), to just proxy some of the LO event handling off the browser main thread to an additional thread (set up through a new comphelper::emscriptenthreading::setUp). That way, problematic LO code that is triggered from event handling (like "Tools - Extensions..." wanting to spawn additional threads, which requires the browser main thread to be unblocked) can be offloaded to the additional thread and get the browser main thread unblocked. Thus there is a new "Emscripten Qt6 JSPI/non-PROXY_TO_PTHREAD" mode now (via --enable-qt6 --enable-emscripten-jspi --disable-emscripten-proxy-to-pthread). It is still experimental and known to occasionally hang and crash, but has been seen to generally work at least with recent emsdk 4.0.3 and a recent Qt dev branch (towards Qt 6.10, built with -feature-wasm-jspi) and running on recent Chrome 132 with "Experimental WebAssembly JavaScript Promise Integration (JSPI)" enabled under <chrome://flags>. There are two places where code is proxied off the browser main thread to the additional thread: One is QtInstance::ProcessEvent, the other is invoking certain kinds of tasks (just "vcl::Dialog maLayoutIdle", for now) in Scheduler::CallbackTaskScheduling (unconditionally proxying off all kinds of tasks there appeared to cause more issues than it would solve). For the latter, the concept of "transferability" has been added to class Task; the new Emscripten mode is the only case making use of that concept. Also for the latter, it is important that the browser main thread does not get blocked at the > SolarMutexGuard aGuard; in QtTimer::timeoutActivated when such a timer event happens while the additional thread has the SolarMutex locked and tries to do something that requires the browser main thread to not be blocked (like spawning additional threads upon "Tools - Extensions..."). Therefore, as a bad hack, and only for the new Emscripen mode, that SolarMutexGuard has been moved from QtTimer::timeoutActivated down to the call of pTask->Invoke() in Scheduler::CallbackTaskScheduling (see the two TODO comments added to the code). This appears to work relatively well, but is still a bad hack that should rather be done properly. And then there's some code across vcl/qt5 that may now end up running proxied off the browser main thread, but which actually requires to be run on the browser main thread (because, e.g., it calls into Qt6 code that accesses JS entities that are only available there). Those places use a newly introduced QtInstance::EmscriptenLightweightRunInMainThread that takes some code via a lambda: For the new Emscripten mode, the code is explicitly proxied back to the browser main thread, while in all other cases it is run directly in place. Some further notes: * -sPTHREAD_POOL_SIZE needed to be bumped by one, to accommodate for the additional new thread. * The code requires two additional JSPI "entry points", _emscripten_check_mailbox (to complement the suspension in QtInstance::ProcessEvent) and the invocation of the Qt6-internal qstdweb::EventListener::handleEvent (to complement a Qt6-internal suspension point hit when e.g. doing "Tools - Extension Manager..." and clicking "Add"). * The new Emscripten mode requires -fexperimental-library (for std::jthread and std::stop_token) when building at least with emsdk 4.0.3, but it shouldn't hurt to have that switch enabled unconditionally for Emscripten builds (which are self-contained, so can't be hit by any compatibility issues that switch might cause). Change-Id: Id3b13e2cc3c304b072f388c6d736e7e663b36c58 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181424 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <stephan.bergmann@allotropia.de> Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
This commit is contained in:
@@ -107,6 +107,7 @@ $(eval $(call gb_Library_add_exception_objects,comphelper,\
|
||||
comphelper/source/misc/docpasswordhelper \
|
||||
comphelper/source/misc/docpasswordrequest \
|
||||
comphelper/source/misc/documentinfo \
|
||||
comphelper/source/misc/emscriptenthreading \
|
||||
comphelper/source/misc/errcode \
|
||||
comphelper/source/misc/evtlistenerhlp \
|
||||
comphelper/source/misc/evtmethodhelper \
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <sal/config.h>
|
||||
|
||||
#include <comphelper/emscriptenthreading.hxx>
|
||||
#include <config_emscripten.h>
|
||||
#include <config_vclplug.h>
|
||||
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::mutex mutex;
|
||||
comphelper::emscriptenthreading::Data* data = nullptr;
|
||||
}
|
||||
|
||||
void comphelper::emscriptenthreading::setUp()
|
||||
{
|
||||
std::scoped_lock g(mutex);
|
||||
assert(data == nullptr);
|
||||
data = new Data;
|
||||
data->eventHandlerThread = std::jthread([](std::stop_token token) {
|
||||
while (!token.stop_requested())
|
||||
{
|
||||
data->proxyingQueue.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void comphelper::emscriptenthreading::tearDown()
|
||||
{
|
||||
std::scoped_lock g(mutex);
|
||||
assert(data != nullptr);
|
||||
data->eventHandlerThread.request_stop();
|
||||
data->eventHandlerThread.join();
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
comphelper::emscriptenthreading::Data& comphelper::emscriptenthreading::getData()
|
||||
{
|
||||
std::scoped_lock g(mutex);
|
||||
assert(data != nullptr);
|
||||
return *data;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|
||||
@@ -12,3 +12,4 @@ _lok_preinit
|
||||
_lok_preinit_2
|
||||
_malloc
|
||||
_free
|
||||
__ZN10emscripten8internal13MethodInvokerINS0_3rvp11default_tagEMN7qstdweb13EventListenerEFvNS_3valEEvPS5_JS6_EE6invokeERKS8_S9_PNS_7_EM_VALE
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sal/config.h>
|
||||
|
||||
#include <config_emscripten.h>
|
||||
#include <config_vclplug.h>
|
||||
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <emscripten/proxying.h>
|
||||
|
||||
namespace comphelper::emscriptenthreading
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
emscripten::ProxyingQueue proxyingQueue;
|
||||
std::jthread eventHandlerThread;
|
||||
};
|
||||
|
||||
void setUp();
|
||||
|
||||
void tearDown();
|
||||
|
||||
Data& getData();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|
||||
+22
-2
@@ -96,6 +96,25 @@ class VCL_DLLPUBLIC SystemWindow
|
||||
class ImplData;
|
||||
|
||||
private:
|
||||
class LayoutIdle: public Idle {
|
||||
public:
|
||||
LayoutIdle(char const * pDebugName, SystemWindow & parent, bool transferable):
|
||||
Idle(pDebugName), parent_(parent),
|
||||
transferState_(
|
||||
transferable ? TransferState::Transferable : TransferState::NotTransferable)
|
||||
{}
|
||||
|
||||
bool DecideTransferredExecution() override;
|
||||
|
||||
bool wasTransferred() const { return transferState_ == TransferState::Transferred; }
|
||||
|
||||
private:
|
||||
enum class TransferState { NotTransferable, Transferable, Transferred };
|
||||
|
||||
SystemWindow & parent_;
|
||||
TransferState transferState_;
|
||||
};
|
||||
|
||||
std::unique_ptr<ImplData> mpImplData;
|
||||
VclPtr<MenuBar> mpMenuBar;
|
||||
OUString maNotebookBarUIFile;
|
||||
@@ -110,7 +129,7 @@ private:
|
||||
bool mbInSetNoteBookBar : 1 = false;
|
||||
bool mbPaintComplete : 1 = false;
|
||||
bool mbIsDeferredInit : 1 = false;
|
||||
Idle maLayoutIdle;
|
||||
LayoutIdle maLayoutIdle;
|
||||
protected:
|
||||
VclPtr<vcl::Window> mpDialogParent;
|
||||
public:
|
||||
@@ -136,7 +155,8 @@ private:
|
||||
|
||||
protected:
|
||||
// Single argument ctors shall be explicit.
|
||||
SAL_DLLPRIVATE explicit SystemWindow(WindowType nType, const char* pIdleDebugName);
|
||||
SAL_DLLPRIVATE explicit SystemWindow(
|
||||
WindowType nType, const char* pIdleDebugName, bool transferableIdle = false);
|
||||
SAL_DLLPRIVATE void loadUI(vcl::Window* pParent, const OUString& rID, const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame = css::uno::Reference<css::frame::XFrame>());
|
||||
|
||||
SAL_DLLPRIVATE void SetWindowState(const vcl::WindowData& rData);
|
||||
|
||||
@@ -82,6 +82,8 @@ public:
|
||||
|
||||
const char *GetDebugName() const { return mpDebugName; }
|
||||
|
||||
virtual bool DecideTransferredExecution();
|
||||
|
||||
// Call handler
|
||||
virtual void Invoke() = 0;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ gb_EMSCRIPTEN_LDFLAGS := $(gb_EMSCRIPTEN_CPPFLAGS)
|
||||
gb_EMSCRIPTEN_LDFLAGS += -s TOTAL_MEMORY=1GB
|
||||
|
||||
ifeq ($(ENABLE_EMSCRIPTEN_PROXY_TO_PTHREAD),)
|
||||
gb_EMSCRIPTEN_LDFLAGS += -sPTHREAD_POOL_SIZE=6
|
||||
gb_EMSCRIPTEN_LDFLAGS += -sPTHREAD_POOL_SIZE=7
|
||||
endif
|
||||
|
||||
# Double the main thread stack size, but keep the default value for other threads:
|
||||
@@ -31,7 +31,9 @@ gb_EMSCRIPTEN_LDFLAGS += --bind -s FORCE_FILESYSTEM=1 -s WASM_BIGINT=1 -s ERROR_
|
||||
gb_EMSCRIPTEN_QTDEFS := -DQT_NO_LINKED_LIST -DQT_NO_JAVA_STYLE_ITERATORS -DQT_NO_EXCEPTIONS -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB
|
||||
|
||||
ifeq ($(ENABLE_EMSCRIPTEN_JSPI),TRUE)
|
||||
gb_EMSCRIPTEN_LDFLAGS += -sJSPI
|
||||
gb_EMSCRIPTEN_LDFLAGS += \
|
||||
-sJSPI \
|
||||
-sJSPI_EXPORTS=_emscripten_check_mailbox,_ZN10emscripten8internal13MethodInvokerINS0_3rvp11default_tagEMN7qstdweb13EventListenerEFvNS_3valEEvPS5_JS6_EE6invokeERKS8_S9_PNS_7_EM_VALE
|
||||
endif
|
||||
|
||||
ifeq ($(ENABLE_EMSCRIPTEN_PROXY_POSIX_SOCKETS),TRUE)
|
||||
@@ -56,6 +58,10 @@ endif
|
||||
gb_LinkTarget_LDFLAGS += $(gb_EMSCRIPTEN_LDFLAGS) $(gb_EMSCRIPTEN_CPPFLAGS) \
|
||||
$(gb_EMSCRIPTEN_EXCEPT) -sEXPORT_EXCEPTION_HANDLING_HELPERS
|
||||
|
||||
# Depending on emsdk version being used, enable e.g. std::jthread and std::stop_token used by some
|
||||
# build configurations:
|
||||
gb_LinkTarget_CXXFLAGS += -fexperimental-library
|
||||
|
||||
ifeq ($(ENABLE_OPTIMIZED),TRUE)
|
||||
ifneq ($(ENABLE_SYMBOLS_FOR),)
|
||||
gb_LinkTarget__emscripten_warnings_ldflags := -Wno-limited-postlink-optimizations
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <config_emscripten.h>
|
||||
#include <config_vclplug.h>
|
||||
#include <vclpluginapi.h>
|
||||
#include <unx/geninst.h>
|
||||
#include <salusereventlist.hxx>
|
||||
@@ -43,6 +45,13 @@ class QApplication;
|
||||
class SalYieldMutex;
|
||||
class SalFrame;
|
||||
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
namespace comphelper::emscriptenthreading
|
||||
{
|
||||
struct Data;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct StdFreeCStr
|
||||
{
|
||||
void operator()(char* arg) const noexcept { std::free(arg); }
|
||||
@@ -71,6 +80,10 @@ class VCLPLUG_QT_PUBLIC QtInstance : public QObject,
|
||||
|
||||
QtFrame* m_pActivePopup;
|
||||
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
comphelper::emscriptenthreading::Data* m_emscriptenThreadingData;
|
||||
#endif
|
||||
|
||||
DECL_DLLPRIVATE_LINK(updateStyleHdl, Timer*, void);
|
||||
void AfterAppInit() override;
|
||||
|
||||
@@ -114,6 +127,7 @@ public:
|
||||
static std::unique_ptr<QApplication> CreateQApplication(int& nArgc, char** pArgv);
|
||||
|
||||
void RunInMainThread(std::function<void()> func);
|
||||
void EmscriptenLightweightRunInMainThread(std::function<void()> func);
|
||||
|
||||
virtual SalFrame* CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle) override;
|
||||
virtual SalFrame* CreateChildFrame(SystemParentData* pParent,
|
||||
|
||||
+15
-4
@@ -195,8 +195,10 @@ QtFrame::~QtFrame()
|
||||
void QtFrame::Damage(sal_Int32 nExtentsX, sal_Int32 nExtentsY, sal_Int32 nExtentsWidth,
|
||||
sal_Int32 nExtentsHeight) const
|
||||
{
|
||||
m_pQWidget->update(scaledQRect(QRect(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight),
|
||||
1 / devicePixelRatioF()));
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread([
|
||||
this, r = scaledQRect(QRect(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight),
|
||||
1 / devicePixelRatioF())
|
||||
] { m_pQWidget->update(r); });
|
||||
}
|
||||
|
||||
SalGraphics* QtFrame::AcquireGraphics()
|
||||
@@ -258,7 +260,13 @@ QWidget* QtFrame::asChild() const
|
||||
return m_pQWidget;
|
||||
}
|
||||
|
||||
qreal QtFrame::devicePixelRatioF() const { return asChild()->devicePixelRatioF(); }
|
||||
qreal QtFrame::devicePixelRatioF() const
|
||||
{
|
||||
qreal ret;
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread(
|
||||
[ child = asChild(), &ret ] { ret = child->devicePixelRatioF(); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool QtFrame::isWindow() const { return asChild()->isWindow(); }
|
||||
|
||||
@@ -430,7 +438,10 @@ void QtFrame::SetMinClientSize(tools::Long nWidth, tools::Long nHeight)
|
||||
if (!isChild())
|
||||
{
|
||||
const qreal fRatio = devicePixelRatioF();
|
||||
asChild()->setMinimumSize(round(nWidth / fRatio), round(nHeight / fRatio));
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread(
|
||||
[ child = asChild(), w = round(nWidth / fRatio), h = round(nHeight / fRatio) ] {
|
||||
child->setMinimumSize(w, h);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include <QtGraphicsBase.hxx>
|
||||
#include <QtInstance.hxx>
|
||||
|
||||
#include <QtGui/QScreen>
|
||||
|
||||
@@ -26,8 +27,11 @@ void QtGraphicsBase::ImplGetResolution(QtFrame* pFrame, sal_Int32& rDPIX, sal_In
|
||||
return;
|
||||
|
||||
QScreen* pScreen = pFrame->GetQWidget()->screen();
|
||||
rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
|
||||
rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
|
||||
qreal devicePixelRatio;
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread(
|
||||
[pScreen, &devicePixelRatio] { devicePixelRatio = pScreen->devicePixelRatio(); });
|
||||
rDPIX = pScreen->logicalDotsPerInchX() * devicePixelRatio + 0.5;
|
||||
rDPIY = pScreen->logicalDotsPerInchY() * devicePixelRatio + 0.5;
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|
||||
|
||||
+81
-1
@@ -52,6 +52,7 @@
|
||||
|
||||
#include <vclpluginapi.h>
|
||||
#include <tools/debug.hxx>
|
||||
#include <comphelper/emscriptenthreading.hxx>
|
||||
#include <comphelper/flagguard.hxx>
|
||||
#include <config_emscripten.h>
|
||||
#include <config_vclplug.h>
|
||||
@@ -61,6 +62,7 @@
|
||||
#include <vcl/sysdata.hxx>
|
||||
#include <sal/log.hxx>
|
||||
#include <o3tl/unreachable.hxx>
|
||||
#include <o3tl/temporary.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>
|
||||
@@ -78,6 +80,13 @@ Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
#include <emscripten/promise.h>
|
||||
#include <emscripten/proxying.h>
|
||||
#include <emscripten/threading.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
/// TODO: not much Qt specific here? could be generalised, esp. for OSX...
|
||||
@@ -226,6 +235,13 @@ void QtInstance::RunInMainThread(std::function<void()> func)
|
||||
func();
|
||||
return;
|
||||
}
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
if (pthread_self() == m_emscriptenThreadingData->eventHandlerThread.native_handle())
|
||||
{
|
||||
EmscriptenLightweightRunInMainThread(func);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
QtYieldMutex* const pMutex(static_cast<QtYieldMutex*>(GetYieldMutex()));
|
||||
{
|
||||
@@ -245,6 +261,26 @@ void QtInstance::RunInMainThread(std::function<void()> func)
|
||||
}
|
||||
}
|
||||
|
||||
void QtInstance::EmscriptenLightweightRunInMainThread(std::function<void()> func)
|
||||
{
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
if (pthread_self() != emscripten_main_runtime_thread_id())
|
||||
{
|
||||
SolarMutexReleaser release;
|
||||
emscripten_sync_run_in_main_runtime_thread(
|
||||
EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(1)
|
||||
| EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_P),
|
||||
+[](void* pf) {
|
||||
SolarMutexGuard g;
|
||||
(*static_cast<std::function<void()>*>(pf))();
|
||||
},
|
||||
&func);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
func();
|
||||
}
|
||||
|
||||
OUString QtInstance::constructToolkitID(std::u16string_view sTKname)
|
||||
{
|
||||
OUString sID(sTKname + OUString::Concat(u" ("));
|
||||
@@ -266,6 +302,11 @@ QtInstance::QtInstance(std::unique_ptr<QApplication>& pQApp)
|
||||
, m_bUpdateFonts(false)
|
||||
, m_pActivePopup(nullptr)
|
||||
{
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
comphelper::emscriptenthreading::setUp();
|
||||
m_emscriptenThreadingData = &comphelper::emscriptenthreading::getData();
|
||||
#endif
|
||||
|
||||
ImplSVData* pSVData = ImplGetSVData();
|
||||
const OUString sToolkit = "qt" + OUString::number(QT_VERSION_MAJOR);
|
||||
pSVData->maAppData.mxToolkitName = constructToolkitID(sToolkit);
|
||||
@@ -309,6 +350,10 @@ QtInstance::~QtInstance()
|
||||
// force freeing the QApplication before freeing the arguments,
|
||||
// as it uses references to the provided arguments!
|
||||
m_pQApplication.reset();
|
||||
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
comphelper::emscriptenthreading::tearDown();
|
||||
#endif
|
||||
}
|
||||
|
||||
void QtInstance::AfterAppInit()
|
||||
@@ -497,6 +542,26 @@ bool QtInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
|
||||
if (bWasEvent)
|
||||
m_aWaitingYieldCond.set();
|
||||
}
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
else if (pthread_self() == m_emscriptenThreadingData->eventHandlerThread.native_handle())
|
||||
{
|
||||
SolarMutexReleaser release;
|
||||
struct Args
|
||||
{
|
||||
QtInstance* This;
|
||||
bool bWait;
|
||||
bool bHandleAllCurrentEvents;
|
||||
bool& bWasEvent;
|
||||
};
|
||||
(void)emscripten_promise_await(emscripten_proxy_promise(
|
||||
m_emscriptenThreadingData->proxyingQueue.queue, emscripten_main_runtime_thread_id(),
|
||||
[](void* p) {
|
||||
Args const& args = *static_cast<Args*>(p);
|
||||
args.bWasEvent = args.This->DoYield(args.bWait, args.bHandleAllCurrentEvents);
|
||||
},
|
||||
&o3tl::temporary<Args>({ this, bWait, bHandleAllCurrentEvents, bWasEvent })));
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
{
|
||||
@@ -545,7 +610,20 @@ void QtInstance::TriggerUserEventProcessing()
|
||||
|
||||
void QtInstance::ProcessEvent(SalUserEvent aEvent)
|
||||
{
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
SolarMutexReleaser release;
|
||||
(void)emscripten_promise_await(
|
||||
emscripten_proxy_promise(m_emscriptenThreadingData->proxyingQueue.queue,
|
||||
m_emscriptenThreadingData->eventHandlerThread.native_handle(),
|
||||
[](void* p) {
|
||||
auto& aEvent = *static_cast<SalUserEvent*>(p);
|
||||
SolarMutexGuard g;
|
||||
aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
|
||||
},
|
||||
&aEvent));
|
||||
#else
|
||||
aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
|
||||
#endif
|
||||
}
|
||||
|
||||
rtl::Reference<QtFilePicker>
|
||||
@@ -601,7 +679,9 @@ QtInstance::CreateClipboard(const css::uno::Sequence<css::uno::Any>& arguments)
|
||||
if (it != m_aClipboards.end())
|
||||
return it->second;
|
||||
|
||||
css::uno::Reference<css::uno::XInterface> xClipboard = QtClipboard::create(sel);
|
||||
css::uno::Reference<css::uno::XInterface> xClipboard;
|
||||
EmscriptenLightweightRunInMainThread(
|
||||
[&sel, &xClipboard] { xClipboard = QtClipboard::create(sel); });
|
||||
if (xClipboard.is())
|
||||
m_aClipboards[sel] = xClipboard;
|
||||
|
||||
|
||||
+15
-7
@@ -769,19 +769,24 @@ QPushButton* QtMenu::ImplAddMenuBarButton(const QIcon& rIcon, const QString& rTo
|
||||
if (!pWidget)
|
||||
{
|
||||
assert(!m_pButtonGroup);
|
||||
pWidget = new QWidget(mpQMenuBar);
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread(
|
||||
[this, &pWidget] { pWidget = new QWidget(mpQMenuBar); });
|
||||
assert(!pWidget->layout());
|
||||
pLayout = new QHBoxLayout();
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread(
|
||||
[&pLayout] { pLayout = new QHBoxLayout(); });
|
||||
pLayout->setContentsMargins(QMargins());
|
||||
pLayout->setSpacing(0);
|
||||
pWidget->setLayout(pLayout);
|
||||
m_pButtonGroup = new QButtonGroup(pLayout);
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread(
|
||||
[this, pLayout] { m_pButtonGroup = new QButtonGroup(pLayout); });
|
||||
m_pButtonGroup->setObjectName(gButtonGroupKey);
|
||||
m_pButtonGroup->setExclusive(false);
|
||||
connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
|
||||
&QtMenu::slotMenuBarButtonClicked);
|
||||
pWidget->show();
|
||||
mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner);
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread([this, pWidget] {
|
||||
pWidget->show();
|
||||
mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner);
|
||||
});
|
||||
}
|
||||
else
|
||||
pLayout = static_cast<QHBoxLayout*>(pWidget->layout());
|
||||
@@ -792,7 +797,8 @@ QPushButton* QtMenu::ImplAddMenuBarButton(const QIcon& rIcon, const QString& rTo
|
||||
if (pButton)
|
||||
RemoveMenuBarButton(nId);
|
||||
|
||||
pButton = new QPushButton();
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread(
|
||||
[&pButton] { pButton = new QPushButton(); });
|
||||
// we don't want the button to increase the QMenuBar height, so a fixed size square it is
|
||||
const int nFixedLength
|
||||
= mpQMenuBar->height() - 2 * mpQMenuBar->style()->pixelMetric(QStyle::PM_MenuBarVMargin);
|
||||
@@ -828,7 +834,9 @@ void QtMenu::connectHelpShortcut(QMenu* pMenu)
|
||||
{
|
||||
assert(pMenu);
|
||||
QKeySequence sequence(QKeySequence::HelpContents);
|
||||
QShortcut* pQShortcut = new QShortcut(sequence, pMenu);
|
||||
QShortcut* pQShortcut;
|
||||
GetQtInstance().EmscriptenLightweightRunInMainThread(
|
||||
[&sequence, pMenu, &pQShortcut] { pQShortcut = new QShortcut(sequence, pMenu); });
|
||||
connect(pQShortcut, &QShortcut::activated, this, QtMenu::slotShowHelp);
|
||||
connect(pQShortcut, &QShortcut::activatedAmbiguously, this, QtMenu::slotShowHelp);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <config_emscripten.h>
|
||||
#include <config_vclplug.h>
|
||||
#include <vcl/svapp.hxx>
|
||||
#include <sal/log.hxx>
|
||||
|
||||
@@ -41,7 +43,12 @@ QtTimer::QtTimer()
|
||||
|
||||
void QtTimer::timeoutActivated()
|
||||
{
|
||||
#if !(defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD)
|
||||
//TODO: While the special Emscripten Qt6 JSPI/non-PROXY_TO_PTHREAD mode doesn't lock the
|
||||
// SolarMutex here, but only when calling pTask->Invoke() in Scheduler::CallbackTaskScheduling,
|
||||
// that looks too brittle in general, so treat that special mode specially here.
|
||||
SolarMutexGuard aGuard;
|
||||
#endif
|
||||
if (Application::IsUseSystemEventLoop())
|
||||
{
|
||||
const ImplSVData* pSVData = ImplGetSVData();
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <typeinfo>
|
||||
|
||||
#include <com/sun/star/uno/Exception.hpp>
|
||||
#include <config_emscripten.h>
|
||||
#include <config_vclplug.h>
|
||||
#include <sal/log.hxx>
|
||||
#include <sal/types.h>
|
||||
#include <svdata.hxx>
|
||||
@@ -37,6 +39,7 @@
|
||||
#include <vcl/idle.hxx>
|
||||
#include <saltimer.hxx>
|
||||
#include <salinst.hxx>
|
||||
#include <comphelper/emscriptenthreading.hxx>
|
||||
#include <comphelper/profilezone.hxx>
|
||||
#include <schedulerimpl.hxx>
|
||||
|
||||
@@ -358,7 +361,12 @@ void Scheduler::CallbackTaskScheduling()
|
||||
ImplSVData *pSVData = ImplGetSVData();
|
||||
ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
|
||||
|
||||
#if !(defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD)
|
||||
//TODO: While the special Emscripten Qt6 JSPI/non-PROXY_TO_PTHREAD mode doesn't lock the
|
||||
// SolarMutex in QtTimer::timeoutActivated, but only down below when calling pTask->Invoke(),
|
||||
// that looks too brittle in general, so treat that special mode specially here.
|
||||
DBG_TESTSOLARMUTEX();
|
||||
#endif
|
||||
|
||||
SchedulerGuard aSchedulerGuard;
|
||||
if ( !rSchedCtx.mbActive || InfiniteTimeoutMs == rSchedCtx.mnTimerPeriod )
|
||||
@@ -506,7 +514,23 @@ void Scheduler::CallbackTaskScheduling()
|
||||
{
|
||||
// prepare Scheduler object for deletion after handling
|
||||
pTask->SetDeletionFlags();
|
||||
#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
|
||||
if (pTask->DecideTransferredExecution())
|
||||
{
|
||||
auto & data = comphelper::emscriptenthreading::getData();
|
||||
data.proxyingQueue.proxyAsync(data.eventHandlerThread.native_handle(), [pTask] {
|
||||
SolarMutexGuard g;
|
||||
pTask->Invoke();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
SolarMutexGuard g;
|
||||
pTask->Invoke();
|
||||
}
|
||||
#else
|
||||
pTask->Invoke();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
catch (css::uno::Exception&)
|
||||
@@ -693,4 +717,9 @@ Task::~Task() COVERITY_NOEXCEPT_FALSE
|
||||
assert(nullptr == mpSchedulerData || comphelper::IsFuzzing());
|
||||
}
|
||||
|
||||
bool Task::DecideTransferredExecution()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
||||
@@ -548,7 +548,7 @@ void Dialog::ImplLOKNotifier(vcl::Window* pParent)
|
||||
}
|
||||
|
||||
Dialog::Dialog( WindowType nType )
|
||||
: SystemWindow( nType, "vcl::Dialog maLayoutIdle" )
|
||||
: SystemWindow( nType, "vcl::Dialog maLayoutIdle", true )
|
||||
, mnInitFlag(InitFlag::Default)
|
||||
{
|
||||
ImplInitDialogData();
|
||||
@@ -574,7 +574,7 @@ void Dialog::ImplDeferredInit(vcl::Window* pParent, WinBits nBits)
|
||||
}
|
||||
|
||||
Dialog::Dialog(vcl::Window* pParent, const OUString& rID, const OUString& rUIXMLDescription)
|
||||
: SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle")
|
||||
: SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle", true)
|
||||
, mnInitFlag(InitFlag::Default)
|
||||
{
|
||||
ImplLOKNotifier(pParent);
|
||||
@@ -583,7 +583,7 @@ Dialog::Dialog(vcl::Window* pParent, const OUString& rID, const OUString& rUIXML
|
||||
}
|
||||
|
||||
Dialog::Dialog(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag)
|
||||
: SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle")
|
||||
: SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle", true)
|
||||
, mnInitFlag(eFlag)
|
||||
{
|
||||
ImplLOKNotifier(pParent);
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "menubarwindow.hxx"
|
||||
|
||||
#include <comphelper/scopeguard.hxx>
|
||||
#include <o3tl/safeint.hxx>
|
||||
#include <sal/config.h>
|
||||
#include <sal/log.hxx>
|
||||
@@ -66,10 +68,20 @@ SystemWindow::ImplData::ImplData()
|
||||
maMaxOutSize = Size( SHRT_MAX, SHRT_MAX );
|
||||
}
|
||||
|
||||
SystemWindow::SystemWindow(WindowType nType, const char* pIdleDebugName)
|
||||
bool SystemWindow::LayoutIdle::DecideTransferredExecution()
|
||||
{
|
||||
if (transferState_ == TransferState::NotTransferable) {
|
||||
return false;
|
||||
}
|
||||
parent_.acquire(); // paired with release in ImplHandleLayoutTimerHdl
|
||||
transferState_ = TransferState::Transferred;
|
||||
return true;
|
||||
}
|
||||
|
||||
SystemWindow::SystemWindow(WindowType nType, const char* pIdleDebugName, bool transferableIdle)
|
||||
: Window(nType)
|
||||
, mpImplData(new ImplData)
|
||||
, maLayoutIdle( pIdleDebugName )
|
||||
, maLayoutIdle( pIdleDebugName, *this, transferableIdle )
|
||||
{
|
||||
mpWindowImpl->mbSysWin = true;
|
||||
mpWindowImpl->mnActivateMode = ActivateModeFlags::GrabFocus;
|
||||
@@ -1036,6 +1048,11 @@ void SystemWindow::setPosSizeOnContainee(Size aSize, Window &rBox)
|
||||
|
||||
IMPL_LINK_NOARG( SystemWindow, ImplHandleLayoutTimerHdl, Timer*, void )
|
||||
{
|
||||
comphelper::ScopeGuard g([this] {
|
||||
if (maLayoutIdle.wasTransferred()) {
|
||||
release(); // paired with acquire in LayoutIdle::DecideTransferredExecution
|
||||
}
|
||||
});
|
||||
Window *pBox = GetWindow(GetWindowType::FirstChild);
|
||||
if (!isLayoutEnabled())
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user