T44: herdr's shortcuts work, which a user found dead: under kitty's flags a key Shift makes a symbol of goes as its own key with Shift, the symbol its alternate, as Ghostty sends it, read from the layout gpui reads on macOS and Windows; on Windows keys that type no text go as win32-input-mode records, which ConPTY asks for and herdr reads, as Windows Terminal sends them; on Linux gpui keeps no key code, which the docs say; shot learns wait, n and ]
This commit is contained in:
19 files changed
+548
-118
No files matched your search
+107
-9
@@ -1,3 +1,6 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use block::ConcreteBlock;
|
||||
use objc::{
|
||||
class, msg_send,
|
||||
@@ -5,13 +8,43 @@ use objc::{
|
||||
sel, sel_impl,
|
||||
};
|
||||
|
||||
#[link(name = "Carbon", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
static kTISPropertyUnicodeKeyLayoutData: *const c_void;
|
||||
fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut c_void;
|
||||
fn TISGetInputSourceProperty(source: *mut c_void, key: *const c_void) -> *const c_void;
|
||||
fn LMGetKbdType() -> u16;
|
||||
fn UCKeyTranslate(
|
||||
layout: *const c_void,
|
||||
code: u16,
|
||||
action: u16,
|
||||
modifiers: u32,
|
||||
keyboard: u32,
|
||||
options: u32,
|
||||
dead: *mut u32,
|
||||
most: usize,
|
||||
length: *mut usize,
|
||||
text: *mut u16,
|
||||
) -> u32;
|
||||
}
|
||||
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
fn CFDataGetBytePtr(data: *const c_void) -> *const u8;
|
||||
fn CFRelease(object: *const c_void);
|
||||
}
|
||||
|
||||
/// AppKit's event types for a key going down and coming up.
|
||||
const KEY_DOWN: u64 = 10;
|
||||
const KEY_UP: u64 = 11;
|
||||
|
||||
/// The Command key's bit in an event's modifier flags.
|
||||
/// The Shift and Command keys' bits in an event's modifier flags.
|
||||
const SHIFT: u64 = 1 << 17;
|
||||
const COMMAND: u64 = 1 << 20;
|
||||
|
||||
/// Command in a layout's modifier state, as `UCKeyTranslate` takes it.
|
||||
const LAYOUT_COMMAND: u32 = 1;
|
||||
|
||||
/// The device bits of the left and right Option keys in an event's modifier flags.
|
||||
const LEFT_OPTION: u64 = 0x20;
|
||||
const RIGHT_OPTION: u64 = 0x40;
|
||||
@@ -23,11 +56,13 @@ pub struct KeyFacts {
|
||||
pub right_option: bool,
|
||||
/// Held down; gpui loses this for keys AppKit routes through commands.
|
||||
pub repeat: bool,
|
||||
/// The key's own character, where gpui named the one Shift made and dropped Shift.
|
||||
pub unshifted: Option<char>,
|
||||
}
|
||||
|
||||
/// The facts of the key event AppKit is handling now.
|
||||
pub fn key_facts() -> anyhow::Result<KeyFacts> {
|
||||
unsafe {
|
||||
/// The facts of the key event AppKit is handling now, which gpui named `name`, with `shift` or not.
|
||||
pub fn key_facts(name: &str, shift: bool) -> anyhow::Result<KeyFacts> {
|
||||
let (flags, repeat, code) = unsafe {
|
||||
let app: *mut Object = msg_send![class!(NSApplication), sharedApplication];
|
||||
let event: *mut Object = msg_send![app, currentEvent];
|
||||
anyhow::ensure!(!event.is_null(), "macOS: no key event to read");
|
||||
@@ -38,12 +73,75 @@ pub fn key_facts() -> anyhow::Result<KeyFacts> {
|
||||
);
|
||||
let flags: u64 = msg_send![event, modifierFlags];
|
||||
let repeat: BOOL = msg_send![event, isARepeat];
|
||||
Ok(KeyFacts {
|
||||
left_option: flags & LEFT_OPTION != 0,
|
||||
right_option: flags & RIGHT_OPTION != 0,
|
||||
repeat: repeat != NO,
|
||||
})
|
||||
let code: u16 = msg_send![event, keyCode];
|
||||
(flags, repeat != NO, code)
|
||||
};
|
||||
let one = name.chars().count() == 1;
|
||||
let unshifted = if flags & SHIFT != 0 && !shift && one {
|
||||
let own = own(code, flags)?;
|
||||
log::debug!("macOS: Shift made {name:?} of key {code}, whose own is {own:?}");
|
||||
own
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(KeyFacts {
|
||||
left_option: flags & LEFT_OPTION != 0,
|
||||
right_option: flags & RIGHT_OPTION != 0,
|
||||
repeat,
|
||||
unshifted,
|
||||
})
|
||||
}
|
||||
|
||||
/// Key `code`'s character without Shift, read as gpui reads key names: Command's layout while Command is down or the plain one types no ASCII.
|
||||
fn own(code: u16, flags: u64) -> anyhow::Result<Option<char>> {
|
||||
let command =
|
||||
flags & COMMAND != 0 || (!typed(0, 0)?.is_ascii() && typed(0, LAYOUT_COMMAND)?.is_ascii());
|
||||
let own = typed(code, if command { LAYOUT_COMMAND } else { 0 })?;
|
||||
let mut chars = own.chars();
|
||||
Ok(match (chars.next(), chars.next()) {
|
||||
(Some(ch), None) if !ch.is_control() => Some(ch),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// What key `code` types under `modifiers` in the current layout; a dead key types its own mark.
|
||||
fn typed(code: u16, modifiers: u32) -> anyhow::Result<String> {
|
||||
const SPACE: u16 = 49;
|
||||
let mut text = [0u16; 4];
|
||||
let mut length = 0;
|
||||
unsafe {
|
||||
let source = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
anyhow::ensure!(!source.is_null(), "macOS: no keyboard layout");
|
||||
let data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
|
||||
if !data.is_null() {
|
||||
let layout = CFDataGetBytePtr(data).cast::<c_void>();
|
||||
let keyboard = u32::from(LMGetKbdType());
|
||||
let mut dead = 0;
|
||||
for key in [code, SPACE] {
|
||||
UCKeyTranslate(
|
||||
layout,
|
||||
key,
|
||||
0,
|
||||
modifiers,
|
||||
keyboard,
|
||||
0,
|
||||
&mut dead,
|
||||
text.len(),
|
||||
&mut length,
|
||||
text.as_mut_ptr(),
|
||||
);
|
||||
if dead == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
CFRelease(source);
|
||||
anyhow::ensure!(
|
||||
!data.is_null(),
|
||||
"macOS: the keyboard layout has no key table"
|
||||
);
|
||||
}
|
||||
String::from_utf16(&text[..length]).context("macOS: the layout typed no UTF-16")
|
||||
}
|
||||
|
||||
/// Turns off the accent menu a held key opens, so held keys repeat as in other terminals.
|
||||
|
||||
Reference in new issue
Block a user