1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if SDL_VIDEO_DRIVER_WAYLAND
24
25#include "../SDL_sysvideo.h"
26#include "SDL_waylandvideo.h"
27#include "SDL_waylandevents_c.h"
28#include "../../events/SDL_keyboard_c.h"
29#include "text-input-unstable-v3-client-protocol.h"
30
31bool Wayland_InitKeyboard(SDL_VideoDevice *_this)
32{
33#ifdef SDL_USE_IME
34 SDL_VideoData *internal = _this->internal;
35 if (!internal->text_input_manager) {
36 SDL_IME_Init();
37 }
38#endif
39 SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
40
41 return true;
42}
43
44void Wayland_QuitKeyboard(SDL_VideoDevice *_this)
45{
46#ifdef SDL_USE_IME
47 SDL_VideoData *internal = _this->internal;
48 if (!internal->text_input_manager) {
49 SDL_IME_Quit();
50 }
51#endif
52}
53
54bool Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
55{
56 SDL_VideoData *internal = _this->internal;
57 struct SDL_WaylandInput *input = internal->input;
58
59 if (internal->text_input_manager) {
60 if (input && input->text_input) {
61 const SDL_Rect *rect = &input->text_input->cursor_rect;
62 enum zwp_text_input_v3_content_hint hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE;
63 enum zwp_text_input_v3_content_purpose purpose;
64
65 switch (SDL_GetTextInputType(props)) {
66 default:
67 case SDL_TEXTINPUT_TYPE_TEXT:
68 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
69 break;
70 case SDL_TEXTINPUT_TYPE_TEXT_NAME:
71 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME;
72 break;
73 case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
74 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL;
75 break;
76 case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
77 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
78 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
79 break;
80 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
81 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
82 hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
83 break;
84 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
85 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
86 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
87 break;
88 case SDL_TEXTINPUT_TYPE_NUMBER:
89 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER;
90 break;
91 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
92 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
93 hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
94 break;
95 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
96 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
97 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
98 break;
99 }
100
101 switch (SDL_GetTextInputCapitalization(props)) {
102 default:
103 case SDL_CAPITALIZE_NONE:
104 break;
105 case SDL_CAPITALIZE_LETTERS:
106 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE;
107 break;
108 case SDL_CAPITALIZE_WORDS:
109 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE;
110 break;
111 case SDL_CAPITALIZE_SENTENCES:
112 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION;
113 break;
114 }
115
116 if (SDL_GetTextInputAutocorrect(props)) {
117 hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK);
118 }
119 if (SDL_GetTextInputMultiline(props)) {
120 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE;
121 }
122
123 zwp_text_input_v3_enable(input->text_input->text_input);
124
125 // Now that it's enabled, set the input properties
126 zwp_text_input_v3_set_content_type(input->text_input->text_input, hint, purpose);
127 if (!SDL_RectEmpty(rect)) {
128 // This gets reset on enable so we have to cache it
129 zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input,
130 rect->x,
131 rect->y,
132 rect->w,
133 rect->h);
134 }
135 zwp_text_input_v3_commit(input->text_input->text_input);
136 }
137 }
138
139 if (input && input->xkb.compose_state) {
140 // Reset compose state so composite and dead keys don't carry over
141 WAYLAND_xkb_compose_state_reset(input->xkb.compose_state);
142 }
143
144 return Wayland_UpdateTextInputArea(_this, window);
145}
146
147bool Wayland_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
148{
149 SDL_VideoData *internal = _this->internal;
150 struct SDL_WaylandInput *input = internal->input;
151
152 if (internal->text_input_manager) {
153 if (input && input->text_input) {
154 zwp_text_input_v3_disable(input->text_input->text_input);
155 zwp_text_input_v3_commit(input->text_input->text_input);
156 }
157 }
158#ifdef SDL_USE_IME
159 else {
160 SDL_IME_Reset();
161 }
162#endif
163
164 if (input && input->xkb.compose_state) {
165 // Reset compose state so composite and dead keys don't carry over
166 WAYLAND_xkb_compose_state_reset(input->xkb.compose_state);
167 }
168 return true;
169}
170
171bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
172{
173 SDL_VideoData *internal = _this->internal;
174 if (internal->text_input_manager) {
175 struct SDL_WaylandInput *input = internal->input;
176 if (input && input->text_input) {
177 if (!SDL_RectsEqual(&window->text_input_rect, &input->text_input->cursor_rect)) {
178 SDL_copyp(&input->text_input->cursor_rect, &window->text_input_rect);
179 zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input,
180 window->text_input_rect.x,
181 window->text_input_rect.y,
182 window->text_input_rect.w,
183 window->text_input_rect.h);
184 zwp_text_input_v3_commit(input->text_input->text_input);
185 }
186 }
187 }
188
189#ifdef SDL_USE_IME
190 else {
191 SDL_IME_UpdateTextInputArea(window);
192 }
193#endif
194 return true;
195}
196
197bool Wayland_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
198{
199 /* In reality we just want to return true when the screen keyboard is the
200 * _only_ way to get text input. So, in addition to checking for the text
201 * input protocol, make sure we don't have any physical keyboards either.
202 */
203 SDL_VideoData *internal = _this->internal;
204 bool haskeyboard = (internal->input != NULL) && (internal->input->keyboard != NULL);
205 bool hastextmanager = (internal->text_input_manager != NULL);
206 return !haskeyboard && hastextmanager;
207}
208
209#endif // SDL_VIDEO_DRIVER_WAYLAND
210