Files

307 lines
12 KiB
C

// Native console client. RSSRV owns the logical console; this file speaks its Port protocol.
#include <ntlite/atomic.h>
#include <ntlite/console_service.h>
#include <ntlite/port.h>
#include <ntlite/resource.h>
#include <ntlite/syscall.h>
#include <intrin.h>
#include <string.h>
static volatile uint32_t gDefaultConsoleSession;
static KSTATUS NtlConsoleGetDefaultSession(SYS_HANDLE* sessionOut);
// Connects to RSSRV's native console service.
KSTATUS NtlConsoleConnect(SYS_HANDLE* sessionOut) {
if (!sessionOut) return NTLSTATUS_INVALID_PARAMETER;
*sessionOut = SYS_HANDLE_INVALID;
SYS_HANDLE resourceSession = SYS_HANDLE_INVALID;
KSTATUS status = NtlResourceConnect(&resourceSession);
if (!KSUCCESS(status)) return status;
SYS_HANDLE listener = SYS_HANDLE_INVALID;
status = NtlResourceLookup(resourceSession, SYS_SV_LITERAL(NTL_CONSOLE_SERVICE_PATH),
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, &listener);
(void)NtlResourceClose(resourceSession);
if (!KSUCCESS(status)) return status;
status = NtlResourceConnectListener(listener, sessionOut);
(void)SysCloseHandle(listener);
return status;
}
// Disconnects one console service session.
KSTATUS NtlConsoleClose(SYS_HANDLE session) {
if (session == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER;
(void)SysDisconnectPort(session);
return SysCloseHandle(session);
}
// Returns one process-local cached console session.
static KSTATUS NtlConsoleGetDefaultSession(SYS_HANDLE* sessionOut) {
if (!sessionOut) return NTLSTATUS_INVALID_PARAMETER;
uint32_t current = gDefaultConsoleSession;
if (current != 0) {
*sessionOut = (SYS_HANDLE)current;
return NTLSTATUS_SUCCESS;
}
SYS_HANDLE created = SYS_HANDLE_INVALID;
KSTATUS status = NtlConsoleConnect(&created);
if (!KSUCCESS(status)) return status;
uint32_t previous = NtlAtomicCompareExchangeU32(&gDefaultConsoleSession, created, 0);
if (previous != 0) {
(void)NtlConsoleClose(created);
created = (SYS_HANDLE)previous;
}
*sessionOut = created;
return NTLSTATUS_SUCCESS;
}
// Executes one fixed console request and validates the reply envelope.
static KSTATUS NtlConsoleCall(SYS_HANDLE session,
const NTL_CONSOLE_REQUEST* request,
NTL_CONSOLE_REPLY* replyOut) {
if (!request || !replyOut) return NTLSTATUS_INVALID_PARAMETER;
if (session == SYS_HANDLE_INVALID) {
KSTATUS status = NtlConsoleGetDefaultSession(&session);
if (!KSUCCESS(status)) return status;
}
NTL_CONSOLE_REPLY reply;
memset(&reply, 0, sizeof(reply));
SYS_PORT_MESSAGE_INFO info;
memset(&info, 0, sizeof(info));
info.Size = sizeof(info);
SYS_CALL_PORT_REQUEST call;
memset(&call, 0, sizeof(call));
call.Size = sizeof(call);
call.EndpointHandle = session;
call.Flags = SYS_PORT_FLAG_NONE;
call.RequestPayload = request;
call.RequestLength = sizeof(*request);
call.ReplyPayload = &reply;
call.ReplyCapacity = sizeof(reply);
call.ReplyInfoOut = &info;
call.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
KSTATUS status = SysCallPort(&call);
if (!KSUCCESS(status)) return status;
if (info.Type != SYS_PORT_MESSAGE_REPLY || info.PayloadLength != sizeof(reply) || info.HandleCount != 0 ||
reply.Size != sizeof(reply) || reply.Version != NTL_CONSOLE_PROTOCOL_VERSION ||
reply.Operation != request->Operation || reply.Flags != NTL_CONSOLE_FLAG_NONE)
return NTLSTATUS_FAILURE;
*replyOut = reply;
return reply.Status;
}
// Writes arbitrary bytes to the console in bounded protocol chunks.
KSTATUS NtlConsoleWrite(SYS_HANDLE session, const void* data, size_t length, size_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
if (length && !data) return NTLSTATUS_INVALID_PARAMETER;
const char* bytes = (const char*)data;
size_t total = 0;
while (total < length) {
size_t chunk = length - total;
if (chunk > NTL_CONSOLE_WRITE_MAX) chunk = NTL_CONSOLE_WRITE_MAX;
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_WRITE;
request.Flags = NTL_CONSOLE_FLAG_NONE;
request.Data.Write.Length = (uint32_t)chunk;
memcpy(request.Data.Write.Data, bytes + total, chunk);
NTL_CONSOLE_REPLY reply;
memset(&reply, 0, sizeof(reply));
KSTATUS status = NtlConsoleCall(session, &request, &reply);
total += reply.Written;
if (!KSUCCESS(status) || reply.Written != chunk) {
if (writtenOut) *writtenOut = total;
return KSUCCESS(status) ? NTLSTATUS_FAILURE : status;
}
}
if (writtenOut) *writtenOut = total;
return NTLSTATUS_SUCCESS;
}
// Queries the logical console's current dimensions and cursor state.
KSTATUS NtlConsoleQueryInfo(SYS_HANDLE session, NTL_CONSOLE_INFO* infoOut) {
if (!infoOut) return NTLSTATUS_INVALID_PARAMETER;
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_QUERY_INFO;
NTL_CONSOLE_REPLY reply;
KSTATUS status = NtlConsoleCall(session, &request, &reply);
if (KSUCCESS(status)) *infoOut = reply.Info;
return status;
}
// Executes one console operation whose reply has only status and optional written count.
static KSTATUS NtlConsoleSimpleCall(SYS_HANDLE session, NTL_CONSOLE_REQUEST* request,
uint32_t* writtenOut) {
NTL_CONSOLE_REPLY reply;
memset(&reply, 0, sizeof(reply));
KSTATUS status = NtlConsoleCall(session, request, &reply);
if (writtenOut) *writtenOut = reply.Written;
return status;
}
// Sends one positioned text span in a single console request.
KSTATUS NtlConsoleWriteAt(SYS_HANDLE session, int32_t x, int32_t y,
const char* text, size_t length, size_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
if (!text || length > NTL_CONSOLE_WRITE_MAX - 8u || x < 0 || y < 0)
return NTLSTATUS_INVALID_PARAMETER;
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_WRITE_AT;
request.Data.WriteAt.X = x;
request.Data.WriteAt.Y = y;
request.Data.WriteAt.Length = (uint32_t)length;
memcpy(request.Data.WriteAt.Data, text, length);
NTL_CONSOLE_REPLY reply;
KSTATUS status = NtlConsoleCall(session, &request, &reply);
if (writtenOut) *writtenOut = reply.Written;
return status;
}
// Updates the logical console cursor.
KSTATUS NtlConsoleSetCursor(SYS_HANDLE session, int32_t x, int32_t y) {
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_SET_CURSOR;
request.Data.Cursor.X = x;
request.Data.Cursor.Y = y;
return NtlConsoleSimpleCall(session, &request, 0);
}
// Fills console characters over one linear cell range.
KSTATUS NtlConsoleFillCharacter(SYS_HANDLE session, char character, int32_t x, int32_t y,
size_t length, size_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
if (length > UINT32_MAX) return NTLSTATUS_INVALID_PARAMETER;
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_FILL_CHARACTER;
request.Data.FillCharacter.Character = character;
request.Data.FillCharacter.X = x;
request.Data.FillCharacter.Y = y;
request.Data.FillCharacter.Length = (uint32_t)length;
uint32_t written = 0;
KSTATUS status = NtlConsoleSimpleCall(session, &request, &written);
if (writtenOut) *writtenOut = written;
return status;
}
// Fills console attributes over one linear cell range.
KSTATUS NtlConsoleFillAttribute(SYS_HANDLE session, uint16_t attribute, int32_t x, int32_t y,
size_t length, size_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
if (length > UINT32_MAX) return NTLSTATUS_INVALID_PARAMETER;
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_FILL_ATTRIBUTE;
request.Data.FillAttribute.Attribute = attribute;
request.Data.FillAttribute.X = x;
request.Data.FillAttribute.Y = y;
request.Data.FillAttribute.Length = (uint32_t)length;
uint32_t written = 0;
KSTATUS status = NtlConsoleSimpleCall(session, &request, &written);
if (writtenOut) *writtenOut = written;
return status;
}
// Draws one glyph at explicit pixel coordinates without moving console state.
KSTATUS NtlConsolePutCharacter(SYS_HANDLE session, char character, int32_t x, int32_t y,
uint32_t foreground, uint32_t background) {
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_PUT_CHARACTER;
request.Data.PutCharacter.Character = character;
request.Data.PutCharacter.X = x;
request.Data.PutCharacter.Y = y;
request.Data.PutCharacter.Foreground = foreground;
request.Data.PutCharacter.Background = background;
return NtlConsoleSimpleCall(session, &request, 0);
}
// Clears the console's current display target.
KSTATUS NtlConsoleClear(SYS_HANDLE session, uint32_t color) {
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_CLEAR;
request.Data.Clear.Color = color;
return NtlConsoleSimpleCall(session, &request, 0);
}
// Executes one console input operation and returns its key record.
static KSTATUS NtlConsoleInputCall(SYS_HANDLE session, uint32_t operation,
uint64_t timeoutMilliseconds, INPUT_KEY_RECORD* recordOut) {
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = operation;
request.TimeoutMilliseconds = timeoutMilliseconds;
NTL_CONSOLE_REPLY reply;
KSTATUS status = NtlConsoleCall(session, &request, &reply);
if (KSUCCESS(status) && recordOut) *recordOut = reply.Input;
return status;
}
// Reads one logical console input record.
KSTATUS NtlConsoleReadInput(SYS_HANDLE session, uint64_t timeoutMilliseconds,
INPUT_KEY_RECORD* recordOut) {
if (!recordOut) return NTLSTATUS_INVALID_PARAMETER;
return NtlConsoleInputCall(session, NTL_CONSOLE_OP_READ_INPUT, timeoutMilliseconds, recordOut);
}
// Peeks one logical console input record.
KSTATUS NtlConsolePeekInput(SYS_HANDLE session, uint64_t timeoutMilliseconds,
INPUT_KEY_RECORD* recordOut) {
if (!recordOut) return NTLSTATUS_INVALID_PARAMETER;
return NtlConsoleInputCall(session, NTL_CONSOLE_OP_PEEK_INPUT, timeoutMilliseconds, recordOut);
}
// Discards every queued logical console input record.
KSTATUS NtlConsoleFlushInput(SYS_HANDLE session) {
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
request.Operation = NTL_CONSOLE_OP_FLUSH_INPUT;
return NtlConsoleSimpleCall(session, &request, 0);
}
// Reads until one console key record has a character representation.
KSTATUS NtlConsoleReadCharacter(SYS_HANDLE session, char* characterOut) {
if (!characterOut) return NTLSTATUS_INVALID_PARAMETER;
for (;;) {
INPUT_KEY_RECORD record;
KSTATUS status = NtlConsoleReadInput(session, SYS_PORT_TIMEOUT_INFINITE, &record);
if (!KSUCCESS(status)) return status;
if (record.Char != 0) {
*characterOut = record.Char;
return NTLSTATUS_SUCCESS;
}
}
}