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 }