[host] windows: handle graceful shutdown on user switch

This commit is contained in:
Geoffrey McRae 2024-03-06 15:02:37 +11:00
parent 6a72633674
commit 545e736389
4 changed files with 185 additions and 161 deletions

View file

@ -37,7 +37,7 @@
int app_main(int argc, char * argv[]); int app_main(int argc, char * argv[]);
bool app_init(void); bool app_init(void);
void app_shutdown(void); void app_shutdown(void);
void app_quit(void); void app_quit(int exitcode);
// these must be implemented for each OS // these must be implemented for each OS
const char * os_getExecutable(void); const char * os_getExecutable(void);

View file

@ -62,7 +62,7 @@ int main(int argc, char * argv[])
void sigHandler(int signo) void sigHandler(int signo)
{ {
DEBUG_INFO("SIGINT"); DEBUG_INFO("SIGINT");
app_quit(); app_quit(LG_HOST_EXIT_USER);
} }
bool app_init(void) bool app_init(void)

View file

@ -246,7 +246,7 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
NULL 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) else if (clicked == ID_MENU_SHOW_LOG)
{ {
const char * logFile = option_get_string("os", "logFile"); const char * logFile = option_get_string("os", "logFile");
@ -259,6 +259,14 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
break; break;
} }
case WM_WTSSESSION_CHANGE:
if (wParam == WTS_CONSOLE_DISCONNECT)
{
DEBUG_INFO("Console disconnected, shutting down");
app_quit(LG_HOST_EXIT_CAPTURE);
}
break;
default: default:
if (msg == app.trayRestartMsg) if (msg == app.trayRestartMsg)
RegisterTrayIcon(); 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_SEPARATOR, 0 , NULL );
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" ); 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 // create the application thread
LGThread * thread; LGThread * thread;
if (!lgCreateThread("appThread", appThread, NULL, &thread)) if (!lgCreateThread("appThread", appThread, NULL, &thread))
@ -470,6 +481,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
} }
shutdown: shutdown:
WTSUnRegisterSessionNotification(app.messageWnd);
DestroyMenu(app.trayMenu); DestroyMenu(app.trayMenu);
app_shutdown(); app_shutdown();
UnregisterWait(app.exitWait); UnregisterWait(app.exitWait);

View file

