blob: a9ba6d1c8c1056ab4676e88bc45fd7ab8cba0c1b [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/exo/shell_surface_base.h"
#include <algorithm>
#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/public/cpp/ash_constants.h"
#include "ash/public/cpp/rounded_corner_utils.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/drag_window_resizer.h"
#include "ash/wm/resize_shadow_controller.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/window_resizer.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/check.h"
#include "base/debug/stack_trace.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "build/chromeos_buildflags.h"
#include "cc/trees/layer_tree_frame_sink.h"
#include "chromeos/crosapi/cpp/crosapi_constants.h"
#include "chromeos/ui/base/window_pin_type.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
#include "components/app_restore/app_restore_info.h"
#include "components/app_restore/app_restore_utils.h"
#include "components/app_restore/window_properties.h"
#include "components/exo/custom_window_state_delegate.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "components/exo/window_properties.h"
#include "components/exo/wm_helper.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_occlusion_tracker.h"
#include "ui/aura/window_targeter.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/class_property.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor_extra/shadow.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/shadow_controller.h"
#include "ui/wm/core/shadow_types.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"
namespace exo {
namespace {
// The accelerator keys used to close ShellSurfaces.
const struct {
ui::KeyboardCode keycode;
int modifiers;
} kCloseWindowAccelerators[] = {
{ui::VKEY_W, ui::EF_CONTROL_DOWN},
{ui::VKEY_W, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN},
{ui::VKEY_F4, ui::EF_ALT_DOWN}};
class ShellSurfaceWidget : public views::Widget {
public:
ShellSurfaceWidget() = default;
ShellSurfaceWidget(const ShellSurfaceWidget&) = delete;
ShellSurfaceWidget& operator=(const ShellSurfaceWidget&) = delete;
// Overridden from views::Widget:
void OnKeyEvent(ui::KeyEvent* event) override {
if (GetFocusManager()->GetFocusedView() &&
GetFocusManager()->GetFocusedView()->GetWidget() != this) {
// If the focus is on the overlay widget, dispatch the key event normally.
views::Widget::OnKeyEvent(event);
} else if (GetFocusManager()->ProcessAccelerator(ui::Accelerator(*event))) {
// Otherwise handle only accelerators. Do not call Widget::OnKeyEvent that
// eats focus management keys (like the tab key) as well.
event->SetHandled();
}
}
};
class CustomFrameView : public ash::NonClientFrameViewAsh {
public:
using ShapeRects = std::vector<gfx::Rect>;
CustomFrameView(views::Widget* widget,
ShellSurfaceBase* shell_surface,
bool enabled)
: NonClientFrameViewAsh(widget), shell_surface_(shell_surface) {
SetFrameEnabled(enabled);
if (!enabled)
NonClientFrameViewAsh::SetShouldPaintHeader(false);
}
CustomFrameView(const CustomFrameView&) = delete;
CustomFrameView& operator=(const CustomFrameView&) = delete;
~CustomFrameView() override = default;
// Overridden from ash::NonClientFrameViewAsh:
void SetShouldPaintHeader(bool paint) override {
if (GetFrameEnabled()) {
NonClientFrameViewAsh::SetShouldPaintHeader(paint);
return;
}
}
// Overridden from views::NonClientFrameView:
gfx::Rect GetBoundsForClientView() const override {
if (GetFrameEnabled())
return ash::NonClientFrameViewAsh::GetBoundsForClientView();
return bounds();
}
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override {
if (GetFrameEnabled()) {
return ash::NonClientFrameViewAsh::GetWindowBoundsForClientBounds(
client_bounds);
}
return client_bounds;
}
int NonClientHitTest(const gfx::Point& point) override {
if (GetFrameEnabled() || shell_surface_->server_side_resize())
return ash::NonClientFrameViewAsh::NonClientHitTest(point);
return GetWidget()->client_view()->NonClientHitTest(point);
}
void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override {
if (GetFrameEnabled())
return ash::NonClientFrameViewAsh::GetWindowMask(size, window_mask);
}
void ResetWindowControls() override {
if (GetFrameEnabled())
return ash::NonClientFrameViewAsh::ResetWindowControls();
}
void UpdateWindowIcon() override {
if (GetFrameEnabled())
return ash::NonClientFrameViewAsh::ResetWindowControls();
}
void UpdateWindowTitle() override {
if (GetFrameEnabled())
return ash::NonClientFrameViewAsh::UpdateWindowTitle();
}
void SizeConstraintsChanged() override {
if (GetFrameEnabled())
return ash::NonClientFrameViewAsh::SizeConstraintsChanged();
}
gfx::Size GetMinimumSize() const override {
gfx::Size minimum_size = shell_surface_->GetMinimumSize();
if (GetFrameEnabled()) {
return ash::NonClientFrameViewAsh::GetWindowBoundsForClientBounds(
gfx::Rect(minimum_size))
.size();
}
return minimum_size;
}
gfx::Size GetMaximumSize() const override {
gfx::Size maximum_size = shell_surface_->GetMaximumSize();
if (GetFrameEnabled() && !maximum_size.IsEmpty()) {
return ash::NonClientFrameViewAsh::GetWindowBoundsForClientBounds(
gfx::Rect(maximum_size))
.size();
}
return maximum_size;
}
private:
ShellSurfaceBase* const shell_surface_;
};
class CustomWindowTargeter : public aura::WindowTargeter {
public:
explicit CustomWindowTargeter(ShellSurfaceBase* shell_surface)
: shell_surface_(shell_surface), widget_(shell_surface->GetWidget()) {}
CustomWindowTargeter(const CustomWindowTargeter&) = delete;
CustomWindowTargeter& operator=(const CustomWindowTargeter&) = delete;
~CustomWindowTargeter() override = default;
// Overridden from aura::WindowTargeter:
bool EventLocationInsideBounds(aura::Window* window,
const ui::LocatedEvent& event) const override {
gfx::Point local_point =
ConvertEventLocationToWindowCoordinates(window, event);
if (IsInResizeHandle(window, event, local_point))
return true;
Surface* surface = GetShellRootSurface(window);
if (!surface)
return false;
int component =
widget_->non_client_view()
? widget_->non_client_view()->NonClientHitTest(local_point)
: HTNOWHERE;
if (component != HTNOWHERE && component != HTCLIENT &&
component != HTBORDER) {
return true;
}
aura::Window::ConvertPointToTarget(window, surface->window(), &local_point);
return surface->HitTest(local_point);
}
private:
bool IsInResizeHandle(aura::Window* window,
const ui::LocatedEvent& event,
const gfx::Point& local_point) const {
if (window != widget_->GetNativeWindow() ||
!widget_->widget_delegate()->CanResize()) {
return false;
}
if (!shell_surface_->server_side_resize())
return false;
ui::EventTarget* parent =
static_cast<ui::EventTarget*>(window)->GetParentTarget();
if (parent) {
aura::WindowTargeter* parent_targeter =
static_cast<aura::WindowTargeter*>(parent->GetEventTargeter());
if (parent_targeter) {
gfx::Rect mouse_rect;
gfx::Rect touch_rect;
if (parent_targeter->GetHitTestRects(window, &mouse_rect,
&touch_rect)) {
const gfx::Vector2d offset = -window->bounds().OffsetFromOrigin();
mouse_rect.Offset(offset);
touch_rect.Offset(offset);
if (event.IsTouchEvent() || event.IsGestureEvent()
? touch_rect.Contains(local_point)
: mouse_rect.Contains(local_point)) {
return true;
}
}
}
}
return false;
}
ShellSurfaceBase* shell_surface_;
views::Widget* const widget_;
};
void CloseAllShellSurfaceTransientChildren(aura::Window* window) {
// Deleting a window may delete other transient children. Remove other shell
// surface bases first so they don't get deleted.
auto list = wm::GetTransientChildren(window);
for (size_t i = 0; i < list.size(); ++i) {
if (GetShellSurfaceBaseForWindow(list[i]))
wm::RemoveTransientChild(window, list[i]);
}
}
int shell_id = 0;
void ShowSnapPreview(aura::Window* window,
chromeos::SnapDirection snap_direction) {
chromeos::SnapController::Get()->ShowSnapPreview(
window, snap_direction,
/*allow_haptic_feedback=*/false);
}
void CommitSnap(aura::Window* window, chromeos::SnapDirection snap_direction) {
chromeos::SnapController::Get()->CommitSnap(window, snap_direction);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// ShellSurfaceBase, public:
ShellSurfaceBase::ShellSurfaceBase(Surface* surface,
const gfx::Point& origin,
bool can_minimize,
int container)
: SurfaceTreeHost(base::StringPrintf("ExoShellSurfaceHost-%d", shell_id)),
origin_(origin),
container_(container),
can_minimize_(can_minimize) {
WMHelper::GetInstance()->AddActivationObserver(this);
surface->AddSurfaceObserver(this);
SetRootSurface(surface);
host_window()->Show();
set_owned_by_client();
SetCanMinimize(can_minimize_);
SetCanMaximize(ash::desks_util::IsDeskContainerId(container_));
SetCanResize(true);
SetShowTitle(false);
}
ShellSurfaceBase::~ShellSurfaceBase() {
// If the surface was TrustedPinned, we have to unpin first as this might have
// locked down some system functions.
if (current_pinned_state_ == chromeos::WindowPinType::kTrustedPinned) {
pending_pinned_state_ = chromeos::WindowPinType::kNone;
UpdatePinned();
}
// Close the overlay in case the window is deleted by the server.
overlay_widget_.reset();
// Remove activation observer before hiding widget to prevent it from
// casuing the configure callback to be called.
WMHelper::GetInstance()->RemoveActivationObserver(this);
// Client is gone by now, so don't call callbacks.
close_callback_.Reset();
pre_close_callback_.Reset();
surface_destroyed_callback_.Reset();
if (widget_) {
widget_->GetNativeWindow()->RemoveObserver(this);
widget_->RemoveObserver(this);
// Remove transient children which are shell surfaces so they are not
// automatically destroyed.
CloseAllShellSurfaceTransientChildren(widget_->GetNativeWindow());
if (widget_->IsVisible())
widget_->Hide();
widget_->CloseNow();
}
if (parent_)
parent_->RemoveObserver(this);
if (root_surface())
root_surface()->RemoveSurfaceObserver(this);
if (has_grab_)
WMHelper::GetInstance()->GetCaptureClient()->RemoveObserver(this);
CHECK(!views::WidgetObserver::IsInObserverList());
}
void ShellSurfaceBase::Activate() {
TRACE_EVENT0("exo", "ShellSurfaceBase::Activate");
if (!widget_ || widget_->IsActive())
return;
widget_->Activate();
}
void ShellSurfaceBase::SetTitle(const std::u16string& title) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetTitle", "title",
base::UTF16ToUTF8(title));
WidgetDelegate::SetTitle(title);
}
void ShellSurfaceBase::SetIcon(const gfx::ImageSkia& icon) {
TRACE_EVENT0("exo", "ShellSurfaceBase::SetIcon");
WidgetDelegate::SetIcon(icon);
}
void ShellSurfaceBase::SetSystemModal(bool system_modal) {
if (system_modal == system_modal_)
return;
bool non_system_modal_window_was_active =
!system_modal_ && widget_ && widget_->IsActive();
system_modal_ = system_modal;
if (widget_) {
UpdateSystemModal();
// Deactivate to give the focus back to normal windows.
if (!system_modal_ && !non_system_modal_window_was_active_) {
widget_->Deactivate();
}
}
non_system_modal_window_was_active_ = non_system_modal_window_was_active;
}
void ShellSurfaceBase::SetBoundsForShadows(
const absl::optional<gfx::Rect>& shadow_bounds) {
if (shadow_bounds_ != shadow_bounds) {
// Set normal shadow bounds.
shadow_bounds_ = shadow_bounds;
shadow_bounds_changed_ = true;
if (widget_ && shadow_bounds) {
// Set resize shadow bounds and origin.
const gfx::Rect bounds = shadow_bounds.value();
const gfx::Point absolute_origin =
widget_->GetNativeWindow()->bounds().origin();
const gfx::Rect absolute_bounds =
gfx::Rect(absolute_origin.x(), absolute_origin.y(), bounds.width(),
bounds.height());
ash::Shell::Get()
->resize_shadow_controller()
->UpdateResizeShadowBoundsOfWindow(widget_->GetNativeWindow(),
absolute_bounds);
}
}
}
void ShellSurfaceBase::UpdateSystemModal() {
DCHECK(widget_);
DCHECK_EQ(container_, ash::kShellWindowId_SystemModalContainer);
widget_->GetNativeWindow()->SetProperty(
aura::client::kModalKey,
system_modal_ ? ui::MODAL_TYPE_SYSTEM : ui::MODAL_TYPE_NONE);
}
void ShellSurfaceBase::SetApplicationId(const char* application_id) {
// Store the value in |application_id_| in case the window does not exist yet.
if (application_id)
application_id_ = std::string(application_id);
else
application_id_.reset();
if (widget_ && widget_->GetNativeWindow()) {
SetShellApplicationId(widget_->GetNativeWindow(), application_id_);
WMHelper::AppPropertyResolver::Params params;
if (application_id_)
params.app_id = *application_id_;
if (startup_id_)
params.startup_id = *startup_id_;
ui::PropertyHandler& property_handler = *widget_->GetNativeWindow();
WMHelper::GetInstance()->PopulateAppProperties(params, property_handler);
}
this->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
/* send_native_event */ false);
}
void ShellSurfaceBase::SetStartupId(const char* startup_id) {
// Store the value in |startup_id_| in case the window does not exist yet.
if (startup_id)
startup_id_ = std::string(startup_id);
else
startup_id_.reset();
if (widget_ && widget_->GetNativeWindow())
SetShellStartupId(widget_->GetNativeWindow(), startup_id_);
}
void ShellSurfaceBase::SetUseImmersiveForFullscreen(bool value) {
// Store the value in case the window doesn't exist yet.
immersive_implied_by_fullscreen_ = value;
if (widget_ && widget_->GetNativeWindow())
SetShellUseImmersiveForFullscreen(widget_->GetNativeWindow(), value);
}
void ShellSurfaceBase::ShowSnapPreviewToPrimary() {
ShowSnapPreview(widget_->GetNativeWindow(),
chromeos::SnapDirection::kPrimary);
}
void ShellSurfaceBase::ShowSnapPreviewToSecondary() {
ShowSnapPreview(widget_->GetNativeWindow(),
chromeos::SnapDirection::kSecondary);
}
void ShellSurfaceBase::HideSnapPreview() {
ShowSnapPreview(widget_->GetNativeWindow(), chromeos::SnapDirection::kNone);
}
void ShellSurfaceBase::SetSnappedToPrimary() {
CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kPrimary);
}
void ShellSurfaceBase::SetSnappedToSecondary() {
CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kSecondary);
}
void ShellSurfaceBase::UnsetSnap() {
CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kNone);
}
void ShellSurfaceBase::SetCanGoBack() {
if (widget_)
widget_->GetNativeWindow()->SetProperty(ash::kMinimizeOnBackKey, false);
}
void ShellSurfaceBase::UnsetCanGoBack() {
if (widget_)
widget_->GetNativeWindow()->SetProperty(ash::kMinimizeOnBackKey, true);
}
void ShellSurfaceBase::SetPip() {
if (!widget_) {
pending_pip_ = true;
return;
}
// Set all the necessary window properties and window state.
auto* window = widget_->GetNativeWindow();
window->SetProperty(ash::kWindowPipTypeKey, true);
window->SetProperty(aura::client::kZOrderingKey,
ui::ZOrderLevel::kFloatingWindow);
// Pip windows should start in the bottom right corner of the screen so move
// |window| to the bottom right of the work area and let the pip positioner
// move it within the work area.
auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(window);
gfx::Size window_size = window->bounds().size();
window->SetBoundsInScreen(
gfx::Rect(display.work_area().bottom_right(), window_size), display);
pending_pip_ = false;
}
void ShellSurfaceBase::UnsetPip() {
if (!widget_) {
pending_pip_ = false;
return;
}
// Set all the necessary window properties and window state.
auto* window = widget_->GetNativeWindow();
window->SetProperty(ash::kWindowPipTypeKey, false);
window->SetProperty(aura::client::kZOrderingKey, ui::ZOrderLevel::kNormal);
}
void ShellSurfaceBase::MoveToDesk(int desk_index) {
if (widget_) {
ash::DesksController::Get()->SendToDeskAtIndex(widget_->GetNativeWindow(),
desk_index);
}
}
void ShellSurfaceBase::SetVisibleOnAllWorkspaces() {
if (widget_)
widget_->SetVisibleOnAllWorkspaces(true);
}
void ShellSurfaceBase::SetInitialWorkspace(const char* initial_workspace) {
if (initial_workspace)
initial_workspace_ = std::string(initial_workspace);
else
initial_workspace_.reset();
}
void ShellSurfaceBase::Pin(bool trusted) {
pending_pinned_state_ = trusted ? chromeos::WindowPinType::kTrustedPinned
: chromeos::WindowPinType::kPinned;
UpdatePinned();
}
void ShellSurfaceBase::Unpin() {
// Only need to do something when we have to set a pinned mode.
if (pending_pinned_state_ == chromeos::WindowPinType::kNone)
return;
// Remove any pending pin states which might not have been applied yet.
pending_pinned_state_ = chromeos::WindowPinType::kNone;
UpdatePinned();
}
void ShellSurfaceBase::UpdatePinned() {
if (!widget_) {
// It is possible to get here before the widget has actually been created.
// The state will be set once the widget gets created.
return;
}
if (current_pinned_state_ != pending_pinned_state_) {
auto* window = widget_->GetNativeWindow();
if (pending_pinned_state_ == chromeos::WindowPinType::kNone) {
ash::WindowState::Get(window)->Restore();
} else {
bool trusted_pinned =
pending_pinned_state_ == chromeos::WindowPinType::kTrustedPinned;
ash::window_util::PinWindow(window,
/*trusted=*/trusted_pinned);
}
current_pinned_state_ = pending_pinned_state_;
}
}
void ShellSurfaceBase::SetChildAxTreeId(ui::AXTreeID child_ax_tree_id) {
GetViewAccessibility().OverrideChildTreeID(child_ax_tree_id);
this->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false);
}
void ShellSurfaceBase::SetGeometry(const gfx::Rect& geometry) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetGeometry", "geometry",
geometry.ToString());
if (geometry.IsEmpty()) {
DLOG(WARNING) << "Surface geometry must be non-empty";
return;
}
pending_geometry_ = geometry;
}
void ShellSurfaceBase::SetWindowBounds(const gfx::Rect& bounds) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetWindowBounds", "bounds",
bounds.ToString());
if (!widget_) {
initial_bounds_.emplace(bounds);
return;
}
// TODO(crbug.com/1261321): This will not work if the full server side
// decoration is used.
DCHECK(!frame_enabled());
widget_->SetBounds(bounds);
}
void ShellSurfaceBase::SetRestoreInfo(int32_t restore_session_id,
int32_t restore_window_id) {
// Restore information must be set before widget is created.
DCHECK(!widget_);
restore_session_id_.emplace(restore_session_id);
restore_window_id_.emplace(restore_window_id);
}
void ShellSurfaceBase::SetDisplay(int64_t display_id) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetDisplay", "display_id", display_id);
pending_display_id_ = display_id;
}
void ShellSurfaceBase::SetOrigin(const gfx::Point& origin) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetOrigin", "origin",
origin.ToString());
origin_ = origin;
}
void ShellSurfaceBase::SetActivatable(bool activatable) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetActivatable", "activatable",
activatable);
activatable_ = activatable;
}
void ShellSurfaceBase::SetContainer(int container) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetContainer", "container", container);
SetContainerInternal(container);
}
void ShellSurfaceBase::SetMaximumSize(const gfx::Size& size) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetMaximumSize", "size",
size.ToString());
pending_maximum_size_ = size;
}
void ShellSurfaceBase::SetMinimumSize(const gfx::Size& size) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetMinimumSize", "size",
size.ToString());
pending_minimum_size_ = size;
}
void ShellSurfaceBase::SetAspectRatio(const gfx::SizeF& aspect_ratio) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetAspectRatio", "aspect_ratio",
aspect_ratio.ToString());
pending_aspect_ratio_ = aspect_ratio;
}
void ShellSurfaceBase::SetCanMinimize(bool can_minimize) {
TRACE_EVENT1("exo", "ShellSurfaceBase::SetCanMinimize", "can_minimize",
can_minimize);
can_minimize_ = can_minimize;
WidgetDelegate::SetCanMinimize(!parent_ && can_minimize_);
}
void ShellSurfaceBase::DisableMovement() {
movement_disabled_ = true;
SetCanResize(false);
if (widget_)
widget_->set_movement_disabled(true);
}
void ShellSurfaceBase::UpdateResizability() {
SetCanResize(CalculateCanResize());
auto max_size = GetMaximumSize();
// Allow maximizeing if the max size is bigger than 32k resolution.
SetCanMaximize(CanResize() && !parent_ &&
ash::desks_util::IsDeskContainerId(container_) &&
(max_size.IsEmpty() ||
(max_size.width() > ash::kAllowMaximizeThreshold &&
max_size.height() > ash::kAllowMaximizeThreshold)));
}
void ShellSurfaceBase::RebindRootSurface(Surface* root_surface,
bool can_minimize,
int container) {
can_minimize_ = can_minimize;
container_ = container;
this->root_surface()->RemoveSurfaceObserver(this);
root_surface->AddSurfaceObserver(this);
SetRootSurface(root_surface);
host_window()->Show();
// Re-apply window properties to the new root surface.
auto* window = widget_ ? widget_->GetNativeWindow() : nullptr;
if (window) {
// Int properties.
for (auto* const key :
{aura::client::kSkipImeProcessing, chromeos::kFrameRestoreLookKey,
ash::kFrameRateThrottleKey}) {
if (base::Contains(window->GetAllPropertyKeys(), key)) {
OnWindowPropertyChanged(window, key,
/*old_value(unused)=*/0);
}
}
// Boolean property.
if (base::Contains(window->GetAllPropertyKeys(),
aura::client::kWindowWorkspaceKey)) {
OnWindowPropertyChanged(window, aura::client::kWindowWorkspaceKey,
/*old_value(unused)=*/0);
}
if (window->HasFocus())
host_window()->Focus();
}
SetCanMinimize(can_minimize_);
SetCanMaximize(ash::desks_util::IsDeskContainerId(container_));
SetCanResize(true);
SetShowTitle(false);
}
std::unique_ptr<base::trace_event::TracedValue>
ShellSurfaceBase::AsTracedValue() const {
std::unique_ptr<base::trace_event::TracedValue> value(
new base::trace_event::TracedValue());
value->SetString("title", base::UTF16ToUTF8(GetWindowTitle()));
if (GetWidget() && GetWidget()->GetNativeWindow()) {
const std::string* application_id =
GetShellApplicationId(GetWidget()->GetNativeWindow());
if (application_id)
value->SetString("application_id", *application_id);
const std::string* startup_id =
GetShellStartupId(GetWidget()->GetNativeWindow());
if (startup_id)
value->SetString("startup_id", *startup_id);
}
return value;
}
void ShellSurfaceBase::AddOverlay(OverlayParams&& overlay_params) {
DCHECK(widget_);
DCHECK(!overlay_widget_);
overlay_overlaps_frame_ = overlay_params.overlaps_frame;
overlay_can_resize_ = std::move(overlay_params.can_resize);
views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
params.parent = widget_->GetNativeWindow();
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
if (overlay_params.translucent)
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
if (overlay_params.focusable)
params.activatable = views::Widget::InitParams::Activatable::kYes;
params.delegate = new views::WidgetDelegate();
params.delegate->SetOwnedByWidget(true);
params.delegate->SetContentsView(std::move(overlay_params.contents_view));
params.name = "Overlay";
overlay_widget_ = std::make_unique<views::Widget>();
overlay_widget_->Init(std::move(params));
overlay_widget_->GetNativeWindow()->SetEventTargeter(
std::make_unique<aura::WindowTargeter>());
overlay_widget_->Show();
// Setup Focus Traversal.
overlay_widget_->SetFocusTraversableParentView(this);
overlay_widget_->SetFocusTraversableParent(
GetWidget()->GetFocusTraversable());
SetFocusTraversesOut(true);
skip_ime_processing_ = GetWidget()->GetNativeWindow()->GetProperty(
aura::client::kSkipImeProcessing);
if (skip_ime_processing_) {
GetWidget()->GetNativeWindow()->SetProperty(
aura::client::kSkipImeProcessing, false);
}
UpdateWidgetBounds();
UpdateResizability();
}
void ShellSurfaceBase::RemoveOverlay() {
overlay_widget_.reset();
SetFocusTraversesOut(false);
if (skip_ime_processing_) {
GetWidget()->GetNativeWindow()->SetProperty(
aura::client::kSkipImeProcessing, true);
}
UpdateResizability();
}
////////////////////////////////////////////////////////////////////////////////
// SurfaceDelegate overrides:
void ShellSurfaceBase::OnSurfaceCommit() {
// Pause occlusion tracking since we will update a bunch of window properties.
aura::WindowOcclusionTracker::ScopedPause pause_occlusion;
// SetShadowBounds requires synchronizing shadow bounds with the next frame,
// so submit the next frame to a new surface and let the host window use the
// new surface.
if (shadow_bounds_changed_)
host_window()->AllocateLocalSurfaceId();
DCHECK(presentation_callbacks().empty());
root_surface()->CommitSurfaceHierarchy(false);
if (!OnPreWidgetCommit())
return;
CommitWidget();
OnPostWidgetCommit();
SubmitCompositorFrame();
}
bool ShellSurfaceBase::IsInputEnabled(Surface*) const {
return true;
}
void ShellSurfaceBase::OnSetFrame(SurfaceFrameType frame_type) {
if (!IsFrameDecorationSupported(frame_type)) {
DLOG(WARNING)
<< "popup does not support frame decoration other than NONE/SHADOW.";
return;
}
bool frame_was_disabled = !frame_enabled();
bool frame_type_changed = frame_type_ != frame_type;
frame_type_ = frame_type;
switch (frame_type) {
case SurfaceFrameType::NONE:
shadow_bounds_.reset();
break;
case SurfaceFrameType::NORMAL:
case SurfaceFrameType::AUTOHIDE:
case SurfaceFrameType::OVERLAY:
case SurfaceFrameType::SHADOW:
// Initialize the shadow if it didn't exist. Do not reset if
// the frame type just switched from another enabled type or
// there is a pending shadow_bounds_ change to avoid overriding
// a shadow bounds which have been changed and not yet committed.
if (frame_type_changed &&
(!shadow_bounds_ || (frame_was_disabled && !shadow_bounds_changed_)))
shadow_bounds_ = gfx::Rect();
break;
}
if (!widget_)
return;
// Override redirect window and popup can request NONE/SHADOW. The shadow
// will be updated in next commit.
if (widget_->non_client_view()) {
CustomFrameView* frame_view =
static_cast<CustomFrameView*>(widget_->non_client_view()->frame_view());
if (frame_view->GetFrameEnabled() == frame_enabled())
return;
frame_view->SetFrameEnabled(frame_enabled());
frame_view->SetShouldPaintHeader(frame_enabled());
}
widget_->GetRootView()->Layout();
// TODO(oshima): We probably should wait applying these if the
// window is animating.
UpdateWidgetBounds();
UpdateSurfaceBounds();
}
void ShellSurfaceBase::OnSetFrameColors(SkColor active_color,
SkColor inactive_color) {
has_frame_colors_ = true;
active_frame_color_ = SkColorSetA(active_color, SK_AlphaOPAQUE);
inactive_frame_color_ = SkColorSetA(inactive_color, SK_AlphaOPAQUE);
if (widget_) {
// Set kTrackDefaultFrameColors to false to prevent clobbering the active
// and inactive frame colors during theme changes.
widget_->GetNativeWindow()->SetProperty(chromeos::kTrackDefaultFrameColors,
false);
widget_->GetNativeWindow()->SetProperty(chromeos::kFrameActiveColorKey,
active_frame_color_);
widget_->GetNativeWindow()->SetProperty(chromeos::kFrameInactiveColorKey,
inactive_frame_color_);
}
}
void ShellSurfaceBase::OnSetStartupId(const char* startup_id) {
SetStartupId(startup_id);
}
void ShellSurfaceBase::OnSetApplicationId(const char* application_id) {
SetApplicationId(application_id);
}
void ShellSurfaceBase::OnActivationRequested() {
if (widget_ && HasPermissionToActivate(widget_->GetNativeWindow()))
this->Activate();
}
void ShellSurfaceBase::OnSetServerStartResize() {
server_side_resize_ = true;
}
////////////////////////////////////////////////////////////////////////////////
// SurfaceObserver overrides:
void ShellSurfaceBase::OnSurfaceDestroying(Surface* surface) {
DCHECK_EQ(root_surface(), surface);
surface->RemoveSurfaceObserver(this);
SetRootSurface(nullptr);
overlay_widget_.reset();
if (widget_)
SetShellRootSurface(widget_->GetNativeWindow(), nullptr);
// Hide widget before surface is destroyed. This allows hide animations to
// run using the current surface contents.
if (widget_) {
// Remove transient children which are shell surfaces so they are not
// automatically hidden.
CloseAllShellSurfaceTransientChildren(widget_->GetNativeWindow());
widget_->Hide();
}
// Note: In its use in the Wayland server implementation, the surface
// destroyed callback may destroy the ShellSurface instance. This call needs
// to be last so that the instance can be destroyed.
if (!surface_destroyed_callback_.is_null())
std::move(surface_destroyed_callback_).Run();
}
////////////////////////////////////////////////////////////////////////////////
// views::WidgetDelegate overrides:
bool ShellSurfaceBase::OnCloseRequested(
views::Widget::ClosedReason close_reason) {
if (!pre_close_callback_.is_null())
pre_close_callback_.Run();
// Closing the shell surface is a potentially asynchronous operation, so we
// will defer actually closing the Widget right now, and come back and call
// CloseNow() when the callback completes and the shell surface is destroyed
// (see ~ShellSurfaceBase()).
if (!close_callback_.is_null())
close_callback_.Run();
return false;
}
void ShellSurfaceBase::WindowClosing() {
SetEnabled(false);
if (widget_)
widget_->RemoveObserver(this);
widget_ = nullptr;
}
views::Widget* ShellSurfaceBase::GetWidget() {
return widget_;
}
const views::Widget* ShellSurfaceBase::GetWidget() const {
return widget_;
}
views::View* ShellSurfaceBase::GetContentsView() {
return this;
}
std::unique_ptr<views::NonClientFrameView>
ShellSurfaceBase::CreateNonClientFrameView(views::Widget* widget) {
return CreateNonClientFrameViewInternal(widget);
}
bool ShellSurfaceBase::ShouldSaveWindowPlacement() const {
return !is_popup_ && !movement_disabled_;
}
bool ShellSurfaceBase::WidgetHasHitTestMask() const {
return true;
}
void ShellSurfaceBase::GetWidgetHitTestMask(SkPath* mask) const {
GetHitTestMask(mask);
gfx::Point origin = host_window()->bounds().origin();
SkMatrix matrix;
float scale = GetScale();
matrix.setScaleTranslate(
SkFloatToScalar(1.0f / scale), SkFloatToScalar(1.0f / scale),
SkIntToScalar(origin.x()), SkIntToScalar(origin.y()));
mask->transform(matrix);
}
void ShellSurfaceBase::OnCaptureChanged(aura::Window* lost_capture,
aura::Window* gained_capture) {
if (lost_capture == widget_->GetNativeWindow() && is_popup_) {
WMHelper::GetInstance()->GetCaptureClient()->RemoveObserver(this);
if (gained_capture &&
lost_capture == wm::GetTransientParent(gained_capture)) {
// Don't close if the capture has been transferred to the child popup.
return;
}
aura::Window* parent = wm::GetTransientParent(lost_capture);
if (parent) {
// The capture needs to be transferred to the parent if it had grab.
views::Widget* parent_widget =
views::Widget::GetWidgetForNativeWindow(parent);
ShellSurfaceBase* parent_shell_surface = static_cast<ShellSurfaceBase*>(
parent_widget->widget_delegate()->GetContentsView());
if (parent_shell_surface->has_grab_)
parent_shell_surface->StartCapture();
}
widget_->Close();
}
}
////////////////////////////////////////////////////////////////////////////////
// views::WidgetObserver overrides:
void ShellSurfaceBase::OnWidgetClosing(views::Widget* widget) {
DCHECK(widget_ == widget);
// To force the widget to close we first disconnect this shell surface from
// its underlying surface, by asserting to it that the surface destroyed
// itself. After that, it is safe to call CloseNow() on the widget.
//
// TODO(crbug.com/1010326): This only closes the aura/exo pieces, but we
// should go one level deeper and destroy the wayland stuff. Some options:
// - Invoke xkill under-the-hood, which will only work for x11 and won't
// work if the container itself is stuck.
// - Close the wl connection to the client (i.e. wlkill) this is
// problematic with X11 as all of xwayland shares the same client.
// - Transitively kill all the wl_resources rooted at this window's
// wl_surface, which is not really supported in wayland.
OnSurfaceDestroying(root_surface());
}
////////////////////////////////////////////////////////////////////////////////
// views::Views overrides:
gfx::Size ShellSurfaceBase::CalculatePreferredSize() const {
if (!geometry_.IsEmpty())
return geometry_.size();
// The root surface's content bounds should be used instead of the host window
// bounds because the host window bounds are not updated until the widget is
// committed, meaning that if we need to calculate the preferred size before
// then (e.g. in OnPreWidgetCommit()), then we need to use the root surface's
// to ensure that we're using the correct bounds' size.
return root_surface()->surface_hierarchy_content_bounds().size();
}
gfx::Size ShellSurfaceBase::GetMinimumSize() const {
return minimum_size_.IsEmpty() ? gfx::Size(1, 1) : minimum_size_;
}
gfx::Size ShellSurfaceBase::GetMaximumSize() const {
// On ChromeOS, non empty maximum size will make the window
// non maximizable.
return maximum_size_;
}
void ShellSurfaceBase::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kClient;
if (application_id_) {
node_data->AddStringAttribute(
ax::mojom::StringAttribute::kChildTreeNodeAppId, *application_id_);
}
}
views::FocusTraversable* ShellSurfaceBase::GetFocusTraversable() {
return overlay_widget_.get();
}
////////////////////////////////////////////////////////////////////////////////
// aura::WindowObserver overrides:
void ShellSurfaceBase::OnWindowDestroying(aura::Window* window) {
if (window == parent_)
SetParentInternal(nullptr);
window->RemoveObserver(this);
if (widget_ && window == widget_->GetNativeWindow() && root_surface())
root_surface()->ThrottleFrameRate(false);
}
void ShellSurfaceBase::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old_value) {
if (widget_ && window == widget_->GetNativeWindow()) {
if (key == aura::client::kSkipImeProcessing) {
SetSkipImeProcessingToDescendentSurfaces(
window, window->GetProperty(aura::client::kSkipImeProcessing));
} else if (key == chromeos::kFrameRestoreLookKey) {
root_surface()->SetFrameLocked(
window->GetProperty(chromeos::kFrameRestoreLookKey));
} else if (key == aura::client::kWindowWorkspaceKey) {
root_surface()->OnDeskChanged(GetWindowDeskStateChanged(window));
} else if (key == ash::kFrameRateThrottleKey) {
root_surface()->ThrottleFrameRate(
window->GetProperty(ash::kFrameRateThrottleKey));
}
}
}
void ShellSurfaceBase::OnWindowAddedToRootWindow(aura::Window* window) {
UpdateDisplayOnTree();
}
void ShellSurfaceBase::OnWindowParentChanged(aura::Window* window,
aura::Window* parent) {
root_surface()->OnDeskChanged(GetWindowDeskStateChanged(window));
}
////////////////////////////////////////////////////////////////////////////////
// wm::ActivationChangeObserver overrides:
void ShellSurfaceBase::OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (!widget_)
return;
if (overlay_widget_ && overlay_widget_->widget_delegate()->CanActivate()) {
aura::client::FocusClient* client =
aura::client::GetFocusClient(widget_->GetNativeWindow());
client->ResetFocusWithinActiveWindow(overlay_widget_->GetNativeWindow());
}
if (gained_active == widget_->GetNativeWindow() ||
lost_active == widget_->GetNativeWindow()) {
DCHECK(gained_active != widget_->GetNativeWindow() || CanActivate());
UpdateShadow();
}
}
// Returns true if surface is currently being resized.
bool ShellSurfaceBase::IsDragged() const {
if (in_extended_drag_)
return true;
if (!widget_)
return false;
ash::WindowState* window_state =
ash::WindowState::Get(widget_->GetNativeWindow());
return window_state->is_dragged();
}
////////////////////////////////////////////////////////////////////////////////
// ui::AcceleratorTarget overrides:
bool ShellSurfaceBase::AcceleratorPressed(const ui::Accelerator& accelerator) {
for (const auto& entry : kCloseWindowAccelerators) {
if (ui::Accelerator(entry.keycode, entry.modifiers) == accelerator) {
OnCloseRequested(views::Widget::ClosedReason::kUnspecified);
return true;
}
}
return views::View::AcceleratorPressed(accelerator);
}
////////////////////////////////////////////////////////////////////////////////
// ShellSurfaceBase, protected:
void ShellSurfaceBase::CreateShellSurfaceWidget(
ui::WindowShowState show_state) {
DCHECK(GetEnabled());
DCHECK(!widget_);
// Sommelier sets the null application id for override redirect windows,
// which controls its bounds by itself.
bool emulate_x11_override_redirect =
!is_popup_ && parent_ && ash::desks_util::IsDeskContainerId(container_) &&
!application_id_.has_value();
if (emulate_x11_override_redirect) {
// override redirect is used for menu, tooltips etc, which should be placed
// above normal windows, but below lock screen. Specify the container here
// to avoid using parent_ in params.parent.
SetContainerInternal(ash::kShellWindowId_ShelfBubbleContainer);
// X11 override redirect should not be activatable.
activatable_ = false;
DisableMovement();
}
if (system_modal_)
SetModalType(ui::MODAL_TYPE_SYSTEM);
views::Widget::InitParams params;
params.type = emulate_x11_override_redirect
? views::Widget::InitParams::TYPE_MENU
: (is_popup_ ? views::Widget::InitParams::TYPE_POPUP
: views::Widget::InitParams::TYPE_WINDOW);
params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET;
params.delegate = this;
params.shadow_type = views::Widget::InitParams::ShadowType::kNone;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.show_state = show_state;
if (initial_workspace_.has_value()) {
const std::string kToggleVisibleOnAllWorkspacesValue = "-1";
if (initial_workspace_ == kToggleVisibleOnAllWorkspacesValue) {
// If |initial_workspace_| is -1, window is visible on all workspaces.
params.visible_on_all_workspaces = true;
} else {
params.workspace = initial_workspace_.value();
}
}
// Make shell surface a transient child if |parent_| has been set and
// container_ isn't specified.
aura::Window* root_window =
WMHelper::GetInstance()->GetRootWindowForNewWindows();
if (ash::desks_util::IsDeskContainerId(container_)) {
DCHECK_EQ(ash::desks_util::GetActiveDeskContainerId(), container_);
if (parent_)
params.parent = parent_;
else
params.context = root_window;
} else {
params.parent = ash::Shell::GetContainer(root_window, container_);
}
if (initial_bounds_)
params.bounds = *initial_bounds_;
else
params.bounds = gfx::Rect(origin_, gfx::Size());
WMHelper::AppPropertyResolver::Params property_resolver_params;
if (application_id_)
property_resolver_params.app_id = *application_id_;
if (startup_id_)
property_resolver_params.startup_id = *startup_id_;
property_resolver_params.for_creation = true;
WMHelper::GetInstance()->PopulateAppProperties(
property_resolver_params, params.init_properties_container);
SetShellApplicationId(&params.init_properties_container, application_id_);
SetShellRootSurface(&params.init_properties_container, root_surface());
SetShellStartupId(&params.init_properties_container, startup_id_);
bool activatable = activatable_;
// ShellSurfaces in system modal container are only activatable if input
// region is non-empty. See OnCommitSurface() for more details.
if (container_ == ash::kShellWindowId_SystemModalContainer)
activatable &= HasHitTestRegion();
// Transient child needs to have an application id to be activatable.
if (parent_)
activatable &= application_id_.has_value();
params.activatable = activatable
? views::Widget::InitParams::Activatable::kYes
: views::Widget::InitParams::Activatable::kNo;
if (restore_session_id_) {
params.init_properties_container.SetProperty(app_restore::kWindowIdKey,
*restore_session_id_);
}
if (restore_window_id_) {
params.init_properties_container.SetProperty(
app_restore::kRestoreWindowIdKey, *restore_window_id_);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Restore `params` to those of the saved `restore_window_id_`.
app_restore::ModifyWidgetParams(params.init_properties_container.GetProperty(
app_restore::kRestoreWindowIdKey),
&params);
#endif
// If app restore specifies the initial bounds, set `initial_bounds_` to it so
// that shell surface knows the initial bounds is set.
if (!params.bounds.IsEmpty() && !initial_bounds_)
initial_bounds_.emplace(params.bounds);
OverrideInitParams(&params);
// Note: NativeWidget owns this widget.
widget_ = new ShellSurfaceWidget;
widget_->Init(std::move(params));
widget_->AddObserver(this);
// As setting the pinned mode may have come in earlier we apply it now.
UpdatePinned();
aura::Window* window = widget_->GetNativeWindow();
window->SetName(base::StringPrintf("ExoShellSurface-%d", shell_id++));
window->AddChild(host_window());
// Works for both mash and non-mash. https://crbug.com/839521
window->SetEventTargetingPolicy(
aura::EventTargetingPolicy::kTargetAndDescendants);
InstallCustomWindowTargeter();
// Start tracking changes to window bounds and window state.
window->AddObserver(this);
ash::WindowState* window_state = ash::WindowState::Get(window);
InitializeWindowState(window_state);
SetShellUseImmersiveForFullscreen(window, immersive_implied_by_fullscreen_);
// Fade visibility animations for non-activatable windows.
if (!CanActivate()) {
wm::SetWindowVisibilityAnimationType(
window, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
}
// Register close window accelerators.
views::FocusManager* focus_manager = widget_->GetFocusManager();
for (const auto& entry : kCloseWindowAccelerators) {
focus_manager->RegisterAccelerator(
ui::Accelerator(entry.keycode, entry.modifiers),
ui::AcceleratorManager::kNormalPriority, this);
}
// Show widget next time Commit() is called.
if (show_state != ui::SHOW_STATE_MINIMIZED)
pending_show_widget_ = true;
UpdateDisplayOnTree();
if (frame_type_ != SurfaceFrameType::NONE)
OnSetFrame(frame_type_);
if (pending_pip_) {
SetPip();
pending_pip_ = false;
}
root_surface()->OnDeskChanged(GetWindowDeskStateChanged(window));
WMHelper::GetInstance()->NotifyExoWindowCreated(widget_->GetNativeWindow());
}
ShellSurfaceBase::OverlayParams::OverlayParams(
std::unique_ptr<views::View> overlay)
: contents_view(std::move(overlay)) {}
ShellSurfaceBase::OverlayParams::~OverlayParams() = default;
bool ShellSurfaceBase::IsResizing() const {
ash::WindowState* window_state =
ash::WindowState::Get(widget_->GetNativeWindow());
if (!window_state->is_dragged())
return false;
return window_state->drag_details() &&
(window_state->drag_details()->bounds_change &
ash::WindowResizer::kBoundsChange_Resizes);
}
void ShellSurfaceBase::UpdateWidgetBounds() {
DCHECK(widget_);
absl::optional<gfx::Rect> bounds = GetWidgetBounds();
if (bounds && overlay_widget_) {
gfx::Rect content_bounds(bounds->size());
int height = 0;
if (!overlay_overlaps_frame_ && frame_enabled()) {
auto* frame_view = static_cast<const ash::NonClientFrameViewAsh*>(
widget_->non_client_view()->frame_view());
height = frame_view->NonClientTopBorderHeight();
}
content_bounds.set_height(content_bounds.height() - height);
content_bounds.set_y(height);
overlay_widget_->SetBounds(content_bounds);
}
aura::Window* window = widget_->GetNativeWindow();
ash::WindowState* window_state = ash::WindowState::Get(window);
// Return early if the shell is currently managing the bounds of the widget.
if (!window_state->allow_set_bounds_direct()) {
// 1) When a window is either maximized/fullscreen/pinned.
if (window_state->IsMaximizedOrFullscreenOrPinned())
return;
// 2) When a window is snapped.
if (window_state->IsSnapped())
return;
// 3) When a window is being interactively resized.
if (IsResizing())
return;
// 4) When a window's bounds are being animated.
if (window->layer()->GetAnimator()->IsAnimatingProperty(
ui::LayerAnimationElement::BOUNDS))
return;
}
if (bounds)
SetWidgetBounds(*bounds);
}
void ShellSurfaceBase::UpdateSurfaceBounds() {
gfx::Point origin = GetClientViewBounds().origin();
origin += GetSurfaceOrigin().OffsetFromOrigin();
origin -= ToFlooredVector2d(ScaleVector2d(
root_surface_origin().OffsetFromOrigin(), 1.f / GetScale()));
gfx::Rect surface_bounds(origin, host_window()->bounds().size());
if (host_window()->bounds() == surface_bounds)
return;
host_window()->SetBounds(surface_bounds);
}
void ShellSurfaceBase::UpdateShadow() {
if (!widget_ || !root_surface())
return;
aura::Window* window = widget_->GetNativeWindow();
if (!shadow_bounds_) {
wm::SetShadowElevation(window, wm::kShadowElevationNone);
} else {
// Use a small style shadow for popup surface.
if (frame_type_ == SurfaceFrameType::SHADOW && is_popup_)
wm::SetShadowElevation(window, wm::kShadowElevationMenuOrTooltip);
else
wm::SetShadowElevation(window, wm::kShadowElevationDefault);
ui::Shadow* shadow = wm::ShadowController::GetShadowForWindow(window);
// Maximized/Fullscreen window does not create a shadow.
if (!shadow)
return;
gfx::Rect shadow_bounds = GetShadowBounds();
gfx::Point origin = GetClientViewBounds().origin();
if (!window->GetProperty(aura::client::kUseWindowBoundsForShadow)) {
origin += GetSurfaceOrigin().OffsetFromOrigin();
origin -= ToFlooredVector2d(ScaleVector2d(
root_surface_origin().OffsetFromOrigin(), 1.f / GetScale()));
if (origin.x() != 0 || origin.y() != 0) {
shadow_bounds.set_origin(origin);
if (widget_) {
gfx::Point widget_origin =
widget_->GetWindowBoundsInScreen().origin();
origin += ToFlooredVector2d(
ScaleVector2d(gfx::Vector2d(widget_origin.x(), widget_origin.y()),
1.f / GetScale()));
gfx::Rect bounds = geometry_;
bounds.set_origin(origin);
ash::Shell::Get()
->resize_shadow_controller()
->UpdateResizeShadowBoundsOfWindow(widget_->GetNativeWindow(),
bounds);
}
}
}
shadow->SetContentBounds(shadow_bounds);
// Surfaces that can't be activated are usually menus and tooltips. Use a
// small style shadow for them.
if (!CanActivate())
shadow->SetElevation(wm::kShadowElevationMenuOrTooltip);
UpdateCornerRadius();
}
}
void ShellSurfaceBase::UpdateCornerRadius() {
if (!widget_)
return;
if (!ash::features::IsPipRoundedCornersEnabled())
return;
ash::WindowState* window_state =
ash::WindowState::Get(widget_->GetNativeWindow());
// The host window's transform scales by |1/GetScale()| but we do not want the
// rounded corners scaled that way. So we multiply the radius by |GetScale()|.
ash::SetCornerRadius(
window_state->window(), host_window()->layer(),
window_state->IsPip()
? base::ClampRound(GetScale() * ash::kPipRoundedCornerRadius)
: 0);
}
void ShellSurfaceBase::UpdateFrameType() {
// Nothing to do here for now as frame type is updated immediately in
// OnSetFrame() by default.
}
gfx::Rect ShellSurfaceBase::GetVisibleBounds() const {
// Use |geometry_| if set, otherwise use the visual bounds of the surface.
if (geometry_.IsEmpty()) {
gfx::Size size;
if (root_surface()) {
float int_part;
DCHECK(std::modf(root_surface()->content_size().width(), &int_part) ==
0.0f &&
std::modf(root_surface()->content_size().height(), &int_part) ==
0.0f);
size = gfx::ToCeiledSize(root_surface()->content_size());
if (client_submits_surfaces_in_pixel_coordinates()) {
float dsf = host_window()->layer()->device_scale_factor();
size = gfx::ScaleToRoundedSize(size, 1.0f / dsf);
}
}
return gfx::Rect(size);
}
const auto* screen = display::Screen::GetScreen();
display::Display display;
if (!screen->GetDisplayWithDisplayId(display_id_, &display))
return geometry_;
// Convert from display to screen coordinates.
return geometry_ + display.bounds().OffsetFromOrigin();
}
gfx::Rect ShellSurfaceBase::GetClientViewBounds() const {
return widget_->non_client_view()
? widget_->non_client_view()
->frame_view()
->GetBoundsForClientView()
: gfx::Rect(widget_->GetWindowBoundsInScreen().size());
}
gfx::Rect ShellSurfaceBase::GetWidgetBoundsFromVisibleBounds() const {
auto visible_bounds = GetVisibleBounds();
return widget_->non_client_view()
? widget_->non_client_view()->GetWindowBoundsForClientBounds(
visible_bounds)
: visible_bounds;
}
gfx::Rect ShellSurfaceBase::GetShadowBounds() const {
return shadow_bounds_->IsEmpty()
? gfx::Rect(widget_->GetNativeWindow()->bounds().size())
: gfx::ScaleToEnclosedRect(*shadow_bounds_, 1.f / GetScale());
}
void ShellSurfaceBase::InstallCustomWindowTargeter() {
aura::Window* window = widget_->GetNativeWindow();
window->SetEventTargeter(std::make_unique<CustomWindowTargeter>(this));
}
std::unique_ptr<views::NonClientFrameView>
ShellSurfaceBase::CreateNonClientFrameViewInternal(views::Widget* widget) {
aura::Window* window = widget_->GetNativeWindow();
// ShellSurfaces always use immersive mode.
window->SetProperty(chromeos::kImmersiveIsActive, true);
ash::WindowState* window_state = ash::WindowState::Get(window);
if (!frame_enabled() && !window_state->HasDelegate()) {
window_state->SetDelegate(std::make_unique<CustomWindowStateDelegate>());
}
auto frame_view =
std::make_unique<CustomFrameView>(widget, this, frame_enabled());
if (has_frame_colors_)
frame_view->SetFrameColors(active_frame_color_, inactive_frame_color_);
return frame_view;
}
bool ShellSurfaceBase::ShouldExitFullscreenFromRestoreOrMaximized() {
if (widget_ && widget_->GetNativeWindow()) {
return widget_->GetNativeWindow()->GetProperty(
kRestoreOrMaximizeExitsFullscreen);
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// ShellSurfaceBase, private:
float ShellSurfaceBase::GetScale() const {
return 1.f;
}
void ShellSurfaceBase::StartCapture() {
widget_->set_auto_release_capture(false);
WMHelper::GetInstance()->GetCaptureClient()->AddObserver(this);
// Just capture on the window.
widget_->SetCapture(nullptr /* view */);
}
void ShellSurfaceBase::OnPostWidgetCommit() {
// |shadow_bounds_changed_| represents whether |shadow_bounds_| has changed
// since the last commit, but as UpdateShadow() can be called multiple times
// in a single commit process, we need to ensure that it's not reset halfway
// in the current commit by resetting it here.
shadow_bounds_changed_ = false;
}
void ShellSurfaceBase::SetContainerInternal(int container) {
container_ = container;
WidgetDelegate::SetCanMaximize(
!parent_ && ash::desks_util::IsDeskContainerId(container_));
if (widget_)
widget_->OnSizeConstraintsChanged();
}
void ShellSurfaceBase::SetParentInternal(aura::Window* parent) {
parent_ = parent;
WidgetDelegate::SetCanMinimize(!parent_ && can_minimize_);
UpdateResizability();
if (widget_)
widget_->OnSizeConstraintsChanged();
}
bool ShellSurfaceBase::CalculateCanResize() const {
if (overlay_widget_ && overlay_can_resize_.has_value())
return *overlay_can_resize_;
return !movement_disabled_ && GetCanResizeFromSizeConstraints();
}
void ShellSurfaceBase::CommitWidget() {
// Apply new window geometry.
geometry_ = pending_geometry_;
display_id_ = pending_display_id_;
// Apply new minimum/maximium size.
bool size_constraint_changed = minimum_size_ != pending_minimum_size_ ||
maximum_size_ != pending_maximum_size_;
minimum_size_ = pending_minimum_size_;
maximum_size_ = pending_maximum_size_;
UpdateResizability();
if (!widget_)
return;
if (!pending_aspect_ratio_.IsEmpty()) {
widget_->SetAspectRatio(pending_aspect_ratio_);
} else if (widget_->GetNativeWindow()) {
// TODO(yoshiki): Move the logic to clear aspect ratio into view::Widget.
widget_->GetNativeWindow()->ClearProperty(aura::client::kAspectRatio);
}
UpdateWidgetBounds();
SurfaceTreeHost::UpdateHostWindowBounds();
UpdateFrameType();
gfx::Rect bounds = geometry_;
if (!bounds.IsEmpty() && !widget_->GetNativeWindow()->GetProperty(
aura::client::kUseWindowBoundsForShadow)) {
SetBoundsForShadows(absl::make_optional(bounds));
}
UpdateShadow();
// System modal container is used by clients to implement overlay
// windows using a single ShellSurface instance. If hit-test
// region is empty, then it is non interactive window and won't be
// activated.
if (container_ == ash::kShellWindowId_SystemModalContainer) {
// Prevent window from being activated when hit test region is empty.
bool activatable = activatable_ && HasHitTestRegion();
if (activatable != CanActivate()) {
SetCanActivate(activatable);
// Activate or deactivate window if activation state changed.
if (activatable) {
// Automatically activate only if the window is modal.
// Non modal window should be activated by a user action.
// TODO(oshima): Non modal system window does not have an associated
// task ID, and as a result, it cannot be activated from client side.
// Fix this (b/65460424) and remove this if condition.
if (system_modal_)
wm::ActivateWindow(widget_->GetNativeWindow());
} else if (widget_->IsActive()) {
wm::DeactivateWindow(widget_->GetNativeWindow());
}
}
}
UpdateSurfaceBounds();
// Don't show yet if the shell surface doesn't have content or is minimized
// while waiting for content.
bool should_show =
!host_window()->bounds().IsEmpty() && !widget_->IsMinimized();
// Do not layout the window if the position should not be controlled by window
// manager. (popup, emulating x11 override direct, or requested not to move)
if (is_popup_ || movement_disabled_)
needs_layout_on_show_ = false;
// Do not center if the initial bounds is set.
if (initial_bounds_)
needs_layout_on_show_ = false;
// Show widget if needed.
if (pending_show_widget_ && should_show) {
DCHECK(!widget_->IsClosed());
DCHECK(!widget_->IsVisible());
pending_show_widget_ = false;
auto* window = widget_->GetNativeWindow();
auto* window_state = ash::WindowState::Get(window);
// TODO(crbug.com/1261321): correct the initial origin once lacros can
// communicate it instead of centering.
if (window_state->IsMaximizedOrFullscreenOrPinned()) {
gfx::Size current_content_size = CalculatePreferredSize();
gfx::Rect restore_bounds = display::Screen::GetScreen()
->GetDisplayNearestWindow(window)
.work_area();
if (!current_content_size.IsEmpty())
restore_bounds.ClampToCenteredSize(current_content_size);
window_state->SetRestoreBoundsInScreen(restore_bounds);
}
// TODO(crbug.com/1291592): Hook this up with the WM's window positioning
// logic.
if (needs_layout_on_show_)
widget_->CenterWindow(GetWidgetBoundsFromVisibleBounds().size());
widget_->Show();
if (has_grab_)
StartCapture();
if (container_ == ash::kShellWindowId_SystemModalContainer)
UpdateSystemModal();
}
if (size_constraint_changed)
widget_->OnSizeConstraintsChanged();
}
bool ShellSurfaceBase::IsFrameDecorationSupported(SurfaceFrameType frame_type) {
if (!is_popup_)
return true;
// Popup doesn't support frame types other than NONE/SHADOW.
return frame_type == SurfaceFrameType::SHADOW ||
frame_type == SurfaceFrameType::NONE;
}
void ShellSurfaceBase::SetOrientationLock(
chromeos::OrientationType orientation_lock) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
TRACE_EVENT1("exo", "ShellSurfaceBase::SetOrientationLock",
"orientation_lock", static_cast<int>(orientation_lock));
if (!widget_) {
initial_orientation_lock_ = orientation_lock;
return;
}
ash::Shell* shell = ash::Shell::Get();
shell->screen_orientation_controller()->LockOrientationForWindow(
widget_->GetNativeWindow(), orientation_lock);
#else
NOTREACHED();
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
} // namespace exo