[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
shlwapi
powrprof
rpcrt4
)
target_include_directories(platform_Windows

View File

@ -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;
}

View File

@ -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)
{
@ -734,16 +760,36 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv)
}
stopped:
if (service.running)
{
SetEvent(service.exitEvent);
switch (WaitForSingleObject(service.process, 1000))
{
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;
}
if (service.running)
{
doLog("Terminating the host application\n");
if (TerminateProcess(service.process, LG_HOST_EXIT_KILLED))
{
while(WaitForSingleObject(service.process, INFINITE) != WAIT_OBJECT_0) {}
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;
}