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