From 545e736389faaca29a618afd5be30ba120b96b12 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 6 Mar 2024 15:02:37 +1100 Subject: [PATCH] [host] windows: handle graceful shutdown on user switch --- host/include/interface/platform.h | 2 +- host/platform/Linux/src/platform.c | 2 +- host/platform/Windows/src/platform.c | 15 +- host/src/app.c | 327 ++++++++++++++------------- 4 files changed, 185 insertions(+), 161 deletions(-) diff --git a/host/include/interface/platform.h b/host/include/interface/platform.h index 77b424e7..8e061ca3 100644 --- a/host/include/interface/platform.h +++ b/host/include/interface/platform.h @@ -37,7 +37,7 @@ int app_main(int argc, char * argv[]); bool app_init(void); void app_shutdown(void); -void app_quit(void); +void app_quit(int exitcode); // these must be implemented for each OS const char * os_getExecutable(void); diff --git a/host/platform/Linux/src/platform.c b/host/platform/Linux/src/platform.c index 474dd382..8666daa9 100644 --- a/host/platform/Linux/src/platform.c +++ b/host/platform/Linux/src/platform.c @@ -62,7 +62,7 @@ int main(int argc, char * argv[]) void sigHandler(int signo) { DEBUG_INFO("SIGINT"); - app_quit(); + app_quit(LG_HOST_EXIT_USER); } bool app_init(void) diff --git a/host/platform/Windows/src/platform.c b/host/platform/Windows/src/platform.c index 9cc6faaa..6fb3e902 100644 --- a/host/platform/Windows/src/platform.c +++ b/host/platform/Windows/src/platform.c @@ -246,7 +246,7 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) NULL ); - if (clicked == ID_MENU_EXIT ) app_quit(); + if (clicked == ID_MENU_EXIT ) app_quit(LG_HOST_EXIT_USER); else if (clicked == ID_MENU_SHOW_LOG) { const char * logFile = option_get_string("os", "logFile"); @@ -259,6 +259,14 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) break; } + case WM_WTSSESSION_CHANGE: + if (wParam == WTS_CONSOLE_DISCONNECT) + { + DEBUG_INFO("Console disconnected, shutting down"); + app_quit(LG_HOST_EXIT_CAPTURE); + } + break; + default: if (msg == app.trayRestartMsg) RegisterTrayIcon(); @@ -440,6 +448,9 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL ); AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" ); + if (!WTSRegisterSessionNotification(app.messageWnd, NOTIFY_FOR_THIS_SESSION)) + DEBUG_WINERROR("WTSRegisterSessionNotification failed", GetLastError()); + // create the application thread LGThread * thread; if (!lgCreateThread("appThread", appThread, NULL, &thread)) @@ -470,6 +481,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine } shutdown: + WTSUnRegisterSessionNotification(app.messageWnd); + DestroyMenu(app.trayMenu); app_shutdown(); UnregisterWait(app.exitWait); diff --git a/host/src/app.c b/host/src/app.c index a17ec5e7..410ec1d3 100644 --- a/host/src/app.c +++ b/host/src/app.c @@ -68,8 +68,8 @@ enum AppState { APP_STATE_RUNNING, APP_STATE_IDLE, - APP_STATE_RESTART, - APP_STATE_REINIT, + APP_STATE_TRANSITION_TO_IDLE, + APP_STATE_REINIT_LGMP, APP_STATE_SHUTDOWN }; @@ -103,10 +103,12 @@ struct app uint32_t frameSerial; CaptureInterface * iface; + bool captureStarted; - enum AppState state; + enum AppState state, lastState; LGTimer * lgmpTimer; LGThread * frameThread; + bool threadsStarted; }; static struct app app; @@ -156,6 +158,14 @@ static struct Option options[] = {0} }; +inline static void setAppState(enum AppState state) +{ + if (app.state == APP_STATE_SHUTDOWN) + return; + app.lastState = app.state; + app.state = state; +} + static bool lgmpTimer(void * opaque) { LGMP_STATUS status; @@ -166,12 +176,12 @@ static bool lgmpTimer(void * opaque) { DEBUG_ERROR("LGMP reported the shared memory has been corrrupted, " "attempting to recover"); - app.state = APP_STATE_REINIT; + setAppState(APP_STATE_REINIT_LGMP); return false; } DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status)); - app.state = APP_STATE_SHUTDOWN; + setAppState(APP_STATE_SHUTDOWN); return false; } @@ -196,7 +206,7 @@ static bool lgmpTimer(void * opaque) return true; } -static bool sendFrame(CaptureResult result) +static bool sendFrame(CaptureResult result, bool * restart) { CaptureFrame frame = { 0 }; bool repeatFrame = false; @@ -225,13 +235,14 @@ static bool sendFrame(CaptureResult result) case CAPTURE_RESULT_REINIT: { - app.state = APP_STATE_RESTART; + *restart = true; DEBUG_INFO("Frame thread reinit"); return false; } case CAPTURE_RESULT_ERROR: { + *restart = false; DEBUG_ERROR("Failed to get the frame"); return false; } @@ -363,39 +374,43 @@ static int frameThread(void * opaque) while(app.state == APP_STATE_RUNNING) { - if (!sendFrame(CAPTURE_RESULT_OK)) + bool restart = false; + if (!sendFrame(CAPTURE_RESULT_OK, &restart)) + { + if (restart) + setAppState(APP_STATE_TRANSITION_TO_IDLE); break; + } } DEBUG_INFO("Frame thread stopped"); + return 0; } bool startThreads(void) { - app.state = APP_STATE_RUNNING; - if (!app.iface->asyncCapture) + if (app.threadsStarted) return true; - if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread)) - { - DEBUG_ERROR("Failed to create the frame thread"); - return false; - } + if (app.iface->asyncCapture) + if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread)) + { + DEBUG_ERROR("Failed to create the frame thread"); + return false; + } + app.threadsStarted = true; return true; } bool stopThreads(void) { - app.iface->stop(); - if (app.state != APP_STATE_SHUTDOWN && - app.state != APP_STATE_REINIT) - app.state = APP_STATE_IDLE; - - if (!app.iface->asyncCapture) + if (!app.threadsStarted) return true; - if (app.frameThread) + app.iface->stop(); + + if (app.iface->asyncCapture && app.frameThread) { if (!lgJoinThread(app.frameThread, NULL)) { @@ -406,32 +421,37 @@ bool stopThreads(void) app.frameThread = NULL; } + app.threadsStarted = false; return true; } static bool captureStart(void) { - if (app.state == APP_STATE_IDLE) - { - if (!app.iface->init(app.ivshmemBase, &app.alignSize)) - { - DEBUG_ERROR("Failed to initialize the capture device"); - return false; - } + if (app.captureStarted) + return true; - if (app.iface->start && !app.iface->start()) - { - DEBUG_ERROR("Failed to start the capture device"); - return false; - } + if (!app.iface->init(app.ivshmemBase, &app.alignSize)) + { + DEBUG_ERROR("Failed to initialize the capture device"); + return false; + } + + if (app.iface->start && !app.iface->start()) + { + DEBUG_ERROR("Failed to start the capture device"); + return false; } DEBUG_INFO("==== [ Capture Start ] ===="); + app.captureStarted = true; return true; } static bool captureStop(void) { + if (!app.captureStarted) + return true; + DEBUG_INFO("==== [ Capture Stop ] ===="); if (!app.iface->deinit()) @@ -441,6 +461,7 @@ static bool captureStop(void) } app.frameValid = false; + app.captureStarted = false; return true; } @@ -790,7 +811,8 @@ int app_main(int argc, char * argv[]) // make sure rng is actually seeded for LGMP srand((unsigned)time(NULL)); - app.state = APP_STATE_RUNNING; + app.lastState = APP_STATE_RUNNING; + app.state = APP_STATE_RUNNING; ivshmemOptionsInit(); // register capture interface options @@ -887,7 +909,8 @@ int app_main(int argc, char * argv[]) continue; } - if (iface->init(app.ivshmemBase, &app.alignSize)) + app.iface = iface; + if (captureStart()) break; iface->free(); @@ -896,6 +919,8 @@ int app_main(int argc, char * argv[]) if (!iface) { + app.iface = NULL; + if (*ifaceName) DEBUG_ERROR("Specified capture interface not supported"); else @@ -907,8 +932,6 @@ int app_main(int argc, char * argv[]) DEBUG_INFO("Using : %s", iface->getName()); DEBUG_INFO("Capture Method : %s", iface->asyncCapture ? "Asynchronous" : "Synchronous"); - - app.iface = iface; } if (!lgmpSetup(&shmDev)) @@ -919,141 +942,129 @@ int app_main(int argc, char * argv[]) LG_LOCK_INIT(app.pointerLock); - if (app.iface->start && !app.iface->start()) + do { - DEBUG_ERROR("Failed to start the capture interface"); - exitcode = LG_HOST_EXIT_FATAL; - goto fail_lgmp; - } - - while(app.state != APP_STATE_SHUTDOWN) - { - if (app.state == APP_STATE_REINIT) + switch(app.state) { - DEBUG_INFO("Performing LGMP reinitialization"); - lgmpShutdown(); - app.state = APP_STATE_RUNNING; - if (!lgmpSetup(&shmDev)) - goto fail_lgmp; - } - - if (app.state == APP_STATE_IDLE) - { - if(lgmpHostQueueHasSubs(app.pointerQueue) || - lgmpHostQueueHasSubs(app.frameQueue)) - { - if (!captureStart()) - { - exitcode = LG_HOST_EXIT_FAILED; - goto fail_capture; - } - - if (!startThreads()) - { - exitcode = LG_HOST_EXIT_FAILED; - goto fail_threads; - } - } - else - { - usleep(100000); - continue; - } - } - - while(likely(app.state != APP_STATE_SHUTDOWN && ( - lgmpHostQueueHasSubs(app.pointerQueue) || - lgmpHostQueueHasSubs(app.frameQueue)))) - { - if (unlikely( - app.state == APP_STATE_RESTART || - app.state == APP_STATE_REINIT)) + case APP_STATE_REINIT_LGMP: + DEBUG_INFO("Performing LGMP reinitialization"); + lgmpShutdown(); + setAppState(app.lastState); + if (!lgmpSetup(&shmDev)) + goto fail_lgmp; break; - if (unlikely(lgmpHostQueueNewSubs(app.pointerQueue) > 0)) - { - LG_LOCK(app.pointerLock); - sendPointer(true); - LG_UNLOCK(app.pointerLock); - } - - const uint64_t delta = microtime() - previousFrameTime; - if (delta < throttleUs) - { - const uint64_t us = throttleUs - delta; - // only delay if the time is reasonable - if (us > 1000) - nsleep(us * 1000); - } - - const uint64_t captureStart = microtime(); - - const CaptureResult result = app.iface->capture( - app.captureIndex, app.frameBuffer[app.captureIndex]); - - if (likely(result == CAPTURE_RESULT_OK)) - previousFrameTime = captureStart; - else if (likely(result == CAPTURE_RESULT_TIMEOUT)) - { - if (!app.iface->asyncCapture) - if (unlikely(app.frameValid && - lgmpHostQueueNewSubs(app.frameQueue) > 0)) - { - LGMP_STATUS status; - if ((status = lgmpHostQueuePost(app.frameQueue, 0, - app.frameMemory[app.readIndex])) != LGMP_OK) - DEBUG_ERROR("%s", lgmpStatusString(status)); - } - } - else - { - switch(result) + case APP_STATE_IDLE: + // if there are no clients subscribed, just remain idle + if (!lgmpHostQueueHasSubs(app.pointerQueue) && + !lgmpHostQueueHasSubs(app.frameQueue)) { - case CAPTURE_RESULT_REINIT: - app.state = APP_STATE_RESTART; - continue; - - case CAPTURE_RESULT_ERROR: - DEBUG_ERROR("Capture interface reported a fatal error"); - exitcode = LG_HOST_EXIT_FAILED; - goto fail_capture; - - default: - DEBUG_ASSERT("Invalid capture result"); + usleep(100000); + continue; } - } - if (!app.iface->asyncCapture) - sendFrame(result); - } + // clients subscribed, start the capture + if (!captureStart() || !startThreads()) + { + exitcode = LG_HOST_EXIT_FAILED; + goto fail; + } + setAppState(APP_STATE_RUNNING); + break; - if (app.state != APP_STATE_SHUTDOWN) - { - if (!stopThreads()) + case APP_STATE_TRANSITION_TO_IDLE: + if (!stopThreads() || !captureStop()) + { + exitcode = LG_HOST_EXIT_FAILED; + goto fail; + } + setAppState(APP_STATE_IDLE); + break; + + case APP_STATE_RUNNING: { - exitcode = LG_HOST_EXIT_FAILED; - goto fail_threads; + // if there are no clients subscribed, go idle + if (!lgmpHostQueueHasSubs(app.pointerQueue) && + !lgmpHostQueueHasSubs(app.frameQueue)) + { + setAppState(APP_STATE_TRANSITION_TO_IDLE); + break; + } + + // if there is a brand new client, send them the pointer + if (unlikely(lgmpHostQueueNewSubs(app.pointerQueue) > 0)) + { + LG_LOCK(app.pointerLock); + sendPointer(true); + LG_UNLOCK(app.pointerLock); + } + + const uint64_t delta = microtime() - previousFrameTime; + if (delta < throttleUs) + { + const uint64_t us = throttleUs - delta; + // only delay if the time is reasonable + if (us > 1000) + nsleep(us * 1000); + } + + const uint64_t captureStartTime = microtime(); + + const CaptureResult result = app.iface->capture( + app.captureIndex, app.frameBuffer[app.captureIndex]); + + if (likely(result == CAPTURE_RESULT_OK)) + previousFrameTime = captureStartTime; + else if (likely(result == CAPTURE_RESULT_TIMEOUT)) + { + if (!app.iface->asyncCapture) + if (unlikely(app.frameValid && + lgmpHostQueueNewSubs(app.frameQueue) > 0)) + { + LGMP_STATUS status; + if ((status = lgmpHostQueuePost(app.frameQueue, 0, + app.frameMemory[app.readIndex])) != LGMP_OK) + DEBUG_ERROR("%s", lgmpStatusString(status)); + } + } + else + { + switch(result) + { + case CAPTURE_RESULT_REINIT: + setAppState(APP_STATE_TRANSITION_TO_IDLE); + continue; + + case CAPTURE_RESULT_ERROR: + DEBUG_ERROR("Capture interface reported a fatal error"); + exitcode = LG_HOST_EXIT_FAILED; + goto fail; + + default: + DEBUG_ASSERT("Invalid capture result"); + } + } + + if (!app.iface->asyncCapture) + { + bool restart = false; + if (!sendFrame(result, &restart) && restart) + setAppState(APP_STATE_TRANSITION_TO_IDLE); + } + break; } - if (!captureStop()) - { - exitcode = LG_HOST_EXIT_FAILED; - goto fail_capture; - } - - continue; + case APP_STATE_SHUTDOWN: + break; } - - break; } + while(app.state != APP_STATE_SHUTDOWN); exitcode = app.exitcode; + +fail: stopThreads(); - -fail_threads: captureStop(); - -fail_capture: app.iface->free(); LG_LOCK_FREE(app.pointerLock); @@ -1072,7 +1083,7 @@ void app_shutdown(void) app.state = APP_STATE_SHUTDOWN; } -void app_quit(void) +void app_quit(int exitcode) { if (app.state == APP_STATE_SHUTDOWN) { @@ -1080,6 +1091,6 @@ void app_quit(void) exit(LG_HOST_EXIT_USER); } - app.exitcode = LG_HOST_EXIT_USER; + app.exitcode = exitcode; app_shutdown(); }