core: detect inputs that result in surface close and avoid segfault

Fixes #965

When processing keybindings that closed the surface (`close_surface`,
`close_window`), the surface and associated runtime structures would be
freed so we could segfault.

This PR introduces a new enum result for input events (only key for now)
that returns whether an event resulted in a close. In this case, callers
can properly return immediately and avoid writing to deallocated memory.
This commit is contained in:
Mitchell Hashimoto
2023-12-07 10:24:39 -08:00
parent 571170c574
commit 9de5d991a2
4 changed files with 74 additions and 20 deletions

View File

@@ -794,7 +794,7 @@ pub const Surface = struct {
} else .invalid;
// Invoke the core Ghostty logic to handle this input.
const consumed = self.core_surface.keyCallback(.{
const effect = self.core_surface.keyCallback(.{
.action = action,
.key = key,
.physical_key = physical_key,
@@ -808,11 +808,15 @@ pub const Surface = struct {
return;
};
// If we consume the key then we want to reset the dead key state.
if (consumed and is_down) {
self.keymap_state = .{};
self.core_surface.preeditCallback(null) catch {};
return;
switch (effect) {
.closed => return,
.ignored => {},
.consumed => if (is_down) {
// If we consume the key then we want to reset the dead
// key state.
self.keymap_state = .{};
self.core_surface.preeditCallback(null) catch {};
},
}
}

View File

@@ -928,16 +928,21 @@ pub const Surface = struct {
.utf8 = "",
};
const consumed = core_win.keyCallback(key_event) catch |err| {
const effect = core_win.keyCallback(key_event) catch |err| {
log.err("error in key callback err={}", .{err});
return;
};
// Surface closed.
if (effect == .closed) return;
// If it wasn't consumed, we set it on our self so that charcallback
// can make another attempt. Otherwise, we set null so the charcallback
// is ignored.
core_win.rt_surface.key_event = null;
if (!consumed and (action == .press or action == .repeat)) {
if (effect == .ignored and
(action == .press or action == .repeat))
{
core_win.rt_surface.key_event = key_event;
}
}

View File

@@ -1394,7 +1394,7 @@ fn keyEvent(
}
// Invoke the core Ghostty logic to handle this input.
const consumed = self.core_surface.keyCallback(.{
const effect = self.core_surface.keyCallback(.{
.action = action,
.key = key,
.physical_key = physical_key,
@@ -1408,11 +1408,16 @@ fn keyEvent(
return false;
};
// If we consume the key then we want to reset the dead key state.
if (consumed and (action == .press or action == .repeat)) {
c.gtk_im_context_reset(self.im_context);
self.core_surface.preeditCallback(null) catch {};
return true;
switch (effect) {
.closed => return true,
.ignored => {},
.consumed => if (action == .press or action == .repeat) {
// If we consume the key then we want to reset the dead key
// state.
c.gtk_im_context_reset(self.im_context);
self.core_surface.preeditCallback(null) catch {};
return true;
},
}
return false;