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