feat: libc - add SYS_GETCHAR_NB and a non-blocking stdin readiness probe

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NQRTGoYgnZQsh7uCh6Jsxm
This commit is contained in:
2026-08-08 14:42:38 +02:00
co-authored by Claude Opus 5
parent fe10216f8b
commit e8407afcdb
11 changed files with 125 additions and 17 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 91 #define MONTAUK_BUILD_NUMBER 92
+45 -3
View File
@@ -15,9 +15,14 @@ namespace montauk::abi {
static bool Sys_IsKeyAvailable() { static bool Sys_IsKeyAvailable() {
auto* proc = Sched::GetCurrentProcessPtr(); auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) { if (proc && proc->redirected) {
Ipc::HandleSnapshot snapshot; Ipc::HandleSnapshot inputSnapshot;
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, snapshot); Ipc::HandleSnapshot mailboxSnapshot;
if (mailbox != nullptr) return Ipc::MailboxHasMessage(mailbox); Ipc::Stream* input = GetRedirInStream(proc, inputSnapshot);
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, mailboxSnapshot);
if (input != nullptr || mailbox != nullptr) {
if (input != nullptr && Ipc::StreamHasData(input)) return true;
return mailbox != nullptr && Ipc::MailboxHasMessage(mailbox);
}
} }
return Drivers::PS2::Keyboard::IsKeyAvailable(); return Drivers::PS2::Keyboard::IsKeyAvailable();
} }
@@ -97,4 +102,41 @@ namespace montauk::abi {
} }
return Drivers::PS2::Keyboard::GetChar(); return Drivers::PS2::Keyboard::GetChar();
} }
/*
* Non-blocking counterpart of Sys_GetChar: returns the next character, or
* 0 when none is pending. IsKeyAvailable is not enough to make GetChar
* safe to call -- a queued key-release or modifier event reports as
* "available" but carries no ascii, and GetChar would go on to block. So
* the drain has to happen on this side of the syscall boundary.
*/
static char Sys_GetCharNb() {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
Ipc::HandleSnapshot inputSnapshot;
Ipc::HandleSnapshot mailboxSnapshot;
Ipc::Stream* input = GetRedirInStream(proc, inputSnapshot);
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, mailboxSnapshot);
if (input != nullptr || mailbox != nullptr) {
if (input != nullptr) {
uint8_t c = 0;
if (Ipc::StreamRead(input, &c, 1, true) > 0) return (char)c;
}
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;
}
return 0;
}
}
while (Drivers::PS2::Keyboard::IsKeyAvailable()) {
auto k = Drivers::PS2::Keyboard::GetKey();
if (k.Pressed && k.Ascii != 0) return k.Ascii;
}
return 0;
}
}; };
+2
View File
@@ -134,6 +134,8 @@ namespace montauk::abi {
return 0; return 0;
case SYS_GETCHAR: case SYS_GETCHAR:
return (int64_t)Sys_GetChar(); return (int64_t)Sys_GetChar();
case SYS_GETCHAR_NB:
return (int64_t)Sys_GetCharNb();
case SYS_PING: case SYS_PING:
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2); return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
case SYS_SPAWN: case SYS_SPAWN:
+3
View File
@@ -307,6 +307,9 @@ namespace montauk::abi {
static constexpr uint64_t SYS_WIFI_CONNECT_ASYNC = 164; // (ssid, password) -> 0 accepted, <0 on error static constexpr uint64_t SYS_WIFI_CONNECT_ASYNC = 164; // (ssid, password) -> 0 accepted, <0 on error
static constexpr uint64_t SYS_NETIFS = 165; // (NetIfInfo*, maxCount) -> count static constexpr uint64_t SYS_NETIFS = 165; // (NetIfInfo*, maxCount) -> count
/* Keyboard.hpp */
static constexpr uint64_t SYS_GETCHAR_NB = 166; // () -> ascii, 0 if nothing pending; never blocks
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM). // Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
+1
View File
@@ -228,6 +228,7 @@ namespace montauk::abi {
static constexpr uint64_t SYS_WIFI_RESULTS = 163; // (WifiNetwork*, maxCount) -> count, no radio work static constexpr uint64_t SYS_WIFI_RESULTS = 163; // (WifiNetwork*, maxCount) -> count, no radio work
static constexpr uint64_t SYS_WIFI_CONNECT_ASYNC = 164; // (ssid, password) -> 0 accepted, <0 on error static constexpr uint64_t SYS_WIFI_CONNECT_ASYNC = 164; // (ssid, password) -> 0 accepted, <0 on error
static constexpr uint64_t SYS_NETIFS = 165; // (NetIfInfo*, maxCount) -> count static constexpr uint64_t SYS_NETIFS = 165; // (NetIfInfo*, maxCount) -> count
static constexpr uint64_t SYS_GETCHAR_NB = 166; // () -> ascii, 0 if nothing pending; never blocks
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM). // Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
+1
View File
@@ -189,6 +189,7 @@ extern "C" {
#define MTK_SYS_WIFI_RESULTS 163 #define MTK_SYS_WIFI_RESULTS 163
#define MTK_SYS_WIFI_CONNECT_ASYNC 164 #define MTK_SYS_WIFI_CONNECT_ASYNC 164
#define MTK_SYS_NETIFS 165 #define MTK_SYS_NETIFS 165
#define MTK_SYS_GETCHAR_NB 166
/* @SYSCALLS-END */ /* @SYSCALLS-END */
#define MTK_SOCK_TCP 1 #define MTK_SOCK_TCP 1
+11
View File
@@ -95,6 +95,17 @@ int ungetc(int c, FILE *stream);
char *fgets(char *s, int size, FILE *stream); char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream); int fputs(const char *s, FILE *stream);
/* MontaukOS extension: non-blocking readiness probe for stdin.
Returns 1 when a complete line is buffered, so a following fgets/fgetc on
stdin returns without blocking; 0 otherwise. Partially typed input is kept.
This is what a select()-style poll over stdin has to be built on -- stdio
descriptors are not waitable kernel objects. */
int montauk_stdin_ready(void);
/* Blocks until a complete line is buffered on stdin, then returns 1. Same
buffer as montauk_stdin_ready(); nothing is consumed either way. */
int montauk_stdin_wait(void);
void perror(const char *s); void perror(const char *s);
FILE *tmpfile(void); FILE *tmpfile(void);
char *tmpnam(char *s); char *tmpnam(char *s);
+2
View File
@@ -197,6 +197,8 @@ namespace montauk {
inline bool is_key_available() { return (bool)syscall0(montauk::abi::SYS_ISKEYAVAILABLE); } inline bool is_key_available() { return (bool)syscall0(montauk::abi::SYS_ISKEYAVAILABLE); }
inline void getkey(montauk::abi::KeyEvent* out) { syscall1(montauk::abi::SYS_GETKEY, (uint64_t)out); } inline void getkey(montauk::abi::KeyEvent* out) { syscall1(montauk::abi::SYS_GETKEY, (uint64_t)out); }
inline char getchar() { return (char)syscall0(montauk::abi::SYS_GETCHAR); } inline char getchar() { return (char)syscall0(montauk::abi::SYS_GETCHAR); }
// Returns 0 rather than blocking when no character is pending.
inline char getchar_nb() { return (char)syscall0(montauk::abi::SYS_GETCHAR_NB); }
inline uint64_t input_wait(uint64_t observedSerial, uint64_t timeoutMs) { inline uint64_t input_wait(uint64_t observedSerial, uint64_t timeoutMs) {
return (uint64_t)syscall2(montauk::abi::SYS_INPUT_WAIT, observedSerial, timeoutMs); return (uint64_t)syscall2(montauk::abi::SYS_INPUT_WAIT, observedSerial, timeoutMs);
} }
+53 -13
View File
@@ -101,6 +101,7 @@ static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) {
#define SYS_FREE 12 #define SYS_FREE 12
#define SYS_GETMILLISECONDS 14 #define SYS_GETMILLISECONDS 14
#define SYS_GETCHAR 18 #define SYS_GETCHAR 18
#define SYS_GETCHAR_NB 166
#define SYS_SPAWN 20 #define SYS_SPAWN 20
#define SYS_WAITPID 23 #define SYS_WAITPID 23
#define SYS_GETARGS 25 #define SYS_GETARGS 25
@@ -1478,9 +1479,23 @@ FILE *stdin = &_stdin_file;
FILE *stdout = &_stdout_file; FILE *stdout = &_stdout_file;
FILE *stderr = &_stderr_file; FILE *stderr = &_stderr_file;
/* ------------------------------------------------------------------------
stdin line editing.
Input is gathered a line at a time so backspace can still take a character
back. The gather is incremental: _stdin_pump() may be called without
blocking, in which case a half-typed line simply stays in the buffer until
the next call. That is what lets montauk_stdin_ready() answer "is a line
available?" without stealing keystrokes -- see the select() shim in the
NetSurf port, which is the reason this is not just a blocking read.
_stdin_line_ready distinguishes "buffer holds a complete line" from "buffer
holds a partial one"; readers must never look at the buffer without it.
------------------------------------------------------------------------ */
static char _stdin_linebuf[512]; static char _stdin_linebuf[512];
static size_t _stdin_line_len = 0; static size_t _stdin_line_len = 0;
static size_t _stdin_line_pos = 0; static size_t _stdin_line_pos = 0;
static int _stdin_line_ready = 0;
static void _stdin_echo_char(char c) { static void _stdin_echo_char(char c) {
if (c == '\b') { if (c == '\b') {
@@ -1492,13 +1507,25 @@ static void _stdin_echo_char(char c) {
_zos_syscall1(SYS_PUTCHAR, (unsigned char)c); _zos_syscall1(SYS_PUTCHAR, (unsigned char)c);
} }
static int _stdin_fill_line(void) { /* Gather keystrokes into _stdin_linebuf. Returns 1 when a complete line is
_stdin_line_len = 0; buffered. With block == 0, returns 0 as soon as input runs dry, keeping
_stdin_line_pos = 0; whatever has been typed so far for the next call. */
static int _stdin_pump(int block) {
if (_stdin_line_ready) {
if (_stdin_line_pos < _stdin_line_len) return 1;
/* Previous line fully read; start gathering the next one. */
_stdin_line_ready = 0;
_stdin_line_len = 0;
_stdin_line_pos = 0;
}
while (_stdin_line_len + 1 < sizeof(_stdin_linebuf)) { for (;;) {
int c = (int)_zos_syscall0(SYS_GETCHAR); int c = block ? (int)_zos_syscall0(SYS_GETCHAR)
if (c <= 0) continue; : (int)_zos_syscall0(SYS_GETCHAR_NB);
if (c <= 0) {
if (block) continue;
return 0;
}
if (c == '\r') c = '\n'; if (c == '\r') c = '\n';
@@ -1513,6 +1540,7 @@ static int _stdin_fill_line(void) {
if (c == '\n') { if (c == '\n') {
_stdin_linebuf[_stdin_line_len++] = (char)c; _stdin_linebuf[_stdin_line_len++] = (char)c;
_stdin_echo_char((char)c); _stdin_echo_char((char)c);
_stdin_line_ready = 1;
return 1; return 1;
} }
@@ -1520,15 +1548,27 @@ static int _stdin_fill_line(void) {
_stdin_linebuf[_stdin_line_len++] = (char)c; _stdin_linebuf[_stdin_line_len++] = (char)c;
_stdin_echo_char((char)c); _stdin_echo_char((char)c);
} }
}
if (_stdin_line_len > 0) { /* Full buffer: break the line here rather than dropping input. */
_stdin_linebuf[_stdin_line_len++] = '\n'; if (_stdin_line_len + 1 >= sizeof(_stdin_linebuf)) {
_stdin_echo_char('\n'); _stdin_linebuf[_stdin_line_len++] = '\n';
return 1; _stdin_echo_char('\n');
_stdin_line_ready = 1;
return 1;
}
} }
}
return 0; static int _stdin_fill_line(void) {
return _stdin_pump(1);
}
int montauk_stdin_ready(void) {
return _stdin_pump(0);
}
int montauk_stdin_wait(void) {
return _stdin_pump(1);
} }
FILE *fopen(const char *path, const char *mode) { FILE *fopen(const char *path, const char *mode) {
@@ -1742,7 +1782,7 @@ int fgetc(FILE *stream) {
} }
if (stream->is_std == 1) { if (stream->is_std == 1) {
if (_stdin_line_pos >= _stdin_line_len) { if (!_stdin_line_ready || _stdin_line_pos >= _stdin_line_len) {
if (!_stdin_fill_line()) { if (!_stdin_fill_line()) {
stream->eof = 1; stream->eof = 1;
return EOF; return EOF;
Binary file not shown.
+6
View File
@@ -233,6 +233,12 @@
Block until a printable character is typed. Block until a printable character is typed.
char montauk::getchar(); char montauk::getchar();
.B SYS_GETCHAR_NB (166)
Return the next printable character, or 0 when none is
pending. Never blocks; key events carrying no ascii are
drained rather than left to stall a following getchar.
char montauk::getchar_nb();
.B SYS_INPUT_WAIT (123) .B SYS_INPUT_WAIT (123)
Block until the input serial number differs from Block until the input serial number differs from
observedSerial or the timeout elapses; used to sleep observedSerial or the timeout elapses; used to sleep