| // Copyright 2018 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/wayland/zaura_shell.h" |
| |
| #include <aura-shell-server-protocol.h> |
| #include <wayland-server-core.h> |
| #include <wayland-server-protocol-core.h> |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/wm/window_state.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/exo/display.h" |
| #include "components/exo/shell_surface_base.h" |
| #include "components/exo/wayland/server_util.h" |
| #include "components/exo/wayland/wayland_display_observer.h" |
| #include "components/exo/wayland/wl_output.h" |
| #include "components/exo/wm_helper.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/window_occlusion_tracker.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/display/display_observer.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/manager/display_util.h" |
| #include "ui/display/screen.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/public/cpp/tablet_mode_observer.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/wm/desks/desk.h" |
| #include "ash/wm/desks/desks_controller.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/exo/wm_helper_chromeos.h" |
| #include "ui/aura/client/aura_constants.h" |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| namespace exo { |
| namespace wayland { |
| |
| namespace { |
| |
| // A property key containing a boolean set to true if na aura surface object is |
| // associated with surface object. |
| DEFINE_UI_CLASS_PROPERTY_KEY(bool, kSurfaceHasAuraSurfaceKey, false) |
| |
| bool TransformRelativeToScreenIsAxisAligned(aura::Window* window) { |
| gfx::Transform transform_relative_to_screen; |
| DCHECK(window->layer()->GetTargetTransformRelativeTo( |
| window->GetRootWindow()->layer(), &transform_relative_to_screen)); |
| transform_relative_to_screen.ConcatTransform( |
| window->GetRootWindow()->layer()->GetTargetTransform()); |
| return transform_relative_to_screen.Preserves2dAxisAlignment(); |
| } |
| |
| // This does not handle non-axis aligned rotations, but we don't have any |
| // slightly (e.g. 45 degree) windows so it is okay. |
| gfx::Rect GetTransformedBoundsInScreen(aura::Window* window) { |
| DCHECK(TransformRelativeToScreenIsAxisAligned(window)); |
| // This assumes that opposite points on the window bounds rectangle will |
| // be mapped to opposite points on the output rectangle. |
| gfx::Point a = window->bounds().origin(); |
| gfx::Point b = window->bounds().bottom_right(); |
| ::wm::ConvertPointToScreen(window->parent(), &a); |
| ::wm::ConvertPointToScreen(window->parent(), &b); |
| return gfx::Rect(std::min(a.x(), b.x()), std::min(a.y(), b.y()), |
| std::abs(a.x() - b.x()), std::abs(a.y() - b.y())); |
| } |
| |
| SurfaceFrameType AuraSurfaceFrameType(uint32_t frame_type) { |
| switch (frame_type) { |
| case ZAURA_SURFACE_FRAME_TYPE_NONE: |
| return SurfaceFrameType::NONE; |
| case ZAURA_SURFACE_FRAME_TYPE_NORMAL: |
| return SurfaceFrameType::NORMAL; |
| case ZAURA_SURFACE_FRAME_TYPE_SHADOW: |
| return SurfaceFrameType::SHADOW; |
| default: |
| VLOG(2) << "Unkonwn aura-shell frame type: " << frame_type; |
| return SurfaceFrameType::NONE; |
| } |
| } |
| |
| zaura_surface_occlusion_state WaylandOcclusionState( |
| const aura::Window::OcclusionState occlusion_state) { |
| switch (occlusion_state) { |
| case aura::Window::OcclusionState::UNKNOWN: |
| return ZAURA_SURFACE_OCCLUSION_STATE_UNKNOWN; |
| case aura::Window::OcclusionState::VISIBLE: |
| return ZAURA_SURFACE_OCCLUSION_STATE_VISIBLE; |
| case aura::Window::OcclusionState::OCCLUDED: |
| return ZAURA_SURFACE_OCCLUSION_STATE_OCCLUDED; |
| case aura::Window::OcclusionState::HIDDEN: |
| return ZAURA_SURFACE_OCCLUSION_STATE_HIDDEN; |
| } |
| return ZAURA_SURFACE_OCCLUSION_STATE_UNKNOWN; |
| } |
| |
| void aura_surface_set_frame(wl_client* client, |
| wl_resource* resource, |
| uint32_t type) { |
| GetUserDataAs<AuraSurface>(resource)->SetFrame(AuraSurfaceFrameType(type)); |
| } |
| |
| void aura_surface_set_parent(wl_client* client, |
| wl_resource* resource, |
| wl_resource* parent_resource, |
| int32_t x, |
| int32_t y) { |
| GetUserDataAs<AuraSurface>(resource)->SetParent( |
| parent_resource ? GetUserDataAs<AuraSurface>(parent_resource) : nullptr, |
| gfx::Point(x, y)); |
| } |
| |
| void aura_surface_set_frame_colors(wl_client* client, |
| wl_resource* resource, |
| uint32_t active_color, |
| uint32_t inactive_color) { |
| GetUserDataAs<AuraSurface>(resource)->SetFrameColors(active_color, |
| inactive_color); |
| } |
| |
| void aura_surface_set_startup_id(wl_client* client, |
| wl_resource* resource, |
| const char* startup_id) { |
| GetUserDataAs<AuraSurface>(resource)->SetStartupId(startup_id); |
| } |
| |
| void aura_surface_set_application_id(wl_client* client, |
| wl_resource* resource, |
| const char* application_id) { |
| GetUserDataAs<AuraSurface>(resource)->SetApplicationId(application_id); |
| } |
| |
| void aura_surface_set_client_surface_id_DEPRECATED(wl_client* client, |
| wl_resource* resource, |
| int client_surface_id) { |
| // DEPRECATED. Use aura_surface_set_client_surface_str_id |
| std::string client_surface_str_id = base::NumberToString(client_surface_id); |
| GetUserDataAs<AuraSurface>(resource)->SetClientSurfaceId( |
| client_surface_str_id.c_str()); |
| } |
| |
| void aura_surface_set_occlusion_tracking(wl_client* client, |
| wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->SetOcclusionTracking(true); |
| } |
| |
| void aura_surface_unset_occlusion_tracking(wl_client* client, |
| wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->SetOcclusionTracking(false); |
| } |
| |
| void aura_surface_activate(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->Activate(); |
| } |
| |
| void aura_surface_draw_attention(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->DrawAttention(); |
| } |
| |
| void aura_surface_set_fullscreen_mode(wl_client* client, |
| wl_resource* resource, |
| uint32_t mode) { |
| GetUserDataAs<AuraSurface>(resource)->SetFullscreenMode(mode); |
| } |
| |
| void aura_surface_set_client_surface_str_id(wl_client* client, |
| wl_resource* resource, |
| const char* client_surface_id) { |
| GetUserDataAs<AuraSurface>(resource)->SetClientSurfaceId(client_surface_id); |
| } |
| |
| void aura_surface_set_server_start_resize(wl_client* client, |
| wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->SetServerStartResize(); |
| } |
| |
| void aura_surface_intent_to_snap(wl_client* client, |
| wl_resource* resource, |
| uint32_t snap_direction) { |
| GetUserDataAs<AuraSurface>(resource)->IntentToSnap(snap_direction); |
| } |
| |
| void aura_surface_set_snap_left(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->SetSnapPrimary(); |
| } |
| |
| void aura_surface_set_snap_right(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->SetSnapSecondary(); |
| } |
| |
| void aura_surface_unset_snap(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->UnsetSnap(); |
| } |
| |
| void aura_surface_set_window_session_id(wl_client* client, |
| wl_resource* resource, |
| int32_t id) { |
| GetUserDataAs<AuraSurface>(resource)->SetWindowSessionId(id); |
| } |
| |
| void aura_surface_set_can_go_back(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->SetCanGoBack(); |
| } |
| |
| void aura_surface_unset_can_go_back(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->UnsetCanGoBack(); |
| } |
| |
| void aura_surface_set_pip(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->SetPip(); |
| } |
| |
| void aura_surface_unset_pip(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->UnsetPip(); |
| } |
| |
| void aura_surface_set_aspect_ratio(wl_client* client, |
| wl_resource* resource, |
| int32_t width, |
| int32_t height) { |
| GetUserDataAs<AuraSurface>(resource)->SetAspectRatio( |
| gfx::SizeF(width, height)); |
| } |
| |
| void aura_surface_move_to_desk(wl_client* client, |
| wl_resource* resource, |
| int index) { |
| GetUserDataAs<AuraSurface>(resource)->MoveToDesk(index); |
| } |
| |
| void aura_surface_set_initial_workspace(wl_client* client, |
| wl_resource* resource, |
| const char* initial_workspace) { |
| GetUserDataAs<AuraSurface>(resource)->SetInitialWorkspace(initial_workspace); |
| } |
| |
| void aura_surface_set_pin(wl_client* client, |
| wl_resource* resource, |
| int32_t trusted) { |
| GetUserDataAs<AuraSurface>(resource)->Pin(trusted); |
| } |
| |
| void aura_surface_unset_pin(wl_client* client, wl_resource* resource) { |
| GetUserDataAs<AuraSurface>(resource)->Unpin(); |
| } |
| |
| const struct zaura_surface_interface aura_surface_implementation = { |
| aura_surface_set_frame, |
| aura_surface_set_parent, |
| aura_surface_set_frame_colors, |
| aura_surface_set_startup_id, |
| aura_surface_set_application_id, |
| aura_surface_set_client_surface_id_DEPRECATED, |
| aura_surface_set_occlusion_tracking, |
| aura_surface_unset_occlusion_tracking, |
| aura_surface_activate, |
| aura_surface_draw_attention, |
| aura_surface_set_fullscreen_mode, |
| aura_surface_set_client_surface_str_id, |
| aura_surface_set_server_start_resize, |
| aura_surface_intent_to_snap, |
| aura_surface_set_snap_left, |
| aura_surface_set_snap_right, |
| aura_surface_unset_snap, |
| aura_surface_set_window_session_id, |
| aura_surface_set_can_go_back, |
| aura_surface_unset_can_go_back, |
| aura_surface_set_pip, |
| aura_surface_unset_pip, |
| aura_surface_set_aspect_ratio, |
| aura_surface_move_to_desk, |
| aura_surface_set_initial_workspace, |
| aura_surface_set_pin, |
| aura_surface_unset_pin}; |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // aura_surface_interface: |
| |
| AuraSurface::AuraSurface(Surface* surface, wl_resource* resource) |
| : surface_(surface), resource_(resource) { |
| surface_->AddSurfaceObserver(this); |
| surface_->SetProperty(kSurfaceHasAuraSurfaceKey, true); |
| WMHelper::GetInstance()->AddActivationObserver(this); |
| } |
| |
| AuraSurface::~AuraSurface() { |
| WMHelper::GetInstance()->RemoveActivationObserver(this); |
| if (surface_) { |
| surface_->RemoveSurfaceObserver(this); |
| surface_->SetProperty(kSurfaceHasAuraSurfaceKey, false); |
| } |
| } |
| |
| void AuraSurface::SetFrame(SurfaceFrameType type) { |
| if (surface_) |
| surface_->SetFrame(type); |
| } |
| |
| void AuraSurface::SetServerStartResize() { |
| if (surface_) |
| surface_->SetServerStartResize(); |
| } |
| |
| void AuraSurface::SetFrameColors(SkColor active_frame_color, |
| SkColor inactive_frame_color) { |
| if (surface_) |
| surface_->SetFrameColors(active_frame_color, inactive_frame_color); |
| } |
| |
| void AuraSurface::SetParent(AuraSurface* parent, const gfx::Point& position) { |
| if (surface_) |
| surface_->SetParent(parent ? parent->surface_ : nullptr, position); |
| } |
| |
| void AuraSurface::SetStartupId(const char* startup_id) { |
| if (surface_) |
| surface_->SetStartupId(startup_id); |
| } |
| |
| void AuraSurface::SetApplicationId(const char* application_id) { |
| if (surface_) |
| surface_->SetApplicationId(application_id); |
| } |
| |
| void AuraSurface::SetClientSurfaceId(const char* client_surface_id) { |
| if (surface_) |
| surface_->SetClientSurfaceId(client_surface_id); |
| } |
| |
| void AuraSurface::SetOcclusionTracking(bool tracking) { |
| if (surface_) |
| surface_->SetOcclusionTracking(tracking); |
| } |
| |
| void AuraSurface::Activate() { |
| if (surface_) |
| surface_->RequestActivation(); |
| } |
| |
| void AuraSurface::DrawAttention() { |
| if (!surface_) |
| return; |
| // TODO(hollingum): implement me. |
| LOG(WARNING) << "Surface requested attention, but that is not implemented"; |
| } |
| |
| void AuraSurface::SetFullscreenMode(uint32_t mode) { |
| if (!surface_) |
| return; |
| |
| switch (mode) { |
| case ZAURA_SURFACE_FULLSCREEN_MODE_PLAIN: |
| surface_->SetUseImmersiveForFullscreen(false); |
| break; |
| case ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE: |
| surface_->SetUseImmersiveForFullscreen(true); |
| break; |
| default: |
| VLOG(2) << "aura_surface_set_fullscreen_mode(): unknown fullscreen_mode: " |
| << mode; |
| break; |
| } |
| } |
| |
| void AuraSurface::IntentToSnap(uint32_t snap_direction) { |
| switch (snap_direction) { |
| case ZAURA_SURFACE_SNAP_DIRECTION_NONE: |
| surface_->HideSnapPreview(); |
| break; |
| case ZAURA_SURFACE_SNAP_DIRECTION_LEFT: |
| surface_->ShowSnapPreviewToPrimary(); |
| break; |
| case ZAURA_SURFACE_SNAP_DIRECTION_RIGHT: |
| surface_->ShowSnapPreviewToSecondary(); |
| break; |
| } |
| } |
| |
| void AuraSurface::SetSnapPrimary() { |
| surface_->SetSnappedToPrimary(); |
| } |
| |
| void AuraSurface::SetSnapSecondary() { |
| surface_->SetSnappedToSecondary(); |
| } |
| |
| void AuraSurface::UnsetSnap() { |
| surface_->UnsetSnap(); |
| } |
| |
| void AuraSurface::SetWindowSessionId(int32_t window_session_id) { |
| surface_->SetWindowSessionId(window_session_id); |
| } |
| |
| void AuraSurface::SetCanGoBack() { |
| surface_->SetCanGoBack(); |
| } |
| |
| void AuraSurface::UnsetCanGoBack() { |
| surface_->UnsetCanGoBack(); |
| } |
| |
| void AuraSurface::SetPip() { |
| surface_->SetPip(); |
| } |
| |
| void AuraSurface::UnsetPip() { |
| surface_->UnsetPip(); |
| } |
| |
| void AuraSurface::SetAspectRatio(const gfx::SizeF& aspect_ratio) { |
| surface_->SetAspectRatio(aspect_ratio); |
| } |
| |
| // Overridden from SurfaceObserver: |
| void AuraSurface::OnSurfaceDestroying(Surface* surface) { |
| surface->RemoveSurfaceObserver(this); |
| surface_ = nullptr; |
| } |
| |
| void AuraSurface::OnWindowOcclusionChanged(Surface* surface) { |
| if (!surface_ || !surface_->IsTrackingOcclusion()) |
| return; |
| auto* window = surface_->window(); |
| ComputeAndSendOcclusion(window->GetOcclusionState(), |
| window->occluded_region_in_root()); |
| } |
| |
| void AuraSurface::OnFrameLockingChanged(Surface* surface, bool lock) { |
| if (wl_resource_get_version(resource_) < |
| ZAURA_SURFACE_LOCK_FRAME_NORMAL_SINCE_VERSION) |
| return; |
| if (lock) |
| zaura_surface_send_lock_frame_normal(resource_); |
| else |
| zaura_surface_send_unlock_frame_normal(resource_); |
| } |
| |
| void AuraSurface::OnWindowActivating(ActivationReason reason, |
| aura::Window* gaining_active, |
| aura::Window* losing_active) { |
| if (!surface_ || !losing_active) |
| return; |
| |
| auto* window = surface_->window(); |
| // Check if this surface is a child of a window that is losing focus. |
| auto* widget = views::Widget::GetTopLevelWidgetForNativeView(window); |
| if (!widget || losing_active != widget->GetNativeWindow() || |
| !surface_->IsTrackingOcclusion()) |
| return; |
| |
| // Result may be changed by animated windows, so compute it explicitly. |
| // We need to send occlusion updates before activation changes because |
| // we can only trigger onUserLeaveHint (which triggers Android PIP) upon |
| // losing activation. Windows that have animations applied to them are |
| // normally ignored by the occlusion tracker, but in this case we want |
| // to send the occlusion state after animations finish before activation |
| // changes. This lets us support showing a new window triggering PIP, |
| // which normally would not work due to the window show animation delaying |
| // any occlusion update. |
| // This happens before any window stacking changes occur, which means that |
| // calling the occlusion tracker here for activation changes which change |
| // the window stacking order may not produce correct results. But, |
| // showing a new window will have it stacked on top already, so this will not |
| // be a problem. |
| // TODO(edcourtney): Currently, this does not work for activating via the |
| // overview, because starting the overview activates some overview specific |
| // window. To support overview, we would need to have it keep the original |
| // window activated and also do this inside OnWindowStackingChanged. |
| // See crbug.com/948492. |
| auto* occlusion_tracker = |
| aura::Env::GetInstance()->GetWindowOcclusionTracker(); |
| if (occlusion_tracker->HasIgnoredAnimatingWindows()) { |
| const auto& occlusion_data = |
| occlusion_tracker->ComputeTargetOcclusionForWindow(window); |
| ComputeAndSendOcclusion(occlusion_data.occlusion_state, |
| occlusion_data.occluded_region); |
| } |
| } |
| |
| void AuraSurface::SendOcclusionFraction(float occlusion_fraction) { |
| if (wl_resource_get_version(resource_) < 8) |
| return; |
| // TODO(edcourtney): For now, we are treating every occlusion change as |
| // from a user action. |
| zaura_surface_send_occlusion_changed( |
| resource_, wl_fixed_from_double(occlusion_fraction), |
| ZAURA_SURFACE_OCCLUSION_CHANGE_REASON_USER_ACTION); |
| wl_client_flush(wl_resource_get_client(resource_)); |
| } |
| |
| void AuraSurface::SendOcclusionState( |
| const aura::Window::OcclusionState occlusion_state) { |
| if (wl_resource_get_version(resource_) < 21) |
| return; |
| zaura_surface_send_occlusion_state_changed( |
| resource_, WaylandOcclusionState(occlusion_state)); |
| wl_client_flush(wl_resource_get_client(resource_)); |
| } |
| |
| void AuraSurface::ComputeAndSendOcclusion( |
| const aura::Window::OcclusionState occlusion_state, |
| const SkRegion& occluded_region) { |
| SendOcclusionState(occlusion_state); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // Should re-write in locked case - we don't want to trigger PIP upon |
| // locking the screen. |
| if (ash::Shell::Get()->session_controller()->IsScreenLocked()) { |
| SendOcclusionFraction(0.0f); |
| return; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // Send the occlusion fraction. |
| auto* window = surface_->window(); |
| float fraction_occluded = 0.0f; |
| switch (occlusion_state) { |
| case aura::Window::OcclusionState::VISIBLE: { |
| const gfx::Rect display_bounds_in_screen = |
| display::Screen::GetScreen() |
| ->GetDisplayNearestWindow(window) |
| .bounds(); |
| const gfx::Rect bounds_in_screen = GetTransformedBoundsInScreen(window); |
| const int tracked_area = |
| bounds_in_screen.width() * bounds_in_screen.height(); |
| SkRegion tracked_and_occluded_region = occluded_region; |
| tracked_and_occluded_region.op(gfx::RectToSkIRect(bounds_in_screen), |
| SkRegion::Op::kIntersect_Op); |
| |
| // Clip the area outside of the display. |
| gfx::Rect area_inside_display = bounds_in_screen; |
| area_inside_display.Intersect(display_bounds_in_screen); |
| int occluded_area = tracked_area - area_inside_display.width() * |
| area_inside_display.height(); |
| |
| for (SkRegion::Iterator i(tracked_and_occluded_region); !i.done(); |
| i.next()) { |
| occluded_area += i.rect().width() * i.rect().height(); |
| } |
| if (tracked_area) { |
| fraction_occluded = static_cast<float>(occluded_area) / |
| static_cast<float>(tracked_area); |
| } |
| break; |
| } |
| case aura::Window::OcclusionState::OCCLUDED: |
| case aura::Window::OcclusionState::HIDDEN: |
| // Consider the OCCLUDED and HIDDEN cases as 100% occlusion. |
| fraction_occluded = 1.0f; |
| break; |
| case aura::Window::OcclusionState::UNKNOWN: |
| return; // Window is not tracked. |
| } |
| SendOcclusionFraction(fraction_occluded); |
| } |
| |
| void AuraSurface::OnDeskChanged(Surface* surface, int state) { |
| if (wl_resource_get_version(resource_) < |
| ZAURA_SURFACE_DESK_CHANGED_SINCE_VERSION) { |
| return; |
| } |
| |
| zaura_surface_send_desk_changed(resource_, state); |
| } |
| |
| void AuraSurface::MoveToDesk(int desk_index) { |
| constexpr int kToggleVisibleOnAllWorkspacesValue = -1; |
| if (desk_index == kToggleVisibleOnAllWorkspacesValue) { |
| surface_->SetVisibleOnAllWorkspaces(); |
| } else { |
| surface_->MoveToDesk(desk_index); |
| } |
| } |
| |
| void AuraSurface::SetInitialWorkspace(const char* initial_workspace) { |
| surface_->SetInitialWorkspace(initial_workspace); |
| } |
| |
| void AuraSurface::Pin(bool trusted) { |
| surface_->Pin(trusted); |
| } |
| |
| void AuraSurface::Unpin() { |
| surface_->Unpin(); |
| } |
| |
| namespace { |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // aura_output_interface: |
| |
| class AuraOutput : public WaylandDisplayObserver { |
| public: |
| explicit AuraOutput(wl_resource* resource) : resource_(resource) {} |
| |
| AuraOutput(const AuraOutput&) = delete; |
| AuraOutput& operator=(const AuraOutput&) = delete; |
| |
| // Overridden from WaylandDisplayObserver: |
| bool SendDisplayMetrics(const display::Display& display, |
| uint32_t changed_metrics) override { |
| if (!(changed_metrics & |
| (display::DisplayObserver::DISPLAY_METRIC_BOUNDS | |
| display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR | |
| display::DisplayObserver::DISPLAY_METRIC_ROTATION))) { |
| return false; |
| } |
| |
| const WMHelper* wm_helper = WMHelper::GetInstance(); |
| const display::ManagedDisplayInfo& display_info = |
| wm_helper->GetDisplayInfo(display.id()); |
| |
| if (wl_resource_get_version(resource_) >= |
| ZAURA_OUTPUT_SCALE_SINCE_VERSION) { |
| display::ManagedDisplayMode active_mode; |
| bool rv = |
| wm_helper->GetActiveModeForDisplayId(display.id(), &active_mode); |
| DCHECK(rv); |
| const int32_t current_output_scale = |
| std::round(display_info.zoom_factor() * 1000.f); |
| std::vector<float> zoom_factors = |
| display::GetDisplayZoomFactors(active_mode); |
| |
| // Ensure that the current zoom factor is a part of the list. |
| auto it = std::find_if( |
| zoom_factors.begin(), zoom_factors.end(), |
| [&display_info](float zoom_factor) -> bool { |
| return std::abs(display_info.zoom_factor() - zoom_factor) <= |
| std::numeric_limits<float>::epsilon(); |
| }); |
| if (it == zoom_factors.end()) |
| zoom_factors.push_back(display_info.zoom_factor()); |
| |
| for (float zoom_factor : zoom_factors) { |
| int32_t output_scale = std::round(zoom_factor * 1000.f); |
| uint32_t flags = 0; |
| if (output_scale == 1000) |
| flags |= ZAURA_OUTPUT_SCALE_PROPERTY_PREFERRED; |
| if (current_output_scale == output_scale) |
| flags |= ZAURA_OUTPUT_SCALE_PROPERTY_CURRENT; |
| |
| // TODO(malaykeshav): This can be removed in the future when client |
| // has been updated. |
| if (wl_resource_get_version(resource_) < 6) |
| output_scale = std::round(1000.f / zoom_factor); |
| |
| zaura_output_send_scale(resource_, flags, output_scale); |
| } |
| } |
| |
| if (wl_resource_get_version(resource_) >= |
| ZAURA_OUTPUT_CONNECTION_SINCE_VERSION) { |
| zaura_output_send_connection(resource_, |
| display.IsInternal() |
| ? ZAURA_OUTPUT_CONNECTION_TYPE_INTERNAL |
| : ZAURA_OUTPUT_CONNECTION_TYPE_UNKNOWN); |
| } |
| |
| if (wl_resource_get_version(resource_) >= |
| ZAURA_OUTPUT_DEVICE_SCALE_FACTOR_SINCE_VERSION) { |
| zaura_output_send_device_scale_factor( |
| resource_, display_info.device_scale_factor() * 1000); |
| } |
| |
| return true; |
| } |
| |
| private: |
| wl_resource* const resource_; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // aura_shell_interface: |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // IDs of bugs that have been fixed in the exo implementation. These are |
| // propagated to clients on aura_shell bind and can be used to gate client |
| // logic on the presence of certain fixes. |
| const uint32_t kFixedBugIds[] = { |
| 1151508, // Do not remove, used for sanity checks by |wayland_simple_client| |
| }; |
| |
| // Implements aura shell interface and monitors workspace state needed |
| // for the aura shell interface. |
| class WaylandAuraShell : public ash::DesksController::Observer, |
| public ash::TabletModeObserver { |
| public: |
| WaylandAuraShell(wl_resource* aura_shell_resource, Display* display) |
| : aura_shell_resource_(aura_shell_resource) { |
| WMHelperChromeOS* helper = WMHelperChromeOS::GetInstance(); |
| helper->AddTabletModeObserver(this); |
| ash::DesksController::Get()->AddObserver(this); |
| if (wl_resource_get_version(aura_shell_resource_) >= |
| ZAURA_SHELL_LAYOUT_MODE_SINCE_VERSION) { |
| auto layout_mode = helper->InTabletMode() |
| ? ZAURA_SHELL_LAYOUT_MODE_TABLET |
| : ZAURA_SHELL_LAYOUT_MODE_WINDOWED; |
| zaura_shell_send_layout_mode(aura_shell_resource_, layout_mode); |
| } |
| if (wl_resource_get_version(aura_shell_resource_) >= |
| ZAURA_SHELL_BUG_FIX_SINCE_VERSION) { |
| for (uint32_t bug_id : kFixedBugIds) { |
| zaura_shell_send_bug_fix(aura_shell_resource_, bug_id); |
| } |
| } |
| display->seat()->SetFocusChangedCallback( |
| base::BindRepeating(&WaylandAuraShell::FocusedSurfaceChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| OnDesksChanged(); |
| OnDeskActivationChanged(); |
| } |
| WaylandAuraShell(const WaylandAuraShell&) = delete; |
| WaylandAuraShell& operator=(const WaylandAuraShell&) = delete; |
| ~WaylandAuraShell() override { |
| WMHelperChromeOS* helper = WMHelperChromeOS::GetInstance(); |
| helper->RemoveTabletModeObserver(this); |
| ash::DesksController::Get()->RemoveObserver(this); |
| } |
| |
| // Overridden from ash::TabletModeObserver: |
| void OnTabletModeStarted() override { |
| if (wl_resource_get_version(aura_shell_resource_) >= |
| ZAURA_SHELL_LAYOUT_MODE_SINCE_VERSION) |
| zaura_shell_send_layout_mode(aura_shell_resource_, |
| ZAURA_SHELL_LAYOUT_MODE_TABLET); |
| } |
| void OnTabletModeEnding() override { |
| if (wl_resource_get_version(aura_shell_resource_) >= |
| ZAURA_SHELL_LAYOUT_MODE_SINCE_VERSION) |
| zaura_shell_send_layout_mode(aura_shell_resource_, |
| ZAURA_SHELL_LAYOUT_MODE_WINDOWED); |
| } |
| void OnTabletModeEnded() override {} |
| |
| // ash::DesksController::Observer: |
| void OnDeskAdded(const ash::Desk* desk) override { OnDesksChanged(); } |
| void OnDeskRemoved(const ash::Desk* desk) override { OnDesksChanged(); } |
| void OnDeskReordered(int old_index, int new_index) override { |
| OnDesksChanged(); |
| } |
| void OnDeskActivationChanged(const ash::Desk* activated, |
| const ash::Desk* deactivated) override { |
| OnDeskActivationChanged(); |
| } |
| void OnDeskSwitchAnimationLaunching() override {} |
| void OnDeskSwitchAnimationFinished() override {} |
| void OnDeskNameChanged(const ash::Desk* desk, |
| const std::u16string& new_name) override { |
| OnDesksChanged(); |
| } |
| |
| private: |
| void OnDesksChanged() { |
| if (wl_resource_get_version(aura_shell_resource_) < |
| ZAURA_SHELL_DESKS_CHANGED_SINCE_VERSION) { |
| return; |
| } |
| |
| wl_array desk_names; |
| wl_array_init(&desk_names); |
| |
| for (const auto& desk : ash::DesksController::Get()->desks()) { |
| std::string name = base::UTF16ToUTF8(desk->name()); |
| char* desk_name = |
| static_cast<char*>(wl_array_add(&desk_names, name.size() + 1)); |
| strcpy(desk_name, name.c_str()); |
| } |
| |
| zaura_shell_send_desks_changed(aura_shell_resource_, &desk_names); |
| wl_array_release(&desk_names); |
| } |
| |
| void OnDeskActivationChanged() { |
| if (wl_resource_get_version(aura_shell_resource_) < |
| ZAURA_SHELL_DESK_ACTIVATION_CHANGED_SINCE_VERSION) { |
| return; |
| } |
| |
| zaura_shell_send_desk_activation_changed( |
| aura_shell_resource_, |
| ash::DesksController::Get()->GetActiveDeskIndex()); |
| } |
| |
| void FocusedSurfaceChanged(Surface* gained_active_surface, |
| Surface* lost_active_surface, |
| bool has_focused_client) { |
| if (wl_resource_get_version(aura_shell_resource_) < |
| ZAURA_SHELL_ACTIVATED_SINCE_VERSION) |
| return; |
| if (gained_active_surface == lost_active_surface) |
| return; |
| |
| wl_resource* gained_active_surface_resource = |
| gained_active_surface ? GetSurfaceResource(gained_active_surface) |
| : nullptr; |
| wl_resource* lost_active_surface_resource = |
| lost_active_surface ? GetSurfaceResource(lost_active_surface) : nullptr; |
| |
| wl_client* client = wl_resource_get_client(aura_shell_resource_); |
| |
| // If surface that gained active is not owned by the aura shell then |
| // set to null. |
| if (gained_active_surface_resource && |
| wl_resource_get_client(gained_active_surface_resource) != client) { |
| gained_active_surface_resource = nullptr; |
| } |
| |
| // If surface that lost active is not owned by the aura shell then set |
| // to null. |
| if (lost_active_surface_resource && |
| wl_resource_get_client(lost_active_surface_resource) != client) { |
| lost_active_surface_resource = nullptr; |
| } |
| |
| zaura_shell_send_activated(aura_shell_resource_, |
| gained_active_surface_resource, |
| lost_active_surface_resource); |
| |
| wl_client_flush(client); |
| } |
| |
| // The aura shell resource associated with observer. |
| wl_resource* const aura_shell_resource_; |
| |
| base::WeakPtrFactory<WaylandAuraShell> weak_ptr_factory_{this}; |
| }; |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH)) |
| |
| void aura_shell_get_aura_surface(wl_client* client, |
| wl_resource* resource, |
| uint32_t id, |
| wl_resource* surface_resource) { |
| Surface* surface = GetUserDataAs<Surface>(surface_resource); |
| if (surface->GetProperty(kSurfaceHasAuraSurfaceKey)) { |
| wl_resource_post_error( |
| resource, ZAURA_SHELL_ERROR_AURA_SURFACE_EXISTS, |
| "an aura surface object for that surface already exists"); |
| return; |
| } |
| |
| wl_resource* aura_surface_resource = wl_resource_create( |
| client, &zaura_surface_interface, wl_resource_get_version(resource), id); |
| |
| SetImplementation( |
| aura_surface_resource, &aura_surface_implementation, |
| std::make_unique<AuraSurface>(surface, aura_surface_resource)); |
| } |
| |
| void aura_shell_get_aura_output(wl_client* client, |
| wl_resource* resource, |
| uint32_t id, |
| wl_resource* output_resource) { |
| WaylandDisplayHandler* display_handler = |
| GetUserDataAs<WaylandDisplayHandler>(output_resource); |
| |
| wl_resource* aura_output_resource = wl_resource_create( |
| client, &zaura_output_interface, wl_resource_get_version(resource), id); |
| |
| auto aura_output = std::make_unique<AuraOutput>(aura_output_resource); |
| display_handler->AddObserver(aura_output.get()); |
| |
| SetImplementation(aura_output_resource, nullptr, std::move(aura_output)); |
| } |
| |
| const struct zaura_shell_interface aura_shell_implementation = { |
| aura_shell_get_aura_surface, |
| aura_shell_get_aura_output, |
| }; |
| |
| } // namespace |
| |
| void bind_aura_shell(wl_client* client, |
| void* data, |
| uint32_t version, |
| uint32_t id) { |
| wl_resource* resource = |
| wl_resource_create(client, &zaura_shell_interface, |
| std::min(version, kZAuraShellVersion), id); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| Display* display = static_cast<Display*>(data); |
| SetImplementation(resource, &aura_shell_implementation, |
| std::make_unique<WaylandAuraShell>(resource, display)); |
| #else |
| wl_resource_set_implementation(resource, &aura_shell_implementation, nullptr, |
| nullptr); |
| #endif |
| } |
| |
| } // namespace wayland |
| } // namespace exo |