Fix console session changes via fast user switching

We need to respawn Sunshine.exe in the new console session.
This commit is contained in:
Cameron Gutman 2023-05-14 19:27:57 -05:00
commit 9955890023

View file

@ -16,6 +16,7 @@
SERVICE_STATUS_HANDLE service_status_handle; SERVICE_STATUS_HANDLE service_status_handle;
SERVICE_STATUS service_status; SERVICE_STATUS service_status;
HANDLE stop_event; HANDLE stop_event;
HANDLE session_change_event;
#define SERVICE_NAME "SunshineSvc" #define SERVICE_NAME "SunshineSvc"
@ -25,6 +26,14 @@ HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpConte
case SERVICE_CONTROL_INTERROGATE: case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR; return NO_ERROR;
case SERVICE_CONTROL_SESSIONCHANGE:
// If a new session connects to the console, restart Sunshine
// to allow it to spawn inside the new console session.
if (dwEventType == WTS_CONSOLE_CONNECT) {
SetEvent(session_change_event);
}
return NO_ERROR;
case SERVICE_CONTROL_PRESHUTDOWN: case SERVICE_CONTROL_PRESHUTDOWN:
// The system is shutting down // The system is shutting down
case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_STOP:
@ -39,7 +48,7 @@ HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpConte
return NO_ERROR; return NO_ERROR;
default: default:
return NO_ERROR; return ERROR_CALL_NOT_IMPLEMENTED;
} }
} }
@ -87,13 +96,7 @@ AllocateProcThreadAttributeList(DWORD attribute_count) {
} }
HANDLE HANDLE
DuplicateTokenForConsoleSession() { DuplicateTokenForSession(DWORD console_session_id) {
auto console_session_id = WTSGetActiveConsoleSessionId();
if (console_session_id == 0xFFFFFFFF) {
// No console session yet
return NULL;
}
HANDLE current_token; HANDLE current_token;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, &current_token)) { if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, &current_token)) {
return NULL; return NULL;
@ -201,6 +204,7 @@ ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) {
service_status.dwCurrentState = SERVICE_START_PENDING; service_status.dwCurrentState = SERVICE_START_PENDING;
SetServiceStatus(service_status_handle, &service_status); SetServiceStatus(service_status_handle, &service_status);
// Create a manual-reset stop event
stop_event = CreateEventA(NULL, TRUE, FALSE, NULL); stop_event = CreateEventA(NULL, TRUE, FALSE, NULL);
if (stop_event == NULL) { if (stop_event == NULL) {
// Tell SCM we failed to start // Tell SCM we failed to start
@ -210,6 +214,16 @@ ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) {
return; return;
} }
// Create an auto-reset session change event
session_change_event = CreateEventA(NULL, FALSE, FALSE, NULL);
if (session_change_event == NULL) {
// Tell SCM we failed to start
service_status.dwWin32ExitCode = GetLastError();
service_status.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(service_status_handle, &service_status);
return;
}
auto log_file_handle = OpenLogFileHandle(); auto log_file_handle = OpenLogFileHandle();
if (log_file_handle == INVALID_HANDLE_VALUE) { if (log_file_handle == INVALID_HANDLE_VALUE) {
// Tell SCM we failed to start // Tell SCM we failed to start
@ -248,13 +262,19 @@ ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) {
NULL); NULL);
// Tell SCM we're running (and stoppable now) // Tell SCM we're running (and stoppable now)
service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PRESHUTDOWN; service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PRESHUTDOWN | SERVICE_ACCEPT_SESSIONCHANGE;
service_status.dwCurrentState = SERVICE_RUNNING; service_status.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(service_status_handle, &service_status); SetServiceStatus(service_status_handle, &service_status);
// Loop every 3 seconds until the stop event is set or Sunshine.exe is running // Loop every 3 seconds until the stop event is set or Sunshine.exe is running
while (WaitForSingleObject(stop_event, 3000) != WAIT_OBJECT_0) { while (WaitForSingleObject(stop_event, 3000) != WAIT_OBJECT_0) {
auto console_token = DuplicateTokenForConsoleSession(); auto console_session_id = WTSGetActiveConsoleSessionId();
if (console_session_id == 0xFFFFFFFF) {
// No console session yet
continue;
}
auto console_token = DuplicateTokenForSession(console_session_id);
if (console_token == NULL) { if (console_token == NULL) {
continue; continue;
} }
@ -292,30 +312,42 @@ ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) {
continue; continue;
} }
// Wait for either the stop event to be set or Sunshine.exe to terminate bool still_running;
const HANDLE wait_objects[] = { stop_event, process_info.hProcess }; do {
switch (WaitForMultipleObjects(_countof(wait_objects), wait_objects, FALSE, INFINITE)) { // Wait for the stop event to be set, Sunshine.exe to terminate, or the console session to change
case WAIT_OBJECT_0: const HANDLE wait_objects[] = { stop_event, process_info.hProcess, session_change_event };
// The service is shutting down, so try to gracefully terminate Sunshine.exe. switch (WaitForMultipleObjects(_countof(wait_objects), wait_objects, FALSE, INFINITE)) {
// If it doesn't terminate in 20 seconds, we will forcefully terminate it. case WAIT_OBJECT_0 + 2:
if (!RunTerminationHelper(console_token, process_info.dwProcessId) || if (WTSGetActiveConsoleSessionId() == console_session_id) {
WaitForSingleObject(process_info.hProcess, 20000) != WAIT_OBJECT_0) { // The active console session didn't actually change. Let Sunshine keep running.
// If it won't terminate gracefully, kill it now still_running = true;
TerminateProcess(process_info.hProcess, ERROR_PROCESS_ABORTED); continue;
} }
break; // Fall-through to terminate Sunshine.exe and start it again.
case WAIT_OBJECT_0:
// The service is shutting down, so try to gracefully terminate Sunshine.exe.
// If it doesn't terminate in 20 seconds, we will forcefully terminate it.
if (!RunTerminationHelper(console_token, process_info.dwProcessId) ||
WaitForSingleObject(process_info.hProcess, 20000) != WAIT_OBJECT_0) {
// If it won't terminate gracefully, kill it now
TerminateProcess(process_info.hProcess, ERROR_PROCESS_ABORTED);
}
still_running = false;
break;
case WAIT_OBJECT_0 + 1: { case WAIT_OBJECT_0 + 1: {
// Sunshine terminated itself. // Sunshine terminated itself.
DWORD exit_code; DWORD exit_code;
if (GetExitCodeProcess(process_info.hProcess, &exit_code) && exit_code == ERROR_SHUTDOWN_IN_PROGRESS) { if (GetExitCodeProcess(process_info.hProcess, &exit_code) && exit_code == ERROR_SHUTDOWN_IN_PROGRESS) {
// Sunshine is asking for us to shut down, so gracefully stop ourselves. // Sunshine is asking for us to shut down, so gracefully stop ourselves.
SetEvent(stop_event); SetEvent(stop_event);
}
still_running = false;
break;
} }
break;
} }
} } while (still_running);
CloseHandle(process_info.hThread); CloseHandle(process_info.hThread);
CloseHandle(process_info.hProcess); CloseHandle(process_info.hProcess);