From 58192ba6c224298bfa9804e016831e77051c1be7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 27 Mar 2020 08:10:00 +0100 Subject: [PATCH] Win32 specific: Using SHChangeNotifyRegister to get notifications on removable media insert / eject events. From now on we no more poll for removable media on Windows. Thanks @mjgtp from prusaprinters.org See the following discussion: https://forum.prusaprinters.org/forum/prusaslicer/prusaslicer-trying-to-access-my-floppy-disk-a The final working code sample was taken from Chromium source code, volume_mount_watcher_win.cc --- src/slic3r/GUI/GUI_App.cpp | 37 +++++++++++++++++++++++- src/slic3r/GUI/MainFrame.cpp | 35 ++++++++++++++++++++-- src/slic3r/GUI/MainFrame.hpp | 2 ++ src/slic3r/GUI/RemovableDriveManager.cpp | 10 ++----- 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index dcc0c4417..f5a4a3d92 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -50,8 +50,8 @@ #include "RemovableDriveManager.hpp" #ifdef __WXMSW__ -#include #include +#include #endif // __WXMSW__ #if ENABLE_THUMBNAIL_GENERATOR_DEBUG @@ -158,6 +158,41 @@ static void register_win32_device_notification_event() } return true; }); + + wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. + auto main_frame = dynamic_cast(win); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); + if (plater == nullptr) + // Maybe some other top level window like a dialog or maybe a pop-up menu? + return true; + wchar_t sPath[MAX_PATH]; + if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) { + struct _ITEMIDLIST* pidl = *reinterpret_cast(wParam); + if (! SHGetPathFromIDList(pidl, sPath)) { + BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed"; + return false; + } + } + switch (lParam) { + case SHCNE_MEDIAINSERTED: + { + //printf("SHCNE_MEDIAINSERTED %S\n", sPath); + plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); + break; + } + case SHCNE_MEDIAREMOVED: + { + //printf("SHCNE_MEDIAREMOVED %S\n", sPath); + plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); + break; + } + default: +// printf("Unknown\n"); + break; + } + return true; + }); } #endif // WIN32 diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 60167bd17..69d20fd9f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -33,6 +33,7 @@ #ifdef _WIN32 #include +#include #endif // _WIN32 namespace Slic3r { @@ -127,6 +128,30 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // DEV_BROADCAST_HANDLE NotificationFilter = { 0 }; // NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE); // NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE; + + // Using Win32 Shell API to register for media insert / removal events. + LPITEMIDLIST ppidl; + if (SHGetSpecialFolderLocation(this->GetHWND(), CSIDL_DESKTOP, &ppidl) == NOERROR) { + SHChangeNotifyEntry shCNE; + shCNE.pidl = ppidl; + shCNE.fRecursive = TRUE; + // Returns a positive integer registration identifier (ID). + // Returns zero if out of memory or in response to invalid parameters. + m_ulSHChangeNotifyRegister = SHChangeNotifyRegister(this->GetHWND(), // Hwnd to receive notification + SHCNE_DISKEVENTS, // Event types of interest (sources) + SHCNE_MEDIAINSERTED | SHCNE_MEDIAREMOVED, + //SHCNE_UPDATEITEM, // Events of interest - use SHCNE_ALLEVENTS for all events + WM_USER_MEDIACHANGED, // Notification message to be sent upon the event + 1, // Number of entries in the pfsne array + &shCNE); // Array of SHChangeNotifyEntry structures that + // contain the notifications. This array should + // always be set to one when calling SHChnageNotifyRegister + // or SHChangeNotifyDeregister will not work properly. + assert(m_ulSHChangeNotifyRegister != 0); // Shell notification failed + } else { + // Failed to get desktop location + assert(false); + } #endif // _WIN32 // propagate event @@ -161,8 +186,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S void MainFrame::shutdown() { #ifdef _WIN32 - ::UnregisterDeviceNotification(HDEVNOTIFY(m_hDeviceNotify)); - m_hDeviceNotify = nullptr; + if (m_hDeviceNotify) { + ::UnregisterDeviceNotification(HDEVNOTIFY(m_hDeviceNotify)); + m_hDeviceNotify = nullptr; + } + if (m_ulSHChangeNotifyRegister) { + SHChangeNotifyDeregister(m_ulSHChangeNotifyRegister); + m_ulSHChangeNotifyRegister = 0; + } #endif // _WIN32 if (m_plater) diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 8c8b98090..6038e6d2f 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -144,6 +144,8 @@ public: #ifdef _WIN32 void* m_hDeviceNotify { nullptr }; + uint32_t m_ulSHChangeNotifyRegister { 0 }; + static constexpr int WM_USER_MEDIACHANGED { 0x7FFF }; // WM_USER from 0x0400 to 0x7FFF, picking the last one to not interfere with wxWidgets allocation #endif // _WIN32 }; diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index c0efa47fe..d67ac4a22 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -452,14 +452,8 @@ void RemovableDriveManager::thread_proc() { std::unique_lock lck(m_thread_stop_mutex); #ifdef _WIN32 - // Windows do not send an update on insert / eject of an SD card into an external SD card reader. - // Windows also do not send an update on software eject of a FLASH drive. - // We can likely use the Windows WMI API, but it will be quite time consuming to implement. - // https://www.codeproject.com/Articles/10539/Making-WMI-Queries-In-C - // https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page - // https://docs.microsoft.com/en-us/windows/win32/wmisdk/com-api-for-wmi - // https://docs.microsoft.com/en-us/windows/win32/wmisdk/example--receiving-event-notifications-through-wmi- - m_thread_stop_condition.wait_for(lck, std::chrono::seconds(2), [this]{ return m_stop || m_wakeup; }); + // Reacting to updates by WM_DEVICECHANGE and WM_USER_MEDIACHANGED + m_thread_stop_condition.wait(lck, [this]{ return m_stop || m_wakeup; }); #else m_thread_stop_condition.wait_for(lck, std::chrono::seconds(2), [this]{ return m_stop; }); #endif