feat: switch text to UTF-8; add Unicode key events, dead keys & Czech layout; fix numpad navigation; add NumLock support

This commit is contained in:
daniel committed 2026-10-03 10:11:49 +02:00
1 parent 32660ddd1a
commit 2aa6360b3d
101 files changed
+3733 -1639

No files matched your search

+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 289
#define MONTAUK_BUILD_NUMBER 296
+20 -3
View File
@@ -122,8 +122,9 @@ namespace montauk::abi {
return WriteAllToStream(stream, (const uint8_t*)data, len);
}
static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) {
if (key == nullptr) return InvalidArgument;
// Queue a key event (raw bytes, `len` of them) on a redirected child's key
// mailbox. Readers accept both the 9-byte legacy and the full format.
static int ChildIoQueueKey(int childPid, const void* key, uint16_t len) {
auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
return PermissionDenied;
@@ -133,7 +134,7 @@ namespace montauk::abi {
for (;;) {
uint64_t observedWake = Sched::ObserveObjectWake(mailbox);
int rc = Ipc::MailboxSend(mailbox, 0, key, sizeof(KeyEvent));
int rc = Ipc::MailboxSend(mailbox, 0, key, len);
if (rc < 0) return GenericFailure;
if (rc == 0) {
Sched::BlockOnObjectSince(mailbox, 0, observedWake);
@@ -143,6 +144,22 @@ namespace montauk::abi {
}
}
static int Sys_ChildIoWriteKey(int childPid, const KeyEventV1* key) {
if (key == nullptr) return InvalidArgument;
KeyEventV1 copy = *key;
return ChildIoQueueKey(childPid, &copy, sizeof(copy));
}
// Size-versioned: copies min(size, sizeof(KeyEvent)) bytes (validated
// readable by the dispatcher) and zero-fills the rest.
static int Sys_ChildIoWriteKeyEx(int childPid, const void* key, uint64_t size) {
if (key == nullptr || size < sizeof(KeyEventV1)) return InvalidArgument;
KeyEvent ev;
memset(&ev, 0, sizeof(ev));
memcpy(&ev, key, size < sizeof(ev) ? size : sizeof(ev));
return ChildIoQueueKey(childPid, &ev, sizeof(ev));
}
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
+137 -22
View File
@@ -12,6 +12,107 @@
#include "Common.hpp"
namespace montauk::abi {
// ---- Key text ----------------------------------------------------------
// Keyboard layouts are applied in userspace (desktop, terminal), which send
// KeyEvents carrying the Unicode codepoint. Binaries built before that send
// 9-byte KeyEventV1s whose `ascii` is a Windows-1252 byte; both kinds share
// a redirected child's key mailbox, told apart by message length.
static uint32_t KeyCp1252ToUnicode(uint8_t b) {
static constexpr uint16_t high[32] = {
0x20AC, 0x81, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x8D, 0x017D, 0x8F,
0x90, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x9D, 0x017E, 0x0178,
};
return (b >= 0x80 && b <= 0x9F) ? high[b - 0x80] : b;
}
// The character a key event types, or 0 for control/navigation keys.
static uint32_t KeyTextCodepoint(const KeyEvent& ev) {
if (ev.codepoint) return ev.codepoint;
uint8_t b = (uint8_t)ev.ascii;
if (b < 0x20 || b == 0x7F) return 0;
return KeyCp1252ToUnicode(b);
}
static int KeyEncodeUtf8(uint32_t cp, uint8_t* out) {
if (cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) cp = 0xFFFD;
if (cp < 0x80) { out[0] = (uint8_t)cp; return 1; }
if (cp < 0x800) {
out[0] = (uint8_t)(0xC0 | (cp >> 6));
out[1] = (uint8_t)(0x80 | (cp & 0x3F));
return 2;
}
if (cp < 0x10000) {
out[0] = (uint8_t)(0xE0 | (cp >> 12));
out[1] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
out[2] = (uint8_t)(0x80 | (cp & 0x3F));
return 3;
}
out[0] = (uint8_t)(0xF0 | (cp >> 18));
out[1] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
out[2] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
out[3] = (uint8_t)(0x80 | (cp & 0x3F));
return 4;
}
static void KeyFromDriver(const Drivers::PS2::Keyboard::KeyEvent& k, KeyEvent* out) {
memset(out, 0, sizeof(KeyEvent));
out->scancode = k.Scancode;
out->ascii = k.Ascii;
out->pressed = k.Pressed;
out->shift = k.Shift;
out->ctrl = k.Ctrl;
out->alt = k.Alt;
out->capslock = k.CapsLock;
out->altgr = k.AltGr;
out->extended = k.Extended;
// The kernel's own scancode table is US ASCII.
uint8_t a = (uint8_t)k.Ascii;
out->codepoint = (a >= 0x20 && a < 0x7F) ? a : 0;
}
// Receive one key from a redirected child's mailbox as a full KeyEvent,
// upgrading legacy 9-byte messages. Returns MailboxRecv's result.
static int KeyMailboxRecv(Ipc::Mailbox* mailbox, KeyEvent* out) {
memset(out, 0, sizeof(KeyEvent));
uint16_t len = sizeof(KeyEvent);
int rc = Ipc::MailboxRecv(mailbox, nullptr, out, &len, true);
if (rc > 0 && len < sizeof(KeyEvent)) {
memset((uint8_t*)out + len, 0, sizeof(KeyEvent) - len);
out->codepoint = KeyTextCodepoint(*out);
}
return rc;
}
// Turn a pressed key into the next getchar byte. Characters above ASCII are
// returned as UTF-8, one byte per call; the rest is parked in the process.
static bool KeyTakeChar(Sched::Process* proc, const KeyEvent& ev, char* out) {
if (!ev.pressed) return false;
uint32_t cp = KeyTextCodepoint(ev);
if (cp >= 0x80 && proc != nullptr) {
int n = KeyEncodeUtf8(cp, proc->getcharUtf8);
proc->getcharUtf8Len = (uint8_t)n;
proc->getcharUtf8Pos = 1;
*out = (char)proc->getcharUtf8[0];
return true;
}
if (ev.ascii != 0 && ev.ascii != KEY_ASCII_SUBSTITUTE) {
*out = ev.ascii;
return true;
}
return false;
}
static bool KeyPendingChar(Sched::Process* proc, char* out) {
if (proc == nullptr || proc->getcharUtf8Pos >= proc->getcharUtf8Len) return false;
*out = (char)proc->getcharUtf8[proc->getcharUtf8Pos++];
return true;
}
// ---- Syscalls ------------------------------------------------------------
static bool Sys_IsKeyAvailable() {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
@@ -27,8 +128,8 @@ namespace montauk::abi {
return Drivers::PS2::Keyboard::IsKeyAvailable();
}
static void Sys_GetKey(KeyEvent* outEvent) {
if (outEvent == nullptr) return;
// Blocking read of the next key event, in the full format.
static void ReadKeyEvent(KeyEvent* outEvent) {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
Ipc::HandleSnapshot snapshot;
@@ -36,8 +137,7 @@ namespace montauk::abi {
if (mailbox != nullptr) {
for (;;) {
uint64_t observedWake = Sched::ObserveObjectWake(mailbox);
uint16_t len = sizeof(KeyEvent);
int rc = Ipc::MailboxRecv(mailbox, nullptr, outEvent, &len, true);
int rc = KeyMailboxRecv(mailbox, outEvent);
if (rc > 0) return;
if (rc < 0) {
memset(outEvent, 0, sizeof(KeyEvent));
@@ -47,20 +147,33 @@ namespace montauk::abi {
}
}
}
auto k = Drivers::PS2::Keyboard::GetKey();
outEvent->scancode = k.Scancode;
outEvent->ascii = k.Ascii;
outEvent->pressed = k.Pressed;
outEvent->shift = k.Shift;
outEvent->ctrl = k.Ctrl;
outEvent->alt = k.Alt;
outEvent->capslock = k.CapsLock;
outEvent->altgr = k.AltGr;
outEvent->extended = k.Extended;
KeyFromDriver(Drivers::PS2::Keyboard::GetKey(), outEvent);
}
// SYS_GETKEY: legacy 9-byte event. A new-format event's first 9 bytes are
// already a valid KeyEventV1 (ascii holds the 1252 byte or SUB).
static void Sys_GetKey(KeyEventV1* outEvent) {
if (outEvent == nullptr) return;
KeyEvent ev;
ReadKeyEvent(&ev);
memcpy(outEvent, &ev, sizeof(KeyEventV1));
}
// SYS_GETKEY_EX: size-versioned. The caller's size was validated by the
// dispatcher (>= sizeof(KeyEventV1), writable).
static int Sys_GetKeyEx(void* outEvent, uint64_t size) {
KeyEvent ev;
ReadKeyEvent(&ev);
uint64_t n = size < sizeof(KeyEvent) ? size : sizeof(KeyEvent);
memcpy(outEvent, &ev, n);
if (size > n) memset((uint8_t*)outEvent + n, 0, size - n);
return Success;
}
static char Sys_GetChar() {
auto* proc = Sched::GetCurrentProcessPtr();
char pending;
if (KeyPendingChar(proc, &pending)) return pending;
if (proc && proc->redirected) {
Ipc::HandleSnapshot inputSnapshot;
Ipc::HandleSnapshot mailboxSnapshot;
@@ -78,11 +191,11 @@ namespace montauk::abi {
}
if (mailbox != nullptr) {
KeyEvent ev{};
uint16_t len = sizeof(ev);
int rc = Ipc::MailboxRecv(mailbox, nullptr, &ev, &len, true);
KeyEvent ev;
int rc = KeyMailboxRecv(mailbox, &ev);
if (rc > 0) {
if (ev.pressed && ev.ascii != 0) return ev.ascii;
char c;
if (KeyTakeChar(proc, ev, &c)) return c;
continue;
}
if (rc < 0 && input == nullptr) return 0;
@@ -115,6 +228,8 @@ namespace montauk::abi {
*/
static char Sys_GetCharNb() {
auto* proc = Sched::GetCurrentProcessPtr();
char pending;
if (KeyPendingChar(proc, &pending)) return pending;
if (proc && proc->redirected) {
Ipc::HandleSnapshot inputSnapshot;
Ipc::HandleSnapshot mailboxSnapshot;
@@ -127,10 +242,10 @@ namespace montauk::abi {
}
while (mailbox != nullptr) {
KeyEvent ev{};
uint16_t len = sizeof(ev);
if (Ipc::MailboxRecv(mailbox, nullptr, &ev, &len, true) <= 0) break;
if (ev.pressed && ev.ascii != 0) return ev.ascii;
KeyEvent ev;
if (KeyMailboxRecv(mailbox, &ev) <= 0) break;
char c;
if (KeyTakeChar(proc, ev, &c)) return c;
}
return 0;
}
+12 -4
View File
@@ -139,8 +139,8 @@ namespace montauk::abi {
case SYS_ISKEYAVAILABLE:
return (int64_t)Sys_IsKeyAvailable();
case SYS_GETKEY:
if (!UserMemory::Writable<KeyEvent>(frame->arg1)) return BadPointer;
Sys_GetKey((KeyEvent*)frame->arg1);
if (!UserMemory::Writable<KeyEventV1>(frame->arg1)) return BadPointer;
Sys_GetKey((KeyEventV1*)frame->arg1);
return Success;
case SYS_GETCHAR:
return (int64_t)Sys_GetChar();
@@ -365,8 +365,8 @@ namespace montauk::abi {
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, false)) return BadPointer;
return (int64_t)Sys_ChildIoWrite((int)frame->arg1, (const char*)frame->arg2, (int)frame->arg3);
case SYS_CHILDIO_WRITEKEY:
if (!UserMemory::Readable<KeyEvent>(frame->arg2)) return BadPointer;
return (int64_t)Sys_ChildIoWriteKey((int)frame->arg1, (const KeyEvent*)frame->arg2);
if (!UserMemory::Readable<KeyEventV1>(frame->arg2)) return BadPointer;
return (int64_t)Sys_ChildIoWriteKey((int)frame->arg1, (const KeyEventV1*)frame->arg2);
case SYS_CHILDIO_SETTERMSZ:
return (int64_t)Sys_ChildIoSetTermsz((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
case SYS_WINCREATE:
@@ -527,6 +527,14 @@ namespace montauk::abi {
return Sched::Consent::Next((ConsentRequest*)frame->arg1);
case SYS_CONSENT_REPLY:
return Sched::Consent::Reply(frame->arg1, (int)frame->arg2);
case SYS_GETKEY_EX:
if (frame->arg2 < sizeof(KeyEventV1) || frame->arg2 > 4096) return InvalidArgument;
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
return Sys_GetKeyEx((void*)frame->arg1, frame->arg2);
case SYS_CHILDIO_WRITEKEY_EX:
if (frame->arg3 < sizeof(KeyEventV1) || frame->arg3 > 4096) return InvalidArgument;
if (!UserMemory::Range(frame->arg2, frame->arg3, false)) return BadPointer;
return (int64_t)Sys_ChildIoWriteKeyEx((int)frame->arg1, (const void*)frame->arg2, frame->arg3);
case SYS_THREAD_SPAWN:
return Sys_ThreadSpawn(frame->arg1, frame->arg2, frame->arg3);
case SYS_THREAD_EXIT:
+38 -2
View File
@@ -358,6 +358,12 @@ namespace montauk::abi {
static constexpr uint64_t SYS_CONSENT_NEXT = 192; // (ConsentRequest*) -> 1 delivered, 0 none queued
static constexpr uint64_t SYS_CONSENT_REPLY = 193; // (requestId, CONSENT_ALLOW/DENY) -> 0, -1 stale id
/* Unicode key events. Size-versioned: the caller passes sizeof(its
KeyEvent); the kernel copies min(size, its own) bytes and zero-fills the
rest, so the struct can grow without another syscall. */
static constexpr uint64_t SYS_GETKEY_EX = 194; // (KeyEvent*, size) -> 0; blocks like SYS_GETKEY
static constexpr uint64_t SYS_CHILDIO_WRITEKEY_EX = 195; // (childPid, const KeyEvent*, size) -> status
/* Kernel-owned process capabilities. User identities may namespace
per-user resources, but never participate in authorization decisions. */
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
@@ -600,7 +606,10 @@ namespace montauk::abi {
uint64_t txPackets;
};
struct KeyEvent {
// Legacy 9-byte key event, read by SYS_GETKEY and written by
// SYS_CHILDIO_WRITEKEY. Kept so binaries built before codepoints still work.
// `ascii` is a Windows-1252 byte.
struct KeyEventV1 {
uint8_t scancode;
char ascii;
bool pressed;
@@ -616,6 +625,32 @@ namespace montauk::abi {
bool extended;
};
// Key event with the Unicode text it types (SYS_GETKEY_EX,
// SYS_CHILDIO_WRITEKEY_EX, WinEvent). Its first 9 bytes ARE a KeyEventV1, so
// a legacy reader sees a valid event. `codepoint` is the character the key
// produces (0 for control/navigation keys and dead keys). `ascii` is its
// Windows-1252 byte, or KEY_ASCII_SUBSTITUTE when 1252 has none.
struct KeyEvent {
uint8_t scancode;
char ascii;
bool pressed;
bool shift;
bool ctrl;
bool alt;
bool capslock;
bool altgr;
bool extended;
uint8_t _reserved[3];
uint32_t codepoint;
};
static_assert(sizeof(KeyEventV1) == 9, "KeyEventV1 is frozen ABI");
static_assert(sizeof(KeyEvent) == 16, "KeyEvent must fit the WinEvent union");
// ASCII SUB: `ascii` of a text key whose character has no Windows-1252 byte.
// A control character, so legacy code that only accepts ascii >= 0x20 skips
// it, while code testing `ascii == 0` for navigation keys is not confused.
static constexpr char KEY_ASCII_SUBSTITUTE = 0x1A;
struct MouseState {
int32_t x;
int32_t y;
@@ -653,12 +688,13 @@ namespace montauk::abi {
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
uint8_t _pad[3];
union {
KeyEvent key;
KeyEvent key; // 16 bytes: the union was already that size (mouse)
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
struct { int32_t w, h; } resize;
struct { int32_t scale; } scale;
};
};
static_assert(sizeof(WinEvent) == 20, "WinEvent layout is shared with existing binaries");
struct WinInfo {
int32_t id;
+14
View File
@@ -139,6 +139,7 @@ namespace Drivers::PS2::Keyboard {
ack = Drivers::PS2::ReadData();
g_Modifiers = {};
g_Modifiers.NumLock = true; // keypad types digits, as on most PCs
g_BufferHead = 0;
g_BufferTail = 0;
g_ExtendedScancode = false;
@@ -257,6 +258,9 @@ namespace Drivers::PS2::Keyboard {
ascii = g_ScancodeToAscii[keycode];
}
}
// Num Lock off: the keypad is navigation keys, which type nothing.
if (!g_Modifiers.NumLock && IsKeypadNavScancode(keycode))
ascii = 0;
KeyEvent event = {
.Scancode = scancode,
@@ -310,6 +314,12 @@ namespace Drivers::PS2::Keyboard {
KeyEvent stamped = event;
if (stamped.Scancode == ScCapsLock && stamped.Pressed)
g_Modifiers.CapsLock = !g_Modifiers.CapsLock;
// Num Lock likewise. Like the PS/2 path, the lock key itself is not
// delivered to apps.
if (stamped.Scancode == ScNumLock) {
if (stamped.Pressed) g_Modifiers.NumLock = !g_Modifiers.NumLock;
return;
}
stamped.CapsLock = g_Modifiers.CapsLock;
PushAndNotify(stamped);
}
@@ -318,4 +328,8 @@ namespace Drivers::PS2::Keyboard {
return g_Modifiers.CapsLock;
}
bool GetNumLock() {
return g_Modifiers.NumLock;
}
};
+10
View File
@@ -63,4 +63,14 @@ namespace Drivers::PS2::Keyboard {
// compute ASCII, since the lock is toggled centrally here.
bool GetCapsLock();
// Current Num Lock state, owned here like Caps Lock. On (the default), the
// keypad types digits; off, it acts as the navigation keys printed on it.
bool GetNumLock();
// Set-1 scancodes of the keypad keys that are a digit/'.' with Num Lock on
// and Home/arrows/PgUp/End/PgDn/Ins/Del with it off (not '-' and '+').
inline bool IsKeypadNavScancode(uint8_t keycode) {
return keycode >= 0x47 && keycode <= 0x53 && keycode != 0x4A && keycode != 0x4E;
}
};
+13 -3
View File
@@ -223,6 +223,12 @@ namespace Drivers::USB::HidKeyboard {
if (caps && base >= 'a' && base <= 'z') upper = !shift;
ascii = upper ? g_ScancodeToAsciiShifted[scancode] : base;
}
// Num Lock off: the keypad is navigation keys, which type nothing.
// (Only keypad usages get here with these scancodes: the dedicated
// navigation keys are nonChar already.)
if (!Drivers::PS2::Keyboard::GetNumLock() &&
Drivers::PS2::Keyboard::IsKeypadNavScancode(scancode))
ascii = 0;
Drivers::PS2::Keyboard::KeyEvent event = {
.Scancode = scancode,
@@ -367,10 +373,14 @@ namespace Drivers::USB::HidKeyboard {
uint8_t scancode = g_HidToScancode[key];
if (scancode != 0) {
InjectKey(scancode, true, modifiers, IsNonCharKey(key), IsExtendedKey(key));
g_RepeatKey = key;
g_RepeatStartMs = now;
g_LastRepeatMs = 0;
newKeyPressed = true;
// Lock keys toggle once per press; repeating them while
// held would flip Caps/Num/Scroll Lock on and off.
if (key != 0x39 && key != 0x53 && key != 0x47) {
g_RepeatKey = key;
g_RepeatStartMs = now;
g_LastRepeatMs = 0;
}
}
}
}
+96 -12
View File
@@ -301,6 +301,53 @@ namespace Fs::Fat32 {
// Short name generation and LFN helpers for Create
// =========================================================================
// =========================================================================
// Name encoding: VFS names are UTF-8, LFN entries are UTF-16
// =========================================================================
// Decode one UTF-8 character at s (NUL-terminated); stores its length.
// A byte that does not start a well-formed sequence is taken as one
// Latin-1 character, so legacy single-byte names still round-trip.
static uint32_t DecodeUtf8(const char* s, int* consumed) {
const uint8_t* u = (const uint8_t*)s;
uint8_t b0 = u[0];
*consumed = 1;
if (b0 < 0x80) return b0;
int n;
uint32_t cp, min;
if (b0 >= 0xC2 && b0 <= 0xDF) { n = 2; cp = b0 & 0x1F; min = 0x80; }
else if (b0 >= 0xE0 && b0 <= 0xEF) { n = 3; cp = b0 & 0x0F; min = 0x800; }
else if (b0 >= 0xF0 && b0 <= 0xF4) { n = 4; cp = b0 & 0x07; min = 0x10000; }
else return b0;
for (int i = 1; i < n; i++) {
if ((u[i] & 0xC0) != 0x80) return b0; // also stops at NUL
cp = (cp << 6) | (u[i] & 0x3F);
}
if (cp < min || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) return b0;
*consumed = n;
return cp;
}
static int EncodeUtf8(uint32_t cp, char* out) {
if (cp < 0x80) { out[0] = (char)cp; return 1; }
if (cp < 0x800) {
out[0] = (char)(0xC0 | (cp >> 6));
out[1] = (char)(0x80 | (cp & 0x3F));
return 2;
}
if (cp < 0x10000) {
out[0] = (char)(0xE0 | (cp >> 12));
out[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
out[2] = (char)(0x80 | (cp & 0x3F));
return 3;
}
out[0] = (char)(0xF0 | (cp >> 18));
out[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
out[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
out[3] = (char)(0x80 | (cp & 0x3F));
return 4;
}
static void GenerateShortName(const char* longName, char* shortName) {
memset(shortName, ' ', 11);
@@ -315,9 +362,15 @@ namespace Fs::Fat32 {
// Base name (up to 8 chars, uppercase, skip invalid chars)
int baseEnd = (lastDot >= 0) ? lastDot : nameLen;
int j = 0;
// A non-ASCII character becomes one '_' (8.3 names are OEM codepage;
// the real name lives in the LFN entries).
for (int i = 0; i < baseEnd && j < 8; i++) {
char c = longName[i];
if (c == ' ' || c == '.') continue;
if ((uint8_t)c >= 0x80) {
if (((uint8_t)c & 0xC0) == 0x80) continue;
c = '_';
}
if (c >= 'a' && c <= 'z') c -= 32;
shortName[j++] = c;
}
@@ -327,6 +380,10 @@ namespace Fs::Fat32 {
j = 0;
for (int i = lastDot + 1; longName[i] && j < 3; i++) {
char c = longName[i];
if ((uint8_t)c >= 0x80) {
if (((uint8_t)c & 0xC0) == 0x80) continue;
c = '_';
}
if (c >= 'a' && c <= 'z') c -= 32;
shortName[8 + j++] = c;
}
@@ -403,12 +460,22 @@ namespace Fs::Fat32 {
// Returns the number of LFN entries.
static int BuildLfnEntries(const char* longName, uint8_t checksum,
uint8_t* outEntries) {
// Convert to UTF-16
uint16_t utf16[MaxNameLen];
// Convert UTF-8 to UTF-16 (LFN holds at most 255 code units)
uint16_t utf16[MaxNameLen + 13];
int len = 0;
while (longName[len] && len < MaxNameLen - 1) {
utf16[len] = (uint8_t)longName[len];
len++;
for (const char* p = longName; *p;) {
int n;
uint32_t cp = DecodeUtf8(p, &n);
int units = cp >= 0x10000 ? 2 : 1;
if (len + units > 255) break;
if (units == 2) {
cp -= 0x10000;
utf16[len++] = (uint16_t)(0xD800 | (cp >> 10));
utf16[len++] = (uint16_t)(0xDC00 | (cp & 0x3FF));
} else {
utf16[len++] = (uint16_t)cp;
}
p += n;
}
utf16[len] = 0;
@@ -464,9 +531,11 @@ namespace Fs::Fat32 {
// Compare case-insensitively
if (!StrEqualNoCase(longName, reconstructed)) return true;
// Check if long name has lowercase (short names are uppercase)
// Check if long name has lowercase (short names are uppercase) or
// characters the short name could not hold
for (int i = 0; longName[i]; i++) {
if (longName[i] >= 'a' && longName[i] <= 'z') return true;
if ((uint8_t)longName[i] >= 0x80) return true;
}
return false;
}
@@ -619,10 +688,24 @@ namespace Fs::Fat32 {
for (int i = 0; i < 2; i++) memcpy(&out[11 + i], entry + 28 + i * 2, 2);
}
static void Utf16ToAscii(const uint16_t* src, int maxLen, char* dst) {
// LFN UTF-16 -> UTF-8 name. Unpaired surrogates become U+FFFD; the name is
// cut on a character boundary if it does not fit MaxNameLen bytes.
static void Utf16ToUtf8(const uint16_t* src, int maxLen, char* dst,
int dstCap = MaxNameLen) {
int j = 0;
for (int i = 0; i < maxLen && src[i] != 0 && j < MaxNameLen - 1; i++) {
dst[j++] = (src[i] < 128) ? (char)src[i] : '?';
for (int i = 0; i < maxLen && src[i] != 0 && src[i] != 0xFFFF; i++) {
uint32_t cp = src[i];
if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < maxLen &&
src[i + 1] >= 0xDC00 && src[i + 1] <= 0xDFFF) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (src[i + 1] - 0xDC00);
i++;
} else if (cp >= 0xD800 && cp <= 0xDFFF) {
cp = 0xFFFD;
}
char enc[4];
int n = EncodeUtf8(cp, enc);
if (j + n > dstCap - 1) break;
for (int k = 0; k < n; k++) dst[j++] = enc[k];
}
dst[j] = '\0';
}
@@ -685,7 +768,7 @@ namespace Fs::Fat32 {
pe.attributes = attr;
if (hasLfn) {
Utf16ToAscii(lfnBuf, MaxNameLen, pe.name);
Utf16ToUtf8(lfnBuf, MaxNameLen, pe.name);
} else {
ParseShortName(e, pe.name);
}
@@ -752,7 +835,8 @@ namespace Fs::Fat32 {
if (attr & ATTR_VOLUME_ID) { hasLfn = false; continue; }
if (hasLfn) {
Utf16ToAscii(lfnBuf, MaxNameLen, outNames[count]);
// Leave room for the '/' appended to directory names
Utf16ToUtf8(lfnBuf, MaxNameLen, outNames[count], MaxNameLen - 1);
} else {
ParseShortName(e, outNames[count]);
}
@@ -848,7 +932,7 @@ namespace Fs::Fat32 {
char entryName[MaxNameLen];
if (hasLfn) {
Utf16ToAscii(lfnBuf, MaxNameLen, entryName);
Utf16ToUtf8(lfnBuf, MaxNameLen, entryName);
} else {
ParseShortName(e, entryName);
}
+7
View File
@@ -324,6 +324,8 @@ namespace Sched {
processTable[i].inTail = 0;
processTable[i].keyHead = 0;
processTable[i].keyTail = 0;
processTable[i].getcharUtf8Len = 0;
processTable[i].getcharUtf8Pos = 0;
processTable[i].termCols = 0;
processTable[i].termRows = 0;
processTable[i].ioOutHandle = -1;
@@ -654,6 +656,8 @@ namespace Sched {
proc.inTail = 0;
proc.keyHead = 0;
proc.keyTail = 0;
proc.getcharUtf8Len = 0;
proc.getcharUtf8Pos = 0;
proc.termCols = 0;
proc.termRows = 0;
proc.ioOutHandle = -1;
@@ -835,6 +839,7 @@ namespace Sched {
thr.outBuf = nullptr; thr.outHead = 0; thr.outTail = 0;
thr.inBuf = nullptr; thr.inHead = 0; thr.inTail = 0;
thr.keyHead = 0; thr.keyTail = 0;
thr.getcharUtf8Len = 0; thr.getcharUtf8Pos = 0;
thr.termCols = 0; thr.termRows = 0;
thr.ioOutHandle = -1; thr.ioInHandle = -1;
thr.ioKeyHandle = -1; thr.ioWaitsetHandle = -1;
@@ -1700,6 +1705,8 @@ namespace Sched {
proc.inTail = 0;
proc.keyHead = 0;
proc.keyTail = 0;
proc.getcharUtf8Len = 0;
proc.getcharUtf8Pos = 0;
proc.termCols = 0;
proc.termRows = 0;
proc.ioOutHandle = -1;
+5 -1
View File
@@ -135,9 +135,13 @@ namespace Sched {
uint8_t* inBuf = nullptr; // 4KB ring: parent writes, child reads (getchar)
volatile uint32_t inHead = 0;
volatile uint32_t inTail = 0;
montauk::abi::KeyEvent keyBuf[64]; // parent injects, child reads (getkey/iskeyavailable)
volatile uint32_t keyHead = 0;
volatile uint32_t keyTail = 0;
// SYS_GETCHAR returns a key's UTF-8 encoding one byte per call; the
// bytes not handed out yet wait here.
uint8_t getcharUtf8[4] = {};
uint8_t getcharUtf8Len = 0;
uint8_t getcharUtf8Pos = 0;
static constexpr uint32_t IoBufSize = 4096;
// GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE)