| // Copyright 2015 The Chromium Authors |
| // 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 <stdint.h> |
| |
| #include "ash/display/screen_orientation_controller.h" |
| #include "ash/frame/non_client_frame_view_ash.h" |
| #include "ash/metrics/login_unlock_throughput_recorder.h" |
| #include "ash/public/cpp/rounded_corner_utils.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/shell.h" |
| #include "ash/wm/desks/desks_controller.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "ash/wm/resize_shadow_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/check_op.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/notreached.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/trace_event/traced_value.h" |
| #include "chromeos/constants/chromeos_features.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/frame/caption_buttons/snap_controller.h" |
| #include "chromeos/ui/frame/frame_utils.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 "components/viz/host/host_frame_sink_manager.h" |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/client/capture_client.h" |
| #include "ui/aura/client/focus_client.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/aura/window_occlusion_tracker.h" |
| #include "ui/aura/window_targeter.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/class_property.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/point.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/geometry/rounded_corners_f.h" |
| #include "ui/gfx/geometry/rrect_f.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/controls/menu/menu_config.h" |
| #include "ui/views/views_delegate.h" |
| #include "ui/views/widget/tooltip_manager.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/window/client_view.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_properties.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace exo { |
| namespace { |
| |
| bool IsRadiiUniform(const gfx::RoundedCornersF& radii) { |
| return radii.upper_left() == radii.upper_right() && |
| radii.lower_left() == radii.lower_right() && |
| radii.upper_left() == radii.lower_left(); |
| } |
| |
| // 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(); |
| } |
| |
| // Overridden from views::NonClientFrameView: |
| void UpdateWindowRoundedCorners() override { |
| if (!chromeos::features::IsRoundedWindowsEnabled() && GetFrameEnabled()) { |
| header_view_->SetHeaderCornerRadius( |
| chromeos::GetFrameCornerRadius(frame()->GetNativeWindow())); |
| } |
| |
| if (!GetWidget()) { |
| return; |
| } |
| |
| // TODO(b/302034956): Use `ApplyRoundedCornersToSurfaceTree()` to round pip |
| // window as well. |
| // Round a pip window. Pip windows are rounded by applying rounded corner |
| // to host window using ui::Layer API. |
| const ash::WindowState* window_state = |
| ash::WindowState::Get(GetWidget()->GetNativeWindow()); |
| |
| // When un-pipped (window state changed from pip), we must undo the |
| // rounded corners of the host_window. |
| const int pip_corner_radius = |
| window_state->IsPip() ? chromeos::kPipRoundedCornerRadius : 0; |
| const gfx::RoundedCornersF pip_radii(pip_corner_radius); |
| |
| ui::Layer* layer = shell_surface_->host_window()->layer(); |
| if (layer->rounded_corner_radii() != pip_radii) { |
| layer->SetRoundedCornerRadius(pip_radii); |
| layer->SetIsFastRoundedCorner(/*enable=*/!pip_radii.IsEmpty()); |
| } |
| |
| // If we have a pip window, ignore `window_radii`. |
| if (window_state->IsPip()) { |
| return; |
| } |
| |
| std::optional<gfx::RoundedCornersF> window_radii = |
| shell_surface_->window_corners_radii(); |
| |
| if (!chromeos::features::IsRoundedWindowsEnabled() || !window_radii) { |
| return; |
| } |
| |
| // TODO(crbug.com/1415486): Support variable window radii. |
| DCHECK(IsRadiiUniform(window_radii.value())); |
| |
| const int corner_radius = window_radii->upper_left(); |
| |
| if (GetFrameEnabled()) { |
| header_view_->SetHeaderCornerRadius(corner_radius); |
| } |
| |
| GetWidget()->client_view()->UpdateWindowRoundedCorners(corner_radius); |
| } |
| |
| 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: |
| const raw_ptr<ShellSurfaceBase> shell_surface_; |
| }; |
| |
| class CustomClientView : public views::ClientView { |
| public: |
| CustomClientView(views::Widget* widget, ShellSurfaceBase* shell_surface) |
| : views::ClientView(widget, shell_surface), |
| shell_surface_(shell_surface) {} |
| |
| CustomClientView(const CustomClientView&) = delete; |
| CustomClientView& operator=(const CustomClientView&) = delete; |
| |
| ~CustomClientView() override = default; |
| |
| // ClientView: |
| void UpdateWindowRoundedCorners(int corner_radius) override { |
| DCHECK(GetWidget()); |
| const CustomFrameView* custom_frame_view = static_cast<CustomFrameView*>( |
| GetWidget()->non_client_view()->frame_view()); |
| |
| // In the typical scenario with frame enabled, we round: |
| // * Upper corners of the frame. |
| // * Lower corners of the client view. |
| // But when the frame is overlapped with the client view, for upper corners, |
| // both the top (frame) and the bottom (client view) views need to be |
| // rounded. |
| const bool should_round_client_view_upper_corner = |
| !custom_frame_view->GetFrameEnabled() || |
| custom_frame_view->GetFrameOverlapped(); |
| |
| const float corner_radius_f = corner_radius; |
| const gfx::RoundedCornersF root_surface_radii = { |
| should_round_client_view_upper_corner ? corner_radius_f : 0, |
| should_round_client_view_upper_corner ? corner_radius_f : 0, |
| corner_radius_f, corner_radius_f}; |
| |
| const Surface* root_surface = shell_surface_->root_surface(); |
| |
| shell_surface_->ApplyRoundedCornersToSurfaceTree( |
| gfx::RectF(root_surface->surface_hierarchy_content_bounds()), |
| root_surface_radii); |
| } |
| |
| private: |
| raw_ptr<ShellSurfaceBase> 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 (shell_surface_->shape_dp() && |
| !shell_surface_->shape_dp()->Contains(local_point)) { |
| return false; |
| } |
| |
| 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; |
| } |
| |
| raw_ptr<ShellSurfaceBase> shell_surface_; |
| const raw_ptr<views::Widget, DanglingUntriaged> 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, |
| float snap_ratio) { |
| chromeos::SnapController::Get()->CommitSnap( |
| window, snap_direction, snap_ratio, |
| chromeos::SnapController::SnapRequestSource:: |
| kFromLacrosSnapButtonOrWindowLayoutMenu); |
| } |
| |
| } // 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); |
| WMHelper::GetInstance()->AddTooltipObserver(this); |
| surface->AddSurfaceObserver(this); |
| SetRootSurface(surface); |
| host_window()->Show(); |
| set_owned_by_client(); |
| |
| SetCanMinimize(can_minimize_); |
| SetCanMaximize(ash::desks_util::IsDeskContainerId(container_)); |
| SetCanFullscreen(ash::desks_util::IsDeskContainerId(container_)); |
| SetCanResize(true); |
| SetShowTitle(false); |
| } |
| |
| ShellSurfaceBase::~ShellSurfaceBase() { |
| // For overlapped frames, a relationship is created between the host window |
| // layer and the frame header layer. We need to notify frame header to remove |
| // the relationship before host window to destroyed. |
| if (frame_overlapped()) { |
| auto* frame_header = chromeos::FrameHeader::Get(widget_); |
| frame_header->RemoveLayerBeneath(); |
| } |
| |
| // 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_) { |
| if (has_grab_) { |
| widget_->ReleaseCapture(); |
| WMHelper::GetInstance()->GetCaptureClient()->RemoveObserver(this); |
| } |
| 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); |
| WMHelper::GetInstance()->RemoveTooltipObserver(this); |
| CHECK(!views::WidgetObserver::IsInObserverList()); |
| } |
| |
| void ShellSurfaceBase::Activate() { |
| TRACE_EVENT0("exo", "ShellSurfaceBase::Activate"); |
| |
| if (!widget_ || pending_show_widget_) { |
| initially_activated_ = true; |
| return; |
| } |
| |
| if (widget_->IsActive()) |
| return; |
| |
| widget_->Activate(); |
| } |
| |
| void ShellSurfaceBase::Deactivate() { |
| TRACE_EVENT0("exo", "ShellSurfaceBase::Deactivate"); |
| |
| if (!widget_ || pending_show_widget_) { |
| initially_activated_ = false; |
| return; |
| } |
| |
| if (!widget_->IsActive()) |
| return; |
| |
| widget_->Deactivate(); |
| } |
| |
| void ShellSurfaceBase::SetTitle(const std::u16string& title) { |
| TRACE_EVENT1("exo", "ShellSurfaceBase::SetTitle", "title", |
| base::UTF16ToUTF8(title)); |
| WidgetDelegate::SetTitle(title); |
| |
| aura::Env::GetInstance() |
| ->context_factory() |
| ->GetHostFrameSinkManager() |
| ->SetFrameSinkDebugLabel(host_window()->GetFrameSinkId(), |
| base::UTF16ToUTF8(title)); |
| } |
| |
| void ShellSurfaceBase::SetIcon(const gfx::ImageSkia& icon) { |
| TRACE_EVENT0("exo", "ShellSurfaceBase::SetIcon"); |
| WidgetDelegate::SetIcon(ui::ImageModel::FromImageSkia(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::SetTopInset(int height) { |
| TRACE_EVENT1("exo", "ShellSurfaceBase::SetTopInset", "height", height); |
| pending_top_inset_height_ = height; |
| } |
| |
| void ShellSurfaceBase::SetWindowCornersRadii( |
| const gfx::RoundedCornersF& radii) { |
| TRACE_EVENT1("exo", "ShellSurfaceBase::SetWindowCornerRadii", "radii", |
| radii.ToString()); |
| pending_window_corners_radii_dp_ = radii; |
| } |
| |
| void ShellSurfaceBase::SetShadowCornersRadii( |
| const gfx::RoundedCornersF& radii) { |
| TRACE_EVENT1("exo", "ShellSurfaceBase::SetShadowCornersRadii", "shadow_radii", |
| radii.ToString()); |
| pending_shadow_corners_radii_dp_ = radii; |
| } |
| |
| void ShellSurfaceBase::SetBoundsForShadows( |
| const std::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::UpdateShape() { |
| auto* widget_window = widget_->GetNativeWindow(); |
| if (!widget_window || !widget_window->layer()) { |
| return; |
| } |
| |
| if (!shape_dp_.has_value()) { |
| widget_window->layer()->SetAlphaShape(nullptr); |
| return; |
| } |
| |
| // TODO(crbug.com/1465999): The current implementation of window shape must |
| // only be used on frameless windows with shadows disabled, otherwise we risk |
| // the layer bounds not matching the bounds of the root surface. This needs to |
| // be updated such that the shape is applied to the root surface's geometry. |
| DCHECK_EQ(frame_type_, SurfaceFrameType::NONE); |
| |
| auto shape_rects_dp = std::make_unique<ui::Layer::ShapeRects>(); |
| for (gfx::Rect rect : shape_dp_.value()) { |
| shape_rects_dp->push_back(std::move(rect)); |
| } |
| |
| widget_window->layer()->SetAlphaShape(std::move(shape_rects_dp)); |
| } |
| |
| 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::SetSnapPrimary(float snap_ratio) { |
| CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kPrimary, |
| snap_ratio); |
| } |
| |
| void ShellSurfaceBase::SetSnapSecondary(float snap_ratio) { |
| CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kSecondary, |
| snap_ratio); |
| } |
| |
| void ShellSurfaceBase::UnsetSnap() { |
| if (widget_ && widget_->GetNativeWindow()) { |
| CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kNone, |
| chromeos::kDefaultSnapRatio); |
| } |
| } |
| |
| 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() { |
| // Ash does not implement restoring the pip state. Additionally it does not |
| // make sense for browser pip window to unset pip since the browser(lacros) |
| // creates a separate window for a pip and once pip is not needed, |
| // the window is destroyed rather than restoring it to some other state. |
| // However, ClientControlledShellSurface(Arc++), has a concept of restoring |
| // from pip state and implements UnsetPip. |
| NOTIMPLEMENTED(); |
| } |
| |
| void ShellSurfaceBase::SetFloatToLocation( |
| chromeos::FloatStartLocation float_start_location) { |
| chromeos::FloatControllerBase::Get()->SetFloat(widget_->GetNativeWindow(), |
| float_start_location); |
| } |
| |
| 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::UpdateTopInset() { |
| 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; |
| } |
| |
| // Apply new top inset height. |
| if (pending_top_inset_height_ != top_inset_height_) { |
| widget_->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, |
| pending_top_inset_height_); |
| top_inset_height_ = pending_top_inset_height_; |
| } |
| } |
| |
| 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; |
| } |
| |
| SecurityDelegate* security = GetSecurityDelegate(); |
| if (!security) { |
| return; |
| } |
| |
| switch (security->CanSetBounds(widget_->GetNativeWindow())) { |
| // Disallowed by default. |
| case SecurityDelegate::SetBoundsPolicy::IGNORE: |
| break; |
| |
| // For selected clients (Borealis) expand the requested bounds to include |
| // the decorations, if any. |
| // |
| // TODO(crbug.com/1261321, b/268395213): Instead, tell clients how large the |
| // decorations are, so they can make better decisions. |
| case SecurityDelegate::SetBoundsPolicy::ADJUST_IF_DECORATED: |
| if (widget_->non_client_view()) { |
| gfx::Rect expanded_bounds{ |
| widget_->non_client_view()->GetWindowBoundsForClientBounds(bounds)}; |
| |
| // If this expansion pushes the title bar offscreen, push it back |
| // onscreen while preserving requested X coordinate, width, and height. |
| gfx::Rect work_area = display::Screen::GetScreen() |
| ->GetDisplayMatching(bounds) |
| .work_area(); |
| if (!work_area.IsEmpty() && expanded_bounds.y() < work_area.y()) { |
| expanded_bounds.Offset(0, work_area.y() - expanded_bounds.y()); |
| } |
| widget_->SetBounds(expanded_bounds); |
| } else { |
| // No decorations, so no adjustment needed. |
| widget_->SetBounds(bounds); |
| } |
| break; |
| |
| // Other clients (Lacros) may set bounds, but it's a bug to do so for |
| // decorated windows. The chosen way to detect such bugs is a DCHECK. |
| // |
| // TODO(crbug.com/1261321, b/268395213): Instead, tell clients how large the |
| // decorations are, so they can make better decisions. |
| case SecurityDelegate::SetBoundsPolicy::DCHECK_IF_DECORATED: |
| DCHECK(!frame_enabled()); |
| widget_->SetBounds(bounds); |
| break; |
| } |
| } |
| |
| 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); |
| ash::LoginUnlockThroughputRecorder* throughput_recorder = |
| ash::Shell::Get()->login_unlock_throughput_recorder(); |
| throughput_recorder->OnRestoredWindowCreated(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::UnsetFloat() { |
| chromeos::FloatControllerBase::Get()->UnsetFloat(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::SetPersistable(bool persistable) { |
| // This should be called before the widget is created. |
| DCHECK(!widget_); |
| |
| persistable_ = persistable; |
| } |
| |
| 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(); |
| bool max_size_resizability_only = false; |
| if (widget_ && widget_->GetNativeWindow()) { |
| max_size_resizability_only = widget_->GetNativeWindow()->GetProperty( |
| kMaximumSizeForResizabilityOnly); |
| } |
| |
| // Allow maximizing if the max size is bigger than 32k resolution. |
| SetCanMaximize(CanResize() && !parent_ && |
| ash::desks_util::IsDeskContainerId(container_) && |
| (max_size.IsEmpty() || max_size_resizability_only)); |
| } |
| |
| 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()) |
| root_surface->window()->Focus(); |
| } |
| |
| SetCanMinimize(can_minimize_); |
| SetCanMaximize(ash::desks_util::IsDeskContainerId(container_)); |
| SetCanFullscreen(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>()); |
| |
| if (overlay_params.corners_radii) { |
| ui::Layer* layer = overlay_widget_->GetLayer(); |
| const gfx::RoundedCornersF& radii = overlay_params.corners_radii.value(); |
| layer->SetRoundedCornerRadius(radii); |
| layer->SetIsFastRoundedCorner(/*enable=*/!radii.IsEmpty()); |
| } |
| |
| 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); |
| } |
| |
| set_bounds_is_dirty(true); |
| 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_) { |
| AllocateLocalSurfaceId(); |
| } |
| |
| const gfx::Rect old_content_bounds = |
| root_surface()->surface_hierarchy_content_bounds(); |
| |
| root_surface()->CommitSurfaceHierarchy(false); |
| |
| set_bounds_is_dirty(bounds_is_dirty() || |
| old_content_bounds != |
| root_surface()->surface_hierarchy_content_bounds()); |
| |
| if (!OnPreWidgetCommit()) |
| return; |
| |
| WillCommit(); |
| |
| 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_type_changed = frame_type_ != frame_type; |
| |
| // aura-shell's set_frame, when used with xdg-shell, works iff the frame type |
| // or frame colors were specified before firsrt buffer commit. If these are |
| // not specified, the widget's layer is set to 'NOT_DRAWN' and the frame can't |
| // be drawn. `ClientControlledShellSurface` is not affected. |
| if (frame_type_changed && widget_ && |
| widget_->GetNativeWindow()->layer()->type() == ui::LAYER_NOT_DRAWN) { |
| if (frame_type != SurfaceFrameType::NONE && |
| frame_type != SurfaceFrameType::SHADOW) { |
| DLOG(FATAL) |
| << "A shell surface with NOT_DRAWN layer can't support visible frame"; |
| return; |
| } |
| } |
| bool frame_was_disabled = !frame_enabled(); |
| |
| frame_type_ = frame_type; |
| switch (frame_type) { |
| case SurfaceFrameType::NONE: |
| shadow_bounds_.reset(); |
| break; |
| case SurfaceFrameType::NORMAL: |
| case SurfaceFrameType::AUTOHIDE: |
| case SurfaceFrameType::OVERLAY: |
| case SurfaceFrameType::OVERLAP: |
| 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() && |
| frame_view->GetFrameOverlapped() == frame_overlapped()) { |
| return; |
| } |
| |
| frame_view->SetFrameEnabled(frame_enabled()); |
| frame_view->SetShouldPaintHeader(frame_enabled()); |
| |
| frame_view->SetFrameOverlapped(frame_overlapped()); |
| |
| auto* frame_header = chromeos::FrameHeader::Get(widget_); |
| if (frame_overlapped()) { |
| frame_header->AddLayerBeneath(host_window()); |
| } else { |
| frame_header->RemoveLayerBeneath(); |
| } |
| } |
| |
| widget_->GetRootView()->DeprecatedLayoutImmediately(); |
| // TODO(oshima): We probably should wait applying these if the |
| // window is animating. |
| set_bounds_is_dirty(true); |
| UpdateWidgetBounds(); |
| UpdateHostWindowOrigin(); |
| } |
| |
| 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() { |
| RequestActivation(); |
| } |
| |
| void ShellSurfaceBase::RequestActivation() { |
| if (widget_ && GetSecurityDelegate() && |
| GetSecurityDelegate()->CanSelfActivate(widget_->GetNativeWindow())) { |
| this->Activate(); |
| } |
| } |
| |
| void ShellSurfaceBase::RequestDeactivation() { |
| if (widget_ && GetSecurityDelegate() && |
| GetSecurityDelegate()->CanSelfActivate(widget_->GetNativeWindow())) { |
| this->Deactivate(); |
| } |
| } |
| |
| void ShellSurfaceBase::OnSetServerStartResize() { |
| server_side_resize_ = true; |
| } |
| |
| bool ShellSurfaceBase::IsReady() const { |
| return !pending_show_widget_; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // SurfaceObserver overrides: |
| |
| void ShellSurfaceBase::OnSurfaceDestroying(Surface* surface) { |
| DCHECK_EQ(root_surface(), surface); |
| surface->RemoveSurfaceObserver(this); |
| SetRootSurface(nullptr); |
| |
| overlay_widget_.reset(); |
| |
| // 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; |
| } |
| |
| views::ClientView* ShellSurfaceBase::CreateClientView(views::Widget* widget) { |
| return new CustomClientView(widget, this); |
| } |
| |
| std::unique_ptr<views::NonClientFrameView> |
| ShellSurfaceBase::CreateNonClientFrameView(views::Widget* widget) { |
| return CreateNonClientFrameViewInternal(widget); |
| } |
| |
| bool ShellSurfaceBase::ShouldSaveWindowPlacement() const { |
| return !is_popup_ && !movement_disabled_; |
| } |
| |
| bool ShellSurfaceBase::WidgetHasHitTestMask() const { |
| return true; |
| } |
| |
| void ShellSurfaceBase::GetWidgetHitTestMask(SkPath* mask) const { |
| // If a window shape is applied set the hit test mask to the boundary path |
| // of the masked region. |
| if (shape_dp_) { |
| shape_dp_->GetBoundaryPath(mask); |
| return; |
| } |
| |
| GetHitTestMask(mask); |
| |
| const float scale = GetScale(); |
| |
| // `mask` should be in the Widget's coordinates, but the above |
| // GetHitTestMask() call returns the mask in the root_surface's coordinates. |
| // We need to offset the difference. |
| auto widget_bounds = widget_->GetWindowBoundsInScreen().origin(); |
| auto root_surface_bounds = |
| root_surface()->window()->GetBoundsInScreen().origin(); |
| auto offset = root_surface_bounds - widget_bounds.OffsetFromOrigin(); |
| |
| SkMatrix matrix; |
| matrix.setScaleTranslate( |
| SkFloatToScalar(1.0f / scale), SkFloatToScalar(1.0f / scale), |
| SkIntToScalar(offset.x()), SkIntToScalar(offset.y())); |
| mask->transform(matrix); |
| } |
| |
| void ShellSurfaceBase::OnCaptureChanged(aura::Window* lost_capture, |
| aura::Window* gained_capture) { |
| if (lost_capture != widget_->GetNativeWindow() || !is_popup_) |
| return; |
| |
| // If the capture mode is active, do not close the popup to include it in a |
| // screenshot. |
| if (!views::ViewsDelegate::GetInstance() |
| ->ShouldCloseMenuIfMouseCaptureLost()) { |
| return; |
| } |
| |
| WMHelper::GetInstance()->GetCaptureClient()->RemoveObserver(this); |
| |
| // Fast return for a simple case: if `lost_capture` is the parent of |
| // `gained_capture`, do nothing. |
| aura::Window* gained_capture_parent = |
| gained_capture ? wm::GetTransientParent(gained_capture) : nullptr; |
| if (lost_capture == gained_capture_parent) |
| return; |
| |
| if (!gained_capture) { |
| // If `gained_capture` is nullptr, find the closest ancestor of |
| // `lost_capture` that is a popup with grab. |
| for (aura::Window* next = wm::GetTransientParent(lost_capture); |
| next != nullptr; next = wm::GetTransientParent(next)) { |
| if (IsPopupWithGrab(next)) { |
| gained_capture = next; |
| break; |
| } |
| } |
| // Give capture to the new `gained_capture`. |
| if (gained_capture) { |
| ShellSurfaceBase* parent_shell_surface = |
| GetShellSurfaceBaseForWindow(gained_capture); |
| parent_shell_surface->StartCapture(); |
| } |
| } |
| |
| // Close any popup that satisfies the following conditions: |
| // 1) it has grab, and it is not `gained_capture` or any of its ancestors; or |
| // 2) descendants of any popup satisfying (1). |
| // |
| // Imagine there are the following popups: |
| // |
| // popup_e |
| // (no grab) |
| // | |
| // popup_d |
| // (has grab; lost_capture) |
| // | |
| // popup_c popup_b |
| // (no grab) (has grab; gained_capture) |
| // \ / |
| // \ / |
| // popup_a |
| // |
| // In this case, popup_e and popup_d are the ones to close, in the order |
| // from leaf to root. |
| |
| // Please note that `gained_capture_ancestors` also includes `gained_capture`. |
| base::flat_set<aura::Window*> gained_capture_ancestors; |
| for (aura::Window* next = gained_capture; next != nullptr; |
| next = wm::GetTransientParent(next)) { |
| gained_capture_ancestors.insert(next); |
| } |
| |
| // BFS to collect all transient windows. The boolean field indicates whether |
| // the corresponding window is a popup to be closed. |
| std::vector<std::pair<aura::Window*, bool>> all; |
| |
| auto is_close_candidate_with_popup_grab = |
| [&gained_capture_ancestors](aura::Window* window) { |
| return IsPopupWithGrab(window) && |
| !base::Contains(gained_capture_ancestors, window); |
| }; |
| |
| aura::Window* root = wm::GetTransientRoot(lost_capture); |
| all.emplace_back(root, is_close_candidate_with_popup_grab(root)); |
| |
| // Use index instead of iterator because the vector grows during the |
| // iteration. |
| for (size_t i = 0; i < all.size(); ++i) { |
| const std::vector<raw_ptr<aura::Window, VectorExperimental>>& children = |
| wm::GetTransientChildren(all[i].first); |
| for (aura::Window* child : children) { |
| const bool to_close = |
| all[i].second || is_close_candidate_with_popup_grab(child); |
| all.emplace_back(child, to_close); |
| } |
| } |
| |
| // Traverse backwards so that popups are closed in the direction from leaf to |
| // root. |
| for (auto iter = all.rbegin(); iter != all.rend(); ++iter) { |
| if (!iter->second) |
| continue; |
| |
| ShellSurfaceBase* shell_surface = |
| exo::GetShellSurfaceBaseForWindow(iter->first); |
| DCHECK(shell_surface); |
| shell_surface->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. |
| surface_destroyed_callback_.Reset(); |
| 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) { |
| surface_destroyed_callback_.Reset(); |
| if (!close_callback_.is_null()) { |
| close_callback_.Run(); |
| } |
| |
| if (window == parent_) |
| SetParentInternal(nullptr); |
| window->RemoveObserver(this); |
| if (IsShellSurfaceWindow(window) && root_surface()) { |
| root_surface()->ThrottleFrameRate(false); |
| } |
| } |
| |
| void ShellSurfaceBase::OnWindowPropertyChanged(aura::Window* window, |
| const void* key, |
| intptr_t old_value) { |
| if (!IsShellSurfaceWindow(window) || !root_surface()) { |
| return; |
| } |
| |
| 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) { |
| if (!IsShellSurfaceWindow(window)) { |
| return; |
| } |
| UpdateDisplayOnTree(); |
| } |
| |
| void ShellSurfaceBase::OnWindowParentChanged(aura::Window* window, |
| aura::Window* parent) { |
| if (!IsShellSurfaceWindow(window) || !root_surface()) { |
| return; |
| } |
| 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(); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // wm::TooltipObserver overrides: |
| |
| void ShellSurfaceBase::OnTooltipShown(aura::Window* target, |
| const std::u16string& text, |
| const gfx::Rect& bounds) { |
| if (root_surface()) { |
| root_surface()->OnTooltipShown(text, bounds); |
| } |
| } |
| |
| void ShellSurfaceBase::OnTooltipHidden(aura::Window* target) { |
| if (root_surface()) { |
| root_surface()->OnTooltipHidden(); |
| } |
| } |
| |
| // 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); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // SurfaceTreeHost: |
| void ShellSurfaceBase::SetRootSurface(Surface* root_surface) { |
| SurfaceTreeHost::SetRootSurface(root_surface); |
| if (widget_) { |
| SetShellRootSurface(widget_->GetNativeWindow(), root_surface); |
| } |
| } |
| |
| float ShellSurfaceBase::GetPendingScaleFactor() const { |
| if (!host_window()->parent() && !HasDoubleBufferedPendingScaleFactor()) { |
| // Before the initial commit, `host_window()` has not been a descendant of |
| // the root window yet so we need to fetch the scale factor directly from |
| // the pending target display. |
| display::Display display; |
| if (display::Screen::GetScreen()->GetDisplayWithDisplayId( |
| pending_display_id_, &display)) { |
| return display.device_scale_factor(); |
| } |
| } |
| return SurfaceTreeHost::GetPendingScaleFactor(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 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 = ash::Shell::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()); |
| |
| // This is called before CommitWidget: |
| if (pending_display_id_ != display::kInvalidDisplayId) { |
| params.display_id = pending_display_id_; |
| } |
| |
| params.name = base::StringPrintf("ExoShellSurface-%d", shell_id++); |
| |
| 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()); |
| } |
| |
| params.init_properties_container.SetProperty(wm::kPersistableKey, |
| persistable_); |
| |
| // Restore `params` to those of the saved `restore_window_id_`. |
| app_restore::ModifyWidgetParams(params.init_properties_container.GetProperty( |
| app_restore::kRestoreWindowIdKey), |
| ¶ms); |
| |
| // 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(); |
| |
| UpdateTopInset(); |
| |
| aura::Window* window = widget_->GetNativeWindow(); |
| { |
| // AddChild involves propagating a non-1 device_scale_factor to |
| // `host_window()`. Changing device_scale_factor this way does not send |
| // configure events. So suppress allocation of its LocalSurfaceId. |
| viz::ScopedSurfaceIdAllocator scoped_suppression = |
| host_window()->GetSurfaceIdAllocator(base::NullCallback()); |
| window->AddChild(host_window()); |
| } |
| window->SetEventTargetingPolicy( |
| aura::EventTargetingPolicy::kTargetAndDescendants); |
| if (is_menu_) { |
| // Sets menu config id to kGroupingPropertyKey if the window is menu. |
| window->SetNativeWindowProperty( |
| views::TooltipManager::kGroupingPropertyKey, |
| reinterpret_cast<void*>(views::MenuConfig::kMenuControllerGroupingId)); |
| } |
| InstallCustomWindowTargeter(); |
| |
| // TODO(fangzhoug): Consider performing the first shell_surface configure here |
| // s.t. there's no gap between the first configure to the point we start |
| // observing states of the window. crbug.com/1505583 |
| // 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 `window_state` is null. |
| // This happesn when the window type is popup. |
| if (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)); |
| root_surface()->OnFullscreenStateChanged(widget_->IsFullscreen()); |
| |
| WMHelper::GetInstance()->NotifyExoWindowCreated(widget_->GetNativeWindow()); |
| } |
| |
| bool ShellSurfaceBase::IsShellSurfaceWindow(const aura::Window* window) const { |
| return widget_ && window == 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 || !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_); |
| std::optional<gfx::Rect> bounds = GetWidgetBounds(); |
| if (!bounds) { |
| return; |
| } |
| |
| ash::WindowState* window_state = |
| ash::WindowState::Get(widget_->GetNativeWindow()); |
| gfx::Rect adjusted_bounds = ComputeAdjustedBounds(*bounds); |
| |
| bool should_update_widget_bounds = bounds_is_dirty() || |
| adjusted_bounds != *bounds || |
| (window_state && window_state->IsPip()); |
| |
| set_bounds_is_dirty(false); |
| |
| if (!should_update_widget_bounds) { |
| return; |
| } |
| |
| if (overlay_widget_) { |
| gfx::Rect content_bounds(adjusted_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(); |
| // 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::UpdateHostWindowOrigin() { |
| // There's an animation happening on cloned layers, `host_window()` layer may |
| // be "ahead" of client's commit_target_layer. Do not update its origin, |
| // instead, rely on SurfaceLayer stretching until the client catches up. |
| if (GetCommitTargetLayer() != host_window()->layer()) { |
| return; |
| } |
| gfx::Point origin = GetClientViewBounds().origin(); |
| |
| origin += GetSurfaceOrigin().OffsetFromOrigin(); |
| // As `origin` is in DP here, it eventually needs to be converted to pixels. |
| // We need to make subpixel adjustment so the `origin` in pixel coordinates |
| // will align exactly to a pixel boundary. Here, we calculate the closest |
| // pixel boundary by converting to pixels and rounding the original DP value. |
| // Note that this shouldn't take `scaled_root_origin` into account as its |
| // original value is in pixels and it needs a different type of subpixel |
| // adjustment (i.e. preserving the original pixel distance between two |
| // points). |
| const gfx::Vector2dF surface_origin_subpixel_offset = |
| ScaleVector2d(ToRoundedVector2d(ScaleVector2d(origin.OffsetFromOrigin(), |
| GetScaleFactor())), |
| 1.f / GetScaleFactor()) - |
| origin.OffsetFromOrigin(); |
| |
| const gfx::Vector2dF root_surface_origin_dp = ScaleVector2d( |
| root_surface_origin_pixel().OffsetFromOrigin(), 1.f / GetScaleFactor()); |
| origin -= ToFlooredVector2d(root_surface_origin_dp); |
| // Subpixel offset used to adjust the offset of `root_origin` so it will |
| // exactly match the original value in pixels. |
| const gfx::Vector2dF root_surface_origin_subpixel_offset = |
| ToFlooredVector2d(root_surface_origin_dp) - root_surface_origin_dp; |
| |
| // Two offsets can be simply added together because |
| // `surface_origin_subpixel_offset` is used for shifting the origin on a pixel |
| // boundary while `root_surface_origin_subpixel_offset` just ensures that the |
| // root surface origin stays the same value in pixel while scrolling when a |
| // sub surface moves (e.g. by scrolling) but the actual value it's preserving |
| // doesn't matter. |
| host_window()->layer()->SetSubpixelPositionOffset( |
| surface_origin_subpixel_offset + root_surface_origin_subpixel_offset); |
| |
| if (origin != host_window()->bounds().origin()) { |
| AllocateLocalSurfaceId(); |
| } |
| |
| gfx::Rect surface_bounds(origin, host_window()->bounds().size()); |
| if (host_window()->bounds() == surface_bounds) |
| return; |
| // This may not be necessary |
| set_bounds_is_dirty(true); |
| host_window()->SetBounds(surface_bounds); |
| } |
| |
| void ShellSurfaceBase::UpdateShadow() { |
| if (!widget_ || !root_surface()) |
| return; |
| |
| aura::Window* window = widget_->GetNativeWindow(); |
| |
| // Window shadows should be disabled if a window shape has been set. |
| // |
| // Or if `host_window()`'s layer is not commit_target_layer, `shadow_bounds_` |
| // committed by the client should not go to current `widget_`'s shadow, but to |
| // the old widget's shadow prior to layer clone. Don't show the new shadow for |
| // now. |
| // TODO(crbug.com/1491604): Find the old widget's shadow layer and update it, |
| // and maybe show new widget's shadow by predicting its dimensions. |
| if (!shadow_bounds_ || shape_dp_.has_value() || |
| GetCommitTargetLayer() != host_window()->layer()) { |
| 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(); |
| if (origin.x() != 0 || origin.y() != 0) { |
| shadow_bounds.set_origin(origin); |
| if (widget_) { |
| gfx::Point widget_origin_in_root = |
| widget_->GetNativeWindow()->bounds().origin(); |
| origin += ToFlooredVector2d( |
| ScaleVector2d(gfx::Vector2d(widget_origin_in_root.x(), |
| widget_origin_in_root.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); |
| |
| UpdateShadowRoundedCorners(); |
| } |
| |
| if (window->layer()->type() == ui::LAYER_NOT_DRAWN) { |
| DCHECK(!window->GetProperty(chromeos::kWindowManagerManagesOpacityKey)); |
| |
| // Snapped window should not be opaque because it can be drag-resized, in |
| // which case the widget's window can be exposed while waiting for |
| // configure_ack + commit. |
| bool window_is_opaque = widget_->IsFullscreen() || widget_->IsMaximized(); |
| window->SetTransparent(!window_is_opaque); |
| if (root_surface()->FillsBoundsOpaquely()) { |
| // Manually control occlusion, but do not make the window |
| // opaque as the host window may not be at the same size unless the |
| // window state is either in fullscreen or maximized. |
| window->SetOpaqueRegionsForOcclusion( |
| {gfx::Rect(window->bounds().size())}); |
| } else { |
| window->SetOpaqueRegionsForOcclusion({}); |
| } |
| } |
| } |
| |
| void ShellSurfaceBase::UpdateShadowRoundedCorners() { |
| if (!widget_) { |
| return; |
| } |
| |
| shadow_corners_radii_dp_ = pending_shadow_corners_radii_dp_; |
| |
| aura::Window* window = widget_->GetNativeWindow(); |
| ui::Shadow* shadow = wm::ShadowController::GetShadowForWindow(window); |
| |
| if (!shadow) { |
| return; |
| } |
| |
| gfx::RoundedCornersF shadow_radii; |
| |
| const ash::WindowState* window_state = ash::WindowState::Get(window); |
| if (window_state && window_state->IsPip()) { |
| shadow_radii = gfx::RoundedCornersF(chromeos::kPipRoundedCornerRadius); |
| } else if (chromeos::features::IsRoundedWindowsEnabled() && |
| (shadow_corners_radii_dp_.has_value() || |
| window_corners_radii_dp_.has_value())) { |
| // For backward version compatibility, fallback to use the window radii if |
| // the shadow radii is not specified. |
| // TODO(crbug.com/1415486): Revisit once all the clients have migrated. |
| shadow_radii = shadow_corners_radii_dp_.value_or( |
| window_corners_radii_dp_.value_or(gfx::RoundedCornersF())); |
| |
| // TODO(crbug.com/1415486): Support shadow with variable radius corners. |
| DCHECK(IsRadiiUniform(shadow_radii)); |
| } |
| |
| shadow->SetRoundedCornerRadius(shadow_radii.upper_left()); |
| } |
| |
| void ShellSurfaceBase::UpdateFrameType() { |
| // Nothing to do here for now as frame type is updated immediately in |
| // OnSetFrame() by default. |
| } |
| |
| void ShellSurfaceBase::UpdateWindowRoundedCorners() { |
| // If non_client_view is not available, it means that widget_ is neither a |
| // normal window or a bubble. Therefore it should not have any decorations |
| // including a rounded window. |
| if (!widget_ || !widget_->non_client_view()) { |
| DCHECK(widget_ && !pending_window_corners_radii_dp_); |
| // It is possible to get here before the widget has actually been created. |
| // The state will be set once the widget gets created. |
| return; |
| } |
| |
| window_corners_radii_dp_ = pending_window_corners_radii_dp_; |
| widget_->non_client_view()->frame_view()->UpdateWindowRoundedCorners(); |
| } |
| |
| 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); |
| } |
| |
| return geometry_; |
| } |
| |
| gfx::Rect ShellSurfaceBase::GetClientViewBounds() const { |
| return (widget_->non_client_view() && !frame_overlapped()) |
| ? widget_->non_client_view() |
| ->frame_view() |
| ->GetBoundsForClientView() |
| // When frame is overlapped with client area, window bounds is the |
| // same as client bounds. |
| : gfx::Rect(widget_->GetWindowBoundsInScreen().size()); |
| } |
| |
| gfx::Rect ShellSurfaceBase::GetWidgetBoundsFromVisibleBounds() const { |
| auto visible_bounds = GetVisibleBounds(); |
| return widget_->non_client_view() |
| ? widget_->non_client_view()->GetWindowBoundsForClientBounds( |
| visible_bounds) |
| : visible_bounds; |
| } |
| |
| gfx::Rect ShellSurfaceBase::GetShadowBounds() const { |
| return shadow_bounds_->IsEmpty() |
| ? gfx::Rect(widget_->GetNativeWindow()->bounds().size()) |
| : gfx::ScaleToEnclosedRect(*shadow_bounds_, 1.f / GetScale()); |
| } |
| |
| void ShellSurfaceBase::InstallCustomWindowTargeter() { |
| aura::Window* window = widget_->GetNativeWindow(); |
| window->SetEventTargeter(std::make_unique<CustomWindowTargeter>(this)); |
| } |
| |
| std::unique_ptr<views::NonClientFrameView> |
| ShellSurfaceBase::CreateNonClientFrameViewInternal(views::Widget* widget) { |
| aura::Window* window = widget_->GetNativeWindow(); |
| // ShellSurfaces always use immersive mode. |
| window->SetProperty(chromeos::kImmersiveIsActive, true); |
| ash::WindowState* window_state = ash::WindowState::Get(window); |
| if (!frame_enabled() && window_state && !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; |
| |
| UpdateTopInset(); |
| } |
| |
| void ShellSurfaceBase::SetContainerInternal(int container) { |
| container_ = container; |
| WidgetDelegate::SetCanMaximize( |
| !parent_ && ash::desks_util::IsDeskContainerId(container_)); |
| WidgetDelegate::SetCanFullscreen( |
| !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() { |
| bool size_constraint_changed = minimum_size_ != pending_minimum_size_ || |
| maximum_size_ != pending_maximum_size_; |
| set_bounds_is_dirty( |
| bounds_is_dirty() || origin_ != pending_geometry_.origin() || |
| geometry_ != pending_geometry_ || display_id_ != pending_display_id_ || |
| size_constraint_changed); |
| |
| // Apply new window geometry. |
| geometry_ = pending_geometry_; |
| display_id_ = pending_display_id_; |
| shape_dp_ = pending_shape_dp_; |
| |
| // Apply new minimum/maximium 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); |
| } |
| |
| // The calling order matters. The frame type has to be updated before |
| // calculating the bounds because the bounds computation depends on the frame |
| // type (e.g. caption height). |
| UpdateFrameType(); |
| UpdateWidgetBounds(); |
| UpdateSurfaceLayerSizeAndRootSurfaceOrigin(); |
| |
| // 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()); |
| } |
| } |
| } |
| |
| UpdateHostWindowOrigin(); |
| UpdateShape(); |
| |
| gfx::Rect bounds = geometry_; |
| if (!bounds.IsEmpty() && !widget_->GetNativeWindow()->GetProperty( |
| aura::client::kUseWindowBoundsForShadow)) { |
| SetBoundsForShadows(std::make_optional(bounds)); |
| } |
| |
| // The calling order matters. Updated window radius is need to correctly |
| // update the radius of the shadow. |
| UpdateWindowRoundedCorners(); |
| UpdateShadow(); |
| |
| // 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(oshima): This should be set to the |
| // `views::Widget::InitParams.bounds` |
| if (window_state && window_state->IsMaximizedOrFullscreenOrPinned() && |
| (!initial_bounds_ || initial_bounds_->IsEmpty())) { |
| 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; |
| } |
| |
| if (restore_window_id_.has_value()) { |
| ash::LoginUnlockThroughputRecorder* throughput_recorder = |
| ash::Shell::Get()->login_unlock_throughput_recorder(); |
| |
| aura::Window* root_window = host_window()->GetRootWindow(); |
| if (root_window) { |
| ui::Compositor* compositor = root_window->layer()->GetCompositor(); |
| throughput_recorder->OnBeforeRestoredWindowShown( |
| restore_window_id_.value(), compositor); |
| } |
| } |
| |
| if (initially_activated_) { |
| // Widget will minimize itself if the initial state is minimized. |
| widget_->Show(); |
| } else { |
| widget_->ShowInactive(); |
| } |
| |
| 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) { |
| 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); |
| } |
| |
| 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; |
| } |
| |
| void ShellSurfaceBase::SetShape(std::optional<cc::Region> shape) { |
| if (!shape) { |
| pending_shape_dp_.reset(); |
| return; |
| } |
| |
| if (frame_enabled()) { |
| LOG(ERROR) << "SetShape() is not supported for windows with frame enabled."; |
| return; |
| } |
| |
| // SetShape() may be called some time after a window has been created. In case |
| // server_side_resize_ has been set we disable it here. |
| server_side_resize_ = false; |
| |
| // Although window shape is only supported for frameless windows we must also |
| // ensure window shadows are disabled as shadows can contribute to the widget |
| // window's layer bounds. |
| // TODO(crbug.com/1465999): This will not be necessary once the implementation |
| // is updated to use the root surface's geometry. |
| OnSetFrame(SurfaceFrameType::NONE); |
| |
| pending_shape_dp_ = std::move(shape); |
| } |
| |
| // static |
| bool ShellSurfaceBase::IsPopupWithGrab(aura::Window* window) { |
| ShellSurfaceBase* shell_surface = exo::GetShellSurfaceBaseForWindow(window); |
| if (shell_surface && shell_surface->has_grab_) { |
| DCHECK(shell_surface->is_popup_); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } // namespace exo |