diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 34366e2db5..1ea09c2692 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -787,6 +787,10 @@ void Window::SetAppDetails(const mate::Dictionary& options) { relaunch_command, relaunch_display_name, window_->GetAcceleratedWidget()); } + +void Window::SetForwardMouseMessages(bool forward) { + window_->SetForwardMouseMessages(forward); +} #endif #if defined(TOOLKIT_VIEWS) @@ -1060,6 +1064,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setThumbnailClip", &Window::SetThumbnailClip) .SetMethod("setThumbnailToolTip", &Window::SetThumbnailToolTip) .SetMethod("setAppDetails", &Window::SetAppDetails) + .SetMethod("setForwardMouseMessages", &Window::SetForwardMouseMessages) #endif #if defined(TOOLKIT_VIEWS) .SetMethod("setIcon", &Window::SetIcon) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 22f7e73077..c47cb19694 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -201,6 +201,7 @@ class Window : public mate::TrackableObject, bool SetThumbnailClip(const gfx::Rect& region); bool SetThumbnailToolTip(const std::string& tooltip); void SetAppDetails(const mate::Dictionary& options); + void SetForwardMouseMessages(bool forward); #endif #if defined(TOOLKIT_VIEWS) diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index dd87405789..f8df7ac62e 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -152,6 +152,9 @@ class NativeWindow : public base::SupportsUserData, virtual gfx::NativeView GetNativeView() const = 0; virtual gfx::NativeWindow GetNativeWindow() const = 0; virtual gfx::AcceleratedWidget GetAcceleratedWidget() const = 0; +#if defined(OS_WIN) + virtual void SetForwardMouseMessages(bool forward) = 0; +#endif // Taskbar/Dock APIs. enum ProgressState { diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 94e2f09c96..7990f33c31 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -1064,6 +1064,13 @@ void NativeWindowViews::SetEnabled(bool enable) { #endif } +#if defined(OS_WIN) +void NativeWindowViews::SetForwardMouseMessages(bool forward) { + forwarding_mouse_messages_ = forward; + SetIgnoreMouseEvents(forward); +} +#endif + void NativeWindowViews::OnWidgetActivationChanged( views::Widget* widget, bool active) { if (widget != window_.get()) diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 5d94c2255b..2c2a06702e 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -7,6 +7,7 @@ #include "atom/browser/native_window.h" +#include #include #include @@ -133,6 +134,7 @@ class NativeWindowViews : public NativeWindow, #if defined(OS_WIN) TaskbarHost& taskbar_host() { return taskbar_host_; } + void SetForwardMouseMessages(bool forward) override; #endif private: @@ -169,6 +171,8 @@ class NativeWindowViews : public NativeWindow, bool PreHandleMSG( UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override; void HandleSizeEvent(WPARAM w_param, LPARAM l_param); + static LRESULT CALLBACK SubclassProc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param, UINT_PTR subclass_id, DWORD_PTR ref_data); + static LRESULT CALLBACK MouseHookProc(int n_code, WPARAM w_param, LPARAM l_param); #endif // NativeWindow: @@ -259,6 +263,11 @@ class NativeWindowViews : public NativeWindow, // The icons of window and taskbar. base::win::ScopedHICON window_icon_; base::win::ScopedHICON app_icon_; + + // Handles to legacy windows iterated by the mouse hook + static std::map legacy_window_map_; + static HHOOK mouse_hook_; + bool forwarding_mouse_messages_ = false; #endif // Handles unhandled keyboard messages coming back from the renderer process. diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index abda0d0b02..06b26c3888 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -80,6 +80,9 @@ bool IsScreenReaderActive() { } // namespace +std::map NativeWindowViews::legacy_window_map_; +HHOOK NativeWindowViews::mouse_hook_ = NULL; + bool NativeWindowViews::ExecuteWindowsCommand(int command_id) { std::string command = AppCommandToString(command_id); NotifyWindowExecuteWindowsCommand(command); @@ -151,6 +154,24 @@ bool NativeWindowViews::PreHandleMSG( if (w_param) { NotifyWindowEndSession(); } + return false; + } + case WM_PARENTNOTIFY: { + if (LOWORD(w_param) == WM_CREATE) { + // Because of reasons regarding legacy drivers and stuff, a window that + // matches the client area is created and used internally by Chromium. + // This window is subclassed in order to fix some issues when forwarding + // mouse messages; see comments in |SubclassProc|. If by any chance + // Chromium removes the legacy window in the future it may be fine to + // move the logic to this very switch statement. + HWND legacy_window = reinterpret_cast(l_param); + SetWindowSubclass(legacy_window, SubclassProc, 1, reinterpret_cast(this)); + if (legacy_window_map_.size() == 0) { + mouse_hook_ = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, NULL, 0); + } + legacy_window_map_.insert({ legacy_window, this }); + } + return false; } default: return false; @@ -207,4 +228,69 @@ void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) { } } +LRESULT CALLBACK NativeWindowViews::SubclassProc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param, UINT_PTR subclass_id, DWORD_PTR ref_data) { + NativeWindowViews* window = reinterpret_cast(ref_data); + switch (msg) { + case WM_MOUSELEAVE: { + // When input is forwarded to underlying windows, this message is posted. + // If not handled, it interferes with Chromium logic, causing for example + // mouseleave events to fire. If those events are used to exit forward + // mode, excessive flickering on for example hover items in underlying + // windows can occur due to rapidly entering and leaving forwarding mode. + // By consuming and ignoring the message, we're essentially telling + // Chromium that we have not left the window despite somebody else getting + // the messages. As to why this is catched for the legacy window and not + // the actual browser window is simply that the legacy window somehow + // makes use of these events; posting to the main window didn't work. + if (window->forwarding_mouse_messages_) { + return 0; + } + break; + } + case WM_DESTROY: { + legacy_window_map_.erase(hwnd); + if (legacy_window_map_.size() == 0) { + UnhookWindowsHookEx(mouse_hook_); + mouse_hook_ = NULL; + } + break; + } + } + + return DefSubclassProc(hwnd, msg, w_param, l_param); +} + +LRESULT CALLBACK NativeWindowViews::MouseHookProc(int n_code, WPARAM w_param, LPARAM l_param) { + if (n_code < 0) { + return CallNextHookEx(NULL, n_code, w_param, l_param); + } + + // Post a WM_MOUSEMOVE message for those windows whose client area contains + // the cursor and are set to forward messages since they are in a state where + // they would otherwise ignore all mouse input. + if (w_param == WM_MOUSEMOVE) { + for (auto legacy : legacy_window_map_) { + if (!legacy.second->forwarding_mouse_messages_) { + continue; + } + + // At first I considered enumerating windows to check whether the cursor + // was directly above the window, but since nothing bad seems to happen + // if we post the message even if some other window occludes it I have + // just left it as is. + RECT client_rect; + GetClientRect(legacy.first, &client_rect); + POINT p = ((MSLLHOOKSTRUCT*)l_param)->pt; + ScreenToClient(legacy.first, &p); + if (PtInRect(&client_rect, p)) { + WPARAM w = 0; // No virtual keys pressed for our purposes + LPARAM l = MAKELPARAM(p.x, p.y); + PostMessage(legacy.first, WM_MOUSEMOVE, w, l); + } + } + } + + return CallNextHookEx(NULL, n_code, w_param, l_param); +} + } // namespace atom