diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index fd4100c..4b1cad4 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 91 +#define MONTAUK_BUILD_NUMBER 92 diff --git a/kernel/src/Api/Keyboard.hpp b/kernel/src/Api/Keyboard.hpp index c0e964b..653bf92 100644 --- a/kernel/src/Api/Keyboard.hpp +++ b/kernel/src/Api/Keyboard.hpp @@ -15,9 +15,14 @@ namespace montauk::abi { static bool Sys_IsKeyAvailable() { auto* proc = Sched::GetCurrentProcessPtr(); if (proc && proc->redirected) { - Ipc::HandleSnapshot snapshot; - Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, snapshot); - if (mailbox != nullptr) return Ipc::MailboxHasMessage(mailbox); + 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 && Ipc::StreamHasData(input)) return true; + return mailbox != nullptr && Ipc::MailboxHasMessage(mailbox); + } } return Drivers::PS2::Keyboard::IsKeyAvailable(); } @@ -97,4 +102,41 @@ namespace montauk::abi { } 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; + } }; diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index fc00a28..60f4ce8 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -134,6 +134,8 @@ namespace montauk::abi { return 0; case SYS_GETCHAR: return (int64_t)Sys_GetChar(); + case SYS_GETCHAR_NB: + return (int64_t)Sys_GetCharNb(); case SYS_PING: return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2); case SYS_SPAWN: diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index 885eddc..a6d7707 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -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_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). static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 9fc1f28..e2f0de3 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -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_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_GETCHAR_NB = 166; // () -> ascii, 0 if nothing pending; never blocks // Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM). static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index a58800d..7b762cb 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -189,6 +189,7 @@ extern "C" { #define MTK_SYS_WIFI_RESULTS 163 #define MTK_SYS_WIFI_CONNECT_ASYNC 164 #define MTK_SYS_NETIFS 165 +#define MTK_SYS_GETCHAR_NB 166 /* @SYSCALLS-END */ #define MTK_SOCK_TCP 1 diff --git a/programs/include/libc/stdio.h b/programs/include/libc/stdio.h index a3c9dfd..e11c199 100644 --- a/programs/include/libc/stdio.h +++ b/programs/include/libc/stdio.h @@ -95,6 +95,17 @@ int ungetc(int c, FILE *stream); char *fgets(char *s, int size, 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); FILE *tmpfile(void); char *tmpnam(char *s); diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 93efccc..74c744b 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -197,6 +197,8 @@ namespace montauk { 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 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) { return (uint64_t)syscall2(montauk::abi::SYS_INPUT_WAIT, observedSerial, timeoutMs); } diff --git a/programs/lib/libc/libc.c b/programs/lib/libc/libc.c index f437253..94f5255 100644 --- a/programs/lib/libc/libc.c +++ b/programs/lib/libc/libc.c @@ -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_GETMILLISECONDS 14 #define SYS_GETCHAR 18 +#define SYS_GETCHAR_NB 166 #define SYS_SPAWN 20 #define SYS_WAITPID 23 #define SYS_GETARGS 25 @@ -1478,9 +1479,23 @@ FILE *stdin = &_stdin_file; FILE *stdout = &_stdout_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 size_t _stdin_line_len = 0; static size_t _stdin_line_pos = 0; +static int _stdin_line_ready = 0; static void _stdin_echo_char(char c) { if (c == '\b') { @@ -1492,13 +1507,25 @@ static void _stdin_echo_char(char c) { _zos_syscall1(SYS_PUTCHAR, (unsigned char)c); } -static int _stdin_fill_line(void) { - _stdin_line_len = 0; - _stdin_line_pos = 0; +/* Gather keystrokes into _stdin_linebuf. Returns 1 when a complete line is + buffered. With block == 0, returns 0 as soon as input runs dry, keeping + 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)) { - int c = (int)_zos_syscall0(SYS_GETCHAR); - if (c <= 0) continue; + for (;;) { + int c = block ? (int)_zos_syscall0(SYS_GETCHAR) + : (int)_zos_syscall0(SYS_GETCHAR_NB); + if (c <= 0) { + if (block) continue; + return 0; + } if (c == '\r') c = '\n'; @@ -1513,6 +1540,7 @@ static int _stdin_fill_line(void) { if (c == '\n') { _stdin_linebuf[_stdin_line_len++] = (char)c; _stdin_echo_char((char)c); + _stdin_line_ready = 1; return 1; } @@ -1520,15 +1548,27 @@ static int _stdin_fill_line(void) { _stdin_linebuf[_stdin_line_len++] = (char)c; _stdin_echo_char((char)c); } - } - if (_stdin_line_len > 0) { - _stdin_linebuf[_stdin_line_len++] = '\n'; - _stdin_echo_char('\n'); - return 1; + /* Full buffer: break the line here rather than dropping input. */ + if (_stdin_line_len + 1 >= sizeof(_stdin_linebuf)) { + _stdin_linebuf[_stdin_line_len++] = '\n'; + _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) { @@ -1742,7 +1782,7 @@ int fgetc(FILE *stream) { } 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()) { stream->eof = 1; return EOF; diff --git a/programs/lib/libc/obj/libc.o b/programs/lib/libc/obj/libc.o index 4682b36..63b668a 100644 Binary files a/programs/lib/libc/obj/libc.o and b/programs/lib/libc/obj/libc.o differ diff --git a/programs/man/syscalls.2 b/programs/man/syscalls.2 index 3ed3316..43f1000 100644 --- a/programs/man/syscalls.2 +++ b/programs/man/syscalls.2 @@ -233,6 +233,12 @@ Block until a printable character is typed. 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) Block until the input serial number differs from observedSerial or the timeout elapses; used to sleep