Various cleanups.

- Update input handling to retain SDL_TEXTINPUT after Tab insertion for better platform consistency.
- Allow multiple app instances in macOS by modifying `Info.plist`.
- Bump version to 1.3.8-alpha.
This commit is contained in:
2025-12-04 00:05:13 -08:00
parent 998b1b9817
commit 495183ebd2
3 changed files with 315 additions and 308 deletions

View File

@@ -4,7 +4,7 @@ project(kte)
include(GNUInstallDirs) include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(KTE_VERSION "1.3.7") set(KTE_VERSION "1.3.8-alpha")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default. # Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available. # Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.

View File

@@ -63,66 +63,66 @@ map_key(const SDL_Keycode key,
// Movement and basic keys // Movement and basic keys
// These keys exit k-prefix mode if active (user pressed C-k then a special key). // These keys exit k-prefix mode if active (user pressed C-k then a special key).
switch (key) { switch (key) {
case SDLK_LEFT: case SDLK_LEFT:
k_prefix = false; k_prefix = false;
out = {true, CommandId::MoveLeft, "", 0}; out = {true, CommandId::MoveLeft, "", 0};
return true;
case SDLK_RIGHT:
k_prefix = false;
out = {true, CommandId::MoveRight, "", 0};
return true;
case SDLK_UP:
k_prefix = false;
out = {true, CommandId::MoveUp, "", 0};
return true;
case SDLK_DOWN:
k_prefix = false;
out = {true, CommandId::MoveDown, "", 0};
return true;
case SDLK_HOME:
k_prefix = false;
out = {true, CommandId::MoveHome, "", 0};
return true;
case SDLK_END:
k_prefix = false;
out = {true, CommandId::MoveEnd, "", 0};
return true;
case SDLK_PAGEUP:
k_prefix = false;
out = {true, CommandId::PageUp, "", 0};
return true;
case SDLK_PAGEDOWN:
k_prefix = false;
out = {true, CommandId::PageDown, "", 0};
return true;
case SDLK_DELETE:
k_prefix = false;
out = {true, CommandId::DeleteChar, "", 0};
return true;
case SDLK_BACKSPACE:
k_prefix = false;
out = {true, CommandId::Backspace, "", 0};
return true;
case SDLK_TAB:
// Insert a literal tab character when not interpreting a k-prefix suffix.
// If k-prefix is active, let the k-prefix handler below consume the key
// (so Tab doesn't leave k-prefix stuck).
if (!k_prefix) {
out = {true, CommandId::InsertText, std::string("\t"), 0};
return true; return true;
} case SDLK_RIGHT:
break; // fall through so k-prefix handler can process k_prefix = false;
case SDLK_RETURN: out = {true, CommandId::MoveRight, "", 0};
case SDLK_KP_ENTER: return true;
out = {true, CommandId::Newline, "", 0}; case SDLK_UP:
return true; k_prefix = false;
case SDLK_ESCAPE: out = {true, CommandId::MoveUp, "", 0};
k_prefix = false; return true;
esc_meta = true; // next key will be treated as Meta case SDLK_DOWN:
out.hasCommand = false; // no immediate command for bare ESC in GUI k_prefix = false;
return true; out = {true, CommandId::MoveDown, "", 0};
default: return true;
break; case SDLK_HOME:
k_prefix = false;
out = {true, CommandId::MoveHome, "", 0};
return true;
case SDLK_END:
k_prefix = false;
out = {true, CommandId::MoveEnd, "", 0};
return true;
case SDLK_PAGEUP:
k_prefix = false;
out = {true, CommandId::PageUp, "", 0};
return true;
case SDLK_PAGEDOWN:
k_prefix = false;
out = {true, CommandId::PageDown, "", 0};
return true;
case SDLK_DELETE:
k_prefix = false;
out = {true, CommandId::DeleteChar, "", 0};
return true;
case SDLK_BACKSPACE:
k_prefix = false;
out = {true, CommandId::Backspace, "", 0};
return true;
case SDLK_TAB:
// Insert a literal tab character when not interpreting a k-prefix suffix.
// If k-prefix is active, let the k-prefix handler below consume the key
// (so Tab doesn't leave k-prefix stuck).
if (!k_prefix) {
out = {true, CommandId::InsertText, std::string("\t"), 0};
return true;
}
break; // fall through so k-prefix handler can process
case SDLK_RETURN:
case SDLK_KP_ENTER:
out = {true, CommandId::Newline, "", 0};
return true;
case SDLK_ESCAPE:
k_prefix = false;
esc_meta = true; // next key will be treated as Meta
out.hasCommand = false; // no immediate command for bare ESC in GUI
return true;
default:
break;
} }
// If we are in k-prefix, interpret the very next key via the C-k keymap immediately. // If we are in k-prefix, interpret the very next key via the C-k keymap immediately.
@@ -295,284 +295,288 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
MappedInput mi; MappedInput mi;
bool produced = false; bool produced = false;
switch (e.type) { switch (e.type) {
case SDL_MOUSEWHEEL: { case SDL_MOUSEWHEEL: {
// Let ImGui handle mouse wheel when it wants to capture the mouse // Let ImGui handle mouse wheel when it wants to capture the mouse
// (e.g., when hovering the editor child window with scrollbars). // (e.g., when hovering the editor child window with scrollbars).
// This enables native vertical and horizontal scrolling behavior in GUI. // This enables native vertical and horizontal scrolling behavior in GUI.
if (ImGui::GetIO().WantCaptureMouse) if (ImGui::GetIO().WantCaptureMouse)
return false; return false;
// Otherwise, fallback to mapping vertical wheel to editor scroll commands. // Otherwise, fallback to mapping vertical wheel to editor scroll commands.
int dy = e.wheel.y; int dy = e.wheel.y;
#ifdef SDL_MOUSEWHEEL_FLIPPED #ifdef SDL_MOUSEWHEEL_FLIPPED
if (e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) if (e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
dy = -dy; dy = -dy;
#endif #endif
if (dy != 0) { if (dy != 0) {
int repeat = dy > 0 ? dy : -dy; int repeat = dy > 0 ? dy : -dy;
CommandId id = dy > 0 ? CommandId::ScrollUp : CommandId::ScrollDown; CommandId id = dy > 0 ? CommandId::ScrollUp : CommandId::ScrollDown;
std::lock_guard<std::mutex> lk(mu_);
for (int i = 0; i < repeat; ++i) {
q_.push(MappedInput{true, id, std::string(), 0});
}
return true; // consumed
}
return false;
}
case SDL_KEYDOWN: {
// Remember state before mapping; used for TEXTINPUT suppression heuristics
const bool was_k_prefix = k_prefix_;
const bool was_esc_meta = esc_meta_;
SDL_Keymod mods = SDL_Keymod(e.key.keysym.mod);
const SDL_Keycode key = e.key.keysym.sym;
// Handle Paste: Ctrl+V (Windows/Linux) or Cmd+V (macOS)
// Note: SDL defines letter keycodes in lowercase only (e.g., SDLK_v). Shift does not change keycode.
if ((mods & (KMOD_CTRL | KMOD_GUI)) && (key == SDLK_v)) {
char *clip = SDL_GetClipboardText();
if (clip) {
std::string text(clip);
SDL_free(clip);
// Split on '\n' and enqueue as InsertText/Newline commands
std::lock_guard<std::mutex> lk(mu_); std::lock_guard<std::mutex> lk(mu_);
std::size_t start = 0; for (int i = 0; i < repeat; ++i) {
while (start <= text.size()) { q_.push(MappedInput{true, id, std::string(), 0});
std::size_t pos = text.find('\n', start);
std::string_view segment;
bool has_nl = (pos != std::string::npos);
if (has_nl) {
segment = std::string_view(text).substr(start, pos - start);
} else {
segment = std::string_view(text).substr(start);
}
if (!segment.empty()) {
MappedInput ins{true, CommandId::InsertText, std::string(segment), 0};
q_.push(ins);
}
if (has_nl) {
MappedInput nl{true, CommandId::Newline, std::string(), 0};
q_.push(nl);
start = pos + 1;
} else {
break;
}
} }
// Suppress the corresponding TEXTINPUT that may follow
suppress_text_input_once_ = true;
return true; // consumed return true; // consumed
} }
return false;
} }
case SDL_KEYDOWN: {
// Remember state before mapping; used for TEXTINPUT suppression heuristics
const bool was_k_prefix = k_prefix_;
const bool was_esc_meta = esc_meta_;
SDL_Keymod mods = SDL_Keymod(e.key.keysym.mod);
const SDL_Keycode key = e.key.keysym.sym;
produced = map_key(key, mods, // Handle Paste: Ctrl+V (Windows/Linux) or Cmd+V (macOS)
k_prefix_, esc_meta_, // Note: SDL defines letter keycodes in lowercase only (e.g., SDLK_v). Shift does not change keycode.
uarg_active_, uarg_collecting_, uarg_negative_, uarg_had_digits_, uarg_value_, if ((mods & (KMOD_CTRL | KMOD_GUI)) && (key == SDLK_v)) {
uarg_text_, char *clip = SDL_GetClipboardText();
mi); if (clip) {
std::string text(clip);
SDL_free(clip);
// Split on '\n' and enqueue as InsertText/Newline commands
std::lock_guard<std::mutex> lk(mu_);
std::size_t start = 0;
while (start <= text.size()) {
std::size_t pos = text.find('\n', start);
std::string_view segment;
bool has_nl = (pos != std::string::npos);
if (has_nl) {
segment = std::string_view(text).substr(start, pos - start);
} else {
segment = std::string_view(text).substr(start);
}
if (!segment.empty()) {
MappedInput ins{
true, CommandId::InsertText, std::string(segment), 0
};
q_.push(ins);
}
if (has_nl) {
MappedInput nl{true, CommandId::Newline, std::string(), 0};
q_.push(nl);
start = pos + 1;
} else {
break;
}
}
// Suppress the corresponding TEXTINPUT that may follow
suppress_text_input_once_ = true;
return true; // consumed
}
}
// If we inserted a TAB on KEYDOWN, suppress any subsequent SDL_TEXTINPUT produced = map_key(key, mods,
// for this keystroke to avoid double insertion on platforms that emit it. k_prefix_, esc_meta_,
if (produced && mi.hasCommand && mi.id == CommandId::InsertText && mi.arg == "\t") { uarg_active_, uarg_collecting_, uarg_negative_, uarg_had_digits_,
suppress_text_input_once_ = true; uarg_value_,
} uarg_text_,
mi);
// If we just consumed a universal-argument digit or '-' on KEYDOWN and emitted UArgStatus, // Note: Do NOT suppress SDL_TEXTINPUT after inserting a TAB. Most platforms
// suppress the subsequent SDL_TEXTINPUT for this keystroke to avoid duplicating the digit in status. // do not emit TEXTINPUT for Tab, and suppressing here would incorrectly
if (produced && mi.hasCommand && mi.id == CommandId::UArgStatus) { // eat the next character typed if no TEXTINPUT follows the Tab press.
// Digits without shift, or a plain '-'
const bool is_digit_key = (key >= SDLK_0 && key <= SDLK_9) && !(mods & KMOD_SHIFT); // If we just consumed a universal-argument digit or '-' on KEYDOWN and emitted UArgStatus,
const bool is_minus_key = (key == SDLK_MINUS); // suppress the subsequent SDL_TEXTINPUT for this keystroke to avoid duplicating the digit in status.
if (uarg_active_ && uarg_collecting_ &&(is_digit_key || is_minus_key)) { if (produced && mi.hasCommand && mi.id == CommandId::UArgStatus) {
// Digits without shift, or a plain '-'
const bool is_digit_key = (key >= SDLK_0 && key <= SDLK_9) && !(mods & KMOD_SHIFT);
const bool is_minus_key = (key == SDLK_MINUS);
if (uarg_active_ && uarg_collecting_ &&(is_digit_key || is_minus_key)) {
suppress_text_input_once_ = true;
}
}
// Suppress the immediate following SDL_TEXTINPUT when a printable KEYDOWN was used as a
// k-prefix suffix or Meta (Alt/ESC) chord, regardless of whether a command was produced.
const bool is_alt = (mods & (KMOD_ALT | KMOD_LALT | KMOD_RALT)) != 0;
const bool is_printable_letter = (key >= SDLK_SPACE && key <= SDLK_z);
const bool is_non_text_key =
key == SDLK_TAB || key == SDLK_RETURN || key == SDLK_KP_ENTER ||
key == SDLK_BACKSPACE || key == SDLK_DELETE || key == SDLK_ESCAPE ||
key == SDLK_LEFT || key == SDLK_RIGHT || key == SDLK_UP || key == SDLK_DOWN ||
key == SDLK_HOME || key == SDLK_END || key == SDLK_PAGEUP || key == SDLK_PAGEDOWN;
bool should_suppress = false;
if (!is_non_text_key) {
// Any k-prefix suffix that is printable should suppress TEXTINPUT, even if no
// command mapped (we report unknown via status instead of inserting text).
if (was_k_prefix && is_printable_letter) {
should_suppress = true;
}
// Alt/Meta + letter can also generate TEXTINPUT on some platforms
const bool is_meta_symbol = (
key == SDLK_COMMA || key == SDLK_PERIOD || key == SDLK_LESS || key ==
SDLK_GREATER);
if (is_alt && ((key >= SDLK_a && key <= SDLK_z) || is_meta_symbol)) {
should_suppress = true;
}
// ESC-as-meta followed by printable
if (was_esc_meta && (is_printable_letter || is_meta_symbol)) {
should_suppress = true;
}
}
if (should_suppress) {
suppress_text_input_once_ = true; suppress_text_input_once_ = true;
} }
}
// Suppress the immediate following SDL_TEXTINPUT when a printable KEYDOWN was used as a
// k-prefix suffix or Meta (Alt/ESC) chord, regardless of whether a command was produced.
const bool is_alt = (mods & (KMOD_ALT | KMOD_LALT | KMOD_RALT)) != 0;
const bool is_printable_letter = (key >= SDLK_SPACE && key <= SDLK_z);
const bool is_non_text_key =
key == SDLK_TAB || key == SDLK_RETURN || key == SDLK_KP_ENTER ||
key == SDLK_BACKSPACE || key == SDLK_DELETE || key == SDLK_ESCAPE ||
key == SDLK_LEFT || key == SDLK_RIGHT || key == SDLK_UP || key == SDLK_DOWN ||
key == SDLK_HOME || key == SDLK_END || key == SDLK_PAGEUP || key == SDLK_PAGEDOWN;
bool should_suppress = false;
if (!is_non_text_key) {
// Any k-prefix suffix that is printable should suppress TEXTINPUT, even if no
// command mapped (we report unknown via status instead of inserting text).
if (was_k_prefix && is_printable_letter) {
should_suppress = true;
}
// Alt/Meta + letter can also generate TEXTINPUT on some platforms
const bool is_meta_symbol = (
key == SDLK_COMMA || key == SDLK_PERIOD || key == SDLK_LESS || key == SDLK_GREATER);
if (is_alt && ((key >= SDLK_a && key <= SDLK_z) || is_meta_symbol)) {
should_suppress = true;
}
// ESC-as-meta followed by printable
if (was_esc_meta && (is_printable_letter || is_meta_symbol)) {
should_suppress = true;
}
}
if (should_suppress) {
suppress_text_input_once_ = true;
}
break;
}
case SDL_TEXTINPUT: {
// If the previous KEYDOWN requested suppression of this TEXTINPUT (e.g.,
// we already handled a uarg digit/minus or a k-prefix printable), do it
// immediately before any other handling to avoid duplicates.
if (suppress_text_input_once_) {
suppress_text_input_once_ = false; // consume suppression
produced = true; // consumed event
break; break;
} }
case SDL_TEXTINPUT: {
// If universal argument collection is active, consume digit/minus TEXTINPUT // If the previous KEYDOWN requested suppression of this TEXTINPUT (e.g.,
if (uarg_active_ && uarg_collecting_) { // we already handled a uarg digit/minus or a k-prefix printable), do it
const char *txt = e.text.text; // immediately before any other handling to avoid duplicates.
if (txt && *txt) { if (suppress_text_input_once_) {
unsigned char c0 = static_cast<unsigned char>(txt[0]); suppress_text_input_once_ = false; // consume suppression
if (c0 >= '0' && c0 <= '9') { produced = true; // consumed event
int d = c0 - '0'; break;
if (!uarg_had_digits_) {
uarg_value_ = 0;
uarg_had_digits_ = true;
}
if (uarg_value_ < 100000000) {
uarg_value_ = uarg_value_ * 10 + d;
}
uarg_text_.push_back(static_cast<char>(c0));
mi = {true, CommandId::UArgStatus, uarg_text_, 0};
produced = true; // consumed and enqueued status update
break;
}
if (c0 == '-' && !uarg_had_digits_ && !uarg_negative_) {
uarg_negative_ = true;
uarg_text_ = "-";
mi = {true, CommandId::UArgStatus, uarg_text_, 0};
produced = true;
break;
}
} }
// End collection and allow this TEXTINPUT to be processed normally below
uarg_collecting_ = false;
}
// If we are still in k-prefix and KEYDOWN path didn't handle the suffix, // If universal argument collection is active, consume digit/minus TEXTINPUT
// use TEXTINPUT's actual character (handles Shifted letters on macOS) to map the k-command. if (uarg_active_ && uarg_collecting_) {
if (k_prefix_) { const char *txt = e.text.text;
k_prefix_ = false; if (txt && *txt) {
esc_meta_ = false; unsigned char c0 = static_cast<unsigned char>(txt[0]);
const char *txt = e.text.text; if (c0 >= '0' && c0 <= '9') {
if (txt && *txt) { int d = c0 - '0';
unsigned char c0 = static_cast<unsigned char>(txt[0]); if (!uarg_had_digits_) {
int ascii_key = 0; uarg_value_ = 0;
if (c0 < 0x80) { uarg_had_digits_ = true;
ascii_key = static_cast<int>(c0); }
} if (uarg_value_ < 100000000) {
if (ascii_key != 0) { uarg_value_ = uarg_value_ * 10 + d;
// Map via k-prefix table; do not pass Ctrl for TEXTINPUT case }
CommandId id; uarg_text_.push_back(static_cast<char>(c0));
bool mapped = KLookupKCommand(ascii_key, false, id); mi = {true, CommandId::UArgStatus, uarg_text_, 0};
// Diagnostics: log any k-prefix TEXTINPUT suffix mapping produced = true; // consumed and enqueued status update
char disp = (ascii_key >= 0x20 && ascii_key <= 0x7e) break;
? static_cast<char>(ascii_key) }
: '?'; if (c0 == '-' && !uarg_had_digits_ && !uarg_negative_) {
std::fprintf(stderr, uarg_negative_ = true;
"[kge] k-prefix TEXTINPUT suffix: ascii=%d '%c' mapped=%d id=%d\n", uarg_text_ = "-";
ascii_key, disp, mapped ? 1 : 0, mi = {true, CommandId::UArgStatus, uarg_text_, 0};
mapped ? static_cast<int>(id) : -1); produced = true;
std::fflush(stderr);
if (mapped) {
mi = {true, id, "", 0};
produced = true;
break; // handled; do not insert text
} else {
// Unknown k-command via TEXTINPUT path
int shown = KLowerAscii(ascii_key);
char c = (shown >= 0x20 && shown <= 0x7e)
? static_cast<char>(shown)
: '?';
std::string arg(1, c);
mi = {true, CommandId::UnknownKCommand, arg, 0};
produced = true;
break; break;
} }
} }
// End collection and allow this TEXTINPUT to be processed normally below
uarg_collecting_ = false;
} }
// Consume even if no usable ascii was found
produced = true; // If we are still in k-prefix and KEYDOWN path didn't handle the suffix,
break; // use TEXTINPUT's actual character (handles Shifted letters on macOS) to map the k-command.
} if (k_prefix_) {
// (suppression is handled at the top of this case) k_prefix_ = false;
// Handle ESC-as-meta fallback on TEXTINPUT: some platforms emit only TEXTINPUT esc_meta_ = false;
// for the printable part after ESC. If esc_meta_ is set, translate first char. const char *txt = e.text.text;
if (esc_meta_) { if (txt && *txt) {
esc_meta_ = false; // consume meta prefix unsigned char c0 = static_cast<unsigned char>(txt[0]);
const char *txt = e.text.text; int ascii_key = 0;
if (txt && *txt) { if (c0 < 0x80) {
// Parse first UTF-8 codepoint (we care only about common ASCII cases) ascii_key = static_cast<int>(c0);
unsigned char c0 = static_cast<unsigned char>(txt[0]); }
// Map a few common symbols/letters used in our ESC map if (ascii_key != 0) {
int ascii_key = 0; // Map via k-prefix table; do not pass Ctrl for TEXTINPUT case
if (c0 < 0x80) { CommandId id;
// ASCII path bool mapped = KLookupKCommand(ascii_key, false, id);
ascii_key = static_cast<int>(c0); // Diagnostics: log any k-prefix TEXTINPUT suffix mapping
ascii_key = KLowerAscii(ascii_key); char disp = (ascii_key >= 0x20 && ascii_key <= 0x7e)
} else { ? static_cast<char>(ascii_key)
// Basic handling for macOS Option combos that might produce ≤/≥ : '?';
// Compare the UTF-8 prefix for these two symbols std::fprintf(stderr,
std::string s(txt); "[kge] k-prefix TEXTINPUT suffix: ascii=%d '%c' mapped=%d id=%d\n",
if (s.rfind("\xE2\x89\xA4", 0) == 0) { ascii_key, disp, mapped ? 1 : 0,
// U+2264 '≤' mapped ? static_cast<int>(id) : -1);
ascii_key = '<'; std::fflush(stderr);
} else if (s.rfind("\xE2\x89\xA5", 0) == 0) { if (mapped) {
// U+2265 '≥' mi = {true, id, "", 0};
ascii_key = '>'; produced = true;
break; // handled; do not insert text
} else {
// Unknown k-command via TEXTINPUT path
int shown = KLowerAscii(ascii_key);
char c = (shown >= 0x20 && shown <= 0x7e)
? static_cast<char>(shown)
: '?';
std::string arg(1, c);
mi = {true, CommandId::UnknownKCommand, arg, 0};
produced = true;
break;
}
} }
} }
if (ascii_key != 0) { // Consume even if no usable ascii was found
CommandId id;
if (KLookupEscCommand(ascii_key, id)) {
mi = {true, id, "", 0};
produced = true;
break;
}
}
}
// If we get here, swallow the TEXTINPUT (do not insert stray char)
produced = true;
break;
}
if (!k_prefix_ && e.text.text[0] != '\0') {
// Ensure InsertText never carries a newline; those must originate from KEYDOWN
std::string text(e.text.text);
// Strip any CR/LF that might slip through from certain platforms/IME behaviors
text.erase(std::remove(text.begin(), text.end(), '\n'), text.end());
text.erase(std::remove(text.begin(), text.end(), '\r'), text.end());
if (!text.empty()) {
mi.hasCommand = true;
mi.id = CommandId::InsertText;
mi.arg = std::move(text);
mi.count = 0;
produced = true;
} else {
// Nothing to insert after filtering; consume the event
produced = true; produced = true;
break;
} }
} else { // (suppression is handled at the top of this case)
produced = true; // consumed while k-prefix is active // Handle ESC-as-meta fallback on TEXTINPUT: some platforms emit only TEXTINPUT
// for the printable part after ESC. If esc_meta_ is set, translate first char.
if (esc_meta_) {
esc_meta_ = false; // consume meta prefix
const char *txt = e.text.text;
if (txt && *txt) {
// Parse first UTF-8 codepoint (we care only about common ASCII cases)
unsigned char c0 = static_cast<unsigned char>(txt[0]);
// Map a few common symbols/letters used in our ESC map
int ascii_key = 0;
if (c0 < 0x80) {
// ASCII path
ascii_key = static_cast<int>(c0);
ascii_key = KLowerAscii(ascii_key);
} else {
// Basic handling for macOS Option combos that might produce ≤/≥
// Compare the UTF-8 prefix for these two symbols
std::string s(txt);
if (s.rfind("\xE2\x89\xA4", 0) == 0) {
// U+2264 '≤'
ascii_key = '<';
} else if (s.rfind("\xE2\x89\xA5", 0) == 0) {
// U+2265 '≥'
ascii_key = '>';
}
}
if (ascii_key != 0) {
CommandId id;
if (KLookupEscCommand(ascii_key, id)) {
mi = {true, id, "", 0};
produced = true;
break;
}
}
}
// If we get here, swallow the TEXTINPUT (do not insert stray char)
produced = true;
break;
}
if (!k_prefix_ && e.text.text[0] != '\0') {
// Ensure InsertText never carries a newline; those must originate from KEYDOWN
std::string text(e.text.text);
// Strip any CR/LF that might slip through from certain platforms/IME behaviors
text.erase(std::remove(text.begin(), text.end(), '\n'), text.end());
text.erase(std::remove(text.begin(), text.end(), '\r'), text.end());
if (!text.empty()) {
mi.hasCommand = true;
mi.id = CommandId::InsertText;
mi.arg = std::move(text);
mi.count = 0;
produced = true;
} else {
// Nothing to insert after filtering; consume the event
produced = true;
}
} else {
produced = true; // consumed while k-prefix is active
}
break;
} }
break; default:
} break;
default:
break;
} }
if (produced && mi.hasCommand) { if (produced && mi.hasCommand) {
// Attach universal-argument count if present, then clear the state // Attach universal-argument count if present, then clear the state
if (uarg_active_ &&mi if (uarg_active_ &&mi
. .
id != CommandId::UArgStatus id != CommandId::UArgStatus

View File

@@ -24,5 +24,8 @@
<string>10.13</string> <string>10.13</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<true/> <true/>
<!-- Allow running multiple instances of the app -->
<key>LSMultipleInstancesProhibited</key>
<false/>
</dict> </dict>
</plist> </plist>