Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qohosnativedrageventshandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtCore/private/qohoscommon_p.h>
5#include <QtCore/private/qohoslogger_p.h>
6#include <QtCore/qmimedata.h>
7#include <QtGui/private/qdnd_p.h>
8#include <QtGui/private/qguiapplication_p.h>
9#include <QtGui/private/qhighdpiscaling_p.h>
10#include <algorithm>
11#include <arkui/ui_input_event.h>
12#include <array>
13#include <chrono>
14#include <cstdint>
15#include <database/udmf/udmf.h>
16#include <deque>
17#include <functional>
18#include <future>
19#include <info/application_target_sdk_version.h>
20#include <memory>
21#include <qarkui/qarkuiutils.h>
22#include <qohosjsutils.h>
23#include <qohosplatformdrag.h>
24#include <qohosudmf.h>
25#include <qohosudmfconversions.h>
26#include <qohosutils.h>
27#include <qpa/qplatformdrag.h>
28#include <qpa/qplatformintegration.h>
29#include <qpa/qwindowsysteminterface.h>
30#include <render/qohosbatchingrequestshandler.h>
31#include <render/qohosdrageventutils.h>
32#include <render/qohosnativedrageventshandler.h>
33#include <string>
34#include <type_traits>
35#include <utility>
36#include <vector>
37
38namespace ch = std::chrono;
39
40QT_BEGIN_NAMESPACE
41
42namespace {
43
44// The following DragResult constants are listed in the JS documentation,
45// but they are not included as ArkUI_DragResult enumerators (despite the
46// fact that they actually work according to the JS documentation):
47// - DROP_ENABLED = 3
48// - DROP_DISABLED = 4
49constexpr auto Q_DROP_ENABLED = static_cast<ArkUI_DragResult>(3);
50constexpr auto Q_DROP_DISABLED = static_cast<ArkUI_DragResult>(4);
51
58
59template<typename T>
60QOhosSupplier<T> makeImplicitlySharedSupplier(QOhosSupplier<T> baseSupplier)
61{
62 auto sharedBaseSupplier = QtOhos::moveToSharedPtr(std::move(baseSupplier));
63 return [sharedBaseSupplier]() {
64 return (*sharedBaseSupplier)();
65 };
66}
67
68std::int32_t getDragEventDataTypeCount(::ArkUI_DragEvent *dragEvent)
69{
70 std::int32_t dataTypeCount = 0;
71 QArkUi::callArkUiOrFailOnErrorResult(
72 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_GetDataTypeCount),
73 dragEvent, &dataTypeCount);
74
75 return dataTypeCount;
76}
77
78std::vector<std::string> getDragEventDataTypes(::ArkUI_DragEvent *dragEvent)
79{
80 constexpr auto dataTypeMaxLength = 128;
81
82 auto dataTypeCount = getDragEventDataTypeCount(dragEvent);
83 if (dataTypeCount == 0)
84 return {};
85
86 std::vector<std::array<char, dataTypeMaxLength + 1>> dataTypesStringData;
87 dataTypesStringData.resize(dataTypeCount);
88
89 std::vector<char *> dataTypesStringPointers;
90 for (std::int32_t i = 0; i < dataTypeCount; ++i)
91 dataTypesStringPointers.push_back(dataTypesStringData[i].data());
92
93 QArkUi::callArkUiOrFailOnErrorResult(
94 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_GetDataTypes),
95 dragEvent, dataTypesStringPointers.data(), dataTypesStringPointers.size(), dataTypeMaxLength);
96
97 return {dataTypesStringPointers.begin(), dataTypesStringPointers.end()};
98}
99
100std::shared_ptr<QOhosUdmfData> tryGetDragEventUdmfDataOrNull(::ArkUI_DragEvent *dragEvent)
101{
102 QOhosUdmfData udmfData;
103 auto getUdmfDataRes = QArkUi::callArkUi(
104 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_GetUdmfData),
105 dragEvent, udmfData.nativePtr());
106
107 return getUdmfDataRes == ARKUI_ERROR_CODE_NO_ERROR
108 ? QtOhos::moveToSharedPtr(std::move(udmfData))
109 : nullptr;
110}
111
113 ::ArkUI_DragEvent *dragEvent, QOhosOptional<::ArkUI_DropOperation> optDropOperation)
114{
115 if (optDropOperation.hasValue()) {
116 QArkUi::callArkUiOrFailOnErrorResult(
117 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_SetSuggestedDropOperation),
118 dragEvent, optDropOperation.value());
119 }
120}
121
122std::uint64_t getDragEventModifierKeyStates(::ArkUI_DragEvent *dragEvent)
123{
124 std::uint64_t keys;
125 QArkUi::callArkUiOrFailOnErrorResult(
126 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_GetModifierKeyStates),
127 dragEvent, &keys);
128 return keys;
129}
130
132{
133 static const std::pair<std::uint64_t, Qt::KeyboardModifier> arkUiToQtModifiersMap[] = {
134 {::ARKUI_MODIFIER_KEY_CTRL, Qt::KeyboardModifier::ControlModifier},
135 {::ARKUI_MODIFIER_KEY_SHIFT, Qt::KeyboardModifier::ShiftModifier},
136 {::ARKUI_MODIFIER_KEY_ALT, Qt::KeyboardModifier::AltModifier},
137 {::ARKUI_MODIFIER_KEY_FN, Qt::KeyboardModifier::MetaModifier},
138 };
139
140 Qt::KeyboardModifiers modifiers;
141 for (const auto &mod : arkUiToQtModifiersMap)
142 modifiers.setFlag(mod.second, (modifierKeyStates & mod.first) != 0);
143
144 return modifiers;
145}
146
147template<typename Context, typename Result>
149 QtOhos::QThreadSafeRef<Context> context, std::function<Result(Context &)> qtThreadProcessFunc)
150{
151 constexpr auto maxResultWaitTime = ch::milliseconds(50);
152
153 auto resultPromise = std::make_shared<std::promise<Result>>();
154 auto resultFuture = resultPromise->get_future();
155
156 context.visitInQtThreadIfAlive(
157 [qtThreadProcessFunc = std::move(qtThreadProcessFunc), resultPromise](Context &context) {
158 resultPromise->set_value(qtThreadProcessFunc(context));
159 });
160
161 return
162 resultFuture.wait_for(maxResultWaitTime) == std::future_status::ready
163 ? makeQOhosOptional(resultFuture.get())
164 : makeEmptyQOhosOptional();
165}
166
167template<typename Context, typename Result>
170 QtOhos::QThreadSafeRef<Context> contextRef,
171 QOhosSupplier<ch::nanoseconds> timeoutsSupplier)
172{
173 auto batchUpdater = makeQtOhosBatchingMTRequestsHandler<std::function<void(Context &)>>(
174 [contextRef](std::function<void()> task) {
175 contextRef.visitInQtThreadIfAlive([task = std::move(task)](Context &) {
176 task();
177 });
178 },
179 [contextRef](std::function<void(Context &)> &&request) {
180 request(*contextRef.data());
181 });
182
183 struct ExecutorContext {
184 decltype(batchUpdater) batchUpdater;
185 QOhosSupplier<ch::nanoseconds> timeoutsSupplier;
186 };
187
188 auto executorContext = QtOhos::moveToSharedPtr(
189 ExecutorContext{
190 .batchUpdater = std::move(batchUpdater),
191 .timeoutsSupplier = std::move(timeoutsSupplier),
192 });
193
194 return [executorContext](std::function<Result(Context &)> qtThreadProcessFunc) {
195 const auto maxResultWaitTime = executorContext->timeoutsSupplier();
196
197 auto promise = std::make_shared<std::promise<Result>>();
198 auto future = promise->get_future();
199
200 executorContext->batchUpdater(
201 [&](std::function<void(Context &)> &request) {
202 request = [qtThreadProcessFunc = std::move(qtThreadProcessFunc), promise](Context &context) {
203 promise->set_value(qtThreadProcessFunc(context));
204 };
205 });
206
207 return future.wait_for(maxResultWaitTime) == std::future_status::ready
208 ? makeQOhosOptional(future.get())
209 : makeEmptyQOhosOptional();
210 };
211}
212
213QOhosPlatformDrag *getQOhosPlatformDrag()
214{
215 return static_cast<QOhosPlatformDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
216}
217
219 QWindow &qWindow, const DragEventInfo &dragEventInfo,
220 QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory)
221{
222 QDrag *currentDrag = QDragManager::self()->object();
223 if (currentDrag != nullptr)
224 getQOhosPlatformDrag()->handlePreDrop();
225 QPlatformDropQtResponse qtResponse = QWindowSystemInterface::handleDrop(
226 &qWindow,
227 currentDrag != nullptr ? currentDrag->mimeData() : dropDataFactory().get(),
228 QHighDpi::toNativeLocalPosition(
229 qWindow.mapFromGlobal(QHighDpi::fromNativePixels(dragEventInfo.globalDropPos, &qWindow)), &qWindow),
230 currentDrag != nullptr ? currentDrag->supportedActions() : dragEventInfo.dropActions,
231 Qt::LeftButton, dragEventInfo.keyboardModifiers);
232 auto updatedDropAction =
233 qtResponse.isAccepted()
234 ? qtResponse.acceptedAction()
235 : Qt::IgnoreAction;
236 if (currentDrag != nullptr)
237 getQOhosPlatformDrag()->updateDropAction(updatedDropAction);
238 return updatedDropAction;
239}
240
242 QtOhos::JsState &jsState, QtOhos::QThreadSafeRef<QWindow> qWindowRef, const DragEventInfo &dragEventInfo,
243 QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory, std::int32_t pendingDropRequestId)
244{
245 qOhosPrintfDebug("%s: async processing of drop request with id=%d", Q_FUNC_INFO, pendingDropRequestId);
246
247 auto qtDropActionConsumer = moveToSharedPtr(
248 QtOhos::makeCallOnceConsumerWrapper<QtOhos::JsState &, Qt::DropAction>(
249 [pendingDropRequestId](QtOhos::JsState &, Qt::DropAction qtDropAction) {
250 qOhosPrintfDebug(
251 "%s: got qtDropAction=%d for drop request with id=%d",
252 Q_FUNC_INFO, static_cast<int>(qtDropAction), pendingDropRequestId);
253 QArkUi::callArkUiOrFailOnErrorResult(
254 Q_OHOS_NAMED_FUNC(::OH_ArkUI_NotifyDragResult),
255 pendingDropRequestId,
256 qtDropAction != Qt::IgnoreAction
257 ? ::ARKUI_DRAG_RESULT_SUCCESSFUL
258 : ::ARKUI_DRAG_RESULT_FAILED);
259 QArkUi::callArkUiOrFailOnErrorResult(
260 Q_OHOS_NAMED_FUNC(::OH_ArkUI_NotifyDragEndPendingDone),
261 pendingDropRequestId);
262 }));
263
264 constexpr auto notifyDragEndPendingTimeout = ch::milliseconds(1500);
266 jsState,
267 [pendingDropRequestId, qtDropActionConsumer](const QtOhos::CallbackInfo &cbInfo) {
268 if ((*qtDropActionConsumer)(cbInfo.jsState(), Qt::IgnoreAction))
269 qOhosPrintfDebug("%s: used timeout action for drop request with id=%d", Q_FUNC_INFO, pendingDropRequestId);
270 },
271 notifyDragEndPendingTimeout);
272
273 qWindowRef.visitInQtThreadIfAlive(
274 [dragEventInfo, dropDataFactory = std::move(dropDataFactory), qtDropActionConsumer](QWindow &qWindow) mutable {
275 auto dropAction = processDropInQWindow(qWindow, dragEventInfo, std::move(dropDataFactory));
277 [&](QtOhos::JsState &jsState) {
278 (*qtDropActionConsumer)(jsState, dropAction);
279 });
280 });
281}
282
284{
285 return qEnvironmentVariableIntValue("IO__QT__USE_ASYNC_DROP_END_HANDLING") != 0;
286}
287
289 QtOhos::JsState &jsState, ::ArkUI_DragEvent *dragEvent, QtOhos::QThreadSafeRef<QWindow> qWindowRef,
290 const DragEventInfo &dragEventInfo, QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory)
291{
293 return false;
294
295 std::int32_t pendingDropRequestId = 0;
296 QArkUi::callArkUiOrFailOnErrorResult(
297 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_RequestDragEndPending),
298 dragEvent, &pendingDropRequestId);
299 processPendingDropRequestAsynchronously(
300 jsState, qWindowRef, dragEventInfo,
301 std::move(dropDataFactory), pendingDropRequestId);
302
303 return true;
304}
305
307{
308 return [recentWaitTimeouts = std::deque<ch::steady_clock::time_point>()]() mutable -> ch::nanoseconds {
309 constexpr auto waitTimeout = ch::milliseconds(50);
310 constexpr auto noWaitTimeout = ch::milliseconds(0);
311 constexpr auto maxWaitsPerSecond = 10;
312
313 const auto now = ch::steady_clock::now();
314
315 recentWaitTimeouts.erase(
316 recentWaitTimeouts.begin(),
317 std::lower_bound(
318 recentWaitTimeouts.begin(), recentWaitTimeouts.end(),
319 now - ch::seconds(1)));
320
321 if (recentWaitTimeouts.size() < maxWaitsPerSecond) {
322 recentWaitTimeouts.push_back(now);
323 return waitTimeout;
324 } else {
325 return noWaitTimeout;
326 }
327 };
328}
329
330}
331
333 QtOhos::QThreadSafeRef<QWindow> qWindowRef)
334{
335 auto qtThreadMoveEventsProcessor = makeBestEffortQtThreadFunctionsExecutor<QWindow, Qt::DropAction>(
336 qWindowRef, makeDragMoveQtThreadWaitTimeoutsSupplier());
337 auto eventsHandler = [qWindowRef, qtThreadMoveEventsProcessor = std::move(qtThreadMoveEventsProcessor)](
338 QtOhos::JsState &jsState, ::ArkUI_NodeEventType eventType, ::ArkUI_DragEvent *dragEvent) {
339 DragEventInfo dragEventInfo = {
340 .globalDropPos = QPoint(
341 ::OH_ArkUI_DragEvent_GetTouchPointXToDisplay(dragEvent),
342 ::OH_ArkUI_DragEvent_GetTouchPointYToDisplay(dragEvent)),
343 .dropActions = mapQOhosArkUiDropOperationToQt(getQOhosDragEventDropOperation(dragEvent)),
344 .keyboardModifiers = mapArkUiModifierKeyStatesToQt(getDragEventModifierKeyStates(dragEvent)),
345 };
346
347 qOhosPrintfDebug("QNativeNode: got drag event: %d, (%d,%d)", eventType, dragEventInfo.globalDropPos.x(), dragEventInfo.globalDropPos.y());
348
349 switch (eventType) {
350 case ::NODE_ON_DRAG_ENTER:
351 case ::NODE_ON_DRAG_MOVE:
352 {
353 auto dropDataFactory = makeDummyQMimeDataFactoryFromUdmfDataTypes(
354 getDragEventDataTypes(dragEvent));
355 auto qtDropAction = qtThreadMoveEventsProcessor(
356 [dragEventInfo, dropDataFactory = std::move(dropDataFactory)](QWindow &qWindow) {
357 QDrag *currentDrag = QDragManager::self()->object();
358 QPlatformDragQtResponse qtResponse = QWindowSystemInterface::handleDrag(
359 &qWindow,
360 currentDrag != nullptr ? currentDrag->mimeData() : dropDataFactory().get(),
361 QHighDpi::toNativeLocalPosition(
362 qWindow.mapFromGlobal(QHighDpi::fromNativePixels(dragEventInfo.globalDropPos, &qWindow)), &qWindow),
363 currentDrag != nullptr ? currentDrag->supportedActions() : dragEventInfo.dropActions,
364 Qt::LeftButton, dragEventInfo.keyboardModifiers);
365 if (currentDrag != nullptr && qtResponse.isAccepted() && qtResponse.acceptedAction() != Qt::IgnoreAction)
366 getQOhosPlatformDrag()->updateDropAction(qtResponse.acceptedAction());
367 return qtResponse.acceptedAction();
368 });
369 QArkUi::callArkUiOrFailOnErrorResult(
370 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_SetDragResult),
371 dragEvent,
372 qtDropAction.valueOr(Qt::IgnoreAction) != Qt::IgnoreAction
373 ? Q_DROP_ENABLED
374 : Q_DROP_DISABLED);
375 setDragEventSuggestedDropOperationIfAvailable(
376 dragEvent, qtDropAction.andThen(&tryMapQOhosArkUiDropOperationFromQt));
377 }
378 break;
379 case ::NODE_ON_DRAG_LEAVE:
380 qWindowRef.visitInQtThreadIfAlive(
381 [](QWindow &qWindow) {
382 std::ignore = QWindowSystemInterface::handleDrag(
383 &qWindow, nullptr, QPoint(), Qt::IgnoreAction, Qt::MouseButtons(), Qt::KeyboardModifiers());
384 });
385 break;
386 case ::NODE_ON_DROP:
387 {
388 QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory;
389 if (getDragEventDataTypeCount(dragEvent) != 0) {
390 auto optDragUdmfData = tryGetDragEventUdmfDataOrNull(dragEvent);
391 dropDataFactory = optDragUdmfData
392 ? createQMimeDataFactoryFromUdmfData(std::move(*optDragUdmfData))
393 : &std::make_unique<QMimeData>;
394 } else {
395 dropDataFactory = &std::make_unique<QMimeData>;
396 }
397 auto copyableDropDataFactory = makeImplicitlySharedSupplier(std::move(dropDataFactory));
398
399 bool asyncProcessingStarted = tryStartAsyncProcessingOfDropEvent(
400 jsState, dragEvent, qWindowRef, dragEventInfo, copyableDropDataFactory);
401
402 if (!asyncProcessingStarted) {
403 auto qtDropAction = tryRunInQtThreadAndGetResult<QWindow, Qt::DropAction>(
404 qWindowRef,
405 [dragEventInfo, copyableDropDataFactory](QWindow &qWindow) {
406 return processDropInQWindow(qWindow, dragEventInfo, copyableDropDataFactory);
407 });
408 QArkUi::callArkUiOrFailOnErrorResult(
409 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_SetDragResult),
410 dragEvent,
411 qtDropAction.valueOr(Qt::IgnoreAction) != Qt::IgnoreAction
412 ? ::ARKUI_DRAG_RESULT_SUCCESSFUL
413 : ::ARKUI_DRAG_RESULT_FAILED);
414 setDragEventSuggestedDropOperationIfAvailable(
415 dragEvent, qtDropAction.andThen(&tryMapQOhosArkUiDropOperationFromQt));
416 }
417 }
418 break;
419 default:
420 break;
421 }
422 };
423
424 return [eventsHandler = std::move(eventsHandler)](::ArkUI_NodeEventType eventType, ::ArkUI_DragEvent *dragEvent) {
425 QtOhos::runInJsThreadAndWait(
426 [&](QtOhos::JsState &jsState) {
427 eventsHandler(jsState, eventType, dragEvent);
428 });
429 };
430}
431
432QT_END_NAMESPACE
JsState & jsState() const
QOhosPlatformDrag * getQOhosPlatformDrag()
void processPendingDropRequestAsynchronously(QtOhos::JsState &jsState, QtOhos::QThreadSafeRef< QWindow > qWindowRef, const DragEventInfo &dragEventInfo, QOhosSupplier< std::unique_ptr< QMimeData > > dropDataFactory, std::int32_t pendingDropRequestId)
bool tryStartAsyncProcessingOfDropEvent(QtOhos::JsState &jsState, ::ArkUI_DragEvent *dragEvent, QtOhos::QThreadSafeRef< QWindow > qWindowRef, const DragEventInfo &dragEventInfo, QOhosSupplier< std::unique_ptr< QMimeData > > dropDataFactory)
QOhosOptional< Result > tryRunInQtThreadAndGetResult(QtOhos::QThreadSafeRef< Context > context, std::function< Result(Context &)> qtThreadProcessFunc)
std::vector< std::string > getDragEventDataTypes(::ArkUI_DragEvent *dragEvent)
QOhosSupplier< T > makeImplicitlySharedSupplier(QOhosSupplier< T > baseSupplier)
Qt::KeyboardModifiers mapArkUiModifierKeyStatesToQt(std::uint64_t modifierKeyStates)
Qt::DropAction processDropInQWindow(QWindow &qWindow, const DragEventInfo &dragEventInfo, QOhosSupplier< std::unique_ptr< QMimeData > > dropDataFactory)
void setDragEventSuggestedDropOperationIfAvailable(::ArkUI_DragEvent *dragEvent, QOhosOptional<::ArkUI_DropOperation > optDropOperation)
std::uint64_t getDragEventModifierKeyStates(::ArkUI_DragEvent *dragEvent)
std::function< QOhosOptional< Result >(std::function< Result(Context &)>)> makeBestEffortQtThreadFunctionsExecutor(QtOhos::QThreadSafeRef< Context > contextRef, QOhosSupplier< ch::nanoseconds > timeoutsSupplier)
QOhosSupplier< ch::nanoseconds > makeDragMoveQtThreadWaitTimeoutsSupplier()
std::int32_t getDragEventDataTypeCount(::ArkUI_DragEvent *dragEvent)
std::shared_ptr< QOhosUdmfData > tryGetDragEventUdmfDataOrNull(::ArkUI_DragEvent *dragEvent)
int setJsTimeout(JsState &jsState, std::function< void(const CallbackInfo &)> timeoutFunc, std::chrono::milliseconds delay)
void runInJsThreadAndWait(const std::function< void(JsState &)> &task)
QOhosConsumer<::ArkUI_NodeEventType, ::ArkUI_DragEvent * > makeQOhosNativeDragEventsHandler(QtOhos::QThreadSafeRef< QWindow > qWindowRef)