| 1 | // Copyright (C) 2020 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| 3 | |
| 4 | #include "qwaylandqttextinputmethod.h" |
| 5 | #include "qwaylandqttextinputmethod_p.h" |
| 6 | |
| 7 | #include <QtGui/qevent.h> |
| 8 | #include <QtGui/qguiapplication.h> |
| 9 | #include <QtGui/qinputmethod.h> |
| 10 | #include <QtGui/qcolor.h> |
| 11 | #include <QtGui/qtextformat.h> |
| 12 | |
| 13 | #include <QtWaylandCompositor/qwaylandcompositor.h> |
| 14 | #include <QtWaylandCompositor/qwaylandsurface.h> |
| 15 | |
| 16 | QT_BEGIN_NAMESPACE |
| 17 | |
| 18 | QWaylandQtTextInputMethodPrivate::QWaylandQtTextInputMethodPrivate(QWaylandCompositor *c) |
| 19 | : compositor(c) |
| 20 | { |
| 21 | } |
| 22 | |
| 23 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_enable(Resource *resource, struct ::wl_resource *surface) |
| 24 | { |
| 25 | Q_Q(QWaylandQtTextInputMethod); |
| 26 | if (this->resource == resource) { |
| 27 | QWaylandSurface *waylandSurface = QWaylandSurface::fromResource(resource: surface); |
| 28 | if (surface != nullptr) { |
| 29 | enabledSurfaces[resource] = waylandSurface; |
| 30 | emit q->surfaceEnabled(surface: waylandSurface); |
| 31 | } |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_disable(Resource *resource, struct ::wl_resource *surface) |
| 36 | { |
| 37 | Q_Q(QWaylandQtTextInputMethod); |
| 38 | if (this->resource == resource) { |
| 39 | QWaylandSurface *waylandSurface = QWaylandSurface::fromResource(resource: surface); |
| 40 | QWaylandSurface *enabledSurface = enabledSurfaces.take(resource); |
| 41 | |
| 42 | if (Q_UNLIKELY(enabledSurface != waylandSurface)) |
| 43 | qCWarning(qLcWaylandCompositorInputMethods) << "Disabled surface does not match the one currently enabled" ; |
| 44 | |
| 45 | emit q->surfaceEnabled(surface: waylandSurface); |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_destroy(Resource *resource) |
| 50 | { |
| 51 | if (this->resource == resource) |
| 52 | wl_resource_destroy(resource->handle); |
| 53 | } |
| 54 | |
| 55 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_reset(Resource *resource) |
| 56 | { |
| 57 | if (this->resource == resource) |
| 58 | qApp->inputMethod()->reset(); |
| 59 | } |
| 60 | |
| 61 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_commit(Resource *resource) |
| 62 | { |
| 63 | if (this->resource == resource) |
| 64 | qApp->inputMethod()->commit(); |
| 65 | } |
| 66 | |
| 67 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_show_input_panel(Resource *resource) |
| 68 | { |
| 69 | if (this->resource == resource) |
| 70 | qApp->inputMethod()->show(); |
| 71 | } |
| 72 | |
| 73 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_hide_input_panel(Resource *resource) |
| 74 | { |
| 75 | if (this->resource == resource) |
| 76 | qApp->inputMethod()->hide(); |
| 77 | } |
| 78 | |
| 79 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_invoke_action(Resource *resource, int32_t type, int32_t cursorPosition) |
| 80 | { |
| 81 | if (this->resource == resource) |
| 82 | qApp->inputMethod()->invokeAction(a: QInputMethod::Action(type), cursorPosition); |
| 83 | } |
| 84 | |
| 85 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) |
| 86 | { |
| 87 | if (this->resource == resource) |
| 88 | cursorRectangle = QRect(x, y, width, height); |
| 89 | } |
| 90 | |
| 91 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_start_update(Resource *resource, int32_t queries) |
| 92 | { |
| 93 | if (this->resource == resource) |
| 94 | updatingQueries = Qt::InputMethodQueries(queries); |
| 95 | } |
| 96 | |
| 97 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_hints(Resource *resource, int32_t hints) |
| 98 | { |
| 99 | if (this->resource == resource) |
| 100 | this->hints = Qt::InputMethodHints(hints); |
| 101 | } |
| 102 | |
| 103 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_anchor_position(Resource *resource, int32_t anchorPosition) |
| 104 | { |
| 105 | if (this->resource == resource) |
| 106 | this->anchorPosition = anchorPosition; |
| 107 | } |
| 108 | |
| 109 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_cursor_position(Resource *resource, int32_t cursorPosition) |
| 110 | { |
| 111 | if (this->resource == resource) |
| 112 | this->cursorPosition = cursorPosition; |
| 113 | } |
| 114 | |
| 115 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_surrounding_text(Resource *resource, const QString &surroundingText, int32_t surroundingTextOffset) |
| 116 | { |
| 117 | if (this->resource == resource) { |
| 118 | this->surroundingText = surroundingText; |
| 119 | this->surroundingTextOffset = surroundingTextOffset; |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_absolute_position(Resource *resource, int32_t absolutePosition) |
| 124 | { |
| 125 | if (this->resource == resource) |
| 126 | this->absolutePosition = absolutePosition; |
| 127 | } |
| 128 | |
| 129 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_preferred_language(Resource *resource, const QString &preferredLanguage) |
| 130 | { |
| 131 | if (this->resource == resource) |
| 132 | this->preferredLanguage = preferredLanguage; |
| 133 | } |
| 134 | |
| 135 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_end_update(Resource *resource) |
| 136 | { |
| 137 | Q_Q(QWaylandQtTextInputMethod); |
| 138 | if (this->resource == resource && updatingQueries != 0) { |
| 139 | Qt::InputMethodQueries queries = updatingQueries; |
| 140 | updatingQueries = Qt::InputMethodQueries(); |
| 141 | emit q->updateInputMethod(queries); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_acknowledge_input_method(Resource *resource) |
| 146 | { |
| 147 | if (this->resource == resource) |
| 148 | waitingForSync = false; |
| 149 | } |
| 150 | |
| 151 | QWaylandQtTextInputMethod::QWaylandQtTextInputMethod(QWaylandObject *container, QWaylandCompositor *compositor) |
| 152 | : QWaylandCompositorExtensionTemplate(container, *new QWaylandQtTextInputMethodPrivate(compositor)) |
| 153 | { |
| 154 | connect(sender: &d_func()->focusDestroyListener, signal: &QWaylandDestroyListener::fired, |
| 155 | context: this, slot: &QWaylandQtTextInputMethod::focusSurfaceDestroyed); |
| 156 | |
| 157 | connect(qGuiApp->inputMethod(), signal: &QInputMethod::visibleChanged, context: this, slot: &QWaylandQtTextInputMethod::sendVisibleChanged); |
| 158 | connect(qGuiApp->inputMethod(), signal: &QInputMethod::keyboardRectangleChanged, context: this, slot: &QWaylandQtTextInputMethod::sendKeyboardRectangleChanged); |
| 159 | connect(qGuiApp->inputMethod(), signal: &QInputMethod::inputDirectionChanged, context: this, slot: &QWaylandQtTextInputMethod::sendInputDirectionChanged); |
| 160 | connect(qGuiApp->inputMethod(), signal: &QInputMethod::localeChanged, context: this, slot: &QWaylandQtTextInputMethod::sendLocaleChanged); |
| 161 | } |
| 162 | |
| 163 | |
| 164 | QWaylandQtTextInputMethod::~QWaylandQtTextInputMethod() |
| 165 | { |
| 166 | } |
| 167 | |
| 168 | void QWaylandQtTextInputMethod::focusSurfaceDestroyed() |
| 169 | { |
| 170 | Q_D(QWaylandQtTextInputMethod); |
| 171 | d->focusDestroyListener.reset(); |
| 172 | d->waitingForSync = false; |
| 173 | d->resource = nullptr; |
| 174 | d->focusedSurface = nullptr; |
| 175 | } |
| 176 | |
| 177 | QWaylandSurface *QWaylandQtTextInputMethod::focusedSurface() const |
| 178 | { |
| 179 | Q_D(const QWaylandQtTextInputMethod); |
| 180 | return d->focusedSurface; |
| 181 | } |
| 182 | |
| 183 | QVariant QWaylandQtTextInputMethod::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const |
| 184 | { |
| 185 | Q_D(const QWaylandQtTextInputMethod); |
| 186 | switch (property) { |
| 187 | case Qt::ImHints: |
| 188 | return int(d->hints); |
| 189 | case Qt::ImCursorRectangle: |
| 190 | return d->cursorRectangle; |
| 191 | case Qt::ImCursorPosition: |
| 192 | return d->cursorPosition; |
| 193 | case Qt::ImSurroundingText: |
| 194 | return d->surroundingText; |
| 195 | case Qt::ImAbsolutePosition: |
| 196 | return d->absolutePosition; |
| 197 | case Qt::ImCurrentSelection: |
| 198 | return d->surroundingText.mid(position: qMin(a: d->cursorPosition, b: d->anchorPosition), |
| 199 | n: qAbs(t: d->anchorPosition - d->cursorPosition)); |
| 200 | case Qt::ImAnchorPosition: |
| 201 | return d->anchorPosition; |
| 202 | case Qt::ImTextAfterCursor: |
| 203 | if (argument.isValid()) |
| 204 | return d->surroundingText.mid(position: d->cursorPosition, n: argument.toInt()); |
| 205 | return d->surroundingText.mid(position: d->cursorPosition); |
| 206 | case Qt::ImTextBeforeCursor: |
| 207 | if (argument.isValid()) |
| 208 | return d->surroundingText.left(n: d->cursorPosition).right(n: argument.toInt()); |
| 209 | return d->surroundingText.left(n: d->cursorPosition); |
| 210 | case Qt::ImPreferredLanguage: |
| 211 | return d->preferredLanguage; |
| 212 | |
| 213 | default: |
| 214 | return QVariant(); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | void QWaylandQtTextInputMethod::sendKeyEvent(QKeyEvent *event) |
| 219 | { |
| 220 | Q_D(QWaylandQtTextInputMethod); |
| 221 | if (d->resource == nullptr || d->resource->handle == nullptr) |
| 222 | return; |
| 223 | |
| 224 | d->send_key(d->resource->handle, |
| 225 | int(event->type()), |
| 226 | event->key(), |
| 227 | event->modifiers(), |
| 228 | event->isAutoRepeat(), |
| 229 | event->count(), |
| 230 | event->nativeScanCode(), |
| 231 | event->nativeVirtualKey(), |
| 232 | event->nativeModifiers(), |
| 233 | event->text()); |
| 234 | } |
| 235 | |
| 236 | void QWaylandQtTextInputMethod::sendInputMethodEvent(QInputMethodEvent *event) |
| 237 | { |
| 238 | Q_D(QWaylandQtTextInputMethod); |
| 239 | if (d->resource == nullptr || d->resource->handle == nullptr || d->compositor == nullptr) |
| 240 | return; |
| 241 | |
| 242 | if (d->updatingQueries != 0) { |
| 243 | qCWarning(qLcWaylandCompositorInputMethods) << "Input method event sent while client is updating. Ignored." ; |
| 244 | return; |
| 245 | } |
| 246 | |
| 247 | Q_ASSERT(!d->waitingForSync); |
| 248 | |
| 249 | QString oldSurroundText = d->surroundingText; |
| 250 | int oldCursorPosition = d->cursorPosition; |
| 251 | int oldAnchorPosition = d->anchorPosition; |
| 252 | int oldAbsolutePosition = d->absolutePosition; |
| 253 | QRect oldCursorRectangle = d->cursorRectangle; |
| 254 | QString oldPreferredLanguage = d->preferredLanguage; |
| 255 | Qt::InputMethodHints oldHints = d->hints; |
| 256 | |
| 257 | uint serial = d->compositor->nextSerial(); // ### Not needed if we block on this? |
| 258 | d->send_start_input_method_event(d->resource->handle, serial, d->surroundingTextOffset); |
| 259 | for (const QInputMethodEvent::Attribute &attribute : event->attributes()) { |
| 260 | switch (attribute.type) { |
| 261 | case QInputMethodEvent::TextFormat: |
| 262 | { |
| 263 | auto properties = attribute.value.value<QTextFormat>().properties(); |
| 264 | if (properties.size() != 2 || properties.firstKey() != QTextFormat::FontUnderline || properties.lastKey() != QTextFormat::TextUnderlineStyle) { |
| 265 | qCWarning(qLcWaylandCompositorInputMethods()) << "Only underline text formats currently supported" ; |
| 266 | } |
| 267 | |
| 268 | d->send_input_method_event_attribute(d->resource->handle, |
| 269 | serial, |
| 270 | attribute.type, |
| 271 | attribute.start, |
| 272 | attribute.length, |
| 273 | QString()); |
| 274 | break; |
| 275 | } |
| 276 | case QInputMethodEvent::Cursor: |
| 277 | d->cursorPosition = attribute.start; |
| 278 | d->send_input_method_event_attribute(d->resource->handle, |
| 279 | serial, |
| 280 | attribute.type, |
| 281 | attribute.start, |
| 282 | attribute.length, |
| 283 | attribute.value.typeId() == QMetaType::QColor ? attribute.value.value<QColor>().name() : QString()); |
| 284 | break; |
| 285 | case QInputMethodEvent::Language: // ### What is the type of value? Is it string? |
| 286 | Q_FALLTHROUGH(); |
| 287 | case QInputMethodEvent::Ruby: |
| 288 | d->send_input_method_event_attribute(d->resource->handle, |
| 289 | serial, |
| 290 | attribute.type, |
| 291 | attribute.start, |
| 292 | attribute.length, |
| 293 | attribute.value.toString()); |
| 294 | break; |
| 295 | case QInputMethodEvent::Selection: |
| 296 | d->send_input_method_event_attribute(d->resource->handle, |
| 297 | serial, |
| 298 | attribute.type, |
| 299 | attribute.start, |
| 300 | attribute.length, |
| 301 | QString()); |
| 302 | break; |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | d->waitingForSync = true; |
| 307 | d->send_end_input_method_event(d->resource->handle, |
| 308 | serial, |
| 309 | event->commitString(), |
| 310 | event->preeditString(), |
| 311 | event->replacementStart(), |
| 312 | event->replacementLength()); |
| 313 | |
| 314 | while (d->waitingForSync) |
| 315 | d->compositor->processWaylandEvents(); |
| 316 | |
| 317 | Qt::InputMethodQueries queries; |
| 318 | if (d->surroundingText != oldSurroundText) |
| 319 | queries |= Qt::ImSurroundingText; |
| 320 | if (d->cursorPosition != oldCursorPosition) |
| 321 | queries |= Qt::ImCursorPosition; |
| 322 | if (d->anchorPosition != oldAnchorPosition) |
| 323 | queries |= Qt::ImAnchorPosition; |
| 324 | if (d->absolutePosition != oldAbsolutePosition) |
| 325 | queries |= Qt::ImAbsolutePosition; |
| 326 | if (d->cursorRectangle != oldCursorRectangle) |
| 327 | queries |= Qt::ImCursorRectangle; |
| 328 | if (d->preferredLanguage != oldPreferredLanguage) |
| 329 | queries |= Qt::ImPreferredLanguage; |
| 330 | if (d->hints != oldHints) |
| 331 | queries |= Qt::ImHints; |
| 332 | if (queries != 0) |
| 333 | emit updateInputMethod(queries); |
| 334 | } |
| 335 | |
| 336 | bool QWaylandQtTextInputMethod::isSurfaceEnabled(QWaylandSurface *surface) const |
| 337 | { |
| 338 | Q_D(const QWaylandQtTextInputMethod); |
| 339 | return d->enabledSurfaces.values().contains(surface); |
| 340 | } |
| 341 | |
| 342 | void QWaylandQtTextInputMethod::setFocus(QWaylandSurface *surface) |
| 343 | { |
| 344 | Q_D(QWaylandQtTextInputMethod); |
| 345 | |
| 346 | QWaylandQtTextInputMethodPrivate::Resource *resource = surface != nullptr ? d->resourceMap().value(surface->waylandClient()) : nullptr; |
| 347 | if (d->resource == resource && d->focusedSurface == surface) // same client, same surface |
| 348 | return; |
| 349 | |
| 350 | if (d->resource != nullptr && d->focusedSurface != nullptr) { |
| 351 | d->send_leave(d->resource->handle, d->focusedSurface->resource()); |
| 352 | d->focusDestroyListener.reset(); |
| 353 | } |
| 354 | |
| 355 | d->resource = resource; |
| 356 | d->focusedSurface = surface; |
| 357 | |
| 358 | if (d->resource != nullptr && d->focusedSurface != nullptr) { |
| 359 | d->surroundingText.clear(); |
| 360 | d->cursorPosition = 0; |
| 361 | d->anchorPosition = 0; |
| 362 | d->absolutePosition = 0; |
| 363 | d->cursorRectangle = QRect(); |
| 364 | d->preferredLanguage.clear(); |
| 365 | d->hints = Qt::InputMethodHints(); |
| 366 | d->send_enter(d->resource->handle, d->focusedSurface->resource()); |
| 367 | sendInputDirectionChanged(); |
| 368 | sendLocaleChanged(); |
| 369 | sendInputDirectionChanged(); |
| 370 | d->focusDestroyListener.listenForDestruction(resource: surface->resource()); |
| 371 | if (d->inputPanelVisible && d->enabledSurfaces.values().contains(surface)) |
| 372 | qGuiApp->inputMethod()->show(); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | void QWaylandQtTextInputMethod::sendLocaleChanged() |
| 377 | { |
| 378 | Q_D(QWaylandQtTextInputMethod); |
| 379 | if (d->resource == nullptr || d->resource->handle == nullptr) |
| 380 | return; |
| 381 | |
| 382 | d->send_locale_changed(d->resource->handle, qGuiApp->inputMethod()->locale().bcp47Name()); |
| 383 | } |
| 384 | |
| 385 | void QWaylandQtTextInputMethod::sendInputDirectionChanged() |
| 386 | { |
| 387 | Q_D(QWaylandQtTextInputMethod); |
| 388 | if (d->resource == nullptr || d->resource->handle == nullptr) |
| 389 | return; |
| 390 | |
| 391 | d->send_input_direction_changed(d->resource->handle, int(qGuiApp->inputMethod()->inputDirection())); |
| 392 | } |
| 393 | |
| 394 | void QWaylandQtTextInputMethod::sendKeyboardRectangleChanged() |
| 395 | { |
| 396 | Q_D(QWaylandQtTextInputMethod); |
| 397 | if (d->resource == nullptr || d->resource->handle == nullptr) |
| 398 | return; |
| 399 | |
| 400 | QRectF keyboardRectangle = qGuiApp->inputMethod()->keyboardRectangle(); |
| 401 | d->send_keyboard_rectangle_changed(d->resource->handle, |
| 402 | wl_fixed_from_double(keyboardRectangle.x()), |
| 403 | wl_fixed_from_double(keyboardRectangle.y()), |
| 404 | wl_fixed_from_double(keyboardRectangle.width()), |
| 405 | wl_fixed_from_double(keyboardRectangle.height())); |
| 406 | } |
| 407 | |
| 408 | void QWaylandQtTextInputMethod::sendVisibleChanged() |
| 409 | { |
| 410 | Q_D(QWaylandQtTextInputMethod); |
| 411 | if (d->resource == nullptr || d->resource->handle == nullptr) |
| 412 | return; |
| 413 | |
| 414 | d->send_visible_changed(d->resource->handle, int(qGuiApp->inputMethod()->isVisible())); |
| 415 | } |
| 416 | |
| 417 | void QWaylandQtTextInputMethod::add(::wl_client *client, uint32_t id, int version) |
| 418 | { |
| 419 | Q_D(QWaylandQtTextInputMethod); |
| 420 | d->add(client, id, version); |
| 421 | } |
| 422 | |
| 423 | const struct wl_interface *QWaylandQtTextInputMethod::interface() |
| 424 | { |
| 425 | return QWaylandQtTextInputMethodPrivate::interface(); |
| 426 | } |
| 427 | |
| 428 | QByteArray QWaylandQtTextInputMethod::interfaceName() |
| 429 | { |
| 430 | return QWaylandQtTextInputMethodPrivate::interfaceName(); |
| 431 | } |
| 432 | |
| 433 | QT_END_NAMESPACE |
| 434 | |
| 435 | #include "moc_qwaylandqttextinputmethod.cpp" |
| 436 | |