// RSSRV native console. One logical console owns text output policy and consumes RSSRV logical input. #include "rssrv_internal.h" #include #include #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); }