Skip to content

Commit

Permalink
Move Windows CD-ROM detection to another thread that uses device noti…
Browse files Browse the repository at this point in the history
…fications.

Fixes blocking when a disc is spinning up, except on initial call.
  • Loading branch information
icculus committed Mar 12, 2012
1 parent 16ff5c8 commit 0ae446d
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 27 deletions.
2 changes: 0 additions & 2 deletions docs/TODO.txt
Expand Up @@ -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 ...
Expand Down
198 changes: 173 additions & 25 deletions src/platform_windows.c
Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
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++)
{
drive_str[0] = ch;
if (GetDriveTypeA(drive_str) == DRIVE_CDROM && mediaInDrive(drive_str))
if (drives & (1 << (i - 'A')))
{
drive_str[0] = (char) i;
cb(data, drive_str);
} /* if */
} /* for */
} /* __PHYSFS_platformDetectAvailableCDs */

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 0ae446d

Please sign in to comment.