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
|
||||
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 ...
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue