KWin
Loading...
Searching...
No Matches
textinput_v2.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6#include "display.h"
7#include "seat_p.h"
8#include "surface_p.h"
9#include "textinput_v2_p.h"
10
11namespace KWin
12{
13
14namespace
15{
16const quint32 s_version = 1;
17
18// helpers
19TextInputContentHints convertContentHint(uint32_t hint)
20{
21 const auto hints = QtWaylandServer::zwp_text_input_v2::content_hint(hint);
22 TextInputContentHints ret = TextInputContentHint::None;
23
24 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_auto_completion) {
26 }
27 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_auto_correction) {
29 }
30 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_auto_capitalization) {
32 }
33 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_lowercase) {
35 }
36 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_uppercase) {
38 }
39 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_titlecase) {
41 }
42 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_hidden_text) {
44 }
45 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_sensitive_data) {
47 }
48 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_latin) {
50 }
51 if (hints & QtWaylandServer::zwp_text_input_v2::content_hint_multiline) {
53 }
54 return ret;
55}
56
57TextInputContentPurpose convertContentPurpose(uint32_t purpose)
58{
59 const auto wlPurpose = QtWaylandServer::zwp_text_input_v2::content_purpose(purpose);
60
61 switch (wlPurpose) {
62 case QtWaylandServer::zwp_text_input_v2::content_purpose_alpha:
64 case QtWaylandServer::zwp_text_input_v2::content_purpose_digits:
66 case QtWaylandServer::zwp_text_input_v2::content_purpose_number:
68 case QtWaylandServer::zwp_text_input_v2::content_purpose_phone:
70 case QtWaylandServer::zwp_text_input_v2::content_purpose_url:
72 case QtWaylandServer::zwp_text_input_v2::content_purpose_email:
74 case QtWaylandServer::zwp_text_input_v2::content_purpose_name:
76 case QtWaylandServer::zwp_text_input_v2::content_purpose_password:
78 case QtWaylandServer::zwp_text_input_v2::content_purpose_date:
80 case QtWaylandServer::zwp_text_input_v2::content_purpose_time:
82 case QtWaylandServer::zwp_text_input_v2::content_purpose_datetime:
84 case QtWaylandServer::zwp_text_input_v2::content_purpose_terminal:
86 case QtWaylandServer::zwp_text_input_v2::content_purpose_normal:
88 default:
90 }
91}
92
93class EnabledEmitter
94{
95public:
96 EnabledEmitter(TextInputV2Interface *q)
97 : q(q)
98 , m_wasEnabled(q->isEnabled())
99 {
100 }
101 ~EnabledEmitter()
102 {
103 if (m_wasEnabled != q->isEnabled()) {
104 Q_EMIT q->enabledChanged();
105 }
106 }
107
108private:
109 TextInputV2Interface *q;
110 const bool m_wasEnabled;
111};
112
113}
114
116 : QtWaylandServer::zwp_text_input_manager_v2(*display, s_version)
117 , q(_q)
118{
119}
120
122{
123 wl_resource_destroy(resource->handle);
124}
125
126void TextInputManagerV2InterfacePrivate::zwp_text_input_manager_v2_get_text_input(Resource *resource, uint32_t id, wl_resource *seat)
127{
129 if (!s) {
130 wl_resource_post_error(resource->handle, 0, "Invalid seat");
131 return;
132 }
133
135 auto *textInputResource = textInputPrivate->add(resource->client(), id, resource->version());
136 // Send enter to this new text input object if the surface is already focused.
137 const quint32 serial = s->display()->nextSerial();
138 if (textInputPrivate->surface && textInputPrivate->surface->client()->client() == resource->client()) {
139 textInputPrivate->send_enter(textInputResource->handle, serial, textInputPrivate->surface->resource());
140 }
141}
142
144 : QObject(parent)
145 , d(new TextInputManagerV2InterfacePrivate(this, display))
146{
147}
148
150
152{
153 EnabledEmitter emitter(q);
154 // It should be always synchronized with SeatInterface::focusedTextInputSurface.
155 Q_ASSERT(!surface && newSurface);
156 surface = newSurface;
157 const auto clientResources = textInputsForClient(newSurface->client());
158 for (auto resource : clientResources) {
159 send_enter(resource->handle, serial, newSurface->resource());
160 }
161}
162
163void TextInputV2InterfacePrivate::sendLeave(quint32 serial, SurfaceInterface *leavingSurface)
164{
165 // It should be always synchronized with SeatInterface::focusedTextInputSurface.
166 Q_ASSERT(leavingSurface && surface == leavingSurface);
167 EnabledEmitter emitter(q);
168 surface.clear();
169 const auto clientResources = textInputsForClient(leavingSurface->client());
170 for (auto resource : clientResources) {
171 send_leave(resource->handle, serial, leavingSurface->resource());
172 }
173}
174
175void TextInputV2InterfacePrivate::preEdit(const QString &text, const QString &commit)
176{
177 if (!surface) {
178 return;
179 }
180
181 const auto clientResources = textInputsForClient(surface->client());
182 for (auto resource : clientResources) {
183 send_preedit_string(resource->handle, text, commit);
184 }
185}
186
187void TextInputV2InterfacePrivate::preEditStyling(uint32_t index, uint32_t length, uint32_t style)
188{
189 if (!surface) {
190 return;
191 }
192
193 const auto clientResources = textInputsForClient(surface->client());
194 for (auto resource : clientResources) {
195 send_preedit_styling(resource->handle, index, length, style);
196 }
197}
198
200{
201 if (!surface) {
202 return;
203 }
204 const QList<Resource *> textInputs = textInputsForClient(surface->client());
205 for (auto resource : textInputs) {
206 send_commit_string(resource->handle, text);
207 }
208}
209
210void TextInputV2InterfacePrivate::keysymPressed(quint32 keysym, quint32 modifiers)
211{
212 if (!surface) {
213 return;
214 }
215
216 const QList<Resource *> textInputs = textInputsForClient(surface->client());
217 for (auto resource : textInputs) {
218 send_keysym(resource->handle, seat ? seat->timestamp().count() : 0, keysym, WL_KEYBOARD_KEY_STATE_PRESSED, modifiers);
219 }
220}
221
222void TextInputV2InterfacePrivate::keysymReleased(quint32 keysym, quint32 modifiers)
223{
224 if (!surface) {
225 return;
226 }
227
228 const QList<Resource *> textInputs = textInputsForClient(surface->client());
229 for (auto resource : textInputs) {
230 send_keysym(resource->handle, seat ? seat->timestamp().count() : 0, keysym, WL_KEYBOARD_KEY_STATE_RELEASED, modifiers);
231 }
232}
233
234void TextInputV2InterfacePrivate::deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
235{
236 if (!surface) {
237 return;
238 }
239 const QList<Resource *> textInputs = textInputsForClient(surface->client());
240 for (auto resource : textInputs) {
241 send_delete_surrounding_text(resource->handle, beforeLength, afterLength);
242 }
243}
244
245void TextInputV2InterfacePrivate::setCursorPosition(qint32 index, qint32 anchor)
246{
247 if (!surface) {
248 return;
249 }
250 const QList<Resource *> textInputs = textInputsForClient(surface->client());
251 for (auto resource : textInputs) {
252 send_cursor_position(resource->handle, index, anchor);
253 }
254}
255
256void TextInputV2InterfacePrivate::setTextDirection(Qt::LayoutDirection direction)
257{
258 if (!surface) {
259 return;
260 }
261 text_direction wlDirection;
262 switch (direction) {
263 case Qt::LeftToRight:
264 wlDirection = text_direction::text_direction_ltr;
265 break;
266 case Qt::RightToLeft:
267 wlDirection = text_direction::text_direction_rtl;
268 break;
269 case Qt::LayoutDirectionAuto:
270 wlDirection = text_direction::text_direction_auto;
271 break;
272 default:
273 Q_UNREACHABLE();
274 break;
275 }
276 const QList<Resource *> textInputs = textInputsForClient(surface->client());
277 for (auto resource : textInputs) {
278 send_text_direction(resource->handle, wlDirection);
279 }
280}
281
283{
284 if (!surface) {
285 return;
286 }
287 const QList<Resource *> textInputs = textInputsForClient(surface->client());
288 for (auto resource : textInputs) {
289 send_preedit_cursor(resource->handle, index);
290 }
291}
292
294{
295 if (!surface) {
296 return;
297 }
298 const QList<Resource *> textInputs = textInputsForClient(surface->client());
299 for (auto resource : textInputs) {
300 send_input_panel_state(resource->handle,
301 inputPanelVisible ? ZWP_TEXT_INPUT_V2_INPUT_PANEL_VISIBILITY_VISIBLE : ZWP_TEXT_INPUT_V2_INPUT_PANEL_VISIBILITY_HIDDEN,
304 overlappedSurfaceArea.width(),
305 overlappedSurfaceArea.height());
306 }
307}
308
310{
311 if (!surface) {
312 return;
313 }
314 const QList<Resource *> textInputs = textInputsForClient(surface->client());
315 for (auto resource : textInputs) {
316 send_language(resource->handle, language);
317 }
318}
319
321{
322 if (!surface) {
323 return;
324 }
325 const QList<Resource *> textInputs = textInputsForClient(surface->client());
326 for (auto resource : textInputs) {
327 send_modifiers_map(resource->handle, modifiersMap);
328 }
329}
330
336
337void TextInputV2InterfacePrivate::zwp_text_input_v2_enable(Resource *resource, wl_resource *s)
338{
339 EnabledEmitter emitter(q);
340 auto enabledSurface = SurfaceInterface::get(s);
341 if (m_enabledSurfaces.contains(enabledSurface)) {
342 return;
343 }
344 m_enabledSurfaces.insert(enabledSurface);
345 QObject::connect(enabledSurface, &SurfaceInterface::aboutToBeDestroyed, q, [this, enabledSurface] {
346 EnabledEmitter emitter(q);
347 m_enabledSurfaces.remove(enabledSurface);
348 });
349}
350
351void TextInputV2InterfacePrivate::zwp_text_input_v2_disable(Resource *resource, wl_resource *s)
352{
353 EnabledEmitter emitter(q);
354 auto disabledSurface = SurfaceInterface::get(s);
355 QObject::disconnect(disabledSurface, &SurfaceInterface::aboutToBeDestroyed, q, nullptr);
356 m_enabledSurfaces.remove(disabledSurface);
357 if (disabledSurface == surface) {
358 q->setInputPanelState(false, {0, 0, 0, 0});
359 }
360}
361
362void TextInputV2InterfacePrivate::zwp_text_input_v2_update_state(Resource *resource, uint32_t serial, uint32_t reason)
363{
364 Q_EMIT q->stateUpdated(serial, TextInputV2Interface::UpdateReason(reason));
365}
366
371
372void TextInputV2InterfacePrivate::zwp_text_input_v2_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor)
373{
374 surroundingText = text;
377 Q_EMIT q->surroundingTextChanged();
378}
379
380void TextInputV2InterfacePrivate::zwp_text_input_v2_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose)
381{
382 const auto contentHints = convertContentHint(hint);
383 const auto contentPurpose = convertContentPurpose(purpose);
384 if (this->contentHints != contentHints || this->contentPurpose != contentPurpose) {
387 Q_EMIT q->contentTypeChanged();
388 }
389}
390
391void TextInputV2InterfacePrivate::zwp_text_input_v2_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
392{
393 const QRect rect = QRect(x, y, width, height);
394 if (cursorRectangle != rect) {
395 cursorRectangle = rect;
397 }
398}
399
407
412
413QList<TextInputV2InterfacePrivate::Resource *> TextInputV2InterfacePrivate::textInputsForClient(ClientConnection *client) const
414{
415 return resourceMap().values(client->client());
416}
417
418TextInputV2Interface::TextInputV2Interface(SeatInterface *seat)
419 : QObject(seat)
420 , d(new TextInputV2InterfacePrivate(seat, this))
421{
422}
423
425
427{
428 return d->preferredLanguage;
429}
430
431TextInputContentHints TextInputV2Interface::contentHints() const
432{
433 return d->contentHints;
434}
435
437{
438 return d->contentPurpose;
439}
440
442{
443 return d->surroundingText;
444}
445
447{
448 return d->surroundingTextCursorPosition;
449}
450
452{
453 return d->surroundingTextSelectionAnchor;
454}
455
456void TextInputV2Interface::preEdit(const QString &text, const QString &commit)
457{
458 d->preEdit(text, commit);
459}
460
461void TextInputV2Interface::preEditStyling(uint32_t index, uint32_t length, uint32_t style)
462{
463 d->preEditStyling(index, length, style);
464}
465
466void TextInputV2Interface::commitString(const QString &text)
467{
468 d->commitString(text);
469}
470
471void TextInputV2Interface::keysymPressed(quint32 keysym, quint32 modifiers)
472{
473 d->keysymPressed(keysym, modifiers);
474}
475
476void TextInputV2Interface::keysymReleased(quint32 keysym, quint32 modifiers)
477{
478 d->keysymReleased(keysym, modifiers);
479}
480
481void TextInputV2Interface::deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
482{
483 d->deleteSurroundingText(beforeLength, afterLength);
484}
485
486void TextInputV2Interface::setCursorPosition(qint32 index, qint32 anchor)
487{
488 d->setCursorPosition(index, anchor);
489}
490
491void TextInputV2Interface::setTextDirection(Qt::LayoutDirection direction)
492{
493 d->setTextDirection(direction);
494}
495
497{
498 d->setPreEditCursor(index);
499}
500
501void TextInputV2Interface::setInputPanelState(bool visible, const QRect &overlappedSurfaceArea)
502{
503 if (d->inputPanelVisible == visible && d->overlappedSurfaceArea == overlappedSurfaceArea) {
504 // not changed
505 return;
506 }
507 d->inputPanelVisible = visible;
508 d->overlappedSurfaceArea = overlappedSurfaceArea;
509 d->sendInputPanelState();
510}
511
512void TextInputV2Interface::setLanguage(const QString &languageTag)
513{
514 if (d->language == languageTag) {
515 // not changed
516 return;
517 }
518 d->language = languageTag;
519 d->sendLanguage();
520}
521
522void TextInputV2Interface::setModifiersMap(const QByteArray &modifiersMap)
523{
524 if (d->modifiersMap == modifiersMap) {
525 // not changed
526 return;
527 }
528 d->modifiersMap = modifiersMap;
529 d->sendModifiersMap();
530}
531
532QPointer<SurfaceInterface> TextInputV2Interface::surface() const
533{
534 if (!d->surface) {
535 return nullptr;
536 }
537
538 if (!d->resourceMap().contains(d->surface->client()->client())) {
539 return nullptr;
540 }
541
542 return d->surface;
543}
544
546{
547 return d->cursorRectangle;
548}
549
551{
552 return d->surface && d->m_enabledSurfaces.contains(d->surface);
553}
554
556{
557 return client && d->resourceMap().contains(*client);
558}
559}
560
561#include "moc_textinput_v2.cpp"
Convenient Class which represents a wl_client.
wl_client * client() const
Class holding the Wayland server display loop.
Definition display.h:34
quint32 nextSerial()
Definition display.cpp:134
Represents a Seat on the Wayland Display.
Definition seat.h:134
Display * display() const
Definition seat.cpp:424
static SeatInterface * get(wl_resource *native)
Definition seat.cpp:429
TextInputV2Interface * textInputV2() const
Definition seat.cpp:1269
std::chrono::milliseconds timestamp() const
Definition seat.cpp:483
Resource representing a wl_surface.
Definition surface.h:80
ClientConnection * client() const
Definition surface.cpp:444
static SurfaceInterface * get(wl_resource *native)
Definition surface.cpp:819
wl_resource * resource() const
Definition surface.cpp:449
Represent the Global for the interface.
TextInputManagerV2Interface(Display *display, QObject *parent=nullptr)
void zwp_text_input_manager_v2_destroy(Resource *resource) override
TextInputManagerV2InterfacePrivate(TextInputManagerV2Interface *_q, Display *display)
void zwp_text_input_manager_v2_get_text_input(Resource *resource, uint32_t id, wl_resource *seat) override
Represents a generic Resource for a text input object.
void preEdit(const QString &text, const QString &commitText)
void setPreEditCursor(qint32 index)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
void setInputPanelState(bool visible, const QRect &overlappedSurfaceArea)
bool clientSupportsTextInput(ClientConnection *client) const
TextInputContentHints contentHints() const
qint32 surroundingTextCursorPosition() const
void keysymPressed(quint32 keysym, quint32 modifiers=0)
QString surroundingText() const
void setTextDirection(Qt::LayoutDirection direction)
void preEditStyling(uint32_t index, uint32_t length, uint32_t style)
void setCursorPosition(qint32 index, qint32 anchor)
QString preferredLanguage() const
void stateUpdated(uint32_t serial, UpdateReason reason)
void preferredLanguageChanged(const QString &language)
QPointer< SurfaceInterface > surface() const
TextInputContentPurpose contentPurpose() const
void commitString(const QString &text)
void setModifiersMap(const QByteArray &modifiersMap)
void cursorRectangleChanged(const QRect &rect)
void keysymReleased(quint32 keysym, quint32 modifiers=0)
qint32 surroundingTextSelectionAnchor() const
void setLanguage(const QString &languageTag)
void sendEnter(SurfaceInterface *surface, quint32 serial)
void zwp_text_input_v2_show_input_panel(Resource *resource) override
void keysymReleased(quint32 keysym, quint32 modifiers)
void zwp_text_input_v2_disable(Resource *resource, wl_resource *surface) override
QSet< SurfaceInterface * > m_enabledSurfaces
QPointer< SurfaceInterface > surface
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
void preEditStyling(uint32_t index, uint32_t length, uint32_t style)
void setTextDirection(Qt::LayoutDirection direction)
void zwp_text_input_v2_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override
void zwp_text_input_v2_enable(Resource *resource, wl_resource *surface) override
TextInputContentPurpose contentPurpose
void preEdit(const QString &text, const QString &commit)
void zwp_text_input_v2_set_preferred_language(Resource *resource, const QString &language) override
void zwp_text_input_v2_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor) override
void zwp_text_input_v2_update_state(Resource *resource, uint32_t serial, uint32_t reason) override
static TextInputV2InterfacePrivate * get(TextInputV2Interface *inputInterface)
void sendLeave(quint32 serial, SurfaceInterface *surface)
void keysymPressed(quint32 keysym, quint32 modifiers)
void setCursorPosition(qint32 index, qint32 anchor)
TextInputV2InterfacePrivate(SeatInterface *seat, TextInputV2Interface *_q)
QList< Resource * > textInputsForClient(ClientConnection *client) const
void commitString(const QString &text)
void zwp_text_input_v2_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose) override
TextInputContentHints contentHints
void zwp_text_input_v2_hide_input_panel(Resource *resource) override
TextInputContentPurpose
Definition textinput.h:72