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