summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmake/QtAndroidHelpers.cmake25
-rw-r--r--cmake/QtBaseGlobalTargets.cmake15
-rw-r--r--cmake/QtBuildHelpers.cmake1
-rw-r--r--cmake/QtBuildOptionsHelpers.cmake17
-rw-r--r--cmake/QtBuildRepoHelpers.cmake1
-rw-r--r--coin/instructions/prepare_building_env.yaml3
-rw-r--r--configure.cmake1
-rw-r--r--src/corelib/CMakeLists.txt7
-rw-r--r--src/corelib/Qt6AndroidMacros.cmake6
-rw-r--r--src/corelib/Qt6AndroidPermissionHelpers.cmake40
-rw-r--r--src/corelib/Qt6CTestMacros.cmake8
-rw-r--r--src/corelib/Qt6WasmMacros.cmake9
-rw-r--r--src/corelib/compat/removed_api.cpp2
-rw-r--r--src/corelib/global/qcheckedint_impl.h14
-rw-r--r--src/corelib/kernel/qabstracteventdispatcher.cpp6
-rw-r--r--src/corelib/kernel/qbasictimer.cpp9
-rw-r--r--src/corelib/kernel/qtestsupport_core.cpp29
-rw-r--r--src/corelib/kernel/qtestsupport_core.h9
-rw-r--r--src/corelib/kernel/qvariant.cpp11
-rw-r--r--src/corelib/kernel/qvariant.h14
-rw-r--r--src/corelib/serialization/qtextstream.cpp59
-rw-r--r--src/corelib/serialization/qtextstream_p.h4
-rw-r--r--src/corelib/text/qstring.cpp5
-rw-r--r--src/corelib/thread/qthread_p.h2
-rw-r--r--src/dbus/qdbuspendingcall.h4
-rw-r--r--src/dbus/qdbuspendingreply.cpp18
-rw-r--r--src/dbus/qdbuspendingreply.h21
-rw-r--r--src/gui/accessible/linux/atspiadaptor.cpp18
-rw-r--r--src/gui/accessible/qaccessible.cpp1
-rw-r--r--src/gui/accessible/qaccessible_base.h1
-rw-r--r--src/gui/kernel/qtestsupport_gui.cpp6
-rw-r--r--src/plugins/networkinformation/android/CMakeLists.txt7
-rw-r--r--src/plugins/platforms/wasm/qtloader.js3
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp110
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.cpp309
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.h23
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp181
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h25
-rw-r--r--src/plugins/platforms/wayland/CMakeLists.txt6
-rw-r--r--src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h2
-rw-r--r--src/plugins/platforms/wayland/qwaylandintegration.cpp2
-rw-r--r--src/plugins/platforms/wayland/qwaylandintegration_p.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp27
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h1
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp16
-rw-r--r--src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp31
-rw-r--r--src/testlib/qtestcase.h27
-rw-r--r--src/tools/CMakeLists.txt4
-rw-r--r--src/tools/configure.cmake6
-rw-r--r--src/tools/wasmdeployqt/CMakeLists.txt19
-rw-r--r--src/tools/wasmdeployqt/common.h26
-rw-r--r--src/tools/wasmdeployqt/jsontools.cpp101
-rw-r--r--src/tools/wasmdeployqt/jsontools.h19
-rw-r--r--src/tools/wasmdeployqt/main.cpp417
-rw-r--r--src/tools/wasmdeployqt/wasmbinary.cpp91
-rw-r--r--src/tools/wasmdeployqt/wasmbinary.h24
-rw-r--r--src/widgets/accessible/qaccessiblewidget.cpp7
-rw-r--r--src/widgets/accessible/qaccessiblewidgets.cpp11
-rw-r--r--src/widgets/accessible/qaccessiblewidgets_p.h1
-rw-r--r--src/widgets/kernel/qtestsupport_widgets.cpp6
-rw-r--r--src/widgets/widgets/qdockwidget.cpp12
-rw-r--r--tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp45
-rw-r--r--tests/auto/corelib/text/qstring/tst_qstring.cpp24
-rw-r--r--tests/auto/dbus/qdbuspendingreply/CMakeLists.txt1
-rw-r--r--tests/auto/dbus/qdbuspendingreply/tst_qdbuspendingreply.cpp252
-rw-r--r--tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp3
-rw-r--r--tests/auto/other/qaccessibility/tst_qaccessibility.cpp35
-rw-r--r--tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp61
-rw-r--r--tests/auto/testlib/selftests/expected_cmptest.junitxml14
-rw-r--r--tests/auto/testlib/selftests/expected_cmptest.lightxml26
-rw-r--r--tests/auto/testlib/selftests/expected_cmptest.tap30
-rw-r--r--tests/auto/testlib/selftests/expected_cmptest.teamcity10
-rw-r--r--tests/auto/testlib/selftests/expected_cmptest.txt10
-rw-r--r--tests/auto/testlib/selftests/expected_cmptest.xml26
-rw-r--r--tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp2
-rw-r--r--util/wasm/batchedtestrunner/emrunadapter.js5
-rw-r--r--util/wasm/preload/generate_default_preloads.sh.in19
-rwxr-xr-xutil/wasm/preload/preload_qml_imports.py98
-rwxr-xr-xutil/wasm/preload/preload_qt_plugins.py57
80 files changed, 1883 insertions, 720 deletions
diff --git a/cmake/QtAndroidHelpers.cmake b/cmake/QtAndroidHelpers.cmake
index cb7d4716f12..b473c2c331b 100644
--- a/cmake/QtAndroidHelpers.cmake
+++ b/cmake/QtAndroidHelpers.cmake
@@ -191,21 +191,8 @@ function(qt_internal_android_dependencies_content target file_content_out)
endif()
# Android Permissions
- if(arg_PERMISSIONS)
- foreach(permission IN LISTS arg_PERMISSIONS)
- # Check if the permission has also extra attributes in addition to the permission name
- list(LENGTH permission permission_len)
- if(permission_len EQUAL 1)
- string(APPEND file_contents "<permission name=\"${permission}\" />\n")
- elseif(permission_len EQUAL 2)
- list(GET permission 0 name)
- list(GET permission 1 extras)
- string(APPEND file_contents "<permission name=\"${name}\" extras=\"${extras}\"/>\n")
- else()
- message(FATAL_ERROR "Invalid permission format: ${permission} ${permission_len}")
- endif()
- endforeach()
- endif()
+ _qt_internal_android_convert_permissions(permissions_string ${target} "DEPENDENCIESXML")
+ string(APPEND file_contents "${permissions_string}")
# Android Features
if(arg_FEATURES)
@@ -482,14 +469,8 @@ function(qt_internal_android_add_interface_permissions target)
return()
endif()
- set(postprocessed_permissions "")
- foreach(permission IN LISTS permissions)
- # TODO: skip processing extras for now, add them back once internal API
- # will cover adding extras using internal function.
- list(APPEND postprocessed_permissions "name\;${permission}")
- endforeach()
qt_internal_set_module_transitive_properties(${target} TYPE LINK PROPERTIES
- INTERFACE_QT_ANDROID_PERMISSIONS "${postprocessed_permissions}")
+ INTERFACE_QT_ANDROID_PERMISSIONS "${permissions}")
endfunction()
# The function stores Android features that are required by the module target.
diff --git a/cmake/QtBaseGlobalTargets.cmake b/cmake/QtBaseGlobalTargets.cmake
index 405b5e22635..601592277fe 100644
--- a/cmake/QtBaseGlobalTargets.cmake
+++ b/cmake/QtBaseGlobalTargets.cmake
@@ -437,21 +437,6 @@ elseif(WASM)
"${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/qt-wasmtestrunner.py" @ONLY)
qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/qt-wasmtestrunner.py"
DESTINATION "${INSTALL_LIBEXECDIR}")
-
- if(QT_FEATURE_shared)
- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/util/wasm/preload/preload_qml_imports.py"
- "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/preload_qml_imports.py" COPYONLY)
- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/util/wasm/preload/preload_qt_plugins.py"
- "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/preload_qt_plugins.py" COPYONLY)
- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/util/wasm/preload/generate_default_preloads.sh.in"
- "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/generate_default_preloads.sh.in" COPYONLY)
- qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/preload_qml_imports.py"
- DESTINATION "${INSTALL_LIBEXECDIR}")
- qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/preload_qt_plugins.py"
- DESTINATION "${INSTALL_LIBEXECDIR}")
- qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/generate_default_preloads.sh.in"
- DESTINATION "${INSTALL_LIBEXECDIR}")
- endif()
endif()
# Install CI support files to libexec.
diff --git a/cmake/QtBuildHelpers.cmake b/cmake/QtBuildHelpers.cmake
index 1fb5c6350e1..df0aa33cda2 100644
--- a/cmake/QtBuildHelpers.cmake
+++ b/cmake/QtBuildHelpers.cmake
@@ -487,7 +487,6 @@ macro(qt_internal_setup_build_and_global_variables)
qt_set_language_standards()
qt_internal_set_use_ccache()
- qt_internal_set_unity_build()
qt_internal_set_allow_symlink_in_paths()
qt_internal_set_skip_setup_deployment()
qt_internal_set_qt_allow_download()
diff --git a/cmake/QtBuildOptionsHelpers.cmake b/cmake/QtBuildOptionsHelpers.cmake
index ccd054183d4..ae8cf51e016 100644
--- a/cmake/QtBuildOptionsHelpers.cmake
+++ b/cmake/QtBuildOptionsHelpers.cmake
@@ -444,10 +444,19 @@ endmacro()
macro(qt_internal_set_unity_build)
option(QT_UNITY_BUILD "Enable unity (jumbo) build")
set(QT_UNITY_BUILD_BATCH_SIZE "32" CACHE STRING "Unity build batch size")
- if(QT_UNITY_BUILD)
- set(CMAKE_UNITY_BUILD ON)
- set(CMAKE_UNITY_BUILD_BATCH_SIZE "${QT_UNITY_BUILD_BATCH_SIZE}")
- endif()
+
+ include(CMakeDependentOption)
+ string(TOLOWER "${PROJECT_NAME}" project_name_lower)
+ cmake_dependent_option(
+ "QT_UNITY_BUILD_PROJECT_${project_name_lower}"
+ "Enable unity builds for project ${PROJECT_NAME}"
+ TRUE
+ QT_UNITY_BUILD
+ FALSE
+ )
+
+ set(CMAKE_UNITY_BUILD "${QT_UNITY_BUILD_PROJECT_${project_name_lower}}")
+ set(CMAKE_UNITY_BUILD_BATCH_SIZE "${QT_UNITY_BUILD_BATCH_SIZE}")
endmacro()
macro(qt_internal_set_allow_symlink_in_paths)
diff --git a/cmake/QtBuildRepoHelpers.cmake b/cmake/QtBuildRepoHelpers.cmake
index 86107179475..70a066f11b2 100644
--- a/cmake/QtBuildRepoHelpers.cmake
+++ b/cmake/QtBuildRepoHelpers.cmake
@@ -355,6 +355,7 @@ macro(qt_build_repo_begin)
endif()
_qt_internal_sbom_auto_begin_qt_repo_project()
+ qt_internal_set_unity_build()
endmacro()
# Runs delayed actions on some of the Qt targets.
diff --git a/coin/instructions/prepare_building_env.yaml b/coin/instructions/prepare_building_env.yaml
index 0330369662d..ac2ad89027b 100644
--- a/coin/instructions/prepare_building_env.yaml
+++ b/coin/instructions/prepare_building_env.yaml
@@ -679,6 +679,9 @@ instructions:
- type: AppendToEnvironmentVariable
variableName: COMMON_NON_QTBASE_TARGET_CMAKE_ARGS
variableValue: " -DQT_USE_VCPKG=ON -DVCPKG_INSTALLED_DIR={{.Env.VCPKG_INSTALLED_DIR}} -DVCPKG_HOST_TRIPLET={{.Env.VCPKG_HOST_TRIPLET}} -DVCPKG_TARGET_TRIPLET={{.Env.VCPKG_TARGET_TRIPLET}}"
+ - type: AppendToEnvironmentVariable
+ variableName: COMMON_TARGET_TEST_CMAKE_ARGS
+ variableValue: " -DQT_USE_VCPKG=ON -DVCPKG_INSTALLED_DIR={{.Env.VCPKG_INSTALLED_DIR}} -DVCPKG_HOST_TRIPLET={{.Env.VCPKG_HOST_TRIPLET}} -DVCPKG_TARGET_TRIPLET={{.Env.VCPKG_TARGET_TRIPLET}}"
enable_if:
condition: runtime
env_var: USE_VCPKG
diff --git a/configure.cmake b/configure.cmake
index 8d96ba7e1f0..ddadf08ed57 100644
--- a/configure.cmake
+++ b/configure.cmake
@@ -1010,6 +1010,7 @@ qt_feature("wasm-jspi" PUBLIC
LABEL "WebAssembly JSPI"
PURPOSE "Enables WebAssembly JavaScript Promise Integration (JSPI)"
AUTODETECT OFF
+ CONDITION QT_FEATURE_wasm_exceptions # JSPI requires wasm-exceptions
)
qt_feature_definition("wasm-jspi" "QT_WASM_JSPI" VALUE "1")
qt_feature_config("wasm-jspi" QMAKE_PRIVATE_CONFIG)
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index 249b9b8c1ee..cd3c03666d0 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -491,8 +491,11 @@ if(ANDROID)
set_property(TARGET Core APPEND PROPERTY QT_ANDROID_LIB_DEPENDENCIES
${INSTALL_PLUGINSDIR}/platforms/libplugins_platforms_qtforandroid.so
)
- set_property(TARGET Core APPEND PROPERTY QT_ANDROID_PERMISSIONS
- android.permission.INTERNET android.permission.WRITE_EXTERNAL_STORAGE
+ qt_internal_add_android_permission(Core
+ NAME android.permission.WRITE_EXTERNAL_STORAGE
+ )
+ qt_internal_add_android_permission(Core
+ NAME android.permission.INTERNET
)
endif()
diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake
index ed97a42c83f..e34f38a4d2b 100644
--- a/src/corelib/Qt6AndroidMacros.cmake
+++ b/src/corelib/Qt6AndroidMacros.cmake
@@ -1748,7 +1748,11 @@ endfunction()
function(_qt_internal_android_get_target_deployment_dir out_deploy_dir target)
_qt_internal_android_get_target_android_build_dir(build_dir ${target})
- set(${out_deploy_dir} "${build_dir}/app" PARENT_SCOPE)
+ if(QT_USE_ANDROID_MODERN_BUNDLE)
+ set(${out_deploy_dir} "${build_dir}/app" PARENT_SCOPE)
+ else()
+ set(${out_deploy_dir} "${build_dir}" PARENT_SCOPE)
+ endif()
endfunction()
function(_qt_internal_expose_android_package_source_dir_to_ide target)
diff --git a/src/corelib/Qt6AndroidPermissionHelpers.cmake b/src/corelib/Qt6AndroidPermissionHelpers.cmake
index 7f851e14667..4e17a5ca83a 100644
--- a/src/corelib/Qt6AndroidPermissionHelpers.cmake
+++ b/src/corelib/Qt6AndroidPermissionHelpers.cmake
@@ -25,7 +25,47 @@
#
# `XML`
# Generate XML content compatible with AndroidManifest.xml.
+#
+# `DEPENDENCIESXML`
+# Generate XML content compatible with <module>-android-dependencies.xml.
+# This format doesn't produce generator expression, so it should be used in
+# the scope finalizer.
function(_qt_internal_android_convert_permissions out_var target type)
+ if(type STREQUAL "DEPENDENCIESXML")
+ set(output "")
+ get_target_property(permissions ${target} QT_ANDROID_PERMISSIONS)
+ if(NOT permissions)
+ return()
+ endif()
+ foreach(permission IN LISTS permissions)
+ list(JOIN permission "=\"" permission)
+ list(LENGTH permission permission_length)
+ if(permission_length LESS 1)
+ message(FATAL_ERROR "Invalid QT_ANDROID_PERMISSIONS format for target"
+ " ${target}: ${arg_PERMISSIONS}")
+ endif()
+
+ list(GET permission 0 permission_name)
+ string(APPEND output "<permission ${permission_name}\"")
+
+ math(EXPR permission_length "${permission_length} - 1")
+ set(extras_string "")
+ if(permission_length GREATER_EQUAL 1)
+ set(attributes "")
+ foreach(i RANGE 1 ${permission_length})
+ list(GET permission ${i} permission_attribute)
+ list(APPEND attributes "android:${permission_attribute}'")
+ endforeach()
+ list(JOIN attributes " " attributes)
+ string(REPLACE "\"" "'" attributes "${attributes}")
+ set(extras_string " extras=\"${attributes}\"")
+ endif()
+ string(APPEND output "${extras_string}/>\n")
+ endforeach()
+ set(${out_var} "${output}" PARENT_SCOPE)
+ return()
+ endif()
+
set(permissions_property "$<TARGET_PROPERTY:${target},QT_ANDROID_PERMISSIONS>")
set(permissions_genex "$<$<BOOL:${permissions_property}>:")
if(type STREQUAL "JSON")
diff --git a/src/corelib/Qt6CTestMacros.cmake b/src/corelib/Qt6CTestMacros.cmake
index 939901f5e20..c115e39b272 100644
--- a/src/corelib/Qt6CTestMacros.cmake
+++ b/src/corelib/Qt6CTestMacros.cmake
@@ -307,6 +307,7 @@ macro(_qt_internal_test_expect_pass _dir)
GENERATOR
MAKE_PROGRAM
BUILD_TYPE
+ BUILD_TARGET
)
set(_test_multi_args
BUILD_OPTIONS
@@ -364,6 +365,12 @@ macro(_qt_internal_test_expect_pass _dir)
set(build_type "--build-config" "${build_type}")
endif()
+ if(_ARGS_BUILD_TARGET)
+ set(build_target "--build-target" "${_ARGS_BUILD_TARGET}")
+ else()
+ set(build_target "")
+ endif()
+
# Allow skipping clean step.
set(build_no_clean "")
if(_ARGS_NO_CLEAN_STEP)
@@ -468,6 +475,7 @@ macro(_qt_internal_test_expect_pass _dir)
${build_project}
--build-options "${option_list}"
"${_ARGS_BUILD_OPTIONS}" ${additional_configure_args}
+ ${build_target}
${test_command}
)
diff --git a/src/corelib/Qt6WasmMacros.cmake b/src/corelib/Qt6WasmMacros.cmake
index e185ef67490..eae356679bd 100644
--- a/src/corelib/Qt6WasmMacros.cmake
+++ b/src/corelib/Qt6WasmMacros.cmake
@@ -91,15 +91,6 @@ function(_qt_internal_wasm_add_target_helpers target)
${_target_directory}/qtloader.js COPYONLY)
configure_file("${WASM_BUILD_DIR}/plugins/platforms/qtlogo.svg"
${_target_directory}/qtlogo.svg COPYONLY)
- if(QT_FEATURE_shared)
- set(TARGET_DIR "${_target_directory}")
- set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
- set(QT_HOST_DIR "${QT_HOST_PATH}")
- set(QT_WASM_DIR "${WASM_BUILD_DIR}")
- set(QT_INSTALL_DIR "${QT6_INSTALL_PREFIX}")
- configure_file("${WASM_BUILD_DIR}/libexec/generate_default_preloads.sh.in"
- "${_target_directory}/generate_default_preloads_for_${target}.sh" @ONLY)
- endif()
endif()
endif()
endif()
diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp
index 4285e39a542..94879edcf72 100644
--- a/src/corelib/compat/removed_api.cpp
+++ b/src/corelib/compat/removed_api.cpp
@@ -1474,6 +1474,8 @@ bool QObject::doSetProperty(const char *name, const QVariant *lvalue, QVariant *
#include "qstring.h" // inlined API
+#include "qvariant.h" // inlined API
+
// #include "qotherheader.h"
// // implement removed functions from qotherheader.h
// order sections alphabetically to reduce chances of merge conflicts
diff --git a/src/corelib/global/qcheckedint_impl.h b/src/corelib/global/qcheckedint_impl.h
index 2b4d0ff0e0d..f4081da8bb0 100644
--- a/src/corelib/global/qcheckedint_impl.h
+++ b/src/corelib/global/qcheckedint_impl.h
@@ -302,14 +302,14 @@ public:
QCheckedInt,
AInt,
template <typename AInt, if_is_same_int<AInt> = true>)
-};
-template <typename Int, typename I, typename P>
-Q_DECL_CONST_FUNCTION size_t constexpr inline qHash(QCheckedInt<Int, I, P> key, size_t seed = 0) noexcept
-{
- using QT_PREPEND_NAMESPACE(qHash);
- return qHash(key.value(), seed);
-}
+private:
+ friend size_t constexpr qHash(QCheckedInt key, size_t seed = 0) noexcept
+ {
+ using QT_PREPEND_NAMESPACE(qHash); // ### needed?
+ return qHash(key.value(), seed);
+ }
+};
} // namespace QCheckedIntegers
} // namespace QtPrivate
diff --git a/src/corelib/kernel/qabstracteventdispatcher.cpp b/src/corelib/kernel/qabstracteventdispatcher.cpp
index 997d6f0c98e..fde406a78d8 100644
--- a/src/corelib/kernel/qabstracteventdispatcher.cpp
+++ b/src/corelib/kernel/qabstracteventdispatcher.cpp
@@ -176,8 +176,9 @@ QAbstractEventDispatcher::QAbstractEventDispatcher(QAbstractEventDispatcherPriva
*/
QAbstractEventDispatcher::~QAbstractEventDispatcher()
{
- QThreadData *data = QThreadData::current();
- if (data->eventDispatcher.loadRelaxed() == this)
+ // don't recreate the QThreadData if it has already been destroyed
+ QThreadData *data = QThreadData::currentThreadData();
+ if (data && data->eventDispatcher.loadRelaxed() == this)
data->eventDispatcher.storeRelaxed(nullptr);
}
@@ -192,6 +193,7 @@ QAbstractEventDispatcher::~QAbstractEventDispatcher()
*/
QAbstractEventDispatcher *QAbstractEventDispatcher::instance(QThread *thread)
{
+ // do create a QThreadData, in case this is very early in an adopted thread
QThreadData *data = thread ? QThreadData::get2(thread) : QThreadData::current();
return data->eventDispatcher.loadRelaxed();
}
diff --git a/src/corelib/kernel/qbasictimer.cpp b/src/corelib/kernel/qbasictimer.cpp
index 60387381da6..1906457b13d 100644
--- a/src/corelib/kernel/qbasictimer.cpp
+++ b/src/corelib/kernel/qbasictimer.cpp
@@ -5,6 +5,8 @@
#include "qabstracteventdispatcher.h"
#include "qabstracteventdispatcher_p.h"
+#include <private/qthread_p.h>
+
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE
@@ -209,7 +211,12 @@ void QBasicTimer::start(Duration duration, Qt::TimerType timerType, QObject *obj
void QBasicTimer::stop()
{
if (isActive()) {
- QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance();
+ QAbstractEventDispatcher *eventDispatcher = nullptr;
+
+ // don't create the current thread data if it's already been destroyed
+ if (QThreadData *data = QThreadData::currentThreadData())
+ eventDispatcher = data->eventDispatcher.loadRelaxed();
+
if (eventDispatcher && !eventDispatcher->unregisterTimer(m_id)) {
qWarning("QBasicTimer::stop: Failed. Possibly trying to stop from a different thread");
return;
diff --git a/src/corelib/kernel/qtestsupport_core.cpp b/src/corelib/kernel/qtestsupport_core.cpp
index 0d11f0bd666..c0c24d9db83 100644
--- a/src/corelib/kernel/qtestsupport_core.cpp
+++ b/src/corelib/kernel/qtestsupport_core.cpp
@@ -7,8 +7,37 @@
using namespace std::chrono_literals;
+// Assert that this instantiation of std::atomic is always lock-free so we
+// know that no code will execute on destruction.
+static_assert(std::atomic<std::chrono::milliseconds>::is_always_lock_free);
+
QT_BEGIN_NAMESPACE
+// ### Qt 7: reduce the default: QTBUG-138160
+Q_CONSTINIT std::atomic<std::chrono::milliseconds> QTest::defaultTryTimeout{5s};
+
+/*!
+ \variable QTest::defaultTryTimeout
+ \since 6.11
+
+ This global variable stores the default timeout used by the \c {QTRY_*}
+ functions and \l qWait.
+
+ The most typical use case for this variable is to modify the timeout
+ for an entire test:
+
+ \snippet code/src_qtestlib_qtestcase.cpp set defaultTryTimeout
+
+ However, you can also set it for a specific scope, using
+ \l QAtomicScopedValueRollback:
+
+ \snippet code/src_qtestlib_qtestcase.cpp rollback defaultTryTimeout
+
+ To access the value, call \c load():
+
+ \snippet code/src_qtestlib_qtestcase.cpp get defaultTryTimeout
+*/
+
/*!
\overload
diff --git a/src/corelib/kernel/qtestsupport_core.h b/src/corelib/kernel/qtestsupport_core.h
index bf43c3a814a..6613f610b78 100644
--- a/src/corelib/kernel/qtestsupport_core.h
+++ b/src/corelib/kernel/qtestsupport_core.h
@@ -16,15 +16,12 @@ namespace QTest {
Q_CORE_EXPORT void qSleep(int ms);
Q_CORE_EXPORT void qSleep(std::chrono::milliseconds msecs);
-namespace Internal
-{
-static inline constexpr std::chrono::milliseconds defaultTryTimeout
- = std::chrono::milliseconds(5000);
-} // namespace Internal
+extern Q_CORE_EXPORT std::atomic<std::chrono::milliseconds> defaultTryTimeout;
template <typename Functor>
[[nodiscard]] bool
-qWaitFor(Functor predicate, QDeadlineTimer deadline = QDeadlineTimer(Internal::defaultTryTimeout))
+qWaitFor(Functor predicate, QDeadlineTimer deadline = QDeadlineTimer(
+ defaultTryTimeout.load(std::memory_order_relaxed)))
{
// We should not spin the event loop in case the predicate is already true,
// otherwise we might send new events that invalidate the predicate.
diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp
index e91797a3ffe..34ea987c3a0 100644
--- a/src/corelib/kernel/qvariant.cpp
+++ b/src/corelib/kernel/qvariant.cpp
@@ -1030,14 +1030,11 @@ QVariant::QVariant(const QPersistentModelIndex &modelIndex) : d(std::piecewise_c
*/
/*!
+ \fn QMetaType QVariant::metaType() const
\since 6.0
Returns the QMetaType of the value stored in the variant.
*/
-QMetaType QVariant::metaType() const
-{
- return d.type();
-}
/*!
Assigns the value of the variant \a variant to this variant.
@@ -1085,15 +1082,13 @@ void QVariant::detach()
*/
/*!
+ \fn const char *QVariant::typeName() const
+
Returns the name of the type stored in the variant. The returned
strings describe the C++ datatype used to store the data: for
example, "QFont", "QString", or "QVariantList". An Invalid
variant returns 0.
*/
-const char *QVariant::typeName() const
-{
- return d.type().name();
-}
/*!
Convert this variant to type QMetaType::UnknownType and free up any resources
diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h
index e9d3ffb9ae3..32250ab1e15 100644
--- a/src/corelib/kernel/qvariant.h
+++ b/src/corelib/kernel/qvariant.h
@@ -343,7 +343,9 @@ public:
return id;
}
+ QT_CORE_INLINE_SINCE(6, 10)
const char *typeName() const;
+ QT_CORE_INLINE_SINCE(6, 10)
QMetaType metaType() const;
bool canConvert(QMetaType targetType) const
@@ -740,6 +742,18 @@ QT_WARNING_POP
#endif
+#if QT_CORE_INLINE_IMPL_SINCE(6, 10)
+QMetaType QVariant::metaType() const
+{
+ return d.type();
+}
+
+const char *QVariant::typeName() const
+{
+ return d.type().name();
+}
+#endif
+
inline bool QVariant::isDetached() const
{ return !d.is_shared || d.data.shared->ref.loadRelaxed() == 1; }
diff --git a/src/corelib/serialization/qtextstream.cpp b/src/corelib/serialization/qtextstream.cpp
index 8226be3d219..9e6ed1dad95 100644
--- a/src/corelib/serialization/qtextstream.cpp
+++ b/src/corelib/serialization/qtextstream.cpp
@@ -830,10 +830,29 @@ QTextStreamPrivate::PaddingResult QTextStreamPrivate::padding(qsizetype len) con
return { left, right };
}
+namespace {
+template <typename StringView>
+auto parseSign(StringView data, const QLocale &loc)
+{
+ struct R {
+ StringView sign, rest;
+ explicit operator bool() const noexcept { return !sign.isEmpty(); }
+ };
+ // This assumes that the size in UTF-16 (return value of QLocale functions)
+ // and StringView is the same; in particular, it doesn't work for UTF-8!
+ if (const QString sign = loc.negativeSign(); data.startsWith(sign))
+ return R{data.first(sign.size()), data.sliced(sign.size())};
+ if (const QString sign = loc.positiveSign(); data.startsWith(sign))
+ return R{data.first(sign.size()), data.sliced(sign.size())};
+ return R{nullptr, data};
+}
+} // unnamed namespace
+
/*!
\internal
*/
-void QTextStreamPrivate::putString(QStringView data, bool number)
+template <typename StringView>
+void QTextStreamPrivate::putStringImpl(StringView data, bool number)
{
if (Q_UNLIKELY(params.fieldWidth > data.size())) {
@@ -842,11 +861,10 @@ void QTextStreamPrivate::putString(QStringView data, bool number)
const PaddingResult pad = padding(data.size());
if (params.fieldAlignment == QTextStream::AlignAccountingStyle && number) {
- const QChar sign = data.size() > 0 ? data.front() : QChar();
- if (sign == locale.negativeSign() || sign == locale.positiveSign()) {
+ if (const auto r = parseSign(data, locale)) {
// write the sign before the padding, then skip it later
- write(sign);
- data = data.sliced(1);
+ write(r.sign);
+ data = r.rest;
}
}
@@ -863,29 +881,20 @@ void QTextStreamPrivate::putString(QStringView data, bool number)
*/
void QTextStreamPrivate::putString(QLatin1StringView data, bool number)
{
- if (Q_UNLIKELY(params.fieldWidth > data.size())) {
-
- // handle padding
-
- const PaddingResult pad = padding(data.size());
-
- if (params.fieldAlignment == QTextStream::AlignAccountingStyle && number) {
- const QChar sign = data.size() > 0 ? QLatin1Char(*data.data()) : QChar();
- if (sign == locale.negativeSign() || sign == locale.positiveSign()) {
- // write the sign before the padding, then skip it later
- write(sign);
- data = QLatin1StringView(data.data() + 1, data.size() - 1);
- }
- }
+ putStringImpl(data, number);
+}
- writePadding(pad.left);
- write(data);
- writePadding(pad.right);
- } else {
- write(data);
- }
+/*!
+ \internal
+*/
+void QTextStreamPrivate::putString(QStringView data, bool number)
+{
+ putStringImpl(data, number);
}
+/*!
+ \internal
+*/
void QTextStreamPrivate::putString(QUtf8StringView data, bool number)
{
putString(data.toString(), number);
diff --git a/src/corelib/serialization/qtextstream_p.h b/src/corelib/serialization/qtextstream_p.h
index 6d6bfcd8365..2da7fe8e9d5 100644
--- a/src/corelib/serialization/qtextstream_p.h
+++ b/src/corelib/serialization/qtextstream_p.h
@@ -166,6 +166,10 @@ public:
bool fillReadBuffer(qint64 maxBytes = -1);
void resetReadBuffer();
void flushWriteBuffer();
+
+private:
+ template <typename StringView>
+ void putStringImpl(StringView view, bool number);
};
QT_END_NAMESPACE
diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp
index 9d0631f832d..d506662fba7 100644
--- a/src/corelib/text/qstring.cpp
+++ b/src/corelib/text/qstring.cpp
@@ -8968,7 +8968,10 @@ QString QString::arg_impl(qulonglong a, int fieldWidth, int base, QChar fillChar
\snippet code/src_corelib_text_qstring.cpp 2
\note In Qt versions prior to 6.9, this function was a regular function
- taking \c double.
+ taking \c double. As a consequence of being a template function now, it no
+ longer accepts arguments that merely implicitly convert to floating-point
+ types. A backwards-compatible fix is to cast such types to one of the C++
+ floating-point types.
\sa QLocale::toString(), QLocale::FloatingPointPrecisionOption, {Number Formats}
*/
diff --git a/src/corelib/thread/qthread_p.h b/src/corelib/thread/qthread_p.h
index b34336d7e16..c23a50d158e 100644
--- a/src/corelib/thread/qthread_p.h
+++ b/src/corelib/thread/qthread_p.h
@@ -374,6 +374,8 @@ public:
bool requiresCoreApplication = true;
private:
+ friend class QAbstractEventDispatcher;
+ friend class QBasicTimer;
static Q_AUTOTEST_EXPORT QThreadData *currentThreadData() noexcept Q_DECL_PURE_FUNCTION;
static Q_AUTOTEST_EXPORT QThreadData *createCurrentThreadData();
};
diff --git a/src/dbus/qdbuspendingcall.h b/src/dbus/qdbuspendingcall.h
index 1907fab78b9..a78ce76d548 100644
--- a/src/dbus/qdbuspendingcall.h
+++ b/src/dbus/qdbuspendingcall.h
@@ -12,6 +12,8 @@
#ifndef QT_NO_DBUS
+class tst_QDBusPendingReply;
+
QT_BEGIN_NAMESPACE
@@ -58,6 +60,8 @@ protected:
private:
QDBusPendingCall(); // not defined
+
+ friend class ::tst_QDBusPendingReply;
};
Q_DECLARE_SHARED(QDBusPendingCall)
diff --git a/src/dbus/qdbuspendingreply.cpp b/src/dbus/qdbuspendingreply.cpp
index b0fb2d75af5..c6523d915fd 100644
--- a/src/dbus/qdbuspendingreply.cpp
+++ b/src/dbus/qdbuspendingreply.cpp
@@ -76,6 +76,13 @@
*/
/*!
+ \fn template<typename... Types> QDBusPendingReply<Types...>::QDBusPendingReply(QDBusPendingReply &&other)
+ \since 6.10
+
+ Moves-constructs a new QDBusPendingReply from \a other.
+*/
+
+/*!
\fn template<typename... Types> QDBusPendingReply<Types...>::QDBusPendingReply(const QDBusPendingCall &call)
Creates a QDBusPendingReply object that will take its contents from
@@ -104,6 +111,17 @@
*/
/*!
+ \fn template<typename... Types> QDBusPendingReply &QDBusPendingReply<Types...>::operator=(QDBusPendingReply &&other)
+ \since 6.10
+
+ Move-assigns \a other to this QDBusPendingReply instance and drops
+ the reference to the current pending call. If the current reference
+ is to an unfinished pending call and this is the last reference, the
+ pending call will be canceled and there will be no way of retrieving
+ the reply's contents, when they arrive.
+*/
+
+/*!
\fn template<typename... Types> QDBusPendingReply &QDBusPendingReply<Types...>::operator=(const QDBusPendingCall &call)
Makes this object take its contents from the \a call pending call
diff --git a/src/dbus/qdbuspendingreply.h b/src/dbus/qdbuspendingreply.h
index 7b76bcd1b7c..9b6d2d67bed 100644
--- a/src/dbus/qdbuspendingreply.h
+++ b/src/dbus/qdbuspendingreply.h
@@ -11,6 +11,8 @@
#ifndef QT_NO_DBUS
+class tst_QDBusPendingReply;
+
QT_BEGIN_NAMESPACE
@@ -22,6 +24,8 @@ protected:
~QDBusPendingReplyBase();
QDBusPendingReplyBase(const QDBusPendingReplyBase &) = default;
QDBusPendingReplyBase &operator=(const QDBusPendingReplyBase &) = default;
+ QDBusPendingReplyBase(QDBusPendingReplyBase &&) noexcept = default;
+ QDBusPendingReplyBase &operator=(QDBusPendingReplyBase &&) noexcept = default;
#endif
void assign(const QDBusPendingCall &call);
@@ -55,24 +59,21 @@ namespace QDBusPendingReplyTypes {
template<typename... Types>
class QDBusPendingReply : public QDBusPendingReplyBase
{
+ friend class ::tst_QDBusPendingReply;
template<int Index> using Select = QDBusPendingReplyTypes::Select<Index, Types...>;
public:
enum { Count = std::is_same_v<typename Select<0>::Type, void> ? 0 : sizeof...(Types) };
inline constexpr int count() const { return Count; }
-
inline QDBusPendingReply() = default;
- inline QDBusPendingReply(const QDBusPendingReply &other)
- : QDBusPendingReplyBase(other)
- { }
+ // Rule Of Zero applies!
+
inline Q_IMPLICIT QDBusPendingReply(const QDBusPendingCall &call) // required by qdbusxml2cpp-generated code
{ *this = call; }
inline Q_IMPLICIT QDBusPendingReply(const QDBusMessage &message)
{ *this = message; }
- inline QDBusPendingReply &operator=(const QDBusPendingReply &other)
- { assign(other); return *this; }
inline QDBusPendingReply &operator=(const QDBusPendingCall &call)
{ assign(call); return *this; }
inline QDBusPendingReply &operator=(const QDBusMessage &message)
@@ -136,21 +137,19 @@ private:
template<>
class QDBusPendingReply<> : public QDBusPendingReplyBase
{
+ friend class ::tst_QDBusPendingReply;
public:
enum { Count = 0 };
inline int count() const { return Count; }
inline QDBusPendingReply() = default;
- inline QDBusPendingReply(const QDBusPendingReply &other)
- : QDBusPendingReplyBase(other)
- { }
+ // Rule Of Zero applies!
+
inline Q_IMPLICIT QDBusPendingReply(const QDBusPendingCall &call) // required by qdbusxml2cpp-generated code
{ *this = call; }
inline Q_IMPLICIT QDBusPendingReply(const QDBusMessage &message)
{ *this = message; }
- inline QDBusPendingReply &operator=(const QDBusPendingReply &other)
- { assign(other); return *this; }
inline QDBusPendingReply &operator=(const QDBusPendingCall &call)
{ assign(call); return *this; }
inline QDBusPendingReply &operator=(const QDBusMessage &message)
diff --git a/src/gui/accessible/linux/atspiadaptor.cpp b/src/gui/accessible/linux/atspiadaptor.cpp
index e0cd5aee25c..d6e46a6ca74 100644
--- a/src/gui/accessible/linux/atspiadaptor.cpp
+++ b/src/gui/accessible/linux/atspiadaptor.cpp
@@ -1011,6 +1011,24 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event)
}
break;
}
+ case QAccessible::RoleChanged: {
+ if (sendObject || sendObject_property_change
+ || sendObject_property_change_accessible_role) {
+ QAccessibleInterface *iface = event->accessibleInterface();
+ if (!iface || !iface->isValid()) {
+ qCDebug(lcAccessibilityAtspi, "RoleChanged event from invalid accessible.");
+ return;
+ }
+
+ QString path = pathForInterface(iface);
+ QVariantList args = packDBusSignalArguments(
+ "accessible-role"_L1, 0, 0,
+ QVariant::fromValue(QDBusVariant(uint(getRole(iface)))));
+ sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "PropertyChange"_L1,
+ args);
+ }
+ break;
+ }
case QAccessible::DescriptionChanged: {
if (sendObject || sendObject_property_change || sendObject_property_change_accessible_description) {
QAccessibleInterface *iface = event->accessibleInterface();
diff --git a/src/gui/accessible/qaccessible.cpp b/src/gui/accessible/qaccessible.cpp
index f605ada029c..004f653bb6e 100644
--- a/src/gui/accessible/qaccessible.cpp
+++ b/src/gui/accessible/qaccessible.cpp
@@ -230,6 +230,7 @@ Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\value ParentChanged An object's parent object changed.
\value PopupMenuEnd A pop-up menu has closed.
\value PopupMenuStart A pop-up menu has opened.
+ \value [since 6.11] RoleChanged The role of an object has changed.
\value ScrollingEnd A scrollbar scroll operation has ended (the mouse has
released the slider handle).
\value ScrollingStart A scrollbar scroll operation is about to start; this may
diff --git a/src/gui/accessible/qaccessible_base.h b/src/gui/accessible/qaccessible_base.h
index 9538d126ede..0a1a305b76d 100644
--- a/src/gui/accessible/qaccessible_base.h
+++ b/src/gui/accessible/qaccessible_base.h
@@ -103,6 +103,7 @@ public:
AcceleratorChanged = 0x80C0,
Announcement = 0x80D0,
IdentifierChanged = 0x80E0,
+ RoleChanged = 0x80E1,
// was declared after AcceleratorChanged, without explicit value
InvalidEvent = AcceleratorChanged + 1,
diff --git a/src/gui/kernel/qtestsupport_gui.cpp b/src/gui/kernel/qtestsupport_gui.cpp
index dfcea928fd8..ba247800e7b 100644
--- a/src/gui/kernel/qtestsupport_gui.cpp
+++ b/src/gui/kernel/qtestsupport_gui.cpp
@@ -70,7 +70,7 @@ bool QTest::qWaitForWindowActive(QWindow *window, QDeadlineTimer timeout)
*/
bool QTest::qWaitForWindowActive(QWindow *window)
{
- return qWaitForWindowActive(window, Internal::defaultTryTimeout);
+ return qWaitForWindowActive(window, defaultTryTimeout.load(std::memory_order_relaxed));
}
/*!
@@ -102,7 +102,7 @@ Q_GUI_EXPORT bool QTest::qWaitForWindowFocused(QWindow *window, QDeadlineTimer t
*/
bool QTest::qWaitForWindowFocused(QWindow *window)
{
- return qWaitForWindowFocused(window, Internal::defaultTryTimeout);
+ return qWaitForWindowFocused(window, defaultTryTimeout.load(std::memory_order_relaxed));
}
/*!
@@ -143,7 +143,7 @@ bool QTest::qWaitForWindowExposed(QWindow *window, QDeadlineTimer timeout)
*/
bool QTest::qWaitForWindowExposed(QWindow *window)
{
- return qWaitForWindowExposed(window, Internal::defaultTryTimeout);
+ return qWaitForWindowExposed(window, defaultTryTimeout.load(std::memory_order_relaxed));
}
namespace QTest {
diff --git a/src/plugins/networkinformation/android/CMakeLists.txt b/src/plugins/networkinformation/android/CMakeLists.txt
index 07d9201bbbd..c08f1c71d2c 100644
--- a/src/plugins/networkinformation/android/CMakeLists.txt
+++ b/src/plugins/networkinformation/android/CMakeLists.txt
@@ -38,9 +38,6 @@ set_property(
jar/Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend.jar
)
-set_property(
- TARGET
- QAndroidNetworkInformationPlugin
- APPEND PROPERTY QT_ANDROID_PERMISSIONS
- android.permission.ACCESS_NETWORK_STATE
+qt_internal_add_android_permission(QAndroidNetworkInformationPlugin
+ NAME android.permission.ACCESS_NETWORK_STATE
)
diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js
index dc7f4583da8..909d8da8856 100644
--- a/src/plugins/platforms/wasm/qtloader.js
+++ b/src/plugins/platforms/wasm/qtloader.js
@@ -190,7 +190,8 @@ async function qtLoad(config)
const originalLocateFile = config.locateFile;
config.locateFile = filename => {
const originalLocatedFilename = originalLocateFile ? originalLocateFile(filename) : filename;
- if (originalLocatedFilename.startsWith('libQt6'))
+ if (originalLocatedFilename.startsWith(
+ 'libQt6')) // wasmqtdeploy rely on this behavior, update both in case of change
return `${config.qt.qtdir}/lib/${originalLocatedFilename}`;
return originalLocatedFilename;
}
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp
index e5392f33cd7..44db371ca4d 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.cpp
+++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp
@@ -48,10 +48,6 @@ static void commonCopyEvent(val event)
void QWasmClipboard::cut(val event)
{
- QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
- if (wasmInput && wasmInput->usingTextInput())
- return;
-
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
// Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
QWindowSystemInterface::handleKeyEvent(
@@ -63,10 +59,6 @@ void QWasmClipboard::cut(val event)
void QWasmClipboard::copy(val event)
{
- QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
- if (wasmInput && wasmInput->usingTextInput())
- return;
-
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
// Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
QWindowSystemInterface::handleKeyEvent(
@@ -77,10 +69,6 @@ void QWasmClipboard::copy(val event)
void QWasmClipboard::paste(val event)
{
- QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
- if (wasmInput && wasmInput->usingTextInput())
- return;
-
event.call<void>("preventDefault"); // prevent browser from handling drop event
QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
@@ -183,86 +171,42 @@ void QWasmClipboard::writeToClipboardApi()
{
Q_ASSERT(m_hasClipboardApi);
- // copy event
- // browser event handler detected ctrl c if clipboard API
- // or Qt call from keyboard event handler
-
- QMimeData *_mimes = mimeData(QClipboard::Clipboard);
- if (!_mimes)
+ QMimeData *mimeData = this->mimeData(QClipboard::Clipboard);
+ if (!mimeData)
return;
- emscripten::val clipboardWriteArray = emscripten::val::array();
- QByteArray ba;
-
- for (auto mimetype : _mimes->formats()) {
- // we need to treat binary and text differently, as the blob method below
- // fails for text mimetypes
- // ignore text types
-
- if (mimetype.contains("STRING", Qt::CaseSensitive) || mimetype.contains("TEXT", Qt::CaseSensitive))
- continue;
-
- if (_mimes->hasHtml()) { // prefer html over text
- ba = _mimes->html().toLocal8Bit();
- // force this mime
- mimetype = "text/html";
- } else if (mimetype.contains("text/plain")) {
- ba = _mimes->text().toLocal8Bit();
- } else if (mimetype.contains("image")) {
- QImage img = qvariant_cast<QImage>( _mimes->imageData());
+ // Support for plain text, html and images (png) are standardized,
+ // copy those to the clipboard data object.
+ emscripten::val clipboardData = emscripten::val::object();
+ for (const QString &mimetype: mimeData->formats()) {
+ if (mimetype == QLatin1String("text/plain")) {
+ emscripten::val text = mimeData->text().toEcmaString();
+ clipboardData.set(mimetype.toEcmaString(), text);
+ } else if (mimetype == QLatin1String("text/html")) {
+ emscripten::val html = mimeData->html().toEcmaString();
+ clipboardData.set(mimetype.toEcmaString(), html);
+ } else if (mimetype.contains(QLatin1String("image"))) {
+ // Serialize the Qt image data to browser supported png
+ QImage img = qvariant_cast<QImage>(mimeData->imageData());
+ QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
img.save(&buffer, "PNG");
- mimetype = "image/png"; // chrome only allows png
- // clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write."
- // safari silently fails
- // so we use png internally for now
- } else {
- // DATA
- ba = _mimes->data(mimetype);
- }
- // Create file data Blob
- const char *content = ba.data();
- int dataLength = ba.length();
- if (dataLength < 1) {
- qDebug() << "no content found";
- return;
+ qstdweb::Blob blob = qstdweb::Blob::fromArrayBuffer(qstdweb::Uint8Array::copyFrom(ba).buffer());
+ clipboardData.set(std::string("image/png"), blob.val());
}
-
- emscripten::val document = emscripten::val::global("document");
- emscripten::val window = emscripten::val::global("window");
-
- emscripten::val fileContentView =
- emscripten::val(emscripten::typed_memory_view(dataLength, content));
- emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(dataLength);
- emscripten::val fileContentCopyView =
- emscripten::val::global("Uint8Array").new_(fileContentCopy);
- fileContentCopyView.call<void>("set", fileContentView);
-
- emscripten::val contentArray = emscripten::val::array();
- contentArray.call<void>("push", fileContentCopyView);
-
- // we have a blob, now create a ClipboardItem
- emscripten::val type = emscripten::val::array();
- type.set("type", mimetype.toEcmaString());
-
- emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
-
- emscripten::val clipboardItemObject = emscripten::val::object();
- clipboardItemObject.set(mimetype.toEcmaString(), contentBlob);
-
- val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject);
-
- clipboardWriteArray.call<void>("push", clipboardItemData);
-
- // Clipboard write is only supported with one ClipboardItem at the moment
- // but somehow this still works?
- // break;
}
- val navigator = val::global("navigator");
+ // Return if there is no data (creating an empty ClipboardItem is an error)
+ if (val::global("Object").call<val>("keys", clipboardData)["length"].as<int>() == 0)
+ return;
+ // Write a single clipboard item containing the data formats to the clipboard
+ emscripten::val clipboardItem = val::global("ClipboardItem").new_(clipboardData);
+ emscripten::val clipboardItemArray = emscripten::val::array();
+ clipboardItemArray.call<void>("push", clipboardItem);
+ val navigator = val::global("navigator");
qstdweb::Promise::make(
navigator["clipboard"], "write",
{
@@ -272,7 +216,7 @@ void QWasmClipboard::writeToClipboardApi()
<< QString::fromStdString(error["message"].as<std::string>());
}
},
- clipboardWriteArray);
+ clipboardItemArray);
}
void QWasmClipboard::writeToClipboard()
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp
index 2df7066b8e2..191e2947629 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp
@@ -25,105 +25,88 @@ void QWasmInputContext::inputCallback(emscripten::val event)
{
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "isComposing : " << event["isComposing"].as<bool>();
- QString inputStr = (event["data"] != emscripten::val::null()
- && event["data"] != emscripten::val::undefined()) ?
- QString::fromStdString(event["data"].as<std::string>()) : QString();
-
- QWasmInputContext *wasmInput =
- reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
-
emscripten::val inputType = event["inputType"];
- if (inputType != emscripten::val::null()
- && inputType != emscripten::val::undefined()) {
- const auto inputTypeString = inputType.as<std::string>();
- // There are many inputTypes for InputEvent
- // https://www.w3.org/TR/input-events-1/
- // Some of them should be implemented here later.
- qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString;
- if (!inputTypeString.compare("deleteContentBackward")) {
- QWindowSystemInterface::handleKeyEvent(0,
- QEvent::KeyPress,
- Qt::Key_Backspace,
- Qt::NoModifier);
- QWindowSystemInterface::handleKeyEvent(0,
- QEvent::KeyRelease,
- Qt::Key_Backspace,
- Qt::NoModifier);
- event.call<void>("stopImmediatePropagation");
- return;
- } else if (!inputTypeString.compare("deleteContentForward")) {
- QWindowSystemInterface::handleKeyEvent(0,
- QEvent::KeyPress,
- Qt::Key_Delete,
- Qt::NoModifier);
- QWindowSystemInterface::handleKeyEvent(0,
- QEvent::KeyRelease,
- Qt::Key_Delete,
- Qt::NoModifier);
- event.call<void>("stopImmediatePropagation");
- return;
- } else if (!inputTypeString.compare("insertCompositionText")) {
- qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
- wasmInput->insertPreedit();
- event.call<void>("stopImmediatePropagation");
- return;
- } else if (!inputTypeString.compare("insertReplacementText")) {
- qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
- //auto ranges = event.call<emscripten::val>("getTargetRanges");
- //qCDebug(qLcQpaWasmInputContext) << ranges["length"].as<int>();
- // WA For Korean IME
- // insertReplacementText should have targetRanges but
- // Safari cannot have it and just it seems to be supposed
- // to replace previous input.
- wasmInput->insertText(inputStr, true);
-
- event.call<void>("stopImmediatePropagation");
- return;
- } else if (!inputTypeString.compare("deleteCompositionText")) {
- wasmInput->setPreeditString("", 0);
- wasmInput->insertPreedit();
- event.call<void>("stopImmediatePropagation");
- return;
- } else if (!inputTypeString.compare("insertFromComposition")) {
- wasmInput->setPreeditString(inputStr, 0);
- wasmInput->insertPreedit();
- event.call<void>("stopImmediatePropagation");
- return;
- } else if (!inputTypeString.compare("insertText")) {
- wasmInput->insertText(inputStr);
- event.call<void>("stopImmediatePropagation");
+ if (inputType.isNull() || inputType.isUndefined())
+ return;
+ const auto inputTypeString = inputType.as<std::string>();
+
+ emscripten::val inputData = event["data"];
+ QString inputStr = (!inputData.isNull() && !inputData.isUndefined())
+ ? QString::fromEcmaString(inputData) : QString();
+
+ // There are many inputTypes for InputEvent
+ // https://www.w3.org/TR/input-events-1/
+ // Some of them should be implemented here later.
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString;
+ if (!inputTypeString.compare("deleteContentBackward")) {
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier);
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("deleteContentForward")) {
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier);
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_Delete, Qt::NoModifier);
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("insertCompositionText")) {
+ qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
+ insertPreedit();
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("insertReplacementText")) {
+ qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
+ //auto ranges = event.call<emscripten::val>("getTargetRanges");
+ //qCDebug(qLcQpaWasmInputContext) << ranges["length"].as<int>();
+ // WA For Korean IME
+ // insertReplacementText should have targetRanges but
+ // Safari cannot have it and just it seems to be supposed
+ // to replace previous input.
+ insertText(inputStr, true);
+
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("deleteCompositionText")) {
+ setPreeditString("", 0);
+ insertPreedit();
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("insertFromComposition")) {
+ setPreeditString(inputStr, 0);
+ insertPreedit();
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("insertText")) {
+ insertText(inputStr);
+ event.call<void>("stopImmediatePropagation");
#if QT_CONFIG(clipboard)
- } else if (!inputTypeString.compare("insertFromPaste")) {
- wasmInput->insertText(QGuiApplication::clipboard()->text());
- event.call<void>("stopImmediatePropagation");
- // These can be supported here,
- // But now, keyCallback in QWasmWindow
- // will take them as exceptions.
- //} else if (!inputTypeString.compare("deleteByCut")) {
+ } else if (!inputTypeString.compare("insertFromPaste")) {
+ insertText(QGuiApplication::clipboard()->text());
+ event.call<void>("stopImmediatePropagation");
+ // These can be supported here,
+ // But now, keyCallback in QWasmWindow
+ // will take them as exceptions.
+ //} else if (!inputTypeString.compare("deleteByCut")) {
#endif
- } else {
- qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType \"" << inputType.as<std::string>() << "\" is not supported in Qt yet";
- }
+ } else {
+ qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType \"" <<
+ inputType.as<std::string>() << "\" is not supported in Qt yet";
}
}
void QWasmInputContext::compositionEndCallback(emscripten::val event)
{
- const auto inputStr = QString::fromStdString(event["data"].as<std::string>());
+ const auto inputStr = QString::fromEcmaString(event["data"]);
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << inputStr;
- QWasmInputContext *wasmInput =
- reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
-
- if (wasmInput->preeditString().isEmpty())
+ if (preeditString().isEmpty())
return;
- if (inputStr != wasmInput->preeditString()) {
+ if (inputStr != preeditString()) {
qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO
<< "Composition string" << inputStr
- << "is differ from" << wasmInput->preeditString();
+ << "is differ from" << preeditString();
}
- wasmInput->commitPreeditAndClear();
+ commitPreeditAndClear();
}
void QWasmInputContext::compositionStartCallback(emscripten::val event)
@@ -151,19 +134,15 @@ static void beforeInputCallback(emscripten::val event)
void QWasmInputContext::compositionUpdateCallback(emscripten::val event)
{
- const auto compositionStr = QString::fromStdString(event["data"].as<std::string>());
+ const auto compositionStr = QString::fromEcmaString(event["data"]);
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr;
- QWasmInputContext *wasmInput =
- reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
-
// WA for IOS.
// Not sure now because I cannot test it anymore.
// int replaceSize = 0;
// emscripten::val win = emscripten::val::global("window");
// emscripten::val sel = win.call<emscripten::val>("getSelection");
-// if (sel != emscripten::val::null()
-// && sel != emscripten::val::undefined()
+// if (!sel.isNull() && !sel.isUndefined()
// && sel["rangeCount"].as<int>() > 0) {
// QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
// QCoreApplication::sendEvent(QGuiApplication::focusObject(), &queryEvent);
@@ -172,8 +151,8 @@ void QWasmInputContext::compositionUpdateCallback(emscripten::val event)
// qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
// qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
//
-// const QString &selectedStr = QString::fromUtf8(sel.call<emscripten::val>("toString").as<std::string>());
-// const auto &preeditStr = wasmInput->preeditString();
+// const QString &selectedStr = QString::fromEcmaString(sel.call<emscripten::val>("toString"));
+// const auto &preeditStr = preeditString();
// qCDebug(qLcQpaWasmInputContext) << "Selection.type : " << sel["type"].as<std::string>();
// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Selected: " << selectedStr;
// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "PreeditString: " << preeditStr;
@@ -189,90 +168,13 @@ void QWasmInputContext::compositionUpdateCallback(emscripten::val event)
// qCDebug(qLcQpaWasmInputContext) << "Range.endOffset : " << range["endOffset"].as<int>();
// }
//
-// wasmInput->setPreeditString(compositionStr, replaceSize);
- wasmInput->setPreeditString(compositionStr, 0);
-}
-
-#if QT_CONFIG(clipboard)
-static void copyCallback(emscripten::val event)
-{
- qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
-
- QClipboard *clipboard = QGuiApplication::clipboard();
- QString inputStr = clipboard->text();
- qCDebug(qLcQpaWasmInputContext) << "QClipboard : " << inputStr;
- event["clipboardData"].call<void>("setData",
- emscripten::val("text/plain"),
- inputStr.toStdString());
- event.call<void>("preventDefault");
- event.call<void>("stopImmediatePropagation");
-}
-
-static void cutCallback(emscripten::val event)
-{
- qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
-
- QClipboard *clipboard = QGuiApplication::clipboard();
- QString inputStr = clipboard->text();
- qCDebug(qLcQpaWasmInputContext) << "QClipboard : " << inputStr;
- event["clipboardData"].call<void>("setData",
- emscripten::val("text/plain"),
- inputStr.toStdString());
- event.call<void>("preventDefault");
- event.call<void>("stopImmediatePropagation");
+// setPreeditString(compositionStr, replaceSize);
+ setPreeditString(compositionStr, 0);
}
-static void pasteCallback(emscripten::val event)
-{
- qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
-
- emscripten::val clipboardData = event["clipboardData"].call<emscripten::val>("getData", emscripten::val("text/plain"));
- QString clipboardStr = QString::fromStdString(clipboardData.as<std::string>());
- qCDebug(qLcQpaWasmInputContext) << "wasm clipboard : " << clipboardStr;
- QClipboard *clipboard = QGuiApplication::clipboard();
- if (clipboard->text() != clipboardStr)
- clipboard->setText(clipboardStr);
-
- // propagate to input event (insertFromPaste)
-}
-#endif // QT_CONFIG(clipboard)
-
QWasmInputContext::QWasmInputContext()
{
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
- emscripten::val document = emscripten::val::global("document");
- // This 'input' can be an issue to handle multiple lines,
- // 'textarea' can be used instead.
- m_inputElement = document.call<emscripten::val>("createElement", std::string("input"));
- m_inputElement.set("type", "text");
- m_inputElement.set("contenteditable","true");
- m_inputElement.call<void>("setAttribute", std::string("aria-hidden"), std::string("true"));
-
- m_inputElement["style"].set("position", "absolute");
- m_inputElement["style"].set("left", 0);
- m_inputElement["style"].set("top", 0);
- m_inputElement["style"].set("opacity", 0);
- m_inputElement["style"].set("display", "");
- m_inputElement["style"].set("z-index", -2);
- m_inputElement["style"].set("width", "1px");
- m_inputElement["style"].set("height", "1px");
-
- m_inputElement.set("data-qinputcontext",
- emscripten::val(quintptr(reinterpret_cast<void *>(this))));
- emscripten::val body = document["body"];
- body.call<void>("appendChild", m_inputElement);
-
- m_inputCallback = QWasmEventHandler(m_inputElement, "input", QWasmInputContext::inputCallback);
- m_compositionEndCallback = QWasmEventHandler(m_inputElement, "compositionend", QWasmInputContext::compositionEndCallback);
- m_compositionStartCallback = QWasmEventHandler(m_inputElement, "compositionstart", QWasmInputContext::compositionStartCallback);
- m_compositionUpdateCallback = QWasmEventHandler(m_inputElement, "compositionupdate", QWasmInputContext::compositionUpdateCallback);
-
-#if QT_CONFIG(clipboard)
- // Clipboard for InputContext
- m_clipboardCut = QWasmEventHandler(m_inputElement, "cut", cutCallback);
- m_clipboardCopy = QWasmEventHandler(m_inputElement, "copy", copyCallback);
- m_clipboardPaste = QWasmEventHandler(m_inputElement, "paste", pasteCallback);
-#endif
}
QWasmInputContext::~QWasmInputContext()
@@ -303,6 +205,9 @@ void QWasmInputContext::showInputPanel()
void QWasmInputContext::updateGeometry()
{
+ if (m_inputElement.isNull())
+ return;
+
const QWindow *focusWindow = QGuiApplication::focusWindow();
if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) {
m_inputElement["style"].set("left", "0px");
@@ -312,23 +217,12 @@ void QWasmInputContext::updateGeometry()
Q_ASSERT(m_focusObject);
Q_ASSERT(m_inputMethodAccepted);
- // Set the geometry
- QPoint globalPos;
- const QRect cursorRectangle = QPlatformInputContext::cursorRectangle().toRect();
- if (cursorRectangle.isValid()) {
- qCDebug(qLcQpaWasmInputContext)
- << Q_FUNC_INFO << "cursorRectangle: " << cursorRectangle;
- globalPos = focusWindow->mapToGlobal(cursorRectangle.topLeft());
- if (globalPos.x() > 0)
- globalPos.setX(globalPos.x() - 1);
- if (globalPos.y() > 0)
- globalPos.setY(globalPos.y() - 1);
- }
-
- const auto styleLeft = std::to_string(globalPos.x()) + "px";
- const auto styleTop = std::to_string(globalPos.y()) + "px";
- m_inputElement["style"].set("left", styleLeft);
- m_inputElement["style"].set("top", styleTop);
+ const QRect inputItemRectangle = QPlatformInputContext::inputItemRectangle().toRect();
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "propagating inputItemRectangle:" << inputItemRectangle;
+ m_inputElement["style"].set("left", std::to_string(inputItemRectangle.x()) + "px");
+ m_inputElement["style"].set("top", std::to_string(inputItemRectangle.y()) + "px");
+ m_inputElement["style"].set("width", std::to_string(inputItemRectangle.width()) + "px");
+ m_inputElement["style"].set("height", std::to_string(inputItemRectangle.height()) + "px");
}
}
@@ -341,16 +235,21 @@ void QWasmInputContext::updateInputElement()
updateGeometry();
// If there is no focus object, or no visible input panel, remove focus
- const QWindow *focusWindow = QGuiApplication::focusWindow();
+ QWasmWindow *focusWindow = QWasmWindow::fromWindow(QGuiApplication::focusWindow());
if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) {
- m_inputElement.set("value", "");
+ if (!m_inputElement.isNull()) {
+ m_inputElement.set("value", "");
+ m_inputElement.set("inputMode", std::string("none"));
+ }
- if (QWasmWindow *wasmwindow = QWasmWindow::fromWindow(focusWindow))
- wasmwindow->focus();
- else
- m_inputElement.call<void>("blur");
+ if (focusWindow) {
+ focusWindow->focus();
+ } else {
+ if (!m_inputElement.isNull())
+ m_inputElement.call<void>("blur");
+ }
- m_inputElement.set("inputMode", std::string("none"));
+ m_inputElement = emscripten::val::null();
return;
}
@@ -358,6 +257,8 @@ void QWasmInputContext::updateInputElement()
Q_ASSERT(m_focusObject);
Q_ASSERT(m_inputMethodAccepted);
+ m_inputElement = focusWindow->inputElement();
+
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << QRectF::fromDOMRect(m_inputElement.call<emscripten::val>("getBoundingClientRect"));
// Set the text input
@@ -375,7 +276,15 @@ void QWasmInputContext::updateInputElement()
m_inputElement.set("selectionStart", queryEvent.value(Qt::ImAnchorPosition).toUInt());
m_inputElement.set("selectionEnd", queryEvent.value(Qt::ImCursorPosition).toUInt());
+ QInputMethodQueryEvent query((Qt::InputMethodQueries(Qt::ImHints)));
+ QCoreApplication::sendEvent(m_focusObject, &query);
+ if (Qt::InputMethodHints(query.value(Qt::ImHints).toInt()).testFlag(Qt::ImhHiddenText))
+ m_inputElement.set("type", "password");
+ else
+ m_inputElement.set("type", "text");
+
m_inputElement.set("inputMode", std::string("text"));
+
m_inputElement.call<void>("focus");
}
@@ -383,16 +292,6 @@ void QWasmInputContext::setFocusObject(QObject *object)
{
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << object << inputMethodAccepted();
- QInputMethodQueryEvent query(Qt::InputMethodQueries(Qt::ImEnabled | Qt::ImHints));
- QCoreApplication::sendEvent(object, &query);
- if (query.value(Qt::ImEnabled).toBool()
- && Qt::InputMethodHints(query.value(Qt::ImHints).toInt()).testFlag(Qt::ImhHiddenText)) {
- m_inputElement.set("type", "password");
- } else {
- if (m_inputElement["type"].as<std::string>() != std::string("text"))
- m_inputElement.set("type", "text");
- }
-
// Commit the previous composition before change m_focusObject
if (m_focusObject && !m_preeditString.isEmpty())
commitPreeditAndClear();
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h
index 72476adfe1b..6d24c7fea0d 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.h
+++ b/src/plugins/platforms/wasm/qwasminputcontext.h
@@ -35,38 +35,33 @@ public:
void setPreeditString(QString preeditStr, int replaceSize);
void insertPreedit();
void commitPreeditAndClear();
- emscripten::val m_inputElement = emscripten::val::null();
void insertText(QString inputStr, bool replace = false);
- QWasmEventHandler m_inputCallback;
- QWasmEventHandler m_compositionEndCallback;
- QWasmEventHandler m_compositionStartCallback;
- QWasmEventHandler m_compositionUpdateCallback;
-
bool usingTextInput() const { return m_inputMethodAccepted; }
void setFocusObject(QObject *object) override;
- static void inputCallback(emscripten::val event);
- static void compositionEndCallback(emscripten::val event);
- static void compositionStartCallback(emscripten::val event);
- static void compositionUpdateCallback(emscripten::val event);
+ void inputCallback(emscripten::val event);
+ void compositionEndCallback(emscripten::val event);
+ void compositionStartCallback(emscripten::val event);
+ void compositionUpdateCallback(emscripten::val event);
void updateGeometry();
+ bool isActive() const {
+ return m_focusObject && m_inputMethodAccepted;
+ }
+
private:
void updateInputElement();
private:
- QWasmEventHandler m_clipboardCut;
- QWasmEventHandler m_clipboardCopy;
- QWasmEventHandler m_clipboardPaste;
-
QString m_preeditString;
int m_replaceSize = 0;
bool m_inputMethodAccepted = false;
QObject *m_focusObject = nullptr;
+ emscripten::val m_inputElement = emscripten::val::null();
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index d690bcfe10a..d27385be723 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -51,7 +51,10 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
m_decoratedWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
m_window(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
m_a11yContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
- m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas")))
+ m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas"))),
+ m_focusHelper(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_inputElement(m_document.call<emscripten::val>("createElement", emscripten::val("input")))
+
{
m_decoratedWindow.set("className", "qt-decorated-window");
m_decoratedWindow["style"].set("display", std::string("none"));
@@ -80,17 +83,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
m_canvas["classList"].call<void>("add", emscripten::val("qt-window-canvas"));
- // Set contentEditable for two reasons;
- // 1) so that the window gets clipboard events,
- // 2) For applications who will handle keyboard events, but without having inputMethodAccepted()
- //
- // Set inputMode to none to avoid keyboard popping up on push buttons
- // This is a tradeoff, we are not able to separate between a push button and
- // a widget that reads keyboard events.
- m_canvas.call<void>("setAttribute", std::string("inputmode"), std::string("none"));
- m_canvas.call<void>("setAttribute", std::string("contenteditable"), std::string("true"));
- m_canvas["style"].set("outline", std::string("none"));
-
#if QT_CONFIG(clipboard)
if (QWasmClipboard::shouldInstallWindowEventHandlers()) {
m_cutCallback = QWasmEventHandler(m_canvas, "cut", QWasmClipboard::cut);
@@ -99,9 +91,37 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
}
#endif
- // Set inputMode to none to stop the mobile keyboard from opening
- // when the user clicks on the window.
- m_window.set("inputMode", std::string("none"));
+ // Set up m_focusHelper, which is an invisible child element of the window which takes
+ // focus on behalf of the window any time the window has focus in general, but none
+ // of the special child elements such as the inputElment or a11y elements have focus.
+ // Set inputMode=none set to prevent the virtual keyboard from popping up.
+ m_focusHelper["classList"].call<void>("add", emscripten::val("qt-window-focus-helper"));
+ m_focusHelper.set("inputMode", std::string("none"));
+ m_focusHelper.call<void>("setAttribute", std::string("aria-hidden"), std::string("true"));
+ m_focusHelper.call<void>("setAttribute", std::string("contenteditable"), std::string("true"));
+ m_focusHelper["style"].set("position", "absolute");
+ m_focusHelper["style"].set("left", 0);
+ m_focusHelper["style"].set("top", 0);
+ m_focusHelper["style"].set("width", "1px");
+ m_focusHelper["style"].set("height", "1px");
+ m_focusHelper["style"].set("z-index", -2);
+ m_focusHelper["style"].set("opacity", 0);
+ m_window.call<void>("appendChild", m_focusHelper);
+
+ // Set up m_inputElement, which takes focus whenever a Qt text input UI element has
+ // foucus.
+ m_inputElement["classList"].call<void>("add", emscripten::val("qt-window-input-element"));
+ m_inputElement.set("type", "text");
+ m_inputElement.call<void>("setAttribute", std::string("aria-hidden"), std::string("true"));
+ m_inputElement["style"].set("position", "absolute");
+ m_inputElement["style"].set("left", 0);
+ m_inputElement["style"].set("top", 0);
+ m_inputElement["style"].set("width", "1px");
+ m_inputElement["style"].set("height", "1px");
+ m_inputElement["style"].set("z-index", -2);
+ m_inputElement["style"].set("opacity", 0);
+ m_inputElement["style"].set("display", "");
+ m_window.call<void>("appendChild", m_inputElement);
// Hide the canvas from screen readers.
m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true"));
@@ -193,21 +213,20 @@ void QWasmWindow::registerEventHandlers()
m_wheelEventCallback = QWasmEventHandler(m_window, "wheel",
[this](emscripten::val event) { this->handleWheelEvent(event); });
- QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
- if (wasmInput) {
- m_keyDownCallbackForInputContext =
- QWasmEventHandler(wasmInput->m_inputElement, "keydown",
- [this](emscripten::val event) { this->handleKeyForInputContextEvent(EventType::KeyDown, event); });
- m_keyUpCallbackForInputContext =
- QWasmEventHandler(wasmInput->m_inputElement, "keyup",
- [this](emscripten::val event) { this->handleKeyForInputContextEvent(EventType::KeyUp, event); });
- }
-
- m_keyDownCallback = QWasmEventHandler(m_canvas, "keydown",
+ m_keyDownCallback = QWasmEventHandler(m_window, "keydown",
[this](emscripten::val event) { this->handleKeyEvent(KeyEvent(EventType::KeyDown, event, m_deadKeySupport)); });
- m_keyUpCallback =QWasmEventHandler(m_canvas, "keyup",
+ m_keyUpCallback =QWasmEventHandler(m_window, "keyup",
[this](emscripten::val event) {this->handleKeyEvent(KeyEvent(EventType::KeyUp, event, m_deadKeySupport)); });
-}
+
+ m_inputCallback = QWasmEventHandler(m_window, "input",
+ [this](emscripten::val event){ handleInputEvent(event); });
+ m_compositionUpdateCallback = QWasmEventHandler(m_window, "compositionupdate",
+ [this](emscripten::val event){ handleCompositionUpdateEvent(event); });
+ m_compositionStartCallback = QWasmEventHandler(m_window, "compositionstart",
+ [this](emscripten::val event){ handleCompositionStartEvent(event); });
+ m_compositionEndCallback = QWasmEventHandler(m_window, "compositionend",
+ [this](emscripten::val event){ handleCompositionEndEvent(event); });
+ }
QWasmWindow::~QWasmWindow()
{
@@ -624,10 +643,15 @@ void QWasmWindow::commitParent(QWasmWindowTreeNode *parent)
void QWasmWindow::handleKeyEvent(const KeyEvent &event)
{
- qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent";
- if (processKey(event)) {
- event.webEvent.call<void>("preventDefault");
- event.webEvent.call<void>("stopPropagation");
+ qCDebug(qLcQpaWasmInputContext) << "handleKeyEvent";
+
+ if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive()) {
+ handleKeyForInputContextEvent(event);
+ } else {
+ if (processKey(event)) {
+ event.webEvent.call<void>("preventDefault");
+ event.webEvent.call<void>("stopPropagation");
+ }
}
}
@@ -658,7 +682,7 @@ bool QWasmWindow::processKey(const KeyEvent &event)
#endif
}
-void QWasmWindow::handleKeyForInputContextEvent(EventType eventType, const emscripten::val &event)
+void QWasmWindow::handleKeyForInputContextEvent(const KeyEvent &keyEvent)
{
//
// Things to consider:
@@ -668,40 +692,43 @@ void QWasmWindow::handleKeyForInputContextEvent(EventType eventType, const emscr
// complex (i.e Chinese et al) input handling
// Multiline text edit backspace at start of line
//
- const QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
- if (wasmInput) {
+ emscripten::val event = keyEvent.webEvent;
+ bool useInputContext = [event]() -> bool {
+ const QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
+ if (!wasmInput)
+ return false;
+
const auto keyString = QString::fromStdString(event["key"].as<std::string>());
qCDebug(qLcQpaWasmInputContext) << "Key callback" << keyString << keyString.size();
- if (keyString == "Unidentified") {
- // Android makes a bunch of KeyEvents as "Unidentified"
- // They will be processed just in InputContext.
- return;
- } else if (event["isComposing"].as<bool>()) {
- // Handled by the input context
- return;
- } else if (event["ctrlKey"].as<bool>()
- || event["altKey"].as<bool>()
- || event["metaKey"].as<bool>()) {
- // Not all platforms use 'isComposing' for '~' + 'a', in this
- // case send the key with state ('ctrl', 'alt', or 'meta') to
- // processKeyForInputContext
-
- ; // fallthrough
- } else if (keyString.size() != 1) {
- // This is like; 'Shift','ArrowRight','AltGraph', ...
- // send all of these to processKeyForInputContext
-
- ; // fallthrough
- } else if (wasmInput->inputMethodAccepted()) {
- // processed in inputContext with skipping processKey
- return;
- }
- }
- qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent";
- if (processKeyForInputContext(KeyEvent(eventType, event, m_deadKeySupport)))
- event.call<void>("preventDefault");
- event.call<void>("stopImmediatePropagation");
+ // Events with isComposing set are handled by the input context
+ bool composing = event["isComposing"].as<bool>();
+
+ // Android makes a bunch of KeyEvents as "Unidentified",
+ // make inputContext handle those.
+ bool androidUnidentified = (keyString == "Unidentified");
+
+ // Not all platforms use 'isComposing' for '~' + 'a', in this
+ // case send the key with state ('ctrl', 'alt', or 'meta') to
+ // processKeyForInputContext
+ bool hasModifiers = event["ctrlKey"].as<bool>()
+ || event["altKey"].as<bool>()
+ || event["metaKey"].as<bool>();
+
+ // This is like; 'Shift','ArrowRight','AltGraph', ...
+ // send all of these to processKeyForInputContext
+ bool hasNoncharacterKeyString = keyString.size() != 1;
+
+ bool overrideCompose = !hasModifiers && !hasNoncharacterKeyString && wasmInput->inputMethodAccepted();
+ return composing || androidUnidentified || overrideCompose;
+ }();
+
+ if (!useInputContext) {
+ qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent";
+ if (processKeyForInputContext(keyEvent))
+ event.call<void>("preventDefault");
+ event.call<void>("stopImmediatePropagation");
+ }
}
bool QWasmWindow::processKeyForInputContext(const KeyEvent &event)
@@ -729,6 +756,30 @@ bool QWasmWindow::processKeyForInputContext(const KeyEvent &event)
return result;
}
+void QWasmWindow::handleInputEvent(emscripten::val event)
+{
+ if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive())
+ inputContext->inputCallback(event);
+}
+
+void QWasmWindow::handleCompositionStartEvent(emscripten::val event)
+{
+ if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive())
+ inputContext->compositionStartCallback(event);
+}
+
+void QWasmWindow::handleCompositionUpdateEvent(emscripten::val event)
+{
+ if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive())
+ inputContext->compositionUpdateCallback(event);
+}
+
+void QWasmWindow::handleCompositionEndEvent(emscripten::val event)
+{
+ if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive())
+ inputContext->compositionEndCallback(event);
+}
+
void QWasmWindow::handlePointerEnterLeaveEvent(const PointerEvent &event)
{
if (processPointerEnterLeave(event))
@@ -1040,7 +1091,7 @@ void QWasmWindow::requestActivateWindow()
void QWasmWindow::focus()
{
- m_canvas.call<void>("focus");
+ m_focusHelper.call<void>("focus");
}
bool QWasmWindow::setMouseGrabEnabled(bool grab)
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index 904e736a7e7..0c63ebdc16e 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -100,6 +100,7 @@ public:
emscripten::val context2d() const { return m_context2d; }
emscripten::val a11yContainer() const { return m_a11yContainer; }
emscripten::val inputHandlerElement() const { return m_window; }
+ emscripten::val inputElement() const { return m_inputElement; }
// QNativeInterface::Private::QWasmWindow
emscripten::val document() const override { return m_document; }
@@ -137,8 +138,13 @@ private:
void handleKeyEvent(const KeyEvent &event);
bool processKey(const KeyEvent &event);
- void handleKeyForInputContextEvent(EventType eventType, const emscripten::val &event);
+ void handleKeyForInputContextEvent(const KeyEvent &event);
bool processKeyForInputContext(const KeyEvent &event);
+ void handleInputEvent(emscripten::val event);
+ void handleCompositionStartEvent(emscripten::val event);
+ void handleCompositionUpdateEvent(emscripten::val event);
+ void handleCompositionEndEvent(emscripten::val event);
+
void handlePointerEnterLeaveEvent(const PointerEvent &event);
bool processPointerEnterLeave(const PointerEvent &event);
void processPointer(const PointerEvent &event);
@@ -154,11 +160,14 @@ private:
QWasmDeadKeySupport *m_deadKeySupport;
QRect m_normalGeometry {0, 0, 0 ,0};
- emscripten::val m_document = emscripten::val::undefined();
- emscripten::val m_decoratedWindow = emscripten::val::undefined();
- emscripten::val m_window = emscripten::val::undefined();
- emscripten::val m_a11yContainer = emscripten::val::undefined();
- emscripten::val m_canvas = emscripten::val::undefined();
+ emscripten::val m_document;
+ emscripten::val m_decoratedWindow;
+ emscripten::val m_window;
+ emscripten::val m_a11yContainer;
+ emscripten::val m_canvas;
+ emscripten::val m_focusHelper;
+ emscripten::val m_inputElement;
+
emscripten::val m_context2d = emscripten::val::undefined();
std::unique_ptr<NonClientArea> m_nonClientArea;
@@ -169,6 +178,10 @@ private:
QWasmEventHandler m_keyUpCallback;
QWasmEventHandler m_keyDownCallbackForInputContext;
QWasmEventHandler m_keyUpCallbackForInputContext;
+ QWasmEventHandler m_inputCallback;
+ QWasmEventHandler m_compositionStartCallback;
+ QWasmEventHandler m_compositionUpdateCallback;
+ QWasmEventHandler m_compositionEndCallback;
QWasmEventHandler m_pointerDownCallback;
QWasmEventHandler m_pointerMoveCallback;
diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt
index 254a43c0dc6..40e85ac4b1d 100644
--- a/src/plugins/platforms/wayland/CMakeLists.txt
+++ b/src/plugins/platforms/wayland/CMakeLists.txt
@@ -77,7 +77,6 @@ qt_internal_add_module(WaylandClient
qwaylandplatformservices.cpp qwaylandplatformservices_p.h
qwaylandpointergestures.cpp qwaylandpointergestures_p.h
qwaylandscreen.cpp qwaylandscreen_p.h
- qwaylandsessionmanager.cpp qwaylandsessionmanager_p.h
qwaylandshellsurface.cpp qwaylandshellsurface_p.h
qwaylandshm.cpp qwaylandshm_p.h
qwaylandshmbackingstore.cpp qwaylandshmbackingstore_p.h
@@ -233,6 +232,11 @@ qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_egl AND NOT QT_FEAT
QT_EGL_NO_X11
)
+qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_sessionmanager
+ SOURCES
+ qwaylandsessionmanager.cpp qwaylandsessionmanager_p.h
+)
+
qt_internal_add_docs(WaylandClient
doc/qtwaylandclient.qdocconf
)
diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h
index 4595940508c..a12311eff74 100644
--- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h
+++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h
@@ -119,7 +119,9 @@ private:
QWaylandXdgToplevelDecorationV1 *m_decoration = nullptr;
QScopedPointer<QWaylandXdgExportedV2> m_exported;
QScopedPointer<QWaylandXdgDialogV1> m_xdgDialog;
+#ifndef QT_NO_SESSIONMANAGER
QScopedPointer<QtWayland::xx_toplevel_session_v1> m_session;
+#endif
};
class Positioner : public QtWayland::xdg_positioner {
diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp
index d66710f4e55..4b55aa33a2d 100644
--- a/src/plugins/platforms/wayland/qwaylandintegration.cpp
+++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp
@@ -530,11 +530,13 @@ QWaylandShellIntegration *QWaylandIntegration::createShellIntegration(const QStr
}
}
+#ifndef QT_NO_SESSIONMANAGER
QPlatformSessionManager *QWaylandIntegration::createPlatformSessionManager(const QString &id, const QString &key) const
{
Q_UNUSED(key);
return new QWaylandSessionManager(mDisplay.data(), id);
}
+#endif
void QWaylandIntegration::reset()
{
diff --git a/src/plugins/platforms/wayland/qwaylandintegration_p.h b/src/plugins/platforms/wayland/qwaylandintegration_p.h
index 04a0787d1ef..2b683dc3321 100644
--- a/src/plugins/platforms/wayland/qwaylandintegration_p.h
+++ b/src/plugins/platforms/wayland/qwaylandintegration_p.h
@@ -132,7 +132,9 @@ private:
void initializeShellIntegration();
void initializeInputDeviceIntegration();
QWaylandShellIntegration *createShellIntegration(const QString& interfaceName);
+#ifndef QT_NO_SESSIONMANAGER
QPlatformSessionManager *createPlatformSessionManager(const QString &id, const QString &key) const override;
+#endif
const QString mPlatformName;
QScopedPointer<QPlatformFontDatabase> mFontDb;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
index 638d684ce4f..8a494edb4f9 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
@@ -142,6 +142,9 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event
case QAccessible::NameChanged:
QWindowsUiaMainProvider::notifyNameChange(event);
break;
+ case QAccessible::RoleChanged:
+ QWindowsUiaMainProvider::notifyRoleChange(event);
+ break;
case QAccessible::SelectionAdd:
QWindowsUiaMainProvider::notifySelectionChange(event);
break;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
index 5eaba7fbb37..de9c8e5efe1 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
@@ -27,7 +27,6 @@
#include <QtGui/qaccessible.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qwindow.h>
-#include <qpa/qplatforminputcontextfactory_p.h>
#include <QtCore/private/qcomvariant_p.h>
#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU)
@@ -173,6 +172,18 @@ void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event)
}
}
+void QWindowsUiaMainProvider::notifyRoleChange(QAccessibleEvent *event)
+{
+ if (QAccessibleInterface *accessible = event->accessibleInterface()) {
+ if (auto provider = providerForAccessible(accessible)) {
+ QComVariant oldVal;
+ QComVariant newVal{ roleToControlTypeId(accessible->role()) };
+ UiaRaiseAutomationPropertyChangedEvent(provider.Get(), UIA_ControlTypePropertyId,
+ oldVal.get(), newVal.get());
+ }
+ }
+}
+
void QWindowsUiaMainProvider::notifySelectionChange(QAccessibleEvent *event)
{
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
@@ -570,19 +581,7 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR
*pRetVal = QComVariant{ UIA_WindowControlTypeId }.release();
} else {
// Control type converted from role.
- auto controlType = roleToControlTypeId(accessible->role());
-
- // The native OSK should be disabled if the Qt OSK is in use,
- // or if disabled via application attribute.
- static bool imModuleEmpty = QPlatformInputContextFactory::requested().isEmpty();
- bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_DisableNativeVirtualKeyboard);
-
- // If we want to disable the native OSK auto-showing
- // we have to report text fields as non-editable.
- if (controlType == UIA_EditControlTypeId && (!imModuleEmpty || nativeVKDisabled))
- controlType = UIA_TextControlTypeId;
-
- *pRetVal = QComVariant{ controlType }.release();
+ *pRetVal = QComVariant{ roleToControlTypeId(accessible->role()) }.release();
}
break;
case UIA_HelpTextPropertyId:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
index eae8e26901c..7d89f9ffcad 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
@@ -33,6 +33,7 @@ public:
static void notifyStateChange(QAccessibleStateChangeEvent *event);
static void notifyValueChange(QAccessibleValueChangeEvent *event);
static void notifyNameChange(QAccessibleEvent *event);
+ static void notifyRoleChange(QAccessibleEvent *event);
static void notifySelectionChange(QAccessibleEvent *event);
static void notifyTextChange(QAccessibleEvent *event);
static void raiseNotification(QAccessibleAnnouncementEvent *event);
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
index 7536ece4dbc..4146a56b226 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
@@ -10,6 +10,8 @@
#include <QtGui/qwindow.h>
#include <QtGui/private/qhighdpiscaling_p.h>
+#include <qpa/qplatforminputcontextfactory_p.h>
+
#include <cmath>
QT_BEGIN_NAMESPACE
@@ -153,7 +155,19 @@ long roleToControlTypeId(QAccessible::Role role)
{QAccessible::BlockQuote, UIA_GroupControlTypeId},
};
- return mapping.value(role, UIA_CustomControlTypeId);
+ long controlType = mapping.value(role, UIA_CustomControlTypeId);
+
+ // The native OSK should be disabled if the Qt OSK is in use,
+ // or if disabled via application attribute.
+ static bool imModuleEmpty = QPlatformInputContextFactory::requested().isEmpty();
+ bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_DisableNativeVirtualKeyboard);
+
+ // If we want to disable the native OSK auto-showing
+ // we have to report text fields as non-editable.
+ if (controlType == UIA_EditControlTypeId && (!imModuleEmpty || nativeVKDisabled))
+ controlType = UIA_TextControlTypeId;
+
+ return controlType;
}
// True if a character can be a separator for a text unit.
diff --git a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
index f810cf6241f..4f18a2a7ca7 100644
--- a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
+++ b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
@@ -3,6 +3,7 @@
#include <QTest>
#include <QSqlDatabase>
#include <QFontDatabase>
+#include <QtCore/qatomicscopedvaluerollback.h>
#include <initializer_list>
@@ -24,7 +25,22 @@ class MyTestClass : public QObject
void addSingleStringRows();
void addMultStringRows();
void addDataRow();
+
+ private Q_SLOTS:
+ void initTestCase();
+ void defaultTryTimeout();
};
+
+void MyTestClass::initTestCase()
+{
+//! [set defaultTryTimeout]
+ using namespace std::chrono_literals;
+ // Since the atomic itself (defaultTryTimeout) is the only data,
+ // all reads and stores can be relaxed.
+ QTest::defaultTryTimeout.store(1s, std::memory_order_relaxed);
+//! [set defaultTryTimeout]
+}
+
// dummy
void closeAllDatabases()
{
@@ -230,3 +246,18 @@ const auto restoreDefaultLocale = qScopeGuard([prior = QLocale()]() {
//! [36]
QLocale::setDefault(QLocale::c());
}
+
+void MyTestClass::defaultTryTimeout()
+{
+ using namespace std::chrono_literals;
+
+//! [rollback defaultTryTimeout]
+ const auto timeoutRollback = QAtomicScopedValueRollback(
+ QTest::defaultTryTimeout, 1s, std::memory_order_relaxed);
+//! [rollback defaultTryTimeout]
+
+//! [get defaultTryTimeout]
+ // Since the atomic itself is all the data, all reads and stores can be relaxed.
+ QCOMPARE(QTest::defaultTryTimeout.load(std::memory_order_relaxed), 1s);
+//! [get defaultTryTimeout]
+}
diff --git a/src/testlib/qtestcase.h b/src/testlib/qtestcase.h
index c924f788106..e9d5ad90bf5 100644
--- a/src/testlib/qtestcase.h
+++ b/src/testlib/qtestcase.h
@@ -210,7 +210,8 @@ do { \
QVERIFY(expr); \
} while (false)
-#define QTRY_VERIFY(expr) QTRY_VERIFY_WITH_TIMEOUT(expr, QTest::Internal::defaultTryTimeout)
+#define QTRY_VERIFY(expr) QTRY_VERIFY_WITH_TIMEOUT( \
+ expr, QTest::defaultTryTimeout.load(std::memory_order_relaxed))
// Will try to wait for the expression to become true while allowing event processing
#define QTRY_VERIFY2_WITH_TIMEOUT(expr, messageExpression, timeout) \
@@ -220,7 +221,8 @@ do { \
} while (false)
#define QTRY_VERIFY2(expr, messageExpression) \
- QTRY_VERIFY2_WITH_TIMEOUT(expr, messageExpression, QTest::Internal::defaultTryTimeout)
+ QTRY_VERIFY2_WITH_TIMEOUT(expr, messageExpression, \
+ QTest::defaultTryTimeout.load(std::memory_order_relaxed))
// Will try to wait for the comparison to become successful while allowing event processing
#define QTRY_COMPARE_WITH_TIMEOUT(expr, expected, timeout) \
@@ -230,7 +232,8 @@ do { \
} while (false)
#define QTRY_COMPARE(expr, expected) \
- QTRY_COMPARE_WITH_TIMEOUT(expr, expected, QTest::Internal::defaultTryTimeout)
+ QTRY_COMPARE_WITH_TIMEOUT(expr, expected, \
+ QTest::defaultTryTimeout.load(std::memory_order_relaxed))
#define QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, op, opId, timeout) \
do { \
@@ -243,37 +246,43 @@ do { \
QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, ==, Equal, timeout)
#define QTRY_COMPARE_EQ(computed, baseline) \
- QTRY_COMPARE_EQ_WITH_TIMEOUT(computed, baseline, QTest::Internal::defaultTryTimeout)
+ QTRY_COMPARE_EQ_WITH_TIMEOUT(computed, baseline, \
+ QTest::defaultTryTimeout.load(std::memory_order_relaxed))
#define QTRY_COMPARE_NE_WITH_TIMEOUT(computed, baseline, timeout) \
QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, !=, NotEqual, timeout)
#define QTRY_COMPARE_NE(computed, baseline) \
- QTRY_COMPARE_NE_WITH_TIMEOUT(computed, baseline, QTest::Internal::defaultTryTimeout)
+ QTRY_COMPARE_NE_WITH_TIMEOUT(computed, baseline, \
+ QTest::defaultTryTimeout.load(std::memory_order_relaxed))
#define QTRY_COMPARE_LT_WITH_TIMEOUT(computed, baseline, timeout) \
QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, <, LessThan, timeout)
#define QTRY_COMPARE_LT(computed, baseline) \
- QTRY_COMPARE_LT_WITH_TIMEOUT(computed, baseline, QTest::Internal::defaultTryTimeout)
+ QTRY_COMPARE_LT_WITH_TIMEOUT(computed, baseline, \
+ QTest::defaultTryTimeout.load(std::memory_order_relaxed))
#define QTRY_COMPARE_LE_WITH_TIMEOUT(computed, baseline, timeout) \
QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, <=, LessThanOrEqual, timeout)
#define QTRY_COMPARE_LE(computed, baseline) \
- QTRY_COMPARE_LE_WITH_TIMEOUT(computed, baseline, QTest::Internal::defaultTryTimeout)
+ QTRY_COMPARE_LE_WITH_TIMEOUT(computed, baseline, \
+ QTest::defaultTryTimeout.load(std::memory_order_relaxed))
#define QTRY_COMPARE_GT_WITH_TIMEOUT(computed, baseline, timeout) \
QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, >, GreaterThan, timeout)
#define QTRY_COMPARE_GT(computed, baseline) \
- QTRY_COMPARE_GT_WITH_TIMEOUT(computed, baseline, QTest::Internal::defaultTryTimeout)
+ QTRY_COMPARE_GT_WITH_TIMEOUT(computed, baseline, \
+ QTest::defaultTryTimeout.load(std::memory_order_relaxed))
#define QTRY_COMPARE_GE_WITH_TIMEOUT(computed, baseline, timeout) \
QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, >=, GreaterThanOrEqual, timeout)
#define QTRY_COMPARE_GE(computed, baseline) \
- QTRY_COMPARE_GE_WITH_TIMEOUT(computed, baseline, QTest::Internal::defaultTryTimeout)
+ QTRY_COMPARE_GE_WITH_TIMEOUT(computed, baseline, \
+ QTest::defaultTryTimeout.load(std::memory_order_relaxed))
#define QSKIP_INTERNAL(statement) \
do {\
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index 1ff52f8a84f..2f06cbcf67e 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -28,6 +28,10 @@ if(QT_FEATURE_macdeployqt)
add_subdirectory(macdeployqt)
endif()
+if(QT_FEATURE_wasmdeployqt)
+ add_subdirectory(wasmdeployqt)
+endif()
+
if(QT_FEATURE_windeployqt)
add_subdirectory(windeployqt)
endif()
diff --git a/src/tools/configure.cmake b/src/tools/configure.cmake
index 6a9c1b8e3f3..27ea90b89ac 100644
--- a/src/tools/configure.cmake
+++ b/src/tools/configure.cmake
@@ -18,6 +18,12 @@ qt_feature("macdeployqt" PRIVATE
AUTODETECT CMAKE_HOST_APPLE
CONDITION MACOS AND QT_FEATURE_thread)
+qt_feature("wasmdeployqt" PRIVATE
+ SECTION "Deployment"
+ LABEL "WebAssembly deployment tool"
+ PURPOSE "The WebAssembly deployment tool is designed to automate the process of creating a deployable folder especially for dynamic linking case variant."
+ CONDITION QT_FEATURE_process)
+
qt_feature("windeployqt" PRIVATE
SECTION "Deployment"
LABEL "Windows deployment tool"
diff --git a/src/tools/wasmdeployqt/CMakeLists.txt b/src/tools/wasmdeployqt/CMakeLists.txt
new file mode 100644
index 00000000000..7305c14c269
--- /dev/null
+++ b/src/tools/wasmdeployqt/CMakeLists.txt
@@ -0,0 +1,19 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## wasmdeployqt Tool:
+#####################################################################
+
+qt_get_tool_target_name(target_name wasmdeployqt)
+qt_internal_add_tool(${target_name}
+ TOOLS_TARGET Core
+ USER_FACING
+ INSTALL_VERSIONED_LINK
+ TARGET_DESCRIPTION "Qt WebAssembly Deployment Tool"
+ SOURCES
+ main.cpp wasmbinary.cpp jsontools.cpp
+ LIBRARIES
+ Qt::CorePrivate
+)
+qt_internal_return_unless_building_tools()
diff --git a/src/tools/wasmdeployqt/common.h b/src/tools/wasmdeployqt/common.h
new file mode 100644
index 00000000000..258d6161e67
--- /dev/null
+++ b/src/tools/wasmdeployqt/common.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <QHash>
+#include <QString>
+
+struct PreloadEntry
+{
+ QString source;
+ QString destination;
+
+ bool operator==(const PreloadEntry &other) const
+ {
+ return source == other.source && destination == other.destination;
+ }
+};
+
+inline uint qHash(const PreloadEntry &key, uint seed = 0)
+{
+ return qHash(key.source, seed) ^ qHash(key.destination, seed);
+}
+
+#endif
diff --git a/src/tools/wasmdeployqt/jsontools.cpp b/src/tools/wasmdeployqt/jsontools.cpp
new file mode 100644
index 00000000000..d76f9190b73
--- /dev/null
+++ b/src/tools/wasmdeployqt/jsontools.cpp
@@ -0,0 +1,101 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QDir>
+#include <QJsonArray>
+#include <QJsonObject>
+
+#include "jsontools.h"
+#include "common.h"
+
+#include <iostream>
+#include <optional>
+
+namespace JsonTools {
+
+bool savePreloadFile(QSet<PreloadEntry> preload, QString destFile)
+{
+
+ QJsonArray jsonArray;
+ for (const PreloadEntry &entry : preload) {
+ QJsonObject obj;
+ obj["source"] = entry.source;
+ obj["destination"] = entry.destination;
+ jsonArray.append(obj);
+ }
+ QJsonDocument doc(jsonArray);
+
+ QFile outFile(destFile);
+ if (outFile.exists()) {
+ if (!outFile.remove()) {
+ std::cout << "ERROR: Failed to delete old file: " << outFile.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ }
+ if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ std::cout << "ERROR: Failed to open file for writing:" << outFile.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ if (outFile.write(doc.toJson(QJsonDocument::Indented)) == -1) {
+ std::cout << "ERROR: Failed writing into file :" << outFile.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ if (!outFile.flush()) {
+ std::cout << "ERROR: Failed flushing the file :" << outFile.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ outFile.close();
+ return true;
+}
+
+std::optional<QSet<PreloadEntry>> getPreloadsFromQmlImportScannerOutput(QString output)
+{
+ QString qtLibPath = "$QTDIR/lib";
+ QString qtQmlPath = "$QTDIR/qml";
+ QString qtDeployQmlPath = "/qt/qml";
+ QSet<PreloadEntry> res;
+ auto addImport = [&res](const PreloadEntry &entry) {
+ // qDebug() << "adding " << entry.source << "" << entry.destination;
+ res.insert(entry);
+ };
+
+ QJsonParseError parseError;
+ QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8(), &parseError);
+
+ if (parseError.error != QJsonParseError::NoError) {
+ std::cout << "ERROR: QmlImport JSON parse error: " << parseError.errorString().toStdString()
+ << std::endl;
+ return std::nullopt;
+ }
+ if (!doc.isArray()) {
+ std::cout << "ERROR: QmlImport JSON is not an array." << std::endl;
+ return std::nullopt;
+ }
+
+ QJsonArray jsonArray = doc.array();
+ for (const QJsonValue &value : jsonArray) {
+ if (value.isObject()) {
+ QJsonObject obj = value.toObject();
+ auto relativePath = obj["relativePath"].toString();
+ auto plugin = obj["plugin"].toString();
+ if (plugin.isEmpty() || relativePath.isEmpty()) {
+ continue;
+ }
+ auto pluginFilename = "lib" + plugin + ".so";
+ addImport(PreloadEntry{
+ QDir::cleanPath(qtQmlPath + "/" + relativePath + "/" + pluginFilename),
+ QDir::cleanPath(qtDeployQmlPath + "/" + relativePath + "/" + pluginFilename) });
+ addImport(PreloadEntry{
+ QDir::cleanPath(qtQmlPath + "/" + relativePath + "/" + "qmldir"),
+ QDir::cleanPath(qtDeployQmlPath + "/" + relativePath + "/" + "qmldir") });
+ }
+ }
+
+ return res;
+}
+
+}; // namespace JsonTools
diff --git a/src/tools/wasmdeployqt/jsontools.h b/src/tools/wasmdeployqt/jsontools.h
new file mode 100644
index 00000000000..a1691a2be8d
--- /dev/null
+++ b/src/tools/wasmdeployqt/jsontools.h
@@ -0,0 +1,19 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef JSONTOOLS_H
+#define JSONTOOLS_H
+
+#include <QFileInfo>
+#include <QSet>
+
+#include "common.h"
+
+#include <optional>
+
+namespace JsonTools {
+bool savePreloadFile(QSet<PreloadEntry> preload, QString destFile);
+std::optional<QSet<PreloadEntry>> getPreloadsFromQmlImportScannerOutput(QString output);
+}; // namespace JsonTools
+
+#endif
diff --git a/src/tools/wasmdeployqt/main.cpp b/src/tools/wasmdeployqt/main.cpp
new file mode 100644
index 00000000000..3bf2647cfaf
--- /dev/null
+++ b/src/tools/wasmdeployqt/main.cpp
@@ -0,0 +1,417 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "common.h"
+#include "jsontools.h"
+#include "wasmbinary.h"
+
+#include <QCoreApplication>
+#include <QDir>
+#include <QDirListing>
+#include <QDirIterator>
+#include <QtGlobal>
+#include <QLibraryInfo>
+#include <QJsonDocument>
+#include <QStringList>
+#include <QtCore/QCommandLineOption>
+#include <QtCore/QCommandLineParser>
+#include <QtCore/QProcess>
+#include <QQueue>
+#include <QMap>
+#include <QSet>
+
+#include <optional>
+#include <iostream>
+#include <ostream>
+
+struct Parameters
+{
+ std::optional<QString> argAppPath;
+ QString appWasmPath;
+ std::optional<QDir> qtHostDir;
+ std::optional<QDir> qtWasmDir;
+ QList<QDir> libPaths;
+ std::optional<QDir> qmlRootPath;
+
+ QSet<QString> loadedQtLibraries;
+};
+
+bool parseArguments(Parameters &params)
+{
+ QCoreApplication::setApplicationName("wasmdeployqt");
+ QCoreApplication::setApplicationVersion("1.0");
+ QCommandLineParser parser;
+ parser.setApplicationDescription(
+ QStringLiteral("Qt for WebAssembly deployment tool \n\n"
+ "Example:\n"
+ "wasmdeployqt app.wasm --qml-root-path=repo/myapp "
+ "--qt-wasm-dir=/home/user/qt/shared-qt-wasm/bin"));
+ parser.addHelpOption();
+
+ QStringList args = QCoreApplication::arguments();
+
+ parser.addPositionalArgument("app", "Path to the application.");
+ QCommandLineOption libPathOption("lib-path", "Colon-separated list of library directories.",
+ "paths");
+ parser.addOption(libPathOption);
+ QCommandLineOption qtWasmDirOption("qt-wasm-dir", "Path to the Qt for WebAssembly directory.",
+ "dir");
+ parser.addOption(qtWasmDirOption);
+ QCommandLineOption qtHostDirOption("qt-host-dir", "Path to the Qt host directory.", "dir");
+ parser.addOption(qtHostDirOption);
+ QCommandLineOption qmlRootPathOption("qml-root-path", "Root directory for QML files.", "dir");
+ parser.addOption(qmlRootPathOption);
+ parser.process(args);
+
+ const QStringList positionalArgs = parser.positionalArguments();
+ if (positionalArgs.size() > 1) {
+ std::cout << "ERROR: Expected only one positional argument with path to the app. Received: "
+ << positionalArgs.join(" ").toStdString() << std::endl;
+ return false;
+ }
+ if (!positionalArgs.isEmpty()) {
+ params.argAppPath = positionalArgs.first();
+ }
+
+ if (parser.isSet(libPathOption)) {
+ QStringList paths = parser.value(libPathOption).split(';', Qt::SkipEmptyParts);
+ for (const QString &path : paths) {
+ QDir dir(path);
+ if (dir.exists()) {
+ params.libPaths.append(dir);
+ } else {
+ std::cout << "ERROR: Directory does not exist: " << path.toStdString() << std::endl;
+ return false;
+ }
+ }
+ }
+ if (parser.isSet(qtWasmDirOption)) {
+ QDir dir(parser.value(qtWasmDirOption));
+ if (dir.cdUp() && dir.exists())
+ params.qtWasmDir = dir;
+ else {
+ std::cout << "ERROR: Directory does not exist: " << dir.absolutePath().toStdString()
+ << std::endl;
+ return false;
+ }
+ }
+ if (parser.isSet(qtHostDirOption)) {
+ QDir dir(parser.value(qtHostDirOption));
+ if (dir.cdUp() && dir.exists())
+ params.qtHostDir = dir;
+ else {
+ std::cout << "ERROR: Directory does not exist: " << dir.absolutePath().toStdString()
+ << std::endl;
+ return false;
+ }
+ }
+ if (parser.isSet(qmlRootPathOption)) {
+ QDir dir(parser.value(qmlRootPathOption));
+ if (dir.exists()) {
+ params.qmlRootPath = dir;
+ } else {
+ std::cout << "ERROR: Directory specified for qml-root-path does not exist: "
+ << dir.absolutePath().toStdString() << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+std::optional<QString> detectAppName()
+{
+ QDirIterator it(QDir::currentPath(), QStringList() << "*.html" << "*.wasm" << "*.js",
+ QDir::NoFilter);
+ QMap<QString, QSet<QString>> fileGroups;
+ while (it.hasNext()) {
+ QFileInfo fileInfo(it.next());
+ QString baseName = fileInfo.completeBaseName();
+ QString suffix = fileInfo.suffix();
+ fileGroups[baseName].insert(suffix);
+ }
+ for (auto it = fileGroups.constBegin(); it != fileGroups.constEnd(); ++it) {
+ const QSet<QString> &extensions = it.value();
+ if (extensions.contains("html") && extensions.contains("js")
+ && extensions.contains("wasm")) {
+ return it.key();
+ }
+ }
+ return std::nullopt;
+}
+
+bool verifyPaths(Parameters &params)
+{
+ if (params.argAppPath) {
+ QFileInfo fileInfo(*params.argAppPath);
+ if (!fileInfo.exists()) {
+ std::cout << "ERROR: Cannot find " << params.argAppPath->toStdString() << std::endl;
+ std::cout << "Make sure that the path is valid." << std::endl;
+ return false;
+ }
+ params.appWasmPath = fileInfo.absoluteFilePath();
+ } else {
+ auto appName = detectAppName();
+ if (!appName) {
+ std::cout << "ERROR: Cannot find the application in current directory. Specify the "
+ "path as an argument:"
+ "wasmdeployqt <path-to-app-wasm-binary>"
+ << std::endl;
+ return false;
+ }
+ params.appWasmPath = QDir::current().filePath(*appName + ".wasm");
+ std::cout << "Automatically detected " << params.appWasmPath.toStdString() << std::endl;
+ }
+ if (!params.qtWasmDir) {
+ std::cout << "ERROR: Please set path to Qt WebAssembly installation as "
+ "--qt-wasm-dir=<path_to_qt_wasm_bin>"
+ << std::endl;
+ return false;
+ }
+ if (!params.qtHostDir) {
+ auto qtHostPath = QLibraryInfo::path(QLibraryInfo::BinariesPath);
+ if (qtHostPath.length() == 0) {
+ std::cout << "ERROR: Cannot read Qt host path or detect it from environment. Please "
+ "pass it explicitly with --qt-host-dir=<path>. "
+ << std::endl;
+ } else {
+ auto qtHostDir = QDir(qtHostPath);
+ if (!qtHostDir.cdUp()) {
+ std::cout << "ERROR: Invalid Qt host path: "
+ << qtHostDir.absolutePath().toStdString() << std::endl;
+ return false;
+ }
+ params.qtHostDir = qtHostDir;
+ }
+ }
+ params.libPaths.push_front(params.qtWasmDir->filePath("lib"));
+ params.libPaths.push_front(*params.qtWasmDir);
+ return true;
+}
+
+bool copyFile(QString srcPath, QString destPath)
+{
+ auto file = QFile(destPath);
+ if (file.exists()) {
+ file.remove();
+ }
+ QFileInfo destInfo(destPath);
+ if (!QDir().mkpath(destInfo.path())) {
+ std::cout << "ERROR: Cannot create path " << destInfo.path().toStdString() << std::endl;
+ return false;
+ }
+ if (!QFile::copy(srcPath, destPath)) {
+
+ std::cout << "ERROR: Failed to copy " << srcPath.toStdString() << " to "
+ << destPath.toStdString() << std::endl;
+
+ return false;
+ }
+ return true;
+}
+
+bool copyDirectDependencies(QList<QString> dependencies, const Parameters &params)
+{
+ for (auto &&depFilename : dependencies) {
+ if (params.loadedQtLibraries.contains(depFilename)) {
+ continue; // dont copy library that has been already copied
+ }
+
+ std::optional<QString> libPath;
+ for (auto &&libDir : params.libPaths) {
+ auto path = libDir.filePath(depFilename);
+ QFileInfo file(path);
+ if (file.exists()) {
+ libPath = path;
+ }
+ }
+ if (!libPath) {
+ std::cout << "ERROR: Cannot find required library " << depFilename.toStdString()
+ << std::endl;
+ return false;
+ }
+ if (!copyFile(*libPath, QDir::current().filePath(depFilename)))
+ return false;
+ }
+ std::cout << "INFO: Succesfully copied direct dependencies." << std::endl;
+ return true;
+}
+
+QStringList findSoFiles(const QString &directory)
+{
+ QStringList soFiles;
+ QDir baseDir(directory);
+ if (!baseDir.exists())
+ return soFiles;
+
+ QDirIterator it(directory, QStringList() << "*.so", QDir::Files, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ it.next();
+ QString absPath = it.filePath();
+ QString filePath = baseDir.relativeFilePath(absPath);
+ soFiles.append(filePath);
+ }
+ return soFiles;
+}
+
+bool copyQtLibs(Parameters &params)
+{
+ Q_ASSERT(params.qtWasmDir);
+ auto qtLibDir = *params.qtWasmDir;
+ if (!qtLibDir.cd("lib")) {
+ std::cout << "ERROR: Cannot find lib directory in Qt installation." << std::endl;
+ return false;
+ }
+ auto qtLibTargetDir = QDir(QDir(QDir::current().filePath("qt")).filePath("lib"));
+
+ auto soFiles = findSoFiles(qtLibDir.absolutePath());
+ for (auto &&soFilePath : soFiles) {
+ auto relativeFilePath = QDir("lib").filePath(soFilePath);
+ auto srcPath = qtLibDir.absoluteFilePath(soFilePath);
+ auto destPath = qtLibTargetDir.absoluteFilePath(soFilePath);
+ if (!copyFile(srcPath, destPath))
+ return false;
+ params.loadedQtLibraries.insert(QFileInfo(srcPath).fileName());
+ }
+ std::cout << "INFO: Succesfully deployed qt lib shared objects." << std::endl;
+ return true;
+}
+
+bool copyPreloadPlugins(Parameters &params)
+{
+ Q_ASSERT(params.qtWasmDir);
+ auto qtPluginsDir = *params.qtWasmDir;
+ if (!qtPluginsDir.cd("plugins")) {
+ std::cout << "ERROR: Cannot find plugins directory in Qt installation." << std::endl;
+ return false;
+ }
+ auto qtPluginsTargetDir = QDir(QDir(QDir::current().filePath("qt")).filePath("plugins"));
+
+ // copy files
+ auto soFiles = findSoFiles(qtPluginsDir.absolutePath());
+ for (auto &&soFilePath : soFiles) {
+ auto relativeFilePath = QDir("plugins").filePath(soFilePath);
+ params.loadedQtLibraries.insert(QFileInfo(relativeFilePath).fileName());
+ auto srcPath = qtPluginsDir.absoluteFilePath(soFilePath);
+ auto destPath = qtPluginsTargetDir.absoluteFilePath(soFilePath);
+ if (!copyFile(srcPath, destPath))
+ return false;
+ }
+
+ // qt_plugins.json
+ QSet<PreloadEntry> preload{ { { "qt.conf" }, { "/qt.conf" } } };
+ for (auto &&plugin : soFiles) {
+ PreloadEntry entry;
+ entry.source = QDir("$QTDIR").filePath("plugins") + QDir::separator()
+ + QDir(qtPluginsDir).relativeFilePath(plugin);
+ entry.destination = "/qt/plugins/" + QDir(qtPluginsTargetDir).relativeFilePath(plugin);
+ preload.insert(entry);
+ }
+ JsonTools::savePreloadFile(preload, QDir::current().filePath("qt_plugins.json"));
+
+ QString qtconfContent = "[Paths]\nPrefix = /qt\n";
+ QString filePath = QDir::current().filePath("qt.conf");
+
+ QFile file(filePath);
+ if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ QTextStream out(&file);
+ out << qtconfContent;
+ if (!file.flush()) {
+ std::cout << "ERROR: Failed flushing the file :" << file.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ file.close();
+ } else {
+ std::cout << "ERROR: Failed to write to qt.conf." << std::endl;
+ return false;
+ }
+ std::cout << "INFO: Succesfully deployed qt plugins." << std::endl;
+ return true;
+}
+
+bool copyPreloadQmlImports(Parameters &params)
+{
+ Q_ASSERT(params.qtWasmDir);
+ if (!params.qmlRootPath) {
+ std::cout << "WARNING: qml-root-path not specified. Skipping generating preloads for QML "
+ "imports."
+ << std::endl;
+ std::cout << "WARNING: This may lead to erronous behaviour if applications requires QML "
+ "imports."
+ << std::endl;
+ QSet<PreloadEntry> preload;
+ JsonTools::savePreloadFile(preload, QDir::current().filePath("qt_qml_imports.json"));
+ return true;
+ }
+ auto qmlImportScannerPath = params.qtHostDir
+ ? QDir(params.qtHostDir->filePath("libexec")).filePath("qmlimportscanner")
+ : "qmlimportscanner";
+ QProcess process;
+ auto qmlImportPath = *params.qtWasmDir;
+ qmlImportPath.cd("qml");
+ if (!qmlImportPath.exists()) {
+ std::cout << "ERROR: Cannot find qml import path: "
+ << qmlImportPath.absolutePath().toStdString() << std::endl;
+ return -1;
+ }
+
+ QStringList args{ "-rootPath", params.qmlRootPath->absolutePath(), "-importPath",
+ qmlImportPath.absolutePath() };
+ process.start(qmlImportScannerPath, args);
+ if (!process.waitForFinished()) {
+ std::cout << "ERROR: Failed to execute qmlImportScanner." << std::endl;
+ return false;
+ }
+
+ QString stdoutOutput = process.readAllStandardOutput();
+ auto qmlImports = JsonTools::getPreloadsFromQmlImportScannerOutput(stdoutOutput);
+ if (!qmlImports) {
+ return false;
+ }
+ JsonTools::savePreloadFile(*qmlImports, QDir::current().filePath("qt_qml_imports.json"));
+ for (const PreloadEntry &import : *qmlImports) {
+ auto relativePath = import.source;
+ relativePath.remove("$QTDIR/");
+
+ auto srcPath = params.qtWasmDir->absoluteFilePath(relativePath);
+ auto destPath = QDir(QDir::current().filePath("qt")).absoluteFilePath(relativePath);
+ if (!copyFile(srcPath, destPath))
+ return false;
+ }
+ std::cout << "INFO: Succesfully deployed qml imports." << std::endl;
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+ Parameters params;
+ if (!parseArguments(params)) {
+ return -1;
+ }
+ if (!verifyPaths(params)) {
+ return -1;
+ }
+ std::cout << "INFO: Target: " << params.appWasmPath.toStdString() << std::endl;
+ WasmBinary wasmBinary(params.appWasmPath);
+ if (wasmBinary.type == WasmBinary::Type::INVALID) {
+ return -1;
+ } else if (wasmBinary.type == WasmBinary::Type::STATIC) {
+ std::cout << "INFO: This is statically linked WebAssembly binary." << std::endl;
+ std::cout << "INFO: No extra steps required!" << std::endl;
+ return 0;
+ }
+ std::cout << "INFO: Verified as shared module." << std::endl;
+
+ if (!copyQtLibs(params))
+ return -1;
+ if (!copyPreloadPlugins(params))
+ return -1;
+ if (!copyPreloadQmlImports(params))
+ return -1;
+ if (!copyDirectDependencies(wasmBinary.dependencies, params))
+ return -1;
+
+ std::cout << "INFO: Deployment done!" << std::endl;
+ return 0;
+}
diff --git a/src/tools/wasmdeployqt/wasmbinary.cpp b/src/tools/wasmdeployqt/wasmbinary.cpp
new file mode 100644
index 00000000000..1a041c94066
--- /dev/null
+++ b/src/tools/wasmdeployqt/wasmbinary.cpp
@@ -0,0 +1,91 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "wasmbinary.h"
+
+#include <QFile>
+
+#include <iostream>
+
+WasmBinary::WasmBinary(QString filepath)
+{
+ QFile file(filepath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ std::cout << "ERROR: Cannot open the file " << filepath.toStdString() << std::endl;
+ std::cout << file.errorString().toStdString() << std::endl;
+ type = WasmBinary::Type::INVALID;
+ return;
+ }
+ auto bytes = file.readAll();
+ if (!parsePreambule(bytes)) {
+ type = WasmBinary::Type::INVALID;
+ }
+}
+
+bool WasmBinary::parsePreambule(QByteArrayView data)
+{
+ const auto preambuleSize = 24;
+ if (data.size() < preambuleSize) {
+ std::cout << "ERROR: Preambule of binary shorter than expected!" << std::endl;
+ return false;
+ }
+ uint32_t int32View[6];
+ std::memcpy(int32View, data.data(), sizeof(int32View));
+ if (int32View[0] != 0x6d736100) {
+ std::cout << "ERROR: Magic WASM number not found in binary. Binary corrupted?" << std::endl;
+ return false;
+ }
+ if (data[8] != 0) {
+ type = WasmBinary::Type::STATIC;
+ return true;
+ } else {
+ type = WasmBinary::Type::SHARED;
+ }
+ const auto sectionStart = 9;
+ size_t offset = sectionStart;
+ auto sectionSize = getLeb(data, offset);
+ auto sectionEnd = sectionStart + sectionSize;
+ auto name = getString(data, offset);
+ if (name != "dylink.0") {
+ type = WasmBinary::Type::INVALID;
+ std::cout << "ERROR: dylink.0 was not found in supposedly dynamically linked module"
+ << std::endl;
+ return false;
+ }
+
+ const auto WASM_DYLINK_NEEDED = 0x2;
+ while (offset < sectionEnd) {
+ auto subsectionType = data[offset++];
+ auto subsectionSize = getLeb(data, offset);
+ if (subsectionType == WASM_DYLINK_NEEDED) {
+ auto neededDynlibsCount = getLeb(data, offset);
+ while (neededDynlibsCount--) {
+ dependencies.append(getString(data, offset));
+ }
+ } else {
+ offset += subsectionSize;
+ }
+ }
+ return true;
+}
+
+size_t WasmBinary::getLeb(QByteArrayView data, size_t &offset)
+{
+ auto ret = 0;
+ auto mul = 1;
+ while (true) {
+ auto byte = data[offset++];
+ ret += (byte & 0x7f) * mul;
+ mul *= 0x80;
+ if (!(byte & 0x80))
+ break;
+ }
+ return ret;
+}
+
+QString WasmBinary::getString(QByteArrayView data, size_t &offset)
+{
+ auto length = getLeb(data, offset);
+ offset += length;
+ return QString::fromUtf8(data.sliced(offset - length, length));
+}
diff --git a/src/tools/wasmdeployqt/wasmbinary.h b/src/tools/wasmdeployqt/wasmbinary.h
new file mode 100644
index 00000000000..c3bb3f0eaa4
--- /dev/null
+++ b/src/tools/wasmdeployqt/wasmbinary.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef WASMBINARY_H
+#define WASMBINARY_H
+
+#include <QString>
+#include <QList>
+
+class WasmBinary
+{
+public:
+ enum class Type { INVALID, STATIC, SHARED };
+ WasmBinary(QString filepath);
+ Type type;
+ QList<QString> dependencies;
+
+private:
+ bool parsePreambule(QByteArrayView data);
+ size_t getLeb(QByteArrayView data, size_t &offset);
+ QString getString(QByteArrayView data, size_t &offset);
+};
+
+#endif
diff --git a/src/widgets/accessible/qaccessiblewidget.cpp b/src/widgets/accessible/qaccessiblewidget.cpp
index 5421dc511bb..3d18117dd78 100644
--- a/src/widgets/accessible/qaccessiblewidget.cpp
+++ b/src/widgets/accessible/qaccessiblewidget.cpp
@@ -374,6 +374,8 @@ QStringList QAccessibleWidget::actionNames() const
if (widget()->isEnabled()) {
if (widget()->focusPolicy() != Qt::NoFocus)
names << setFocusAction();
+ if (widget()->contextMenuPolicy() == Qt::ActionsContextMenu && widget()->actions().size() > 0)
+ names << showMenuAction();
}
return names;
}
@@ -388,6 +390,11 @@ void QAccessibleWidget::doAction(const QString &actionName)
if (widget()->isWindow())
widget()->activateWindow();
widget()->setFocus();
+ } else if (actionName == showMenuAction()) {
+ QContextMenuEvent e(QContextMenuEvent::Other,
+ QPoint(), widget()->mapToGlobal(QPoint()),
+ QGuiApplication::keyboardModifiers());
+ QCoreApplication::sendEvent(widget(), &e);
}
}
diff --git a/src/widgets/accessible/qaccessiblewidgets.cpp b/src/widgets/accessible/qaccessiblewidgets.cpp
index 56e9c2a509e..608a99b575d 100644
--- a/src/widgets/accessible/qaccessiblewidgets.cpp
+++ b/src/widgets/accessible/qaccessiblewidgets.cpp
@@ -564,7 +564,7 @@ QWidget *QAccessibleCalendarWidget::navigationBar() const
// If there is a custom title bar widget, that one becomes child 1, after the content 0
// (in that case the buttons are ignored)
QAccessibleDockWidget::QAccessibleDockWidget(QWidget *widget)
- : QAccessibleWidgetV2(widget, QAccessible::Window)
+ : QAccessibleWidgetV2(widget)
{
}
@@ -636,6 +636,15 @@ QString QAccessibleDockWidget::text(QAccessible::Text t) const
}
return QString();
}
+
+QAccessible::Role QAccessibleDockWidget::role() const
+{
+ if (dockWidget()->isFloating())
+ return QAccessible::Window;
+
+ return QAccessible::Pane;
+}
+
#endif // QT_CONFIG(dockwidget)
#ifndef QT_NO_CURSOR
diff --git a/src/widgets/accessible/qaccessiblewidgets_p.h b/src/widgets/accessible/qaccessiblewidgets_p.h
index 6e3744bf993..c4bc6096c90 100644
--- a/src/widgets/accessible/qaccessiblewidgets_p.h
+++ b/src/widgets/accessible/qaccessiblewidgets_p.h
@@ -259,6 +259,7 @@ public:
int childCount() const override;
QRect rect () const override;
QString text(QAccessible::Text t) const override;
+ QAccessible::Role role() const override;
QDockWidget *dockWidget() const;
protected:
diff --git a/src/widgets/kernel/qtestsupport_widgets.cpp b/src/widgets/kernel/qtestsupport_widgets.cpp
index ce40ba8c6dd..e0118605308 100644
--- a/src/widgets/kernel/qtestsupport_widgets.cpp
+++ b/src/widgets/kernel/qtestsupport_widgets.cpp
@@ -79,7 +79,7 @@ bool QTest::qWaitForWindowActive(QWidget *widget, QDeadlineTimer timeout)
*/
bool QTest::qWaitForWindowActive(QWidget *widget)
{
- return qWaitForWindowActive(widget, Internal::defaultTryTimeout);
+ return qWaitForWindowActive(widget, defaultTryTimeout.load(std::memory_order_relaxed));
}
/*!
@@ -114,7 +114,7 @@ Q_WIDGETS_EXPORT bool QTest::qWaitForWindowFocused(QWidget *widget, QDeadlineTim
*/
bool QTest::qWaitForWindowFocused(QWidget *widget)
{
- return qWaitForWindowFocused(widget, Internal::defaultTryTimeout);
+ return qWaitForWindowFocused(widget, defaultTryTimeout.load(std::memory_order_relaxed));
}
/*!
@@ -158,7 +158,7 @@ bool QTest::qWaitForWindowExposed(QWidget *widget, QDeadlineTimer timeout)
*/
bool QTest::qWaitForWindowExposed(QWidget *widget)
{
- return qWaitForWindowExposed(widget, Internal::defaultTryTimeout);
+ return qWaitForWindowExposed(widget, defaultTryTimeout.load(std::memory_order_relaxed));
}
namespace QTest {
diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp
index aa4b4441859..bf0205d0081 100644
--- a/src/widgets/widgets/qdockwidget.cpp
+++ b/src/widgets/widgets/qdockwidget.cpp
@@ -5,6 +5,9 @@
#include <qaction.h>
#include <qapplication.h>
+#if QT_CONFIG(accessibility)
+#include <qaccessible.h>
+#endif
#include <qdrawutil.h>
#include <qevent.h>
#include <qfontmetrics.h>
@@ -1234,6 +1237,15 @@ void QDockWidgetPrivate::setWindowState(WindowStates states, const QRect &rect)
if (floating != wasFloating) {
emit q->topLevelChanged(floating);
+#if QT_CONFIG(accessibility)
+ if (QAccessible::isActive()) {
+ // Accessible role depends on whether QDockWidget is a top level or not,
+ // see QAccessibleDockWidget::role
+ QAccessibleEvent roleChangedEvent(q, QAccessible::RoleChanged);
+ QAccessible::updateAccessibility(&roleChangedEvent);
+ }
+#endif
+
if (!floating && parent) {
QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(q);
if (mwlayout)
diff --git a/tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp b/tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp
index e41a4760e6a..65714036e2d 100644
--- a/tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp
+++ b/tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp
@@ -3009,14 +3009,41 @@ void tst_QTextStream::int_write_with_locale_data()
QTest::addColumn<int>("numberFlags");
QTest::addColumn<int>("input");
QTest::addColumn<QString>("output");
+ QTest::addColumn<int>("fieldWidth");
+ QTest::addColumn<QTextStream::FieldAlignment>("fieldAlignment");
- QTest::newRow("C -123") << QString("C") << 0 << -123 << QString("-123");
- QTest::newRow("C +123") << QString("C") << (int)QTextStream::ForceSign << 123 << QString("+123");
- QTest::newRow("C 12345") << QString("C") << 0 << 12345 << QString("12345");
+ const auto alignDefault = QTextStream().fieldAlignment();
+ constexpr int forceSign = QTextStream::ForceSign;
- QTest::newRow("de_DE -123") << QString("de_DE") << 0 << -123 << QString("-123");
- QTest::newRow("de_DE +123") << QString("de_DE") << (int)QTextStream::ForceSign << 123 << QString("+123");
- QTest::newRow("de_DE 12345") << QString("de_DE") << 0 << 12345 << QString("12.345");
+ QTest::newRow("C -123") << u"C"_s << 0 << -123 << u"-123"_s << 0 << alignDefault;
+ QTest::newRow("C +123") << u"C"_s << forceSign << 123 << u"+123"_s << 0 << alignDefault;
+ QTest::newRow("C 12345") << u"C"_s << 0 << 12345 << u"12345"_s << 0 << alignDefault;
+
+ QTest::newRow("de_DE -123") << u"de_DE"_s << 0 << -123 << u"-123"_s << 0 << alignDefault;
+ QTest::newRow("de_DE +123") << u"de_DE"_s << forceSign << 123 << u"+123"_s << 0 << alignDefault;
+ QTest::newRow("de_DE 12345") << u"de_DE"_s << 0 << 12345 << u"12.345"_s << 0 << alignDefault;
+
+ constexpr auto alignAccountingStyle = QTextStream::FieldAlignment::AlignAccountingStyle;
+
+ {
+ const QLocale loc("ar_EG"_L1);
+ // Arabic as spoken in Egypt has a two-code-point negativeSign():
+ const auto minus = loc.negativeSign();
+ QCOMPARE(minus.size(), 2);
+ // ditto positiveSign():
+ const auto plus = loc.positiveSign();
+ QCOMPARE(plus.size(), 2);
+
+ QTest::addRow("ar_EG -123") << u"ar_EG"_s << 0 << -123
+ << (minus + u" ١٢٣")
+ << 10 << alignAccountingStyle;
+ QTest::newRow("ar_EG +123") << u"ar_EG"_s << forceSign << 123
+ << (plus + u" ١٢٣")
+ << 10 << alignAccountingStyle;
+ QTest::newRow("ar_EG 12345") << u"ar_EG"_s << 0 << 12345
+ << u" ١٢٬٣٤٥"_s
+ << 10 << alignAccountingStyle;
+ }
}
void tst_QTextStream::int_write_with_locale()
@@ -3025,12 +3052,18 @@ void tst_QTextStream::int_write_with_locale()
QFETCH(int, numberFlags);
QFETCH(int, input);
QFETCH(QString, output);
+ QFETCH(const int, fieldWidth);
+ QFETCH(const QTextStream::FieldAlignment, fieldAlignment);
QString result;
QTextStream stream(&result);
stream.setLocale(QLocale(locale));
+ stream.setFieldAlignment(fieldAlignment);
if (numberFlags)
stream.setNumberFlags(QTextStream::NumberFlags(numberFlags));
+ if (fieldWidth)
+ stream.setFieldWidth(fieldWidth);
+
QVERIFY(stream << input);
QCOMPARE(result, output);
}
diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp
index 2cd1398e7f9..c7f4c35f413 100644
--- a/tests/auto/corelib/text/qstring/tst_qstring.cpp
+++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp
@@ -22,8 +22,9 @@
#include <qstringmatcher.h>
#include <qbytearraymatcher.h>
#include <qvariant.h>
-
#include <qlocale.h>
+#include <QtCore/qxptype_traits.h>
+
#include <locale.h>
#include <qhash.h>
#include <private/qtools_p.h>
@@ -623,6 +624,7 @@ private slots:
void fromUcs4();
void toUcs4();
void arg();
+ void arg_negative_tests();
void number();
void number_double_data();
void number_double();
@@ -6981,6 +6983,26 @@ void tst_QString::arg()
u"\u0660\u0660\u0661\u0662\u0663\u066c\u0664\u0665\u0666\u066c\u0667\u0668\u0669"); // ٠٠١٢٣٬٤٥٦٬٧٨٩
}
+template <typename S, typename...Ts>
+using arg_compile_test = decltype(std::declval<S>().arg(std::declval<Ts>()...));
+template <typename S, typename...Ts>
+constexpr bool arg_compiles_v = qxp::is_detected_v<arg_compile_test, S, Ts...>;
+
+void tst_QString::arg_negative_tests()
+{
+ static_assert(!arg_compiles_v<QString&, QObject*>);
+ // QLatin1StringView::arg() is unconstrained...
+ // static_assert(!arg_compiles_v<QLatin1StringView&, QObject*>);
+
+ // integral type called like an FP overload:
+ static_assert(!arg_compiles_v<QString&, int, int, char, int, QChar>);
+ static_assert(!arg_compiles_v<QString&, long, int, char, int, char16_t>);
+
+ // strong enums don't match:
+ enum class Strong {};
+ static_assert(!arg_compiles_v<QString&, Strong>);
+}
+
void tst_QString::number()
{
QCOMPARE(QString::number(int(0)), QLatin1String("0"));
diff --git a/tests/auto/dbus/qdbuspendingreply/CMakeLists.txt b/tests/auto/dbus/qdbuspendingreply/CMakeLists.txt
index 52e11b3dbd9..f68df406d68 100644
--- a/tests/auto/dbus/qdbuspendingreply/CMakeLists.txt
+++ b/tests/auto/dbus/qdbuspendingreply/CMakeLists.txt
@@ -16,4 +16,5 @@ qt_internal_add_test(tst_qdbuspendingreply
tst_qdbuspendingreply.cpp
LIBRARIES
Qt::DBus
+ Qt::DBusPrivate
)
diff --git a/tests/auto/dbus/qdbuspendingreply/tst_qdbuspendingreply.cpp b/tests/auto/dbus/qdbuspendingreply/tst_qdbuspendingreply.cpp
index da25f768d05..361bff0143a 100644
--- a/tests/auto/dbus/qdbuspendingreply/tst_qdbuspendingreply.cpp
+++ b/tests/auto/dbus/qdbuspendingreply/tst_qdbuspendingreply.cpp
@@ -11,6 +11,8 @@
#include <QDBusAbstractAdaptor>
#include <QDBusPendingReply>
+#include <private/qdbuspendingcall_p.h>
+
typedef QMap<int,QString> IntStringMap;
Q_DECLARE_METATYPE(IntStringMap)
@@ -67,6 +69,14 @@ private slots:
void synchronousSimpleTypes();
void errors();
+
+ void getResultFromAnotherInstance_data();
+ void getResultFromAnotherInstance();
+
+ void moveSemantics();
+
+ void copyPreservesReplySignature();
+ void movePreservesReplySignature();
};
class TypesInterface: public QDBusAbstractAdaptor
@@ -561,6 +571,248 @@ void tst_QDBusPendingReply::errors()
QCOMPARE(dummystring, QString());
}
+void tst_QDBusPendingReply::getResultFromAnotherInstance_data()
+{
+ QTest::addColumn<bool>("shouldMove");
+
+ QTest::newRow("copy") << false;
+ QTest::newRow("move") << true;
+}
+
+void tst_QDBusPendingReply::getResultFromAnotherInstance()
+{
+ QFETCH(const bool, shouldMove);
+
+ // void
+ {
+ QDBusPendingReply<> r = iface->asyncCall("retrieveVoid");
+
+ QDBusPendingReply<> other;
+ if (shouldMove)
+ other = std::move(r);
+ else
+ other = r;
+ other.waitForFinished();
+
+ QVERIFY(other.isFinished());
+ QVERIFY(!other.isError());
+ QCOMPARE_EQ(other.count(), 0);
+ }
+
+ // multiple parameters
+ {
+ QDBusPendingReply<int, int> r = iface->asyncCall("retrieveIntInt");
+
+ QDBusPendingReply<int, int> other;
+ if (shouldMove)
+ other = std::move(r);
+ else
+ other = r;
+ other.waitForFinished();
+
+ QVERIFY(other.isFinished());
+ QVERIFY(!other.isError());
+ QCOMPARE(other.count(), 2);
+
+ int i1 = 0;
+ int i2 = 0;
+ adaptor->retrieveIntInt(i1, i2);
+
+ QCOMPARE_EQ(other.argumentAt<0>(), i1);
+ QCOMPARE_EQ(other.argumentAt<1>(), i2);
+ }
+
+ // complex types
+ {
+ QDBusPendingReply<IntStringMap> r = iface->asyncCall("retrieveIntStringMap");
+
+ QDBusPendingReply<IntStringMap> other;
+ if (shouldMove)
+ other = std::move(r);
+ else
+ other = r;
+ other.waitForFinished();
+
+ QVERIFY(other.isFinished());
+ QVERIFY(!other.isError());
+ QCOMPARE_EQ(other.count(), 1);
+ QCOMPARE_EQ(other.value(), adaptor->retrieveIntStringMap());
+ }
+}
+
+void tst_QDBusPendingReply::moveSemantics()
+{
+ // void
+ {
+ QDBusPendingReply<> r = iface->asyncCall("retrieveVoid");
+ r.waitForFinished();
+ QVERIFY(r.isFinished());
+
+ QDBusPendingReply<> copy = r;
+
+ QDBusPendingReply<> other = std::move(copy);
+ QCOMPARE_EQ(other.d, r.d);
+
+ copy = std::move(other);
+ QCOMPARE_EQ(copy.d, r.d);
+ }
+
+ // multiple parameters
+ {
+ QDBusPendingReply<int, int> r = iface->asyncCall("retrieveIntInt");
+ r.waitForFinished();
+ QVERIFY(r.isFinished());
+
+ QDBusPendingReply<int, int> copy = r;
+
+ QDBusPendingReply<int, int> other = std::move(copy);
+ QCOMPARE_EQ(other.d, r.d);
+
+ copy = std::move(other);
+ QCOMPARE_EQ(copy.d, r.d);
+ }
+
+ // complex types
+ {
+ QDBusPendingReply<IntStringMap> r = iface->asyncCall("retrieveIntStringMap");
+ r.waitForFinished();
+ QVERIFY(r.isFinished());
+
+ QDBusPendingReply<IntStringMap> copy = r;
+
+ QDBusPendingReply<IntStringMap> other = std::move(copy);
+ QCOMPARE_EQ(other.d, r.d);
+
+ copy = std::move(other);
+ QCOMPARE_EQ(copy.d, r.d);
+ }
+}
+
+void tst_QDBusPendingReply::copyPreservesReplySignature()
+{
+ // void
+ {
+ QDBusPendingCall c = iface->asyncCall("retrieveVoid");
+ c.waitForFinished();
+ QVERIFY(c.isFinished());
+
+ // QDBusPendingCall does not initialize reply signature
+ QVERIFY(c.d->expectedReplySignature.isNull());
+
+ // copy-construct
+ {
+ QDBusPendingReply<> r = c;
+ QVERIFY(!r.d->expectedReplySignature.isNull());
+ QVERIFY(r.d->expectedReplySignature.isEmpty());
+
+ QDBusPendingReply<> other = r;
+ QCOMPARE_EQ(other.d->expectedReplySignature, r.d->expectedReplySignature);
+ }
+
+ // copy-assign
+ {
+ QDBusPendingReply<> r;
+ r = c;
+ QVERIFY(!r.d->expectedReplySignature.isNull());
+ QVERIFY(r.d->expectedReplySignature.isEmpty());
+
+ QDBusPendingReply<> other;
+ other = r;
+ QCOMPARE_EQ(other.d->expectedReplySignature, r.d->expectedReplySignature);
+ }
+ }
+
+ // complex types
+ {
+ QDBusPendingCall c = iface->asyncCall("retrieveIntStringMap");
+ c.waitForFinished();
+ QVERIFY(c.isFinished());
+
+ // QDBusPendingCall does not initialize reply signature
+ QVERIFY(c.d->expectedReplySignature.isNull());
+
+ // copy-construct
+ {
+ QDBusPendingReply<IntStringMap> r = c;
+ QVERIFY(!r.d->expectedReplySignature.isEmpty());
+
+ QDBusPendingReply<IntStringMap> other = r;
+ QCOMPARE_EQ(other.d->expectedReplySignature, r.d->expectedReplySignature);
+ }
+
+ // copy-assign
+ {
+ QDBusPendingReply<IntStringMap> r;
+ r = c;
+ QVERIFY(!r.d->expectedReplySignature.isEmpty());
+
+ QDBusPendingReply<IntStringMap> other;
+ other = r;
+ QCOMPARE_EQ(other.d->expectedReplySignature, r.d->expectedReplySignature);
+ }
+ }
+}
+
+void tst_QDBusPendingReply::movePreservesReplySignature()
+{
+ // void
+ {
+ QDBusPendingCall c = iface->asyncCall("retrieveVoid");
+ c.waitForFinished();
+ QVERIFY(c.isFinished());
+
+ // QDBusPendingCall does not initialize reply signature
+ QVERIFY(c.d->expectedReplySignature.isNull());
+
+ QDBusPendingReply<> r = c;
+ QVERIFY(!r.d->expectedReplySignature.isNull());
+ QVERIFY(r.d->expectedReplySignature.isEmpty());
+
+ // move-construct
+ {
+ QDBusPendingReply<> copy = r;
+ QDBusPendingReply<> other = std::move(copy);
+ QCOMPARE_EQ(other.d->expectedReplySignature, r.d->expectedReplySignature);
+ }
+
+ // move-assign
+ {
+ QDBusPendingReply<> copy = r;
+ QDBusPendingReply<> other;
+ other = std::move(copy);
+ QCOMPARE_EQ(other.d->expectedReplySignature, r.d->expectedReplySignature);
+ }
+ }
+
+ // complex types
+ {
+ QDBusPendingCall c = iface->asyncCall("retrieveIntStringMap");
+ c.waitForFinished();
+ QVERIFY(c.isFinished());
+
+ // QDBusPendingCall does not initialize reply signature
+ QVERIFY(c.d->expectedReplySignature.isNull());
+
+ QDBusPendingReply<IntStringMap> r = c;
+ QVERIFY(!r.d->expectedReplySignature.isEmpty());
+
+ // move-construct
+ {
+ QDBusPendingReply<IntStringMap> copy = r;
+ QDBusPendingReply<IntStringMap> other = std::move(copy);
+ QCOMPARE_EQ(other.d->expectedReplySignature, r.d->expectedReplySignature);
+ }
+
+ // move-assign
+ {
+ QDBusPendingReply<IntStringMap> copy = r;
+ QDBusPendingReply<IntStringMap> other;
+ other = std::move(copy);
+ QCOMPARE_EQ(other.d->expectedReplySignature, r.d->expectedReplySignature);
+ }
+ }
+}
+
QTEST_MAIN(tst_QDBusPendingReply)
#include "tst_qdbuspendingreply.moc"
diff --git a/tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp b/tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp
index 571570e370f..9297958ba31 100644
--- a/tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp
+++ b/tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp
@@ -87,7 +87,8 @@ void tst_android_deployment_settings::DeploymentSettings_data()
<< "permissions"
<< "[{\"maxSdkVersion\":\"34\",\"minSdkVersion\":\"32\",\"name\":\"PERMISSION_WITH_"
"ATTRIBUTES\"},{\"name\":\"PERMISSION_WITHOUT_ATTRIBUTES\"},{\"name\":\"android."
- "permission.INTERNET\"},{\"name\":\"android.permission.WRITE_EXTERNAL_STORAGE\"}]";
+ "permission.WRITE_EXTERNAL_STORAGE\"},{\"name\":\"android."
+ "permission.INTERNET\"}]";
}
void tst_android_deployment_settings::DeploymentSettings()
diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
index 011852c28e4..95bcc9b20e2 100644
--- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
+++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
@@ -929,6 +929,27 @@ void tst_QAccessibility::actionTest()
QCOMPARE(click_count, 1);
}
QTestAccessibility::clearEvents();
+
+ {
+ QCOMPARE(QAccessibleActionInterface::showMenuAction(), QString(QStringLiteral("ShowMenu")));
+
+ auto widgetHolder = std::make_unique<QWidget>();
+ auto widget = widgetHolder.get();
+ widget->addAction(new QAction("Foo"));
+ widget->addAction(new QAction("Bar"));
+ widget->show();
+
+ QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(widget);
+ QVERIFY(interface);
+ QVERIFY(interface->isValid());
+ QAccessibleActionInterface *actions = interface->actionInterface();
+ QVERIFY(actions);
+
+ QCOMPARE(actions->actionNames(), QStringList());
+ widget->setContextMenuPolicy(Qt::ActionsContextMenu);
+ QCOMPARE(actions->actionNames(), QStringList(QAccessibleActionInterface::showMenuAction()));
+ }
+ QTestAccessibility::clearEvents();
}
void tst_QAccessibility::applicationTest()
@@ -3701,7 +3722,7 @@ void tst_QAccessibility::dockWidgetTest()
// 1 close button
// 2 float button
QVERIFY(accDock1);
- QCOMPARE(accDock1->role(), QAccessible::Window);
+ QCOMPARE(accDock1->role(), QAccessible::Pane);
QCOMPARE(accDock1->text(QAccessible::Name), dock1->windowTitle());
QCOMPARE(accDock1->childCount(), 3);
@@ -3731,7 +3752,7 @@ void tst_QAccessibility::dockWidgetTest()
QVERIFY(!dock1Float->state().invisible);
QVERIFY(accDock2);
- QCOMPARE(accDock2->role(), QAccessible::Window);
+ QCOMPARE(accDock2->role(), QAccessible::Pane);
QCOMPARE(accDock2->text(QAccessible::Name), dock2->windowTitle());
QCOMPARE(accDock2->childCount(), 3);
@@ -3779,7 +3800,7 @@ void tst_QAccessibility::dockWidgetTest()
QAccessibleInterface *accDock3 = accMainWindow->child(4);
QVERIFY(accDock3);
- QCOMPARE(accDock3->role(), QAccessible::Window);
+ QCOMPARE(accDock3->role(), QAccessible::Pane);
QCOMPARE(accDock3->text(QAccessible::Name), dock3->windowTitle());
QCOMPARE(accDock3->childCount(), 2);
QAccessibleInterface *titleWidget = accDock3->child(1);
@@ -3788,6 +3809,14 @@ void tst_QAccessibility::dockWidgetTest()
QAccessibleInterface *dock3Widget = accDock3->child(0);
QCOMPARE(dock3Widget->text(QAccessible::Name), pb3->text());
+ // check role is changed to QAccessible::Window when dock window is undocked
+ // and a corresponding event is sent
+ QTestAccessibility::clearEvents();
+ dock3->setFloating(true);
+ QCOMPARE(accDock3->role(), QAccessible::Window);
+ QAccessibleEvent roleChangedEvent(dock3, QAccessible::RoleChanged);
+ QVERIFY(QTestAccessibility::containsEvent(&roleChangedEvent));
+
QTestAccessibility::clearEvents();
#endif // QT_CONFIG(dockwidget)
}
diff --git a/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp b/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp
index e10dd6da885..8787cc54bf6 100644
--- a/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp
+++ b/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
+#include <QtCore/qatomicscopedvaluerollback.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QTimer>
#ifdef QT_GUI_LIB
@@ -15,6 +16,8 @@
#endif
#include <QSet>
#include <vector>
+
+using namespace std::chrono_literals;
using namespace Qt::StringLiterals;
/* XPM test data for QPixmap, QImage tests (use drag cursors as example) */
@@ -105,6 +108,9 @@ public:
enum class MyClassEnum { MyClassEnumValue1, MyClassEnumValue2 };
Q_ENUM(MyClassEnum)
+private:
+ void defaultTryTimeoutData();
+
private slots:
void compare_unregistered_enums();
void compare_registered_enums();
@@ -152,6 +158,10 @@ private slots:
void tryVerify();
void tryVerify2();
void verifyExplicitOperatorBool();
+ void defaultTryVerifyTimeout_data();
+ void defaultTryVerifyTimeout();
+ void defaultTryCompareTimeout_data();
+ void defaultTryCompareTimeout();
};
enum MyUnregisteredEnum { MyUnregisteredEnumValue1, MyUnregisteredEnumValue2 };
@@ -841,5 +851,56 @@ void tst_Cmptest::verifyExplicitOperatorBool()
QVERIFY(!val2);
}
+void tst_Cmptest::defaultTryTimeoutData()
+{
+ QTest::addColumn<std::chrono::milliseconds>("timeout");
+ QTest::addRow("times-out") << 1ms;
+ QTest::addRow("ample-time") << 1000ms;
+}
+
+void tst_Cmptest::defaultTryVerifyTimeout_data()
+{
+ defaultTryTimeoutData();
+}
+
+void tst_Cmptest::defaultTryVerifyTimeout()
+{
+ QFETCH(const std::chrono::milliseconds, timeout);
+
+ // Check that the default is what expect.
+ QCOMPARE(QTest::defaultTryTimeout.load(std::memory_order_relaxed), 5s);
+
+ {
+ DeferredFlag trueEventually;
+ const auto innerScope = QAtomicScopedValueRollback(QTest::defaultTryTimeout, timeout, std::memory_order_relaxed);
+ QEXPECT_FAIL("times-out", "The timeout (std::chrono::milliseconds) is deliberately too short", Continue);
+ QTRY_VERIFY(trueEventually);
+ }
+
+ // innerScope has now been destroyed, so the timeout should be back to its default.
+ QCOMPARE(QTest::defaultTryTimeout.load(std::memory_order_relaxed), 5s);
+}
+
+void tst_Cmptest::defaultTryCompareTimeout_data()
+{
+ defaultTryTimeoutData();
+}
+
+void tst_Cmptest::defaultTryCompareTimeout()
+{
+ QFETCH(const std::chrono::milliseconds, timeout);
+
+ DeferredFlag trueAlready(true);
+ {
+ DeferredFlag trueEventually;
+ const auto innerScope = QAtomicScopedValueRollback(QTest::defaultTryTimeout, timeout, std::memory_order_relaxed);
+ QEXPECT_FAIL("times-out", "The timeout (std::chrono::milliseconds) is deliberately too short", Continue);
+ QTRY_COMPARE(trueEventually, trueAlready);
+ }
+
+ // innerScope has now been destroyed, so the timeout should be back to its default.
+ QCOMPARE(QTest::defaultTryTimeout.load(std::memory_order_relaxed), 5s);
+}
+
QTEST_MAIN(tst_Cmptest)
#include "tst_cmptest.moc"
diff --git a/tests/auto/testlib/selftests/expected_cmptest.junitxml b/tests/auto/testlib/selftests/expected_cmptest.junitxml
index ce6a1c0c76e..232b86117af 100644
--- a/tests/auto/testlib/selftests/expected_cmptest.junitxml
+++ b/tests/auto/testlib/selftests/expected_cmptest.junitxml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<testsuite name="tst_Cmptest" timestamp="@TEST_START_TIME@" hostname="@HOSTNAME@" tests="74" failures="51" errors="0" skipped="0" time="@TEST_DURATION@">
+<testsuite name="tst_Cmptest" timestamp="@TEST_START_TIME@" hostname="@HOSTNAME@" tests="78" failures="51" errors="0" skipped="0" time="@TEST_DURATION@">
<properties>
<property name="QTestVersion" value="@INSERT_QT_VERSION_HERE@"/>
<property name="QtVersion" value="@INSERT_QT_VERSION_HERE@"/>
@@ -321,5 +321,17 @@
<failure type="fail" message="&apos;!c&apos; returned FALSE. (Should time out and fail)"/>
</testcase>
<testcase name="verifyExplicitOperatorBool" classname="tst_Cmptest" time="@TEST_DURATION@"/>
+ <testcase name="defaultTryVerifyTimeout(times&#x002D;out)" classname="tst_Cmptest" time="@TEST_DURATION@">
+ <system-out>
+ <![CDATA[The timeout (std::chrono::milliseconds) is deliberately too short]]>
+ </system-out>
+ </testcase>
+ <testcase name="defaultTryVerifyTimeout(ample&#x002D;time)" classname="tst_Cmptest" time="@TEST_DURATION@"/>
+ <testcase name="defaultTryCompareTimeout(times&#x002D;out)" classname="tst_Cmptest" time="@TEST_DURATION@">
+ <system-out>
+ <![CDATA[The timeout (std::chrono::milliseconds) is deliberately too short]]>
+ </system-out>
+ </testcase>
+ <testcase name="defaultTryCompareTimeout(ample&#x002D;time)" classname="tst_Cmptest" time="@TEST_DURATION@"/>
<testcase name="cleanupTestCase" classname="tst_Cmptest" time="@TEST_DURATION@"/>
</testsuite>
diff --git a/tests/auto/testlib/selftests/expected_cmptest.lightxml b/tests/auto/testlib/selftests/expected_cmptest.lightxml
index 9919dde3241..b31bc2aef7d 100644
--- a/tests/auto/testlib/selftests/expected_cmptest.lightxml
+++ b/tests/auto/testlib/selftests/expected_cmptest.lightxml
@@ -486,6 +486,32 @@
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
</TestFunction>
+ <TestFunction name="defaultTryVerifyTimeout">
+ <Incident type="xfail" file="qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp" line="0">
+ <DataTag><![CDATA[times-out]]></DataTag>
+ <Description><![CDATA[The timeout (std::chrono::milliseconds) is deliberately too short]]></Description>
+ </Incident>
+ <Incident type="pass" file="" line="0">
+ <DataTag><![CDATA[times-out]]></DataTag>
+ </Incident>
+ <Incident type="pass" file="" line="0">
+ <DataTag><![CDATA[ample-time]]></DataTag>
+ </Incident>
+ <Duration msecs="0"/>
+ </TestFunction>
+ <TestFunction name="defaultTryCompareTimeout">
+ <Incident type="xfail" file="qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp" line="0">
+ <DataTag><![CDATA[times-out]]></DataTag>
+ <Description><![CDATA[The timeout (std::chrono::milliseconds) is deliberately too short]]></Description>
+ </Incident>
+ <Incident type="pass" file="" line="0">
+ <DataTag><![CDATA[times-out]]></DataTag>
+ </Incident>
+ <Incident type="pass" file="" line="0">
+ <DataTag><![CDATA[ample-time]]></DataTag>
+ </Incident>
+ <Duration msecs="0"/>
+ </TestFunction>
<TestFunction name="cleanupTestCase">
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
diff --git a/tests/auto/testlib/selftests/expected_cmptest.tap b/tests/auto/testlib/selftests/expected_cmptest.tap
index 77118713717..180567f2112 100644
--- a/tests/auto/testlib/selftests/expected_cmptest.tap
+++ b/tests/auto/testlib/selftests/expected_cmptest.tap
@@ -719,8 +719,30 @@ not ok 72 - tryVerify2()
line: 0
...
ok 73 - verifyExplicitOperatorBool()
-ok 74 - cleanupTestCase()
-1..74
-# tests 74
-# pass 23
+not ok 74 - defaultTryVerifyTimeout(times-out) # TODO The timeout (std::chrono::milliseconds) is deliberately too short
+ ---
+ extensions:
+ messages:
+ - severity: xfail
+ message: The timeout (std::chrono::milliseconds) is deliberately too short
+ at: tst_Cmptest::defaultTryVerifyTimeout() (qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp:0)
+ file: qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp
+ line: 0
+ ...
+ok 75 - defaultTryVerifyTimeout(ample-time)
+not ok 76 - defaultTryCompareTimeout(times-out) # TODO The timeout (std::chrono::milliseconds) is deliberately too short
+ ---
+ extensions:
+ messages:
+ - severity: xfail
+ message: The timeout (std::chrono::milliseconds) is deliberately too short
+ at: tst_Cmptest::defaultTryCompareTimeout() (qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp:0)
+ file: qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp
+ line: 0
+ ...
+ok 77 - defaultTryCompareTimeout(ample-time)
+ok 78 - cleanupTestCase()
+1..78
+# tests 78
+# pass 27
# fail 51
diff --git a/tests/auto/testlib/selftests/expected_cmptest.teamcity b/tests/auto/testlib/selftests/expected_cmptest.teamcity
index c48262e0376..f88e44f3ee2 100644
--- a/tests/auto/testlib/selftests/expected_cmptest.teamcity
+++ b/tests/auto/testlib/selftests/expected_cmptest.teamcity
@@ -216,6 +216,16 @@
##teamcity[testFinished name='tryVerify2()' flowId='tst_Cmptest']
##teamcity[testStarted name='verifyExplicitOperatorBool()' flowId='tst_Cmptest']
##teamcity[testFinished name='verifyExplicitOperatorBool()' flowId='tst_Cmptest']
+##teamcity[testStarted name='defaultTryVerifyTimeout(times-out)' flowId='tst_Cmptest']
+##teamcity[testStdOut name='defaultTryVerifyTimeout(times-out)' out='XFAIL |[Loc: qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp(0)|]: The timeout (std::chrono::milliseconds) is deliberately too short' flowId='tst_Cmptest']
+##teamcity[testFinished name='defaultTryVerifyTimeout(times-out)' flowId='tst_Cmptest']
+##teamcity[testStarted name='defaultTryVerifyTimeout(ample-time)' flowId='tst_Cmptest']
+##teamcity[testFinished name='defaultTryVerifyTimeout(ample-time)' flowId='tst_Cmptest']
+##teamcity[testStarted name='defaultTryCompareTimeout(times-out)' flowId='tst_Cmptest']
+##teamcity[testStdOut name='defaultTryCompareTimeout(times-out)' out='XFAIL |[Loc: qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp(0)|]: The timeout (std::chrono::milliseconds) is deliberately too short' flowId='tst_Cmptest']
+##teamcity[testFinished name='defaultTryCompareTimeout(times-out)' flowId='tst_Cmptest']
+##teamcity[testStarted name='defaultTryCompareTimeout(ample-time)' flowId='tst_Cmptest']
+##teamcity[testFinished name='defaultTryCompareTimeout(ample-time)' flowId='tst_Cmptest']
##teamcity[testStarted name='cleanupTestCase()' flowId='tst_Cmptest']
##teamcity[testFinished name='cleanupTestCase()' flowId='tst_Cmptest']
##teamcity[testSuiteFinished name='tst_Cmptest' flowId='tst_Cmptest']
diff --git a/tests/auto/testlib/selftests/expected_cmptest.txt b/tests/auto/testlib/selftests/expected_cmptest.txt
index efb305654f4..4c4b340a5ca 100644
--- a/tests/auto/testlib/selftests/expected_cmptest.txt
+++ b/tests/auto/testlib/selftests/expected_cmptest.txt
@@ -252,6 +252,14 @@ FAIL! : tst_Cmptest::tryVerify() '!c' returned FALSE. ()
FAIL! : tst_Cmptest::tryVerify2() '!c' returned FALSE. (Should time out and fail)
Loc: [qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp(0)]
PASS : tst_Cmptest::verifyExplicitOperatorBool()
+XFAIL : tst_Cmptest::defaultTryVerifyTimeout(times-out) The timeout (std::chrono::milliseconds) is deliberately too short
+ Loc: [qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp(0)]
+PASS : tst_Cmptest::defaultTryVerifyTimeout(times-out)
+PASS : tst_Cmptest::defaultTryVerifyTimeout(ample-time)
+XFAIL : tst_Cmptest::defaultTryCompareTimeout(times-out) The timeout (std::chrono::milliseconds) is deliberately too short
+ Loc: [qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp(0)]
+PASS : tst_Cmptest::defaultTryCompareTimeout(times-out)
+PASS : tst_Cmptest::defaultTryCompareTimeout(ample-time)
PASS : tst_Cmptest::cleanupTestCase()
-Totals: 23 passed, 51 failed, 0 skipped, 0 blacklisted, 0ms
+Totals: 27 passed, 51 failed, 0 skipped, 0 blacklisted, 0ms
********* Finished testing of tst_Cmptest *********
diff --git a/tests/auto/testlib/selftests/expected_cmptest.xml b/tests/auto/testlib/selftests/expected_cmptest.xml
index f6ed03b0e9f..5ec93c5ce7e 100644
--- a/tests/auto/testlib/selftests/expected_cmptest.xml
+++ b/tests/auto/testlib/selftests/expected_cmptest.xml
@@ -488,6 +488,32 @@
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
</TestFunction>
+ <TestFunction name="defaultTryVerifyTimeout">
+ <Incident type="xfail" file="qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp" line="0">
+ <DataTag><![CDATA[times-out]]></DataTag>
+ <Description><![CDATA[The timeout (std::chrono::milliseconds) is deliberately too short]]></Description>
+ </Incident>
+ <Incident type="pass" file="" line="0">
+ <DataTag><![CDATA[times-out]]></DataTag>
+ </Incident>
+ <Incident type="pass" file="" line="0">
+ <DataTag><![CDATA[ample-time]]></DataTag>
+ </Incident>
+ <Duration msecs="0"/>
+ </TestFunction>
+ <TestFunction name="defaultTryCompareTimeout">
+ <Incident type="xfail" file="qtbase/tests/auto/testlib/selftests/cmptest/tst_cmptest.cpp" line="0">
+ <DataTag><![CDATA[times-out]]></DataTag>
+ <Description><![CDATA[The timeout (std::chrono::milliseconds) is deliberately too short]]></Description>
+ </Incident>
+ <Incident type="pass" file="" line="0">
+ <DataTag><![CDATA[times-out]]></DataTag>
+ </Incident>
+ <Incident type="pass" file="" line="0">
+ <DataTag><![CDATA[ample-time]]></DataTag>
+ </Incident>
+ <Duration msecs="0"/>
+ </TestFunction>
<TestFunction name="cleanupTestCase">
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
diff --git a/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp b/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
index 59f1f00cc59..1fd6e0a0c4f 100644
--- a/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
+++ b/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
@@ -7,7 +7,7 @@
using namespace emscripten;
-const int timerTimeout = 10;
+const std::chrono::milliseconds timerTimeout{10};
// Test QWasmSuspendResumeControl suspend/resume and event processing,
// via QWasmTimer native timer events.
diff --git a/util/wasm/batchedtestrunner/emrunadapter.js b/util/wasm/batchedtestrunner/emrunadapter.js
index 5b4284e18ff..73f575f9616 100644
--- a/util/wasm/batchedtestrunner/emrunadapter.js
+++ b/util/wasm/batchedtestrunner/emrunadapter.js
@@ -37,6 +37,7 @@ export class EmrunCommunication {
// method increments the output index by 1.
postOutput(output)
{
+ output += "\n";
if (this.#nextOutputBatch) {
this.#nextOutputBatch += output;
} else {
@@ -45,9 +46,9 @@ export class EmrunCommunication {
{
window.setTimeout(() =>
{
- const toSend = this.#nextOutputBatch;
+ const toSend = this.#nextOutputBatch.replace(/\n$/, '');
this.#nextOutputBatch = null;
- this.#post(`^out^${this.#indexOfMessage++}^${toSend}$`)
+ this.#post(`^out^${this.#indexOfMessage++}^${toSend}`)
.finally(resolve);
}, EmrunCommunication.#BATCHING_DELAY);
});
diff --git a/util/wasm/preload/generate_default_preloads.sh.in b/util/wasm/preload/generate_default_preloads.sh.in
deleted file mode 100644
index b38d308fa45..00000000000
--- a/util/wasm/preload/generate_default_preloads.sh.in
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-TARGET_DIR="@TARGET_DIR@"
-SOURCE_DIR="@SOURCE_DIR@"
-QT_HOST_DIR="@QT_HOST_DIR@"
-QT_WASM_DIR="@QT_WASM_DIR@"
-QT_INSTALL_DIR="@QT_INSTALL_DIR@"
-
-python3 \
- "$QT_WASM_DIR/libexec/preload_qt_plugins.py" \
- "$QT_INSTALL_DIR" \
- "$TARGET_DIR"
-
-python3 \
- "$QT_WASM_DIR/libexec/preload_qml_imports.py" \
- "$SOURCE_DIR" \
- "$QT_HOST_DIR" \
- "$QT_INSTALL_DIR" \
- "$TARGET_DIR"
diff --git a/util/wasm/preload/preload_qml_imports.py b/util/wasm/preload/preload_qml_imports.py
deleted file mode 100755
index b78ef5ee744..00000000000
--- a/util/wasm/preload/preload_qml_imports.py
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (C) 2023 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import os
-import sys
-import subprocess
-import json
-
-# Paths to shared libraries and qml imports on the Qt installation on the web server.
-# "$QTDIR" is replaced by qtloader.js at load time (defaults to "qt"), and makes
-# possible to relocate the application build relative to the Qt build on the web server.
-qt_lib_path = "$QTDIR/lib"
-qt_qml_path = "$QTDIR/qml"
-
-# Path to QML imports on the in-memory file system provided by Emscripten. This script emits
-# preload commands which copies QML imports to this directory. In addition, preload_qt_plugins.py
-# creates (and preloads) a qt.conf file which makes Qt load QML plugins from this location.
-qt_deploy_qml_path = "/qt/qml"
-
-
-def preload_file(source, destination):
- preload_files.append({"source": source, "destination": destination})
-
-
-def extract_preload_files_from_imports(imports):
- libraries = []
- for qml_import in imports:
- try:
- relative_path = qml_import["relativePath"]
- plugin = qml_import["plugin"]
-
- # plugin .so
- plugin_filename = "lib" + plugin + ".so"
- so_plugin_source_path = os.path.join(
- qt_qml_path, relative_path, plugin_filename
- )
- so_plugin_destination_path = os.path.join(
- qt_deploy_qml_path, relative_path, plugin_filename
- )
-
- preload_file(so_plugin_source_path, so_plugin_destination_path)
- so_plugin_qt_install_path = os.path.join(
- qt_wasm_path, "qml", relative_path, plugin_filename
- )
-
- # qmldir file
- qmldir_source_path = os.path.join(qt_qml_path, relative_path, "qmldir")
- qmldir_destination_path = os.path.join(
- qt_deploy_qml_path, relative_path, "qmldir"
- )
- preload_file(qmldir_source_path, qmldir_destination_path)
- except Exception as e:
- continue
- return libraries
-
-
-if __name__ == "__main__":
- if len(sys.argv) != 5:
- print("Usage: python preload_qml_imports.py <qml-source-path> <qt-host-path> <qt-wasm-path> <output-dir>")
- sys.exit(1)
-
- qml_source_path = sys.argv[1]
- qt_host_path = sys.argv[2]
- qt_wasm_path = sys.argv[3]
- output_dir = sys.argv[4]
-
- qml_import_path = os.path.join(qt_wasm_path, "qml")
- qmlimportsscanner_path = os.path.join(qt_host_path, "libexec/qmlimportscanner")
-
- command = [qmlimportsscanner_path, "-rootPath", qml_source_path, "-importPath", qml_import_path]
- result = subprocess.run(command, stdout=subprocess.PIPE)
- imports = json.loads(result.stdout)
-
- preload_files = []
- libraries = extract_preload_files_from_imports(imports)
-
- # Deploy plugin dependencies, that is, shared libraries used by the plugins.
- # Skip some of the obvious libraries which will be
- skip_libraries = [
- "libQt6Core.so",
- "libQt6Gui.so",
- "libQt6Quick.so",
- "libQt6Qml.so" "libQt6Network.so",
- "libQt6OpenGL.so",
- ]
-
- libraries = set(libraries) - set(skip_libraries)
- for library in libraries:
- source = os.path.join(qt_lib_path, library)
- # Emscripten looks for shared libraries on "/", shared libraries
- # most be deployed there instead of at /qt/lib
- destination = os.path.join("/", library)
- preload_file(source, destination)
-
- with open(f"{output_dir}/qt_qml_imports.json", "w") as f:
- f.write(json.dumps(preload_files, indent=2))
-
diff --git a/util/wasm/preload/preload_qt_plugins.py b/util/wasm/preload/preload_qt_plugins.py
deleted file mode 100755
index 4b9b3683a70..00000000000
--- a/util/wasm/preload/preload_qt_plugins.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (C) 2023 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import os
-import sys
-import json
-
-# Path to plugins on the Qt installation on the web server. "$QTPATH" is replaced by qtloader.js
-# at load time (defaults to "qt"), which makes it possible to relocate the application build relative
-# to the Qt build on the web server.
-qt_plugins_path = "$QTDIR/plugins"
-
-# Path to plugins on the in-memory file system provided by Emscripten. This script emits
-# preload commands which copies plugins to this directory.
-qt_deploy_plugins_path = "/qt/plugins"
-
-
-def find_so_files(directory):
- so_files = []
- for root, dirs, files in os.walk(directory):
- for file in files:
- if file.endswith(".so"):
- relative_path = os.path.relpath(os.path.join(root, file), directory)
- so_files.append(relative_path)
- return so_files
-
-
-if __name__ == "__main__":
- if len(sys.argv) != 3:
- print("Usage: python preload_qt_plugins.py <qt-wasm-path> <output-dir>")
- sys.exit(1)
-
- qt_wasm_path = sys.argv[1]
- output_dir = sys.argv[2]
-
- # preload all plugins
- plugins = find_so_files(os.path.join(qt_wasm_path, "plugins"))
- preload = [
- {
- "source": os.path.join(qt_plugins_path, plugin),
- "destination": os.path.join(qt_deploy_plugins_path, plugin),
- }
- for plugin in plugins
- ]
-
- # Create and preload qt.conf which will tell Qt to look for plugins
- # and QML imports in /qt/plugins and /qt/qml. The qt.conf file is
- # written to the current directory.
- qtconf = "[Paths]\nPrefix = /qt\n"
- with open(f"{output_dir}/qt.conf", "w") as f:
- f.write(qtconf)
- preload.append({"source": "qt.conf", "destination": "/qt.conf"})
-
- with open(f"{output_dir}/qt_plugins.json", "w") as f:
- f.write(json.dumps(preload, indent=2))
-