KWin
Loading...
Searching...
No Matches
textinput_v1.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2022 Xuetian Weng <wengxt@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
7#include "display.h"
8#include "seat_p.h"
9#include "surface_p.h"
10#include "textinput_v1_p.h"
11#include "wayland/display_p.h"
12#include "wayland/seat.h"
13#include "wayland/surface.h"
14#include <algorithm>
15
16namespace KWin
17{
18
19namespace
20{
21const quint32 s_version = 1;
22
23// helpers
24TextInputContentHints convertContentHint(uint32_t hint)
25{
26 const auto hints = QtWaylandServer::zwp_text_input_v1::content_hint(hint);
27 TextInputContentHints ret = TextInputContentHint::None;
28
29 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_auto_completion) {
31 }
32 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction) {
34 }
35 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization) {
37 }
38 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_lowercase) {
40 }
41 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_uppercase) {
43 }
44 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_titlecase) {
46 }
47 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_hidden_text) {
49 }
50 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_sensitive_data) {
52 }
53 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_latin) {
55 }
56 if (hints & QtWaylandServer::zwp_text_input_v1::content_hint_multiline) {
58 }
59 return ret;
60}
61
62TextInputContentPurpose convertContentPurpose(uint32_t purpose)
63{
64 const auto wlPurpose = QtWaylandServer::zwp_text_input_v1::content_purpose(purpose);
65
66 switch (wlPurpose) {
67 case QtWaylandServer::zwp_text_input_v1::content_purpose_alpha:
69 case QtWaylandServer::zwp_text_input_v1::content_purpose_digits:
71 case QtWaylandServer::zwp_text_input_v1::content_purpose_number:
73 case QtWaylandServer::zwp_text_input_v1::content_purpose_phone:
75 case QtWaylandServer::zwp_text_input_v1::content_purpose_url:
77 case QtWaylandServer::zwp_text_input_v1::content_purpose_email:
79 case QtWaylandServer::zwp_text_input_v1::content_purpose_name:
81 case QtWaylandServer::zwp_text_input_v1::content_purpose_password:
83 case QtWaylandServer::zwp_text_input_v1::content_purpose_date:
85 case QtWaylandServer::zwp_text_input_v1::content_purpose_time:
87 case QtWaylandServer::zwp_text_input_v1::content_purpose_datetime:
89 case QtWaylandServer::zwp_text_input_v1::content_purpose_terminal:
91 case QtWaylandServer::zwp_text_input_v1::content_purpose_normal:
93 default:
95 }
96}
97
98class EnabledEmitter
99{
100public:
101 EnabledEmitter(TextInputV1Interface *q)
102 : q(q)
103 , m_wasEnabled(q->isEnabled())
104 {
105 }
106 ~EnabledEmitter()
107 {
108 if (m_wasEnabled != q->isEnabled()) {
109 Q_EMIT q->enabledChanged();
110 }
111 }
112
113private:
114 TextInputV1Interface *q;
115 const bool m_wasEnabled;
116};
117
118}
119
121 : QtWaylandServer::zwp_text_input_manager_v1(*_display, s_version)
122 , q(_q)
123 , display(_display)
124{
125}
126
128{
129 auto seats = display->seats();
130 if (seats.isEmpty()) {
131 wl_resource_post_error(resource->handle, 0, "No seat on display.");
132 return;
133 }
134 // KWin only has one seat, so it's ok to make textInputV1 belongs to the SeatInterface.
136 textInputPrivate->add(resource->client(), id, resource->version());
137}
138
140 : QObject(parent)
141 , d(new TextInputManagerV1InterfacePrivate(this, display))
142{
143}
144
146
148{
149 EnabledEmitter emitter(q);
150 // It should be always synchronized with SeatInterface::focusedTextInputSurface.
151 Q_ASSERT(newSurface);
152 surface = newSurface;
153
154 if (auto resource = activated.value(newSurface)) {
155 send_enter(resource->handle, newSurface->resource());
156 }
157}
158
160{
161 // It should be always synchronized with SeatInterface::focusedTextInputSurface.
162 Q_ASSERT(leavingSurface && surface == leavingSurface);
163 EnabledEmitter emitter(q);
164 surface = nullptr;
165
166 if (auto resource = activated.value(leavingSurface)) {
167 send_leave(resource->handle);
168 }
169}
170
171void TextInputV1InterfacePrivate::preEdit(const QString &text, const QString &commit)
172{
173 forActivatedResource([this, &text, &commit](Resource *resource) {
174 send_preedit_string(resource->handle, serialHash.value(resource), text, commit);
175 });
176}
177
178void TextInputV1InterfacePrivate::preEditStyling(uint32_t index, uint32_t length, uint32_t style)
179{
180 forActivatedResource([this, index, length, style](Resource *resource) {
181 send_preedit_styling(resource->handle, index, length, style);
182 });
183}
184
186{
187 forActivatedResource([this, text](Resource *resource) {
188 send_commit_string(resource->handle, serialHash.value(resource), text);
189 });
190}
191
192void TextInputV1InterfacePrivate::keysymPressed(quint32 time, quint32 keysym, quint32 modifiers)
193{
194 forActivatedResource([this, time, keysym, modifiers](Resource *resource) {
195 send_keysym(resource->handle, serialHash.value(resource), time, keysym, WL_KEYBOARD_KEY_STATE_PRESSED, modifiers);
196 });
197}
198
199void TextInputV1InterfacePrivate::keysymReleased(quint32 time, quint32 keysym, quint32 modifiers)
200{
201 forActivatedResource([this, time, keysym, modifiers](Resource *resource) {
202 send_keysym(resource->handle, serialHash.value(resource), time, keysym, WL_KEYBOARD_KEY_STATE_RELEASED, modifiers);
203 });
204}
205
207{
208 forActivatedResource([this, index, length](Resource *resource) {
209 send_delete_surrounding_text(resource->handle, index, length);
210 });
211}
212
213void TextInputV1InterfacePrivate::setCursorPosition(qint32 index, qint32 anchor)
214{
215 forActivatedResource([this, index, anchor](Resource *resource) {
216 send_cursor_position(resource->handle, index, anchor);
217 });
218}
219
220void TextInputV1InterfacePrivate::setTextDirection(Qt::LayoutDirection direction)
221{
222 text_direction wlDirection;
223 switch (direction) {
224 case Qt::LeftToRight:
225 wlDirection = text_direction::text_direction_ltr;
226 break;
227 case Qt::RightToLeft:
228 wlDirection = text_direction::text_direction_rtl;
229 break;
230 case Qt::LayoutDirectionAuto:
231 wlDirection = text_direction::text_direction_auto;
232 break;
233 default:
234 Q_UNREACHABLE();
235 break;
236 }
237 forActivatedResource([this, wlDirection](Resource *resource) {
238 send_text_direction(resource->handle, serialHash.value(resource), wlDirection);
239 });
240}
241
243{
244 forActivatedResource([this, index](Resource *resource) {
245 send_preedit_cursor(resource->handle, index);
246 });
247}
248
250{
251 forActivatedResource([this](Resource *resource) {
252 send_input_panel_state(resource->handle,
254 });
255}
256
258{
259 forActivatedResource([this](Resource *resource) {
260 send_language(resource->handle, serialHash.value(resource), language);
261 });
262}
263
265{
266 forActivatedResource([this](Resource *resource) {
267 send_modifiers_map(resource->handle, modifiersMap);
268 });
269}
270
276
278{
279 serialHash.insert(resource, 0);
280}
281
283{
284 serialHash.remove(resource);
285 for (auto iter = activated.begin(); iter != activated.end();) {
286 if (iter.value() == resource) {
287 iter = activated.erase(iter);
288 } else {
289 ++iter;
290 }
291 }
292}
293
294void TextInputV1InterfacePrivate::zwp_text_input_v1_activate(Resource *resource, struct wl_resource *seat, struct wl_resource *surface)
295{
296 auto *s = SeatInterface::get(seat);
297 if (!s || this->seat != s) {
298 wl_resource_post_error(resource->handle, 0, "Invalid seat");
299 return;
300 }
301 EnabledEmitter emitter(q);
302 auto enabledSurface = SurfaceInterface::get(surface);
303 auto *oldResource = activated.value(enabledSurface);
304 if (oldResource == resource) {
305 return;
306 }
307 activated[enabledSurface] = resource;
308 if (this->surface == enabledSurface) {
309 if (oldResource) {
310 send_leave(oldResource->handle);
311 }
312 send_enter(resource->handle, surface);
313 }
314 QObject::connect(enabledSurface, &SurfaceInterface::aboutToBeDestroyed, q, [this, enabledSurface] {
315 EnabledEmitter emitter(q);
316 activated.remove(enabledSurface);
317 });
318}
319
320void TextInputV1InterfacePrivate::zwp_text_input_v1_deactivate(Resource *resource, wl_resource *seat)
321{
322 auto s = SeatInterface::get(seat);
323 if (!s || this->seat != s) {
324 wl_resource_post_error(resource->handle, 0, "Invalid seat");
325 return;
326 }
327 EnabledEmitter emitter(q);
328
329 for (auto iter = activated.begin(); iter != activated.end();) {
330 if (iter.value() == resource) {
331 if (this->surface && this->surface == iter.key()) {
332 send_leave(resource->handle);
333 }
334 iter = activated.erase(iter);
335 } else {
336 ++iter;
337 }
338 }
339}
340
342{
343 Q_EMIT q->reset();
344}
345
346void TextInputV1InterfacePrivate::zwp_text_input_v1_commit_state(Resource *resource, uint32_t serial)
347{
348 serialHash[resource] = serial;
349 Q_EMIT q->stateUpdated(serial);
350}
351
352void TextInputV1InterfacePrivate::zwp_text_input_v1_invoke_action(Resource *resource, uint32_t button, uint32_t index)
353{
354 Q_EMIT q->invokeAction(button, index);
355}
356
361
362void TextInputV1InterfacePrivate::zwp_text_input_v1_set_surrounding_text(Resource *resource, const QString &text, uint32_t cursor, uint32_t anchor)
363{
364 surroundingText = text;
367 Q_EMIT q->surroundingTextChanged();
368}
369
370void TextInputV1InterfacePrivate::zwp_text_input_v1_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose)
371{
372 const auto contentHints = convertContentHint(hint);
373 const auto contentPurpose = convertContentPurpose(purpose);
374 if (this->contentHints != contentHints || this->contentPurpose != contentPurpose) {
377 Q_EMIT q->contentTypeChanged();
378 }
379}
380
381void TextInputV1InterfacePrivate::zwp_text_input_v1_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
382{
383 const QRect rect = QRect(x, y, width, height);
384 if (cursorRectangle != rect) {
385 cursorRectangle = rect;
387 }
388}
389
397
402
403QList<TextInputV1InterfacePrivate::Resource *> TextInputV1InterfacePrivate::textInputsForClient(ClientConnection *client) const
404{
405 return resourceMap().values(client->client());
406}
407
409 : QObject(seat)
410 , d(new TextInputV1InterfacePrivate(seat, this))
411{
412}
413
417
419{
420 return d->preferredLanguage;
421}
422
423TextInputContentHints TextInputV1Interface::contentHints() const
424{
425 return d->contentHints;
426}
427
429{
430 return d->contentPurpose;
431}
432
434{
435 return d->surroundingText;
436}
437
439{
440 return d->surroundingTextCursorPosition;
441}
442
444{
445 return d->surroundingTextSelectionAnchor;
446}
447
448void TextInputV1Interface::preEdit(const QString &text, const QString &commit)
449{
450 d->preEdit(text, commit);
451}
452
453void TextInputV1Interface::preEditStyling(uint32_t index, uint32_t length, uint32_t style)
454{
455 d->preEditStyling(index, length, style);
456}
457
458void TextInputV1Interface::commitString(const QString &text)
459{
460 d->commitString(text);
461}
462
463void TextInputV1Interface::keysymPressed(quint32 time, quint32 keysym, quint32 modifiers)
464{
465 d->keysymPressed(time, keysym, modifiers);
466}
467
468void TextInputV1Interface::keysymReleased(quint32 time, quint32 keysym, quint32 modifiers)
469{
470 d->keysymReleased(time, keysym, modifiers);
471}
472
473void TextInputV1Interface::deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
474{
475 d->deleteSurroundingText(beforeLength, afterLength);
476}
477
478void TextInputV1Interface::setCursorPosition(qint32 index, qint32 anchor)
479{
480 d->setCursorPosition(index, anchor);
481}
482
483void TextInputV1Interface::setTextDirection(Qt::LayoutDirection direction)
484{
485 d->setTextDirection(direction);
486}
487
489{
490 d->setPreEditCursor(index);
491}
492
494{
495 if (d->inputPanelVisible == visible) {
496 // not changed
497 return;
498 }
499 d->inputPanelVisible = visible;
500 d->sendInputPanelState();
501}
502
503void TextInputV1Interface::setLanguage(const QString &languageTag)
504{
505 if (d->language == languageTag) {
506 // not changed
507 return;
508 }
509 d->language = languageTag;
510 d->sendLanguage();
511}
512
513void TextInputV1Interface::setModifiersMap(const QByteArray &modifiersMap)
514{
515 if (d->modifiersMap == modifiersMap) {
516 // not changed
517 return;
518 }
519 d->modifiersMap = modifiersMap;
520 d->sendModifiersMap();
521}
522
523QPointer<SurfaceInterface> TextInputV1Interface::surface() const
524{
525 if (!d->resourceMap().contains(d->surface->client()->client())) {
526 return nullptr;
527 }
528
529 return d->surface;
530}
531
533{
534 return d->cursorRectangle;
535}
536
538{
539 if (!d->surface) {
540 return false;
541 }
542 return d->activated.contains(d->surface);
543}
544
546{
547 return client && d->resourceMap().contains(*client);
548}
549}
550
551#include "moc_textinput_v1.cpp"
Convenient Class which represents a wl_client.
wl_client * client() const
Class holding the Wayland server display loop.
Definition display.h:34
QList< SeatInterface * > seats() const
Definition display.cpp:195
Represents a Seat on the Wayland Display.
Definition seat.h:134
static SeatInterface * get(wl_resource *native)
Definition seat.cpp:429
Resource representing a wl_surface.
Definition surface.h:80
static SurfaceInterface * get(wl_resource *native)
Definition surface.cpp:819
wl_resource * resource() const
Definition surface.cpp:449
Represent the Global for the interface.
TextInputManagerV1Interface(Display *display, QObject *parent=nullptr)
std::unique_ptr< TextInputV1Interface > textInputV1
void zwp_text_input_manager_v1_create_text_input(Resource *resource, uint32_t id) override
TextInputManagerV1InterfacePrivate(TextInputManagerV1Interface *_q, Display *display)
Represents a generic Resource for a text input object.
qint32 surroundingTextSelectionAnchor() const
TextInputContentPurpose contentPurpose() const
void keysymReleased(quint32 time, quint32 keysym, quint32 modifiers=0)
QString preferredLanguage() const
bool clientSupportsTextInput(ClientConnection *client) const
void stateUpdated(quint32 serial)
QPointer< SurfaceInterface > surface() const
void keysymPressed(quint32 time, quint32 keysym, quint32 modifiers=0)
void cursorRectangleChanged(const QRect &rect)
void preEditStyling(uint32_t index, uint32_t length, uint32_t style)
void setInputPanelState(bool visible)
void setLanguage(const QString &languageTag)
qint32 surroundingTextCursorPosition() const
void setPreEditCursor(qint32 index)
void preEdit(const QString &text, const QString &commitText)
void preferredLanguageChanged(const QString &language)
void commitString(const QString &text)
void setTextDirection(Qt::LayoutDirection direction)
void setModifiersMap(const QByteArray &modifiersMap)
void setCursorPosition(qint32 index, qint32 anchor)
void invokeAction(quint32 button, quint32 index)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
TextInputContentHints contentHints() const
QString surroundingText() const
TextInputV1Interface(SeatInterface *seat)
void keysymReleased(quint32 time, quint32 keysym, quint32 modifiers)
QHash< Resource *, quint32 > serialHash
void sendEnter(SurfaceInterface *surface)
TextInputContentPurpose contentPurpose
void zwp_text_input_v1_show_input_panel(Resource *resource) override
void setCursorPosition(qint32 index, qint32 anchor)
void keysymPressed(quint32 time, quint32 keysym, quint32 modifiers)
void commitString(const QString &text)
QPointer< SurfaceInterface > surface
void deleteSurroundingText(qint32 index, quint32 length)
void sendLeave(SurfaceInterface *surface)
void forActivatedResource(const T &callback)
void setTextDirection(Qt::LayoutDirection direction)
void preEditStyling(quint32 index, uint32_t length, uint32_t style)
void zwp_text_input_v1_reset(Resource *resource) override
void zwp_text_input_v1_set_preferred_language(Resource *resource, const QString &language) override
void preEdit(const QString &text, const QString &commit)
TextInputContentHints contentHints
void zwp_text_input_v1_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override
void zwp_text_input_v1_destroy_resource(Resource *resource) override
void zwp_text_input_v1_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose) override
void zwp_text_input_v1_deactivate(Resource *resource, wl_resource *seat) override
QList< Resource * > textInputsForClient(ClientConnection *client) const
QHash< SurfaceInterface *, Resource * > activated
void zwp_text_input_v1_hide_input_panel(Resource *resource) override
void zwp_text_input_v1_invoke_action(Resource *resource, uint32_t button, uint32_t index) override
void zwp_text_input_v1_bind_resource(Resource *resource) override
void zwp_text_input_v1_activate(Resource *resource, struct wl_resource *seat, struct wl_resource *surface) override
void zwp_text_input_v1_set_surrounding_text(Resource *resource, const QString &text, uint32_t cursor, uint32_t anchor) override
QPointer< SeatInterface > seat
void zwp_text_input_v1_commit_state(Resource *resource, uint32_t serial) override
TextInputV1InterfacePrivate(SeatInterface *seat, TextInputV1Interface *_q)
static TextInputV1InterfacePrivate * get(TextInputV1Interface *inputInterface)
TextInputContentPurpose
Definition textinput.h:72