mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
apprt/gtk: only use key2callback
This commit is contained in:
@ -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(
|
||||
ec_key: *c.GtkEventControllerKey,
|
||||
keyval: c.guint,
|
||||
@ -1189,31 +1162,100 @@ pub const Surface = struct {
|
||||
gtk_mods: c.GdkModifierType,
|
||||
ud: ?*anyopaque,
|
||||
) 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 mods = translateMods(gtk_mods);
|
||||
const keyval_unicode = c.gdk_keyval_to_unicode(keyval);
|
||||
|
||||
// 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;
|
||||
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec_key));
|
||||
|
||||
// We always reset our committed text when ending a keypress so that
|
||||
// future keypresses don't think we have a commit event.
|
||||
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.
|
||||
// (These are keybinds explicitly marked as requesting physical mapping).
|
||||
const physical_key = keycode: for (input.keycodes.entries) |entry| {
|
||||
if (entry.native == keycode) break :keycode entry.key;
|
||||
} 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
|
||||
const consumed_mods: input.Mods = consumed: {
|
||||
const raw = c.gdk_key_event_get_consumed_modifiers(event);
|
||||
@ -1254,27 +1296,6 @@ pub const Surface = struct {
|
||||
// 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
|
||||
// a text value. We have to do this because GTK will not process
|
||||
// "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.
|
||||
const consumed = self.core_surface.key2Callback(.{
|
||||
.action = .press,
|
||||
.action = action,
|
||||
.key = key,
|
||||
.physical_key = physical_key,
|
||||
.mods = mods,
|
||||
@ -1298,37 +1319,17 @@ pub const Surface = struct {
|
||||
.utf8 = self.im_buf[0..self.im_len],
|
||||
}) catch |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 (consumed) {
|
||||
if (consumed and (action == .press or action == .repeat)) {
|
||||
c.gtk_im_context_reset(self.im_context);
|
||||
self.core_surface.preeditCallback(null) catch {};
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
fn gtkInputPreeditStart(
|
||||
|
Reference in New Issue
Block a user