Files
nt-lite/code/user/system/rssrv/console.c
T

485 lines
22 KiB
C

// RSSRV native console. One logical console owns text output policy and consumes RSSRV logical input.
#include "rssrv_internal.h"
#include <stdlib.h>
#include <string.h>
#define RSSRV_EVENT_MANUAL_RESET 1u
#define RSSRV_CONSOLE_KEY_QUEUE_MASK (RSSRV_CONSOLE_KEY_QUEUE_SIZE - 1u)
typedef struct RSSRV_CONSOLE_SESSION {
RSSRV_CONSOLE_STATE* Console;
SYS_HANDLE Endpoint;
} RSSRV_CONSOLE_SESSION;
// Acquires the console mutation lock.
static KSTATUS RssrvConsoleLock(RSSRV_CONSOLE_STATE* console) {
if (!console || console->Mutex == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER;
return SysWait(console->Mutex, SYS_WAIT_FLAG_NONE);
}
// Releases the console mutation lock.
static void RssrvConsoleUnlock(RSSRV_CONSOLE_STATE* console) {
if (console && console->Mutex != SYS_HANDLE_INVALID) (void)SysReleaseMutex(console->Mutex);
}
// Refreshes the console-input availability event while the console mutex is held.
static void RssrvConsoleRefreshInputEventLocked(RSSRV_CONSOLE_STATE* console) {
if (console->KeyHead != console->KeyTail) (void)SysSignalEvent(console->KeyEvent);
else (void)SysClearEvent(console->KeyEvent);
}
// Receives one focused logical key record from Session 0.
static void RssrvConsoleRouteKey(void* context, const INPUT_KEY_RECORD* record) {
RSSRV_CONSOLE_STATE* console = (RSSRV_CONSOLE_STATE*)context;
if (!console || !record || !KSUCCESS(RssrvConsoleLock(console))) return;
uint32_t next = (console->KeyTail + 1u) & RSSRV_CONSOLE_KEY_QUEUE_MASK;
if (next != console->KeyHead) {
console->KeyQueue[console->KeyTail] = *record;
console->KeyTail = next;
RssrvConsoleRefreshInputEventLocked(console);
}
RssrvConsoleUnlock(console);
}
// Receives pointer routing for the console target. The text console does not consume pointer events yet.
static void RssrvConsoleRouteMouse(void* context, const INPUT_MOUSE_EVENT* event, int32_t x, int32_t y) {
(void)context;
(void)event;
(void)x;
(void)y;
}
// Reads or peeks one keyboard record routed specifically to this console.
static KSTATUS RssrvConsoleReadInput(RSSRV_CONSOLE_STATE* console,
int peek,
uint64_t timeoutMilliseconds,
INPUT_KEY_RECORD* recordOut) {
if (!console || !recordOut) return NTLSTATUS_INVALID_PARAMETER;
for (;;) {
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
if (console->KeyHead != console->KeyTail) {
*recordOut = console->KeyQueue[console->KeyHead];
if (!peek) {
console->KeyHead = (console->KeyHead + 1u) & RSSRV_CONSOLE_KEY_QUEUE_MASK;
RssrvConsoleRefreshInputEventLocked(console);
}
RssrvConsoleUnlock(console);
return NTLSTATUS_SUCCESS;
}
RssrvConsoleUnlock(console);
if (timeoutMilliseconds == 0) return NTLSTATUS_WOULD_BLOCK;
status = SysWaitSingle(console->KeyEvent, SYS_WAIT_FLAG_NONE, timeoutMilliseconds);
if (!KSUCCESS(status)) return status;
}
}
// Discards all keyboard records already routed to this console.
static KSTATUS RssrvConsoleFlushInput(RSSRV_CONSOLE_STATE* console, uint32_t* discardedOut) {
if (!console) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
uint32_t discarded = (console->KeyTail - console->KeyHead) & RSSRV_CONSOLE_KEY_QUEUE_MASK;
console->KeyHead = console->KeyTail;
RssrvConsoleRefreshInputEventLocked(console);
RssrvConsoleUnlock(console);
if (discardedOut) *discardedOut = discarded;
return NTLSTATUS_SUCCESS;
}
// Records one successful visible console mutation while holding the console lock.
static void RssrvConsoleChangedLocked(RSSRV_CONSOLE_STATE* console) {
console->Generation++;
if (console->Generation == 0) console->Generation = 1;
}
// Writes one console chunk in serialized order.
static KSTATUS RssrvConsoleWrite(RSSRV_CONSOLE_STATE* console,
const char* data,
uint32_t length,
uint32_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
if (!console || length > NTL_CONSOLE_WRITE_MAX || (length && !data))
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
status = RssrvDisplayWriteConsole(console->Display, data, length);
if (KSUCCESS(status) && length) RssrvConsoleChangedLocked(console);
RssrvConsoleUnlock(console);
if (KSUCCESS(status) && writtenOut) *writtenOut = length;
return status;
}
// Writes one positioned status line, retaining the console cursor under the
// same RSSRV session lock so ordinary writers keep their cursor position.
static KSTATUS RssrvConsoleWriteAt(RSSRV_CONSOLE_STATE* console,
int32_t x, int32_t y, const char* data,
uint32_t length, uint32_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
if (!console || !data || x < 0 || y < 0 || length > NTL_CONSOLE_WRITE_MAX - 8u)
return NTLSTATUS_INVALID_PARAMETER;
for (uint32_t i = 0; i < length; ++i)
if ((uint8_t)data[i] > 0x7fu || (uint8_t)data[i] < 0x20u)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
SYS_CONSOLE_INFO original;
memset(&original, 0, sizeof(original));
status = RssrvDisplayQueryConsoleInfo(console->Display, &original);
if (KSUCCESS(status))
status = RssrvDisplaySetConsoleCursor(console->Display, x, y);
if (KSUCCESS(status)) {
status = RssrvDisplayWriteConsole(console->Display, data, length);
if (KSUCCESS(status) && length) RssrvConsoleChangedLocked(console);
KSTATUS restore = RssrvDisplaySetConsoleCursor(console->Display,
(int32_t)original.CursorX,
(int32_t)original.CursorY);
if (KSUCCESS(status)) status = restore;
}
RssrvConsoleUnlock(console);
if (KSUCCESS(status) && writtenOut) *writtenOut = length;
return status;
}
// Queries dimensions from the backing display and snapshots RSSRV's console generation.
static KSTATUS RssrvConsoleQueryInfo(RSSRV_CONSOLE_STATE* console, NTL_CONSOLE_INFO* infoOut) {
if (!console || !infoOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
SYS_CONSOLE_INFO consoleInfo;
SYS_DISPLAY_INFO displayInfo;
memset(&consoleInfo, 0, sizeof(consoleInfo));
memset(&displayInfo, 0, sizeof(displayInfo));
status = RssrvDisplayQueryConsoleInfo(console->Display, &consoleInfo);
if (KSUCCESS(status)) status = RssrvDisplayQueryInfo(console->Display, &displayInfo);
if (KSUCCESS(status)) {
memset(infoOut, 0, sizeof(*infoOut));
infoOut->Size = sizeof(*infoOut);
infoOut->Flags = NTL_CONSOLE_FLAG_NONE;
infoOut->Columns = consoleInfo.Columns;
infoOut->Rows = consoleInfo.Rows;
infoOut->CursorX = consoleInfo.CursorX;
infoOut->CursorY = consoleInfo.CursorY;
infoOut->Attribute = consoleInfo.Attribute;
infoOut->PixelWidth = displayInfo.Mode.Width;
infoOut->PixelHeight = displayInfo.Mode.Height;
infoOut->Generation = console->Generation;
}
RssrvConsoleUnlock(console);
return status;
}
// Sends one fixed console reply.
static KSTATUS RssrvConsoleReply(SYS_HANDLE endpoint,
uint32_t messageId,
uint32_t operation,
KSTATUS status,
uint32_t written,
const INPUT_KEY_RECORD* input,
const NTL_CONSOLE_INFO* consoleInfo) {
NTL_CONSOLE_REPLY reply;
memset(&reply, 0, sizeof(reply));
reply.Size = sizeof(reply);
reply.Version = NTL_CONSOLE_PROTOCOL_VERSION;
reply.Operation = operation;
reply.Flags = NTL_CONSOLE_FLAG_NONE;
reply.Status = status;
reply.Written = written;
if (input) reply.Input = *input;
if (consoleInfo) reply.Info = *consoleInfo;
SYS_REPLY_PORT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.EndpointHandle = endpoint;
request.MessageId = messageId;
request.Flags = SYS_PORT_FLAG_NONE;
request.Payload = &reply;
request.PayloadLength = sizeof(reply);
return SysReplyPort(&request);
}
// Handles one mutation request that must be serialized through the console lock.
static KSTATUS RssrvConsoleMutate(RSSRV_CONSOLE_STATE* console,
const NTL_CONSOLE_REQUEST* request,
uint32_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
size_t written = 0;
switch (request->Operation) {
case NTL_CONSOLE_OP_SET_CURSOR:
status = RssrvDisplaySetConsoleCursor(console->Display,
request->Data.Cursor.X,
request->Data.Cursor.Y);
break;
case NTL_CONSOLE_OP_FILL_CHARACTER:
if ((uint8_t)request->Data.FillCharacter.Character > 0x7Fu) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
if (request->Data.FillCharacter.Reserved[0] || request->Data.FillCharacter.Reserved[1] ||
request->Data.FillCharacter.Reserved[2]) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
status = RssrvDisplayFillConsoleCharacter(console->Display,
request->Data.FillCharacter.Character,
request->Data.FillCharacter.X,
request->Data.FillCharacter.Y,
request->Data.FillCharacter.Length,
&written);
break;
case NTL_CONSOLE_OP_FILL_ATTRIBUTE:
if (request->Data.FillAttribute.Reserved != 0) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
status = RssrvDisplayFillConsoleAttribute(console->Display,
request->Data.FillAttribute.Attribute,
request->Data.FillAttribute.X,
request->Data.FillAttribute.Y,
request->Data.FillAttribute.Length,
&written);
break;
case NTL_CONSOLE_OP_PUT_CHARACTER:
if ((uint8_t)request->Data.PutCharacter.Character > 0x7Fu) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
if (request->Data.PutCharacter.Reserved[0] || request->Data.PutCharacter.Reserved[1] ||
request->Data.PutCharacter.Reserved[2]) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
status = RssrvDisplayPutConsoleCharacter(console->Display,
request->Data.PutCharacter.Character,
request->Data.PutCharacter.X,
request->Data.PutCharacter.Y,
request->Data.PutCharacter.Foreground,
request->Data.PutCharacter.Background);
break;
case NTL_CONSOLE_OP_CLEAR:
status = RssrvDisplayClearConsole(console->Display, request->Data.Clear.Color);
break;
default:
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
if (KSUCCESS(status)) RssrvConsoleChangedLocked(console);
RssrvConsoleUnlock(console);
if (writtenOut) *writtenOut = written > UINT32_MAX ? UINT32_MAX : (uint32_t)written;
return status;
}
// Serves one connected logical-console client.
static void RssrvConsoleSessionThread(uintptr_t argument) {
RSSRV_CONSOLE_SESSION* session = (RSSRV_CONSOLE_SESSION*)argument;
if (!session || !session->Console) {
if (session) free(session);
SysExitThread(1);
}
RSSRV_CONSOLE_STATE* console = session->Console;
SYS_HANDLE endpoint = session->Endpoint;
free(session);
RssrvSetCurrentThreadName("rssrv.console.session");
for (;;) {
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
SYS_PORT_MESSAGE_INFO info;
memset(&info, 0, sizeof(info));
info.Size = sizeof(info);
SYS_RECEIVE_PORT_REQUEST receive;
memset(&receive, 0, sizeof(receive));
receive.Size = sizeof(receive);
receive.EndpointHandle = endpoint;
receive.Flags = SYS_PORT_FLAG_NONE;
receive.Payload = &request;
receive.PayloadCapacity = sizeof(request);
receive.InfoOut = &info;
receive.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
KSTATUS status = SysReceivePort(&receive);
if (status == NTLSTATUS_PORT_DISCONNECTED) break;
if (!KSUCCESS(status)) break;
if (info.Type != SYS_PORT_MESSAGE_REQUEST || info.PayloadLength != sizeof(request) ||
info.HandleCount != 0 || request.Size != sizeof(request) ||
request.Version != NTL_CONSOLE_PROTOCOL_VERSION || request.Flags != NTL_CONSOLE_FLAG_NONE) {
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
NTLSTATUS_INVALID_PARAMETER, 0, 0, 0);
continue;
}
uint32_t written = 0;
INPUT_KEY_RECORD input;
NTL_CONSOLE_INFO consoleInfo;
memset(&input, 0, sizeof(input));
memset(&consoleInfo, 0, sizeof(consoleInfo));
switch (request.Operation) {
case NTL_CONSOLE_OP_WRITE:
if (request.Data.Write.Length > NTL_CONSOLE_WRITE_MAX) status = NTLSTATUS_INVALID_PARAMETER;
else status = RssrvConsoleWrite(console, request.Data.Write.Data,
request.Data.Write.Length, &written);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, written, 0, 0);
break;
case NTL_CONSOLE_OP_WRITE_AT:
if (request.Data.WriteAt.Length > NTL_CONSOLE_WRITE_MAX - 8u) {
status = NTLSTATUS_INVALID_PARAMETER;
} else {
status = RssrvConsoleWriteAt(console, request.Data.WriteAt.X,
request.Data.WriteAt.Y,
request.Data.WriteAt.Data,
request.Data.WriteAt.Length, &written);
}
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, written, 0, 0);
break;
case NTL_CONSOLE_OP_QUERY_INFO:
status = RssrvConsoleQueryInfo(console, &consoleInfo);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, 0, 0, KSUCCESS(status) ? &consoleInfo : 0);
break;
case NTL_CONSOLE_OP_SET_CURSOR:
case NTL_CONSOLE_OP_FILL_CHARACTER:
case NTL_CONSOLE_OP_FILL_ATTRIBUTE:
case NTL_CONSOLE_OP_PUT_CHARACTER:
case NTL_CONSOLE_OP_CLEAR:
status = RssrvConsoleMutate(console, &request, &written);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, written, 0, 0);
break;
case NTL_CONSOLE_OP_READ_INPUT:
status = RssrvConsoleReadInput(console, 0,
request.TimeoutMilliseconds, &input);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, 0, KSUCCESS(status) ? &input : 0, 0);
break;
case NTL_CONSOLE_OP_PEEK_INPUT:
status = RssrvConsoleReadInput(console, 1,
request.TimeoutMilliseconds, &input);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, 0, KSUCCESS(status) ? &input : 0, 0);
break;
case NTL_CONSOLE_OP_FLUSH_INPUT:
status = RssrvConsoleFlushInput(console, &written);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, written, 0, 0);
break;
default:
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
NTLSTATUS_INVALID_PARAMETER, 0, 0, 0);
break;
}
}
(void)SysCloseHandle(endpoint);
SysExitThread(0);
}
// Accepts console sessions and gives each connection one worker thread.
static void RssrvConsoleListenerThread(uintptr_t argument) {
RSSRV_CONSOLE_STATE* console = (RSSRV_CONSOLE_STATE*)argument;
RssrvSetCurrentThreadName("rssrv.console.listen");
if (!console) SysExitThread(1);
for (;;) {
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
SYS_ACCEPT_PORT_REQUEST accept;
memset(&accept, 0, sizeof(accept));
accept.Size = sizeof(accept);
accept.ListenerHandle = console->Listener;
accept.Flags = SYS_PORT_ACCEPT_FLAG_NONE;
accept.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
accept.EndpointHandleOut = &endpoint;
KSTATUS status = SysAcceptPort(&accept);
if (!KSUCCESS(status)) RssrvExit(16);
RSSRV_CONSOLE_SESSION* session = (RSSRV_CONSOLE_SESSION*)malloc(sizeof(*session));
if (!session) {
(void)SysCloseHandle(endpoint);
continue;
}
session->Console = console;
session->Endpoint = endpoint;
SYS_CREATE_THREAD_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Entry = (uintptr_t)RssrvConsoleSessionThread;
create.Argument = (uintptr_t)session;
create.Priority = 1;
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
KSTATUS createStatus = SysCreateThread(&create);
if (!KSUCCESS(createStatus)) {
free(session);
(void)SysCloseHandle(endpoint);
}
}
}
// Initializes Session 0's native console object and registers its focused-input target.
KSTATUS RssrvConsoleInitialize(RSSRV_CONSOLE_STATE* console,
RSSRV_NAMESPACE* namespaceState,
RSSRV_DISPLAY_STATE* display,
RSSRV_SESSION_STATE* session) {
if (!console || !namespaceState || !display || !session)
return NTLSTATUS_INVALID_PARAMETER;
memset(console, 0, sizeof(*console));
console->Mutex = SYS_HANDLE_INVALID;
console->KeyEvent = SYS_HANDLE_INVALID;
console->Listener = SYS_HANDLE_INVALID;
console->Namespace = namespaceState;
console->Display = display;
console->Session = session;
console->Generation = 1;
KSTATUS status = SysCreateMutex(&console->Mutex);
if (!KSUCCESS(status)) return status;
status = SysCreateEvent(RSSRV_EVENT_MANUAL_RESET, 0, &console->KeyEvent);
if (!KSUCCESS(status)) return status;
status = RssrvSessionRegisterInputTarget(session, &console->InputTarget,
RSSRV_INPUT_TARGET_KIND_CONSOLE, console,
RssrvConsoleRouteKey, RssrvConsoleRouteMouse);
if (!KSUCCESS(status)) return status;
return RssrvSessionActivateInputTarget(session, &console->InputTarget);
}
// Publishes /Services/Console and starts its client listener.
KSTATUS RssrvConsoleStart(RSSRV_CONSOLE_STATE* console) {
if (!console || !console->Namespace || !console->Display || !console->Session ||
console->Listener != SYS_HANDLE_INVALID)
return NTLSTATUS_INVALID_PARAMETER;
SYS_CREATE_PORT_REQUEST createPort;
memset(&createPort, 0, sizeof(createPort));
createPort.Size = sizeof(createPort);
createPort.Flags = SYS_PORT_FLAG_NONE;
createPort.Backlog = RSSRV_LISTENER_BACKLOG;
createPort.HandleOut = &console->Listener;
KSTATUS status = SysCreatePort(&createPort);
if (!KSUCCESS(status)) return status;
status = RssrvNamespaceBindOwned(console->Namespace,
NTL_CONSOLE_SERVICE_PATH,
sizeof(NTL_CONSOLE_SERVICE_PATH) - 1u,
NTL_RESOURCE_KIND_CAPABILITY,
console->Listener,
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT);
if (!KSUCCESS(status)) return status;
SYS_CREATE_THREAD_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Entry = (uintptr_t)RssrvConsoleListenerThread;
create.Argument = (uintptr_t)console;
create.Priority = 1;
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
return SysCreateThread(&create);
}