summaryrefslogtreecommitdiff
path: root/src/gui/gui.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/gui.zig')
-rw-r--r--src/gui/gui.zig39
1 files changed, 35 insertions, 4 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 7e34a928..82866a30 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -2473,10 +2473,19 @@ fn dispatch(g: *Gui, core: *pardes.Pardes, sev: *const c.SDL_Event) void {
while (tptr[tlen] != 0) : (tlen += 1) {}
if (tlen == 0) return;
const text: []const u8 = tptr[0..tlen];
- const cplen = std.unicode.utf8ByteSequenceLength(text[0]) catch return;
- if (cplen > text.len) return;
- const cp = std.unicode.utf8Decode(text[0..cplen]) catch return;
- core.update(.{ .key = .{ .cp = cp, .text = text[0..cplen], .ctrl = g.live_ctrl, .alt = g.live_alt } });
+ // ONE event is not one codepoint. An IME commit arrives whole —
+ // the entire phrase the candidate window was holding — and so does
+ // anything composed (dead keys, `Ctrl-Shift-u`, a compose-key
+ // sequence that resolves to more than one scalar). Decoding only
+ // text[0] dropped the rest on the floor, silently. Validate the
+ // whole string first so a truncated or malformed sequence costs
+ // nothing rather than half a phrase already forwarded.
+ const view = std.unicode.Utf8View.init(text) catch return;
+ var it = view.iterator();
+ var at: usize = 0;
+ while (it.nextCodepoint()) |cp| : (at = it.i) {
+ core.update(.{ .key = .{ .cp = cp, .text = text[at..it.i], .ctrl = g.live_ctrl, .alt = g.live_alt } });
+ }
},
c.SDL_EVENT_MOUSE_BUTTON_DOWN, c.SDL_EVENT_MOUSE_BUTTON_UP => {
const b = sev.button;
@@ -2959,6 +2968,19 @@ fn drainEffects(
defer gpa.free(z);
_ = c.SDL_SetClipboardText(z.ptr);
},
+ .read_clipboard => {
+ if (g == null) continue;
+ // SDL answers synchronously, so the paste the core is waiting on
+ // lands inside this same drain — nothing to remember, no reply
+ // path to plumb. SDL3 hands over an OWNED copy that is ours to
+ // SDL_free, and reports "no text" as an EMPTY string rather than
+ // null, so the length check is what actually rejects a miss.
+ const raw = c.SDL_GetClipboardText() orelse continue;
+ defer c.SDL_free(raw);
+ const text = std.mem.span(raw);
+ if (text.len == 0) continue;
+ core.update(.{ .paste = text });
+ },
.lsp => |e| if (threads_ok) spawnLsp(core, queue, e),
.pipe => |e| if (threads_ok) spawnPipe(core, io, gpa, queue, pipe_tasks, e),
.watch => |w| {
@@ -3010,6 +3032,15 @@ fn drainEffectsWeb(core: *pardes.Pardes, gpa: std.mem.Allocator, g: *Gui) void {
defer gpa.free(z);
_ = c.SDL_SetClipboardText(z.ptr);
},
+ .read_clipboard => {
+ // emscripten fills SDL's clipboard only from a real paste gesture,
+ // so an empty answer here is the ordinary case and not a failure.
+ const raw = c.SDL_GetClipboardText() orelse continue;
+ defer c.SDL_free(raw);
+ const text = std.mem.span(raw);
+ if (text.len == 0) continue;
+ core.update(.{ .paste = text });
+ },
// look on a URL → a new tab
.open_link => |url| openLinkWeb(gpa, url.slice()),
// nothing to spawn/write/resize/save/dump into, and no filesystem to