diff --git a/atom/browser/api/atom_api_top_level_window.cc b/atom/browser/api/atom_api_top_level_window.cc index 879a15667a82..10011e747d94 100644 --- a/atom/browser/api/atom_api_top_level_window.cc +++ b/atom/browser/api/atom_api_top_level_window.cc @@ -548,11 +548,9 @@ std::vector<int> TopLevelWindow::GetPosition() { return result; } -#if defined(OS_WIN) || defined(OS_MACOSX) void TopLevelWindow::MoveTop() { window_->MoveTop(); } -#endif void TopLevelWindow::SetTitle(const std::string& title) { window_->SetTitle(title); @@ -1067,9 +1065,7 @@ void TopLevelWindow::BuildPrototype(v8::Isolate* isolate, .SetMethod("setResizable", &TopLevelWindow::SetResizable) .SetMethod("isResizable", &TopLevelWindow::IsResizable) .SetMethod("setMovable", &TopLevelWindow::SetMovable) -#if defined(OS_WIN) || defined(OS_MACOSX) .SetMethod("moveTop", &TopLevelWindow::MoveTop) -#endif .SetMethod("isMovable", &TopLevelWindow::IsMovable) .SetMethod("setMinimizable", &TopLevelWindow::SetMinimizable) .SetMethod("isMinimizable", &TopLevelWindow::IsMinimizable) diff --git a/atom/browser/api/atom_api_top_level_window.h b/atom/browser/api/atom_api_top_level_window.h index 21e29181713b..9366a9177234 100644 --- a/atom/browser/api/atom_api_top_level_window.h +++ b/atom/browser/api/atom_api_top_level_window.h @@ -126,9 +126,7 @@ class TopLevelWindow : public mate::TrackableObject<TopLevelWindow>, void SetResizable(bool resizable); bool IsResizable(); void SetMovable(bool movable); -#if defined(OS_WIN) || defined(OS_MACOSX) void MoveTop(); -#endif bool IsMovable(); void SetMinimizable(bool minimizable); bool IsMinimizable(); diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 1fa2cac35c9b..7dee6b2443f9 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -113,9 +113,7 @@ class NativeWindow : public base::SupportsUserData, virtual double GetSheetOffsetX(); virtual double GetSheetOffsetY(); virtual void SetResizable(bool resizable) = 0; -#if defined(OS_WIN) || defined(OS_MACOSX) virtual void MoveTop() = 0; -#endif virtual bool IsResizable() = 0; virtual void SetMovable(bool movable) = 0; virtual bool IsMovable() = 0; diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 1a412c5a5c56..5b9d2ebcf19b 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -657,15 +657,19 @@ void NativeWindowViews::SetResizable(bool resizable) { resizable_ = resizable; } -#if defined(OS_WIN) void NativeWindowViews::MoveTop() { + // TODO(julien.isorce): fix chromium in order to use existing + // widget()->StackAtTop(). +#if defined(OS_WIN) gfx::Point pos = GetPosition(); gfx::Size size = GetSize(); ::SetWindowPos(GetAcceleratedWidget(), HWND_TOP, pos.x(), pos.y(), size.width(), size.height(), SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); -} +#elif defined(USE_X11) + atom::MoveWindowToForeground(GetAcceleratedWidget()); #endif +} bool NativeWindowViews::IsResizable() { #if defined(OS_WIN) diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 0d7a9850ea5c..53028b4bff12 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -74,9 +74,7 @@ class NativeWindowViews : public NativeWindow, void SetContentSizeConstraints( const extensions::SizeConstraints& size_constraints) override; void SetResizable(bool resizable) override; -#if defined(OS_WIN) void MoveTop() override; -#endif bool IsResizable() override; void SetMovable(bool movable) override; bool IsMovable() override; diff --git a/atom/browser/ui/x/x_window_utils.cc b/atom/browser/ui/x/x_window_utils.cc index 64aa8596a437..410c54e34a6e 100644 --- a/atom/browser/ui/x/x_window_utils.cc +++ b/atom/browser/ui/x/x_window_utils.cc @@ -88,4 +88,25 @@ bool ShouldUseGlobalMenuBar() { return false; } +void MoveWindowToForeground(::Window xwindow) { + XDisplay* xdisplay = gfx::GetXDisplay(); + XEvent xclient; + memset(&xclient, 0, sizeof(xclient)); + + xclient.type = ClientMessage; + xclient.xclient.display = xdisplay; + xclient.xclient.window = xwindow; + xclient.xclient.message_type = GetAtom("_NET_RESTACK_WINDOW"); + xclient.xclient.format = 32; + xclient.xclient.data.l[0] = 2; + xclient.xclient.data.l[1] = 0; + xclient.xclient.data.l[2] = Above; + xclient.xclient.data.l[3] = 0; + xclient.xclient.data.l[4] = 0; + + XSendEvent(xdisplay, DefaultRootWindow(xdisplay), x11::False, + SubstructureRedirectMask | SubstructureNotifyMask, &xclient); + XFlush(xdisplay); +} + } // namespace atom diff --git a/atom/browser/ui/x/x_window_utils.h b/atom/browser/ui/x/x_window_utils.h index 5888a2b25663..a2e3315d0fc0 100644 --- a/atom/browser/ui/x/x_window_utils.h +++ b/atom/browser/ui/x/x_window_utils.h @@ -23,6 +23,9 @@ void SetWindowType(::Window xwindow, const std::string& type); // Returns true if the bus name "com.canonical.AppMenu.Registrar" is available. bool ShouldUseGlobalMenuBar(); +// Bring the given window to the front and give it the focus. +void MoveWindowToForeground(::Window xwindow); + } // namespace atom #endif // ATOM_BROWSER_UI_X_X_WINDOW_UTILS_H_ diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 4ed4e611b9d2..294ecba017d0 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1089,7 +1089,7 @@ can not be focused on. Returns `Boolean` - Whether the window is always on top of other windows. -#### `win.moveTop()` _macOS_ _Windows_ +#### `win.moveTop()` Moves window to top(z-order) regardless of focus diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 575e6c7dcfa1..4c9ef7ca67f4 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -494,6 +494,48 @@ describe('BrowserWindow module', () => { }) }) + describe('BrowserWindow.moveTop()', () => { + it('should not steal focus', async () => { + const posDelta = 50 + const wShownInactive = emittedOnce(w, 'show') + w.showInactive() + await wShownInactive + assert(!w.isFocused()) + + const otherWindow = new BrowserWindow({ show: false, title: 'otherWindow' }) + const otherWindowShown = emittedOnce(otherWindow, 'show') + otherWindow.loadURL('data:text/html,<html><body background-color: rgba(255,255,255,0)></body></html>') + otherWindow.show() + await otherWindowShown + assert(otherWindow.isFocused()) + + w.moveTop() + const wPos = w.getPosition() + const wMoving = emittedOnce(w, 'move') + w.setPosition(wPos[0] + posDelta, wPos[1] + posDelta) + await wMoving + assert(!w.isFocused()) + assert(otherWindow.isFocused()) + + const wFocused = emittedOnce(w, 'focus') + w.focus() + await wFocused + assert(w.isFocused()) + + otherWindow.moveTop() + const otherWindowPos = otherWindow.getPosition() + const otherWindowMoving = emittedOnce(otherWindow, 'move') + otherWindow.setPosition(otherWindowPos[0] + posDelta, otherWindowPos[1] + posDelta) + await otherWindowMoving + assert(!otherWindow.isFocused()) + assert(w.isFocused()) + + await closeWindow(otherWindow, { assertSingleWindow: false }).then(() => { + assert.strictEqual(BrowserWindow.getAllWindows().length, 2) // Test window + w + }) + }) + }) + describe('BrowserWindow.capturePage(rect)', (done) => { it('returns a Promise with a Buffer', async () => { const image = await w.capturePage({