Move Windows CD-ROM detection to another thread that uses device notifications.

Fixes blocking when a disc is spinning up, except on initial call.
This commit is contained in:
Ryan C. Gordon 2012-03-12 04:05:31 -04:00
parent 16ff5c8aca
commit 0ae446d970
2 changed files with 173 additions and 27 deletions

View File

@ -22,8 +22,6 @@ From http://icculus.org/pipermail/physfs/2009-March/000698.html ...
- Deprecate PHYSFS_setSaneConfig(). It really should have been in the extras
directory.
- Clean up the sources to match my ever-changing coding style. :)
- Get current CD list from windows without blocking?
Read this: http://support.microsoft.com/kb/163503
From http://icculus.org/pipermail/physfs/2010-January/000821.html ...

View File

@ -19,6 +19,7 @@
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include <userenv.h>
#include <dbt.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
@ -95,7 +96,10 @@ typedef struct
const char *__PHYSFS_platformDirSeparator = "\\";
static char *userDir = NULL;
static HANDLE libUserEnv = NULL;
static HANDLE detectCDThreadHandle = NULL;
static HWND detectCDHwnd = 0;
static volatile int initialDiscDetectionComplete = 0;
static volatile DWORD drivesWithMediaBitmap = 0;
/*
* Figure out what the last failing Windows API call was, and
@ -192,40 +196,174 @@ static int determineUserDir(void)
} /* determineUserDir */
static BOOL mediaInDrive(const char *drive)
typedef BOOL (WINAPI *fnSTEM)(DWORD, LPDWORD b);
static DWORD pollDiscDrives(void)
{
UINT oldErrorMode;
DWORD tmp;
BOOL retval;
/* Try to use SetThreadErrorMode(), which showed up in Windows 7. */
HANDLE lib = LoadLibraryA("kernel32.dll");
fnSTEM stem = NULL;
char drive[4] = { 'x', ':', '\\', '\0' };
DWORD oldErrorMode = 0;
DWORD drives = 0;
DWORD i;
/* Prevent windows warning message appearing when checking media size */
/* !!! FIXME: Windows 7 offers SetThreadErrorMode(). */
oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
if (lib)
stem = (fnSTEM) GetProcAddress(lib, "SetThreadErrorMode");
if (stem)
stem(SEM_FAILCRITICALERRORS, &oldErrorMode);
else
oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
/* If this function succeeds, there's media in the drive */
retval = GetVolumeInformationA(drive, NULL, 0, NULL, NULL, &tmp, NULL, 0);
/* Do detection. This may block if a disc is spinning up. */
for (i = 'A'; i <= 'Z'; i++)
{
DWORD tmp = 0;
drive[0] = (char) i;
if (GetDriveTypeA(drive) != DRIVE_CDROM)
continue;
/* Revert back to old windows error handler */
SetErrorMode(oldErrorMode);
/* If this function succeeds, there's media in the drive */
if (GetVolumeInformationA(drive, NULL, 0, NULL, NULL, &tmp, NULL, 0))
drives |= (1 << (i - 'A'));
} /* for */
if (stem)
stem(oldErrorMode, NULL);
else
SetErrorMode(oldErrorMode);
if (lib)
FreeLibrary(lib);
return drives;
} /* pollDiscDrives */
static LRESULT CALLBACK detectCDWndProc(HWND hwnd, UINT msg,
WPARAM wp, LPARAM lparam)
{
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR) lparam;
PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME) lparam;
const int removed = (wp == DBT_DEVICEREMOVECOMPLETE);
if (msg == WM_DESTROY)
return 0;
else if ((msg != WM_DEVICECHANGE) ||
((wp != DBT_DEVICEARRIVAL) && (wp != DBT_DEVICEREMOVECOMPLETE)) ||
(lpdb->dbch_devicetype != DBT_DEVTYP_VOLUME) ||
((lpdbv->dbcv_flags & DBTF_MEDIA) == 0))
{
return DefWindowProcW(hwnd, msg, wp, lparam);
} /* else if */
if (removed)
drivesWithMediaBitmap &= ~lpdbv->dbcv_unitmask;
else
drivesWithMediaBitmap |= lpdbv->dbcv_unitmask;
return TRUE;
} /* detectCDWndProc */
static DWORD WINAPI detectCDThread(LPVOID lpParameter)
{
const char *classname = "PhysicsFSDetectCDCatcher";
const char *winname = "PhysicsFSDetectCDMsgWindow";
HINSTANCE hInstance = GetModuleHandleW(NULL);
ATOM class_atom = 0;
WNDCLASSEXA wce;
MSG msg;
memset(&wce, '\0', sizeof (wce));
wce.cbSize = sizeof (wce);
wce.lpfnWndProc = detectCDWndProc;
wce.lpszClassName = classname;
wce.hInstance = hInstance;
class_atom = RegisterClassExA(&wce);
if (class_atom == 0)
{
initialDiscDetectionComplete = 1; /* let main thread go on. */
return 0;
} /* if */
detectCDHwnd = CreateWindowExA(0, classname, winname, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
if (detectCDHwnd == NULL)
{
initialDiscDetectionComplete = 1; /* let main thread go on. */
UnregisterClassA(classname, hInstance);
return 0;
} /* if */
/* We'll get events when discs come and go from now on. */
/* Do initial detection, possibly blocking awhile... */
drivesWithMediaBitmap = pollDiscDrives();
initialDiscDetectionComplete = 1; /* let main thread go on. */
do
{
const BOOL rc = GetMessageW(&msg, detectCDHwnd, 0, 0);
if ((rc == 0) || (rc == -1))
break; /* don't care if WM_QUIT or error break this loop. */
TranslateMessage(&msg);
DispatchMessageW(&msg);
} while (1);
/* we've been asked to quit. */
DestroyWindow(detectCDHwnd);
do
{
const BOOL rc = GetMessage(&msg, detectCDHwnd, 0, 0);
if ((rc == 0) || (rc == -1))
break;
TranslateMessage(&msg);
DispatchMessageW(&msg);
} while (1);
UnregisterClassA(classname, hInstance);
return 0;
} /* detectCDThread */
return retval;
} /* mediaInDrive */
/*
* !!! FIXME: move this to a thread? This function hangs if you call it while
* !!! FIXME: a drive is spinning up right after inserting a disc.
*/
void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
{
/* !!! FIXME: Can CD drives be non-drive letter paths? */
/* !!! FIXME: (so can they be Unicode paths?) */
char drive_str[4] = "x:\\";
char ch;
for (ch = 'A'; ch <= 'Z'; ch++)
char drive_str[4] = { 'x', ':', '\\', '\0' };
DWORD drives = 0;
DWORD i;
/*
* If you poll a drive while a user is inserting a disc, the OS will
* block this thread until the drive has spun up. So we swallow the risk
* once for initial detection, and spin a thread that will get device
* events thereafter, for apps that use this interface to poll for
* disc insertion.
*/
if (!detectCDThreadHandle)
{
drive_str[0] = ch;
if (GetDriveTypeA(drive_str) == DRIVE_CDROM && mediaInDrive(drive_str))
initialDiscDetectionComplete = 0;
detectCDThreadHandle = CreateThread(NULL,0,detectCDThread,NULL,0,NULL);
if (detectCDThreadHandle == NULL)
return; /* oh well. */
while (!initialDiscDetectionComplete)
Sleep(50);
} /* if */
drives = drivesWithMediaBitmap; /* whatever the thread has seen, we take. */
for (i = 'A'; i <= 'Z'; i++)
{
if (drives & (1 << (i - 'A')))
{
drive_str[0] = (char) i;
cb(data, drive_str);
} /* if */
} /* for */
} /* __PHYSFS_platformDetectAvailableCDs */
@ -469,6 +607,16 @@ int __PHYSFS_platformInit(void)
int __PHYSFS_platformDeinit(void)
{
if (detectCDThreadHandle != NULL)
{
if (detectCDHwnd)
PostMessageW(detectCDHwnd, WM_QUIT, 0, 0);
CloseHandle(detectCDThreadHandle);
detectCDThreadHandle = NULL;
initialDiscDetectionComplete = 0;
drivesWithMediaBitmap = 0;
} /* if */
if (libUserEnv)
FreeLibrary(libUserEnv);
libUserEnv = NULL;