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:
parent
16ff5c8aca
commit
0ae446d970
|
@ -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
|
- Deprecate PHYSFS_setSaneConfig(). It really should have been in the extras
|
||||||
directory.
|
directory.
|
||||||
- Clean up the sources to match my ever-changing coding style. :)
|
- 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 ...
|
From http://icculus.org/pipermail/physfs/2010-January/000821.html ...
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#define WIN32_LEAN_AND_MEAN 1
|
#define WIN32_LEAN_AND_MEAN 1
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <userenv.h>
|
#include <userenv.h>
|
||||||
|
#include <dbt.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
@ -95,7 +96,10 @@ typedef struct
|
||||||
const char *__PHYSFS_platformDirSeparator = "\\";
|
const char *__PHYSFS_platformDirSeparator = "\\";
|
||||||
static char *userDir = NULL;
|
static char *userDir = NULL;
|
||||||
static HANDLE libUserEnv = 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
|
* Figure out what the last failing Windows API call was, and
|
||||||
|
@ -192,40 +196,174 @@ static int determineUserDir(void)
|
||||||
} /* determineUserDir */
|
} /* determineUserDir */
|
||||||
|
|
||||||
|
|
||||||
static BOOL mediaInDrive(const char *drive)
|
typedef BOOL (WINAPI *fnSTEM)(DWORD, LPDWORD b);
|
||||||
|
|
||||||
|
static DWORD pollDiscDrives(void)
|
||||||
{
|
{
|
||||||
UINT oldErrorMode;
|
/* Try to use SetThreadErrorMode(), which showed up in Windows 7. */
|
||||||
DWORD tmp;
|
HANDLE lib = LoadLibraryA("kernel32.dll");
|
||||||
BOOL retval;
|
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 */
|
if (lib)
|
||||||
/* !!! FIXME: Windows 7 offers SetThreadErrorMode(). */
|
stem = (fnSTEM) GetProcAddress(lib, "SetThreadErrorMode");
|
||||||
oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
|
|
||||||
|
if (stem)
|
||||||
|
stem(SEM_FAILCRITICALERRORS, &oldErrorMode);
|
||||||
|
else
|
||||||
|
oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
|
||||||
|
|
||||||
/* If this function succeeds, there's media in the drive */
|
/* Do detection. This may block if a disc is spinning up. */
|
||||||
retval = GetVolumeInformationA(drive, NULL, 0, NULL, NULL, &tmp, NULL, 0);
|
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 */
|
/* If this function succeeds, there's media in the drive */
|
||||||
SetErrorMode(oldErrorMode);
|
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)
|
void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
|
||||||
{
|
{
|
||||||
/* !!! FIXME: Can CD drives be non-drive letter paths? */
|
char drive_str[4] = { 'x', ':', '\\', '\0' };
|
||||||
/* !!! FIXME: (so can they be Unicode paths?) */
|
DWORD drives = 0;
|
||||||
char drive_str[4] = "x:\\";
|
DWORD i;
|
||||||
char ch;
|
|
||||||
for (ch = 'A'; ch <= 'Z'; ch++)
|
/*
|
||||||
|
* 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;
|
initialDiscDetectionComplete = 0;
|
||||||
if (GetDriveTypeA(drive_str) == DRIVE_CDROM && mediaInDrive(drive_str))
|
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);
|
cb(data, drive_str);
|
||||||
|
} /* if */
|
||||||
} /* for */
|
} /* for */
|
||||||
} /* __PHYSFS_platformDetectAvailableCDs */
|
} /* __PHYSFS_platformDetectAvailableCDs */
|
||||||
|
|
||||||
|
@ -469,6 +607,16 @@ int __PHYSFS_platformInit(void)
|
||||||
|
|
||||||
int __PHYSFS_platformDeinit(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)
|
if (libUserEnv)
|
||||||
FreeLibrary(libUserEnv);
|
FreeLibrary(libUserEnv);
|
||||||
libUserEnv = NULL;
|
libUserEnv = NULL;
|
||||||
|
|
Loading…
Reference in New Issue