Add Hide Empty Optical Drives mod - #5375
Conversation
|
Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review. To get started, comment See the pull request review process for the full details. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Nice idea, and the mod is careful about not touching the device or the drive letter. The main problems are around the shell integration approach and the unload path — several of them can crash or hang Explorer. 1. Explorer window subclasses are never removed → crash after unload. The same applies to the early 2. 3. The Explorer subclass swallows if (GetManagedOpticalLetterFromVolume(lParam, &letter)) {
...
return TRUE; // never reaches DefSubclassProc
}Returning without calling 4. 5. The mod hands the shell a COM object whose vtable lives in the mod image. The much simpler fix is to not create an enumerator at all and hook the shell's own "should this item be shown" callback instead. 6. The fake 7. 8. The unload path can leave the notification thread running. WaitForSingleObject(g_notificationThread, 3000);
CloseHandle(g_notificationThread);If the wait times out, while (!PostThreadMessageW(g_notificationThreadId, WM_QUIT, 0, 0) &&
WaitForSingleObject(g_notificationThread, 50) == WAIT_TIMEOUT) {
}
WaitForSingleObject(g_notificationThread, INFINITE);9. Blocking 10. 11. Shared state is raced between threads. 12. Effects don't disappear immediately on unload. 13. README has no screenshot. The mod has a visible effect, so a before/after screenshot of This PC would help a lot. Only Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
Updated the mod to hide empty optical drives from This PC while keeping the device and drive letter available. Enhanced media detection and notification handling.
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Overall the design is sound — hooking 1. The initial scan and refresh run before the hook is installed, so enabling the mod with This PC open often does nothing. Windhawk only installs the mod's hooks after Result: when the mod is enabled while a This PC window is open (the normal case — that's how a user tries the mod out), the view re-enumerates unfiltered and the empty drive stays visible until the user presses F5 or media state changes. Move the scan/refresh trigger to void Wh_ModAfterInit() {
HWND hwnd = g_notificationWindow.load(std::memory_order_acquire);
if (hwnd) {
PostMessageW(hwnd, kMsgInitialScan, 0, 0);
PostMessageW(hwnd, kMsgRefreshThisPc, 0, 0);
}
}and drop the 2. Blocking device I/O runs on the notification window's message loop.
It also makes unload slow: Keep the message-loop thread purely for dispatch and move the probing off it — e.g. a second worker thread that waits on an event with a timeout (replacing 3. Add a screenshot to the README. The mod has a visible effect, and the README currently has no image. A before/after pair of This PC (drive visible with no disc → drive gone) makes the mod's purpose immediately clear in the Windhawk catalog. Images must be hosted on Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
Refactor and optimize media state handling for optical drives, including improved retry logic and event-driven detection. Update function signatures and improve code readability.
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The Windhawk integration side is in good shape: threads are signalled and joined inside 1. A drive with a disc in it can end up hidden (blank / unmountable media).
if (error == ERROR_NOT_READY || error == ERROR_NO_MEDIA_IN_DRIVE) {
return MediaState::Empty;
}
if (++retryAttempts[index] >= kMaxRetryAttempts) {
...
changed |= SetCachedMediaState(letter, state); // state == Emptyso after ~10 s the drive is hidden with a disc physically inside it. That contradicts the README's "unknown/error state -> show (fail open)" contract, and it means a blank disc can't be found in This PC to burn to. Use a media-presence check that doesn't depend on a filesystem, and keep // Media presence, independent of whether a filesystem can be mounted.
// dwDesiredAccess == 0 -> no admin rights needed, and
// IOCTL_STORAGE_CHECK_VERIFY2 doesn't require read access either.
WCHAR devicePath[] = L"\\\\.\\X:";
devicePath[4] = letter;
HANDLE drive = CreateFileW(devicePath, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, 0, nullptr);
if (drive == INVALID_HANDLE_VALUE) {
return MediaState::Unknown; // fail open
}
DWORD returned = 0;
BOOL present = DeviceIoControl(drive, IOCTL_STORAGE_CHECK_VERIFY2, nullptr, 0,
nullptr, 0, &returned, nullptr);
DWORD error = present ? ERROR_SUCCESS : GetLastError();
CloseHandle(drive);
if (present) {
return MediaState::Present;
}
if (error == ERROR_NOT_READY || error == ERROR_NO_MEDIA_IN_DRIVE) {
return MediaState::Empty;
}
return MediaState::Unknown; // fail open( Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
Refactor code for readability and maintainability, including changes to comments and formatting.
|
/ai-review |
|
This pull request has already had 3 AI reviews in the last 24 hours, which is the limit, so no review was posted this time. Comment |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The hook target and hooking style are right ( 1. A generic
} else if (result == ProbeResult::Empty ||
result == ProbeResult::NotReady) {
changed |= SetCachedMediaState(letter, MediaState::Empty);
}An optical drive that has spun down after idling — or that is busy — answers The rest of the file already treats } else if (result == ProbeResult::Empty) {
changed |= SetCachedMediaState(letter, MediaState::Empty);
} else if (result == ProbeResult::NotReady) {
// Inconclusive - let the bounded grace window resolve it instead of
// settling on Empty after a single probe.
*retryMask |= bit;
*graceRetryMask |= bit;
} else {
changed |= SetCachedMediaState(letter, MediaState::Unknown);
}2.
3. The mod is roughly 1,300 lines for "hide a drive when it has no disc", and the complexity is where the bugs are. The media state is spread across three parallel bitmasks ( This is worth restructuring before merge — it's the part a future maintainer (or you, in six months) has to reason about. A per-drive struct plus one update routine expresses the whole thing: struct DriveState {
MediaState media = MediaState::Unknown;
bool isOptical = false;
int retriesLeft = 0; // 0 = settled
bool grace = false; // NOT_READY/Empty are transient while true
};
DriveState g_drives[26]; // worker-thread-owned; publish media via the atomics
enum class Trigger { Startup, Resume, SettingsChanged, VolumeArrival,
VolumeRemoval, Retry };
static bool UpdateDrive(WCHAR letter, Trigger trigger);Only the 4. A failed
PIDLIST_ABSOLUTE thisPcPidl = AcquireThisPcPidl();
if (!thisPcPidl) {
return 1;
}The thread then never creates the notification window, so no This matters because Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
Refactor handling of ProbeResult for optical drives.
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Overall this is a well-structured mod: it targets only 1. A failed This PC PIDL lookup permanently disables all device handling for the session
PIDLIST_ABSOLUTE thisPcPidl = AcquireThisPcPidl();
if (!thisPcPidl) {
return 1;
}If this ever fails, there is no notification window, so the mod receives no This isn't hypothetical. Suggested fix — create the window unconditionally and resolve the PIDL lazily on first use: static DWORD WINAPI NotificationThreadProc(void*) {
MSG msg = {};
PeekMessageW(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
HWND hwnd = CreateWindowExW(...); // no PIDL dependency
...
}
// In the subclass proc:
case kMsgRefreshThisPc: {
PIDLIST_ABSOLUTE pidl = g_thisPcPidl.load(std::memory_order_acquire);
if (!pidl) {
pidl = AcquireThisPcPidl(); // retried on every refresh
if (pidl) {
g_thisPcPidl.store(pidl, std::memory_order_release);
}
}
NotifyThisPcUpdated(pidl);
return 0;
}This also removes the need to pass the PIDL through the subclass 2. Every case WM_DEVICECHANGE:
if (wParam == DBT_DEVNODES_CHANGED) {
QueueInitialScan();
break;
}
Since a hot-plugged or removed external optical drive always changes the set of logical drives, you can gate the probes on that: static DWORD g_lastLogicalDrives = 0; // worker-thread-local state
...
DWORD logicalDrives = GetLogicalDrives();
bool driveSetChanged = (logicalDrives != g_lastLogicalDrives);
g_lastLogicalDrives = logicalDrives;and in the Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
|
This pull request has already had 3 AI reviews in the last 24 hours, which is the limit, so no review was posted this time. Comment |
|
/ai-review |
|
This pull request has already had 3 AI reviews in the last 24 hours, which is the limit, so no review was posted this time. Comment |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Good foundation overall: no overlap with an existing mod (nothing in 1. Resume / settings rescan hides a drive that actually has media. In the grace path of } else if (result == ProbeResult::Empty ||
result == ProbeResult::NotReady) {
*retryMask |= bit;
*graceRetryMask |= bit;
retryAttempts[index] = 0;
changed |= SetCachedMediaState(letter, MediaState::Empty);
}An optical drive that has spun down (which is exactly what happens across
} else if (result == ProbeResult::Empty) {
*retryMask |= bit;
*graceRetryMask |= bit;
retryAttempts[index] = 0;
changed |= SetCachedMediaState(letter, MediaState::Empty);
} else {
// NotReady / Unknown are inconclusive: keep the cached state and let the
// bounded retry window settle it.
*retryMask |= bit;
*graceRetryMask |= bit;
retryAttempts[index] = 0;
}2. Don't 3. Dead code and a duplicated scan path. Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ready-for-reviewer Hi, I went through the AI review findings and addressed the issues that appeared to be actual user-facing bugs. The remaining findings are mostly edge cases or architectural suggestions rather than functional problems:
The mod has gone through several review iterations and the remaining suggestions would mostly be improvements for a future refactor rather than blockers for this PR. I would appreciate a human review to determine whether any of these remaining points should actually block submission. Thanks. |
Adds a Windhawk mod that hides empty CD/DVD/BD drives from This PC while keeping the drive fully available to Windows.
The drive automatically reappears when media is inserted and is hidden again when the media is removed.
Tested on Windows 10 22H2 (19045).
Changelog
If this pull request updates an existing mod, describe the changes below:
Mod authorship
If this pull request introduces a new mod, please complete the section below.
This mod was created by:
Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.