KWin
Loading...
Searching...
No Matches
textinput_v3.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
3 SPDX-FileCopyrightText: 2018 Roman Glig <subdiff@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "display.h"
9#include "seat.h"
10#include "surface_p.h"
11#include "textinput_v3_p.h"
12
13namespace KWin
14{
15namespace
16{
17const quint32 s_version = 1;
18
19TextInputContentHints convertContentHint(uint32_t hint)
20{
21 const auto hints = QtWaylandServer::zwp_text_input_v3::content_hint(hint);
22 TextInputContentHints ret = TextInputContentHint::None;
23
24 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_completion) {
26 }
27 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_auto_capitalization) {
29 }
30 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_lowercase) {
32 }
33 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_uppercase) {
35 }
36 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_titlecase) {
38 }
39 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_hidden_text) {
41 }
42 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_sensitive_data) {
44 }
45 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_latin) {
47 }
48 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_multiline) {
50 }
51 if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_spellcheck) {
53 }
54 return ret;
55}
56
57TextInputContentPurpose convertContentPurpose(uint32_t purpose)
58{
59 const auto wlPurpose = QtWaylandServer::zwp_text_input_v3::content_purpose(purpose);
60
61 switch (wlPurpose) {
62 case QtWaylandServer::zwp_text_input_v3::content_purpose_alpha:
64 case QtWaylandServer::zwp_text_input_v3::content_purpose_digits:
66 case QtWaylandServer::zwp_text_input_v3::content_purpose_number:
68 case QtWaylandServer::zwp_text_input_v3::content_purpose_phone:
70 case QtWaylandServer::zwp_text_input_v3::content_purpose_url:
72 case QtWaylandServer::zwp_text_input_v3::content_purpose_email:
74 case QtWaylandServer::zwp_text_input_v3::content_purpose_name:
76 case QtWaylandServer::zwp_text_input_v3::content_purpose_password:
78 case QtWaylandServer::zwp_text_input_v3::content_purpose_pin:
80 case QtWaylandServer::zwp_text_input_v3::content_purpose_date:
82 case QtWaylandServer::zwp_text_input_v3::content_purpose_time:
84 case QtWaylandServer::zwp_text_input_v3::content_purpose_datetime:
86 case QtWaylandServer::zwp_text_input_v3::content_purpose_terminal:
88 case QtWaylandServer::zwp_text_input_v3::content_purpose_normal:
90 default:
92 }
93}
94
95TextInputChangeCause convertChangeCause(uint32_t cause)
96{
97 const auto wlCause = QtWaylandServer::zwp_text_input_v3::change_cause(cause);
98 switch (wlCause) {
99 case QtWaylandServer::zwp_text_input_v3::change_cause_input_method:
101 case QtWaylandServer::zwp_text_input_v3::change_cause_other:
102 default:
104 }
105}
106}
107
109 : QtWaylandServer::zwp_text_input_manager_v3(*display, s_version)
110 , q(_q)
111{
112}
113
115{
116 wl_resource_destroy(resource->handle);
117}
118
119void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_get_text_input(Resource *resource, uint32_t id, wl_resource *seat)
120{
122 if (!s) {
123 wl_resource_post_error(resource->handle, 0, "Invalid seat");
124 return;
125 }
127 auto *textInputResource = textInputPrivate->add(resource->client(), id, resource->version());
128 // Send enter to this new text input object if the surface is already focused.
129 if (textInputPrivate->surface && textInputPrivate->surface->client()->client() == resource->client()) {
130 textInputPrivate->send_enter(textInputResource->handle, textInputPrivate->surface->resource());
131 }
132}
133
135 : QObject(parent)
136 , d(new TextInputManagerV3InterfacePrivate(this, display))
137{
138}
139
141
147
149{
150 // we initialize the serial for the resource to be 0
151 serialHash.insert(resource, 0);
152 enabledHash.insert(resource, false);
153}
154
156{
157 // drop resource from the serial hash
158 serialHash.remove(resource);
159 enabledHash.remove(resource);
161}
162
164{
165 wl_resource_destroy(resource->handle);
166}
167
169{
170 // It should be always synchronized with SeatInterface::focusedTextInputSurface.
171 Q_ASSERT(!surface && newSurface);
172 surface = newSurface;
173 const auto clientResources = textInputsForClient(newSurface->client());
174 for (auto resource : clientResources) {
175 send_enter(resource->handle, newSurface->resource());
176 }
178}
179
181{
182 // It should be always synchronized with SeatInterface::focusedTextInputSurface.
183 Q_ASSERT(leavingSurface && surface == leavingSurface);
184 surface.clear();
185 const auto clientResources = textInputsForClient(leavingSurface->client());
186 for (auto resource : clientResources) {
187 send_leave(resource->handle, leavingSurface->resource());
188 }
190}
191
192void TextInputV3InterfacePrivate::sendPreEdit(const QString &text, const quint32 cursorBegin, const quint32 cursorEnd)
193{
194 if (!surface) {
195 return;
196 }
197
198 pending.preeditText = text;
199 pending.preeditCursorBegin = cursorBegin;
200 pending.preeditCursorEnd = cursorEnd;
201
202 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client());
203 for (auto resource : textInputs) {
204 send_preedit_string(resource->handle, text, cursorBegin, cursorEnd);
205 }
206}
207
209{
210 if (!surface) {
211 return;
212 }
213 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client());
214 for (auto resource : textInputs) {
215 send_commit_string(resource->handle, text);
216 }
217}
218
219void TextInputV3InterfacePrivate::deleteSurroundingText(quint32 before, quint32 after)
220{
221 if (!surface) {
222 return;
223 }
224 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client());
225 for (auto resource : textInputs) {
226 send_delete_surrounding_text(resource->handle, before, after);
227 }
228}
229
231{
232 if (!surface) {
233 return;
234 }
235 const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client());
236
237 preeditText = pending.preeditText;
238 preeditCursorBegin = pending.preeditCursorBegin;
239 preeditCursorEnd = pending.preeditCursorEnd;
241
242 for (auto resource : textInputs) {
243 // zwp_text_input_v3.done takes the serial argument which is equal to number of commit requests issued
244 send_done(resource->handle, serialHash[resource]);
245 }
246}
247
248QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::textInputsForClient(ClientConnection *client) const
249{
250 return resourceMap().values(client->client());
251}
252
253QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::enabledTextInputsForClient(ClientConnection *client) const
254{
255 QList<TextInputV3InterfacePrivate::Resource *> result;
256 const auto [start, end] = resourceMap().equal_range(client->client());
257 for (auto it = start; it != end; ++it) {
258 if (enabledHash[*it]) {
259 result.append(*it);
260 }
261 }
262 return result;
263}
264
266{
267 bool newEnabled = false;
268 if (surface) {
269 const auto clientResources = textInputsForClient(surface->client());
270 newEnabled = std::any_of(clientResources.begin(), clientResources.end(), [this](Resource *resource) {
271 return enabledHash[resource];
272 });
273 }
274
275 if (isEnabled != newEnabled) {
276 isEnabled = newEnabled;
277 Q_EMIT q->enabledChanged();
278 }
279}
280
282{
283 // reset pending state to default
285 pending.enabled = true;
286}
287
289{
290 // reset pending state to default
292 preeditText = QString();
295}
296
297void TextInputV3InterfacePrivate::zwp_text_input_v3_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor)
298{
299 // zwp_text_input_v3_set_surrounding_text is no-op if enabled request is not pending
300 if (!pending.enabled) {
301 return;
302 }
303 pending.surroundingText = text;
304 pending.surroundingTextCursorPosition = cursor;
305 pending.surroundingTextSelectionAnchor = anchor;
306}
307
308void TextInputV3InterfacePrivate::zwp_text_input_v3_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose)
309{
310 // zwp_text_input_v3_set_content_type is no-op if enabled request is not pending
311 if (!pending.enabled) {
312 return;
313 }
314 pending.contentHints = convertContentHint(hint);
315 pending.contentPurpose = convertContentPurpose(purpose);
316}
317
318void TextInputV3InterfacePrivate::zwp_text_input_v3_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
319{
320 // zwp_text_input_v3_set_cursor_rectangle is no-op if enabled request is not pending
321 if (!pending.enabled) {
322 return;
323 }
324 pending.cursorRectangle = QRect(x, y, width, height);
325}
326
328{
329 // zwp_text_input_v3_set_text_change_cause is no-op if enabled request is not pending
330 if (!pending.enabled) {
331 return;
332 }
333 pending.surroundingTextChangeCause = convertChangeCause(cause);
334}
335
337{
338 serialHash[resource]++;
339
340 auto &resourceEnabled = enabledHash[resource];
341 const auto oldResourceEnabled = resourceEnabled;
342 if (resourceEnabled != pending.enabled) {
343 resourceEnabled = pending.enabled;
344 }
345
346 if (surroundingTextChangeCause != pending.surroundingTextChangeCause) {
347 surroundingTextChangeCause = pending.surroundingTextChangeCause;
348 pending.surroundingTextChangeCause = TextInputChangeCause::InputMethod;
349 }
350
351 if (contentHints != pending.contentHints || contentPurpose != pending.contentPurpose) {
352 contentHints = pending.contentHints;
353 contentPurpose = pending.contentPurpose;
354 if (resourceEnabled) {
355 Q_EMIT q->contentTypeChanged();
356 }
357 }
358
359 if (cursorRectangle != pending.cursorRectangle) {
360 cursorRectangle = pending.cursorRectangle;
361 if (resourceEnabled) {
363 }
364 }
365
366 if (surroundingText != pending.surroundingText || surroundingTextCursorPosition != pending.surroundingTextCursorPosition
367 || surroundingTextSelectionAnchor != pending.surroundingTextSelectionAnchor) {
368 surroundingText = pending.surroundingText;
369 surroundingTextCursorPosition = pending.surroundingTextCursorPosition;
370 surroundingTextSelectionAnchor = pending.surroundingTextSelectionAnchor;
371 if (resourceEnabled) {
372 Q_EMIT q->surroundingTextChanged();
373 }
374 }
375
376 Q_EMIT q->stateCommitted(serialHash[resource]);
377
378 // Gtk text input implementation expect done to be sent after every commit to synchronize the serial value between commit() and done().
379 // So we need to send the current preedit text with done().
380 // If current preedit is empty, there is no need to send it.
381 if (!preeditText.isEmpty() || preeditCursorBegin != 0 || preeditCursorEnd != 0) {
382 send_preedit_string(resource->handle, preeditText, preeditCursorBegin, preeditCursorEnd);
383 }
384 send_done(resource->handle, serialHash[resource]);
385
386 if (resourceEnabled && oldResourceEnabled) {
387 Q_EMIT q->enableRequested();
388 }
389
391}
392
394{
395 pending.cursorRectangle = QRect();
396 pending.surroundingTextChangeCause = TextInputChangeCause::InputMethod;
397 pending.contentHints = TextInputContentHints(TextInputContentHint::None);
399 pending.enabled = false;
400 pending.surroundingText = QString();
401 pending.surroundingTextCursorPosition = 0;
402 pending.surroundingTextSelectionAnchor = 0;
404}
405
407{
408 pending.preeditText = QString();
409 pending.preeditCursorBegin = 0;
410 pending.preeditCursorEnd = 0;
411}
412
413TextInputV3Interface::TextInputV3Interface(SeatInterface *seat)
414 : QObject(seat)
415 , d(new TextInputV3InterfacePrivate(seat, this))
416{
417}
418
420
421TextInputContentHints TextInputV3Interface::contentHints() const
422{
423 return d->contentHints;
424}
425
427{
428 return d->contentPurpose;
429}
430
432{
433 return d->surroundingText;
434}
435
437{
438 return d->surroundingTextCursorPosition;
439}
440
442{
443 return d->surroundingTextSelectionAnchor;
444}
445
446void TextInputV3Interface::deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
447{
448 d->deleteSurroundingText(beforeLength, afterLength);
449}
450
451void TextInputV3Interface::sendPreEditString(const QString &text, quint32 cursorBegin, quint32 cursorEnd)
452{
453 d->sendPreEdit(text, cursorBegin, cursorEnd);
454}
455
456void TextInputV3Interface::commitString(const QString &text)
457{
458 d->commitString(text);
459}
460
462{
463 d->done();
464}
465
466QPointer<SurfaceInterface> TextInputV3Interface::surface() const
467{
468 if (!d->surface) {
469 return nullptr;
470 }
471
472 if (!d->resourceMap().contains(d->surface->client()->client())) {
473 return nullptr;
474 }
475
476 return d->surface;
477}
478
480{
481 return d->cursorRectangle;
482}
483
485{
486 return d->isEnabled;
487}
488
490{
491 return client && d->resourceMap().contains(*client);
492}
493}
494
495#include "moc_textinput_v3.cpp"
Convenient Class which represents a wl_client.
wl_client * client() const
Class holding the Wayland server display loop.
Definition display.h:34
Represents a Seat on the Wayland Display.
Definition seat.h:134
static SeatInterface * get(wl_resource *native)
Definition seat.cpp:429
TextInputV3Interface * textInputV3() const
Definition seat.cpp:1274
Resource representing a wl_surface.
Definition surface.h:80
ClientConnection * client() const
Definition surface.cpp:444
wl_resource * resource() const
Definition surface.cpp:449
Represent the Global for the interface.
TextInputManagerV3Interface(Display *display, QObject *parent=nullptr)
void zwp_text_input_manager_v3_get_text_input(Resource *resource, uint32_t id, wl_resource *seat) override
TextInputManagerV3InterfacePrivate(TextInputManagerV3Interface *_q, Display *display)
void zwp_text_input_manager_v3_destroy(Resource *resource) override
Represents a generic Resource for a text input object.A TextInputV3Interface gets created by the Text...
void stateCommitted(quint32 serial)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
void cursorRectangleChanged(const QRect &rect)
TextInputContentPurpose contentPurpose() const
bool clientSupportsTextInput(ClientConnection *client) const
QPointer< SurfaceInterface > surface() const
qint32 surroundingTextSelectionAnchor() const
void commitString(const QString &text)
QString surroundingText() const
qint32 surroundingTextCursorPosition() const
TextInputContentHints contentHints() const
void sendPreEditString(const QString &text, quint32 cursorBegin, quint32 cursorEnd)
QList< TextInputV3InterfacePrivate::Resource * > enabledTextInputsForClient(ClientConnection *client) const
QList< TextInputV3InterfacePrivate::Resource * > textInputsForClient(ClientConnection *client) const
void zwp_text_input_v3_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor) override
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
void zwp_text_input_v3_commit(Resource *resource) override
TextInputContentPurpose contentPurpose
QHash< Resource *, bool > enabledHash
void zwp_text_input_v3_disable(Resource *resource) override
void commitString(const QString &text)
void sendLeave(SurfaceInterface *surface)
TextInputV3InterfacePrivate(SeatInterface *seat, TextInputV3Interface *_q)
void zwp_text_input_v3_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose) override
void zwp_text_input_v3_destroy_resource(Resource *resource) override
void sendPreEdit(const QString &text, const quint32 cursorBegin, const quint32 cursorEnd)
void sendEnter(SurfaceInterface *surface)
void zwp_text_input_v3_enable(Resource *resource) override
static TextInputV3InterfacePrivate * get(TextInputV3Interface *inputInterface)
QHash< Resource *, quint32 > serialHash
struct KWin::TextInputV3InterfacePrivate::@31 pending
void zwp_text_input_v3_destroy(Resource *resource) override
TextInputContentHints contentHints
void zwp_text_input_v3_set_text_change_cause(Resource *resource, uint32_t cause) override
QPointer< SurfaceInterface > surface
void zwp_text_input_v3_bind_resource(Resource *resource) override
void zwp_text_input_v3_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override
TextInputChangeCause surroundingTextChangeCause
TextInputChangeCause
Definition textinput.h:131
TextInputContentPurpose
Definition textinput.h:72