// 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 #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/chromeos_ui_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 "chromeos/ui/frame/multitask_menu/float_controller_base.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/security_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; 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(window)->GetParentTarget(); if (parent) { aura::WindowTargeter* parent_targeter = static_cast(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& 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; } pending_pip_ = false; // 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); if (initial_bounds_) return; // If no initial bounds is specified, 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); } 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) { // TODO(crbug.com/1327490): Rename restore info variables. // 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::SetRestoreInfoWithWindowIdSource( int32_t restore_session_id, const std::string& restore_window_id_source) { restore_session_id_.emplace(restore_session_id); if (!restore_window_id_source.empty()) restore_window_id_source_.emplace(restore_window_id_source); } void ShellSurfaceBase::SetFloat() { // TODO(crbug.com/1347534): This currently can unset float as well, but its // necessary until configure request is ready otherwise the window will remain // floated forever. chromeos::FloatControllerBase::Get()->ToggleFloat(widget_->GetNativeWindow()); } void ShellSurfaceBase::UnsetFloat() { chromeos::FloatControllerBase::Get()->ToggleFloat(widget_->GetNativeWindow()); } 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::SetMenu() { is_menu_ = true; } 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 ShellSurfaceBase::AsTracedValue() const { std::unique_ptr 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(); overlay_widget_->Init(std::move(params)); overlay_widget_->GetNativeWindow()->SetEventTargeter( std::make_unique()); 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(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_ && GetSecurityDelegate() && GetSecurityDelegate()->CanSelfActivate(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 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( 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 ? window_state->is_dragged() : false; } //////////////////////////////////////////////////////////////////////////////// // 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 || is_menu_) ? 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_z_order_.has_value()) params.z_order = initial_z_order_.value(); 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(¶ms.init_properties_container, application_id_); SetShellRootSurface(¶ms.init_properties_container, root_surface()); SetShellStartupId(¶ms.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 (restore_window_id_source_) { params.init_properties_container.SetProperty( app_restore::kRestoreWindowIdKey, app_restore::FetchRestoreWindowId(*restore_window_id_source_)); params.init_properties_container.SetProperty( app_restore::kAppIdKey, restore_window_id_source_.value()); } #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), ¶ms); #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(¶ms); // 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); // Skip initializing window state when it is menu. // TODO(crbug.com/1338597): Remove `window_state` condition when tooltip fix // is done. Without the fix, window_state can be null when it is tooltip and // the parent window is menu, so add null check of `window_state` here. if (!is_menu_ && window_state) 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(); root_surface()->OnDeskChanged(GetWindowDeskStateChanged(window)); WMHelper::GetInstance()->NotifyExoWindowCreated(widget_->GetNativeWindow()); } ShellSurfaceBase::OverlayParams::OverlayParams( std::unique_ptr 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 || !window_state->is_dragged()) return false; return window_state->drag_details() && (window_state->drag_details()->bounds_change & ash::WindowResizer::kBoundsChange_Resizes); } gfx::Rect ShellSurfaceBase::ComputeAdjustedBounds( const gfx::Rect& bounds) const { return bounds; } void ShellSurfaceBase::UpdateWidgetBounds() { DCHECK(widget_); absl::optional bounds = GetWidgetBounds(); if (!bounds) return; gfx::Rect adjusted_bounds = ComputeAdjustedBounds(*bounds); if (overlay_widget_) { gfx::Rect content_bounds(adjusted_bounds.size()); int height = 0; if (!overlay_overlaps_frame_ && frame_enabled()) { auto* frame_view = static_cast( 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 && !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; } SetWidgetBounds(adjusted_bounds, adjusted_bounds != *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()|. if (window_state) { ash::SetCornerRadius( window_state->window(), host_window()->layer(), window_state->IsPip() ? base::ClampRound(GetScale() * chromeos::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(this)); } std::unique_ptr 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 && !window_state->HasDelegate()) { window_state->SetDelegate(std::make_unique()); } auto frame_view = std::make_unique(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 && 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()); needs_layout_on_show_ = false; } 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(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) } void ShellSurfaceBase::SetZOrder(ui::ZOrderLevel z_order) { // If there is already a widget, we can immediately set its z order. if (widget_) { widget_->SetZOrderLevel(z_order); return; } // Otherwise, we want to save `z_order` for when `widget_` is initialized. initial_z_order_ = z_order; } } // namespace exo