diff --git a/src/Surface.zig b/src/Surface.zig index 87a3037c7..8807c411f 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -142,6 +142,7 @@ const DerivedConfig = struct { confirm_close_surface: bool, mouse_interval: u64, macos_non_native_fullscreen: bool, + macos_option_as_alt: bool, pub fn init(alloc_gpa: Allocator, config: *const configpkg.Config) !DerivedConfig { var arena = ArenaAllocator.init(alloc_gpa); @@ -158,6 +159,7 @@ const DerivedConfig = struct { .confirm_close_surface = config.@"confirm-close-surface", .mouse_interval = config.@"click-repeat-interval" * 1_000_000, // 500ms .macos_non_native_fullscreen = config.@"macos-non-native-fullscreen", + .macos_option_as_alt = config.@"macos-option-as-alt", // Assignments happen sequentially so we have to do this last // so that the memory is captured from allocs above. @@ -985,7 +987,11 @@ pub fn preeditCallback(self: *Surface, preedit: ?u21) !void { try self.queueRender(); } -pub fn charCallback(self: *Surface, codepoint: u21) !void { +pub fn charCallback( + self: *Surface, + codepoint: u21, + mods: input.Mods, +) !void { const tracy = trace(@src()); defer tracy.end(); @@ -1015,13 +1021,26 @@ pub fn charCallback(self: *Surface, codepoint: u21) !void { try self.io.terminal.scrollViewport(.{ .bottom = {} }); } - // Ask our IO thread to write the data var data: termio.Message.WriteReq.Small.Array = undefined; - const len = try std.unicode.utf8Encode(codepoint, &data); + + // Prefix our data with ESC if we have alt pressed. + var i: u8 = 0; + if (mods.alt) 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()) { + if (!self.config.macos_option_as_alt) 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, + .len = len + i, }, }, .{ .forever = {} }); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index de257cdd4..efd1480af 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -390,13 +390,23 @@ pub const Surface = struct { // We don't handle release events because we don't use them yet. if (action != .press and action != .repeat) return; + // If we're on macOS and we have macos-option-as-alt enabled, + // then we strip the alt modifier from the mods for translation. + const translate_mods = translate_mods: { + var translate_mods = mods; + if (self.app.config.@"macos-option-as-alt") + translate_mods.alt = false; + + break :translate_mods translate_mods; + }; + // Translate our key using the keymap for our localized keyboard layout. var buf: [128]u8 = undefined; const result = try self.app.keymap.translate( &buf, &self.keymap_state, @intCast(keycode), - mods, + translate_mods, ); // If we aren't composing, then we set our preedit to empty no matter what. @@ -472,7 +482,7 @@ pub const Surface = struct { // Next, we want to call the char callback with each codepoint. while (it.nextCodepoint()) |cp| { - self.core_surface.charCallback(cp) catch |err| { + self.core_surface.charCallback(cp, mods) catch |err| { log.err("error in char callback err={}", .{err}); return; }; @@ -481,7 +491,7 @@ pub const Surface = struct { pub fn charCallback(self: *Surface, cp_: u32) void { const cp = std.math.cast(u21, cp_) orelse return; - self.core_surface.charCallback(cp) catch |err| { + self.core_surface.charCallback(cp, .{}) catch |err| { log.err("error in char callback err={}", .{err}); return; }; diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 9f222b2c3..44467cb9b 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -597,7 +597,8 @@ pub const Surface = struct { return; } - core_win.charCallback(codepoint) catch |err| { + // TODO: mods + core_win.charCallback(codepoint, .{}) catch |err| { log.err("error in char callback err={}", .{err}); return; }; diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index 47eec5b3e..d15a071df 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -1282,7 +1282,7 @@ pub const Surface = struct { }; var it = view.iterator(); while (it.nextCodepoint()) |cp| { - self.core_surface.charCallback(cp) catch |err| { + self.core_surface.charCallback(cp, mods) catch |err| { log.err("error in char callback err={}", .{err}); return 0; }; @@ -1390,7 +1390,7 @@ pub const Surface = struct { }; var it = view.iterator(); while (it.nextCodepoint()) |cp| { - self.core_surface.charCallback(cp) catch |err| { + self.core_surface.charCallback(cp, .{}) catch |err| { log.err("error in char callback err={}", .{err}); return; }; diff --git a/src/config.zig b/src/config.zig index ae41072a0..a5731289c 100644 --- a/src/config.zig +++ b/src/config.zig @@ -238,6 +238,19 @@ pub const Config = struct { /// animations. @"macos-non-native-fullscreen": bool = false, + /// If true, the Option key will be treated as Alt. This makes terminal + /// sequences expecting Alt to work properly, but will break Unicode + /// input sequences on macOS if you use them via the alt key. You may + /// set this to false to restore the macOS alt-key unicode sequences + /// but this will break terminal sequences expecting Alt to work. + /// + /// Note that if an Option-sequence doesn't produce a printable + /// character, it will be treated as Alt regardless of this setting. + /// (i.e. alt+ctrl+a). + /// + /// This does not work with GLFW builds. + @"macos-option-as-alt": bool = false, + /// This is set by the CLI parser for deinit. _arena: ?ArenaAllocator = null,