File indexing completed on 2024-05-19 16:35:31
0001 /* 0002 SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org> 0003 SPDX-FileCopyrightText: 2018 Roman Glig <subdiff@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "display.h" 0009 #include "seat_interface.h" 0010 #include "surface_interface_p.h" 0011 #include "textinput_v3_interface_p.h" 0012 0013 namespace KWaylandServer 0014 { 0015 namespace 0016 { 0017 const quint32 s_version = 1; 0018 0019 TextInputContentHints convertContentHint(uint32_t hint) 0020 { 0021 const auto hints = QtWaylandServer::zwp_text_input_v3::content_hint(hint); 0022 TextInputContentHints ret = TextInputContentHint::None; 0023 0024 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_completion) { 0025 ret |= TextInputContentHint::AutoCompletion; 0026 } 0027 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_auto_capitalization) { 0028 ret |= TextInputContentHint::AutoCapitalization; 0029 } 0030 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_lowercase) { 0031 ret |= TextInputContentHint::LowerCase; 0032 } 0033 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_uppercase) { 0034 ret |= TextInputContentHint::UpperCase; 0035 } 0036 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_titlecase) { 0037 ret |= TextInputContentHint::TitleCase; 0038 } 0039 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_hidden_text) { 0040 ret |= TextInputContentHint::HiddenText; 0041 } 0042 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_sensitive_data) { 0043 ret |= TextInputContentHint::SensitiveData; 0044 } 0045 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_latin) { 0046 ret |= TextInputContentHint::Latin; 0047 } 0048 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_multiline) { 0049 ret |= TextInputContentHint::MultiLine; 0050 } 0051 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_spellcheck) { 0052 ret |= TextInputContentHint::AutoCorrection; 0053 } 0054 return ret; 0055 } 0056 0057 TextInputContentPurpose convertContentPurpose(uint32_t purpose) 0058 { 0059 const auto wlPurpose = QtWaylandServer::zwp_text_input_v3::content_purpose(purpose); 0060 0061 switch (wlPurpose) { 0062 case QtWaylandServer::zwp_text_input_v3::content_purpose_alpha: 0063 return TextInputContentPurpose::Alpha; 0064 case QtWaylandServer::zwp_text_input_v3::content_purpose_digits: 0065 return TextInputContentPurpose::Digits; 0066 case QtWaylandServer::zwp_text_input_v3::content_purpose_number: 0067 return TextInputContentPurpose::Number; 0068 case QtWaylandServer::zwp_text_input_v3::content_purpose_phone: 0069 return TextInputContentPurpose::Phone; 0070 case QtWaylandServer::zwp_text_input_v3::content_purpose_url: 0071 return TextInputContentPurpose::Url; 0072 case QtWaylandServer::zwp_text_input_v3::content_purpose_email: 0073 return TextInputContentPurpose::Email; 0074 case QtWaylandServer::zwp_text_input_v3::content_purpose_name: 0075 return TextInputContentPurpose::Name; 0076 case QtWaylandServer::zwp_text_input_v3::content_purpose_password: 0077 return TextInputContentPurpose::Password; 0078 case QtWaylandServer::zwp_text_input_v3::content_purpose_pin: 0079 return TextInputContentPurpose::Pin; 0080 case QtWaylandServer::zwp_text_input_v3::content_purpose_date: 0081 return TextInputContentPurpose::Date; 0082 case QtWaylandServer::zwp_text_input_v3::content_purpose_time: 0083 return TextInputContentPurpose::Time; 0084 case QtWaylandServer::zwp_text_input_v3::content_purpose_datetime: 0085 return TextInputContentPurpose::DateTime; 0086 case QtWaylandServer::zwp_text_input_v3::content_purpose_terminal: 0087 return TextInputContentPurpose::Terminal; 0088 case QtWaylandServer::zwp_text_input_v3::content_purpose_normal: 0089 return TextInputContentPurpose::Normal; 0090 default: 0091 return TextInputContentPurpose::Normal; 0092 } 0093 } 0094 0095 TextInputChangeCause convertChangeCause(uint32_t cause) 0096 { 0097 const auto wlCause = QtWaylandServer::zwp_text_input_v3::change_cause(cause); 0098 switch (wlCause) { 0099 case QtWaylandServer::zwp_text_input_v3::change_cause_input_method: 0100 return TextInputChangeCause::InputMethod; 0101 case QtWaylandServer::zwp_text_input_v3::change_cause_other: 0102 default: 0103 return TextInputChangeCause::Other; 0104 } 0105 } 0106 0107 class EnabledEmitter 0108 { 0109 public: 0110 EnabledEmitter(TextInputV3Interface *q) 0111 : q(q) 0112 , m_wasEnabled(q->isEnabled()) 0113 { 0114 } 0115 ~EnabledEmitter() 0116 { 0117 if (m_wasEnabled != q->isEnabled()) { 0118 Q_EMIT q->enabledChanged(); 0119 } 0120 } 0121 0122 private: 0123 TextInputV3Interface *q; 0124 const bool m_wasEnabled; 0125 }; 0126 0127 } 0128 0129 TextInputManagerV3InterfacePrivate::TextInputManagerV3InterfacePrivate(TextInputManagerV3Interface *_q, Display *display) 0130 : QtWaylandServer::zwp_text_input_manager_v3(*display, s_version) 0131 , q(_q) 0132 { 0133 } 0134 0135 void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_destroy(Resource *resource) 0136 { 0137 wl_resource_destroy(resource->handle); 0138 } 0139 0140 void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_get_text_input(Resource *resource, uint32_t id, wl_resource *seat) 0141 { 0142 SeatInterface *s = SeatInterface::get(seat); 0143 if (!s) { 0144 wl_resource_post_error(resource->handle, 0, "Invalid seat"); 0145 return; 0146 } 0147 TextInputV3InterfacePrivate *textInputPrivate = TextInputV3InterfacePrivate::get(s->textInputV3()); 0148 auto *textInputResource = textInputPrivate->add(resource->client(), id, resource->version()); 0149 // Send enter to this new text input object if the surface is already focused. 0150 if (textInputPrivate->surface && textInputPrivate->surface->client()->client() == resource->client()) { 0151 textInputPrivate->send_enter(textInputResource->handle, textInputPrivate->surface->resource()); 0152 } 0153 } 0154 0155 TextInputManagerV3Interface::TextInputManagerV3Interface(Display *display, QObject *parent) 0156 : QObject(parent) 0157 , d(new TextInputManagerV3InterfacePrivate(this, display)) 0158 { 0159 } 0160 0161 TextInputManagerV3Interface::~TextInputManagerV3Interface() = default; 0162 0163 TextInputV3InterfacePrivate::TextInputV3InterfacePrivate(SeatInterface *seat, TextInputV3Interface *_q) 0164 : seat(seat) 0165 , q(_q) 0166 { 0167 } 0168 0169 void TextInputV3InterfacePrivate::zwp_text_input_v3_bind_resource(Resource *resource) 0170 { 0171 // we initialize the serial for the resource to be 0 0172 serialHash.insert(resource, 0); 0173 enabled.insert(resource, false); 0174 } 0175 0176 void TextInputV3InterfacePrivate::zwp_text_input_v3_destroy(Resource *resource) 0177 { 0178 // drop resource from the serial hash 0179 serialHash.remove(resource); 0180 enabled.remove(resource); 0181 } 0182 0183 void TextInputV3InterfacePrivate::sendEnter(SurfaceInterface *newSurface) 0184 { 0185 EnabledEmitter emitter(q); 0186 // It should be always synchronized with SeatInterface::focusedTextInputSurface. 0187 Q_ASSERT(!surface && newSurface); 0188 surface = newSurface; 0189 const auto clientResources = textInputsForClient(newSurface->client()); 0190 for (auto resource : clientResources) { 0191 send_enter(resource->handle, newSurface->resource()); 0192 } 0193 } 0194 0195 void TextInputV3InterfacePrivate::sendLeave(SurfaceInterface *leavingSurface) 0196 { 0197 EnabledEmitter emitter(q); 0198 // It should be always synchronized with SeatInterface::focusedTextInputSurface. 0199 Q_ASSERT(leavingSurface && surface == leavingSurface); 0200 surface.clear(); 0201 const auto clientResources = textInputsForClient(leavingSurface->client()); 0202 for (auto resource : clientResources) { 0203 send_leave(resource->handle, leavingSurface->resource()); 0204 } 0205 } 0206 0207 void TextInputV3InterfacePrivate::sendPreEdit(const QString &text, const quint32 cursorBegin, const quint32 cursorEnd) 0208 { 0209 if (!surface) { 0210 return; 0211 } 0212 0213 pending.preeditText = text; 0214 pending.preeditCursorBegin = cursorBegin; 0215 pending.preeditCursorEnd = cursorEnd; 0216 0217 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client()); 0218 for (auto resource : textInputs) { 0219 send_preedit_string(resource->handle, text, cursorBegin, cursorEnd); 0220 } 0221 } 0222 0223 void TextInputV3InterfacePrivate::commitString(const QString &text) 0224 { 0225 if (!surface) { 0226 return; 0227 } 0228 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client()); 0229 for (auto resource : textInputs) { 0230 send_commit_string(resource->handle, text); 0231 } 0232 } 0233 0234 void TextInputV3InterfacePrivate::deleteSurroundingText(quint32 before, quint32 after) 0235 { 0236 if (!surface) { 0237 return; 0238 } 0239 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client()); 0240 for (auto resource : textInputs) { 0241 send_delete_surrounding_text(resource->handle, before, after); 0242 } 0243 } 0244 0245 void TextInputV3InterfacePrivate::done() 0246 { 0247 if (!surface) { 0248 return; 0249 } 0250 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client()); 0251 0252 preeditText = pending.preeditText; 0253 preeditCursorBegin = pending.preeditCursorBegin; 0254 preeditCursorEnd = pending.preeditCursorEnd; 0255 defaultPendingPreedit(); 0256 0257 for (auto resource : textInputs) { 0258 // zwp_text_input_v3.done takes the serial argument which is equal to number of commit requests issued 0259 send_done(resource->handle, serialHash[resource]); 0260 } 0261 } 0262 0263 QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::textInputsForClient(ClientConnection *client) const 0264 { 0265 return resourceMap().values(client->client()); 0266 } 0267 0268 QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::enabledTextInputsForClient(ClientConnection *client) const 0269 { 0270 QList<TextInputV3InterfacePrivate::Resource *> result; 0271 const auto [start, end] = resourceMap().equal_range(client->client()); 0272 for (auto it = start; it != end; ++it) { 0273 if (enabled[*it]) { 0274 result.append(*it); 0275 } 0276 } 0277 return result; 0278 } 0279 0280 bool TextInputV3InterfacePrivate::isEnabled() const 0281 { 0282 if (!surface) { 0283 return false; 0284 } 0285 const auto clientResources = textInputsForClient(surface->client()); 0286 return std::any_of(clientResources.begin(), clientResources.end(), [this](Resource *resource) { 0287 return enabled[resource]; 0288 }); 0289 } 0290 0291 void TextInputV3InterfacePrivate::zwp_text_input_v3_enable(Resource *resource) 0292 { 0293 // reset pending state to default 0294 defaultPending(); 0295 pending.enabled = true; 0296 } 0297 0298 void TextInputV3InterfacePrivate::zwp_text_input_v3_disable(Resource *resource) 0299 { 0300 // reset pending state to default 0301 defaultPending(); 0302 preeditText = QString(); 0303 preeditCursorBegin = 0; 0304 preeditCursorEnd = 0; 0305 } 0306 0307 void TextInputV3InterfacePrivate::zwp_text_input_v3_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor) 0308 { 0309 // zwp_text_input_v3_set_surrounding_text is no-op if enabled request is not pending 0310 if (!pending.enabled) { 0311 return; 0312 } 0313 pending.surroundingText = text; 0314 pending.surroundingTextCursorPosition = cursor; 0315 pending.surroundingTextSelectionAnchor = anchor; 0316 } 0317 0318 void TextInputV3InterfacePrivate::zwp_text_input_v3_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose) 0319 { 0320 // zwp_text_input_v3_set_content_type is no-op if enabled request is not pending 0321 if (!pending.enabled) { 0322 return; 0323 } 0324 pending.contentHints = convertContentHint(hint); 0325 pending.contentPurpose = convertContentPurpose(purpose); 0326 } 0327 0328 void TextInputV3InterfacePrivate::zwp_text_input_v3_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) 0329 { 0330 // zwp_text_input_v3_set_cursor_rectangle is no-op if enabled request is not pending 0331 if (!pending.enabled) { 0332 return; 0333 } 0334 pending.cursorRectangle = QRect(x, y, width, height); 0335 } 0336 0337 void TextInputV3InterfacePrivate::zwp_text_input_v3_set_text_change_cause(Resource *resource, uint32_t cause) 0338 { 0339 // zwp_text_input_v3_set_text_change_cause is no-op if enabled request is not pending 0340 if (!pending.enabled) { 0341 return; 0342 } 0343 pending.surroundingTextChangeCause = convertChangeCause(cause); 0344 } 0345 0346 void TextInputV3InterfacePrivate::zwp_text_input_v3_commit(Resource *resource) 0347 { 0348 EnabledEmitter emitter(q); 0349 serialHash[resource]++; 0350 0351 auto &resourceEnabled = enabled[resource]; 0352 const auto oldResourceEnabled = resourceEnabled; 0353 if (resourceEnabled != pending.enabled) { 0354 resourceEnabled = pending.enabled; 0355 } 0356 0357 if (surroundingTextChangeCause != pending.surroundingTextChangeCause) { 0358 surroundingTextChangeCause = pending.surroundingTextChangeCause; 0359 pending.surroundingTextChangeCause = TextInputChangeCause::InputMethod; 0360 } 0361 0362 if (contentHints != pending.contentHints || contentPurpose != pending.contentPurpose) { 0363 contentHints = pending.contentHints; 0364 contentPurpose = pending.contentPurpose; 0365 if (resourceEnabled) { 0366 Q_EMIT q->contentTypeChanged(); 0367 } 0368 } 0369 0370 if (cursorRectangle != pending.cursorRectangle) { 0371 cursorRectangle = pending.cursorRectangle; 0372 if (resourceEnabled) { 0373 Q_EMIT q->cursorRectangleChanged(cursorRectangle); 0374 } 0375 } 0376 0377 if (surroundingText != pending.surroundingText || surroundingTextCursorPosition != pending.surroundingTextCursorPosition 0378 || surroundingTextSelectionAnchor != pending.surroundingTextSelectionAnchor) { 0379 surroundingText = pending.surroundingText; 0380 surroundingTextCursorPosition = pending.surroundingTextCursorPosition; 0381 surroundingTextSelectionAnchor = pending.surroundingTextSelectionAnchor; 0382 if (resourceEnabled) { 0383 Q_EMIT q->surroundingTextChanged(); 0384 } 0385 } 0386 0387 Q_EMIT q->stateCommitted(serialHash[resource]); 0388 0389 // Gtk text input implementation expect done to be sent after every commit to synchronize the serial value between commit() and done(). 0390 // So we need to send the current preedit text with done(). 0391 // If current preedit is empty, there is no need to send it. 0392 if (!preeditText.isEmpty() || preeditCursorBegin != 0 || preeditCursorEnd != 0) { 0393 send_preedit_string(resource->handle, preeditText, preeditCursorBegin, preeditCursorEnd); 0394 } 0395 send_done(resource->handle, serialHash[resource]); 0396 0397 if (resourceEnabled && oldResourceEnabled) { 0398 Q_EMIT q->enableRequested(); 0399 } 0400 } 0401 0402 void TextInputV3InterfacePrivate::defaultPending() 0403 { 0404 pending.cursorRectangle = QRect(); 0405 pending.surroundingTextChangeCause = TextInputChangeCause::InputMethod; 0406 pending.contentHints = TextInputContentHints(TextInputContentHint::None); 0407 pending.contentPurpose = TextInputContentPurpose::Normal; 0408 pending.enabled = false; 0409 pending.surroundingText = QString(); 0410 pending.surroundingTextCursorPosition = 0; 0411 pending.surroundingTextSelectionAnchor = 0; 0412 defaultPendingPreedit(); 0413 } 0414 0415 void TextInputV3InterfacePrivate::defaultPendingPreedit() 0416 { 0417 pending.preeditText = QString(); 0418 pending.preeditCursorBegin = 0; 0419 pending.preeditCursorEnd = 0; 0420 } 0421 0422 TextInputV3Interface::TextInputV3Interface(SeatInterface *seat) 0423 : QObject(seat) 0424 , d(new TextInputV3InterfacePrivate(seat, this)) 0425 { 0426 } 0427 0428 TextInputV3Interface::~TextInputV3Interface() = default; 0429 0430 TextInputContentHints TextInputV3Interface::contentHints() const 0431 { 0432 return d->contentHints; 0433 } 0434 0435 TextInputContentPurpose TextInputV3Interface::contentPurpose() const 0436 { 0437 return d->contentPurpose; 0438 } 0439 0440 QString TextInputV3Interface::surroundingText() const 0441 { 0442 return d->surroundingText; 0443 } 0444 0445 qint32 TextInputV3Interface::surroundingTextCursorPosition() const 0446 { 0447 return d->surroundingTextCursorPosition; 0448 } 0449 0450 qint32 TextInputV3Interface::surroundingTextSelectionAnchor() const 0451 { 0452 return d->surroundingTextSelectionAnchor; 0453 } 0454 0455 void TextInputV3Interface::deleteSurroundingText(quint32 beforeLength, quint32 afterLength) 0456 { 0457 d->deleteSurroundingText(beforeLength, afterLength); 0458 } 0459 0460 void TextInputV3Interface::sendPreEditString(const QString &text, quint32 cursorBegin, quint32 cursorEnd) 0461 { 0462 d->sendPreEdit(text, cursorBegin, cursorEnd); 0463 } 0464 0465 void TextInputV3Interface::commitString(const QString &text) 0466 { 0467 d->commitString(text); 0468 } 0469 0470 void TextInputV3Interface::done() 0471 { 0472 d->done(); 0473 } 0474 0475 QPointer<SurfaceInterface> TextInputV3Interface::surface() const 0476 { 0477 if (!d->surface) { 0478 return nullptr; 0479 } 0480 0481 if (!d->resourceMap().contains(d->surface->client()->client())) { 0482 return nullptr; 0483 } 0484 0485 return d->surface; 0486 } 0487 0488 QRect TextInputV3Interface::cursorRectangle() const 0489 { 0490 return d->cursorRectangle; 0491 } 0492 0493 bool TextInputV3Interface::isEnabled() const 0494 { 0495 return d->isEnabled(); 0496 } 0497 0498 bool TextInputV3Interface::clientSupportsTextInput(ClientConnection *client) const 0499 { 0500 return client && d->resourceMap().contains(*client); 0501 } 0502 }