mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
remove charCallback/keyCallback
This commit is contained in:
377
src/Surface.zig
377
src/Surface.zig
@ -987,158 +987,6 @@ pub fn preeditCallback(self: *Surface, preedit: ?u21) !void {
|
|||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn charCallback(
|
|
||||||
self: *Surface,
|
|
||||||
codepoint: u21,
|
|
||||||
mods: input.Mods,
|
|
||||||
) !void {
|
|
||||||
const tracy = trace(@src());
|
|
||||||
defer tracy.end();
|
|
||||||
|
|
||||||
// Dev Mode
|
|
||||||
if (DevMode.enabled and DevMode.instance.visible) {
|
|
||||||
// If the event was handled by imgui, ignore it.
|
|
||||||
if (imgui.IO.get()) |io| {
|
|
||||||
if (io.cval().WantCaptureKeyboard) {
|
|
||||||
try self.queueRender();
|
|
||||||
}
|
|
||||||
} else |_| {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Critical area
|
|
||||||
const critical: struct {
|
|
||||||
alt_esc_prefix: bool,
|
|
||||||
modify_other_keys: bool,
|
|
||||||
} = critical: {
|
|
||||||
self.renderer_state.mutex.lock();
|
|
||||||
defer self.renderer_state.mutex.unlock();
|
|
||||||
|
|
||||||
// Clear the selection if we have one.
|
|
||||||
if (self.io.terminal.screen.selection != null) {
|
|
||||||
self.setSelection(null);
|
|
||||||
try self.queueRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to scroll to the bottom
|
|
||||||
// TODO: detect if we're at the bottom to avoid the render call here.
|
|
||||||
try self.io.terminal.scrollViewport(.{ .bottom = {} });
|
|
||||||
|
|
||||||
break :critical .{
|
|
||||||
.alt_esc_prefix = self.io.terminal.modes.get(.alt_esc_prefix),
|
|
||||||
.modify_other_keys = self.io.terminal.flags.modify_other_keys_2,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Where we're going to write any data. Any data we write has to
|
|
||||||
// fit into the fixed size array so we just define it up front.
|
|
||||||
var data: termio.Message.WriteReq.Small.Array = undefined;
|
|
||||||
|
|
||||||
// In modify other keys state 2, we send the CSI 27 sequence
|
|
||||||
// for any char with a modifier. Ctrl sequences like Ctrl+A
|
|
||||||
// are handled in keyCallback and should never have reached this
|
|
||||||
// point.
|
|
||||||
if (critical.modify_other_keys) {
|
|
||||||
// This copies xterm's `ModifyOtherKeys` function that returns
|
|
||||||
// whether modify other keys should be encoded for the given
|
|
||||||
// input.
|
|
||||||
const should_modify = should_modify: {
|
|
||||||
// xterm IsControlInput
|
|
||||||
if (codepoint >= 0x40 and codepoint <= 0x7F)
|
|
||||||
break :should_modify true;
|
|
||||||
|
|
||||||
// If we have anything other than shift pressed, encode.
|
|
||||||
var mods_no_shift = mods;
|
|
||||||
mods_no_shift.shift = false;
|
|
||||||
if (!mods_no_shift.empty()) break :should_modify true;
|
|
||||||
|
|
||||||
// We only have shift pressed. We only allow space.
|
|
||||||
if (codepoint == ' ') break :should_modify true;
|
|
||||||
|
|
||||||
// This logic isn't complete but I don't fully understand
|
|
||||||
// the rest so I'm going to wait until we can have a
|
|
||||||
// reasonable test scenario.
|
|
||||||
break :should_modify false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (should_modify) {
|
|
||||||
for (input.function_keys.modifiers, 2..) |modset, code| {
|
|
||||||
if (!mods.equal(modset)) continue;
|
|
||||||
|
|
||||||
const resp = try std.fmt.bufPrint(
|
|
||||||
&data,
|
|
||||||
"\x1B[27;{};{}~",
|
|
||||||
.{ code, codepoint },
|
|
||||||
);
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_small = .{
|
|
||||||
.data = data,
|
|
||||||
.len = @intCast(resp.len),
|
|
||||||
},
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's see if we should apply fixterms to this codepoint.
|
|
||||||
// At this stage of key processing, we only need to apply fixterms
|
|
||||||
// to unicode codepoints (the point of charCallback) if we have
|
|
||||||
// ctrl set.
|
|
||||||
if (mods.ctrl) {
|
|
||||||
const csi_u_mods = terminal.csi_u.Mods.fromInput(mods);
|
|
||||||
const resp = try std.fmt.bufPrint(
|
|
||||||
&data,
|
|
||||||
"\x1B[{};{}u",
|
|
||||||
.{ codepoint, csi_u_mods.seqInt() },
|
|
||||||
);
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_small = .{
|
|
||||||
.data = data,
|
|
||||||
.len = @intCast(resp.len),
|
|
||||||
},
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefix our data with ESC if we have alt pressed.
|
|
||||||
var i: u8 = 0;
|
|
||||||
if (mods.alt) alt: {
|
|
||||||
// If the terminal explicitly disabled this feature using mode 1036,
|
|
||||||
// then we don't send the prefix.
|
|
||||||
if (!critical.alt_esc_prefix) {
|
|
||||||
log.debug("alt_esc_prefix disabled with mode, not sending esc prefix", .{});
|
|
||||||
break :alt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On macOS, we have to opt-in to using alt because option
|
|
||||||
// by default is a unicode character sequence.
|
|
||||||
if (comptime builtin.target.isDarwin()) {
|
|
||||||
switch (self.config.macos_option_as_alt) {
|
|
||||||
.false => break :alt,
|
|
||||||
.true => {},
|
|
||||||
.left => if (mods.sides.alt != .left) break :alt,
|
|
||||||
.right => if (mods.sides.alt != .right) break :alt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data[i] = 0x1b;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const len = try std.unicode.utf8Encode(codepoint, data[i..]);
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_small = .{
|
|
||||||
.data = data,
|
|
||||||
.len = len + i,
|
|
||||||
},
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
|
|
||||||
// After sending all our messages we have to notify our IO thread
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key2Callback(
|
pub fn key2Callback(
|
||||||
self: *Surface,
|
self: *Surface,
|
||||||
event: input.KeyEvent,
|
event: input.KeyEvent,
|
||||||
@ -1214,231 +1062,6 @@ pub fn key2Callback(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called for a single key event.
|
|
||||||
///
|
|
||||||
/// This will return true if the key was handled/consumed. In that case,
|
|
||||||
/// the caller doesn't need to call a subsequent `charCallback` for the
|
|
||||||
/// same event. However, the caller can call `charCallback` if they want,
|
|
||||||
/// the surface will retain state to ensure the event is ignored.
|
|
||||||
pub fn keyCallback(
|
|
||||||
self: *Surface,
|
|
||||||
action: input.Action,
|
|
||||||
key: input.Key,
|
|
||||||
physical_key: input.Key,
|
|
||||||
mods: input.Mods,
|
|
||||||
) !bool {
|
|
||||||
const tracy = trace(@src());
|
|
||||||
defer tracy.end();
|
|
||||||
|
|
||||||
// log.warn("KEY CALLBACK action={} key={} physical_key={} mods={}", .{
|
|
||||||
// action,
|
|
||||||
// key,
|
|
||||||
// physical_key,
|
|
||||||
// mods,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Dev Mode
|
|
||||||
if (DevMode.enabled and DevMode.instance.visible) {
|
|
||||||
// If the event was handled by imgui, ignore it.
|
|
||||||
if (imgui.IO.get()) |io| {
|
|
||||||
if (io.cval().WantCaptureKeyboard) {
|
|
||||||
try self.queueRender();
|
|
||||||
}
|
|
||||||
} else |_| {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only handle press events
|
|
||||||
if (action != .press and action != .repeat) return false;
|
|
||||||
|
|
||||||
// Mods for bindings never include caps/num lock.
|
|
||||||
const binding_mods = mods.binding();
|
|
||||||
|
|
||||||
// Check if we're processing a binding first. If so, that negates
|
|
||||||
// any further key processing.
|
|
||||||
{
|
|
||||||
const binding_action_: ?input.Binding.Action = action: {
|
|
||||||
var trigger: input.Binding.Trigger = .{
|
|
||||||
.mods = binding_mods,
|
|
||||||
.key = key,
|
|
||||||
};
|
|
||||||
|
|
||||||
const set = self.config.keybind.set;
|
|
||||||
if (set.get(trigger)) |v| break :action v;
|
|
||||||
|
|
||||||
trigger.key = physical_key;
|
|
||||||
trigger.physical = true;
|
|
||||||
if (set.get(trigger)) |v| break :action v;
|
|
||||||
|
|
||||||
break :action null;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (binding_action_) |binding_action| {
|
|
||||||
//log.warn("BINDING ACTION={}", .{binding_action});
|
|
||||||
try self.performBindingAction(binding_action);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll need to know these values here on.
|
|
||||||
self.renderer_state.mutex.lock();
|
|
||||||
const cursor_key_application = self.io.terminal.modes.get(.cursor_keys);
|
|
||||||
const keypad_key_application = self.io.terminal.modes.get(.keypad_keys);
|
|
||||||
const modify_other_keys = self.io.terminal.flags.modify_other_keys_2;
|
|
||||||
self.renderer_state.mutex.unlock();
|
|
||||||
|
|
||||||
// Check if we're processing a function key.
|
|
||||||
for (input.function_keys.keys.get(key)) |entry| {
|
|
||||||
switch (entry.cursor) {
|
|
||||||
.any => {},
|
|
||||||
.normal => if (cursor_key_application) continue,
|
|
||||||
.application => if (!cursor_key_application) continue,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (entry.keypad) {
|
|
||||||
.any => {},
|
|
||||||
.normal => if (keypad_key_application) continue,
|
|
||||||
.application => if (!keypad_key_application) continue,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (entry.modify_other_keys) {
|
|
||||||
.any => {},
|
|
||||||
.set => if (modify_other_keys) continue,
|
|
||||||
.set_other => if (!modify_other_keys) continue,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mods_int = binding_mods.int();
|
|
||||||
const entry_mods_int = entry.mods.int();
|
|
||||||
if (entry_mods_int == 0) {
|
|
||||||
if (mods_int != 0 and !entry.mods_empty_is_any) continue;
|
|
||||||
// mods are either empty, or empty means any so we allow it.
|
|
||||||
} else if (entry_mods_int != mods_int) {
|
|
||||||
// any set mods require an exact match
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.debug("function key match: {}", .{entry});
|
|
||||||
|
|
||||||
// We found a match, send the sequence and return we as handled.
|
|
||||||
var data: termio.Message.WriteReq.Small.Array = undefined;
|
|
||||||
@memcpy(data[0..entry.sequence.len], entry.sequence);
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_small = .{
|
|
||||||
.data = data,
|
|
||||||
.len = @intCast(entry.sequence.len),
|
|
||||||
},
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have alt pressed, we're going to prefix any of the
|
|
||||||
// translations below with ESC (0x1B).
|
|
||||||
const alt = binding_mods.alt;
|
|
||||||
const unalt_mods = unalt_mods: {
|
|
||||||
var unalt_mods = binding_mods;
|
|
||||||
unalt_mods.alt = false;
|
|
||||||
break :unalt_mods unalt_mods;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle non-printables
|
|
||||||
const char: u8 = char: {
|
|
||||||
const mods_int = unalt_mods.int();
|
|
||||||
const ctrl_only = (input.Mods{ .ctrl = true }).int();
|
|
||||||
|
|
||||||
// If we're only pressing control, check if this is a character
|
|
||||||
// we convert to a non-printable. The best table I've found for
|
|
||||||
// this is:
|
|
||||||
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-ctrl-mapping-of-ascii-keys
|
|
||||||
//
|
|
||||||
// Note that depending on the apprt, these might be handled as
|
|
||||||
// composed characters. But not all app runtimes will do this;
|
|
||||||
// some only compose printable characters. So we manually handle
|
|
||||||
// this here.
|
|
||||||
if (mods_int != ctrl_only) break :char 0;
|
|
||||||
break :char switch (key) {
|
|
||||||
.space => 0,
|
|
||||||
.slash => 0x1F,
|
|
||||||
.zero => 0x30,
|
|
||||||
.one => 0x31,
|
|
||||||
.two => 0x00,
|
|
||||||
.three => 0x1B,
|
|
||||||
.four => 0x1C,
|
|
||||||
.five => 0x1D,
|
|
||||||
.six => 0x1E,
|
|
||||||
.seven => 0x1F,
|
|
||||||
.eight => 0x7F,
|
|
||||||
.nine => 0x39,
|
|
||||||
.backslash => 0x1C,
|
|
||||||
.left_bracket => 0x1B,
|
|
||||||
.right_bracket => 0x1D,
|
|
||||||
.a => 0x01,
|
|
||||||
.b => 0x02,
|
|
||||||
.c => 0x03,
|
|
||||||
.d => 0x04,
|
|
||||||
.e => 0x05,
|
|
||||||
.f => 0x06,
|
|
||||||
.g => 0x07,
|
|
||||||
.h => 0x08,
|
|
||||||
.i => 0x09,
|
|
||||||
.j => 0x0A,
|
|
||||||
.k => 0x0B,
|
|
||||||
.l => 0x0C,
|
|
||||||
.m => 0x0D,
|
|
||||||
.n => 0x0E,
|
|
||||||
.o => 0x0F,
|
|
||||||
.p => 0x10,
|
|
||||||
.q => 0x11,
|
|
||||||
.r => 0x12,
|
|
||||||
.s => 0x13,
|
|
||||||
.t => 0x14,
|
|
||||||
.u => 0x15,
|
|
||||||
.v => 0x16,
|
|
||||||
.w => 0x17,
|
|
||||||
.x => 0x18,
|
|
||||||
.y => 0x19,
|
|
||||||
.z => 0x1A,
|
|
||||||
else => 0,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if (char > 0) {
|
|
||||||
// Ask our IO thread to write the data
|
|
||||||
var data: termio.Message.WriteReq.Small.Array = undefined;
|
|
||||||
|
|
||||||
// Write our data. If we need to alt-prefix we add that first.
|
|
||||||
var i: u8 = 0;
|
|
||||||
if (alt) {
|
|
||||||
data[i] = 0x1B;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
data[i] = @intCast(char);
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_small = .{
|
|
||||||
.data = data,
|
|
||||||
.len = i,
|
|
||||||
},
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
|
|
||||||
// After sending all our messages we have to notify our IO thread
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
|
|
||||||
// Control charactesr trigger a scroll
|
|
||||||
{
|
|
||||||
self.renderer_state.mutex.lock();
|
|
||||||
defer self.renderer_state.mutex.unlock();
|
|
||||||
self.scrollToBottom() catch |err| {
|
|
||||||
log.warn("error scrolling to bottom err={}", .{err});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focusCallback(self: *Surface, focused: bool) !void {
|
pub fn focusCallback(self: *Surface, focused: bool) !void {
|
||||||
// Notify our render thread of the new state
|
// Notify our render thread of the new state
|
||||||
_ = self.renderer_thread.mailbox.push(.{
|
_ = self.renderer_thread.mailbox.push(.{
|
||||||
|
@ -532,8 +532,25 @@ pub const Surface = struct {
|
|||||||
|
|
||||||
pub fn charCallback(self: *Surface, cp_: u32) void {
|
pub fn charCallback(self: *Surface, cp_: u32) void {
|
||||||
const cp = std.math.cast(u21, cp_) orelse return;
|
const cp = std.math.cast(u21, cp_) orelse return;
|
||||||
self.core_surface.charCallback(cp, .{}) catch |err| {
|
var buf: [4]u8 = undefined;
|
||||||
log.err("error in char callback err={}", .{err});
|
const len = std.unicode.utf8Encode(cp, &buf) catch |err| {
|
||||||
|
log.err("error encoding codepoint={} err={}", .{ cp, err });
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// For a char callback we just construct a key event with invalid
|
||||||
|
// keys but with text. This should result in the text being sent
|
||||||
|
// as-is.
|
||||||
|
_ = self.core_surface.key2Callback(.{
|
||||||
|
.action = .press,
|
||||||
|
.key = .invalid,
|
||||||
|
.physical_key = .invalid,
|
||||||
|
.mods = .{},
|
||||||
|
.consumed_mods = .{},
|
||||||
|
.composing = false,
|
||||||
|
.utf8 = buf[0..len],
|
||||||
|
}) catch |err| {
|
||||||
|
log.err("error in key callback err={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1402,17 +1402,18 @@ pub const Surface = struct {
|
|||||||
// We're not in a keypress, so this was sent from an on-screen emoji
|
// We're not in a keypress, so this was sent from an on-screen emoji
|
||||||
// keyboard or someting like that. Send the characters directly to
|
// keyboard or someting like that. Send the characters directly to
|
||||||
// the surface.
|
// the surface.
|
||||||
const view = std.unicode.Utf8View.init(str) catch |err| {
|
_ = self.core_surface.key2Callback(.{
|
||||||
log.warn("cannot build utf8 view over input: {}", .{err});
|
.action = .press,
|
||||||
|
.key = .invalid,
|
||||||
|
.physical_key = .invalid,
|
||||||
|
.mods = .{},
|
||||||
|
.consumed_mods = .{},
|
||||||
|
.composing = false,
|
||||||
|
.utf8 = str,
|
||||||
|
}) catch |err| {
|
||||||
|
log.err("error in key callback err={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
var it = view.iterator();
|
|
||||||
while (it.nextCodepoint()) |cp| {
|
|
||||||
self.core_surface.charCallback(cp, .{}) catch |err| {
|
|
||||||
log.err("error in char callback err={}", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) void {
|
fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) void {
|
||||||
|
Reference in New Issue
Block a user