[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 (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread)) if (app.iface->asyncCapture)
{ if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
DEBUG_ERROR("Failed to create the frame thread"); {
return false; DEBUG_ERROR("Failed to create the frame thread");
} 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,32 +421,37 @@ 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))
{
DEBUG_ERROR("Failed to initialize the capture device");
return false;
}
if (app.iface->start && !app.iface->start()) if (!app.iface->init(app.ivshmemBase, &app.alignSize))
{ {
DEBUG_ERROR("Failed to start the capture device"); DEBUG_ERROR("Failed to initialize the capture device");
return false; return false;
} }
if (app.iface->start && !app.iface->start())
{
DEBUG_ERROR("Failed to start the capture device");
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,7 +811,8 @@ 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.state = APP_STATE_RUNNING; app.lastState = APP_STATE_RUNNING;
app.state = APP_STATE_RUNNING;
ivshmemOptionsInit(); ivshmemOptionsInit();
// register capture interface options // register capture interface options
@ -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,141 +942,129 @@ 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)
{ {
DEBUG_INFO("Performing LGMP reinitialization"); case APP_STATE_REINIT_LGMP:
lgmpShutdown(); DEBUG_INFO("Performing LGMP reinitialization");
app.state = APP_STATE_RUNNING; lgmpShutdown();
if (!lgmpSetup(&shmDev)) setAppState(app.lastState);
goto fail_lgmp; 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))
break; break;
if (unlikely(lgmpHostQueueNewSubs(app.pointerQueue) > 0)) case APP_STATE_IDLE:
{ // if there are no clients subscribed, just remain idle
LG_LOCK(app.pointerLock); if (!lgmpHostQueueHasSubs(app.pointerQueue) &&
sendPointer(true); !lgmpHostQueueHasSubs(app.frameQueue))
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 CAPTURE_RESULT_REINIT: usleep(100000);
app.state = APP_STATE_RESTART; continue;
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");
} }
}
if (!app.iface->asyncCapture) // clients subscribed, start the capture
sendFrame(result); if (!captureStart() || !startThreads())
} {
exitcode = LG_HOST_EXIT_FAILED;
goto fail;
}
setAppState(APP_STATE_RUNNING);
break;
if (app.state != APP_STATE_SHUTDOWN) case APP_STATE_TRANSITION_TO_IDLE:
{ if (!stopThreads() || !captureStop())
if (!stopThreads()) {
exitcode = LG_HOST_EXIT_FAILED;
goto fail;
}
setAppState(APP_STATE_IDLE);
break;
case APP_STATE_RUNNING:
{ {
exitcode = LG_HOST_EXIT_FAILED; // if there are no clients subscribed, go idle
goto fail_threads; 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()) case APP_STATE_SHUTDOWN:
{ break;
exitcode = LG_HOST_EXIT_FAILED;
goto fail_capture;
}
continue;
} }
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();
} }