apprt/gtk: only use key2callback

This commit is contained in:
Mitchell Hashimoto
2023-08-16 13:02:31 -07:00
parent 83ba2b9825
commit 896d0e8fcf

View File

@ -1155,33 +1155,6 @@ pub const Surface = struct {
}; };
} }
/// Key press event. This is where we do ALL of our key handling,
/// translation to keyboard layouts, dead key handling, etc. Key handling
/// is complicated so this comment will explain what's going on.
///
/// At a high level, we want to do the following:
///
/// 1. Emit a keyCallback for the key press with the right keys.
/// 2. Emit a charCallback if a unicode char was generated from the
/// keypresses, but only if keyCallback didn't consume the input.
///
/// This callback will first set the "in_keypress" flag to true. This
/// lets our IM callbacks know that we're in a keypress event so they don't
/// emit a charCallback since this function will do it after the keyCallback
/// (remember, the order matters!).
///
/// Next, we run the keypress through the input method context in order
/// to determine if we're in a dead key state, completed unicode char, etc.
/// This all happens through various callbacks: preedit, commit, etc.
/// These inspect "in_keypress" if they have to and set some instance
/// state.
///
/// Finally, we map our keys to input.Keys, emit the keyCallback, then
/// emit the charCallback if we have to.
///
/// Note we ALSO have an IMContext attached directly to the widget
/// which can emit preedit and commit callbacks. But, if we're not
/// in a keypress, we let those automatically work.
fn gtkKeyPressed( fn gtkKeyPressed(
ec_key: *c.GtkEventControllerKey, ec_key: *c.GtkEventControllerKey,
keyval: c.guint, keyval: c.guint,
@ -1189,31 +1162,100 @@ pub const Surface = struct {
gtk_mods: c.GdkModifierType, gtk_mods: c.GdkModifierType,
ud: ?*anyopaque, ud: ?*anyopaque,
) callconv(.C) c.gboolean { ) callconv(.C) c.gboolean {
return if (keyEvent(.press, ec_key, keyval, keycode, gtk_mods, ud)) 1 else 0;
}
fn gtkKeyReleased(
ec_key: *c.GtkEventControllerKey,
keyval: c.guint,
keycode: c.guint,
state: c.GdkModifierType,
ud: ?*anyopaque,
) callconv(.C) c.gboolean {
return if (keyEvent(.release, ec_key, keyval, keycode, state, ud)) 1 else 0;
}
/// Key press event. This is where we do ALL of our key handling,
/// translation to keyboard layouts, dead key handling, etc. Key handling
/// is complicated so this comment will explain what's going on.
///
/// At a high level, we want to construct an `input.KeyEvent` and
/// pass that to `keyCallback`. At a low level, this is more complicated
/// than it appears because we need to construct all of this information
/// and its not given to us.
///
/// For press events, we run the keypress through the input method context
/// in order to determine if we're in a dead key state, completed unicode
/// char, etc. This all happens through various callbacks: preedit, commit,
/// etc. These inspect "in_keypress" if they have to and set some instance
/// state.
///
/// We then take all of the information in order to determine if we have
/// a unicode character or if we have to map the keyval to a code to
/// get the underlying logical key, etc.
///
/// Finally, we can emit the keyCallback.
///
/// Note we ALSO have an IMContext attached directly to the widget
/// which can emit preedit and commit callbacks. But, if we're not
/// in a keypress, we let those automatically work.
fn keyEvent(
action: input.Action,
ec_key: *c.GtkEventControllerKey,
keyval: c.guint,
keycode: c.guint,
gtk_mods: c.GdkModifierType,
ud: ?*anyopaque,
) bool {
const self = userdataSelf(ud.?); const self = userdataSelf(ud.?);
const mods = translateMods(gtk_mods); const mods = translateMods(gtk_mods);
const keyval_unicode = c.gdk_keyval_to_unicode(keyval); const keyval_unicode = c.gdk_keyval_to_unicode(keyval);
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec_key));
// 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.
self.in_keypress = true;
defer self.in_keypress = false;
// We always reset our committed text when ending a keypress so that // We always reset our committed text when ending a keypress so that
// future keypresses don't think we have a commit event. // future keypresses don't think we have a commit event.
defer self.im_len = 0; defer self.im_len = 0;
// We only want to send the event through the IM context if we're a press
if (action == .press or action == .repeat) {
// 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.
self.in_keypress = true;
defer self.in_keypress = false;
// 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;
// If this is a dead key, then we're composing a character and
// we need to set our proper preedit state.
if (self.im_composing) preedit: {
const text = self.im_buf[0..self.im_len];
const view = std.unicode.Utf8View.init(text) catch |err| {
log.warn("cannot build utf8 view over input: {}", .{err});
break :preedit;
};
var it = view.iterator();
const cp: u21 = it.nextCodepoint() orelse 0;
self.core_surface.preeditCallback(cp) catch |err| {
log.err("error in preedit callback err={}", .{err});
break :preedit;
};
} else {
// If we aren't composing, then we set our preedit to
// empty no matter what.
self.core_surface.preeditCallback(null) catch {};
}
}
// We want to get the physical unmapped key to process physical keybinds. // We want to get the physical unmapped key to process physical keybinds.
// (These are keybinds explicitly marked as requesting physical mapping). // (These are keybinds explicitly marked as requesting physical mapping).
const physical_key = keycode: for (input.keycodes.entries) |entry| { const physical_key = keycode: for (input.keycodes.entries) |entry| {
if (entry.native == keycode) break :keycode entry.key; if (entry.native == keycode) break :keycode entry.key;
} else .invalid; } else .invalid;
// Pass the event through the IM controller to handle dead key states.
// Filter is true if the event was handled by the IM controller.
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec_key));
_ = c.gtk_im_context_filter_keypress(self.im_context, event) != 0;
// Get our consumed modifiers // Get our consumed modifiers
const consumed_mods: input.Mods = consumed: { const consumed_mods: input.Mods = consumed: {
const raw = c.gdk_key_event_get_consumed_modifiers(event); const raw = c.gdk_key_event_get_consumed_modifiers(event);
@ -1254,27 +1296,6 @@ pub const Surface = struct {
// mods, // mods,
// }); // });
// If this is a dead key, then we're composing a character and
// we need to set our proper preedit state.
if (self.im_composing) preedit: {
const text = self.im_buf[0..self.im_len];
const view = std.unicode.Utf8View.init(text) catch |err| {
log.warn("cannot build utf8 view over input: {}", .{err});
break :preedit;
};
var it = view.iterator();
const cp: u21 = it.nextCodepoint() orelse 0;
self.core_surface.preeditCallback(cp) catch |err| {
log.err("error in preedit callback err={}", .{err});
break :preedit;
};
} else {
// If we aren't composing, then we set our preedit to
// empty no matter what.
self.core_surface.preeditCallback(null) catch {};
}
// If we have no UTF-8 text, we try to convert our keyval to // If we have no UTF-8 text, we try to convert our keyval to
// a text value. We have to do this because GTK will not process // a text value. We have to do this because GTK will not process
// "Ctrl+Shift+1" (on US keyboards) as "Ctrl+!" but instead as "". // "Ctrl+Shift+1" (on US keyboards) as "Ctrl+!" but instead as "".
@ -1289,7 +1310,7 @@ pub const Surface = struct {
// Invoke the core Ghostty logic to handle this input. // Invoke the core Ghostty logic to handle this input.
const consumed = self.core_surface.key2Callback(.{ const consumed = self.core_surface.key2Callback(.{
.action = .press, .action = action,
.key = key, .key = key,
.physical_key = physical_key, .physical_key = physical_key,
.mods = mods, .mods = mods,
@ -1298,37 +1319,17 @@ pub const Surface = struct {
.utf8 = self.im_buf[0..self.im_len], .utf8 = self.im_buf[0..self.im_len],
}) catch |err| { }) catch |err| {
log.err("error in key callback err={}", .{err}); log.err("error in key callback err={}", .{err});
return 0; return false;
}; };
// If we consume the key then we want to reset the dead key state. // If we consume the key then we want to reset the dead key state.
if (consumed) { if (consumed and (action == .press or action == .repeat)) {
c.gtk_im_context_reset(self.im_context); c.gtk_im_context_reset(self.im_context);
self.core_surface.preeditCallback(null) catch {}; self.core_surface.preeditCallback(null) catch {};
return 1; return true;
} }
return 0; return false;
}
fn gtkKeyReleased(
_: *c.GtkEventControllerKey,
keyval: c.guint,
keycode: c.guint,
state: c.GdkModifierType,
ud: ?*anyopaque,
) callconv(.C) c.gboolean {
_ = keycode;
const key = translateKey(keyval);
const mods = translateMods(state);
const self = userdataSelf(ud.?);
const consumed = self.core_surface.keyCallback(.release, key, key, mods) catch |err| {
log.err("error in key callback err={}", .{err});
return 0;
};
return if (consumed) 1 else 0;
} }
fn gtkInputPreeditStart( fn gtkInputPreeditStart(