| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/display/resolution_notification_controller.h" |
| |
| #include <utility> |
| |
| #include "ash/display/display_change_dialog.h" |
| #include "ash/display/display_util.h" |
| #include "ash/public/cpp/notification_utils.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/screen_layout_observer.h" |
| #include "base/functional/bind.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/display/display_features.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/manager/managed_display_info.h" |
| #include "ui/display/util/display_util.h" |
| |
| namespace ash { |
| |
| struct ResolutionNotificationController::ResolutionChangeInfo { |
| ResolutionChangeInfo(int64_t display_id, |
| const display::ManagedDisplayMode& old_resolution, |
| const display::ManagedDisplayMode& new_resolution, |
| base::OnceClosure accept_callback); |
| |
| ResolutionChangeInfo(const ResolutionChangeInfo&) = delete; |
| ResolutionChangeInfo& operator=(const ResolutionChangeInfo&) = delete; |
| |
| ~ResolutionChangeInfo(); |
| |
| // The id of the display where the resolution change happens. |
| const int64_t display_id; |
| |
| // The resolution before the change. |
| display::ManagedDisplayMode old_resolution; |
| |
| // The requested resolution. Note that this may be different from |
| // |current_resolution| which is the actual resolution set. |
| display::ManagedDisplayMode new_resolution; |
| |
| // The actual resolution after the change. |
| display::ManagedDisplayMode current_resolution; |
| |
| // The callback when accept is chosen. |
| base::OnceClosure accept_callback; |
| }; |
| |
| ResolutionNotificationController::ResolutionChangeInfo::ResolutionChangeInfo( |
| int64_t display_id, |
| const display::ManagedDisplayMode& old_resolution, |
| const display::ManagedDisplayMode& new_resolution, |
| base::OnceClosure accept_callback) |
| : display_id(display_id), |
| old_resolution(old_resolution), |
| new_resolution(new_resolution), |
| accept_callback(std::move(accept_callback)) {} |
| |
| ResolutionNotificationController::ResolutionChangeInfo:: |
| ~ResolutionChangeInfo() = default; |
| |
| ResolutionNotificationController::ResolutionNotificationController() { |
| Shell::Get()->window_tree_host_manager()->AddObserver(this); |
| } |
| |
| ResolutionNotificationController::~ResolutionNotificationController() { |
| Shell::Get()->window_tree_host_manager()->RemoveObserver(this); |
| } |
| |
| bool ResolutionNotificationController::PrepareNotificationAndSetDisplayMode( |
| int64_t display_id, |
| const display::ManagedDisplayMode& old_resolution, |
| const display::ManagedDisplayMode& new_resolution, |
| crosapi::mojom::DisplayConfigSource source, |
| base::OnceClosure accept_callback) { |
| Shell::Get()->screen_layout_observer()->SetDisplayChangedFromSettingsUI( |
| display_id); |
| display::DisplayManager* const display_manager = |
| Shell::Get()->display_manager(); |
| if (source == crosapi::mojom::DisplayConfigSource::kPolicy || |
| display::IsInternalDisplayId(display_id)) { |
| // We don't show notifications to confirm/revert the resolution change in |
| // the case of an internal display or policy-forced changes. |
| return display_manager->SetDisplayMode(display_id, new_resolution); |
| } |
| |
| // If multiple resolution changes are invoked for the same display, |
| // the original resolution for the first resolution change has to be used |
| // instead of the specified |old_resolution|. |
| display::ManagedDisplayMode original_resolution; |
| if (change_info_ && change_info_->display_id == display_id) { |
| DCHECK_EQ(change_info_->new_resolution.size(), old_resolution.size()); |
| original_resolution = change_info_->old_resolution; |
| } |
| |
| if (change_info_ && change_info_->display_id != display_id) { |
| // Preparing the notification for a new resolution change of another display |
| // before the previous one was accepted. We decided that it's safer to |
| // revert the previous resolution change since the user didn't explicitly |
| // accept it, and we have no way of knowing for sure that it worked. |
| RevertResolutionChange(false /* display_was_removed */); |
| } |
| |
| change_info_ = std::make_unique<ResolutionChangeInfo>( |
| display_id, old_resolution, new_resolution, std::move(accept_callback)); |
| if (!original_resolution.size().IsEmpty()) |
| change_info_->old_resolution = original_resolution; |
| |
| if (!display_manager->SetDisplayMode(display_id, new_resolution)) { |
| // Discard the prepared notification data since we failed to set the new |
| // resolution. |
| change_info_.reset(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ResolutionNotificationController::ShouldShowDisplayChangeDialog() const { |
| return change_info_ && Shell::Get()->session_controller()->login_status() != |
| LoginStatus::KIOSK_APP; |
| } |
| |
| void ResolutionNotificationController::CreateOrReplaceModalDialog() { |
| if (confirmation_dialog_) |
| confirmation_dialog_->GetWidget()->CloseNow(); |
| |
| if (!ShouldShowDisplayChangeDialog()) |
| return; |
| |
| const std::u16string display_name = |
| base::UTF8ToUTF16(Shell::Get()->display_manager()->GetDisplayNameForId( |
| change_info_->display_id)); |
| const std::u16string actual_display_size = |
| base::UTF8ToUTF16(change_info_->current_resolution.size().ToString()); |
| const std::u16string requested_display_size = |
| base::UTF8ToUTF16(change_info_->new_resolution.size().ToString()); |
| |
| std::u16string dialog_title = |
| l10n_util::GetStringUTF16(IDS_ASH_RESOLUTION_CHANGE_DIALOG_TITLE); |
| |
| // Construct the timeout message, leaving a placeholder for the countdown |
| // timer so that the string does not need to be completely rebuilt every |
| // timer tick. |
| constexpr char16_t kTimeoutPlaceHolder[] = u"$1"; |
| |
| std::u16string timeout_message_with_placeholder; |
| if (display::features::IsListAllDisplayModesEnabled()) { |
| dialog_title = l10n_util::GetStringUTF16( |
| IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_TITLE); |
| |
| const std::u16string actual_refresh_rate = ConvertRefreshRateToString16( |
| change_info_->current_resolution.refresh_rate()); |
| const std::u16string requested_refresh_rate = ConvertRefreshRateToString16( |
| change_info_->new_resolution.refresh_rate()); |
| |
| const bool no_fallback = actual_display_size == requested_display_size && |
| actual_refresh_rate == requested_refresh_rate; |
| |
| timeout_message_with_placeholder = |
| no_fallback ? l10n_util::GetStringFUTF16( |
| IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_CHANGED, |
| display_name, actual_display_size, |
| actual_refresh_rate, kTimeoutPlaceHolder) |
| : l10n_util::GetStringFUTF16( |
| IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_FALLBACK, |
| {display_name, requested_display_size, |
| requested_refresh_rate, actual_display_size, |
| actual_refresh_rate, kTimeoutPlaceHolder}, |
| /*offsets=*/nullptr); |
| |
| } else { |
| timeout_message_with_placeholder = |
| actual_display_size == requested_display_size |
| ? l10n_util::GetStringFUTF16( |
| IDS_ASH_RESOLUTION_CHANGE_DIALOG_CHANGED, display_name, |
| actual_display_size, kTimeoutPlaceHolder) |
| : l10n_util::GetStringFUTF16( |
| IDS_ASH_RESOLUTION_CHANGE_DIALOG_FALLBACK, display_name, |
| requested_display_size, actual_display_size, |
| kTimeoutPlaceHolder); |
| } |
| |
| DisplayChangeDialog* dialog = new DisplayChangeDialog( |
| std::move(dialog_title), std::move(timeout_message_with_placeholder), |
| base::BindOnce(&ResolutionNotificationController::AcceptResolutionChange, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&ResolutionNotificationController::RevertResolutionChange, |
| weak_factory_.GetWeakPtr())); |
| confirmation_dialog_ = dialog->GetWeakPtr(); |
| } |
| |
| void ResolutionNotificationController::AcceptResolutionChange() { |
| if (!change_info_) |
| return; |
| base::OnceClosure callback = std::move(change_info_->accept_callback); |
| change_info_.reset(); |
| std::move(callback).Run(); |
| } |
| |
| void ResolutionNotificationController::RevertResolutionChange( |
| bool display_was_removed) { |
| if (!change_info_) |
| return; |
| const int64_t display_id = change_info_->display_id; |
| display::ManagedDisplayMode old_resolution = change_info_->old_resolution; |
| change_info_.reset(); |
| Shell::Get()->screen_layout_observer()->SetDisplayChangedFromSettingsUI( |
| display_id); |
| if (display_was_removed) { |
| // If display was removed then we are inside the stack of |
| // DisplayManager::UpdateDisplaysWith(), and we need to update the selected |
| // mode of this removed display without reentering again into |
| // UpdateDisplaysWith() because this can cause a crash. crbug.com/709722. |
| Shell::Get()->display_manager()->SetSelectedModeForDisplayId( |
| display_id, old_resolution); |
| } else { |
| Shell::Get()->display_manager()->SetDisplayMode(display_id, old_resolution); |
| } |
| } |
| |
| void ResolutionNotificationController::OnDisplayRemoved( |
| const display::Display& old_display) { |
| if (change_info_ && change_info_->display_id == old_display.id()) { |
| if (confirmation_dialog_) |
| confirmation_dialog_->GetWidget()->CloseNow(); |
| RevertResolutionChange(true /* display_was_removed */); |
| } |
| } |
| |
| void ResolutionNotificationController::OnDisplayConfigurationChanged() { |
| if (!change_info_) |
| return; |
| |
| display::ManagedDisplayMode mode; |
| if (Shell::Get()->display_manager()->GetActiveModeForDisplayId( |
| change_info_->display_id, &mode)) { |
| change_info_->current_resolution = mode; |
| } |
| |
| CreateOrReplaceModalDialog(); |
| } |
| |
| |
| } // namespace ash |