| // 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/frame/non_client_frame_view_ash.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/public/cpp/window_state_type.h" |
| #include "ash/public/interfaces/window_pin_type.mojom.h" |
| #include "ash/shell.h" |
| #include "ash/wm/drag_window_resizer.h" |
| #include "ash/wm/window_resizer.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_state_delegate.h" |
| #include "ash/wm/window_util.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/traced_value.h" |
| #include "cc/trees/layer_tree_frame_sink.h" |
| #include "components/exo/shell_surface_util.h" |
| #include "components/exo/surface.h" |
| #include "components/exo/wm_helper.h" |
| #include "services/ws/public/mojom/window_tree_constants.mojom.h" |
| #include "third_party/skia/include/core/SkPath.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/window.h" |
| #include "ui/aura/window_observer.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/dip_util.h" |
| #include "ui/compositor_extra/shadow.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/geometry/vector2d_conversions.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; |
| |
| // Overridden from views::Widget: |
| void OnKeyEvent(ui::KeyEvent* event) override { |
| // Handle only accelerators. Do not call Widget::OnKeyEvent that eats focus |
| // management keys (like the tab key) as well. |
| if (GetFocusManager()->ProcessAccelerator(ui::Accelerator(*event))) |
| event->SetHandled(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ShellSurfaceWidget); |
| }; |
| |
| class CustomFrameView : public ash::NonClientFrameViewAsh, |
| public aura::WindowObserver { |
| public: |
| using ShapeRects = std::vector<gfx::Rect>; |
| |
| CustomFrameView(views::Widget* widget, |
| ShellSurfaceBase* shell_surface, |
| bool enabled, |
| bool client_controlled_move_resize) |
| : NonClientFrameViewAsh(widget), |
| shell_surface_(shell_surface), |
| client_controlled_move_resize_(client_controlled_move_resize) { |
| SetEnabled(enabled); |
| SetVisible(enabled); |
| if (!enabled) |
| NonClientFrameViewAsh::SetShouldPaintHeader(false); |
| |
| frame()->GetNativeWindow()->AddObserver(this); |
| } |
| |
| ~CustomFrameView() override { |
| if (frame() && frame()->GetNativeWindow() && |
| frame()->GetNativeWindow()->HasObserver(this)) { |
| frame()->GetNativeWindow()->RemoveObserver(this); |
| } |
| } |
| |
| // Overridden from ash::NonClientFrameViewAsh: |
| void SetShouldPaintHeader(bool paint) override { |
| if (visible()) { |
| NonClientFrameViewAsh::SetShouldPaintHeader(paint); |
| return; |
| } |
| } |
| |
| // Overridden from aura::WindowObserver: |
| void OnWindowBoundsChanged(aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds, |
| ui::PropertyChangeReason reason) override { |
| // When window bounds are changed, we need to update the header view so that |
| // the window mask layer bounds can be set correctly in function |
| // SetShouldPaintHeader(). Note: this can be removed if the layer mask in |
| // CustomFrameView becomes unnecessary. |
| // TODO(oshima): Investigate if we can eliminate this. |
| NonClientFrameViewAsh::UpdateHeaderView(); |
| } |
| |
| void OnWindowDestroying(aura::Window* window) override { |
| DCHECK_EQ(frame()->GetNativeWindow(), window); |
| window->RemoveObserver(this); |
| } |
| |
| // Overridden from views::NonClientFrameView: |
| gfx::Rect GetBoundsForClientView() const override { |
| if (visible()) |
| return ash::NonClientFrameViewAsh::GetBoundsForClientView(); |
| return bounds(); |
| } |
| gfx::Rect GetWindowBoundsForClientBounds( |
| const gfx::Rect& client_bounds) const override { |
| if (visible()) { |
| return ash::NonClientFrameViewAsh::GetWindowBoundsForClientBounds( |
| client_bounds); |
| } |
| return client_bounds; |
| } |
| int NonClientHitTest(const gfx::Point& point) override { |
| if (visible() || !client_controlled_move_resize_) |
| return ash::NonClientFrameViewAsh::NonClientHitTest(point); |
| return GetWidget()->client_view()->NonClientHitTest(point); |
| } |
| void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override { |
| if (visible()) |
| return ash::NonClientFrameViewAsh::GetWindowMask(size, window_mask); |
| } |
| void ResetWindowControls() override { |
| if (visible()) |
| return ash::NonClientFrameViewAsh::ResetWindowControls(); |
| } |
| void UpdateWindowIcon() override { |
| if (visible()) |
| return ash::NonClientFrameViewAsh::ResetWindowControls(); |
| } |
| void UpdateWindowTitle() override { |
| if (visible()) |
| return ash::NonClientFrameViewAsh::UpdateWindowTitle(); |
| } |
| void SizeConstraintsChanged() override { |
| if (visible()) |
| return ash::NonClientFrameViewAsh::SizeConstraintsChanged(); |
| } |
| gfx::Size GetMinimumSize() const override { |
| gfx::Size minimum_size = shell_surface_->GetMinimumSize(); |
| if (visible()) { |
| 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 (visible() && !maximum_size.IsEmpty()) { |
| return ash::NonClientFrameViewAsh::GetWindowBoundsForClientBounds( |
| gfx::Rect(maximum_size)) |
| .size(); |
| } |
| return maximum_size; |
| } |
| |
| private: |
| ShellSurfaceBase* const shell_surface_; |
| // TODO(oshima): Remove this once the transition to new drag/resize |
| // is complete. https://crbug.com/801666. |
| const bool client_controlled_move_resize_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CustomFrameView); |
| }; |
| |
| class CustomWindowTargeter : public aura::WindowTargeter { |
| public: |
| CustomWindowTargeter(views::Widget* widget, |
| bool client_controlled_move_resize) |
| : widget_(widget), |
| client_controlled_move_resize_(client_controlled_move_resize) {} |
| ~CustomWindowTargeter() override {} |
| |
| // Overridden from aura::WindowTargeter: |
| bool EventLocationInsideBounds(aura::Window* window, |
| const ui::LocatedEvent& event) const override { |
| gfx::Point local_point = event.location(); |
| |
| if (window->parent()) { |
| aura::Window::ConvertPointToTarget(window->parent(), window, |
| &local_point); |
| } |
| |
| if (IsInResizeHandle(window, event, local_point)) |
| return true; |
| |
| Surface* surface = GetShellMainSurface(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; |
| } |
| |
| // Use ash's resize handle detection logic if |
| // a) ClientControlledShellSurface uses server side resize or |
| // b) xdg shell is using the server side decoration. |
| if (ash::wm::GetWindowState(widget_->GetNativeWindow()) |
| ->allow_set_bounds_direct() |
| ? client_controlled_move_resize_ |
| : !widget_->non_client_view()->frame_view()->visible()) { |
| 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; |
| } |
| |
| views::Widget* const widget_; |
| const bool client_controlled_move_resize_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CustomWindowTargeter); |
| }; |
| |
| // A place holder to disable default implementation created by |
| // ash::NonClientFrameViewAsh, which triggers immersive fullscreen etc, which |
| // we don't need. |
| class CustomWindowStateDelegate : public ash::wm::WindowStateDelegate { |
| public: |
| CustomWindowStateDelegate() {} |
| ~CustomWindowStateDelegate() override {} |
| |
| // Overridden from ash::wm::WindowStateDelegate: |
| bool ToggleFullscreen(ash::wm::WindowState* window_state) override { |
| return false; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(CustomWindowStateDelegate); |
| }; |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ShellSurfaceBase, public: |
| |
| ShellSurfaceBase::ShellSurfaceBase(Surface* surface, |
| const gfx::Point& origin, |
| bool activatable, |
| bool can_minimize, |
| int container) |
| : SurfaceTreeHost("ExoShellSurfaceHost"), |
| origin_(origin), |
| container_(container), |
| activatable_(activatable), |
| can_minimize_(can_minimize) { |
| WMHelper::GetInstance()->AddActivationObserver(this); |
| surface->AddSurfaceObserver(this); |
| SetRootSurface(surface); |
| host_window()->Show(); |
| set_owned_by_client(); |
| } |
| |
| ShellSurfaceBase::~ShellSurfaceBase() { |
| // Remove activation observer before hiding widget to prevent it from |
| // casuing the configure callback to be called. |
| WMHelper::GetInstance()->RemoveActivationObserver(this); |
| if (widget_) { |
| widget_->GetNativeWindow()->RemoveObserver(this); |
| // Remove transient children so they are not automatically destroyed. |
| for (auto* child : wm::GetTransientChildren(widget_->GetNativeWindow())) |
| wm::RemoveTransientChild(widget_->GetNativeWindow(), child); |
| 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); |
| } |
| |
| void ShellSurfaceBase::Activate() { |
| TRACE_EVENT0("exo", "ShellSurfaceBase::Activate"); |
| |
| if (!widget_ || widget_->IsActive()) |
| return; |
| |
| widget_->Activate(); |
| } |
| |
| void ShellSurfaceBase::SetTitle(const base::string16& title) { |
| TRACE_EVENT1("exo", "ShellSurfaceBase::SetTitle", "title", |
| base::UTF16ToUTF8(title)); |
| |
| title_ = title; |
| if (widget_) |
| widget_->UpdateWindowTitle(); |
| } |
| |
| void ShellSurfaceBase::SetIcon(const gfx::ImageSkia& icon) { |
| TRACE_EVENT0("exo", "ShellSurfaceBase::SetIcon"); |
| |
| icon_ = icon; |
| if (widget_) |
| widget_->UpdateWindowIcon(); |
| } |
| |
| void ShellSurfaceBase::SetSystemModal(bool system_modal) { |
| // System modal container is used by clients to implement client side |
| // managed system modal dialogs using a single ShellSurface instance. |
| // Hit-test region will be non-empty when at least one dialog exists on |
| // the client side. Here we detect the transition between no client side |
| // dialog and at least one dialog so activatable state is properly |
| // updated. |
| if (container_ != ash::kShellWindowId_SystemModalContainer) { |
| LOG(ERROR) |
| << "Only a window in SystemModalContainer can change the modality"; |
| return; |
| } |
| |
| 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::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_); |
| } |
| |
| 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::SetChildAxTreeId(ui::AXTreeID child_ax_tree_id) { |
| child_ax_tree_id_ = child_ax_tree_id; |
| |
| this->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false); |
| } |
| |
| void ShellSurfaceBase::Close() { |
| if (!close_callback_.is_null()) |
| close_callback_.Run(); |
| } |
| |
| 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::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); |
| |
| container_ = 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; |
| } |
| |
| void ShellSurfaceBase::DisableMovement() { |
| movement_disabled_ = true; |
| |
| if (widget_) |
| widget_->set_movement_disabled(true); |
| } |
| |
| 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(title_)); |
| 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; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // SurfaceDelegate overrides: |
| |
| void ShellSurfaceBase::OnSurfaceCommit() { |
| // 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(); |
| |
| SurfaceTreeHost::OnSurfaceCommit(); |
| |
| if (!OnPreWidgetCommit()) |
| return; |
| |
| CommitWidget(); |
| OnPostWidgetCommit(); |
| |
| SubmitCompositorFrame(); |
| } |
| |
| bool ShellSurfaceBase::IsInputEnabled(Surface*) const { |
| return true; |
| } |
| |
| void ShellSurfaceBase::OnSetFrame(SurfaceFrameType frame_type) { |
| if (is_popup_) { |
| // TODO(oshima): Consider supporting shadow type. |
| DLOG(WARNING) << "popup does not support frame decoration"; |
| return; |
| } |
| |
| bool frame_was_disabled = !frame_enabled(); |
| frame_type_ = frame_type; |
| switch (frame_type) { |
| case SurfaceFrameType::NONE: |
| shadow_bounds_.reset(); |
| break; |
| case SurfaceFrameType::NORMAL: |
| case SurfaceFrameType::AUTOHIDE: |
| case SurfaceFrameType::OVERLAY: |
| // Initialize the shadow if it didn't exist. Do not reset if |
| // the frame type just switched from another enabled type. |
| if (!shadow_bounds_ || frame_was_disabled) |
| shadow_bounds_ = gfx::Rect(); |
| break; |
| case SurfaceFrameType::SHADOW: |
| shadow_bounds_ = gfx::Rect(); |
| break; |
| } |
| if (!widget_) |
| return; |
| CustomFrameView* frame_view = |
| static_cast<CustomFrameView*>(widget_->non_client_view()->frame_view()); |
| if (frame_view->enabled() == frame_enabled()) |
| return; |
| |
| frame_view->SetEnabled(frame_enabled()); |
| frame_view->SetVisible(frame_enabled()); |
| frame_view->SetShouldPaintHeader(frame_enabled()); |
| frame_view->SetHeaderHeight(base::nullopt); |
| 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_) { |
| widget_->GetNativeWindow()->SetProperty(ash::kFrameActiveColorKey, |
| active_frame_color_); |
| widget_->GetNativeWindow()->SetProperty(ash::kFrameInactiveColorKey, |
| inactive_frame_color_); |
| } |
| } |
| |
| void ShellSurfaceBase::OnSetStartupId(const char* startup_id) { |
| SetStartupId(startup_id); |
| } |
| |
| void ShellSurfaceBase::OnSetApplicationId(const char* application_id) { |
| SetApplicationId(application_id); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // SurfaceObserver overrides: |
| |
| void ShellSurfaceBase::OnSurfaceDestroying(Surface* surface) { |
| DCHECK_EQ(root_surface(), surface); |
| surface->RemoveSurfaceObserver(this); |
| SetRootSurface(nullptr); |
| |
| if (widget_) |
| SetShellMainSurface(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 so they are not automatically hidden. |
| for (auto* child : wm::GetTransientChildren(widget_->GetNativeWindow())) |
| wm::RemoveTransientChild(widget_->GetNativeWindow(), child); |
| 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::CanResize() const { |
| if (movement_disabled_) |
| return false; |
| // The shell surface is resizable by default when min/max size is empty, |
| // othersize it's resizable when min size != max size. |
| return minimum_size_.IsEmpty() || minimum_size_ != maximum_size_; |
| } |
| |
| bool ShellSurfaceBase::CanMaximize() const { |
| // Shell surfaces in system modal container cannot be maximized. |
| if (container_ != ash::kShellWindowId_DefaultContainer) |
| return false; |
| |
| // Non-transient shell surfaces can be maximized. |
| return !parent_; |
| } |
| |
| bool ShellSurfaceBase::CanMinimize() const { |
| // Non-transient shell surfaces can be minimized. |
| return !parent_ && can_minimize_; |
| } |
| |
| base::string16 ShellSurfaceBase::GetWindowTitle() const { |
| return title_; |
| } |
| |
| bool ShellSurfaceBase::ShouldShowWindowTitle() const { |
| return false; |
| } |
| |
| gfx::ImageSkia ShellSurfaceBase::GetWindowIcon() { |
| return icon_; |
| } |
| |
| bool ShellSurfaceBase::OnCloseRequested( |
| views::Widget::ClosedReason close_reason) { |
| // 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()). |
| Close(); |
| return false; |
| } |
| |
| void ShellSurfaceBase::WindowClosing() { |
| SetEnabled(false); |
| widget_ = nullptr; |
| } |
| |
| views::Widget* ShellSurfaceBase::GetWidget() { |
| return widget_; |
| } |
| |
| const views::Widget* ShellSurfaceBase::GetWidget() const { |
| return widget_; |
| } |
| |
| views::View* ShellSurfaceBase::GetContentsView() { |
| return this; |
| } |
| |
| views::NonClientFrameView* ShellSurfaceBase::CreateNonClientFrameView( |
| views::Widget* widget) { |
| aura::Window* window = widget_->GetNativeWindow(); |
| // ShellSurfaces always use immersive mode. |
| window->SetProperty(ash::kImmersiveIsActive, true); |
| ash::wm::WindowState* window_state = ash::wm::GetWindowState(window); |
| if (!frame_enabled() && !window_state->HasDelegate()) { |
| window_state->SetDelegate(std::make_unique<CustomWindowStateDelegate>()); |
| } |
| CustomFrameView* frame_view = new CustomFrameView( |
| widget, this, frame_enabled(), client_controlled_move_resize_); |
| if (has_frame_colors_) |
| frame_view->SetFrameColors(active_frame_color_, inactive_frame_color_); |
| return frame_view; |
| } |
| |
| 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::Views overrides: |
| |
| gfx::Size ShellSurfaceBase::CalculatePreferredSize() const { |
| if (!geometry_.IsEmpty()) |
| return geometry_.size(); |
| |
| return host_window()->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 (child_ax_tree_id_ == ui::AXTreeIDUnknown()) |
| return; |
| |
| node_data->AddStringAttribute(ax::mojom::StringAttribute::kChildTreeId, |
| child_ax_tree_id_.ToString()); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // aura::WindowObserver overrides: |
| |
| void ShellSurfaceBase::OnWindowDestroying(aura::Window* window) { |
| if (window == parent_) { |
| parent_ = nullptr; |
| // |parent_| being set to null effects the ability to maximize the window. |
| if (widget_) |
| widget_->OnSizeConstraintsChanged(); |
| } |
| window->RemoveObserver(this); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // wm::ActivationChangeObserver overrides: |
| |
| void ShellSurfaceBase::OnWindowActivated(ActivationReason reason, |
| aura::Window* gained_active, |
| aura::Window* lost_active) { |
| if (!widget_) |
| return; |
| |
| if (gained_active == widget_->GetNativeWindow() || |
| lost_active == widget_->GetNativeWindow()) { |
| DCHECK(gained_active != widget_->GetNativeWindow() || CanActivate()); |
| UpdateShadow(); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ui::AcceleratorTarget overrides: |
| |
| bool ShellSurfaceBase::AcceleratorPressed(const ui::Accelerator& accelerator) { |
| for (const auto& entry : kCloseWindowAccelerators) { |
| if (ui::Accelerator(entry.keycode, entry.modifiers) == accelerator) { |
| if (!close_callback_.is_null()) |
| close_callback_.Run(); |
| return true; |
| } |
| } |
| return views::View::AcceleratorPressed(accelerator); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ShellSurfaceBase, protected: |
| |
| void ShellSurfaceBase::CreateShellSurfaceWidget( |
| ui::WindowShowState show_state) { |
| DCHECK(enabled()); |
| DCHECK(!widget_); |
| |
| views::Widget::InitParams params; |
| params.type = 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::SHADOW_TYPE_NONE; |
| params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| params.show_state = show_state; |
| // Make shell surface a transient child if |parent_| has been set. |
| params.parent = |
| parent_ ? parent_ |
| : ash::Shell::GetContainer( |
| WMHelper::GetInstance()->GetRootWindowForNewWindows(), |
| container_); |
| params.bounds = gfx::Rect(origin_, gfx::Size()); |
| 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_YES |
| : views::Widget::InitParams::ACTIVATABLE_NO; |
| // Note: NativeWidget owns this widget. |
| widget_ = new ShellSurfaceWidget; |
| widget_->Init(params); |
| |
| aura::Window* window = widget_->GetNativeWindow(); |
| window->SetName("ExoShellSurface"); |
| window->AddChild(host_window()); |
| // Works for both mash and non-mash. https://crbug.com/839521 |
| window->SetEventTargetingPolicy( |
| ws::mojom::EventTargetingPolicy::TARGET_AND_DESCENDANTS); |
| InstallCustomWindowTargeter(); |
| SetShellApplicationId(window, application_id_); |
| SetShellStartupId(window, startup_id_); |
| SetShellMainSurface(window, root_surface()); |
| |
| // Start tracking changes to window bounds and window state. |
| window->AddObserver(this); |
| ash::wm::WindowState* window_state = ash::wm::GetWindowState(window); |
| InitializeWindowState(window_state); |
| |
| // AutoHide shelf in fullscreen state. |
| window_state->SetHideShelfWhenFullscreen(false); |
| |
| // 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. |
| pending_show_widget_ = true; |
| } |
| |
| bool ShellSurfaceBase::IsResizing() const { |
| ash::wm::WindowState* window_state = |
| ash::wm::GetWindowState(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_); |
| |
| aura::Window* window = widget_->GetNativeWindow(); |
| ash::wm::WindowState* window_state = ash::wm::GetWindowState(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; |
| } |
| |
| base::Optional<gfx::Rect> bounds = GetWidgetBounds(); |
| 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())); |
| |
| host_window()->SetBounds(gfx::Rect(origin, host_window()->bounds().size())); |
| // The host window might have not been added to the widget yet. |
| if (host_window()->parent()) { |
| ui::SnapLayerToPhysicalPixelBoundary(widget_->GetNativeWindow()->layer(), |
| host_window()->layer()); |
| } |
| } |
| |
| void ShellSurfaceBase::UpdateShadow() { |
| if (!widget_ || !root_surface()) |
| return; |
| |
| shadow_bounds_changed_ = false; |
| |
| aura::Window* window = widget_->GetNativeWindow(); |
| |
| if (!shadow_bounds_) { |
| wm::SetShadowElevation(window, wm::kShadowElevationNone); |
| } else { |
| wm::SetShadowElevation(window, wm::kShadowElevationDefault); |
| |
| ui::Shadow* shadow = wm::ShadowController::GetShadowForWindow(window); |
| // Maximized/Fullscreen window does not create a shadow. |
| if (!shadow) |
| return; |
| |
| shadow->SetContentBounds(GetShadowBounds()); |
| // Surfaces that can't be activated are usually menus and tooltips. Use a |
| // small style shadow for them. |
| if (!CanActivate()) |
| shadow->SetElevation(wm::kShadowElevationMenuOrTooltip); |
| // We don't have rounded corners unless frame is enabled. |
| if (!frame_enabled()) |
| shadow->SetRoundedCornerRadius(0); |
| } |
| } |
| |
| gfx::Rect ShellSurfaceBase::GetVisibleBounds() const { |
| // Use |geometry_| if set, otherwise use the visual bounds of the surface. |
| if (geometry_.IsEmpty()) { |
| return root_surface() ? gfx::Rect(root_surface()->content_size()) |
| : gfx::Rect(); |
| } |
| |
| 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::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>( |
| widget_, client_controlled_move_resize_)); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ShellSurfaceBase, private: |
| |
| float ShellSurfaceBase::GetScale() const { |
| return 1.f; |
| } |
| |
| void ShellSurfaceBase::SetParentWindow(aura::Window* parent) { |
| if (parent_) { |
| parent_->RemoveObserver(this); |
| if (widget_) |
| wm::RemoveTransientChild(parent_, widget_->GetNativeWindow()); |
| } |
| parent_ = parent; |
| if (parent_) { |
| parent_->AddObserver(this); |
| if (widget_) |
| wm::AddTransientChild(parent_, widget_->GetNativeWindow()); |
| } |
| |
| // If |parent_| is set effects the ability to maximize the window. |
| if (widget_) |
| widget_->OnSizeConstraintsChanged(); |
| } |
| |
| 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::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_; |
| |
| 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(); |
| 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(); |
| |
| // Show widget if needed. |
| if (pending_show_widget_) { |
| DCHECK(!widget_->IsClosed()); |
| DCHECK(!widget_->IsVisible()); |
| pending_show_widget_ = false; |
| widget_->Show(); |
| if (has_grab_) |
| StartCapture(); |
| |
| if (container_ == ash::kShellWindowId_SystemModalContainer) |
| UpdateSystemModal(); |
| } |
| |
| if (size_constraint_changed) |
| widget_->OnSizeConstraintsChanged(); |
| } |
| |
| } // namespace exo |