1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.h"
7 #include "apps/ui/views/app_window_frame_view.h"
8 #include "ash/ash_constants.h"
9 #include "ash/frame/custom_frame_view_ash.h"
10 #include "ash/screen_util.h"
11 #include "ash/shell.h"
12 #include "ash/wm/immersive_fullscreen_controller.h"
13 #include "ash/wm/panels/panel_frame_view.h"
14 #include "ash/wm/window_properties.h"
15 #include "ash/wm/window_state.h"
16 #include "ash/wm/window_state_delegate.h"
17 #include "ash/wm/window_state_observer.h"
18 #include "chrome/browser/ui/ash/ash_util.h"
19 #include "chrome/browser/ui/ash/multi_user/multi_user_context_menu.h"
20 #include "chrome/browser/ui/host_desktop.h"
21 #include "chrome/browser/ui/views/apps/app_window_easy_resize_window_targeter.h"
22 #include "chrome/browser/ui/views/apps/shaped_app_window_targeter.h"
23 #include "chrome/browser/web_applications/web_app.h"
24 #include "ui/aura/client/aura_constants.h"
25 #include "ui/aura/window.h"
26 #include "ui/aura/window_observer.h"
27 #include "ui/base/hit_test.h"
28 #include "ui/base/models/simple_menu_model.h"
29 #include "ui/gfx/image/image_skia.h"
30 #include "ui/views/controls/menu/menu_runner.h"
31 #include "ui/views/widget/widget.h"
33 #if defined(OS_CHROMEOS)
34 #include "ash/shell_window_ids.h"
38 #include "chrome/browser/shell_integration_linux.h"
41 using extensions::AppWindow
;
45 // This class handles a user's fullscreen request (Shift+F4/F4).
46 class NativeAppWindowStateDelegate
: public ash::wm::WindowStateDelegate
,
47 public ash::wm::WindowStateObserver
,
48 public aura::WindowObserver
{
50 NativeAppWindowStateDelegate(AppWindow
* app_window
,
51 extensions::NativeAppWindow
* native_app_window
)
52 : app_window_(app_window
),
54 ash::wm::GetWindowState(native_app_window
->GetNativeWindow())) {
55 // Add a window state observer to exit fullscreen properly in case
56 // fullscreen is exited without going through AppWindow::Restore(). This
57 // is the case when exiting immersive fullscreen via the "Restore" window
59 // TODO(pkotwicz): This is a hack. Remove ASAP. http://crbug.com/319048
60 window_state_
->AddObserver(this);
61 window_state_
->window()->AddObserver(this);
63 ~NativeAppWindowStateDelegate() override
{
65 window_state_
->RemoveObserver(this);
66 window_state_
->window()->RemoveObserver(this);
71 // Overridden from ash::wm::WindowStateDelegate.
72 bool ToggleFullscreen(ash::wm::WindowState
* window_state
) override
{
73 // Windows which cannot be maximized should not be fullscreened.
74 DCHECK(window_state
->IsFullscreen() || window_state
->CanMaximize());
75 if (window_state
->IsFullscreen())
76 app_window_
->Restore();
77 else if (window_state
->CanMaximize())
78 app_window_
->OSFullscreen();
82 // Overridden from ash::wm::WindowStateObserver:
83 void OnPostWindowStateTypeChange(ash::wm::WindowState
* window_state
,
84 ash::wm::WindowStateType old_type
) override
{
85 // Since the window state might get set by a window manager, it is possible
86 // to come here before the application set its |BaseWindow|.
87 if (!window_state
->IsFullscreen() && !window_state
->IsMinimized() &&
88 app_window_
->GetBaseWindow() &&
89 app_window_
->GetBaseWindow()->IsFullscreenOrPending()) {
90 app_window_
->Restore();
91 // Usually OnNativeWindowChanged() is called when the window bounds are
92 // changed as a result of a state type change. Because the change in state
93 // type has already occurred, we need to call OnNativeWindowChanged()
95 app_window_
->OnNativeWindowChanged();
99 // Overridden from aura::WindowObserver:
100 void OnWindowDestroying(aura::Window
* window
) override
{
101 window_state_
->RemoveObserver(this);
102 window_state_
->window()->RemoveObserver(this);
103 window_state_
= NULL
;
107 AppWindow
* app_window_
;
108 ash::wm::WindowState
* window_state_
;
110 DISALLOW_COPY_AND_ASSIGN(NativeAppWindowStateDelegate
);
115 ChromeNativeAppWindowViewsAura::ChromeNativeAppWindowViewsAura() {
118 ChromeNativeAppWindowViewsAura::~ChromeNativeAppWindowViewsAura() {
121 void ChromeNativeAppWindowViewsAura::InitializeWindow(
122 AppWindow
* app_window
,
123 const AppWindow::CreateParams
& create_params
) {
124 ChromeNativeAppWindowViews::InitializeWindow(app_window
, create_params
);
125 // Restore docked state on ash desktop and ignore it elsewhere.
126 if (create_params
.state
== ui::SHOW_STATE_DOCKED
&&
127 chrome::GetHostDesktopTypeForNativeWindow(widget()->GetNativeWindow()) ==
128 chrome::HOST_DESKTOP_TYPE_ASH
) {
129 widget()->GetNativeWindow()->SetProperty(aura::client::kShowStateKey
,
130 create_params
.state
);
134 void ChromeNativeAppWindowViewsAura::OnBeforeWidgetInit(
135 const AppWindow::CreateParams
& create_params
,
136 views::Widget::InitParams
* init_params
,
137 views::Widget
* widget
) {
138 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
139 std::string app_name
= web_app::GenerateApplicationNameFromExtensionId(
140 app_window()->extension_id());
141 // Set up a custom WM_CLASS for app windows. This allows task switchers in
142 // X11 environments to distinguish them from main browser windows.
143 init_params
->wm_class_name
= web_app::GetWMClassFromAppName(app_name
);
144 init_params
->wm_class_class
= shell_integration_linux::GetProgramClassName();
145 const char kX11WindowRoleApp
[] = "app";
146 init_params
->wm_role_name
= std::string(kX11WindowRoleApp
);
149 ChromeNativeAppWindowViews::OnBeforeWidgetInit(create_params
, init_params
,
152 #if defined(OS_CHROMEOS)
153 if (create_params
.is_ime_window
) {
154 // Puts ime windows into ime window container.
155 init_params
->parent
=
156 ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(),
157 ash::kShellWindowId_ImeWindowParentContainer
);
162 void ChromeNativeAppWindowViewsAura::OnBeforePanelWidgetInit(
163 bool use_default_bounds
,
164 views::Widget::InitParams
* init_params
,
165 views::Widget
* widget
) {
166 ChromeNativeAppWindowViews::OnBeforePanelWidgetInit(use_default_bounds
,
170 if (ash::Shell::HasInstance() && use_default_bounds
) {
171 // Open a new panel on the target root.
172 init_params
->bounds
= ash::ScreenUtil::ConvertRectToScreen(
173 ash::Shell::GetTargetRootWindow(), gfx::Rect(GetPreferredSize()));
177 views::NonClientFrameView
*
178 ChromeNativeAppWindowViewsAura::CreateNonStandardAppFrame() {
179 apps::AppWindowFrameView
* frame
=
180 new apps::AppWindowFrameView(widget(), this, HasFrameColor(),
181 ActiveFrameColor(), InactiveFrameColor());
184 // For Aura windows on the Ash desktop the sizes are different and the user
185 // can resize the window from slightly outside the bounds as well.
186 if (chrome::IsNativeWindowInAsh(widget()->GetNativeWindow())) {
187 frame
->SetResizeSizes(ash::kResizeInsideBoundsSize
,
188 ash::kResizeOutsideBoundsSize
,
189 ash::kResizeAreaCornerSize
);
192 #if !defined(OS_CHROMEOS)
193 // For non-Ash windows, install an easy resize window targeter, which ensures
194 // that the root window (not the app) receives mouse events on the edges.
195 if (chrome::GetHostDesktopTypeForNativeWindow(widget()->GetNativeWindow()) !=
196 chrome::HOST_DESKTOP_TYPE_ASH
) {
197 aura::Window
* window
= widget()->GetNativeWindow();
198 int resize_inside
= frame
->resize_inside_bounds_size();
199 gfx::Insets
inset(resize_inside
, resize_inside
, resize_inside
,
201 // Add the AppWindowEasyResizeWindowTargeter on the window, not its root
202 // window. The root window does not have a delegate, which is needed to
203 // handle the event in Linux.
204 window
->SetEventTargeter(scoped_ptr
<ui::EventTargeter
>(
205 new AppWindowEasyResizeWindowTargeter(window
, inset
, this)));
212 gfx::Rect
ChromeNativeAppWindowViewsAura::GetRestoredBounds() const {
214 widget()->GetNativeWindow()->GetProperty(ash::kRestoreBoundsOverrideKey
);
215 if (bounds
&& !bounds
->IsEmpty())
218 return ChromeNativeAppWindowViews::GetRestoredBounds();
221 ui::WindowShowState
ChromeNativeAppWindowViewsAura::GetRestoredState() const {
222 // Use kRestoreShowStateKey in case a window is minimized/hidden.
223 ui::WindowShowState restore_state
= widget()->GetNativeWindow()->GetProperty(
224 aura::client::kRestoreShowStateKey
);
225 if (widget()->GetNativeWindow()->GetProperty(
226 ash::kRestoreBoundsOverrideKey
)) {
227 // If an override is given, we use that restore state (after filtering).
228 restore_state
= widget()->GetNativeWindow()->GetProperty(
229 ash::kRestoreShowStateOverrideKey
);
231 // Otherwise first normal states are checked.
233 return ui::SHOW_STATE_MAXIMIZED
;
234 if (IsFullscreen()) {
235 if (immersive_fullscreen_controller_
.get() &&
236 immersive_fullscreen_controller_
->IsEnabled()) {
237 // Restore windows which were previously in immersive fullscreen to
238 // maximized. Restoring the window to a different fullscreen type
239 // makes for a bad experience.
240 return ui::SHOW_STATE_MAXIMIZED
;
242 return ui::SHOW_STATE_FULLSCREEN
;
244 if (widget()->GetNativeWindow()->GetProperty(aura::client::kShowStateKey
) ==
245 ui::SHOW_STATE_DOCKED
) {
246 return ui::SHOW_STATE_DOCKED
;
249 // Whitelist states to return so that invalid and transient states
250 // are not saved and used to restore windows when they are recreated.
251 switch (restore_state
) {
252 case ui::SHOW_STATE_NORMAL
:
253 case ui::SHOW_STATE_MAXIMIZED
:
254 case ui::SHOW_STATE_FULLSCREEN
:
255 return restore_state
;
257 case ui::SHOW_STATE_DEFAULT
:
258 case ui::SHOW_STATE_MINIMIZED
:
259 case ui::SHOW_STATE_INACTIVE
:
260 case ui::SHOW_STATE_DOCKED
:
261 case ui::SHOW_STATE_END
:
262 return ui::SHOW_STATE_NORMAL
;
265 return ui::SHOW_STATE_NORMAL
;
268 bool ChromeNativeAppWindowViewsAura::IsAlwaysOnTop() const {
269 return app_window()->window_type_is_panel()
270 ? ash::wm::GetWindowState(widget()->GetNativeWindow())
272 : widget()->IsAlwaysOnTop();
275 void ChromeNativeAppWindowViewsAura::ShowContextMenuForView(
278 ui::MenuSourceType source_type
) {
279 #if defined(OS_CHROMEOS)
280 scoped_ptr
<ui::MenuModel
> model
=
281 CreateMultiUserContextMenu(app_window()->GetNativeWindow());
285 // Only show context menu if point is in caption.
286 gfx::Point
point_in_view_coords(p
);
287 views::View::ConvertPointFromScreen(widget()->non_client_view(),
288 &point_in_view_coords
);
290 widget()->non_client_view()->NonClientHitTest(point_in_view_coords
);
291 if (hit_test
== HTCAPTION
) {
292 menu_runner_
.reset(new views::MenuRunner(
294 views::MenuRunner::HAS_MNEMONICS
| views::MenuRunner::CONTEXT_MENU
));
295 if (menu_runner_
->RunMenuAt(source
->GetWidget(), NULL
,
296 gfx::Rect(p
, gfx::Size(0, 0)),
297 views::MENU_ANCHOR_TOPLEFT
, source_type
) ==
298 views::MenuRunner::MENU_DELETED
) {
305 views::NonClientFrameView
*
306 ChromeNativeAppWindowViewsAura::CreateNonClientFrameView(
307 views::Widget
* widget
) {
308 if (chrome::IsNativeViewInAsh(widget
->GetNativeView())) {
309 // Set the delegate now because CustomFrameViewAsh sets the
310 // WindowStateDelegate if one is not already set.
311 ash::wm::GetWindowState(GetNativeWindow())
313 scoped_ptr
<ash::wm::WindowStateDelegate
>(
314 new NativeAppWindowStateDelegate(app_window(), this)).Pass());
317 return CreateNonStandardAppFrame();
319 if (app_window()->window_type_is_panel()) {
320 views::NonClientFrameView
* frame_view
=
321 new ash::PanelFrameView(widget
, ash::PanelFrameView::FRAME_ASH
);
322 frame_view
->set_context_menu_controller(this);
326 ash::CustomFrameViewAsh
* custom_frame_view
=
327 new ash::CustomFrameViewAsh(widget
);
328 // Non-frameless app windows can be put into immersive fullscreen.
329 immersive_fullscreen_controller_
.reset(
330 new ash::ImmersiveFullscreenController());
331 custom_frame_view
->InitImmersiveFullscreenControllerForView(
332 immersive_fullscreen_controller_
.get());
333 custom_frame_view
->GetHeaderView()->set_context_menu_controller(this);
335 if (HasFrameColor()) {
336 custom_frame_view
->SetFrameColors(ActiveFrameColor(),
337 InactiveFrameColor());
340 return custom_frame_view
;
343 return ChromeNativeAppWindowViews::CreateNonClientFrameView(widget
);
346 void ChromeNativeAppWindowViewsAura::SetFullscreen(int fullscreen_types
) {
347 ChromeNativeAppWindowViews::SetFullscreen(fullscreen_types
);
349 if (immersive_fullscreen_controller_
.get()) {
350 // |immersive_fullscreen_controller_| should only be set if immersive
351 // fullscreen is the fullscreen type used by the OS.
352 immersive_fullscreen_controller_
->SetEnabled(
353 ash::ImmersiveFullscreenController::WINDOW_TYPE_PACKAGED_APP
,
354 (fullscreen_types
& AppWindow::FULLSCREEN_TYPE_OS
) != 0);
355 // Autohide the shelf instead of hiding the shelf completely when only in
357 ash::wm::WindowState
* window_state
=
358 ash::wm::GetWindowState(widget()->GetNativeWindow());
359 window_state
->set_hide_shelf_when_fullscreen(fullscreen_types
!=
360 AppWindow::FULLSCREEN_TYPE_OS
);
361 DCHECK(ash::Shell::HasInstance());
362 ash::Shell::GetInstance()->UpdateShelfVisibility();
366 void ChromeNativeAppWindowViewsAura::UpdateShape(scoped_ptr
<SkRegion
> region
) {
367 bool had_shape
= !!shape();
369 ChromeNativeAppWindowViews::UpdateShape(region
.Pass());
371 aura::Window
* native_window
= widget()->GetNativeWindow();
372 if (shape() && !had_shape
) {
373 native_window
->SetEventTargeter(scoped_ptr
<ui::EventTargeter
>(
374 new ShapedAppWindowTargeter(native_window
, this)));
375 } else if (!shape() && had_shape
) {
376 native_window
->SetEventTargeter(scoped_ptr
<ui::EventTargeter
>());