307 lines
12 KiB
C
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;
|
|
}
|
|
}
|
|
}
|