Merge pull request #919 from mitchellh/gtk-ime

apprt/gtk: support IME popups such as Asian language input
This commit is contained in:
Mitchell Hashimoto
2023-11-20 19:01:23 -08:00
committed by GitHub

View File

@ -658,6 +658,11 @@ fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
log.err("surface failed to realize: {}", .{err});
return;
};
// When we have a realized surface, we also attach our input method context.
// We do this here instead of init because this allows us to relase the ref
// to the GLArea when we unrealized.
c.gtk_im_context_set_client_widget(self.im_context, @ptrCast(self.gl_area));
}
/// This is called when the underlying OpenGL resources must be released.
@ -667,6 +672,9 @@ fn gtkUnrealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
const self = userdataSelf(ud.?);
self.core_surface.renderer.displayUnrealized();
// See gtkRealize for why we do this here.
c.gtk_im_context_set_client_widget(self.im_context, null);
}
/// render signal
@ -924,6 +932,17 @@ fn keyEvent(
// We only want to send the event through the IM context if we're a press
if (action == .press or action == .repeat) {
// This can trigger an input method so we need to notify the im context
// where the cursor is so it can render the dropdowns in the correct
// place.
const ime_point = self.core_surface.imePoint();
c.gtk_im_context_set_cursor_location(self.im_context, &.{
.x = @intFromFloat(ime_point.x),
.y = @intFromFloat(ime_point.y),
.width = 1,
.height = 1,
});
// We mark that we're in a keypress event. We use this in our
// IM commit callback to determine if we need to send a char callback
// to the core surface or not.
@ -932,7 +951,8 @@ fn keyEvent(
// Pass the event through the IM controller to handle dead key states.
// Filter is true if the event was handled by the IM controller.
_ = c.gtk_im_context_filter_keypress(self.im_context, event) != 0;
const im_handled = c.gtk_im_context_filter_keypress(self.im_context, event) != 0;
// log.warn("im_handled={} im_len={} im_composing={}", .{ im_handled, self.im_len, self.im_composing });
// If this is a dead key, then we're composing a character and
// we need to set our proper preedit state.
@ -942,10 +962,19 @@ fn keyEvent(
log.err("error in preedit callback err={}", .{err});
break :preedit;
};
// If we're composing then we don't want to send the key
// event to the core surface so we always return immediately.
if (im_handled) return true;
} else {
// If we aren't composing, then we set our preedit to
// empty no matter what.
self.core_surface.preeditCallback(null) catch {};
// If the IM handled this and we have no text, then we just
// return because this probably just changed the input method
// or something.
if (im_handled and self.im_len == 0) return true;
}
}
@ -1056,6 +1085,7 @@ fn gtkInputPreeditStart(
// Mark that we are now composing a string with a dead key state.
// We'll record the string in the preedit-changed callback.
self.im_composing = true;
self.im_len = 0;
}
fn gtkInputPreeditChanged(
@ -1071,6 +1101,12 @@ fn gtkInputPreeditChanged(
defer c.g_free(buf);
const str = std.mem.sliceTo(buf, 0);
// If our string becomes empty we ignore this. This can happen after
// a commit event when the preedit is being cleared and we don't want
// to set im_len to zero. This is safe because preeditstart always sets
// im_len to zero.
if (str.len == 0) return;
// Copy the preedit string into the im_buf. This is safe because
// commit will always overwrite this.
self.im_len = @intCast(@min(self.im_buf.len, str.len));
@ -1085,7 +1121,6 @@ fn gtkInputPreeditEnd(
const self = userdataSelf(ud.?);
if (!self.in_keypress) return;
self.im_composing = false;
self.im_len = 0;
}
fn gtkInputCommit(
@ -1104,7 +1139,7 @@ fn gtkInputCommit(
@memcpy(self.im_buf[0..str.len], str);
self.im_len = @intCast(str.len);
// log.debug("input commit: {x}", .{self.im_buf[0]});
// log.debug("input commit len={}", .{self.im_len});
} else {
log.warn("not enough buffer space for input method commit", .{});
}
@ -1131,6 +1166,11 @@ fn gtkInputCommit(
fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) void {
const self = userdataSelf(ud.?);
// Notify our IM context
c.gtk_im_context_focus_in(self.im_context);
// Notify our surface
self.core_surface.focusCallback(true) catch |err| {
log.err("error in focus callback err={}", .{err});
return;
@ -1139,6 +1179,10 @@ fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) vo
fn gtkFocusLeave(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) void {
const self = userdataSelf(ud.?);
// Notify our IM context
c.gtk_im_context_focus_out(self.im_context);
self.core_surface.focusCallback(false) catch |err| {
log.err("error in focus callback err={}", .{err});
return;