File indexing completed on 2024-11-10 04:57:36
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.h" 0010 #include "surface_p.h" 0011 #include "textinput_v3_p.h" 0012 0013 namespace KWin 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 0108 TextInputManagerV3InterfacePrivate::TextInputManagerV3InterfacePrivate(TextInputManagerV3Interface *_q, Display *display) 0109 : QtWaylandServer::zwp_text_input_manager_v3(*display, s_version) 0110 , q(_q) 0111 { 0112 } 0113 0114 void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_destroy(Resource *resource) 0115 { 0116 wl_resource_destroy(resource->handle); 0117 } 0118 0119 void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_get_text_input(Resource *resource, uint32_t id, wl_resource *seat) 0120 { 0121 SeatInterface *s = SeatInterface::get(seat); 0122 if (!s) { 0123 wl_resource_post_error(resource->handle, 0, "Invalid seat"); 0124 return; 0125 } 0126 TextInputV3InterfacePrivate *textInputPrivate = TextInputV3InterfacePrivate::get(s->textInputV3()); 0127 auto *textInputResource = textInputPrivate->add(resource->client(), id, resource->version()); 0128 // Send enter to this new text input object if the surface is already focused. 0129 if (textInputPrivate->surface && textInputPrivate->surface->client()->client() == resource->client()) { 0130 textInputPrivate->send_enter(textInputResource->handle, textInputPrivate->surface->resource()); 0131 } 0132 } 0133 0134 TextInputManagerV3Interface::TextInputManagerV3Interface(Display *display, QObject *parent) 0135 : QObject(parent) 0136 , d(new TextInputManagerV3InterfacePrivate(this, display)) 0137 { 0138 } 0139 0140 TextInputManagerV3Interface::~TextInputManagerV3Interface() = default; 0141 0142 TextInputV3InterfacePrivate::TextInputV3InterfacePrivate(SeatInterface *seat, TextInputV3Interface *_q) 0143 : seat(seat) 0144 , q(_q) 0145 { 0146 } 0147 0148 void TextInputV3InterfacePrivate::zwp_text_input_v3_bind_resource(Resource *resource) 0149 { 0150 // we initialize the serial for the resource to be 0 0151 serialHash.insert(resource, 0); 0152 enabledHash.insert(resource, false); 0153 } 0154 0155 void TextInputV3InterfacePrivate::zwp_text_input_v3_destroy_resource(Resource *resource) 0156 { 0157 // drop resource from the serial hash 0158 serialHash.remove(resource); 0159 enabledHash.remove(resource); 0160 updateEnabled(); 0161 } 0162 0163 void TextInputV3InterfacePrivate::zwp_text_input_v3_destroy(Resource *resource) 0164 { 0165 wl_resource_destroy(resource->handle); 0166 } 0167 0168 void TextInputV3InterfacePrivate::sendEnter(SurfaceInterface *newSurface) 0169 { 0170 // It should be always synchronized with SeatInterface::focusedTextInputSurface. 0171 Q_ASSERT(!surface && newSurface); 0172 surface = newSurface; 0173 const auto clientResources = textInputsForClient(newSurface->client()); 0174 for (auto resource : clientResources) { 0175 send_enter(resource->handle, newSurface->resource()); 0176 } 0177 updateEnabled(); 0178 } 0179 0180 void TextInputV3InterfacePrivate::sendLeave(SurfaceInterface *leavingSurface) 0181 { 0182 // It should be always synchronized with SeatInterface::focusedTextInputSurface. 0183 Q_ASSERT(leavingSurface && surface == leavingSurface); 0184 surface.clear(); 0185 const auto clientResources = textInputsForClient(leavingSurface->client()); 0186 for (auto resource : clientResources) { 0187 send_leave(resource->handle, leavingSurface->resource()); 0188 } 0189 updateEnabled(); 0190 } 0191 0192 void TextInputV3InterfacePrivate::sendPreEdit(const QString &text, const quint32 cursorBegin, const quint32 cursorEnd) 0193 { 0194 if (!surface) { 0195 return; 0196 } 0197 0198 pending.preeditText = text; 0199 pending.preeditCursorBegin = cursorBegin; 0200 pending.preeditCursorEnd = cursorEnd; 0201 0202 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client()); 0203 for (auto resource : textInputs) { 0204 send_preedit_string(resource->handle, text, cursorBegin, cursorEnd); 0205 } 0206 } 0207 0208 void TextInputV3InterfacePrivate::commitString(const QString &text) 0209 { 0210 if (!surface) { 0211 return; 0212 } 0213 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client()); 0214 for (auto resource : textInputs) { 0215 send_commit_string(resource->handle, text); 0216 } 0217 } 0218 0219 void TextInputV3InterfacePrivate::deleteSurroundingText(quint32 before, quint32 after) 0220 { 0221 if (!surface) { 0222 return; 0223 } 0224 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client()); 0225 for (auto resource : textInputs) { 0226 send_delete_surrounding_text(resource->handle, before, after); 0227 } 0228 } 0229 0230 void TextInputV3InterfacePrivate::done() 0231 { 0232 if (!surface) { 0233 return; 0234 } 0235 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client()); 0236 0237 preeditText = pending.preeditText; 0238 preeditCursorBegin = pending.preeditCursorBegin; 0239 preeditCursorEnd = pending.preeditCursorEnd; 0240 defaultPendingPreedit(); 0241 0242 for (auto resource : textInputs) { 0243 // zwp_text_input_v3.done takes the serial argument which is equal to number of commit requests issued 0244 send_done(resource->handle, serialHash[resource]); 0245 } 0246 } 0247 0248 QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::textInputsForClient(ClientConnection *client) const 0249 { 0250 return resourceMap().values(client->client()); 0251 } 0252 0253 QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::enabledTextInputsForClient(ClientConnection *client) const 0254 { 0255 QList<TextInputV3InterfacePrivate::Resource *> result; 0256 const auto [start, end] = resourceMap().equal_range(client->client()); 0257 for (auto it = start; it != end; ++it) { 0258 if (enabledHash[*it]) { 0259 result.append(*it); 0260 } 0261 } 0262 return result; 0263 } 0264 0265 void TextInputV3InterfacePrivate::updateEnabled() 0266 { 0267 bool newEnabled = false; 0268 if (surface) { 0269 const auto clientResources = textInputsForClient(surface->client()); 0270 newEnabled = std::any_of(clientResources.begin(), clientResources.end(), [this](Resource *resource) { 0271 return enabledHash[resource]; 0272 }); 0273 } 0274 0275 if (isEnabled != newEnabled) { 0276 isEnabled = newEnabled; 0277 Q_EMIT q->enabledChanged(); 0278 } 0279 } 0280 0281 void TextInputV3InterfacePrivate::zwp_text_input_v3_enable(Resource *resource) 0282 { 0283 // reset pending state to default 0284 defaultPending(); 0285 pending.enabled = true; 0286 } 0287 0288 void TextInputV3InterfacePrivate::zwp_text_input_v3_disable(Resource *resource) 0289 { 0290 // reset pending state to default 0291 defaultPending(); 0292 preeditText = QString(); 0293 preeditCursorBegin = 0; 0294 preeditCursorEnd = 0; 0295 } 0296 0297 void TextInputV3InterfacePrivate::zwp_text_input_v3_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor) 0298 { 0299 // zwp_text_input_v3_set_surrounding_text is no-op if enabled request is not pending 0300 if (!pending.enabled) { 0301 return; 0302 } 0303 pending.surroundingText = text; 0304 pending.surroundingTextCursorPosition = cursor; 0305 pending.surroundingTextSelectionAnchor = anchor; 0306 } 0307 0308 void TextInputV3InterfacePrivate::zwp_text_input_v3_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose) 0309 { 0310 // zwp_text_input_v3_set_content_type is no-op if enabled request is not pending 0311 if (!pending.enabled) { 0312 return; 0313 } 0314 pending.contentHints = convertContentHint(hint); 0315 pending.contentPurpose = convertContentPurpose(purpose); 0316 } 0317 0318 void TextInputV3InterfacePrivate::zwp_text_input_v3_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) 0319 { 0320 // zwp_text_input_v3_set_cursor_rectangle is no-op if enabled request is not pending 0321 if (!pending.enabled) { 0322 return; 0323 } 0324 pending.cursorRectangle = QRect(x, y, width, height); 0325 } 0326 0327 void TextInputV3InterfacePrivate::zwp_text_input_v3_set_text_change_cause(Resource *resource, uint32_t cause) 0328 { 0329 // zwp_text_input_v3_set_text_change_cause is no-op if enabled request is not pending 0330 if (!pending.enabled) { 0331 return; 0332 } 0333 pending.surroundingTextChangeCause = convertChangeCause(cause); 0334 } 0335 0336 void TextInputV3InterfacePrivate::zwp_text_input_v3_commit(Resource *resource) 0337 { 0338 serialHash[resource]++; 0339 0340 auto &resourceEnabled = enabledHash[resource]; 0341 const auto oldResourceEnabled = resourceEnabled; 0342 if (resourceEnabled != pending.enabled) { 0343 resourceEnabled = pending.enabled; 0344 } 0345 0346 if (surroundingTextChangeCause != pending.surroundingTextChangeCause) { 0347 surroundingTextChangeCause = pending.surroundingTextChangeCause; 0348 pending.surroundingTextChangeCause = TextInputChangeCause::InputMethod; 0349 } 0350 0351 if (contentHints != pending.contentHints || contentPurpose != pending.contentPurpose) { 0352 contentHints = pending.contentHints; 0353 contentPurpose = pending.contentPurpose; 0354 if (resourceEnabled) { 0355 Q_EMIT q->contentTypeChanged(); 0356 } 0357 } 0358 0359 if (cursorRectangle != pending.cursorRectangle) { 0360 cursorRectangle = pending.cursorRectangle; 0361 if (resourceEnabled) { 0362 Q_EMIT q->cursorRectangleChanged(cursorRectangle); 0363 } 0364 } 0365 0366 if (surroundingText != pending.surroundingText || surroundingTextCursorPosition != pending.surroundingTextCursorPosition 0367 || surroundingTextSelectionAnchor != pending.surroundingTextSelectionAnchor) { 0368 surroundingText = pending.surroundingText; 0369 surroundingTextCursorPosition = pending.surroundingTextCursorPosition; 0370 surroundingTextSelectionAnchor = pending.surroundingTextSelectionAnchor; 0371 if (resourceEnabled) { 0372 Q_EMIT q->surroundingTextChanged(); 0373 } 0374 } 0375 0376 Q_EMIT q->stateCommitted(serialHash[resource]); 0377 0378 // Gtk text input implementation expect done to be sent after every commit to synchronize the serial value between commit() and done(). 0379 // So we need to send the current preedit text with done(). 0380 // If current preedit is empty, there is no need to send it. 0381 if (!preeditText.isEmpty() || preeditCursorBegin != 0 || preeditCursorEnd != 0) { 0382 send_preedit_string(resource->handle, preeditText, preeditCursorBegin, preeditCursorEnd); 0383 } 0384 send_done(resource->handle, serialHash[resource]); 0385 0386 if (resourceEnabled && oldResourceEnabled) { 0387 Q_EMIT q->enableRequested(); 0388 } 0389 0390 updateEnabled(); 0391 } 0392 0393 void TextInputV3InterfacePrivate::defaultPending() 0394 { 0395 pending.cursorRectangle = QRect(); 0396 pending.surroundingTextChangeCause = TextInputChangeCause::InputMethod; 0397 pending.contentHints = TextInputContentHints(TextInputContentHint::None); 0398 pending.contentPurpose = TextInputContentPurpose::Normal; 0399 pending.enabled = false; 0400 pending.surroundingText = QString(); 0401 pending.surroundingTextCursorPosition = 0; 0402 pending.surroundingTextSelectionAnchor = 0; 0403 defaultPendingPreedit(); 0404 } 0405 0406 void TextInputV3InterfacePrivate::defaultPendingPreedit() 0407 { 0408 pending.preeditText = QString(); 0409 pending.preeditCursorBegin = 0; 0410 pending.preeditCursorEnd = 0; 0411 } 0412 0413 TextInputV3Interface::TextInputV3Interface(SeatInterface *seat) 0414 : QObject(seat) 0415 , d(new TextInputV3InterfacePrivate(seat, this)) 0416 { 0417 } 0418 0419 TextInputV3Interface::~TextInputV3Interface() = default; 0420 0421 TextInputContentHints TextInputV3Interface::contentHints() const 0422 { 0423 return d->contentHints; 0424 } 0425 0426 TextInputContentPurpose TextInputV3Interface::contentPurpose() const 0427 { 0428 return d->contentPurpose; 0429 } 0430 0431 QString TextInputV3Interface::surroundingText() const 0432 { 0433 return d->surroundingText; 0434 } 0435 0436 qint32 TextInputV3Interface::surroundingTextCursorPosition() const 0437 { 0438 return d->surroundingTextCursorPosition; 0439 } 0440 0441 qint32 TextInputV3Interface::surroundingTextSelectionAnchor() const 0442 { 0443 return d->surroundingTextSelectionAnchor; 0444 } 0445 0446 void TextInputV3Interface::deleteSurroundingText(quint32 beforeLength, quint32 afterLength) 0447 { 0448 d->deleteSurroundingText(beforeLength, afterLength); 0449 } 0450 0451 void TextInputV3Interface::sendPreEditString(const QString &text, quint32 cursorBegin, quint32 cursorEnd) 0452 { 0453 d->sendPreEdit(text, cursorBegin, cursorEnd); 0454 } 0455 0456 void TextInputV3Interface::commitString(const QString &text) 0457 { 0458 d->commitString(text); 0459 } 0460 0461 void TextInputV3Interface::done() 0462 { 0463 d->done(); 0464 } 0465 0466 QPointer<SurfaceInterface> TextInputV3Interface::surface() const 0467 { 0468 if (!d->surface) { 0469 return nullptr; 0470 } 0471 0472 if (!d->resourceMap().contains(d->surface->client()->client())) { 0473 return nullptr; 0474 } 0475 0476 return d->surface; 0477 } 0478 0479 QRect TextInputV3Interface::cursorRectangle() const 0480 { 0481 return d->cursorRectangle; 0482 } 0483 0484 bool TextInputV3Interface::isEnabled() const 0485 { 0486 return d->isEnabled; 0487 } 0488 0489 bool TextInputV3Interface::clientSupportsTextInput(ClientConnection *client) const 0490 { 0491 return client && d->resourceMap().contains(*client); 0492 } 0493 } 0494 0495 #include "moc_textinput_v3.cpp"