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();
|
||||
}
|
||||
|
||||
void Window::SetIgnoreMouseEvents(bool ignore) {
|
||||
return window_->SetIgnoreMouseEvents(ignore);
|
||||
void Window::SetIgnoreMouseEvents(bool ignore, mate::Arguments* args) {
|
||||
mate::Dictionary options;
|
||||
bool forward = false;
|
||||
args->GetNext(&options) && options.Get("forward", &forward);
|
||||
return window_->SetIgnoreMouseEvents(ignore, forward);
|
||||
}
|
||||
|
||||
void Window::SetContentProtection(bool enable) {
|
||||
|
|
|
@ -166,7 +166,7 @@ class Window : public mate::TrackableObject<Window>,
|
|||
std::string GetRepresentedFilename();
|
||||
void SetDocumentEdited(bool edited);
|
||||
bool IsDocumentEdited();
|
||||
void SetIgnoreMouseEvents(bool ignore);
|
||||
void SetIgnoreMouseEvents(bool ignore, mate::Arguments* args);
|
||||
void SetContentProtection(bool enable);
|
||||
void SetFocusable(bool focusable);
|
||||
void SetProgressBar(double progress, mate::Arguments* args);
|
||||
|
|
|
@ -143,7 +143,7 @@ class NativeWindow : public base::SupportsUserData,
|
|||
virtual std::string GetRepresentedFilename();
|
||||
virtual void SetDocumentEdited(bool edited);
|
||||
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 SetFocusable(bool focusable);
|
||||
virtual void SetMenu(AtomMenuModel* menu);
|
||||
|
|
|
@ -334,6 +334,11 @@ NativeWindowViews::NativeWindowViews(
|
|||
|
||||
NativeWindowViews::~NativeWindowViews() {
|
||||
window_->RemoveObserver(this);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// Disable mouse forwarding to relinquish resources, should any be held.
|
||||
SetForwardMouseMessages(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeWindowViews::Close() {
|
||||
|
@ -790,7 +795,7 @@ bool NativeWindowViews::HasShadow() {
|
|||
!= wm::ShadowElevation::NONE;
|
||||
}
|
||||
|
||||
void NativeWindowViews::SetIgnoreMouseEvents(bool ignore) {
|
||||
void NativeWindowViews::SetIgnoreMouseEvents(bool ignore, bool forward) {
|
||||
#if defined(OS_WIN)
|
||||
LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE);
|
||||
if (ignore)
|
||||
|
@ -798,6 +803,13 @@ void NativeWindowViews::SetIgnoreMouseEvents(bool ignore) {
|
|||
else
|
||||
ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED);
|
||||
::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)
|
||||
if (ignore) {
|
||||
XRectangle r = {0, 0, 1, 1};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "atom/browser/native_window.h"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -101,7 +102,7 @@ class NativeWindowViews : public NativeWindow,
|
|||
void SetBackgroundColor(const std::string& color_name) override;
|
||||
void SetHasShadow(bool has_shadow) override;
|
||||
bool HasShadow() override;
|
||||
void SetIgnoreMouseEvents(bool ignore) override;
|
||||
void SetIgnoreMouseEvents(bool ignore, bool forward) override;
|
||||
void SetContentProtection(bool enable) override;
|
||||
void SetFocusable(bool focusable) override;
|
||||
void SetMenu(AtomMenuModel* menu_model) override;
|
||||
|
@ -169,6 +170,12 @@ 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);
|
||||
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
|
||||
|
||||
// NativeWindow:
|
||||
|
@ -259,6 +266,12 @@ class NativeWindowViews : public NativeWindow,
|
|||
// The icons of window and taskbar.
|
||||
base::win::ScopedHICON window_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
|
||||
|
||||
// Handles unhandled keyboard messages coming back from the renderer process.
|
||||
|
|
|
@ -80,6 +80,9 @@ bool IsScreenReaderActive() {
|
|||
|
||||
} // namespace
|
||||
|
||||
std::set<NativeWindowViews*> NativeWindowViews::forwarding_windows_;
|
||||
HHOOK NativeWindowViews::mouse_hook_ = NULL;
|
||||
|
||||
bool NativeWindowViews::ExecuteWindowsCommand(int command_id) {
|
||||
std::string command = AppCommandToString(command_id);
|
||||
NotifyWindowExecuteWindowsCommand(command);
|
||||
|
@ -151,6 +154,16 @@ 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 is used when forwarding mouse messages.
|
||||
legacy_window_ = reinterpret_cast<HWND>(l_param);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
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
|
||||
|
|
|
@ -1311,9 +1311,14 @@ Returns `Boolean` - Whether the window is visible on all workspaces.
|
|||
|
||||
**Note:** This API always returns false on Windows.
|
||||
|
||||
#### `win.setIgnoreMouseEvents(ignore)`
|
||||
#### `win.setIgnoreMouseEvents(ignore[, options])`
|
||||
|
||||
* `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.
|
||||
|
||||
|
|
|
@ -103,6 +103,27 @@ let win = new BrowserWindow()
|
|||
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
|
||||
|
||||
By default, the frameless window is non-draggable. Apps need to specify
|
||||
|
|
Loading…
Reference in a new issue