[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.
This commit is contained in:
Quantum 2021-07-19 06:16:27 -04:00 committed by Geoffrey McRae
parent a4f5ce08b9
commit 16ee1a825c
3 changed files with 110 additions and 10 deletions

View File

@ -28,6 +28,7 @@ target_link_libraries(platform_Windows
psapi psapi
shlwapi shlwapi
powrprof powrprof
rpcrt4
) )
target_include_directories(platform_Windows target_include_directories(platform_Windows

View File

@ -57,6 +57,9 @@ struct AppState
NOTIFYICONDATA iconData; NOTIFYICONDATA iconData;
UINT trayRestartMsg; UINT trayRestartMsg;
HMENU trayMenu; HMENU trayMenu;
HANDLE exitThreadEvent;
LGThread * exitThread;
}; };
static struct AppState app = {0}; static struct AppState app = {0};
@ -212,6 +215,7 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
switch(msg) switch(msg)
{ {
case WM_DESTROY: case WM_DESTROY:
Shell_NotifyIcon(NIM_DELETE, &app.iconData);
PostQuitMessage(0); PostQuitMessage(0);
break; break;
@ -264,12 +268,31 @@ static int appThread(void * opaque)
{ {
RegisterTrayIcon(); RegisterTrayIcon();
int result = app_main(app.argc, app.argv); int result = app_main(app.argc, app.argv);
DestroyWindow(app.messageWnd);
Shell_NotifyIcon(NIM_DELETE, &app.iconData);
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
return result; 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) LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam)
{ {
return SendMessage(app.messageWnd, Msg, wParam, 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, .type = OPTION_TYPE_STRING,
.value.x_string = logFilePath .value.x_string = logFilePath
}, },
{
.module = "os",
.name = "exitEvent",
.description = "Exit when the specified event is signaled",
.type = OPTION_TYPE_STRING,
.value.x_string = ""
},
{0} {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.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 // this is needed so that unprivileged processes can send us this message
_ChangeWindowMessageFilterEx = (PChangeWindowMessageFilterEx)GetProcAddress(user32, "ChangeWindowMessageFilterEx"); _ChangeWindowMessageFilterEx = (PChangeWindowMessageFilterEx)GetProcAddress(user32, "ChangeWindowMessageFilterEx");
if (_ChangeWindowMessageFilterEx) if (_ChangeWindowMessageFilterEx)
@ -449,12 +481,20 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
shutdown: shutdown:
DestroyMenu(app.trayMenu); DestroyMenu(app.trayMenu);
app_shutdown(); app_shutdown();
SetEvent(app.exitThreadEvent);
if (!lgJoinThread(thread, &result)) if (!lgJoinThread(thread, &result))
{ {
DEBUG_ERROR("Failed to join the main application thread"); DEBUG_ERROR("Failed to join the main application thread");
result = LG_HOST_EXIT_FAILED; 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: finish:
for(int i = 0; i < app.argc; ++i) for(int i = 0; i < app.argc; ++i)
@ -518,6 +558,19 @@ bool app_init(void)
// try to boost the scheduler priority // try to boost the scheduler priority
boostPriority(); 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; return true;
} }

View File

@ -50,6 +50,8 @@ struct Service
FILE * logFile; FILE * logFile;
bool running; bool running;
HANDLE process; HANDLE process;
HANDLE exitEvent;
char exitEventName[64];
}; };
struct Service service = { 0 }; struct Service service = { 0 };
@ -311,10 +313,19 @@ void Launch(void)
.lpDesktop = "WinSta0\\Default" .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( if (!f_CreateProcessAsUserA(
hToken, hToken,
os_getExecutable(), os_getExecutable(),
NULL, cmdline,
NULL, NULL,
NULL, NULL,
FALSE, FALSE,
@ -643,6 +654,21 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv)
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); 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; int failCount = 0;
while(1) while(1)
{ {
@ -736,14 +762,34 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv)
stopped: stopped:
if (service.running) if (service.running)
{ {
doLog("Terminating the host application\n"); SetEvent(service.exitEvent);
if (TerminateProcess(service.process, LG_HOST_EXIT_KILLED)) switch (WaitForSingleObject(service.process, 1000))
{ {
while(WaitForSingleObject(service.process, INFINITE) != WAIT_OBJECT_0) {} case WAIT_OBJECT_0:
doLog("Host application terminated\n"); 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); CloseHandle(service.process);
service.process = NULL; service.process = NULL;
} }