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