diff --git a/atom.gyp b/atom.gyp index 9805db874f80..c0afe970d716 100644 --- a/atom.gyp +++ b/atom.gyp @@ -179,6 +179,8 @@ 'atom/browser/ui/win/notify_icon_host.h', 'atom/browser/ui/win/notify_icon.cc', 'atom/browser/ui/win/notify_icon.h', + 'atom/browser/ui/x/window_state_watcher.cc', + 'atom/browser/ui/x/window_state_watcher.h', 'atom/browser/ui/x/x_window_utils.cc', 'atom/browser/ui/x/x_window_utils.h', 'atom/browser/web_view/web_view_manager.cc', diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 49ba2c8df7b2..b65bcbcbb57f 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -112,6 +112,30 @@ void Window::OnWindowFocus() { Emit("focus"); } +void Window::OnWindowMaximize() { + Emit("maximize"); +} + +void Window::OnWindowUnmaximize() { + Emit("unmaximize"); +} + +void Window::OnWindowMinimize() { + Emit("minimize"); +} + +void Window::OnWindowRestore() { + Emit("restore"); +} + +void Window::OnWindowEnterFullScreen() { + Emit("enter-full-screen"); +} + +void Window::OnWindowLeaveFullScreen() { + Emit("leave-full-screen"); +} + void Window::OnRendererUnresponsive() { Emit("unresponsive"); } @@ -193,8 +217,8 @@ bool Window::IsMinimized() { return window_->IsMinimized(); } -void Window::SetFullscreen(bool fullscreen) { - window_->SetFullscreen(fullscreen); +void Window::SetFullScreen(bool fullscreen) { + window_->SetFullScreen(fullscreen); } bool Window::IsFullscreen() { @@ -422,7 +446,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("minimize", &Window::Minimize) .SetMethod("restore", &Window::Restore) .SetMethod("isMinimized", &Window::IsMinimized) - .SetMethod("setFullScreen", &Window::SetFullscreen) + .SetMethod("setFullScreen", &Window::SetFullScreen) .SetMethod("isFullScreen", &Window::IsFullscreen) .SetMethod("getSize", &Window::GetSize) .SetMethod("setSize", &Window::SetSize) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 370634f13105..c5a7e73320b6 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -43,7 +43,7 @@ class Window : public mate::EventEmitter, explicit Window(const mate::Dictionary& options); virtual ~Window(); - // Implementations of NativeWindowObserver: + // NativeWindowObserver: void OnPageTitleUpdated(bool* prevent_default, const std::string& title) override; void WillCreatePopupWindow(const base::string16& frame_name, @@ -54,6 +54,12 @@ class Window : public mate::EventEmitter, void OnWindowClosed() override; void OnWindowBlur() override; void OnWindowFocus() override; + void OnWindowMaximize() override; + void OnWindowUnmaximize() override; + void OnWindowMinimize() override; + void OnWindowRestore() override; + void OnWindowEnterFullScreen() override; + void OnWindowLeaveFullScreen() override; void OnRendererUnresponsive() override; void OnRendererResponsive() override; @@ -74,7 +80,7 @@ class Window : public mate::EventEmitter, void Minimize(); void Restore(); bool IsMinimized(); - void SetFullscreen(bool fullscreen); + void SetFullScreen(bool fullscreen); bool IsFullscreen(); void SetSize(int width, int height); std::vector GetSize(); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 26a8559445e3..1aa77a478380 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -204,7 +204,7 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { } bool fullscreen; if (options.Get(switches::kFullscreen, &fullscreen) && fullscreen) { - SetFullscreen(true); + SetFullScreen(true); } bool skip; if (options.Get(switches::kSkipTaskbar, &skip) && skip) { @@ -466,6 +466,32 @@ void NativeWindow::NotifyWindowFocus() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowFocus()); } +void NativeWindow::NotifyWindowMaximize() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowMaximize()); +} + +void NativeWindow::NotifyWindowUnmaximize() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowUnmaximize()); +} + +void NativeWindow::NotifyWindowMinimize() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowMinimize()); +} + +void NativeWindow::NotifyWindowRestore() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowRestore()); +} + +void NativeWindow::NotifyWindowEnterFullScreen() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowEnterFullScreen()); +} + +void NativeWindow::NotifyWindowLeaveFullScreen() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowLeaveFullScreen()); +} + bool NativeWindow::ShouldCreateWebContents( content::WebContents* web_contents, int route_id, diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index a434d28175ad..4c9f84bd0c39 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -111,7 +111,7 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, virtual void Minimize() = 0; virtual void Restore() = 0; virtual bool IsMinimized() = 0; - virtual void SetFullscreen(bool fullscreen) = 0; + virtual void SetFullScreen(bool fullscreen) = 0; virtual bool IsFullscreen() = 0; virtual void SetSize(const gfx::Size& size) = 0; virtual gfx::Size GetSize() = 0; @@ -192,6 +192,12 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, void NotifyWindowClosed(); void NotifyWindowBlur(); void NotifyWindowFocus(); + void NotifyWindowMaximize(); + void NotifyWindowUnmaximize(); + void NotifyWindowMinimize(); + void NotifyWindowRestore(); + void NotifyWindowEnterFullScreen(); + void NotifyWindowLeaveFullScreen(); void AddObserver(NativeWindowObserver* obs) { observers_.AddObserver(obs); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index bcdf62f6fc40..3170239ed866 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -37,7 +37,7 @@ class NativeWindowMac : public NativeWindow { virtual void Minimize() OVERRIDE; virtual void Restore() OVERRIDE; virtual bool IsMinimized() OVERRIDE; - virtual void SetFullscreen(bool fullscreen) OVERRIDE; + virtual void SetFullScreen(bool fullscreen) OVERRIDE; virtual bool IsFullscreen() OVERRIDE; virtual void SetSize(const gfx::Size& size) OVERRIDE; virtual gfx::Size GetSize() OVERRIDE; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 18827e91bccb..131e2096d42d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -81,11 +81,36 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; shell_->ClipWebView(); } +- (void)windowDidMiniaturize:(NSNotification*)notification { + shell_->NotifyWindowMinimize(); +} + +- (void)windowDidDeminiaturize:(NSNotification*)notification { + shell_->NotifyWindowRestore(); +} + +- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame { + // Cocoa doen't have concept of maximize/unmaximize, so wee need to emulate + // them by calculating size change when zooming. + if (newFrame.size.width < [window frame].size.width || + newFrame.size.height < [window frame].size.height) + shell_->NotifyWindowUnmaximize(); + else + shell_->NotifyWindowMaximize(); + return YES; +} + +- (void)windowDidEnterFullScreen:(NSNotification*)notification { + shell_->NotifyWindowEnterFullScreen(); +} + - (void)windowDidExitFullScreen:(NSNotification*)notification { if (!shell_->has_frame()) { NSWindow* window = shell_->GetNativeWindow(); [[window standardWindowButton:NSWindowFullScreenButton] setHidden:YES]; } + + shell_->NotifyWindowLeaveFullScreen(); } - (void)windowWillClose:(NSNotification*)notification { @@ -375,7 +400,7 @@ bool NativeWindowMac::IsMinimized() { return [window_ isMiniaturized]; } -void NativeWindowMac::SetFullscreen(bool fullscreen) { +void NativeWindowMac::SetFullScreen(bool fullscreen) { if (fullscreen == IsFullscreen()) return; @@ -520,10 +545,10 @@ void NativeWindowMac::SetKiosk(bool kiosk) { NSApplicationPresentationDisableHideApplication; [NSApp setPresentationOptions:options]; is_kiosk_ = true; - SetFullscreen(true); + SetFullScreen(true); } else if (!kiosk && is_kiosk_) { is_kiosk_ = false; - SetFullscreen(false); + SetFullScreen(false); [NSApp setPresentationOptions:kiosk_options_]; } } diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 3d3240585f4d..e83018087524 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -39,6 +39,14 @@ class NativeWindowObserver { // Called when window gains focus. virtual void OnWindowFocus() {} + // Called when window state changed. + virtual void OnWindowMaximize() {} + virtual void OnWindowUnmaximize() {} + virtual void OnWindowMinimize() {} + virtual void OnWindowRestore() {} + virtual void OnWindowEnterFullScreen() {} + virtual void OnWindowLeaveFullScreen() {} + // Called when renderer is hung. virtual void OnRendererUnresponsive() {} diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 05a72fbe5596..9ee492aaa64f 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -32,6 +32,7 @@ #include "atom/browser/browser.h" #include "atom/browser/ui/views/global_menu_bar_x11.h" #include "atom/browser/ui/views/frameless_view.h" +#include "atom/browser/ui/x/window_state_watcher.h" #include "atom/browser/ui/x/x_window_utils.h" #include "base/environment.h" #include "base/nix/xdg_util.h" @@ -144,6 +145,9 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, menu_bar_autohide_(false), menu_bar_visible_(false), menu_bar_alt_pressed_(false), +#if defined(OS_WIN) + is_minimized_(false), +#endif keyboard_event_handler_(new views::UnhandledKeyboardEventHandler), use_content_size_(false), resizable_(true) { @@ -189,6 +193,9 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, window_->Init(params); #if defined(USE_X11) + // Start monitoring window states. + window_state_watcher_.reset(new WindowStateWatcher(this)); + // Set _GTK_THEME_VARIANT to dark if we have "dark-theme" option set. bool use_dark_theme = false; if (options.Get(switches::kDarkTheme, &use_dark_theme) && use_dark_theme) { @@ -311,8 +318,15 @@ bool NativeWindowViews::IsMinimized() { return window_->IsMinimized(); } -void NativeWindowViews::SetFullscreen(bool fullscreen) { +void NativeWindowViews::SetFullScreen(bool fullscreen) { window_->SetFullscreen(fullscreen); +#if defined(OS_WIN) + // There is no native fullscreen state on Windows. + if (fullscreen) + NotifyWindowEnterFullScreen(); + else + NotifyWindowLeaveFullScreen(); +#endif } bool NativeWindowViews::IsFullscreen() { @@ -484,7 +498,7 @@ void NativeWindowViews::SetSkipTaskbar(bool skip) { } void NativeWindowViews::SetKiosk(bool kiosk) { - SetFullscreen(kiosk); + SetFullScreen(kiosk); } bool NativeWindowViews::IsKiosk() { @@ -716,6 +730,27 @@ views::NonClientFrameView* NativeWindowViews::CreateNonClientFrameView( #endif } +#if defined(OS_WIN) +bool NativeWindowViews::ExecuteWindowsCommand(int command_id) { + // Windows uses the 4 lower order bits of |command_id| for type-specific + // information so we must exclude this when comparing. + static const int sc_mask = 0xFFF0; + if ((command_id & sc_mask) == SC_MINIMIZE) { + NotifyWindowMinimize(); + is_minimized_ = true; + } else if ((command_id & sc_mask) == SC_RESTORE) { + if (is_minimized_) + NotifyWindowRestore(); + else + NotifyWindowUnmaximize(); + is_minimized_ = false; + } else if ((command_id & sc_mask) == SC_MAXIMIZE) { + NotifyWindowMaximize(); + } + return false; +} +#endif + gfx::ImageSkia NativeWindowViews::GetDevToolsWindowIcon() { return GetWindowAppIcon(); } diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 97baa2a4630b..4f641b463139 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -22,6 +22,7 @@ namespace atom { class GlobalMenuBarX11; class MenuBar; +class WindowStateWatcher; class NativeWindowViews : public NativeWindow, public views::WidgetDelegateView, @@ -47,7 +48,7 @@ class NativeWindowViews : public NativeWindow, void Minimize() override; void Restore() override; bool IsMinimized() override; - void SetFullscreen(bool fullscreen) override; + void SetFullScreen(bool fullscreen) override; bool IsFullscreen() override; void SetSize(const gfx::Size& size) override; gfx::Size GetSize() override; @@ -110,6 +111,9 @@ class NativeWindowViews : public NativeWindow, views::ClientView* CreateClientView(views::Widget* widget) override; views::NonClientFrameView* CreateNonClientFrameView( views::Widget* widget) override; +#if defined(OS_WIN) + bool ExecuteWindowsCommand(int command_id) override; +#endif // brightray::InspectableWebContentsDelegate: gfx::ImageSkia GetDevToolsWindowIcon() override; @@ -144,6 +148,13 @@ class NativeWindowViews : public NativeWindow, #if defined(USE_X11) scoped_ptr global_menu_bar_; + + // Handles window state events. + scoped_ptr window_state_watcher_; +#elif defined(OS_WIN) + // Records window was whether restored from minimized state or maximized + // state. + bool is_minimized_; #endif // Handles unhandled keyboard messages coming back from the renderer process. diff --git a/atom/browser/ui/x/window_state_watcher.cc b/atom/browser/ui/x/window_state_watcher.cc new file mode 100644 index 000000000000..e1b2716b868f --- /dev/null +++ b/atom/browser/ui/x/window_state_watcher.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/x/window_state_watcher.h" + +#include + +#include "ui/events/platform/platform_event_source.h" + +namespace atom { + +namespace { + +const char* kAtomsToCache[] = { + "_NET_WM_STATE", + NULL, +}; + +} // namespace + +WindowStateWatcher::WindowStateWatcher(NativeWindowViews* window) + : window_(window), + widget_(window->GetAcceleratedWidget()), + atom_cache_(gfx::GetXDisplay(), kAtomsToCache), + was_minimized_(false), + was_maximized_(false) { + ui::PlatformEventSource::GetInstance()->AddPlatformEventObserver(this); +} + +WindowStateWatcher::~WindowStateWatcher() { + ui::PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this); +} + +void WindowStateWatcher::WillProcessEvent(const ui::PlatformEvent& event) { + if (IsWindowStateEvent(event)) { + was_minimized_ = window_->IsMinimized(); + was_maximized_ = window_->IsMaximized(); + } +} + +void WindowStateWatcher::DidProcessEvent(const ui::PlatformEvent& event) { + if (IsWindowStateEvent(event)) { + bool is_minimized = window_->IsMinimized(); + bool is_maximized = window_->IsMaximized(); + bool is_fullscreen = window_->IsFullscreen(); + if (is_minimized != was_minimized_) { + if (is_minimized) + window_->NotifyWindowMinimize(); + else + window_->NotifyWindowRestore(); + } else if (is_maximized != was_maximized_) { + if (is_maximized) + window_->NotifyWindowMaximize(); + else + window_->NotifyWindowUnmaximize(); + } else { + // If this is neither a "maximize" or "minimize" event, then we think it + // is a "fullscreen" event. + // The "IsFullscreen()" becomes true immediately before "WillProcessEvent" + // is called, so we can not handle this like "maximize" and "minimize" by + // watching whether they have changed. + if (is_fullscreen) + window_->NotifyWindowEnterFullScreen(); + else + window_->NotifyWindowLeaveFullScreen(); + } + } +} + +bool WindowStateWatcher::IsWindowStateEvent(const ui::PlatformEvent& event) { + ::Atom changed_atom = event->xproperty.atom; + return (changed_atom == atom_cache_.GetAtom("_NET_WM_STATE") && + event->type == PropertyNotify && + event->xproperty.window == widget_); +} + +} // namespace atom diff --git a/atom/browser/ui/x/window_state_watcher.h b/atom/browser/ui/x/window_state_watcher.h new file mode 100644 index 000000000000..2888c9fc6fec --- /dev/null +++ b/atom/browser/ui/x/window_state_watcher.h @@ -0,0 +1,41 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_X_WINDOW_STATE_WATCHER_H_ +#define ATOM_BROWSER_UI_X_WINDOW_STATE_WATCHER_H_ + +#include "ui/events/platform/platform_event_observer.h" + +#include "atom/browser/native_window_views.h" +#include "ui/gfx/x/x11_atom_cache.h" + +namespace atom { + +class WindowStateWatcher : public ui::PlatformEventObserver { + public: + explicit WindowStateWatcher(NativeWindowViews* window); + virtual ~WindowStateWatcher(); + + protected: + // ui::PlatformEventObserver: + void WillProcessEvent(const ui::PlatformEvent& event) override; + void DidProcessEvent(const ui::PlatformEvent& event) override; + + private: + bool IsWindowStateEvent(const ui::PlatformEvent& event); + + NativeWindowViews* window_; + gfx::AcceleratedWidget widget_; + + ui::X11AtomCache atom_cache_; + + bool was_minimized_; + bool was_maximized_; + + DISALLOW_COPY_AND_ASSIGN(WindowStateWatcher); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_X_WINDOW_STATE_WATCHER_H_ diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 711a09d10cea..6486ddd0a29e 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -147,6 +147,30 @@ Emitted when window loses focus. Emitted when window gains focus. +### Event: 'maximize' + +Emitted when window is maximized. + +### Event: 'unmaximize' + +Emitted when window exits from maximized state. + +### Event: 'minimize' + +Emitted when window is minimized. + +### Event: 'restore' + +Emitted when window is restored from minimized state. + +### Event: 'enter-full-screen' + +Emitted when window enters full screen state. + +### Event: 'leave-full-screen' + +Emitted when window leaves full screen state. + ### Event: 'devtools-opened' Emitted when devtools is opened. diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index c81e1731d5ae..09e0542c9055 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -183,3 +183,37 @@ describe 'browser-window module', -> assert.equal frameName, 'target' done() w.loadUrl "file://#{fixtures}/pages/target-name.html" + + describe 'maximize event', -> + return if isCI and process.platform is 'linux' + it 'emits when window is maximized', (done) -> + @timeout 10000 + w.once 'maximize', -> done() + w.show() + w.maximize() + + describe 'unmaximize event', -> + return if isCI and process.platform is 'linux' + it 'emits when window is unmaximized', (done) -> + @timeout 10000 + w.once 'unmaximize', -> done() + w.show() + w.maximize() + w.unmaximize() + + describe 'minimize event', -> + return if isCI and process.platform is 'linux' + it 'emits when window is minimized', (done) -> + @timeout 10000 + w.once 'minimize', -> done() + w.show() + w.minimize() + + describe 'restore event', -> + return if isCI and process.platform is 'linux' + it 'emits when window is restored', (done) -> + @timeout 10000 + w.once 'restore', -> done() + w.show() + w.minimize() + w.restore()