253 lines
9.6 KiB
C
253 lines
9.6 KiB
C
// Native logical-input client. RSSRV owns keyboard interpretation and pointer policy; this file speaks its Port protocol.
|
|
|
|
#include <ntlite/atomic.h>
|
|
#include <stdbool.h>
|
|
#include <ntlite/input_service.h>
|
|
#include <ntlite/port.h>
|
|
#include <ntlite/resource.h>
|
|
#include <ntlite/syscall.h>
|
|
#include <intrin.h>
|
|
#include <string.h>
|
|
|
|
static volatile uint32_t gDefaultInputSession;
|
|
static KSTATUS NtlInputGetDefaultSession(SYS_HANDLE* sessionOut);
|
|
|
|
// Connects to the RSSRV listener published at /Services/Input.
|
|
KSTATUS NtlInputConnect(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,
|
|
SysSvFromCstr(NTL_INPUT_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 logical-input session.
|
|
KSTATUS NtlInputClose(SYS_HANDLE session) {
|
|
if (session == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER;
|
|
(void)SysDisconnectPort(session);
|
|
return SysCloseHandle(session);
|
|
}
|
|
|
|
// Executes one fixed input-service request and validates its reply envelope.
|
|
static KSTATUS NtlInputCall(SYS_HANDLE session,
|
|
uint32_t operation,
|
|
uint64_t timeoutMilliseconds,
|
|
NTL_INPUT_REPLY* replyOut) {
|
|
if (session == SYS_HANDLE_INVALID || !replyOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
NTL_INPUT_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Version = NTL_INPUT_PROTOCOL_VERSION;
|
|
request.Operation = operation;
|
|
request.Flags = NTL_INPUT_FLAG_NONE;
|
|
request.TimeoutMilliseconds = timeoutMilliseconds;
|
|
|
|
NTL_INPUT_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_INPUT_PROTOCOL_VERSION ||
|
|
reply.Operation != operation || reply.Flags != NTL_INPUT_FLAG_NONE)
|
|
return NTLSTATUS_FAILURE;
|
|
|
|
*replyOut = reply;
|
|
return reply.Status;
|
|
}
|
|
|
|
// Reads one logical keyboard event.
|
|
KSTATUS NtlInputReadEvent(SYS_HANDLE session, uint64_t timeoutMilliseconds, INPUT_KEY_RECORD* recordOut) {
|
|
if (!recordOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (session == SYS_HANDLE_INVALID) {
|
|
KSTATUS connectStatus = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(connectStatus)) return connectStatus;
|
|
}
|
|
NTL_INPUT_REPLY reply;
|
|
KSTATUS status = NtlInputCall(session, NTL_INPUT_OP_READ_KEY, timeoutMilliseconds, &reply);
|
|
if (KSUCCESS(status)) *recordOut = reply.Key;
|
|
return status;
|
|
}
|
|
|
|
// Peeks one logical keyboard event.
|
|
KSTATUS NtlInputPeekEvent(SYS_HANDLE session, uint64_t timeoutMilliseconds, INPUT_KEY_RECORD* recordOut) {
|
|
if (!recordOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (session == SYS_HANDLE_INVALID) {
|
|
KSTATUS connectStatus = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(connectStatus)) return connectStatus;
|
|
}
|
|
NTL_INPUT_REPLY reply;
|
|
KSTATUS status = NtlInputCall(session, NTL_INPUT_OP_PEEK_KEY, timeoutMilliseconds, &reply);
|
|
if (KSUCCESS(status)) *recordOut = reply.Key;
|
|
return status;
|
|
}
|
|
|
|
// Reads one logical mouse event.
|
|
KSTATUS NtlInputReadMouse(SYS_HANDLE session, uint64_t timeoutMilliseconds, INPUT_MOUSE_EVENT* eventOut) {
|
|
if (!eventOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (session == SYS_HANDLE_INVALID) {
|
|
KSTATUS connectStatus = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(connectStatus)) return connectStatus;
|
|
}
|
|
NTL_INPUT_REPLY reply;
|
|
KSTATUS status = NtlInputCall(session, NTL_INPUT_OP_READ_MOUSE, timeoutMilliseconds, &reply);
|
|
if (KSUCCESS(status)) *eventOut = reply.Mouse;
|
|
return status;
|
|
}
|
|
|
|
// Peeks one logical mouse event.
|
|
KSTATUS NtlInputPeekMouse(SYS_HANDLE session, uint64_t timeoutMilliseconds, INPUT_MOUSE_EVENT* eventOut) {
|
|
if (!eventOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (session == SYS_HANDLE_INVALID) {
|
|
KSTATUS connectStatus = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(connectStatus)) return connectStatus;
|
|
}
|
|
NTL_INPUT_REPLY reply;
|
|
KSTATUS status = NtlInputCall(session, NTL_INPUT_OP_PEEK_MOUSE, timeoutMilliseconds, &reply);
|
|
if (KSUCCESS(status)) *eventOut = reply.Mouse;
|
|
return status;
|
|
}
|
|
|
|
// Queries one RSSRV-owned logical input snapshot.
|
|
KSTATUS NtlInputQueryState(SYS_HANDLE session, NTL_INPUT_STATE* stateOut) {
|
|
if (!stateOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (session == SYS_HANDLE_INVALID) {
|
|
KSTATUS connectStatus = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(connectStatus)) return connectStatus;
|
|
}
|
|
NTL_INPUT_REPLY reply;
|
|
KSTATUS status = NtlInputCall(session, NTL_INPUT_OP_QUERY_STATE, 0, &reply);
|
|
if (KSUCCESS(status)) *stateOut = reply.State;
|
|
return status;
|
|
}
|
|
|
|
// Returns one process-local cached input session, connecting it on first use.
|
|
static KSTATUS NtlInputGetDefaultSession(SYS_HANDLE* sessionOut) {
|
|
if (!sessionOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
uint32_t current = gDefaultInputSession;
|
|
if (current != 0) {
|
|
*sessionOut = (SYS_HANDLE)current;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
SYS_HANDLE created = SYS_HANDLE_INVALID;
|
|
KSTATUS status = NtlInputConnect(&created);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
uint32_t previous = NtlAtomicCompareExchangeU32(&gDefaultInputSession, created, 0);
|
|
if (previous != 0) {
|
|
(void)NtlInputClose(created);
|
|
created = (SYS_HANDLE)previous;
|
|
}
|
|
*sessionOut = created;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Peeks the next logical key without consuming it.
|
|
KSTATUS NtlInputPollKeyboard(INPUT_KEY* keyOut) {
|
|
if (!keyOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
SYS_HANDLE session = SYS_HANDLE_INVALID;
|
|
KSTATUS status = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(status)) return status;
|
|
INPUT_KEY_RECORD record;
|
|
status = NtlInputPeekEvent(session, 0, &record);
|
|
if (KSUCCESS(status)) *keyOut = record.Key;
|
|
return status;
|
|
}
|
|
|
|
// Queries one logical key's current down state.
|
|
KSTATUS NtlInputIsKeyDown(INPUT_KEY key, bool* downOut) {
|
|
if (!downOut || key == INPUT_KEY_NONE || key >= NTL_INPUT_KEY_BYTES * 8u)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
NTL_INPUT_STATE state;
|
|
KSTATUS status = NtlInputQueryDefaultState(&state);
|
|
if (!KSUCCESS(status)) return status;
|
|
*downOut = (state.KeyDown[key >> 3] & (uint8_t)(1u << (key & 7u))) != 0;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Consumes logical key records until one has a character representation.
|
|
KSTATUS NtlInputReadKeyboardChar(char* charOut) {
|
|
if (!charOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
SYS_HANDLE session = SYS_HANDLE_INVALID;
|
|
KSTATUS status = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(status)) return status;
|
|
for (;;) {
|
|
INPUT_KEY_RECORD record;
|
|
status = NtlInputReadEvent(session, SYS_PORT_TIMEOUT_INFINITE, &record);
|
|
if (!KSUCCESS(status)) return status;
|
|
if (record.Char != 0) {
|
|
*charOut = record.Char;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reads one logical key.
|
|
KSTATUS NtlInputReadKey(INPUT_KEY* keyOut) {
|
|
if (!keyOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
SYS_HANDLE session = SYS_HANDLE_INVALID;
|
|
KSTATUS status = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(status)) return status;
|
|
INPUT_KEY_RECORD record;
|
|
status = NtlInputReadEvent(session, SYS_PORT_TIMEOUT_INFINITE, &record);
|
|
if (KSUCCESS(status)) *keyOut = record.Key;
|
|
return status;
|
|
}
|
|
|
|
// Returns the current logical modifier mask, or zero if the service cannot be queried.
|
|
uint32_t NtlInputGetKeyModifiers(void) {
|
|
NTL_INPUT_STATE state;
|
|
return KSUCCESS(NtlInputQueryDefaultState(&state)) ? state.Modifiers : 0u;
|
|
}
|
|
|
|
// Queries the process-default logical input session.
|
|
KSTATUS NtlInputQueryDefaultState(NTL_INPUT_STATE* stateOut) {
|
|
if (!stateOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
SYS_HANDLE session = SYS_HANDLE_INVALID;
|
|
KSTATUS status = NtlInputGetDefaultSession(&session);
|
|
if (!KSUCCESS(status)) return status;
|
|
return NtlInputQueryState(session, stateOut);
|
|
}
|
|
|
|
// Returns the current RSSRV-owned logical pointer position.
|
|
KSTATUS NtlInputGetMousePos(int32_t* xOut, int32_t* yOut) {
|
|
if (!xOut || !yOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
NTL_INPUT_STATE state;
|
|
KSTATUS status = NtlInputQueryDefaultState(&state);
|
|
if (!KSUCCESS(status)) return status;
|
|
*xOut = state.MouseX;
|
|
*yOut = state.MouseY;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|