@ -68,8 +68,8 @@ enum AppState
{ {
APP_STATE_RUNNING, APP_STATE_RUNNING,
APP_STATE_IDLE, APP_STATE_IDLE,
APP_STATE_RESTART, APP_STATE_TRANSITION_TO_IDLE,
APP_STATE_REINIT, APP_STATE_REINIT_LGMP,
APP_STATE_SHUTDOWN APP_STATE_SHUTDOWN
}; };
@ -103,10 +103,12 @@ struct app
uint32_t frameSerial; uint32_t frameSerial;
CaptureInterface * iface; CaptureInterface * iface;
bool captureStarted;
enum AppState state; enum AppState state, lastState;
LGTimer * lgmpTimer; LGTimer * lgmpTimer;
LGThread * frameThread; LGThread * frameThread;
bool threadsStarted;
}; };
static struct app app; static struct app app;
@ -156,6 +158,14 @@ static struct Option options[] =
{0} {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) static bool lgmpTimer(void * opaque)
{ {
LGMP_STATUS status; LGMP_STATUS status;
@ -166,12 +176,12 @@ static bool lgmpTimer(void * opaque)
{ {
DEBUG_ERROR("LGMP reported the shared memory has been corrrupted, " DEBUG_ERROR("LGMP reported the shared memory has been corrrupted, "
"attempting to recover"); "attempting to recover");
app.state = APP_STATE_REINIT; setAppState(APP_STATE_REINIT_LGMP);
return false; return false;
} }
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status)); DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
app.state = APP_STATE_SHUTDOWN; setAppState(APP_STATE_SHUTDOWN);
return false; return false;
} }
@ -196,7 +206,7 @@ static bool lgmpTimer(void * opaque)
return true; return true;
} }
static bool sendFrame(CaptureResult result) static bool sendFrame(CaptureResult result, bool * restart)
{ {
CaptureFrame frame = { 0 }; CaptureFrame frame = { 0 };
bool repeatFrame = false; bool repeatFrame = false;
@ -225,13 +235,14 @@ static bool sendFrame(CaptureResult result)
case CAPTURE_RESULT_REINIT: case CAPTURE_RESULT_REINIT:
{ {
app.state = APP_STATE_RESTART; *restart = true;
DEBUG_INFO("Frame thread reinit"); DEBUG_INFO("Frame thread reinit");
return false; return false;
} }
case CAPTURE_RESULT_ERROR: case CAPTURE_RESULT_ERROR:
{ {
*restart = false;
DEBUG_ERROR("Failed to get the frame"); DEBUG_ERROR("Failed to get the frame");
return false; return false;
} }
@ -363,39 +374,43 @@ static int frameThread(void * opaque)
while(app.state == APP_STATE_RUNNING) 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; break;
} }
}
DEBUG_INFO("Frame thread stopped"); DEBUG_INFO("Frame thread stopped");
return 0; return 0;
} }
bool startThreads(void) bool startThreads(void)
{ {
app.state = APP_STATE_RUNNING; if (app.threadsStarted)
if (!app.iface->asyncCapture)
return true; return true;
if (app.iface->asyncCapture)
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread)) if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
{ {
DEBUG_ERROR("Failed to create the frame thread"); DEBUG_ERROR("Failed to create the frame thread");
return false; return false;
} }
app.threadsStarted = true;
return true; return true;
} }
bool stopThreads(void) bool stopThreads(void)
{ {
app.iface->stop(); if (!app.threadsStarted)
if (app.state != APP_STATE_SHUTDOWN &&
app.state != APP_STATE_REINIT)
app.state = APP_STATE_IDLE;
if (!app.iface->asyncCapture)
return true; return true;
if (app.frameThread) app.iface->stop();
if (app.iface->asyncCapture && app.frameThread)
{ {
if (!lgJoinThread(app.frameThread, NULL)) if (!lgJoinThread(app.frameThread, NULL))
{ {
@ -406,13 +421,15 @@ bool stopThreads(void)
app.frameThread = NULL; app.frameThread = NULL;
} }
app.threadsStarted = false;
return true; return true;
} }
static bool captureStart(void) static bool captureStart(void)
{ {
if (app.state == APP_STATE_IDLE) if (app.captureStarted)
{ return true;
if (!app.iface->init(app.ivshmemBase, &app.alignSize)) if (!app.iface->init(app.ivshmemBase, &app.alignSize))
{ {
DEBUG_ERROR("Failed to initialize the capture device"); DEBUG_ERROR("Failed to initialize the capture device");
@ -424,14 +441,17 @@ static bool captureStart(void)
DEBUG_ERROR("Failed to start the capture device"); DEBUG_ERROR("Failed to start the capture device");
return false; return false;
} }
}
DEBUG_INFO("==== [ Capture Start ] ===="); DEBUG_INFO("==== [ Capture Start ] ====");
app.captureStarted = true;
return true; return true;
} }
static bool captureStop(void) static bool captureStop(void)
{ {
if (!app.captureStarted)
return true;
DEBUG_INFO("==== [ Capture Stop ] ===="); DEBUG_INFO("==== [ Capture Stop ] ====");
if (!app.iface->deinit()) if (!app.iface->deinit())
@ -441,6 +461,7 @@ static bool captureStop(void)
} }
app.frameValid = false; app.frameValid = false;
app.captureStarted = false;
return true; return true;
} }
@ -790,6 +811,7 @@ int app_main(int argc, char * argv[])
// make sure rng is actually seeded for LGMP // make sure rng is actually seeded for LGMP
srand((unsigned)time(NULL)); srand((unsigned)time(NULL));
app.lastState = APP_STATE_RUNNING;
app.state = APP_STATE_RUNNING; app.state = APP_STATE_RUNNING;
ivshmemOptionsInit(); ivshmemOptionsInit();
@ -887,7 +909,8 @@ int app_main(int argc, char * argv[])
continue; continue;
} }
if (iface->init(app.ivshmemBase, &app.alignSize)) app.iface = iface;
if (captureStart())
break; break;
iface->free(); iface->free();
@ -896,6 +919,8 @@ int app_main(int argc, char * argv[])
if (!iface) if (!iface)
{ {
app.iface = NULL;
if (*ifaceName) if (*ifaceName)
DEBUG_ERROR("Specified capture interface not supported"); DEBUG_ERROR("Specified capture interface not supported");
else else
@ -907,8 +932,6 @@ int app_main(int argc, char * argv[])
DEBUG_INFO("Using : %s", iface->getName()); DEBUG_INFO("Using : %s", iface->getName());
DEBUG_INFO("Capture Method : %s", iface->asyncCapture ? DEBUG_INFO("Capture Method : %s", iface->asyncCapture ?
"Asynchronous" : "Synchronous"); "Asynchronous" : "Synchronous");
app.iface = iface;
} }
if (!lgmpSetup(&shmDev)) if (!lgmpSetup(&shmDev))
@ -919,57 +942,56 @@ int app_main(int argc, char * argv[])
LG_LOCK_INIT(app.pointerLock); LG_LOCK_INIT(app.pointerLock);
if (app.iface->start && !app.iface->start()) do
{ {
DEBUG_ERROR("Failed to start the capture interface"); switch(app.state)
exitcode = LG_HOST_EXIT_FATAL;
goto fail_lgmp;
}
while(app.state != APP_STATE_SHUTDOWN)
{
if (app.state == APP_STATE_REINIT)
{ {
case APP_STATE_REINIT_LGMP:
DEBUG_INFO("Performing LGMP reinitialization"); DEBUG_INFO("Performing LGMP reinitialization");
lgmpShutdown(); lgmpShutdown();
app.state = APP_STATE_RUNNING; setAppState(app.lastState);
if (!lgmpSetup(&shmDev)) if (!lgmpSetup(&shmDev))
goto fail_lgmp; goto fail_lgmp;
} break;
if (app.state == APP_STATE_IDLE) case APP_STATE_IDLE:
{ // if there are no clients subscribed, just remain idle
if(lgmpHostQueueHasSubs(app.pointerQueue) || if (!lgmpHostQueueHasSubs(app.pointerQueue) &&
lgmpHostQueueHasSubs(app.frameQueue)) !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); usleep(100000);
continue; continue;
} }
}
while(likely(app.state != APP_STATE_SHUTDOWN && ( // clients subscribed, start the capture
lgmpHostQueueHasSubs(app.pointerQueue) || if (!captureStart() || !startThreads())
lgmpHostQueueHasSubs(app.frameQueue))))
{ {
if (unlikely( exitcode = LG_HOST_EXIT_FAILED;
app.state == APP_STATE_RESTART || goto fail;
app.state == APP_STATE_REINIT)) }
setAppState(APP_STATE_RUNNING);
break; break;
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:
{
// 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)) if (unlikely(lgmpHostQueueNewSubs(app.pointerQueue) > 0))
{ {
LG_LOCK(app.pointerLock); LG_LOCK(app.pointerLock);
@ -986,13 +1008,13 @@ int app_main(int argc, char * argv[])
nsleep(us * 1000); nsleep(us * 1000);
} }
const uint64_t captureStart = microtime(); const uint64_t captureStartTime = microtime();
const CaptureResult result = app.iface->capture( const CaptureResult result = app.iface->capture(
app.captureIndex, app.frameBuffer[app.captureIndex]); app.captureIndex, app.frameBuffer[app.captureIndex]);
if (likely(result == CAPTURE_RESULT_OK)) if (likely(result == CAPTURE_RESULT_OK))
previousFrameTime = captureStart; previousFrameTime = captureStartTime;
else if (likely(result == CAPTURE_RESULT_TIMEOUT)) else if (likely(result == CAPTURE_RESULT_TIMEOUT))
{ {
if (!app.iface->asyncCapture) if (!app.iface->asyncCapture)
@ -1010,13 +1032,13 @@ int app_main(int argc, char * argv[])
switch(result) switch(result)
{ {
case CAPTURE_RESULT_REINIT: case CAPTURE_RESULT_REINIT:
app.state = APP_STATE_RESTART; setAppState(APP_STATE_TRANSITION_TO_IDLE);
continue; continue;
case CAPTURE_RESULT_ERROR: case CAPTURE_RESULT_ERROR:
DEBUG_ERROR("Capture interface reported a fatal error"); DEBUG_ERROR("Capture interface reported a fatal error");
exitcode = LG_HOST_EXIT_FAILED; exitcode = LG_HOST_EXIT_FAILED;
goto fail_capture; goto fail;
default: default:
DEBUG_ASSERT("Invalid capture result"); DEBUG_ASSERT("Invalid capture result");
@ -1024,36 +1046,25 @@ int app_main(int argc, char * argv[])
} }
if (!app.iface->asyncCapture) if (!app.iface->asyncCapture)
sendFrame(result);
}
if (app.state != APP_STATE_SHUTDOWN)
{ {
if (!stopThreads()) bool restart = false;
{ if (!sendFrame(result, &restart) && restart)
exitcode = LG_HOST_EXIT_FAILED; setAppState(APP_STATE_TRANSITION_TO_IDLE);
goto fail_threads;
} }
if (!captureStop())
{
exitcode = LG_HOST_EXIT_FAILED;
goto fail_capture;
}
continue;
}
break; break;
} }
case APP_STATE_SHUTDOWN:
break;
}
}
while(app.state != APP_STATE_SHUTDOWN);
exitcode = app.exitcode; exitcode = app.exitcode;
fail:
stopThreads(); stopThreads();
fail_threads:
captureStop(); captureStop();
fail_capture:
app.iface->free(); app.iface->free();
LG_LOCK_FREE(app.pointerLock); LG_LOCK_FREE(app.pointerLock);
@ -1072,7 +1083,7 @@ void app_shutdown(void)
app.state = APP_STATE_SHUTDOWN; app.state = APP_STATE_SHUTDOWN;
} }
void app_quit(void) void app_quit(int exitcode)
{ {
if (app.state == APP_STATE_SHUTDOWN) if (app.state == APP_STATE_SHUTDOWN)
{ {
@ -1080,6 +1091,6 @@ void app_quit(void)
exit(LG_HOST_EXIT_USER); exit(LG_HOST_EXIT_USER);
} }
app.exitcode = LG_HOST_EXIT_USER; app.exitcode = exitcode;
app_shutdown(); app_shutdown();
} }