Files
nt-lite/code/user/system/rssrv/session.c
T

467 lines
20 KiB
C

// RSSRV interactive-session policy. Session 0 owns display routing, activation, focus, pointer capture, and DWM authority.
#include "rssrv_internal.h"
#include <ntlite/internal/session_control.h>
#include <ntlite/port.h>
#include <string.h>
#define RSSRV_SESSION_ZERO_PATH "/Sessions/0"
#define RSSRV_SESSION_ZERO_CONSOLE_PATH "/Sessions/0/Console"
#define RSSRV_CURRENT_CONSOLE_PATH "/Console"
#define RSSRV_DWM_LISTENER_BACKLOG 1u
// Acquires the session-state mutex.
static KSTATUS RssrvSessionLock(RSSRV_SESSION_STATE* session) {
if (!session || session->Mutex == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER;
return SysWait(session->Mutex, SYS_WAIT_FLAG_NONE);
}
// Releases the session-state mutex.
static void RssrvSessionUnlock(RSSRV_SESSION_STATE* session) {
if (session && session->Mutex != SYS_HANDLE_INVALID) (void)SysReleaseMutex(session->Mutex);
}
// Advances the session generation while preserving zero as an uninitialized value.
static void RssrvSessionChangedLocked(RSSRV_SESSION_STATE* session) {
session->Generation++;
if (session->Generation == 0) session->Generation = 1;
}
// Sends one best-effort DWM input notification while holding the session mutex.
static void RssrvSessionSendDwmEventLocked(RSSRV_SESSION_STATE* session,
uint32_t type,
const INPUT_KEY_RECORD* key,
const INPUT_MOUSE_EVENT* mouse,
int32_t x,
int32_t y) {
if (!session || session->DwmEndpoint == SYS_HANDLE_INVALID) return;
NTL_SESSION_DWM_EVENT event;
memset(&event, 0, sizeof(event));
event.Size = sizeof(event);
event.Version = NTL_SESSION_DWM_PROTOCOL_VERSION;
event.Type = type;
event.Flags = NTL_SESSION_DWM_FLAG_NONE;
event.PointerX = x;
event.PointerY = y;
if (key) event.Key = *key;
if (mouse) event.Mouse = *mouse;
SYS_SEND_PORT_REQUEST send;
memset(&send, 0, sizeof(send));
send.Size = sizeof(send);
send.EndpointHandle = session->DwmEndpoint;
send.Flags = SYS_PORT_FLAG_NONE;
send.Payload = &event;
send.PayloadLength = sizeof(event);
send.TimeoutMilliseconds = 0;
(void)SysSendPort(&send);
}
// Routes one focused keyboard record into the connected DWM desktop target.
static void RssrvSessionDwmRouteKey(void* context, const INPUT_KEY_RECORD* record) {
RSSRV_SESSION_STATE* session = (RSSRV_SESSION_STATE*)context;
if (!session || !record) return;
if (!KSUCCESS(RssrvSessionLock(session))) return;
RssrvSessionSendDwmEventLocked(session, NTL_SESSION_DWM_EVENT_DELIVER_KEY, record, 0, 0, 0);
RssrvSessionUnlock(session);
}
// Routes one focused/captured mouse record into the connected DWM desktop target.
static void RssrvSessionDwmRouteMouse(void* context,
const INPUT_MOUSE_EVENT* event,
int32_t x,
int32_t y) {
RSSRV_SESSION_STATE* session = (RSSRV_SESSION_STATE*)context;
if (!session || !event) return;
if (!KSUCCESS(RssrvSessionLock(session))) return;
RssrvSessionSendDwmEventLocked(session, NTL_SESSION_DWM_EVENT_DELIVER_MOUSE, 0, event, x, y);
RssrvSessionUnlock(session);
}
// Replies to one private DWM session-control request.
static void RssrvSessionReplyDwm(SYS_HANDLE endpoint,
uint32_t messageId,
uint32_t operation,
KSTATUS status) {
NTL_SESSION_DWM_REPLY reply;
memset(&reply, 0, sizeof(reply));
reply.Size = sizeof(reply);
reply.Version = NTL_SESSION_DWM_PROTOCOL_VERSION;
reply.Operation = operation;
reply.Flags = NTL_SESSION_DWM_FLAG_NONE;
reply.Status = status;
SYS_REPLY_PORT_REQUEST send;
memset(&send, 0, sizeof(send));
send.Size = sizeof(send);
send.EndpointHandle = endpoint;
send.MessageId = messageId;
send.Flags = SYS_PORT_FLAG_NONE;
send.Payload = &reply;
send.PayloadLength = sizeof(reply);
(void)SysReplyPort(&send);
}
// Applies one DWM focus/capture request while preserving RSSRV as the session authority owner.
static KSTATUS RssrvSessionApplyDwmRequest(RSSRV_SESSION_STATE* session,
const NTL_SESSION_DWM_REQUEST* request) {
if (!session || !request ||
((const uint8_t*)request)[offsetof(NTL_SESSION_DWM_REQUEST, Enabled)] > 1u)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvSessionLock(session);
if (!KSUCCESS(status)) return status;
RSSRV_INPUT_TARGET* desktop = &session->DesktopTarget;
RSSRV_INPUT_TARGET* fallback = session->ActiveTarget == desktop ? (RSSRV_INPUT_TARGET*)0 : session->ActiveTarget;
switch (request->Operation) {
case NTL_SESSION_DWM_OP_SET_KEYBOARD_FOCUS:
if (request->Enabled) {
if (session->KeyboardFocus != desktop) {
session->KeyboardFocus = desktop;
RssrvSessionChangedLocked(session);
}
} else if (session->KeyboardFocus == desktop) {
session->KeyboardFocus = fallback;
RssrvSessionChangedLocked(session);
}
break;
case NTL_SESSION_DWM_OP_SET_POINTER_FOCUS:
if (request->Enabled) {
if (session->PointerFocus != desktop) {
session->PointerFocus = desktop;
RssrvSessionChangedLocked(session);
}
} else if (session->PointerFocus == desktop) {
session->PointerFocus = fallback;
RssrvSessionChangedLocked(session);
}
break;
case NTL_SESSION_DWM_OP_SET_POINTER_CAPTURE:
if (request->Enabled) {
if (session->PointerCapture != desktop) {
session->PointerCapture = desktop;
RssrvSessionChangedLocked(session);
}
} else if (session->PointerCapture == desktop) {
session->PointerCapture = (RSSRV_INPUT_TARGET*)0;
RssrvSessionChangedLocked(session);
}
break;
default:
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
RssrvSessionUnlock(session);
return status;
}
// Restores non-DWM routing after one DWM control connection disappears.
static void RssrvSessionDisconnectDwm(RSSRV_SESSION_STATE* session, SYS_HANDLE endpoint) {
if (!session) return;
if (!KSUCCESS(RssrvSessionLock(session))) return;
if (session->DwmEndpoint == endpoint) {
RSSRV_INPUT_TARGET* desktop = &session->DesktopTarget;
RSSRV_INPUT_TARGET* fallback = session->ActiveTarget == desktop ? (RSSRV_INPUT_TARGET*)0 : session->ActiveTarget;
if (session->KeyboardFocus == desktop) session->KeyboardFocus = fallback;
if (session->PointerFocus == desktop) session->PointerFocus = fallback;
if (session->PointerCapture == desktop) session->PointerCapture = (RSSRV_INPUT_TARGET*)0;
session->DwmEndpoint = SYS_HANDLE_INVALID;
RssrvSessionChangedLocked(session);
}
RssrvSessionUnlock(session);
}
// Accepts replaceable DWM connections and serves the private Session 0 authority protocol.
static void RssrvSessionDwmListenerThread(uintptr_t argument) {
RSSRV_SESSION_STATE* session = (RSSRV_SESSION_STATE*)argument;
RssrvSetCurrentThreadName("rssrv.session.dwm");
if (!session) 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 = session->DwmListener;
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);
status = RssrvSessionLock(session);
if (!KSUCCESS(status)) {
(void)SysCloseHandle(endpoint);
continue;
}
if (session->DwmEndpoint != SYS_HANDLE_INVALID) {
RssrvSessionUnlock(session);
(void)SysDisconnectPort(endpoint);
(void)SysCloseHandle(endpoint);
continue;
}
session->DwmEndpoint = endpoint;
RssrvSessionChangedLocked(session);
RssrvSessionUnlock(session);
for (;;) {
NTL_SESSION_DWM_REQUEST request;
SYS_PORT_MESSAGE_INFO info;
memset(&request, 0, sizeof(request));
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;
status = SysReceivePort(&receive);
if (status == NTLSTATUS_PORT_DISCONNECTED || !KSUCCESS(status)) break;
if (info.Type != SYS_PORT_MESSAGE_REQUEST || info.HandleCount != 0 ||
info.PayloadLength != sizeof(request) || request.Size != sizeof(request) ||
request.Version != NTL_SESSION_DWM_PROTOCOL_VERSION ||
request.Flags != NTL_SESSION_DWM_FLAG_NONE || request.Reserved[0] != 0 ||
request.Reserved[1] != 0 || request.Reserved[2] != 0) {
RssrvSessionReplyDwm(endpoint, info.MessageId, request.Operation, NTLSTATUS_INVALID_PARAMETER);
continue;
}
status = RssrvSessionApplyDwmRequest(session, &request);
RssrvSessionReplyDwm(endpoint, info.MessageId, request.Operation, status);
}
RssrvSessionDisconnectDwm(session, endpoint);
(void)SysDisconnectPort(endpoint);
(void)SysCloseHandle(endpoint);
}
}
// Initializes Session 0 and creates the private listener that init may delegate only to DWM.
KSTATUS RssrvSessionInitialize(RSSRV_SESSION_STATE* session,
RSSRV_NAMESPACE* namespaceState,
RSSRV_DISPLAY_STATE* display) {
if (!session || !namespaceState || !display) return NTLSTATUS_INVALID_PARAMETER;
memset(session, 0, sizeof(*session));
session->Mutex = SYS_HANDLE_INVALID;
session->DwmListener = SYS_HANDLE_INVALID;
session->DwmEndpoint = SYS_HANDLE_INVALID;
session->Namespace = namespaceState;
session->Display = display;
session->Id = 0;
session->Generation = 1;
session->NextTargetId = 1;
SYS_DISPLAY_INFO info;
memset(&info, 0, sizeof(info));
KSTATUS status = RssrvDisplayQueryInfo(display, &info);
if (!KSUCCESS(status)) return status;
session->DisplayWidth = info.Mode.Width ? (int32_t)info.Mode.Width : 1;
session->DisplayHeight = info.Mode.Height ? (int32_t)info.Mode.Height : 1;
status = SysCreateMutex(&session->Mutex);
if (!KSUCCESS(status)) return status;
status = RssrvNamespaceCreateDirectory(namespaceState,
RSSRV_SESSION_ZERO_PATH,
sizeof(RSSRV_SESSION_ZERO_PATH) - 1u);
if (!KSUCCESS(status)) return status;
SYS_CREATE_PORT_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Flags = SYS_PORT_FLAG_NONE;
create.Backlog = RSSRV_DWM_LISTENER_BACKLOG;
create.HandleOut = &session->DwmListener;
return SysCreatePort(&create);
}
// Registers one stable in-process input target with Session 0.
KSTATUS RssrvSessionRegisterInputTarget(RSSRV_SESSION_STATE* session,
RSSRV_INPUT_TARGET* target,
uint32_t kind,
void* context,
RSSRV_INPUT_TARGET_KEY_ROUTINE keyRoutine,
RSSRV_INPUT_TARGET_MOUSE_ROUTINE mouseRoutine) {
if (!session || !target || kind == RSSRV_INPUT_TARGET_KIND_NONE || !context)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvSessionLock(session);
if (!KSUCCESS(status)) return status;
if (target->Id != 0) {
RssrvSessionUnlock(session);
return NTLSTATUS_OBJECT_NAME_COLLISION;
}
memset(target, 0, sizeof(*target));
target->Id = session->NextTargetId++;
if (target->Id == 0) target->Id = session->NextTargetId++;
target->Kind = kind;
target->Session = session;
target->Context = context;
target->KeyRoutine = keyRoutine;
target->MouseRoutine = mouseRoutine;
RssrvSessionChangedLocked(session);
RssrvSessionUnlock(session);
return NTLSTATUS_SUCCESS;
}
// Activates one registered target and gives it ordinary keyboard and pointer focus.
KSTATUS RssrvSessionActivateInputTarget(RSSRV_SESSION_STATE* session, RSSRV_INPUT_TARGET* target) {
if (!session || !target || target->Id == 0 || target->Session != session) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvSessionLock(session);
if (!KSUCCESS(status)) return status;
session->ActiveTarget = target;
session->KeyboardFocus = target;
session->PointerFocus = target;
RssrvSessionChangedLocked(session);
RssrvSessionUnlock(session);
return NTLSTATUS_SUCCESS;
}
// Sets keyboard focus without changing activation or pointer routing.
KSTATUS RssrvSessionSetKeyboardFocus(RSSRV_SESSION_STATE* session, RSSRV_INPUT_TARGET* target) {
if (!session || (target && (target->Id == 0 || target->Session != session))) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvSessionLock(session);
if (!KSUCCESS(status)) return status;
if (session->KeyboardFocus != target) {
session->KeyboardFocus = target;
RssrvSessionChangedLocked(session);
}
RssrvSessionUnlock(session);
return NTLSTATUS_SUCCESS;
}
// Sets ordinary pointer focus without disturbing an active pointer capture.
KSTATUS RssrvSessionSetPointerFocus(RSSRV_SESSION_STATE* session, RSSRV_INPUT_TARGET* target) {
if (!session || (target && (target->Id == 0 || target->Session != session))) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvSessionLock(session);
if (!KSUCCESS(status)) return status;
if (session->PointerFocus != target) {
session->PointerFocus = target;
RssrvSessionChangedLocked(session);
}
RssrvSessionUnlock(session);
return NTLSTATUS_SUCCESS;
}
// Sets or releases pointer capture independently of ordinary pointer focus.
KSTATUS RssrvSessionSetPointerCapture(RSSRV_SESSION_STATE* session, RSSRV_INPUT_TARGET* target) {
if (!session || (target && (target->Id == 0 || target->Session != session))) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvSessionLock(session);
if (!KSUCCESS(status)) return status;
if (session->PointerCapture != target) {
session->PointerCapture = target;
RssrvSessionChangedLocked(session);
}
RssrvSessionUnlock(session);
return NTLSTATUS_SUCCESS;
}
// Returns the active session display bounds used to clamp the logical pointer.
KSTATUS RssrvSessionQueryDisplayBounds(RSSRV_SESSION_STATE* session, int32_t* widthOut, int32_t* heightOut) {
if (!session || !widthOut || !heightOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvSessionLock(session);
if (!KSUCCESS(status)) return status;
*widthOut = session->DisplayWidth;
*heightOut = session->DisplayHeight;
RssrvSessionUnlock(session);
return NTLSTATUS_SUCCESS;
}
// Gives DWM a non-consuming observation of one logical key before authoritative session routing occurs.
void RssrvSessionObserveKey(RSSRV_SESSION_STATE* session, const INPUT_KEY_RECORD* record) {
if (!session || !record) return;
if (!KSUCCESS(RssrvSessionLock(session))) return;
RssrvSessionSendDwmEventLocked(session, NTL_SESSION_DWM_EVENT_OBSERVE_KEY, record, 0, 0, 0);
RssrvSessionUnlock(session);
}
// Gives DWM a non-consuming observation of one logical mouse event for hit testing and cursor policy.
void RssrvSessionObserveMouse(RSSRV_SESSION_STATE* session,
const INPUT_MOUSE_EVENT* event,
int32_t x,
int32_t y) {
if (!session || !event) return;
if (!KSUCCESS(RssrvSessionLock(session))) return;
RssrvSessionSendDwmEventLocked(session, NTL_SESSION_DWM_EVENT_OBSERVE_MOUSE, 0, event, x, y);
RssrvSessionUnlock(session);
}
// Routes one logical keyboard record to the current keyboard-focus target.
void RssrvSessionRouteKey(RSSRV_SESSION_STATE* session, const INPUT_KEY_RECORD* record) {
if (!session || !record) return;
RSSRV_INPUT_TARGET* target = (RSSRV_INPUT_TARGET*)0;
if (KSUCCESS(RssrvSessionLock(session))) {
target = session->KeyboardFocus;
RssrvSessionUnlock(session);
}
if (target && target->KeyRoutine) target->KeyRoutine(target->Context, record);
}
// Routes one logical mouse event through capture or pointer focus; DWM owns visible cursor policy.
void RssrvSessionRouteMouse(RSSRV_SESSION_STATE* session,
const INPUT_MOUSE_EVENT* event,
int32_t x,
int32_t y) {
if (!session || !event) return;
RSSRV_INPUT_TARGET* target = (RSSRV_INPUT_TARGET*)0;
if (KSUCCESS(RssrvSessionLock(session))) {
target = session->PointerCapture ? session->PointerCapture : session->PointerFocus;
RssrvSessionUnlock(session);
}
if (target && target->MouseRoutine) target->MouseRoutine(target->Context, event, x, y);
}
// Publishes Session 0's console alias and starts the private DWM authority listener.
KSTATUS RssrvSessionStart(RSSRV_SESSION_STATE* session, RSSRV_CONSOLE_STATE* console) {
if (!session || !console || console->Listener == SYS_HANDLE_INVALID || !session->ActiveTarget ||
session->DwmListener == SYS_HANDLE_INVALID)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvSessionRegisterInputTarget(session, &session->DesktopTarget,
RSSRV_INPUT_TARGET_KIND_DESKTOP, session,
RssrvSessionDwmRouteKey, RssrvSessionDwmRouteMouse);
if (!KSUCCESS(status)) return status;
SYS_HANDLE sessionConsole = SYS_HANDLE_INVALID;
SYS_DERIVE_HANDLE_REQUEST derive;
memset(&derive, 0, sizeof(derive));
derive.Size = sizeof(derive);
derive.SourceHandle = console->Listener;
derive.DesiredRights = SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
derive.DerivedHandleOut = &sessionConsole;
status = SysDeriveHandle(&derive);
if (!KSUCCESS(status)) return status;
status = RssrvNamespaceBindOwned(session->Namespace,
RSSRV_SESSION_ZERO_CONSOLE_PATH,
sizeof(RSSRV_SESSION_ZERO_CONSOLE_PATH) - 1u,
NTL_RESOURCE_KIND_CAPABILITY,
sessionConsole,
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT);
if (!KSUCCESS(status)) {
(void)SysCloseHandle(sessionConsole);
return status;
}
status = RssrvNamespaceCreateSymbolicLink(session->Namespace,
RSSRV_CURRENT_CONSOLE_PATH,
sizeof(RSSRV_CURRENT_CONSOLE_PATH) - 1u,
RSSRV_SESSION_ZERO_CONSOLE_PATH,
sizeof(RSSRV_SESSION_ZERO_CONSOLE_PATH) - 1u);
if (!KSUCCESS(status)) return status;
SYS_CREATE_THREAD_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Entry = (uintptr_t)RssrvSessionDwmListenerThread;
create.Argument = (uintptr_t)session;
create.Priority = 1u;
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
return SysCreateThread(&create);
}