From 16ee1a825c10a416dcbfe30078162796c045c7be Mon Sep 17 00:00:00 2001 From: Quantum Date: Mon, 19 Jul 2021 06:16:27 -0400 Subject: [PATCH] [host] windows: use event to gracefully signal exit This allows the process to be terminated without resorting to TerminateProcess. With some fixes, this allows the notification icon to be removed when the service is restarted. Furthermore, instead of sending WM_DESTROY to fool the window into believing it's being destroyed, we actually call DestroyWindow now. --- host/platform/Windows/CMakeLists.txt | 1 + host/platform/Windows/src/platform.c | 59 +++++++++++++++++++++++++-- host/platform/Windows/src/service.c | 60 ++++++++++++++++++++++++---- 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/host/platform/Windows/CMakeLists.txt b/host/platform/Windows/CMakeLists.txt index 4ccf06c3..d689001c 100644 --- a/host/platform/Windows/CMakeLists.txt +++ b/host/platform/Windows/CMakeLists.txt @@ -28,6 +28,7 @@ target_link_libraries(platform_Windows psapi shlwapi powrprof + rpcrt4 ) target_include_directories(platform_Windows diff --git a/host/platform/Windows/src/platform.c b/host/platform/Windows/src/platform.c index 39dce89c..e7e93842 100644 --- a/host/platform/Windows/src/platform.c +++ b/host/platform/Windows/src/platform.c @@ -57,6 +57,9 @@ struct AppState NOTIFYICONDATA iconData; UINT trayRestartMsg; HMENU trayMenu; + + HANDLE exitThreadEvent; + LGThread * exitThread; }; static struct AppState app = {0}; @@ -212,6 +215,7 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) switch(msg) { case WM_DESTROY: + Shell_NotifyIcon(NIM_DELETE, &app.iconData); PostQuitMessage(0); break; @@ -264,12 +268,31 @@ static int appThread(void * opaque) { RegisterTrayIcon(); int result = app_main(app.argc, app.argv); - - Shell_NotifyIcon(NIM_DELETE, &app.iconData); - SendMessage(app.messageWnd, WM_DESTROY, 0, 0); + DestroyWindow(app.messageWnd); return result; } +static int exitThread(void * opaque) +{ + HANDLE handles[2] = { (HANDLE) opaque, app.exitThreadEvent }; + + switch (WaitForMultipleObjects(2, handles, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + DEBUG_INFO("Received exit event"); + SendMessage(app.messageWnd, WM_CLOSE, 0, 0); + break; + + case WAIT_OBJECT_0 + 1: + break; + + case WAIT_FAILED: + DEBUG_ERROR("WaitForMultipleObjects failed: 0x%lx", GetLastError()); + } + + return 0; +} + LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam) { return SendMessage(app.messageWnd, Msg, wParam, lParam); @@ -362,6 +385,13 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine .type = OPTION_TYPE_STRING, .value.x_string = logFilePath }, + { + .module = "os", + .name = "exitEvent", + .description = "Exit when the specified event is signaled", + .type = OPTION_TYPE_STRING, + .value.x_string = "" + }, {0} }; @@ -404,6 +434,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine app.messageWnd = CreateWindowEx(0, MAKEINTATOM(class), NULL, 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); + app.exitThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + // this is needed so that unprivileged processes can send us this message _ChangeWindowMessageFilterEx = (PChangeWindowMessageFilterEx)GetProcAddress(user32, "ChangeWindowMessageFilterEx"); if (_ChangeWindowMessageFilterEx) @@ -449,12 +481,20 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine shutdown: DestroyMenu(app.trayMenu); app_shutdown(); + SetEvent(app.exitThreadEvent); + if (!lgJoinThread(thread, &result)) { DEBUG_ERROR("Failed to join the main application thread"); result = LG_HOST_EXIT_FAILED; } + if (app.exitThread && !lgJoinThread(app.exitThread, &result)) + { + DEBUG_ERROR("Failed to join the exit thread"); + result = LG_HOST_EXIT_FAILED; + } + finish: for(int i = 0; i < app.argc; ++i) @@ -518,6 +558,19 @@ bool app_init(void) // try to boost the scheduler priority boostPriority(); + // open exit signaling event + HANDLE exitEvent = NULL; + const char * exitEventName = option_get_string("os", "exitEvent"); + if (exitEventName && exitEventName[0]) + { + exitEvent = OpenEvent(SYNCHRONIZE, FALSE, exitEventName); + if (!exitEvent) + DEBUG_WARN("Failed to open exitEvent with error 0x%lx: %s", GetLastError(), exitEventName); + } + + if (exitEvent && !lgCreateThread("exitThread", exitThread, exitEvent, &app.exitThread)) + DEBUG_ERROR("Failed to create the exit thread"); + return true; } diff --git a/host/platform/Windows/src/service.c b/host/platform/Windows/src/service.c index 92b330ac..be6ef9db 100644 --- a/host/platform/Windows/src/service.c +++ b/host/platform/Windows/src/service.c @@ -50,6 +50,8 @@ struct Service FILE * logFile; bool running; HANDLE process; + HANDLE exitEvent; + char exitEventName[64]; }; struct Service service = { 0 }; @@ -311,10 +313,19 @@ void Launch(void) .lpDesktop = "WinSta0\\Default" }; + char * cmdline = NULL; + char cmdbuf[128]; + if (service.exitEvent) + { + snprintf(cmdbuf, sizeof(cmdbuf), "looking-glass-host.exe os:exitEvent=%s", + service.exitEventName); + cmdline = cmdbuf; + } + if (!f_CreateProcessAsUserA( hToken, os_getExecutable(), - NULL, + cmdline, NULL, NULL, FALSE, @@ -643,6 +654,21 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv) ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); + UUID uuid; + RPC_CSTR uuidStr; + UuidCreate(&uuid); + + if (UuidToString(&uuid, &uuidStr) == RPC_S_OK) + { + strcpy(service.exitEventName, "Global\\"); + strcat(service.exitEventName, (const char*) uuidStr); + RpcStringFree(&uuidStr); + + service.exitEvent = CreateEvent(NULL, FALSE, FALSE, service.exitEventName); + if (!service.exitEvent) + doLog("Failed to create exit event: 0x%lx\n", GetLastError()); + } + int failCount = 0; while(1) { @@ -736,14 +762,34 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv) stopped: if (service.running) { - doLog("Terminating the host application\n"); - if (TerminateProcess(service.process, LG_HOST_EXIT_KILLED)) + SetEvent(service.exitEvent); + switch (WaitForSingleObject(service.process, 1000)) { - while(WaitForSingleObject(service.process, INFINITE) != WAIT_OBJECT_0) {} - doLog("Host application terminated\n"); + case WAIT_OBJECT_0: + service.running = false; + break; + case WAIT_TIMEOUT: + doLog("Host application failed to exit in 1 second\n"); + break; + case WAIT_FAILED: + doLog("WaitForSingleObject failed: 0x%lx\n", GetLastError()); + break; } - else - doLog("Failed to terminate the host application\n"); + + if (service.running) + { + doLog("Terminating the host application\n"); + if (TerminateProcess(service.process, LG_HOST_EXIT_KILLED)) + { + if (WaitForSingleObject(service.process, INFINITE) == WAIT_OBJECT_0) + doLog("Host application terminated\n"); + else + doLog("WaitForSingleObject failed: 0x%lx\n", GetLastError()); + } + else + doLog("Failed to terminate the host application\n"); + } + CloseHandle(service.process); service.process = NULL; }