5#include <QtCore/private/qnapi_p.h>
6#include <qohosjsenv_p.h>
7#include <QtCore/private/qohoslogger_p.h>
8#include <QtCore/qnamespace.h>
9#include <QtGui/private/qhighdpiscaling_p.h>
14#include <QInputDevice>
15#include <QGuiApplication>
16#include <QTextCharFormat>
18#include <qohosjsutils.h>
19#include <inputmethod/inputmethod_controller_capi.h>
27using InsertedTextId = QtOhos::TypedId<std::uint64_t,
struct InsertedTextIdTag>;
34 InsertedTextId
id()
const;
56 JsInputMethodInsertedTextComposer() =
default;
57 void initOrIncrementId();
59 QOhosOptional<InsertedTextId> m_id;
90 if (!m_id.has_value()) {
91 m_id = InsertedTextId(0);
93 auto oldValue = m_id.value().value();
94 m_id = InsertedTextId(++oldValue);
101 return QOhosInsertedText(text, m_id.value());
106 using TextInputType = ::InputMethod_TextInputType;
107 if (hints & Qt::ImhMultiLine) {
108 return TextInputType::IME_TEXT_INPUT_TYPE_MULTILINE;
109 }
else if (hints & Qt::ImhDigitsOnly) {
110 return (hints & Qt::ImhHiddenText)
111 ? TextInputType::IME_TEXT_INPUT_TYPE_NUMBER_PASSWORD
112 : TextInputType::IME_TEXT_INPUT_TYPE_NUMBER;
113 }
else if (hints & Qt::ImhDialableCharactersOnly) {
114 return TextInputType::IME_TEXT_INPUT_TYPE_PHONE;
115 }
else if (hints & (Qt::ImhDate | Qt::ImhTime)) {
116 return TextInputType::IME_TEXT_INPUT_TYPE_DATETIME;
117 }
else if (hints & Qt::ImhEmailCharactersOnly) {
118 return TextInputType::IME_TEXT_INPUT_TYPE_EMAIL_ADDRESS;
119 }
else if (hints & Qt::ImhUrlCharactersOnly) {
120 return TextInputType::IME_TEXT_INPUT_TYPE_URL;
121 }
else if (hints & Qt::ImhHiddenText) {
122 return TextInputType::IME_TEXT_INPUT_TYPE_VISIBLE_PASSWORD;
124 return TextInputType::IME_TEXT_INPUT_TYPE_TEXT;
131 case ::InputMethod_Direction::IME_DIRECTION_NONE:
133 case ::InputMethod_Direction::IME_DIRECTION_UP:
135 case ::InputMethod_Direction::IME_DIRECTION_DOWN:
137 case ::InputMethod_Direction::IME_DIRECTION_LEFT:
139 case ::InputMethod_Direction::IME_DIRECTION_RIGHT:
147 switch (qtEnterKeyType) {
148 case Qt::EnterKeyType::EnterKeyReturn:
149 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_NEWLINE;
150 case Qt::EnterKeyType::EnterKeyDone:
151 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_DONE;
152 case Qt::EnterKeyType::EnterKeyGo:
153 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_GO;
154 case Qt::EnterKeyType::EnterKeySend:
155 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_SEND;
156 case Qt::EnterKeyType::EnterKeySearch:
157 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_SEARCH;
158 case Qt::EnterKeyType::EnterKeyNext:
159 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_NEXT;
160 case Qt::EnterKeyType::EnterKeyPrevious:
161 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_PREVIOUS;
162 case Qt::EnterKeyType::EnterKeyDefault:
163 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_NONE;
166 "%s: Cannot map Qt::EnterKeyType to OHOS value. Returning ::InputMethod_EnterKeyType::IME_ENTER_KEY_UNSPECIFIED",
168 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_UNSPECIFIED;
172 QOhosInputContext::RequestKeyboardReason qtRequestKeyboardReason)
174 switch (qtRequestKeyboardReason) {
175 case QOhosInputContext::RequestKeyboardReason::NONE:
176 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_NONE;
177 case QOhosInputContext::RequestKeyboardReason::MOUSE:
178 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_MOUSE;
179 case QOhosInputContext::RequestKeyboardReason::TOUCH:
180 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_TOUCH;
181 case QOhosInputContext::RequestKeyboardReason::OTHER:
182 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_OTHER;
185 "%s: Cannot map QOhosInputContext::RequestKeyboardReason to OHOS value. Returning ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_NONE",
187 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_NONE;
193 const auto value = query->value(property).toInt(&converted);
200 if (!lastInsertedTextId.has_value()) {
201 qOhosPrintfError(
"%s: JsInputMethodInsertedTextComposer has no last inserted text ID", Q_FUNC_INFO);
205 auto currentInsertedTextId = insertedText.id();
206 if (currentInsertedTextId != lastInsertedTextId.value()) {
208 "%s: inserted text and one currently processed differ from each other, system won't be notified with changeSelection()", Q_FUNC_INFO);
213 [&](
QtOhos::
JsState &jsState, QOhosTaskPromise<> taskPromise) {
214 auto startPosition = cursorPosition;
215 auto endPosition = cursorPosition + insertedText
.text().length();
216 jsState.evalToPromiseOrRejectOnThrow(
217 "@ohos.inputMethod.getController().changeSelection(*)", {insertedText
.text(), startPosition, endPosition})
218 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"changeSelection()"))
219 .onFinally(
std::move(taskPromise).makeChained(Q_FUNC_INFO));
226 int x = qBound(rect.left(), p.x(), rect.right());
227 int y = qBound(rect.top(), p.y(), rect.bottom());
236 , m_qtImEnabled(
false)
243 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::QOhosInputContext");
246 QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
247 this, &QOhosInputContext::onCursorRectangleChanged);
249 QGuiApplication::instance()->installEventFilter(
this);
256 if (m_imConnectionState == ImConnectionState::Attached) {
257 setImConnectionState(ImConnectionState::Detached);
258 setImConnectionState(ImConnectionState::Attached);
264 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::commit");
266 auto preeditText =
std::exchange(m_pendingPreeditText, {});
268 QInputMethodEvent event;
269 event.setCommitString(preeditText);
270 sendFocusObjectInputMethodEvent(&event);
275 if (!focusObjectOrNull()) {
280 m_qtImEnabled = queryImEnabled();
281 const auto qtInputMethodHintsValue = queryInputMethodHints();
282 const auto qtEnterKeyTypeValue = queryEnterKeyType();
284 const bool imStateChanged =
285 m_qtInputMethodHints != qtInputMethodHintsValue
286 || m_qtEnterKeyType != qtEnterKeyTypeValue;
288 auto imAlreadyAttached = m_imConnectionState == ImConnectionState::Attached;
289 if (imAlreadyAttached && imStateChanged)
290 updateInputMethodControllerAttributes(qtInputMethodHintsValue, qtEnterKeyTypeValue);
292 m_qtInputMethodHints = qtInputMethodHintsValue;
293 m_qtEnterKeyType = qtEnterKeyTypeValue;
295 if (inputMethodAccepted() && m_qtImEnabled) {
296 auto updateCausedByWidgetTransform = queries == Qt::ImInputItemClipRectangle;
297 auto updateCausedByQueryAllImParameters = queries == Qt::ImQueryAll;
299 if (updateCausedByQueryAllImParameters)
302 if (!updateCausedByWidgetTransform || !imAlreadyAttached) {
312 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::invokeAction");
317 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::keyboardRect");
323 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::isAnimating");
329 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::showInputPanel");
330 setImConnectionState(ImConnectionState::Attached);
335 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::hideInputPanel");
336 setImConnectionState(ImConnectionState::Detached);
341 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::isInputPanelVisible");
342 return m_imConnectionRequestedState == ImConnectionState::Attached;
347 m_focusObject = object;
352 return m_focusObject;
357 if (requestedState != m_imConnectionRequestedState) {
358 setImConnectionStateImpl(requestedState);
360 if (m_imConnectionState == ImConnectionState::Attached && !m_softwareKeyboardVisible)
365void QOhosInputContext::setImConnectionStateImpl(ImConnectionState requestedState)
367 m_imConnectionRequestedState = m_qtImEnabled ? requestedState : ImConnectionState::Detached;
369 if (!m_imConnectionRequestActive) {
370 m_imConnectionRequestActive =
true;
372 dispatchRequestedImStateChange(requestedState);
376void QOhosInputContext::dispatchRequestedImStateChange(ImConnectionState requestedState)
379 if (requestedState == ImConnectionState::Attached)
380 result = attachToInputMethodController();
382 result = detachFromInputMethodController();
383 handleRequestedImConnectionState(requestedState, result);
388 class ImCallbacks :
public QOhosInputMethodProxy::ClientCallbacks
392 : m_inputContext(inputContext)
397 void onInsertText(
std::string text)
override
399 m_inputContext.sendInsertedTextToQt(
std::move(text));
402 void onInsertPreviewText(
std::string previewText)
override
404 m_inputContext.sendInsertedPreviewTextToQt(
std::move(previewText));
407 void onFinishPreviewText()
override
412 void onDeleteForward(
int length)
override
414 m_inputContext.sendFocusObjectFunctionalKeyEvent(Qt::Key_Delete, QChar::fromLatin1(
'\u007F'), length);
417 void onDeleteBackward(
int length)
override
419 m_inputContext.sendFocusObjectFunctionalKeyEvent(Qt::Key_Backspace, QChar::fromLatin1(
'\u0008'), length);
422 void onSendKeyboardStatus(::InputMethod_KeyboardStatus keyboardStatus)
override
424 bool ohosKeyboardShown = keyboardStatus == ::InputMethod_KeyboardStatus::IME_KEYBOARD_STATUS_SHOW;
428 void onSendEnterKey(::InputMethod_EnterKeyType)
override
430 m_inputContext.sendFocusObjectFunctionalKeyEvent(Qt::Key_Enter, QChar::fromLatin1(
'\u000D'));
433 void onMoveCursor(::InputMethod_Direction direction)
override
435 auto qtDirection = tryMapInputMethodDirectionToQt(direction);
436 if (!qtDirection.has_value()) {
438 "got unsupported InputMethod_Direction value (%d), cursor won't be moved!",
439 static_cast<
int>(direction));
443 m_inputContext.sendCursorMoveToQt(qtDirection.value());
450 qOhosPrintfWarning(
"%s: proxy already exists!", Q_FUNC_INFO);
452 m_imProxy = std::make_shared<QOhosInputMethodProxy>(
453 std::make_shared<ImCallbacks>(*
this),
454 mapQtToOhosImeRequestReason(
455 m_lastInputTypeToTriggerSoftKeyboard.value_or(RequestKeyboardReason::OTHER)));
457 if (m_imProxy->hasAttachedSuccessfully()) {
458 m_imProxy->notifyConfigurationChange(
459 mapQtToOhosImeEnterKeyType(m_qtEnterKeyType),
460 mapQtInputMethodHintsToOhosImeTextInputType(m_qtInputMethodHints));
469 qOhosPrintfWarning(
"%s: proxy doesn't exist!", Q_FUNC_INFO);
475void QOhosInputContext::handleRequestedImConnectionState(ImConnectionState requestedState,
bool success)
477 m_imConnectionRequestActive =
false;
480 qOhosWarning(QtForOhos)
482 << (requestedState == ImConnectionState::Attached ?
"attach to" :
"detach from")
487 m_imConnectionState = requestedState;
489 if (m_imConnectionRequestedState != m_imConnectionState)
490 setImConnectionStateImpl(m_imConnectionRequestedState);
492 onCursorRectangleChanged();
497 if (m_imProxy ==
nullptr) {
498 qOhosPrintfError(
"%s: proxy doesn't exist!", Q_FUNC_INFO);
502 m_imProxy->showTextInput(mapQtToOhosImeRequestReason(
503 m_lastInputTypeToTriggerSoftKeyboard.value_or(RequestKeyboardReason::OTHER)));
508 m_softwareKeyboardVisible = visible;
513 m_lastInputTypeToTriggerSoftKeyboard = inputType;
518 if (m_imConnectionState == ImConnectionState::Detached) {
519 qOhosDebug(QtForOhos) <<
"Cursor rectangle changed while detached; deferring update until attach";
520 m_updateCursorRectangleAfterAttaching =
true;
524 auto *focusedWindow = QGuiApplication::focusWindow();
525 if (focusedWindow ==
nullptr) {
526 qOhosCritical(QtForOhos)
527 <<
"Could not retrieve focused window. Updating cursor position isn't possible";
531 auto focusedWindowPosition = focusedWindow->position();
532 QRect cursorRectangle = QGuiApplication::inputMethod()->cursorRectangle().toRect();
533 if (!m_updateCursorRectangleAfterAttaching
534 && cursorRectangle == m_lastCursorRectangle
535 && m_lastFocusedWindowPosition == focusedWindowPosition) {
539 m_lastCursorRectangle = cursorRectangle;
540 m_lastFocusedWindowPosition = focusedWindowPosition;
541 auto globalInputItemRectangle =
542 QGuiApplication::inputMethod()->inputItemClipRectangle()
543 .translated(m_lastFocusedWindowPosition)
545 auto globalCursorRectangle = m_lastCursorRectangle.translated(m_lastFocusedWindowPosition);
546 auto inputItemClampedCursorPos = clampToRect(
547 {globalCursorRectangle.x(), globalCursorRectangle.y()},
548 globalInputItemRectangle.isValid()
549 ? globalInputItemRectangle
550 : focusedWindow->geometry());
551 auto nativeCursorPos = QHighDpiScaling::mapPositionToNative(
552 inputItemClampedCursorPos, focusedWindow->screen()->handle());
553 auto screenBasedCursorPos = nativeCursorPos - focusedWindow->screen()->handle()->geometry().topLeft();
555 screenBasedCursorPos.x(), screenBasedCursorPos.y(),
556 globalCursorRectangle.width(), globalCursorRectangle.height()
558 m_updateCursorRectangleAfterAttaching =
false;
562 Qt::InputMethodHints qtInputMethodHints, Qt::EnterKeyType qtEnterKeyType)
564 if (m_imProxy ==
nullptr) {
565 qOhosPrintfError(
"%s: proxy doesn't exist!", Q_FUNC_INFO);
569 m_imProxy->notifyConfigurationChange(
570 mapQtToOhosImeEnterKeyType(qtEnterKeyType),
571 mapQtInputMethodHintsToOhosImeTextInputType(qtInputMethodHints));
576 if (m_imProxy ==
nullptr) {
577 qOhosPrintfError(
"%s: proxy doesn't exist!", Q_FUNC_INFO);
581 m_imProxy->notifyCursorUpdate(globalCursorRect);
586 if (obj->isWidgetType() && queryImEnabled()) {
587 auto focusReason = event->reason();
588 if (focusReason == Qt::OtherFocusReason || focusReason == Qt::ActiveWindowFocusReason)
589 setLastInputTypeToTriggerSoftKeyboard(RequestKeyboardReason::NONE);
595 if (event->type() == QEvent::FocusIn)
596 handleFocusInEvent(obj,
static_cast<QFocusEvent *>(event));
604 auto query = tryQueryFocusObjectInputMethod(Qt::ImEnabled);
608 if (!query->value(Qt::ImEnabled).toBool()) {
609 qOhosDebug(QtForOhos) <<
"onInsertText(): focus object is not able to handle InputMethod";
613 m_pendingPreeditText.clear();
615 QInputMethodEvent event;
616 event.setCommitString(QString::fromStdString(insertedText.text()));
617 sendFocusObjectInputMethodEvent(&event);
619 auto cursorPosition = tryQueryCursorPosition();
620 if (cursorPosition.has_value())
621 notifyOhosInputMethodAboutPossibleAutocorrection(insertedText, cursorPosition.value());
623 qOhosWarning(QtForOhos) <<
"Couldn't obtain IM cursorPosition";
628 auto query = tryQueryFocusObjectInputMethod(Qt::ImEnabled);
632 if (!query->value(Qt::ImEnabled).toBool()) {
633 qOhosPrintfDebug(
"%s: focus object is not able to handle InputMethod", Q_FUNC_INFO);
637 const QString previewString = QString::fromStdString(previewText);
638 QList<QInputMethodEvent::Attribute> imEventAttributes;
639 if (!previewString.isEmpty()) {
640 QTextCharFormat format;
641 format.setFontUnderline(
true);
642 imEventAttributes.append(
643 QInputMethodEvent::Attribute(
644 QInputMethodEvent::TextFormat, 0, previewString.length(), format));
647 m_pendingPreeditText = previewString;
649 QInputMethodEvent preeditStringEvent(previewString, imEventAttributes);
650 sendFocusObjectInputMethodEvent(&preeditStringEvent);
655 auto *focusObject = focusObjectOrNull();
656 if (focusObject ==
nullptr) {
657 qOhosWarning(QtForOhos) <<
"sendFocusObjectInputMethodEvent(): no focus object to send event to!";
661 QCoreApplication::sendEvent(focusObject, event);
667 case QOhosInputContext::Direction::CURSOR_UP:
668 sendFocusObjectFunctionalKeyEvent(Qt::Key_Up, QChar(std::int32_t(Qt::Key_Up)));
670 case QOhosInputContext::Direction::CURSOR_DOWN:
671 sendFocusObjectFunctionalKeyEvent(Qt::Key_Down, QChar(std::int32_t(Qt::Key_Down)));
673 case QOhosInputContext::Direction::CURSOR_LEFT:
674 sendFocusObjectFunctionalKeyEvent(Qt::Key_Left, QChar(std::int32_t(Qt::Key_Left)));
676 case QOhosInputContext::Direction::CURSOR_RIGHT:
677 sendFocusObjectFunctionalKeyEvent(Qt::Key_Right, QChar(std::int32_t(Qt::Key_Right)));
682void QOhosInputContext::sendFocusObjectFunctionalKeyEvent(Qt::Key key,
const QChar &keyChar,
int repeatCount)
684 auto *focusObject = focusObjectOrNull();
685 if (focusObject ==
nullptr) {
686 qOhosWarning(QtForOhos) <<
"sendFocusObjectFunctionalKeyEvent(): no focus object to send event to!";
690 for (
int i = 0; i < repeatCount; ++i) {
691 QGuiApplication::postEvent(focusObject,
new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier, QString(keyChar)));
692 QGuiApplication::postEvent(focusObject,
new QKeyEvent(QEvent::KeyRelease, key, Qt::NoModifier, QString(keyChar)));
698 auto query = tryQueryFocusObjectInputMethod(Qt::ImEnabled);
699 return !query.isNull() && query->value(Qt::ImEnabled).toBool();
704 QOhosOptional<Qt::InputMethodHints> hints;
705 auto query = tryQueryFocusObjectInputMethod(Qt::ImHints);
706 if (!query.isNull()) {
707 auto imHintsInt = tryGetIntPropertyFromQuery(Qt::ImHints, query);
708 if (imHintsInt.has_value())
709 hints =
static_cast<Qt::InputMethodHints>(imHintsInt.value());
712 return hints.value_or(defaultInputMethodHints);
717 QOhosOptional<Qt::EnterKeyType> enterKeyType;
718 auto query = tryQueryFocusObjectInputMethod(Qt::ImEnterKeyType);
719 if (!query.isNull()) {
720 auto imEnterKeyTypeInt = tryGetIntPropertyFromQuery(Qt::ImEnterKeyType, query);
721 if (imEnterKeyTypeInt.has_value())
722 enterKeyType =
static_cast<Qt::EnterKeyType>(imEnterKeyTypeInt.value());
725 return enterKeyType.value_or(defaultEnterKeyType);
730 auto query = tryQueryFocusObjectInputMethod(Qt::ImCursorPosition);
731 return !query.isNull()
732 ? tryGetIntPropertyFromQuery(Qt::ImCursorPosition, query)
733 : makeEmptyQOhosOptional();
736QSharedPointer<QInputMethodQueryEvent>
QOhosInputContext::tryQueryFocusObjectInputMethod(Qt::InputMethodQueries queries)
const
738 auto *focusObject = focusObjectOrNull();
739 if (focusObject ==
nullptr) {
740 qOhosWarning(QtForOhos) <<
"tryQueryFocusObjectInputMethod(): no focus object to query information from!";
744 auto imqEvent = QSharedPointer<QInputMethodQueryEvent>::create(queries);
745 QCoreApplication::sendEvent(focusObject, imqEvent.data());
void update(Qt::InputMethodQueries queries) override
Notification on editor updates.
QRectF keyboardRect() const override
This function can be reimplemented to return virtual keyboard rectangle in currently active window co...
void setSoftwareKeyboardVisibilityStatus(bool visible)
void showInputPanel() override
Request to show input panel.
void invokeAction(QInputMethod::Action action, int cursorPosition) override
Called when the word currently being composed in the input item is tapped by the user.
void setLastInputTypeToTriggerSoftKeyboard(RequestKeyboardReason inputType)
void reset() override
Method to be called when input method needs to be reset.
void hideInputPanel() override
Request to hide input panel.
bool eventFilter(QObject *obj, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
bool isInputPanelVisible() const override
Returns input panel visibility status.
QObject * focusObjectOrNull() const
void setFocusObject(QObject *object) override
This virtual method gets called to notify updated focus to object.
bool isAnimating() const override
This function can be reimplemented to return true whenever input method is animating shown or hidden.
QtOhos::enums::ohos::inputMethod::Direction Direction
JsInputMethodInsertedTextComposer & operator=(const JsInputMethodInsertedTextComposer &)=delete
JsInputMethodInsertedTextComposer(JsInputMethodInsertedTextComposer &&)=delete
static JsInputMethodInsertedTextComposer & instance()
JsInputMethodInsertedTextComposer & operator=(JsInputMethodInsertedTextComposer &&)=delete
QOhosOptional< InsertedTextId > lastId() const
JsInputMethodInsertedTextComposer(const JsInputMethodInsertedTextComposer &)=delete
QOhosInsertedText makeInsertedText(const std::string &text)
InsertedTextId id() const
QOhosInsertedText(const std::string &text, const InsertedTextId &id)
Combined button and popup list for selecting options.
QOhosOptional< QOhosInputContext::Direction > tryMapInputMethodDirectionToQt(::InputMethod_Direction direction)
::InputMethod_TextInputType mapQtInputMethodHintsToOhosImeTextInputType(Qt::InputMethodHints hints)
::InputMethod_EnterKeyType mapQtToOhosImeEnterKeyType(Qt::EnterKeyType qtEnterKeyType)
const Qt::EnterKeyType defaultEnterKeyType
void notifyOhosInputMethodAboutPossibleAutocorrection(const QOhosInsertedText &insertedText, int cursorPosition)
const Qt::InputMethodHints defaultInputMethodHints
QPoint clampToRect(const QPoint &p, const QRect &rect)
::InputMethod_RequestKeyboardReason mapQtToOhosImeRequestReason(QOhosInputContext::RequestKeyboardReason qtRequestKeyboardReason)
QOhosOptional< int > tryGetIntPropertyFromQuery(Qt::InputMethodQuery property, QSharedPointer< QInputMethodQueryEvent > query)
void invokeInJsThreadAndWaitForContinue(std::function< void(JsState &, QOhosTaskPromise<>)> &&task, std::string callerContextName={})
std::nullopt_t makeEmptyQOhosOptional()