mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +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(
|
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(
|
||||||
|
Reference in New Issue
Block a user