Merge pull request #10183 from andens/mouse_forward
Mouse forward functionality on Windows
This commit is contained in:
commit
f908678e8e
8 changed files with 156 additions and 7 deletions
|
@ -651,8 +651,11 @@ bool Window::IsDocumentEdited() {
|
||||||
return window_->IsDocumentEdited();
|
return window_->IsDocumentEdited();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::SetIgnoreMouseEvents(bool ignore) {
|
void Window::SetIgnoreMouseEvents(bool ignore, mate::Arguments* args) {
|
||||||
return window_->SetIgnoreMouseEvents(ignore);
|
mate::Dictionary options;
|
||||||
|
bool forward = false;
|
||||||
|
args->GetNext(&options) && options.Get("forward", &forward);
|
||||||
|
return window_->SetIgnoreMouseEvents(ignore, forward);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::SetContentProtection(bool enable) {
|
void Window::SetContentProtection(bool enable) {
|
||||||
|
|
|
@ -166,7 +166,7 @@ class Window : public mate::TrackableObject<Window>,
|
||||||
std::string GetRepresentedFilename();
|
std::string GetRepresentedFilename();
|
||||||
void SetDocumentEdited(bool edited);
|
void SetDocumentEdited(bool edited);
|
||||||
bool IsDocumentEdited();
|
bool IsDocumentEdited();
|
||||||
void SetIgnoreMouseEvents(bool ignore);
|
void SetIgnoreMouseEvents(bool ignore, mate::Arguments* args);
|
||||||
void SetContentProtection(bool enable);
|
void SetContentProtection(bool enable);
|
||||||
void SetFocusable(bool focusable);
|
void SetFocusable(bool focusable);
|
||||||
void SetProgressBar(double progress, mate::Arguments* args);
|
void SetProgressBar(double progress, mate::Arguments* args);
|
||||||
|
|
|
@ -143,7 +143,7 @@ class NativeWindow : public base::SupportsUserData,
|
||||||
virtual std::string GetRepresentedFilename();
|
virtual std::string GetRepresentedFilename();
|
||||||
virtual void SetDocumentEdited(bool edited);
|
virtual void SetDocumentEdited(bool edited);
|
||||||
virtual bool IsDocumentEdited();
|
virtual bool IsDocumentEdited();
|
||||||
virtual void SetIgnoreMouseEvents(bool ignore) = 0;
|
virtual void SetIgnoreMouseEvents(bool ignore, bool forward) = 0;
|
||||||
virtual void SetContentProtection(bool enable) = 0;
|
virtual void SetContentProtection(bool enable) = 0;
|
||||||
virtual void SetFocusable(bool focusable);
|
virtual void SetFocusable(bool focusable);
|
||||||
virtual void SetMenu(AtomMenuModel* menu);
|
virtual void SetMenu(AtomMenuModel* menu);
|
||||||
|
|
|
@ -334,6 +334,11 @@ NativeWindowViews::NativeWindowViews(
|
||||||
|
|
||||||
NativeWindowViews::~NativeWindowViews() {
|
NativeWindowViews::~NativeWindowViews() {
|
||||||
window_->RemoveObserver(this);
|
window_->RemoveObserver(this);
|
||||||
|
|
||||||
|
#if defined(OS_WIN)
|
||||||
|
// Disable mouse forwarding to relinquish resources, should any be held.
|
||||||
|
SetForwardMouseMessages(false);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void NativeWindowViews::Close() {
|
void NativeWindowViews::Close() {
|
||||||
|
@ -790,7 +795,7 @@ bool NativeWindowViews::HasShadow() {
|
||||||
!= wm::ShadowElevation::NONE;
|
!= wm::ShadowElevation::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NativeWindowViews::SetIgnoreMouseEvents(bool ignore) {
|
void NativeWindowViews::SetIgnoreMouseEvents(bool ignore, bool forward) {
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE);
|
LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE);
|
||||||
if (ignore)
|
if (ignore)
|
||||||
|
@ -798,6 +803,13 @@ void NativeWindowViews::SetIgnoreMouseEvents(bool ignore) {
|
||||||
else
|
else
|
||||||
ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED);
|
ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED);
|
||||||
::SetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE, ex_style);
|
::SetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE, ex_style);
|
||||||
|
|
||||||
|
// Forwarding is always disabled when not ignoring mouse messages.
|
||||||
|
if (!ignore) {
|
||||||
|
SetForwardMouseMessages(false);
|
||||||
|
} else {
|
||||||
|
SetForwardMouseMessages(forward);
|
||||||
|
}
|
||||||
#elif defined(USE_X11)
|
#elif defined(USE_X11)
|
||||||
if (ignore) {
|
if (ignore) {
|
||||||
XRectangle r = {0, 0, 1, 1};
|
XRectangle r = {0, 0, 1, 1};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "atom/browser/native_window.h"
|
#include "atom/browser/native_window.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ class NativeWindowViews : public NativeWindow,
|
||||||
void SetBackgroundColor(const std::string& color_name) override;
|
void SetBackgroundColor(const std::string& color_name) override;
|
||||||
void SetHasShadow(bool has_shadow) override;
|
void SetHasShadow(bool has_shadow) override;
|
||||||
bool HasShadow() override;
|
bool HasShadow() override;
|
||||||
void SetIgnoreMouseEvents(bool ignore) override;
|
void SetIgnoreMouseEvents(bool ignore, bool forward) override;
|
||||||
void SetContentProtection(bool enable) override;
|
void SetContentProtection(bool enable) override;
|
||||||
void SetFocusable(bool focusable) override;
|
void SetFocusable(bool focusable) override;
|
||||||
void SetMenu(AtomMenuModel* menu_model) override;
|
void SetMenu(AtomMenuModel* menu_model) override;
|
||||||
|
@ -169,6 +170,12 @@ class NativeWindowViews : public NativeWindow,
|
||||||
bool PreHandleMSG(
|
bool PreHandleMSG(
|
||||||
UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override;
|
UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override;
|
||||||
void HandleSizeEvent(WPARAM w_param, LPARAM l_param);
|
void HandleSizeEvent(WPARAM w_param, LPARAM l_param);
|
||||||
|
void SetForwardMouseMessages(bool forward);
|
||||||
|
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
|
#endif
|
||||||
|
|
||||||
// NativeWindow:
|
// NativeWindow:
|
||||||
|
@ -259,6 +266,12 @@ class NativeWindowViews : public NativeWindow,
|
||||||
// The icons of window and taskbar.
|
// The icons of window and taskbar.
|
||||||
base::win::ScopedHICON window_icon_;
|
base::win::ScopedHICON window_icon_;
|
||||||
base::win::ScopedHICON app_icon_;
|
base::win::ScopedHICON app_icon_;
|
||||||
|
|
||||||
|
// The set of windows currently forwarding mouse messages.
|
||||||
|
static std::set<NativeWindowViews*> forwarding_windows_;
|
||||||
|
static HHOOK mouse_hook_;
|
||||||
|
bool forwarding_mouse_messages_ = false;
|
||||||
|
HWND legacy_window_ = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Handles unhandled keyboard messages coming back from the renderer process.
|
// Handles unhandled keyboard messages coming back from the renderer process.
|
||||||
|
|
|
@ -80,6 +80,9 @@ bool IsScreenReaderActive() {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
std::set<NativeWindowViews*> NativeWindowViews::forwarding_windows_;
|
||||||
|
HHOOK NativeWindowViews::mouse_hook_ = NULL;
|
||||||
|
|
||||||
bool NativeWindowViews::ExecuteWindowsCommand(int command_id) {
|
bool NativeWindowViews::ExecuteWindowsCommand(int command_id) {
|
||||||
std::string command = AppCommandToString(command_id);
|
std::string command = AppCommandToString(command_id);
|
||||||
NotifyWindowExecuteWindowsCommand(command);
|
NotifyWindowExecuteWindowsCommand(command);
|
||||||
|
@ -151,6 +154,16 @@ bool NativeWindowViews::PreHandleMSG(
|
||||||
if (w_param) {
|
if (w_param) {
|
||||||
NotifyWindowEndSession();
|
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 is used when forwarding mouse messages.
|
||||||
|
legacy_window_ = reinterpret_cast<HWND>(l_param);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
@ -207,4 +220,86 @@ void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NativeWindowViews::SetForwardMouseMessages(bool forward) {
|
||||||
|
if (forward && !forwarding_mouse_messages_) {
|
||||||
|
forwarding_mouse_messages_ = true;
|
||||||
|
forwarding_windows_.insert(this);
|
||||||
|
|
||||||
|
// Subclassing is used to fix some issues when forwarding mouse messages;
|
||||||
|
// see comments in |SubclassProc|.
|
||||||
|
SetWindowSubclass(
|
||||||
|
legacy_window_, SubclassProc, 1, reinterpret_cast<DWORD_PTR>(this));
|
||||||
|
|
||||||
|
if (!mouse_hook_) {
|
||||||
|
mouse_hook_ = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, NULL, 0);
|
||||||
|
}
|
||||||
|
} else if (!forward && forwarding_mouse_messages_) {
|
||||||
|
forwarding_mouse_messages_ = false;
|
||||||
|
forwarding_windows_.erase(this);
|
||||||
|
|
||||||
|
RemoveWindowSubclass(legacy_window_, SubclassProc, 1);
|
||||||
|
|
||||||
|
if (forwarding_windows_.size() == 0) {
|
||||||
|
UnhookWindowsHookEx(mouse_hook_);
|
||||||
|
mouse_hook_ = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<NativeWindowViews*>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 since they are in a state where they would otherwise ignore all
|
||||||
|
// mouse input.
|
||||||
|
if (w_param == WM_MOUSEMOVE) {
|
||||||
|
for (auto window : forwarding_windows_) {
|
||||||
|
// 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(window->legacy_window_, &client_rect);
|
||||||
|
POINT p = reinterpret_cast<MSLLHOOKSTRUCT*>(l_param)->pt;
|
||||||
|
ScreenToClient(window->legacy_window_, &p);
|
||||||
|
if (PtInRect(&client_rect, p)) {
|
||||||
|
WPARAM w = 0; // No virtual keys pressed for our purposes
|
||||||
|
LPARAM l = MAKELPARAM(p.x, p.y);
|
||||||
|
PostMessage(window->legacy_window_, WM_MOUSEMOVE, w, l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CallNextHookEx(NULL, n_code, w_param, l_param);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace atom
|
} // namespace atom
|
||||||
|
|
|
@ -1311,9 +1311,14 @@ Returns `Boolean` - Whether the window is visible on all workspaces.
|
||||||
|
|
||||||
**Note:** This API always returns false on Windows.
|
**Note:** This API always returns false on Windows.
|
||||||
|
|
||||||
#### `win.setIgnoreMouseEvents(ignore)`
|
#### `win.setIgnoreMouseEvents(ignore[, options])`
|
||||||
|
|
||||||
* `ignore` Boolean
|
* `ignore` Boolean
|
||||||
|
* `options` Object (optional)
|
||||||
|
* `forward` Boolean (optional) _Windows_ - If true, forwards mouse move
|
||||||
|
messages to Chromium, enabling mouse related events such as `mouseleave`.
|
||||||
|
Only used when `ignore` is true. If `ignore` is false, forwarding is always
|
||||||
|
disabled regardless of this value.
|
||||||
|
|
||||||
Makes the window ignore all mouse events.
|
Makes the window ignore all mouse events.
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,27 @@ let win = new BrowserWindow()
|
||||||
win.setIgnoreMouseEvents(true)
|
win.setIgnoreMouseEvents(true)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Forwarding
|
||||||
|
|
||||||
|
Ignoring mouse messages makes the web page oblivious to mouse movement, meaning
|
||||||
|
that mouse movement events will not be emitted. On Windows operating systems an
|
||||||
|
optional parameter can be used to forward mouse move messages to the web page,
|
||||||
|
allowing events such as `mouseleave` to be emitted:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let win = require('electron').remote.getCurrentWindow()
|
||||||
|
let el = document.getElementById('clickThroughElement')
|
||||||
|
el.addEventListener('mouseenter', () => {
|
||||||
|
win.setIgnoreMouseEvents(true, {forward: true})
|
||||||
|
})
|
||||||
|
el.addEventListener('mouseleave', () => {
|
||||||
|
win.setIgnoreMouseEvents(false)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This makes the web page click-through when over `el`, and returns to normal
|
||||||
|
outside it.
|
||||||
|
|
||||||
## Draggable region
|
## Draggable region
|
||||||
|
|
||||||
By default, the frameless window is non-draggable. Apps need to specify
|
By default, the frameless window is non-draggable. Apps need to specify
|
||||||
|
|
Loading…
Reference in a new issue