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 <arkui/drag_and_drop.h>
11#include <arkui/native_node.h>
12#include <arkui/native_type.h>
13#include <arkui/ui_input_event.h>
14#include <array>
15#include <chrono>
16#include <condition_variable>
17#include <cstdint>
18#include <database/udmf/udmf.h>
19#include <functional>
20#include <future>
21#include <info/application_target_sdk_version.h>
22#include <memory>
23#include <mutex>
24#include <optional>
25#include <qarkui/qarkuiutils.h>
26#include <qarkui/qnativenodeapi.h>
27#include <qarkui/qohosdrageventcshim.h>
28#include <qohosjsutils.h>
29#include <qohosplatformdrag.h>
30#include <qohosudmf.h>
31#include <qohosudmfconversions.h>
32#include <qohosutils.h>
33#include <qpa/qplatformdrag.h>
34#include <qpa/qplatformintegration.h>
35#include <qpa/qwindowsysteminterface.h>
36#include <render/qohosbatchingrequestshandler.h>
37#include <render/qohosdrageventutils.h>
38#include <render/qohosnativedrageventshandler.h>
39#include <string>
40#include <type_traits>
41#include <vector>
42
43namespace ch = std::chrono;
44
45QT_BEGIN_NAMESPACE
46
47namespace {
48
49// The following DragResult values are listed in the ArkTS documentation and accepted by
50// OH_ArkUI_DragEvent_SetDragResult(), but they have no corresponding ArkUI_DragResult
51// enumerators and 4 currently lies outside the C++ value range [0, 3] of ArkUI_DragResult,
52// so both are passed as integers to qohosdrageventcshim.c.
58
65
66template<typename T>
67QOhosSupplier<T> makeImplicitlySharedSupplier(QOhosSupplier<T> baseSupplier)
68{
69 auto sharedBaseSupplier = QtOhos::moveToSharedPtr(std::move(baseSupplier));
70 return [sharedBaseSupplier]() {
71 return (*sharedBaseSupplier)();
72 };
73}
74
75std::int32_t getDragEventDataTypeCount(::ArkUI_DragEvent *dragEvent)
76{
77 std::int32_t dataTypeCount = 0;
78 QArkUi::callArkUiOrFailOnErrorResult(
79 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_GetDataTypeCount),
80 dragEvent, &dataTypeCount);
81
82 return dataTypeCount;
83}
84
85std::vector<std::string> getDragEventDataTypes(::ArkUI_DragEvent *dragEvent)
86{
87 constexpr auto dataTypeMaxLength = 128;
88
89 auto dataTypeCount = getDragEventDataTypeCount(dragEvent);
90 if (dataTypeCount == 0)
91 return {};
92
93 std::vector<std::array<char, dataTypeMaxLength + 1>> dataTypesStringData;
94 dataTypesStringData.resize(dataTypeCount);
95
96 std::vector<char *> dataTypesStringPointers;
97 for (std::int32_t i = 0; i < dataTypeCount; ++i)
98 dataTypesStringPointers.push_back(dataTypesStringData[i].data());
99
100 QArkUi::callArkUiOrFailOnErrorResult(
101 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_GetDataTypes),
102 dragEvent, dataTypesStringPointers.data(), dataTypesStringPointers.size(), dataTypeMaxLength);
103
104 return {dataTypesStringPointers.begin(), dataTypesStringPointers.end()};
105}
106
107std::shared_ptr<QOhosUdmfData> tryGetDragEventUdmfDataOrNull(::ArkUI_DragEvent *dragEvent)
108{
109 QOhosUdmfData udmfData;
110 auto getUdmfDataRes = QArkUi::callArkUi(
111 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_GetUdmfData),
112 dragEvent, udmfData.nativePtr());
113
114 return getUdmfDataRes == ARKUI_ERROR_CODE_NO_ERROR
115 ? QtOhos::moveToSharedPtr(std::move(udmfData))
116 : nullptr;
117}
118
120 ::ArkUI_DragEvent *dragEvent, std::optional<::ArkUI_DropOperation> optDropOperation)
121{
122 if (optDropOperation.has_value()) {
123 QArkUi::callArkUiOrFailOnErrorResult(
124 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_SetSuggestedDropOperation),
125 dragEvent, optDropOperation.value());
126 }
127}
128
129std::uint64_t getDragEventModifierKeyStates(::ArkUI_DragEvent *dragEvent)
130{
131 std::uint64_t keys;
132 QArkUi::callArkUiOrFailOnErrorResult(
133 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_GetModifierKeyStates),
134 dragEvent, &keys);
135 return keys;
136}
137
139{
140 static const std::pair<std::uint64_t, Qt::KeyboardModifier> arkUiToQtModifiersMap[] = {
141 {::ARKUI_MODIFIER_KEY_CTRL, Qt::KeyboardModifier::ControlModifier},
142 {::ARKUI_MODIFIER_KEY_SHIFT, Qt::KeyboardModifier::ShiftModifier},
143 {::ARKUI_MODIFIER_KEY_ALT, Qt::KeyboardModifier::AltModifier},
144 {::ARKUI_MODIFIER_KEY_FN, Qt::KeyboardModifier::MetaModifier},
145 };
146
147 Qt::KeyboardModifiers modifiers;
148 for (const auto &mod : arkUiToQtModifiersMap)
149 modifiers.setFlag(mod.second, (modifierKeyStates & mod.first) != 0);
150
151 return modifiers;
152}
153
154template<typename Context, typename Result>
156 QtOhos::QThreadSafeRef<Context> context, std::function<Result(Context &)> qtThreadProcessFunc)
157{
158 constexpr auto maxResultWaitTime = ch::milliseconds(50);
159
160 auto resultPromise = std::make_shared<std::promise<Result>>();
161 auto resultFuture = resultPromise->get_future();
162
163 context.visitInQtThreadIfAlive(
164 [qtThreadProcessFunc = std::move(qtThreadProcessFunc), resultPromise](Context &context) {
165 resultPromise->set_value(qtThreadProcessFunc(context));
166 });
167
168 return
169 resultFuture.wait_for(maxResultWaitTime) == std::future_status::ready
170 ? std::optional<Result>(resultFuture.get())
171 : std::nullopt;
172}
173
174QOhosPlatformDrag *getQOhosPlatformDrag()
175{
176 return static_cast<QOhosPlatformDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
177}
178
180 QWindow &qWindow, const DragEventInfo &dragEventInfo,
181 QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory)
182{
183 QDrag *currentDrag = QDragManager::self()->object();
184 if (currentDrag != nullptr)
185 getQOhosPlatformDrag()->handlePreDrop();
186 QPlatformDropQtResponse qtResponse = QWindowSystemInterface::handleDrop(
187 &qWindow,
188 currentDrag != nullptr ? currentDrag->mimeData() : dropDataFactory().get(),
189 dragEventInfo.localDropPos,
190 currentDrag != nullptr ? currentDrag->supportedActions() : dragEventInfo.dropActions,
191 Qt::LeftButton, dragEventInfo.keyboardModifiers);
192 auto updatedDropAction =
193 qtResponse.isAccepted()
194 ? qtResponse.acceptedAction()
195 : Qt::IgnoreAction;
196 if (currentDrag != nullptr)
197 getQOhosPlatformDrag()->updateDropAction(updatedDropAction);
198 return updatedDropAction;
199}
200
202 QtOhos::JsState &jsState, QtOhos::QThreadSafeRef<QWindow> qWindowRef, const DragEventInfo &dragEventInfo,
203 QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory, std::int32_t pendingDropRequestId)
204{
205 qOhosPrintfDebug("%s: async processing of drop request with id=%d", Q_FUNC_INFO, pendingDropRequestId);
206
207 auto qtDropActionConsumer = moveToSharedPtr(
208 QtOhos::makeCallOnceConsumerWrapper<QtOhos::JsState &, Qt::DropAction>(
209 [pendingDropRequestId](QtOhos::JsState &, Qt::DropAction qtDropAction) {
210 qOhosPrintfDebug(
211 "%s: got qtDropAction=%d for drop request with id=%d",
212 Q_FUNC_INFO, static_cast<int>(qtDropAction), pendingDropRequestId);
213 QArkUi::callArkUiOrFailOnErrorResult(
214 Q_OHOS_NAMED_FUNC(::OH_ArkUI_NotifyDragResult),
215 pendingDropRequestId,
216 qtDropAction != Qt::IgnoreAction
217 ? ::ARKUI_DRAG_RESULT_SUCCESSFUL
218 : ::ARKUI_DRAG_RESULT_FAILED);
219 QArkUi::callArkUiOrFailOnErrorResult(
220 Q_OHOS_NAMED_FUNC(::OH_ArkUI_NotifyDragEndPendingDone),
221 pendingDropRequestId);
222 }));
223
224 constexpr auto notifyDragEndPendingTimeout = ch::milliseconds(1500);
226 jsState,
227 [pendingDropRequestId, qtDropActionConsumer](const QtOhos::CallbackInfo &cbInfo) {
228 if ((*qtDropActionConsumer)(cbInfo.jsState(), Qt::IgnoreAction))
229 qOhosPrintfDebug("%s: used timeout action for drop request with id=%d", Q_FUNC_INFO, pendingDropRequestId);
230 },
231 notifyDragEndPendingTimeout);
232
233 qWindowRef.visitInQtThreadIfAlive(
234 [dragEventInfo, dropDataFactory = std::move(dropDataFactory), qtDropActionConsumer](QWindow &qWindow) mutable {
235 auto dropAction = processDropInQWindow(qWindow, dragEventInfo, std::move(dropDataFactory));
237 [&](QtOhos::JsState &jsState) {
238 (*qtDropActionConsumer)(jsState, dropAction);
239 },
240 Q_FUNC_INFO);
241 });
242}
243
245{
246 return qEnvironmentVariableIntValue("IO__QT__USE_ASYNC_DROP_END_HANDLING") != 0;
247}
248
250 QtOhos::JsState &jsState, ::ArkUI_DragEvent *dragEvent, QtOhos::QThreadSafeRef<QWindow> qWindowRef,
251 const DragEventInfo &dragEventInfo, QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory)
252{
254 return false;
255
256 std::int32_t pendingDropRequestId = 0;
257 QArkUi::callArkUiOrFailOnErrorResult(
258 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_RequestDragEndPending),
259 dragEvent, &pendingDropRequestId);
260 processPendingDropRequestAsynchronously(
261 jsState, qWindowRef, dragEventInfo,
262 std::move(dropDataFactory), pendingDropRequestId);
263
264 return true;
265}
266
267QPoint getDragEventTouchDisplayPosition(::ArkUI_DragEvent *dragEvent)
268{
269 return QPoint(
270 ::OH_ArkUI_DragEvent_GetTouchPointXToDisplay(dragEvent),
271 ::OH_ArkUI_DragEvent_GetTouchPointYToDisplay(dragEvent));
272}
273
275{
276public:
277 explicit DragMoveResponder(QtOhos::QThreadSafeRef<QWindow> qWindowRef);
278
280 const DragEventInfo &dragEventInfo, QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory);
281
283
284private:
285 struct DropActionState
286 {
287 std::mutex mutex;
288 std::condition_variable answerCondVar;
289 std::uint64_t answeredRequestId = 0;
290 std::optional<Qt::DropAction> optDropAction;
291 };
292
293 std::shared_ptr<DropActionState> m_sharedDropActionState;
294 QOhosConsumer<std::function<void(std::function<void(QWindow &)> &)>> m_pendingQueryUpdater;
295 std::uint64_t m_lastRequestId = 0;
296 std::optional<ch::steady_clock::time_point> m_optLastFailedWaitTime;
297};
298
309
311 const DragEventInfo &dragEventInfo, QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory)
312{
313 // the timeout outlasts a usual round trip but not a frame, the cooldown keeps a miss rare
314 constexpr auto answerWaitTimeout = ch::milliseconds(5);
315 constexpr auto waitCooldownAfterTimeout = ch::milliseconds(200);
316
317 const auto requestId = ++m_lastRequestId;
318 auto queryFunc =
319 [dragEventInfo, dropDataFactory = std::move(dropDataFactory),
320 sharedDropActionState = m_sharedDropActionState, requestId](QWindow &qWindow) {
321 QDrag *currentDrag = QDragManager::self()->object();
322 QPlatformDragQtResponse qtResponse = QWindowSystemInterface::handleDrag(
323 &qWindow,
324 currentDrag != nullptr ? currentDrag->mimeData() : dropDataFactory().get(),
325 dragEventInfo.localDropPos,
326 currentDrag != nullptr ? currentDrag->supportedActions() : dragEventInfo.dropActions,
327 Qt::LeftButton, dragEventInfo.keyboardModifiers);
328 if (currentDrag != nullptr && qtResponse.isAccepted() && qtResponse.acceptedAction() != Qt::IgnoreAction)
329 getQOhosPlatformDrag()->updateDropAction(qtResponse.acceptedAction());
330
331 {
332 std::lock_guard<std::mutex> stateLock(sharedDropActionState->mutex);
333 if (requestId <= sharedDropActionState->answeredRequestId)
334 return;
335
336 sharedDropActionState->answeredRequestId = requestId;
337 sharedDropActionState->optDropAction = qtResponse.acceptedAction();
338 }
339 sharedDropActionState->answerCondVar.notify_all();
340 };
341
342 m_pendingQueryUpdater(
343 [&](std::function<void(QWindow &)> &pendingQuery) {
344 pendingQuery = std::move(queryFunc);
345 });
346
347 const auto now = ch::steady_clock::now();
348 const bool waitingMakesSense =
349 !m_optLastFailedWaitTime || now - m_optLastFailedWaitTime.value() >= waitCooldownAfterTimeout;
350
351 {
352 std::unique_lock<std::mutex> stateLock(m_sharedDropActionState->mutex);
353 if (waitingMakesSense) {
354 const bool answered = m_sharedDropActionState->answerCondVar.wait_for(
355 stateLock, answerWaitTimeout,
356 [&]() {
357 return m_sharedDropActionState->answeredRequestId >= requestId;
358 });
359 if (answered)
360 m_optLastFailedWaitTime.reset();
361 else
362 m_optLastFailedWaitTime = now;
363 }
364
365 return m_sharedDropActionState->optDropAction;
366 }
367}
368
370{
371 std::lock_guard<std::mutex> stateLock(m_sharedDropActionState->mutex);
372 m_sharedDropActionState->answeredRequestId = m_lastRequestId;
373 m_sharedDropActionState->optDropAction.reset();
374}
375
376}
377
379 QtOhos::QThreadSafeRef<QWindow> qWindowRef)
380{
381 auto eventsHandler = [qWindowRef, dragMoveResponder = DragMoveResponder(qWindowRef)](
382 QtOhos::JsState &jsState, ::ArkUI_NodeEvent *nodeEvent) mutable {
383 auto eventType = QArkUi::callArkUi(Q_OHOS_NAMED_FUNC(OH_ArkUI_NodeEvent_GetEventType), nodeEvent);
384 auto *dragEvent = QArkUi::callArkUiOrFailOnNullResult(Q_OHOS_NAMED_FUNC(::OH_ArkUI_NodeEvent_GetDragEvent), nodeEvent);
385 auto node = QArkUi::callArkUiOrFailOnNullResult(Q_OHOS_NAMED_FUNC(OH_ArkUI_NodeEvent_GetNodeHandle), nodeEvent);
386
387 auto touchDisplayPosition = getDragEventTouchDisplayPosition(dragEvent);
388 auto nodeDisplayPosition = QArkUi::Node::nodeDisplayPosition(node);
389 auto localPosition = touchDisplayPosition - nodeDisplayPosition;
390
391 DragEventInfo dragEventInfo = {
392 .localDropPos = localPosition,
393 .dropActions = mapQOhosArkUiDropOperationToQt(getQOhosDragEventDropOperation(dragEvent)),
394 .keyboardModifiers = mapArkUiModifierKeyStatesToQt(getDragEventModifierKeyStates(dragEvent)),
395 };
396
397 qOhosPrintfDebug("QNativeNode: got drag event: %d, (%d,%d)", eventType, dragEventInfo.localDropPos.x(), dragEventInfo.localDropPos.y());
398
399 switch (eventType) {
400 case ::NODE_ON_DRAG_ENTER:
401 dragMoveResponder.forgetLastDropAction();
402 Q_FALLTHROUGH();
403 case ::NODE_ON_DRAG_MOVE:
404 {
405 auto qtDropAction = dragMoveResponder.respondToMove(
406 dragEventInfo,
407 makeDummyQMimeDataFactoryFromUdmfDataTypes(getDragEventDataTypes(dragEvent)));
408 QArkUi::callArkUiOrFailOnErrorResult(
409 Q_OHOS_NAMED_FUNC(::qOhosSetArkUiDragEventDragResult),
410 dragEvent,
411 static_cast<std::uint32_t>(
412 qtDropAction.value_or(Qt::IgnoreAction) != Qt::IgnoreAction
413 ? DropStatus::Enabled
414 : DropStatus::Disabled));
415 setDragEventSuggestedDropOperationIfAvailable(
416 dragEvent, qAndThen(qtDropAction, &tryMapQOhosArkUiDropOperationFromQt));
417 }
418 break;
419 case ::NODE_ON_DRAG_LEAVE:
420 qWindowRef.visitInQtThreadIfAlive(
421 [](QWindow &qWindow) {
422 std::ignore = QWindowSystemInterface::handleDrag(
423 &qWindow, nullptr, QPoint(), Qt::IgnoreAction, Qt::MouseButtons(), Qt::KeyboardModifiers());
424 });
425 break;
426 case ::NODE_ON_DROP:
427 {
428 QOhosSupplier<std::unique_ptr<QMimeData>> dropDataFactory;
429 if (getDragEventDataTypeCount(dragEvent) != 0) {
430 auto optDragUdmfData = tryGetDragEventUdmfDataOrNull(dragEvent);
431 dropDataFactory = optDragUdmfData
432 ? createQMimeDataFactoryFromUdmfData(std::move(*optDragUdmfData))
433 : &std::make_unique<QMimeData>;
434 } else {
435 dropDataFactory = &std::make_unique<QMimeData>;
436 }
437 auto copyableDropDataFactory = makeImplicitlySharedSupplier(std::move(dropDataFactory));
438
439 bool asyncProcessingStarted = tryStartAsyncProcessingOfDropEvent(
440 jsState, dragEvent, qWindowRef, dragEventInfo, copyableDropDataFactory);
441
442 if (!asyncProcessingStarted) {
443 auto qtDropAction = tryRunInQtThreadAndGetResult<QWindow, Qt::DropAction>(
444 qWindowRef,
445 [dragEventInfo, copyableDropDataFactory](QWindow &qWindow) {
446 return processDropInQWindow(qWindow, dragEventInfo, copyableDropDataFactory);
447 });
448 QArkUi::callArkUiOrFailOnErrorResult(
449 Q_OHOS_NAMED_FUNC(::OH_ArkUI_DragEvent_SetDragResult),
450 dragEvent,
451 qtDropAction.value_or(Qt::IgnoreAction) != Qt::IgnoreAction
452 ? ::ARKUI_DRAG_RESULT_SUCCESSFUL
453 : ::ARKUI_DRAG_RESULT_FAILED);
454 setDragEventSuggestedDropOperationIfAvailable(
455 dragEvent, qAndThen(qtDropAction, &tryMapQOhosArkUiDropOperationFromQt));
456 }
457 }
458 break;
459 default:
460 break;
461 }
462 };
463
464 return [eventsHandler = std::move(eventsHandler)](::ArkUI_NodeEvent *nodeEvent) mutable {
465 QtOhos::runInJsThreadAndWait(
466 [&](QtOhos::JsState &jsState) {
467 eventsHandler(jsState, nodeEvent);
468 },
469 Q_FUNC_INFO);
470 };
471}
472
473QT_END_NAMESPACE
std::optional< Qt::DropAction > respondToMove(const DragEventInfo &dragEventInfo, QOhosSupplier< std::unique_ptr< QMimeData > > dropDataFactory)
DragMoveResponder(QtOhos::QThreadSafeRef< QWindow > qWindowRef)
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)
void setDragEventSuggestedDropOperationIfAvailable(::ArkUI_DragEvent *dragEvent, std::optional<::ArkUI_DropOperation > optDropOperation)
bool tryStartAsyncProcessingOfDropEvent(QtOhos::JsState &jsState, ::ArkUI_DragEvent *dragEvent, QtOhos::QThreadSafeRef< QWindow > qWindowRef, const DragEventInfo &dragEventInfo, QOhosSupplier< std::unique_ptr< QMimeData > > dropDataFactory)
QPoint getDragEventTouchDisplayPosition(::ArkUI_DragEvent *dragEvent)
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)
std::uint64_t getDragEventModifierKeyStates(::ArkUI_DragEvent *dragEvent)
std::optional< Result > tryRunInQtThreadAndGetResult(QtOhos::QThreadSafeRef< Context > context, std::function< Result(Context &)> qtThreadProcessFunc)
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, std::string callerContextName={})
QOhosConsumer<::ArkUI_NodeEvent * > makeQOhosNativeDragEventsHandler(QtOhos::QThreadSafeRef< QWindow > qWindowRef)