From a1e70afbb1f49deb01e328f44fdf3e7e11cf2cd9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Aug 2023 17:53:20 -0700 Subject: [PATCH] modifyOtherKeys state 2 should send sequences for all chars See: https://github.com/mitchellh/ghostty/issues/242#issuecomment-1678268533 Quoted: @hovsater OKAY! I've consulted _the source_, i.e. `xterm`. None of the other reference material was illuminating and there is so much conflicting implementation out there and so very few terminals actually support `modifyOtherKeys`. I believe I've figured it out. I believe that `C-S-h` is only supported via `modifyOtherKeys` state 2. iTerm emits it for state 1 but I think this is a mistake and I can't get any other terminal to do it, including `xterm`. Here is my test script on Linux: ``` printf "\x1b[>4;1m" # change to "2" for state 2 showkey -a ``` With state 1, I couldn't get any terminal to output anything for `C-S-h`. **But with state 2, xterm outputs: ** `CSI 27;6;72~`. One thing to note is 72 is `H` (uppercase), so in even this case, iTerm appears to be sending the wrong code or `dte -K` is outputting the wrong case (less likely I think). When I launch `dte` (the full editor), it only requests `modifyOtherKeys` state 1. So, with only `modifyOtherKeys` support, it shouldn't get access to `C-S-h`. Note that I couldn't get any terminal on macOS to show the same sequences as xterm under any circumstance. I also cracked open the `xterm` source and I only eyeballed it but I believe this is not sending the sequences under state 1: https://sourcegraph.com/github.com/ThomasDickey/xterm-snapshots@c2b36af8d216926b8931c6f9cebefd69228e437c/-/blob/input.c?L579 **I could be very wrong, I'm not confident.** Every implementation (and there are only few) seems different and the behaviors are not consistent at all. Hence, I'm falling back to `xterm`, but even then I could be reading the source wrong. But when I ran `xterm` manually I could only get `C-S-h` to show up in state 2. --- src/Surface.zig | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Surface.zig b/src/Surface.zig index c4516700d..f5b5a550e 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1008,6 +1008,7 @@ pub fn charCallback( // 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(); @@ -1024,11 +1025,38 @@ pub fn charCallback( break :critical .{ .alt_esc_prefix = self.io.terminal.modes.alt_esc_prefix, + .modify_other_keys = self.io.terminal.modes.modify_other_keys, }; }; + // 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 and !mods.empty()) { + 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; + } + } + // Prefix our data with ESC if we have alt pressed. var i: u8 = 0; if (mods.alt) alt: {