434 lines
19 KiB
C
434 lines
19 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;
|
|
}
|
|
|
|
// 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 (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 (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_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);
|
|
}
|