SDL: wayland: Set the text input cursor rect properly

From c7549eb0b61309992df8cd00f17e0d63ba6e3895 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Tue, 20 May 2025 12:43:16 -0400
Subject: [PATCH] wayland: Set the text input cursor rect properly

The text input cursor should reflect the cursor position, not the entire text input rect. Set it correctly so that IME chooser dialogs appear in the correct location.
---
 src/video/wayland/SDL_waylandevents_c.h |  3 +-
 src/video/wayland/SDL_waylandkeyboard.c | 54 ++++++++++++++++++-------
 2 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index a80793922f874..cb7eb95ebbabc 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -170,7 +170,8 @@ typedef struct SDL_WaylandSeat
     struct
     {
         struct zwp_text_input_v3 *zwp_text_input;
-        SDL_Rect cursor_rect;
+        SDL_Rect text_input_rect;
+        int text_input_cursor;
         bool enabled;
         bool has_preedit;
     } text_input;
diff --git a/src/video/wayland/SDL_waylandkeyboard.c b/src/video/wayland/SDL_waylandkeyboard.c
index 967d76cef1fbe..ae5bb13b37ecd 100644
--- a/src/video/wayland/SDL_waylandkeyboard.c
+++ b/src/video/wayland/SDL_waylandkeyboard.c
@@ -61,6 +61,8 @@ void Wayland_UpdateTextInput(SDL_VideoData *display)
 
             if (seat->text_input.zwp_text_input) {
                 if (focus && focus->text_input_props.active) {
+                    SDL_Window *window = focus->sdlwindow;
+
                     // Enabling will reset all state, so don't do it redundantly.
                     if (!seat->text_input.enabled) {
                         seat->text_input.enabled = true;
@@ -68,15 +70,24 @@ void Wayland_UpdateTextInput(SDL_VideoData *display)
 
                         // Now that it's enabled, set the input properties
                         zwp_text_input_v3_set_content_type(seat->text_input.zwp_text_input, focus->text_input_props.hint, focus->text_input_props.purpose);
-                        if (!SDL_RectEmpty(&focus->sdlwindow->text_input_rect)) {
-                            SDL_copyp(&seat->text_input.cursor_rect, &focus->sdlwindow->text_input_rect);
-
-                            // This gets reset on enable so we have to cache it
+                        if (!SDL_RectEmpty(&window->text_input_rect)) {
+                            const SDL_Rect scaled_rect = {
+                                (int)SDL_floor(window->text_input_rect.x / focus->pointer_scale.x),
+                                (int)SDL_floor(window->text_input_rect.y / focus->pointer_scale.y),
+                                (int)SDL_ceil(window->text_input_rect.w / focus->pointer_scale.x),
+                                (int)SDL_ceil(window->text_input_rect.h / focus->pointer_scale.y)
+                            };
+                            const int scaled_cursor = (int)SDL_floor(window->text_input_cursor / focus->pointer_scale.x);
+
+                            SDL_copyp(&seat->text_input.text_input_rect, &scaled_rect);
+                            seat->text_input.text_input_cursor = scaled_cursor;
+
+                            // Clamp the x value so it doesn't run too far past the end of the text input area.
                             zwp_text_input_v3_set_cursor_rectangle(seat->text_input.zwp_text_input,
-                                                                   focus->sdlwindow->text_input_rect.x,
-                                                                   focus->sdlwindow->text_input_rect.y,
-                                                                   focus->sdlwindow->text_input_rect.w,
-                                                                   focus->sdlwindow->text_input_rect.h);
+                                                                   SDL_min(scaled_rect.x + scaled_cursor, scaled_rect.x + scaled_rect.w),
+                                                                   scaled_rect.y,
+                                                                   1,
+                                                                   scaled_rect.h);
                         }
                         zwp_text_input_v3_commit(seat->text_input.zwp_text_input);
 
@@ -88,7 +99,8 @@ void Wayland_UpdateTextInput(SDL_VideoData *display)
                 } else {
                     if (seat->text_input.enabled) {
                         seat->text_input.enabled = false;
-                        SDL_zero(seat->text_input.cursor_rect);
+                        SDL_zero(seat->text_input.text_input_rect);
+                        seat->text_input.text_input_cursor = 0;
                         zwp_text_input_v3_disable(seat->text_input.zwp_text_input);
                         zwp_text_input_v3_commit(seat->text_input.zwp_text_input);
                     }
@@ -203,13 +215,25 @@ bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
 
         wl_list_for_each (seat, &internal->seat_list, link) {
             if (seat->text_input.zwp_text_input && seat->keyboard.focus == window->internal) {
-                if (!SDL_RectsEqual(&window->text_input_rect, &seat->text_input.cursor_rect)) {
-                    SDL_copyp(&seat->text_input.cursor_rect, &window->text_input_rect);
+                SDL_WindowData *wind = window->internal;
+                const SDL_Rect scaled_rect = {
+                    (int)SDL_floor(window->text_input_rect.x / wind->pointer_scale.x),
+                    (int)SDL_floor(window->text_input_rect.y / wind->pointer_scale.y),
+                    (int)SDL_ceil(window->text_input_rect.w / wind->pointer_scale.x),
+                    (int)SDL_ceil(window->text_input_rect.h / wind->pointer_scale.y)
+                };
+                const int scaled_cursor = (int)SDL_floor(window->text_input_cursor / wind->pointer_scale.x);
+
+                if (!SDL_RectsEqual(&scaled_rect, &seat->text_input.text_input_rect) || scaled_cursor != seat->text_input.text_input_cursor) {
+                    SDL_copyp(&seat->text_input.text_input_rect, &scaled_rect);
+                    seat->text_input.text_input_cursor = scaled_cursor;
+
+                    // Clamp the x value so it doesn't run too far past the end of the text input area.
                     zwp_text_input_v3_set_cursor_rectangle(seat->text_input.zwp_text_input,
-                                                           window->text_input_rect.x,
-                                                           window->text_input_rect.y,
-                                                           window->text_input_rect.w,
-                                                           window->text_input_rect.h);
+                                                           SDL_min(scaled_rect.x + scaled_cursor, scaled_rect.x + scaled_rect.w),
+                                                           scaled_rect.y,
+                                                           1,
+                                                           scaled_rect.h);
                     zwp_text_input_v3_commit(seat->text_input.zwp_text_input);
                 }
             }