mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
macOS: Input Improvements (#4591)
Sorry for the vague title. This PR addresses multiple issues: 1. Fixes #4540 2. #4522 is fixed for macOS only 3. Fixes #4590 4. Fixes an untracked issue where `command+key` events will not send release events for Kitty keyboard protocol, something I only noticed while working on this. There are multiple components to this PR. ## Part 1: `App/Surface.keyEventIsBinding` This new API (also available in libghostty as `ghostty_surface_key_is_binding`) returns a boolean true if the given key event would match a binding trigger if it was the next key event sent. It does not process the binding now. This can be used by event handlers that intercept key events to determine if it should send the event to Ghostty. This helps resolve #4590 for us but is also part of all resolved issues. ## Part 2: macOS `performKeyEquivalent` changes macOS calls `performKeyEquivalent` for any key combination that may trigger a key equivalent. if this returns `true` then it is handled and macOS ceases processing the event. We were already using this to intercept things like `Ctrl+/` which triggers a context menu in macOS Sequoia. But we now expand this to intercept all events to check for bindings. This lets us fix #4590. Additionally, it's been changed to special case `cmd+period`. I'm sure more need to be added. ## Part 3: NSEvent local listener for command keyUp events macOS simply doesn't send `keyUp` events for key events with command pressed. The only way to work around this is to register an `NSEvent` local listener. We now do this. This fixes the untracked issue noted above.
This commit is contained in:
@ -686,6 +686,7 @@ void ghostty_app_tick(ghostty_app_t);
|
|||||||
void* ghostty_app_userdata(ghostty_app_t);
|
void* ghostty_app_userdata(ghostty_app_t);
|
||||||
void ghostty_app_set_focus(ghostty_app_t, bool);
|
void ghostty_app_set_focus(ghostty_app_t, bool);
|
||||||
bool ghostty_app_key(ghostty_app_t, ghostty_input_key_s);
|
bool ghostty_app_key(ghostty_app_t, ghostty_input_key_s);
|
||||||
|
bool ghostty_app_key_is_binding(ghostty_app_t, ghostty_input_key_s);
|
||||||
void ghostty_app_keyboard_changed(ghostty_app_t);
|
void ghostty_app_keyboard_changed(ghostty_app_t);
|
||||||
void ghostty_app_open_config(ghostty_app_t);
|
void ghostty_app_open_config(ghostty_app_t);
|
||||||
void ghostty_app_update_config(ghostty_app_t, ghostty_config_t);
|
void ghostty_app_update_config(ghostty_app_t, ghostty_config_t);
|
||||||
@ -713,7 +714,8 @@ void ghostty_surface_set_color_scheme(ghostty_surface_t,
|
|||||||
ghostty_color_scheme_e);
|
ghostty_color_scheme_e);
|
||||||
ghostty_input_mods_e ghostty_surface_key_translation_mods(ghostty_surface_t,
|
ghostty_input_mods_e ghostty_surface_key_translation_mods(ghostty_surface_t,
|
||||||
ghostty_input_mods_e);
|
ghostty_input_mods_e);
|
||||||
void ghostty_surface_key(ghostty_surface_t, ghostty_input_key_s);
|
bool ghostty_surface_key(ghostty_surface_t, ghostty_input_key_s);
|
||||||
|
bool ghostty_surface_key_is_binding(ghostty_surface_t, ghostty_input_key_s);
|
||||||
void ghostty_surface_text(ghostty_surface_t, const char*, uintptr_t);
|
void ghostty_surface_text(ghostty_surface_t, const char*, uintptr_t);
|
||||||
bool ghostty_surface_mouse_captured(ghostty_surface_t);
|
bool ghostty_surface_mouse_captured(ghostty_surface_t);
|
||||||
bool ghostty_surface_mouse_button(ghostty_surface_t,
|
bool ghostty_surface_mouse_button(ghostty_surface_t,
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
29C15B1D2CDC3B2900520DD4 /* bat in Resources */ = {isa = PBXBuildFile; fileRef = 29C15B1C2CDC3B2000520DD4 /* bat */; };
|
29C15B1D2CDC3B2900520DD4 /* bat in Resources */ = {isa = PBXBuildFile; fileRef = 29C15B1C2CDC3B2000520DD4 /* bat */; };
|
||||||
55154BE02B33911F001622DC /* ghostty in Resources */ = {isa = PBXBuildFile; fileRef = 55154BDF2B33911F001622DC /* ghostty */; };
|
55154BE02B33911F001622DC /* ghostty in Resources */ = {isa = PBXBuildFile; fileRef = 55154BDF2B33911F001622DC /* ghostty */; };
|
||||||
552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; };
|
552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; };
|
||||||
9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; };
|
|
||||||
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
||||||
|
9351BE8E3D22937F003B3499 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* vim */; };
|
||||||
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
||||||
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
||||||
A514C8D82B54DC6800493A16 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
|
A514C8D82B54DC6800493A16 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
|
||||||
@ -87,6 +87,8 @@
|
|||||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDB29B8009000646FDA /* SplitView.swift */; };
|
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDB29B8009000646FDA /* SplitView.swift */; };
|
||||||
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */; };
|
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */; };
|
||||||
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
|
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
|
||||||
|
A5CF66D42D289CEE00139794 /* NSEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CF66D32D289CEA00139794 /* NSEvent+Extension.swift */; };
|
||||||
|
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CF66D62D29DDB100139794 /* Ghostty.Event.swift */; };
|
||||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */; };
|
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */; };
|
||||||
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3C2B37804400D21823 /* CodableBridge.swift */; };
|
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3C2B37804400D21823 /* CodableBridge.swift */; };
|
||||||
A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */; };
|
A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */; };
|
||||||
@ -108,8 +110,8 @@
|
|||||||
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; };
|
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; };
|
||||||
55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = "<group>"; };
|
55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = "<group>"; };
|
||||||
552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = "<group>"; };
|
552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = "<group>"; };
|
||||||
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
|
|
||||||
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||||
|
9351BE8E2D22937F003B3499 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
|
||||||
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
|
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
|
||||||
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; };
|
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; };
|
||||||
A51BFC1D2B2FB5CE00E92F16 /* About.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = About.xib; sourceTree = "<group>"; };
|
A51BFC1D2B2FB5CE00E92F16 /* About.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = About.xib; sourceTree = "<group>"; };
|
||||||
@ -177,6 +179,8 @@
|
|||||||
A5CEAFDB29B8009000646FDA /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
|
A5CEAFDB29B8009000646FDA /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
|
||||||
A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = "<group>"; };
|
A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = "<group>"; };
|
||||||
A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
||||||
|
A5CF66D32D289CEA00139794 /* NSEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSEvent+Extension.swift"; sourceTree = "<group>"; };
|
||||||
|
A5CF66D62D29DDB100139794 /* Ghostty.Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Event.swift; sourceTree = "<group>"; };
|
||||||
A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalRestorable.swift; sourceTree = "<group>"; };
|
A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalRestorable.swift; sourceTree = "<group>"; };
|
||||||
A5D0AF3C2B37804400D21823 /* CodableBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableBridge.swift; sourceTree = "<group>"; };
|
A5D0AF3C2B37804400D21823 /* CodableBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableBridge.swift; sourceTree = "<group>"; };
|
||||||
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ghostty-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ghostty-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -351,12 +355,14 @@
|
|||||||
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */,
|
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */,
|
||||||
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */,
|
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */,
|
||||||
A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */,
|
A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */,
|
||||||
|
A5CF66D62D29DDB100139794 /* Ghostty.Event.swift */,
|
||||||
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */,
|
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */,
|
||||||
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */,
|
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */,
|
||||||
A59630A32AF059BB00D64628 /* Ghostty.SplitNode.swift */,
|
A59630A32AF059BB00D64628 /* Ghostty.SplitNode.swift */,
|
||||||
A59630A12AF0415000D64628 /* Ghostty.TerminalSplit.swift */,
|
A59630A12AF0415000D64628 /* Ghostty.TerminalSplit.swift */,
|
||||||
A55685DF29A03A9F004303CE /* AppError.swift */,
|
A55685DF29A03A9F004303CE /* AppError.swift */,
|
||||||
A52FFF5A2CAA54A8000C6A5B /* FullscreenMode+Extension.swift */,
|
A52FFF5A2CAA54A8000C6A5B /* FullscreenMode+Extension.swift */,
|
||||||
|
A5CF66D32D289CEA00139794 /* NSEvent+Extension.swift */,
|
||||||
);
|
);
|
||||||
path = Ghostty;
|
path = Ghostty;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -405,7 +411,7 @@
|
|||||||
A5985CE52C33060F00C57AD3 /* man */,
|
A5985CE52C33060F00C57AD3 /* man */,
|
||||||
A5A1F8842A489D6800D1E8BC /* terminfo */,
|
A5A1F8842A489D6800D1E8BC /* terminfo */,
|
||||||
FC5218F92D10FFC7004C93E0 /* zsh */,
|
FC5218F92D10FFC7004C93E0 /* zsh */,
|
||||||
9351BE8E2D22937F003B3499 /* nvim */,
|
9351BE8E2D22937F003B3499 /* vim */,
|
||||||
);
|
);
|
||||||
name = Resources;
|
name = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -582,7 +588,7 @@
|
|||||||
A5985CE62C33060F00C57AD3 /* man in Resources */,
|
A5985CE62C33060F00C57AD3 /* man in Resources */,
|
||||||
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */,
|
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */,
|
||||||
552964E62B34A9B400030505 /* vim in Resources */,
|
552964E62B34A9B400030505 /* vim in Resources */,
|
||||||
9351BE8E3D22937F003B3499 /* nvim in Resources */,
|
9351BE8E3D22937F003B3499 /* vim in Resources */,
|
||||||
A5CBD05C2CA0C5C70017A1AE /* QuickTerminal.xib in Resources */,
|
A5CBD05C2CA0C5C70017A1AE /* QuickTerminal.xib in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -611,12 +617,14 @@
|
|||||||
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
|
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
|
||||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
||||||
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */,
|
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */,
|
||||||
|
A5CF66D42D289CEE00139794 /* NSEvent+Extension.swift in Sources */,
|
||||||
A5CBD0642CA122E70017A1AE /* QuickTerminalPosition.swift in Sources */,
|
A5CBD0642CA122E70017A1AE /* QuickTerminalPosition.swift in Sources */,
|
||||||
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
||||||
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */,
|
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */,
|
||||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||||
A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */,
|
A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */,
|
||||||
A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */,
|
A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */,
|
||||||
|
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */,
|
||||||
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
||||||
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
||||||
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */,
|
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */,
|
||||||
|
@ -425,6 +425,15 @@ class AppDelegate: NSObject,
|
|||||||
// because we let it capture and propagate.
|
// because we let it capture and propagate.
|
||||||
guard NSApp.mainWindow == nil else { return event }
|
guard NSApp.mainWindow == nil else { return event }
|
||||||
|
|
||||||
|
// If this event as-is would result in a key binding then we send it.
|
||||||
|
if let app = ghostty.app,
|
||||||
|
ghostty_app_key_is_binding(
|
||||||
|
app,
|
||||||
|
event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)) {
|
||||||
|
ghostty_app_key(app, event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// If this event would be handled by our menu then we do nothing.
|
// If this event would be handled by our menu then we do nothing.
|
||||||
if let mainMenu = NSApp.mainMenu,
|
if let mainMenu = NSApp.mainMenu,
|
||||||
mainMenu.performKeyEquivalent(with: event) {
|
mainMenu.performKeyEquivalent(with: event) {
|
||||||
@ -438,13 +447,7 @@ class AppDelegate: NSObject,
|
|||||||
guard let ghostty = self.ghostty.app else { return event }
|
guard let ghostty = self.ghostty.app else { return event }
|
||||||
|
|
||||||
// Build our event input and call ghostty
|
// Build our event input and call ghostty
|
||||||
var key_ev = ghostty_input_key_s()
|
if (ghostty_app_key(ghostty, event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS))) {
|
||||||
key_ev.action = GHOSTTY_ACTION_PRESS
|
|
||||||
key_ev.mods = Ghostty.ghosttyMods(event.modifierFlags)
|
|
||||||
key_ev.keycode = UInt32(event.keyCode)
|
|
||||||
key_ev.text = nil
|
|
||||||
key_ev.composing = false
|
|
||||||
if (ghostty_app_key(ghostty, key_ev)) {
|
|
||||||
// The key was used so we want to stop it from going to our Mac app
|
// The key was used so we want to stop it from going to our Mac app
|
||||||
Ghostty.logger.debug("local key event handled event=\(event)")
|
Ghostty.logger.debug("local key event handled event=\(event)")
|
||||||
return nil
|
return nil
|
||||||
|
15
macos/Sources/Ghostty/Ghostty.Event.swift
Normal file
15
macos/Sources/Ghostty/Ghostty.Event.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Cocoa
|
||||||
|
import GhosttyKit
|
||||||
|
|
||||||
|
extension Ghostty {
|
||||||
|
/// A comparable event.
|
||||||
|
struct ComparableKeyEvent: Equatable {
|
||||||
|
let keyCode: UInt16
|
||||||
|
let flags: NSEvent.ModifierFlags
|
||||||
|
|
||||||
|
init(event: NSEvent) {
|
||||||
|
self.keyCode = event.keyCode
|
||||||
|
self.flags = event.modifierFlags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
macos/Sources/Ghostty/NSEvent+Extension.swift
Normal file
15
macos/Sources/Ghostty/NSEvent+Extension.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Cocoa
|
||||||
|
import GhosttyKit
|
||||||
|
|
||||||
|
extension NSEvent {
|
||||||
|
/// Create a Ghostty key event for a given keyboard action.
|
||||||
|
func ghosttyKeyEvent(_ action: ghostty_input_action_e) -> ghostty_input_key_s {
|
||||||
|
var key_ev = ghostty_input_key_s()
|
||||||
|
key_ev.action = action
|
||||||
|
key_ev.mods = Ghostty.ghosttyMods(modifierFlags)
|
||||||
|
key_ev.keycode = UInt32(keyCode)
|
||||||
|
key_ev.text = nil
|
||||||
|
key_ev.composing = false
|
||||||
|
return key_ev
|
||||||
|
}
|
||||||
|
}
|
@ -113,6 +113,9 @@ extension Ghostty {
|
|||||||
// A small delay that is introduced before a title change to avoid flickers
|
// A small delay that is introduced before a title change to avoid flickers
|
||||||
private var titleChangeTimer: Timer?
|
private var titleChangeTimer: Timer?
|
||||||
|
|
||||||
|
/// Event monitor (see individual events for why)
|
||||||
|
private var eventMonitor: Any? = nil
|
||||||
|
|
||||||
// We need to support being a first responder so that we can get input events
|
// We need to support being a first responder so that we can get input events
|
||||||
override var acceptsFirstResponder: Bool { return true }
|
override var acceptsFirstResponder: Bool { return true }
|
||||||
|
|
||||||
@ -170,6 +173,15 @@ extension Ghostty {
|
|||||||
name: NSWindow.didChangeScreenNotification,
|
name: NSWindow.didChangeScreenNotification,
|
||||||
object: nil)
|
object: nil)
|
||||||
|
|
||||||
|
// Listen for local events that we need to know of outside of
|
||||||
|
// single surface handlers.
|
||||||
|
self.eventMonitor = NSEvent.addLocalMonitorForEvents(
|
||||||
|
matching: [
|
||||||
|
// We need keyUp because command+key events don't trigger keyUp.
|
||||||
|
.keyUp
|
||||||
|
]
|
||||||
|
) { [weak self] event in self?.localEventHandler(event) }
|
||||||
|
|
||||||
// Setup our surface. This will also initialize all the terminal IO.
|
// Setup our surface. This will also initialize all the terminal IO.
|
||||||
let surface_cfg = baseConfig ?? SurfaceConfiguration()
|
let surface_cfg = baseConfig ?? SurfaceConfiguration()
|
||||||
var surface_cfg_c = surface_cfg.ghosttyConfig(view: self)
|
var surface_cfg_c = surface_cfg.ghosttyConfig(view: self)
|
||||||
@ -212,6 +224,11 @@ extension Ghostty {
|
|||||||
let center = NotificationCenter.default
|
let center = NotificationCenter.default
|
||||||
center.removeObserver(self)
|
center.removeObserver(self)
|
||||||
|
|
||||||
|
// Remove our event monitor
|
||||||
|
if let eventMonitor {
|
||||||
|
NSEvent.removeMonitor(eventMonitor)
|
||||||
|
}
|
||||||
|
|
||||||
// Whenever the surface is removed, we need to note that our restorable
|
// Whenever the surface is removed, we need to note that our restorable
|
||||||
// state is invalid to prevent the surface from being restored.
|
// state is invalid to prevent the surface from being restored.
|
||||||
invalidateRestorableState()
|
invalidateRestorableState()
|
||||||
@ -356,6 +373,30 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Local Events
|
||||||
|
|
||||||
|
private func localEventHandler(_ event: NSEvent) -> NSEvent? {
|
||||||
|
return switch event.type {
|
||||||
|
case .keyUp:
|
||||||
|
localEventKeyUp(event)
|
||||||
|
|
||||||
|
default:
|
||||||
|
event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func localEventKeyUp(_ event: NSEvent) -> NSEvent? {
|
||||||
|
// We only care about events with "command" because all others will
|
||||||
|
// trigger the normal responder chain.
|
||||||
|
if (!event.modifierFlags.contains(.command)) { return event }
|
||||||
|
|
||||||
|
// Command keyUp events are never sent to the normal responder chain
|
||||||
|
// so we send them here.
|
||||||
|
guard focused else { return event }
|
||||||
|
self.keyUp(with: event)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
|
|
||||||
@objc private func onUpdateRendererHealth(notification: SwiftUI.Notification) {
|
@objc private func onUpdateRendererHealth(notification: SwiftUI.Notification) {
|
||||||
@ -773,7 +814,7 @@ extension Ghostty {
|
|||||||
if let list = keyTextAccumulator, list.count > 0 {
|
if let list = keyTextAccumulator, list.count > 0 {
|
||||||
handled = true
|
handled = true
|
||||||
for text in list {
|
for text in list {
|
||||||
keyAction(action, event: event, text: text)
|
_ = keyAction(action, event: event, text: text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,38 +824,49 @@ extension Ghostty {
|
|||||||
// the preedit.
|
// the preedit.
|
||||||
if (markedText.length > 0 || markedTextBefore) {
|
if (markedText.length > 0 || markedTextBefore) {
|
||||||
handled = true
|
handled = true
|
||||||
keyAction(action, event: event, preedit: markedText.string)
|
_ = keyAction(action, event: event, preedit: markedText.string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
// No text or anything, we want to handle this manually.
|
// No text or anything, we want to handle this manually.
|
||||||
keyAction(action, event: event)
|
_ = keyAction(action, event: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func keyUp(with event: NSEvent) {
|
override func keyUp(with event: NSEvent) {
|
||||||
keyAction(GHOSTTY_ACTION_RELEASE, event: event)
|
_ = keyAction(GHOSTTY_ACTION_RELEASE, event: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Special case handling for some control keys
|
/// Special case handling for some control keys
|
||||||
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
||||||
// Only process key down events
|
switch (event.type) {
|
||||||
if (event.type != .keyDown) {
|
case .keyDown:
|
||||||
|
// Continue, we care about key down events
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Any other key event we don't care about. I don't think its even
|
||||||
|
// possible to receive any other event type.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only process events if we're focused. Some key events like C-/ macOS
|
// Only process events if we're focused. Some key events like C-/ macOS
|
||||||
// appears to send to the first view in the hierarchy rather than the
|
// appears to send to the first view in the hierarchy rather than the
|
||||||
// the first responder (I don't know why). This prevents us from handling it.
|
// the first responder (I don't know why). This prevents us from handling it.
|
||||||
|
// Besides C-/, its important we don't process key equivalents if unfocused
|
||||||
|
// because there are other event listeners for that (i.e. AppDelegate's
|
||||||
|
// local event handler).
|
||||||
if (!focused) {
|
if (!focused) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only process keys when Control is active. All known issues we're
|
// If this event as-is would result in a key binding then we send it.
|
||||||
// resolving happen only in this scenario. This probably isn't fully robust
|
if let surface,
|
||||||
// but we can broaden the scope as we find more cases.
|
ghostty_surface_key_is_binding(
|
||||||
if (!event.modifierFlags.contains(.control)) {
|
surface,
|
||||||
return false
|
event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)) {
|
||||||
|
self.keyDown(with: event)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
let equivalent: String
|
let equivalent: String
|
||||||
@ -832,14 +884,25 @@ extension Ghostty {
|
|||||||
case "\r":
|
case "\r":
|
||||||
// Pass C-<return> through verbatim
|
// Pass C-<return> through verbatim
|
||||||
// (prevent the default context menu equivalent)
|
// (prevent the default context menu equivalent)
|
||||||
|
if (!event.modifierFlags.contains(.control)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
equivalent = "\r"
|
equivalent = "\r"
|
||||||
|
|
||||||
|
case ".":
|
||||||
|
if (!event.modifierFlags.contains(.command)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
equivalent = "."
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Ignore other events
|
// Ignore other events
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let newEvent = NSEvent.keyEvent(
|
let finalEvent = NSEvent.keyEvent(
|
||||||
with: .keyDown,
|
with: .keyDown,
|
||||||
location: event.locationInWindow,
|
location: event.locationInWindow,
|
||||||
modifierFlags: event.modifierFlags,
|
modifierFlags: event.modifierFlags,
|
||||||
@ -852,7 +915,7 @@ extension Ghostty {
|
|||||||
keyCode: event.keyCode
|
keyCode: event.keyCode
|
||||||
)
|
)
|
||||||
|
|
||||||
self.keyDown(with: newEvent!)
|
self.keyDown(with: finalEvent!)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,45 +960,38 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyAction(action, event: event)
|
_ = keyAction(action, event: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) {
|
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) -> Bool {
|
||||||
guard let surface = self.surface else { return }
|
guard let surface = self.surface else { return false }
|
||||||
|
return ghostty_surface_key(surface, event.ghosttyKeyEvent(action))
|
||||||
var key_ev = ghostty_input_key_s()
|
|
||||||
key_ev.action = action
|
|
||||||
key_ev.mods = Ghostty.ghosttyMods(event.modifierFlags)
|
|
||||||
key_ev.keycode = UInt32(event.keyCode)
|
|
||||||
key_ev.text = nil
|
|
||||||
key_ev.composing = false
|
|
||||||
ghostty_surface_key(surface, key_ev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent, preedit: String) {
|
private func keyAction(
|
||||||
guard let surface = self.surface else { return }
|
_ action: ghostty_input_action_e,
|
||||||
|
event: NSEvent, preedit: String
|
||||||
|
) -> Bool {
|
||||||
|
guard let surface = self.surface else { return false }
|
||||||
|
|
||||||
preedit.withCString { ptr in
|
return preedit.withCString { ptr in
|
||||||
var key_ev = ghostty_input_key_s()
|
var key_ev = event.ghosttyKeyEvent(action)
|
||||||
key_ev.action = action
|
|
||||||
key_ev.mods = Ghostty.ghosttyMods(event.modifierFlags)
|
|
||||||
key_ev.keycode = UInt32(event.keyCode)
|
|
||||||
key_ev.text = ptr
|
key_ev.text = ptr
|
||||||
key_ev.composing = true
|
key_ev.composing = true
|
||||||
ghostty_surface_key(surface, key_ev)
|
return ghostty_surface_key(surface, key_ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent, text: String) {
|
private func keyAction(
|
||||||
guard let surface = self.surface else { return }
|
_ action: ghostty_input_action_e,
|
||||||
|
event: NSEvent, text: String
|
||||||
|
) -> Bool {
|
||||||
|
guard let surface = self.surface else { return false }
|
||||||
|
|
||||||
text.withCString { ptr in
|
return text.withCString { ptr in
|
||||||
var key_ev = ghostty_input_key_s()
|
var key_ev = event.ghosttyKeyEvent(action)
|
||||||
key_ev.action = action
|
|
||||||
key_ev.mods = Ghostty.ghosttyMods(event.modifierFlags)
|
|
||||||
key_ev.keycode = UInt32(event.keyCode)
|
|
||||||
key_ev.text = ptr
|
key_ev.text = ptr
|
||||||
ghostty_surface_key(surface, key_ev)
|
return ghostty_surface_key(surface, key_ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
src/App.zig
19
src/App.zig
@ -313,6 +313,25 @@ pub fn focusEvent(self: *App, focused: bool) void {
|
|||||||
self.focused = focused;
|
self.focused = focused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given key event would trigger a keybinding
|
||||||
|
/// if it were to be processed. This is useful for determining if
|
||||||
|
/// a key event should be sent to the terminal or not.
|
||||||
|
pub fn keyEventIsBinding(
|
||||||
|
self: *App,
|
||||||
|
rt_app: *apprt.App,
|
||||||
|
event: input.KeyEvent,
|
||||||
|
) bool {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
switch (event.action) {
|
||||||
|
.release => return false,
|
||||||
|
.press, .repeat => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a keybinding for this event then we return true.
|
||||||
|
return rt_app.config.keybind.set.getEvent(event) != null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle a key event at the app-scope. If this key event is used,
|
/// Handle a key event at the app-scope. If this key event is used,
|
||||||
/// this will return true and the caller shouldn't continue processing
|
/// this will return true and the caller shouldn't continue processing
|
||||||
/// the event. If the event is not used, this will return false.
|
/// the event. If the event is not used, this will return false.
|
||||||
|
@ -1637,6 +1637,31 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
|
|||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given key event would trigger a keybinding
|
||||||
|
/// if it were to be processed. This is useful for determining if
|
||||||
|
/// a key event should be sent to the terminal or not.
|
||||||
|
///
|
||||||
|
/// Note that this function does not check if the binding itself
|
||||||
|
/// is performable, only if the key event would trigger a binding.
|
||||||
|
/// If a performable binding is found and the event is not performable,
|
||||||
|
/// then Ghosty will act as though the binding does not exist.
|
||||||
|
pub fn keyEventIsBinding(
|
||||||
|
self: *Surface,
|
||||||
|
event: input.KeyEvent,
|
||||||
|
) bool {
|
||||||
|
switch (event.action) {
|
||||||
|
.release => return false,
|
||||||
|
.press, .repeat => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our keybinding set is either our current nested set (for
|
||||||
|
// sequences) or the root set.
|
||||||
|
const set = self.keyboard.bindings orelse &self.config.keybind.set;
|
||||||
|
|
||||||
|
// If we have a keybinding for this event then we return true.
|
||||||
|
return set.getEvent(event) != null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Called for any key events. This handles keybindings, encoding and
|
/// Called for any key events. This handles keybindings, encoding and
|
||||||
/// sending to the terminal, etc.
|
/// sending to the terminal, etc.
|
||||||
pub fn keyCallback(
|
pub fn keyCallback(
|
||||||
|
@ -147,12 +147,12 @@ pub const App = struct {
|
|||||||
self.core_app.focusEvent(focused);
|
self.core_app.focusEvent(focused);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See CoreApp.keyEvent.
|
/// Convert a C key event into a Zig key event.
|
||||||
pub fn keyEvent(
|
fn coreKeyEvent(
|
||||||
self: *App,
|
self: *App,
|
||||||
target: KeyTarget,
|
target: KeyTarget,
|
||||||
event: KeyEvent,
|
event: KeyEvent,
|
||||||
) !bool {
|
) !?input.KeyEvent {
|
||||||
const action = event.action;
|
const action = event.action;
|
||||||
const keycode = event.keycode;
|
const keycode = event.keycode;
|
||||||
const mods = event.mods;
|
const mods = event.mods;
|
||||||
@ -243,7 +243,7 @@ pub const App = struct {
|
|||||||
result.text,
|
result.text,
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.err("error in preedit callback err={}", .{err});
|
log.err("error in preedit callback err={}", .{err});
|
||||||
return false;
|
return null;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -251,7 +251,7 @@ pub const App = struct {
|
|||||||
.app => {},
|
.app => {},
|
||||||
.surface => |surface| surface.core_surface.preeditCallback(null) catch |err| {
|
.surface => |surface| surface.core_surface.preeditCallback(null) catch |err| {
|
||||||
log.err("error in preedit callback err={}", .{err});
|
log.err("error in preedit callback err={}", .{err});
|
||||||
return false;
|
return null;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +335,7 @@ pub const App = struct {
|
|||||||
} else .invalid;
|
} else .invalid;
|
||||||
|
|
||||||
// Build our final key event
|
// Build our final key event
|
||||||
const input_event: input.KeyEvent = .{
|
return .{
|
||||||
.action = action,
|
.action = action,
|
||||||
.key = key,
|
.key = key,
|
||||||
.physical_key = physical_key,
|
.physical_key = physical_key,
|
||||||
@ -345,24 +345,39 @@ pub const App = struct {
|
|||||||
.utf8 = result.text,
|
.utf8 = result.text,
|
||||||
.unshifted_codepoint = unshifted_codepoint,
|
.unshifted_codepoint = unshifted_codepoint,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See CoreApp.keyEvent.
|
||||||
|
pub fn keyEvent(
|
||||||
|
self: *App,
|
||||||
|
target: KeyTarget,
|
||||||
|
event: KeyEvent,
|
||||||
|
) !bool {
|
||||||
|
// Convert our C key event into a Zig one.
|
||||||
|
const input_event: input.KeyEvent = (try self.coreKeyEvent(
|
||||||
|
target,
|
||||||
|
event,
|
||||||
|
)) orelse return false;
|
||||||
|
|
||||||
// Invoke the core Ghostty logic to handle this input.
|
// Invoke the core Ghostty logic to handle this input.
|
||||||
const effect: CoreSurface.InputEffect = switch (target) {
|
const effect: CoreSurface.InputEffect = switch (target) {
|
||||||
.app => if (self.core_app.keyEvent(
|
.app => if (self.core_app.keyEvent(
|
||||||
self,
|
self,
|
||||||
input_event,
|
input_event,
|
||||||
))
|
)) .consumed else .ignored,
|
||||||
.consumed
|
|
||||||
else
|
|
||||||
.ignored,
|
|
||||||
|
|
||||||
.surface => |surface| try surface.core_surface.keyCallback(input_event),
|
.surface => |surface| try surface.core_surface.keyCallback(
|
||||||
|
input_event,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return switch (effect) {
|
return switch (effect) {
|
||||||
.closed => true,
|
.closed => true,
|
||||||
.ignored => false,
|
.ignored => false,
|
||||||
.consumed => consumed: {
|
.consumed => consumed: {
|
||||||
|
const is_down = input_event.action == .press or
|
||||||
|
input_event.action == .repeat;
|
||||||
|
|
||||||
if (is_down) {
|
if (is_down) {
|
||||||
// If we consume the key then we want to reset the dead
|
// If we consume the key then we want to reset the dead
|
||||||
// key state.
|
// key state.
|
||||||
@ -1371,6 +1386,28 @@ pub const CAPI = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given key event would trigger a binding
|
||||||
|
/// if it were sent to the surface right now. The "right now"
|
||||||
|
/// is important because things like trigger sequences are only
|
||||||
|
/// valid until the next key event.
|
||||||
|
export fn ghostty_app_key_is_binding(
|
||||||
|
app: *App,
|
||||||
|
event: KeyEvent,
|
||||||
|
) bool {
|
||||||
|
const core_event = app.coreKeyEvent(
|
||||||
|
.app,
|
||||||
|
event.keyEvent(),
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("error processing key event err={}", .{err});
|
||||||
|
return false;
|
||||||
|
} orelse {
|
||||||
|
log.warn("error processing key event", .{});
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return app.core_app.keyEventIsBinding(app, core_event);
|
||||||
|
}
|
||||||
|
|
||||||
/// Notify the app that the keyboard was changed. This causes the
|
/// Notify the app that the keyboard was changed. This causes the
|
||||||
/// keyboard layout to be reloaded from the OS.
|
/// keyboard layout to be reloaded from the OS.
|
||||||
export fn ghostty_app_keyboard_changed(v: *App) void {
|
export fn ghostty_app_keyboard_changed(v: *App) void {
|
||||||
@ -1591,16 +1628,38 @@ pub const CAPI = struct {
|
|||||||
export fn ghostty_surface_key(
|
export fn ghostty_surface_key(
|
||||||
surface: *Surface,
|
surface: *Surface,
|
||||||
event: KeyEvent,
|
event: KeyEvent,
|
||||||
) void {
|
) bool {
|
||||||
_ = surface.app.keyEvent(
|
return surface.app.keyEvent(
|
||||||
.{ .surface = surface },
|
.{ .surface = surface },
|
||||||
event.keyEvent(),
|
event.keyEvent(),
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.warn("error processing key event err={}", .{err});
|
log.warn("error processing key event err={}", .{err});
|
||||||
return;
|
return false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given key event would trigger a binding
|
||||||
|
/// if it were sent to the surface right now. The "right now"
|
||||||
|
/// is important because things like trigger sequences are only
|
||||||
|
/// valid until the next key event.
|
||||||
|
export fn ghostty_surface_key_is_binding(
|
||||||
|
surface: *Surface,
|
||||||
|
event: KeyEvent,
|
||||||
|
) bool {
|
||||||
|
const core_event = surface.app.coreKeyEvent(
|
||||||
|
.{ .surface = surface },
|
||||||
|
event.keyEvent(),
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("error processing key event err={}", .{err});
|
||||||
|
return false;
|
||||||
|
} orelse {
|
||||||
|
log.warn("error processing key event", .{});
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return surface.core_surface.keyEventIsBinding(core_event);
|
||||||
|
}
|
||||||
|
|
||||||
/// Send raw text to the terminal. This is treated like a paste
|
/// Send raw text to the terminal. This is treated like a paste
|
||||||
/// so this isn't useful for sending escape sequences. For that,
|
/// so this isn't useful for sending escape sequences. For that,
|
||||||
/// individual key input should be used.
|
/// individual key input should be used.
|
||||||
|
Reference in New Issue
Block a user