/*************************************************************************** ** ** Copyright (C) 2013 BlackBerry Limited. All rights reserved. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qqnxinputcontext_imf.h" #include "qqnxabstractvirtualkeyboard.h" #include "qqnxintegration.h" #include "qqnxscreen.h" #include "qqnxscreeneventhandler.h" #include #include #include #include #include #include #include #include #include #include #include #include "imf/imf_client.h" #include "imf/input_control.h" #include #include #if defined(QQNXINPUTCONTEXT_IMF_EVENT_DEBUG) #define qInputContextIMFRequestDebug qDebug #else #define qInputContextIMFRequestDebug QT_NO_QDEBUG_MACRO #endif #if defined(QQNXINPUTCONTEXT_DEBUG) #define qInputContextDebug qDebug #else #define qInputContextDebug QT_NO_QDEBUG_MACRO #endif static QQnxInputContext *sInputContextInstance; static QColor sSelectedColor(0,0xb8,0,85); static const input_session_t *sSpellCheckSession = 0; static const input_session_t *sInputSession = 0; static bool isSessionOkay(input_session_t *ic) { return ic !=0 && sInputSession != 0 && ic->component_id == sInputSession->component_id; } enum ImfEventType { ImfCommitText, ImfDeleteSurroundingText, ImfFinishComposingText, ImfGetCursorPosition, ImfGetTextAfterCursor, ImfGetTextBeforeCursor, ImfSendEvent, ImfSetComposingRegion, ImfSetComposingText, ImfIsTextSelected, ImfIsAllTextSelected, }; struct SpellCheckInfo { SpellCheckInfo(void *_context, void (*_spellCheckDone)(void *, const QString &, const QList &)) : context(_context), spellCheckDone(_spellCheckDone) {} void *context; void (*spellCheckDone)(void *, const QString &, const QList &); }; Q_GLOBAL_STATIC(QQueue, sSpellCheckQueue) // IMF requests all arrive on IMF's own thread and have to be posted to the main thread to be processed. class QQnxImfRequest { public: QQnxImfRequest(input_session_t *_session, ImfEventType _type) : session(_session), type(_type) { } ~QQnxImfRequest() { } input_session_t *session; ImfEventType type; union { struct { int32_t n; int32_t flags; bool before; spannable_string_t *result; } gtac; // ic_get_text_before_cursor/ic_get_text_after_cursor struct { int32_t result; } gcp; // ic_get_cursor_position struct { int32_t start; int32_t end; int32_t result; } scr; // ic_set_composing_region struct { spannable_string_t* text; int32_t new_cursor_position; int32_t result; } sct; // ic_set_composing_text struct { spannable_string_t* text; int32_t new_cursor_position; int32_t result; } ct; // ic_commit_text struct { int32_t result; } fct; // ic_finish_composing_text struct { int32_t left_length; int32_t right_length; int32_t result; } dst; // ic_delete_surrounding_text struct { event_t *event; int32_t result; } sae; // ic_send_async_event/ic_send_event struct { int32_t *pIsSelected; int32_t result; } its; // ic_is_text_selected/ic_is_all_text_selected }; }; // Invoke an IMF initiated request synchronously on Qt's main thread. As describe below all // IMF requests are made from another thread but need to be executed on the main thread. static void executeIMFRequest(QQnxImfRequest *event) { QMetaObject::invokeMethod(sInputContextInstance, "processImfEvent", Qt::BlockingQueuedConnection, Q_ARG(QQnxImfRequest*, event)); } // The following functions (ic_*) are callback functions called by the input system to query information // about the text object that currently has focus or to make changes to it. All calls are made from the // input system's own thread. The pattern for each callback function is to copy its parameters into // a QQnxImfRequest structure and call executeIMFRequest to have it passed synchronously to Qt's main thread. // Any return values should be pre-initialised with suitable default values as in some cases // (e.g. a stale session) the call will return without having executed any request specific code. // // To make the correspondence more obvious, the names of these functions match those defined in the headers. // They're in an anonymous namespace to avoid compiler conflicts with external functions defined with the // same names. namespace { // See comment at beginning of namespace declaration for general information static int32_t ic_begin_batch_edit(input_session_t *ic) { Q_UNUSED(ic); // Ignore silently. return 0; } // End composition, committing the supplied text. // See comment at beginning of namespace declaration for general information static int32_t ic_commit_text(input_session_t *ic, spannable_string_t *text, int32_t new_cursor_position) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfCommitText); event.ct.text = text; event.ct.new_cursor_position = new_cursor_position; event.ct.result = -1; executeIMFRequest(&event); return event.ct.result; } // Delete left_length characters before and right_length characters after the cursor. // See comment at beginning of namespace declaration for general information static int32_t ic_delete_surrounding_text(input_session_t *ic, int32_t left_length, int32_t right_length) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfDeleteSurroundingText); event.dst.left_length = left_length; event.dst.right_length = right_length; event.dst.result = -1; executeIMFRequest(&event); return event.dst.result; } // See comment at beginning of namespace declaration for general information static int32_t ic_end_batch_edit(input_session_t *ic) { Q_UNUSED(ic); // Ignore silently. return 0; } // End composition, committing what's there. // See comment at beginning of namespace declaration for general information static int32_t ic_finish_composing_text(input_session_t *ic) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfFinishComposingText); event.fct.result = -1; executeIMFRequest(&event); return event.fct.result; } // Return the position of the cursor. // See comment at beginning of namespace declaration for general information static int32_t ic_get_cursor_position(input_session_t *ic) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfGetCursorPosition); event.gcp.result = -1; executeIMFRequest(&event); return event.gcp.result; } // Return the n characters after the cursor. // See comment at beginning of namespace declaration for general information static spannable_string_t *ic_get_text_after_cursor(input_session_t *ic, int32_t n, int32_t flags) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfGetTextAfterCursor); event.gtac.n = n; event.gtac.flags = flags; event.gtac.result = 0; executeIMFRequest(&event); return event.gtac.result; } // Return the n characters before the cursor. // See comment at beginning of namespace declaration for general information static spannable_string_t *ic_get_text_before_cursor(input_session_t *ic, int32_t n, int32_t flags) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfGetTextBeforeCursor); event.gtac.n = n; event.gtac.flags = flags; event.gtac.result = 0; executeIMFRequest(&event); return event.gtac.result; } // Process an event from IMF. Primarily used for reflecting back keyboard events. // See comment at beginning of namespace declaration for general information static int32_t ic_send_event(input_session_t *ic, event_t *event) { qInputContextIMFRequestDebug(); QQnxImfRequest imfEvent(ic, ImfSendEvent); imfEvent.sae.event = event; imfEvent.sae.result = -1; executeIMFRequest(&imfEvent); return imfEvent.sae.result; } // Same as ic_send_event. // See comment at beginning of namespace declaration for general information static int32_t ic_send_async_event(input_session_t *ic, event_t *event) { qInputContextIMFRequestDebug(); // There's no difference from our point of view between ic_send_event & ic_send_async_event QQnxImfRequest imfEvent(ic, ImfSendEvent); imfEvent.sae.event = event; imfEvent.sae.result = -1; executeIMFRequest(&imfEvent); return imfEvent.sae.result; } // Set the range of text between start and end as the composition range. // See comment at beginning of namespace declaration for general information static int32_t ic_set_composing_region(input_session_t *ic, int32_t start, int32_t end) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfSetComposingRegion); event.scr.start = start; event.scr.end = end; event.scr.result = -1; executeIMFRequest(&event); return event.scr.result; } // Update the composition range with the supplied text. This can be called when no composition // range is in effect in which case one is started at the current cursor position. // See comment at beginning of namespace declaration for general information static int32_t ic_set_composing_text(input_session_t *ic, spannable_string_t *text, int32_t new_cursor_position) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfSetComposingText); event.sct.text = text; event.sct.new_cursor_position = new_cursor_position; event.sct.result = -1; executeIMFRequest(&event); return event.sct.result; } // Indicate if any text is selected // See comment at beginning of namespace declaration for general information static int32_t ic_is_text_selected(input_session_t* ic, int32_t* pIsSelected) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfIsTextSelected); event.its.pIsSelected = pIsSelected; event.its.result = -1; executeIMFRequest(&event); return event.its.result; } // Indicate if all text is selected // See comment at beginning of namespace declaration for general information static int32_t ic_is_all_text_selected(input_session_t* ic, int32_t* pIsSelected) { qInputContextIMFRequestDebug(); QQnxImfRequest event(ic, ImfIsAllTextSelected); event.its.pIsSelected = pIsSelected; event.its.result = -1; executeIMFRequest(&event); return event.its.result; } // LCOV_EXCL_START - exclude from code coverage analysis // The following functions are defined in the IMF headers but are not currently called. // Not currently used static int32_t ic_perform_editor_action(input_session_t *ic, int32_t editor_action) { Q_UNUSED(ic); Q_UNUSED(editor_action); qCritical("ic_perform_editor_action not implemented"); return 0; } // Not currently used static int32_t ic_report_fullscreen_mode(input_session_t *ic, int32_t enabled) { Q_UNUSED(ic); Q_UNUSED(enabled); qCritical("ic_report_fullscreen_mode not implemented"); return 0; } // Not currently used static extracted_text_t *ic_get_extracted_text(input_session_t *ic, extracted_text_request_t *request, int32_t flags) { Q_UNUSED(ic); Q_UNUSED(request); Q_UNUSED(flags); qCritical("ic_get_extracted_text not implemented"); return 0; } // Not currently used static spannable_string_t *ic_get_selected_text(input_session_t *ic, int32_t flags) { Q_UNUSED(ic); Q_UNUSED(flags); qCritical("ic_get_selected_text not implemented"); return 0; } // Not currently used static int32_t ic_get_cursor_caps_mode(input_session_t *ic, int32_t req_modes) { Q_UNUSED(ic); Q_UNUSED(req_modes); qCritical("ic_get_cursor_caps_mode not implemented"); return 0; } // Not currently used static int32_t ic_clear_meta_key_states(input_session_t *ic, int32_t states) { Q_UNUSED(ic); Q_UNUSED(states); qCritical("ic_clear_meta_key_states not implemented"); return 0; } // Not currently used static int32_t ic_set_selection(input_session_t *ic, int32_t start, int32_t end) { Q_UNUSED(ic); Q_UNUSED(start); Q_UNUSED(end); qCritical("ic_set_selection not implemented"); return 0; } // End of un-hittable code // LCOV_EXCL_STOP static connection_interface_t ic_funcs = { ic_begin_batch_edit, ic_clear_meta_key_states, ic_commit_text, ic_delete_surrounding_text, ic_end_batch_edit, ic_finish_composing_text, ic_get_cursor_caps_mode, ic_get_cursor_position, ic_get_extracted_text, ic_get_selected_text, ic_get_text_after_cursor, ic_get_text_before_cursor, ic_perform_editor_action, ic_report_fullscreen_mode, 0, //ic_send_key_event ic_send_event, ic_send_async_event, ic_set_composing_region, ic_set_composing_text, ic_set_selection, 0, //ic_set_candidates, 0, //ic_get_cursor_offset, 0, //ic_get_selection, ic_is_text_selected, ic_is_all_text_selected, 0, //ic_get_max_cursor_offset_t }; } // namespace static void initEvent(event_t *pEvent, const input_session_t *pSession, EventType eventType, int eventId, int eventSize) { static int s_transactionId; // Make sure structure is squeaky clean since it's not clear just what is significant. memset(pEvent, 0, eventSize); pEvent->event_type = eventType; pEvent->event_id = eventId; pEvent->pid = getpid(); pEvent->component_id = pSession->component_id; pEvent->transaction_id = ++s_transactionId; } static spannable_string_t *toSpannableString(const QString &text) { qInputContextDebug() << text; spannable_string_t *pString = static_cast(malloc(sizeof(spannable_string_t))); pString->str = static_cast(malloc(sizeof(wchar_t) * text.length() + 1)); pString->length = text.toWCharArray(pString->str); pString->spans = 0; pString->spans_count = 0; pString->str[pString->length] = 0; return pString; } static const input_session_t *(*p_ictrl_open_session)(connection_interface_t *) = 0; static void (*p_ictrl_close_session)(input_session_t *) = 0; static int32_t (*p_ictrl_dispatch_event)(event_t*) = 0; static int32_t (*p_imf_client_init)() = 0; static void (*p_imf_client_disconnect)() = 0; static int32_t (*p_vkb_init_selection_service)() = 0; static int32_t (*p_ictrl_get_num_active_sessions)() = 0; static bool s_imfInitFailed = false; static bool imfAvailable() { static bool s_imfDisabled = getenv("DISABLE_IMF") != 0; static bool s_imfReady = false; if ( s_imfInitFailed || s_imfDisabled) return false; else if ( s_imfReady ) return true; if ( p_imf_client_init == 0 ) { void *handle = dlopen("libinput_client.so.1", 0); if (Q_UNLIKELY(!handle)) { qCritical("libinput_client.so.1 is not present - IMF services are disabled."); s_imfDisabled = true; return false; } p_imf_client_init = (int32_t (*)()) dlsym(handle, "imf_client_init"); p_imf_client_disconnect = (void (*)()) dlsym(handle, "imf_client_disconnect"); p_ictrl_open_session = (const input_session_t *(*)(connection_interface_t *))dlsym(handle, "ictrl_open_session"); p_ictrl_close_session = (void (*)(input_session_t *))dlsym(handle, "ictrl_close_session"); p_ictrl_dispatch_event = (int32_t (*)(event_t *))dlsym(handle, "ictrl_dispatch_event"); p_vkb_init_selection_service = (int32_t (*)())dlsym(handle, "vkb_init_selection_service"); p_ictrl_get_num_active_sessions = (int32_t (*)())dlsym(handle, "ictrl_get_num_active_sessions"); if (Q_UNLIKELY(!p_imf_client_init || !p_ictrl_open_session || !p_ictrl_dispatch_event)) { p_ictrl_open_session = 0; p_ictrl_dispatch_event = 0; s_imfDisabled = true; qCritical("libinput_client.so.1 did not contain the correct symbols, library mismatch? IMF services are disabled."); return false; } s_imfReady = true; } return s_imfReady; } QT_BEGIN_NAMESPACE QQnxInputContext::QQnxInputContext(QQnxIntegration *integration, QQnxAbstractVirtualKeyboard &keyboard) : QPlatformInputContext(), m_caretPosition(0), m_isComposing(false), m_isUpdatingText(false), m_inputPanelVisible(false), m_inputPanelLocale(QLocale::c()), m_focusObject(0), m_integration(integration), m_virtualKeyboard(keyboard) { qInputContextDebug(); if (!imfAvailable()) return; // Save a pointer to ourselves so we can execute calls from IMF through executeIMFRequest // In practice there will only ever be a single instance. Q_ASSERT(sInputContextInstance == 0); sInputContextInstance = this; if (Q_UNLIKELY(p_imf_client_init() != 0)) { s_imfInitFailed = true; qCritical("imf_client_init failed - IMF services will be unavailable"); } connect(&keyboard, SIGNAL(visibilityChanged(bool)), this, SLOT(keyboardVisibilityChanged(bool))); connect(&keyboard, SIGNAL(localeChanged(QLocale)), this, SLOT(keyboardLocaleChanged(QLocale))); keyboardVisibilityChanged(keyboard.isVisible()); keyboardLocaleChanged(keyboard.locale()); } QQnxInputContext::~QQnxInputContext() { qInputContextDebug(); Q_ASSERT(sInputContextInstance == this); sInputContextInstance = 0; if (!imfAvailable()) return; p_imf_client_disconnect(); } bool QQnxInputContext::isValid() const { return imfAvailable(); } void QQnxInputContext::processImfEvent(QQnxImfRequest *imfEvent) { // If input session is no longer current, just bail, imfEvent should already be set with the appropriate // return value. The only exception is spell check events since they're not associated with the // object with focus. if (imfEvent->type != ImfSendEvent || imfEvent->sae.event->event_type != EVENT_SPELL_CHECK) { if (!isSessionOkay(imfEvent->session)) return; } switch (imfEvent->type) { case ImfCommitText: imfEvent->ct.result = onCommitText(imfEvent->ct.text, imfEvent->ct.new_cursor_position); break; case ImfDeleteSurroundingText: imfEvent->dst.result = onDeleteSurroundingText(imfEvent->dst.left_length, imfEvent->dst.right_length); break; case ImfFinishComposingText: imfEvent->fct.result = onFinishComposingText(); break; case ImfGetCursorPosition: imfEvent->gcp.result = onGetCursorPosition(); break; case ImfGetTextAfterCursor: imfEvent->gtac.result = onGetTextAfterCursor(imfEvent->gtac.n, imfEvent->gtac.flags); break; case ImfGetTextBeforeCursor: imfEvent->gtac.result = onGetTextBeforeCursor(imfEvent->gtac.n, imfEvent->gtac.flags); break; case ImfSendEvent: imfEvent->sae.result = onSendEvent(imfEvent->sae.event); break; case ImfSetComposingRegion: imfEvent->scr.result = onSetComposingRegion(imfEvent->scr.start, imfEvent->scr.end); break; case ImfSetComposingText: imfEvent->sct.result = onSetComposingText(imfEvent->sct.text, imfEvent->sct.new_cursor_position); break; case ImfIsTextSelected: imfEvent->its.result = onIsTextSelected(imfEvent->its.pIsSelected); break; case ImfIsAllTextSelected: imfEvent->its.result = onIsAllTextSelected(imfEvent->its.pIsSelected); break; }; //switch } bool QQnxInputContext::filterEvent( const QEvent *event ) { qInputContextDebug() << event; switch (event->type()) { case QEvent::CloseSoftwareInputPanel: return dispatchCloseSoftwareInputPanel(); case QEvent::RequestSoftwareInputPanel: return dispatchRequestSoftwareInputPanel(); default: return false; } } QRectF QQnxInputContext::keyboardRect() const { QRect screenGeometry = m_integration->primaryDisplay()->geometry(); return QRectF(screenGeometry.x(), screenGeometry.height() - m_virtualKeyboard.height(), screenGeometry.width(), m_virtualKeyboard.height()); } void QQnxInputContext::reset() { qInputContextDebug(); endComposition(); } void QQnxInputContext::commit() { qInputContextDebug(); endComposition(); } void QQnxInputContext::update(Qt::InputMethodQueries queries) { qInputContextDebug() << queries; if (queries & Qt::ImCursorPosition) { int lastCaret = m_caretPosition; updateCursorPosition(); // If caret position has changed we need to inform IMF unless this is just due to our own action // such as committing text. if (hasSession() && !m_isUpdatingText && lastCaret != m_caretPosition) { caret_event_t caretEvent; initEvent(&caretEvent.event, sInputSession, EVENT_CARET, CARET_POS_CHANGED, sizeof(caretEvent)); caretEvent.old_pos = lastCaret; caretEvent.new_pos = m_caretPosition; qInputContextDebug("ictrl_dispatch_event caret changed %d %d", lastCaret, m_caretPosition); p_ictrl_dispatch_event(&caretEvent.event); } } } void QQnxInputContext::closeSession() { qInputContextDebug(); if (!imfAvailable()) return; if (sInputSession) { p_ictrl_close_session((input_session_t *)sInputSession); sInputSession = 0; } // These are likely already in the right state but this depends on the text control // having called reset or commit. So, just in case, set them to proper values. m_isComposing = false; m_composingText.clear(); } bool QQnxInputContext::openSession() { if (!imfAvailable()) return false; closeSession(); sInputSession = p_ictrl_open_session(&ic_funcs); qInputContextDebug(); return sInputSession != 0; } bool QQnxInputContext::hasSession() { return sInputSession != 0; } bool QQnxInputContext::hasSelectedText() { QObject *input = qGuiApp->focusObject(); if (!input) return false; QInputMethodQueryEvent query(Qt::ImCurrentSelection); QCoreApplication::sendEvent(input, &query); return !query.value(Qt::ImCurrentSelection).toString().isEmpty(); } bool QQnxInputContext::dispatchRequestSoftwareInputPanel() { qInputContextDebug() << "requesting keyboard" << m_inputPanelVisible; m_virtualKeyboard.showKeyboard(); return true; } bool QQnxInputContext::dispatchCloseSoftwareInputPanel() { qInputContextDebug() << "hiding keyboard" << m_inputPanelVisible; m_virtualKeyboard.hideKeyboard(); return true; } /** * IMF Event Dispatchers. */ bool QQnxInputContext::dispatchFocusGainEvent(int inputHints) { if (hasSession()) dispatchFocusLossEvent(); QObject *input = qGuiApp->focusObject(); if (!input || !openSession()) return false; // Set the last caret position to 0 since we don't really have one and we don't // want to have the old one. m_caretPosition = 0; QInputMethodQueryEvent query(Qt::ImHints); QCoreApplication::sendEvent(input, &query); focus_event_t focusEvent; initEvent(&focusEvent.event, sInputSession, EVENT_FOCUS, FOCUS_GAINED, sizeof(focusEvent)); focusEvent.style = DEFAULT_STYLE; if (inputHints & Qt::ImhNoPredictiveText) focusEvent.style |= NO_PREDICTION | NO_AUTO_CORRECTION; if (inputHints & Qt::ImhNoAutoUppercase) focusEvent.style |= NO_AUTO_TEXT; // Following styles are mutually exclusive if (inputHints & Qt::ImhHiddenText) { focusEvent.style |= IMF_PASSWORD_TYPE; } else if (inputHints & Qt::ImhDialableCharactersOnly) { focusEvent.style |= IMF_PHONE_TYPE; } else if (inputHints & Qt::ImhUrlCharactersOnly) { focusEvent.style |= IMF_URL_TYPE; } else if (inputHints & Qt::ImhEmailCharactersOnly) { focusEvent.style |= IMF_EMAIL_TYPE; } qInputContextDebug() << "ictrl_dispatch_event focus gain style:" << focusEvent.style; p_ictrl_dispatch_event((event_t *)&focusEvent); return true; } void QQnxInputContext::dispatchFocusLossEvent() { if (hasSession()) { qInputContextDebug("ictrl_dispatch_event focus lost"); focus_event_t focusEvent; initEvent(&focusEvent.event, sInputSession, EVENT_FOCUS, FOCUS_LOST, sizeof(focusEvent)); p_ictrl_dispatch_event((event_t *)&focusEvent); closeSession(); } } bool QQnxInputContext::handleKeyboardEvent(int flags, int sym, int mod, int scan, int cap, int sequenceId) { Q_UNUSED(scan); if (!hasSession()) return false; int key = (flags & KEY_SYM_VALID) ? sym : cap; bool navigationKey = false; switch (key) { case KEYCODE_RETURN: /* In a single line edit we should end composition because enter might be used by something. endComposition(); return false;*/ break; case KEYCODE_BACKSPACE: case KEYCODE_DELETE: // If there is a selection range, then we want a delete key to operate on that (by // deleting the contents of the select range) rather than operating on the composition // range. if (hasSelectedText()) return false; break; case KEYCODE_LEFT: key = NAVIGATE_LEFT; navigationKey = true; break; case KEYCODE_RIGHT: key = NAVIGATE_RIGHT; navigationKey = true; break; case KEYCODE_UP: key = NAVIGATE_UP; navigationKey = true; break; case KEYCODE_DOWN: key = NAVIGATE_DOWN; navigationKey = true; break; case KEYCODE_LEFT_CTRL: case KEYCODE_RIGHT_CTRL: case KEYCODE_MENU: case KEYCODE_LEFT_HYPER: case KEYCODE_RIGHT_HYPER: case KEYCODE_INSERT: case KEYCODE_HOME: case KEYCODE_PG_UP: case KEYCODE_END: case KEYCODE_PG_DOWN: // Don't send these key = 0; break; } // Pass the keys we don't know about on through if ( key == 0 ) return false; if (navigationKey) { // Even if we're forwarding up events, we can't do this for // navigation keys. if ( flags & KEY_DOWN ) { navigation_event_t navEvent; initEvent(&navEvent.event, sInputSession, EVENT_NAVIGATION, key, sizeof(navEvent)); navEvent.magnitude = 1; qInputContextDebug("ictrl_dispatch_even navigation %d", key); p_ictrl_dispatch_event(&navEvent.event); } } else { key_event_t keyEvent; initEvent(&keyEvent.event, sInputSession, EVENT_KEY, flags & KEY_DOWN ? IMF_KEY_DOWN : IMF_KEY_UP, sizeof(keyEvent)); keyEvent.key_code = cap; keyEvent.character = sym; keyEvent.meta_key_state = mod; keyEvent.sequence_id = sequenceId; p_ictrl_dispatch_event(&keyEvent.event); qInputContextDebug("ictrl_dispatch_even key %d", key); } return true; } void QQnxInputContext::updateCursorPosition() { QObject *input = qGuiApp->focusObject(); if (!input) return; QInputMethodQueryEvent query(Qt::ImCursorPosition); QCoreApplication::sendEvent(input, &query); m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); qInputContextDebug("%d", m_caretPosition); } void QQnxInputContext::endComposition() { if (!m_isComposing) return; finishComposingText(); if (hasSession()) { action_event_t actionEvent; initEvent(&actionEvent.event, sInputSession, EVENT_ACTION, ACTION_END_COMPOSITION, sizeof(actionEvent)); qInputContextDebug("ictrl_dispatch_even end composition"); p_ictrl_dispatch_event(&actionEvent.event); } } void QQnxInputContext::updateComposition(spannable_string_t *text, int32_t new_cursor_position) { QObject *input = qGuiApp->focusObject(); if (!input) return; if (new_cursor_position > 0) new_cursor_position += text->length - 1; m_composingText = QString::fromWCharArray(text->str, text->length); m_isComposing = true; qInputContextDebug() << m_composingText << new_cursor_position; QList attributes; attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, new_cursor_position, 1, QVariant())); for (unsigned int i = 0; i < text->spans_count; ++i) { QColor highlightColor; bool underline = false; if ((text->spans[i].attributes_mask & COMPOSED_TEXT_ATTRIB) != 0) underline = true; if ((text->spans[i].attributes_mask & ACTIVE_REGION_ATTRIB) != 0) { underline = true; highlightColor = m_highlightColor[ActiveRegion]; } else if ((text->spans[i].attributes_mask & AUTO_CORRECTION_ATTRIB) != 0) { highlightColor = m_highlightColor[AutoCorrected]; } else if ((text->spans[i].attributes_mask & REVERT_CORRECTION_ATTRIB) != 0) { highlightColor = m_highlightColor[Reverted]; } if (underline || highlightColor.isValid()) { QTextCharFormat format; if (underline) format.setFontUnderline(true); if (highlightColor.isValid()) format.setBackground(QBrush(highlightColor)); qInputContextDebug() << " attrib: " << underline << highlightColor << text->spans[i].start << text->spans[i].end; attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, text->spans[i].start, text->spans[i].end - text->spans[i].start + 1, QVariant(format))); } } QInputMethodEvent event(m_composingText, attributes); m_isUpdatingText = true; QCoreApplication::sendEvent(input, &event); m_isUpdatingText = false; updateCursorPosition(); } void QQnxInputContext::finishComposingText() { QObject *input = qGuiApp->focusObject(); if (input) { qInputContextDebug() << m_composingText; QInputMethodEvent event; event.setCommitString(m_composingText); m_isUpdatingText = true; QCoreApplication::sendEvent(input, &event); m_isUpdatingText = false; } m_composingText = QString(); m_isComposing = false; updateCursorPosition(); } // Return the index relative to a UTF-16 sequence of characters for a index that is relative to the // corresponding UTF-32 character string given a starting index in the UTF-16 string and a count // of the number of lead surrogates prior to that index. Updates the highSurrogateCount to reflect the // new surrogate characters encountered. static int adjustIndex(const QChar *text, int utf32Index, int utf16StartIndex, int *highSurrogateCount) { int utf16Index = utf32Index + *highSurrogateCount; while (utf16StartIndex < utf16Index) { if (text[utf16StartIndex].isHighSurrogate()) { ++utf16Index; ++*highSurrogateCount; } ++utf16StartIndex; } return utf16StartIndex; } int QQnxInputContext::handleSpellCheck(spell_check_event_t *event) { // These should never happen. if (sSpellCheckQueue->isEmpty() || event->event.event_id != NOTIFY_SP_CHECK_MISSPELLINGS) return -1; SpellCheckInfo callerInfo = sSpellCheckQueue->dequeue(); spannable_string_t* spellCheckData = *event->data; QString text = QString::fromWCharArray(spellCheckData->str, spellCheckData->length); // Generate the list of indices indicating misspelled words in the text. We use end + 1 // since it's more conventional to have the end index point just past the string. We also // can't use the indices directly since they are relative to UTF-32 encoded data and the // conversion to Qt's UTF-16 internal format might cause lengthening. QList indices; int adjustment = 0; int index = 0; for (unsigned int i = 0; i < spellCheckData->spans_count; ++i) { if (spellCheckData->spans[i].attributes_mask & MISSPELLED_WORD_ATTRIB) { index = adjustIndex(text.data(), spellCheckData->spans[i].start, index, &adjustment); indices.push_back(index); index = adjustIndex(text.data(), spellCheckData->spans[i].end + 1, index, &adjustment); indices.push_back(index); } } callerInfo.spellCheckDone(callerInfo.context, text, indices); return 0; } int32_t QQnxInputContext::processEvent(event_t *event) { int32_t result = -1; switch (event->event_type) { case EVENT_SPELL_CHECK: { qInputContextDebug("EVENT_SPELL_CHECK"); result = handleSpellCheck(reinterpret_cast(event)); break; } case EVENT_NAVIGATION: { qInputContextDebug("EVENT_NAVIGATION"); int key = event->event_id == NAVIGATE_UP ? KEYCODE_UP : event->event_id == NAVIGATE_DOWN ? KEYCODE_DOWN : event->event_id == NAVIGATE_LEFT ? KEYCODE_LEFT : event->event_id == NAVIGATE_RIGHT ? KEYCODE_RIGHT : 0; QQnxScreenEventHandler::injectKeyboardEvent(KEY_DOWN | KEY_CAP_VALID, key, 0, 0, 0); QQnxScreenEventHandler::injectKeyboardEvent(KEY_CAP_VALID, key, 0, 0, 0); result = 0; break; } case EVENT_KEY: { key_event_t *kevent = reinterpret_cast(event); int keySym = kevent->character != 0 ? kevent->character : kevent->key_code; int keyCap = kevent->key_code; int modifiers = 0; if (kevent->meta_key_state & META_SHIFT_ON) modifiers |= KEYMOD_SHIFT; int flags = KEY_SYM_VALID | KEY_CAP_VALID; if (event->event_id == IMF_KEY_DOWN) flags |= KEY_DOWN; qInputContextDebug("EVENT_KEY %d %d", flags, keySym); QQnxScreenEventHandler::injectKeyboardEvent(flags, keySym, modifiers, 0, keyCap); result = 0; break; } case EVENT_ACTION: // Don't care, indicates that IMF is done. break; case EVENT_CARET: case EVENT_NOTHING: case EVENT_FOCUS: case EVENT_USER_ACTION: case EVENT_STROKE: case EVENT_INVOKE_LATER: qCritical() << "Unsupported event type: " << event->event_type; break; default: qCritical() << "Unknown event type: " << event->event_type; } return result; } /** * IMF Event Handlers */ int32_t QQnxInputContext::onCommitText(spannable_string_t *text, int32_t new_cursor_position) { Q_UNUSED(new_cursor_position); updateComposition(text, new_cursor_position); finishComposingText(); return 0; } int32_t QQnxInputContext::onDeleteSurroundingText(int32_t left_length, int32_t right_length) { qInputContextDebug("L: %d R: %d", int(left_length), int(right_length)); QObject *input = qGuiApp->focusObject(); if (!input) return 0; int replacementLength = left_length + right_length; int replacementStart = -left_length; finishComposingText(); QInputMethodEvent event; event.setCommitString(QString(), replacementStart, replacementLength); m_isUpdatingText = true; QCoreApplication::sendEvent(input, &event); m_isUpdatingText = false; updateCursorPosition(); return 0; } int32_t QQnxInputContext::onFinishComposingText() { finishComposingText(); return 0; } int32_t QQnxInputContext::onGetCursorPosition() { qInputContextDebug(); QObject *input = qGuiApp->focusObject(); if (!input) return 0; updateCursorPosition(); return m_caretPosition; } spannable_string_t *QQnxInputContext::onGetTextAfterCursor(int32_t n, int32_t flags) { Q_UNUSED(flags); qInputContextDebug(); QObject *input = qGuiApp->focusObject(); if (!input) return toSpannableString(""); QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText); QCoreApplication::sendEvent(input, &query); QString text = query.value(Qt::ImSurroundingText).toString(); m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); return toSpannableString(text.mid(m_caretPosition, n)); } spannable_string_t *QQnxInputContext::onGetTextBeforeCursor(int32_t n, int32_t flags) { Q_UNUSED(flags); qInputContextDebug(); QObject *input = qGuiApp->focusObject(); if (!input) return toSpannableString(""); QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText); QCoreApplication::sendEvent(input, &query); QString text = query.value(Qt::ImSurroundingText).toString(); m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); if (n < m_caretPosition) return toSpannableString(text.mid(m_caretPosition - n, n)); else return toSpannableString(text.mid(0, m_caretPosition)); } int32_t QQnxInputContext::onSendEvent(event_t *event) { qInputContextDebug(); return processEvent(event); } int32_t QQnxInputContext::onSetComposingRegion(int32_t start, int32_t end) { QObject *input = qGuiApp->focusObject(); if (!input) return 0; QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText); QCoreApplication::sendEvent(input, &query); QString text = query.value(Qt::ImSurroundingText).toString(); m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); qInputContextDebug() << text; m_isUpdatingText = true; // Delete the current text. QInputMethodEvent deleteEvent; deleteEvent.setCommitString(QString(), start - m_caretPosition, end - start); QCoreApplication::sendEvent(input, &deleteEvent); m_composingText = text.mid(start, end - start); m_isComposing = true; QList attributes; QTextCharFormat format; format.setFontUnderline(true); attributes.push_back(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, m_composingText.length(), format)); QInputMethodEvent setTextEvent(m_composingText, attributes); QCoreApplication::sendEvent(input, &setTextEvent); m_isUpdatingText = false; return 0; } int32_t QQnxInputContext::onSetComposingText(spannable_string_t *text, int32_t new_cursor_position) { if (text->length > 0) { updateComposition(text, new_cursor_position); } else { // If the composing text is empty we can simply end composition, the visual effect is the same. // However, sometimes one wants to display hint text in an empty text field and for this to work // QQuickTextEdit.inputMethodComposing has to be false if the composition string is empty. m_composingText.clear(); finishComposingText(); } return 0; } int32_t QQnxInputContext::onIsTextSelected(int32_t* pIsSelected) { *pIsSelected = hasSelectedText(); qInputContextDebug() << *pIsSelected; return 0; } int32_t QQnxInputContext::onIsAllTextSelected(int32_t* pIsSelected) { QObject *input = qGuiApp->focusObject(); if (!input) return -1; QInputMethodQueryEvent query(Qt::ImCurrentSelection | Qt::ImSurroundingText); QCoreApplication::sendEvent(input, &query); *pIsSelected = query.value(Qt::ImSurroundingText).toString().length() == query.value(Qt::ImCurrentSelection).toString().length(); qInputContextDebug() << *pIsSelected; return 0; } void QQnxInputContext::showInputPanel() { qInputContextDebug(); dispatchRequestSoftwareInputPanel(); } void QQnxInputContext::hideInputPanel() { qInputContextDebug(); dispatchCloseSoftwareInputPanel(); } bool QQnxInputContext::isInputPanelVisible() const { return m_inputPanelVisible; } QLocale QQnxInputContext::locale() const { return m_inputPanelLocale; } void QQnxInputContext::keyboardVisibilityChanged(bool visible) { qInputContextDebug() << "visible=" << visible; if (m_inputPanelVisible != visible) { m_inputPanelVisible = visible; emitInputPanelVisibleChanged(); } } void QQnxInputContext::keyboardLocaleChanged(const QLocale &locale) { qInputContextDebug() << "locale=" << locale; if (m_inputPanelLocale != locale) { m_inputPanelLocale = locale; emitLocaleChanged(); } } void QQnxInputContext::setHighlightColor(int index, const QColor &color) { qInputContextDebug() << "setHighlightColor" << index << color << qGuiApp->focusObject(); if (!sInputContextInstance) return; // If the focus has changed, revert all colors to the default. if (sInputContextInstance->m_focusObject != qGuiApp->focusObject()) { QColor invalidColor; sInputContextInstance->m_highlightColor[ActiveRegion] = sSelectedColor; sInputContextInstance->m_highlightColor[AutoCorrected] = invalidColor; sInputContextInstance->m_highlightColor[Reverted] = invalidColor; sInputContextInstance->m_focusObject = qGuiApp->focusObject(); } if (index >= 0 && index <= Reverted) sInputContextInstance->m_highlightColor[index] = color; } void QQnxInputContext::setFocusObject(QObject *object) { qInputContextDebug() << "input item=" << object; // Ensure the colors are reset if we've a change in focus object setHighlightColor(-1, QColor()); if (!inputMethodAccepted()) { if (m_inputPanelVisible) hideInputPanel(); if (hasSession()) dispatchFocusLossEvent(); } else { QInputMethodQueryEvent query(Qt::ImHints | Qt::ImEnterKeyType); QCoreApplication::sendEvent(object, &query); int inputHints = query.value(Qt::ImHints).toInt(); Qt::EnterKeyType qtEnterKeyType = Qt::EnterKeyType(query.value(Qt::ImEnterKeyType).toInt()); dispatchFocusGainEvent(inputHints); m_virtualKeyboard.setInputHints(inputHints); m_virtualKeyboard.setEnterKeyType( QQnxAbstractVirtualKeyboard::qtEnterKeyTypeToQnx(qtEnterKeyType) ); if (!m_inputPanelVisible) showInputPanel(); } } bool QQnxInputContext::checkSpelling(const QString &text, void *context, void (*spellCheckDone)(void *context, const QString &text, const QList &indices)) { qInputContextDebug() << "text" << text; if (!imfAvailable()) return false; if (!sSpellCheckSession) sSpellCheckSession = p_ictrl_open_session(&ic_funcs); action_event_t spellEvent; initEvent(&spellEvent.event, sSpellCheckSession, EVENT_ACTION, ACTION_CHECK_MISSPELLINGS, sizeof(spellEvent)); int len = text.length(); spellEvent.event_data = alloca(sizeof(wchar_t) * (len + 1)); spellEvent.length_data = text.toWCharArray(static_cast(spellEvent.event_data)) * sizeof(wchar_t); int rc = p_ictrl_dispatch_event(reinterpret_cast(&spellEvent)); if (rc == 0) { sSpellCheckQueue->enqueue(SpellCheckInfo(context, spellCheckDone)); return true; } return false; } QT_END_NAMESPACE