File indexing completed on 2024-05-19 05:32:46

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"