1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
ash / display / resolution_notification_controller.cc [blame]
// 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()->display_manager()->AddDisplayManagerObserver(this);
}
ResolutionNotificationController::~ResolutionNotificationController() {
Shell::Get()->display_manager()->RemoveDisplayManagerObserver(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()) {
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;
dialog_title =
no_fallback
? l10n_util::GetStringUTF16(
IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_TITLE_SUCCESS)
: l10n_util::GetStringUTF16(
IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_TITLE_FALLBACK);
timeout_message_with_placeholder =
no_fallback ? l10n_util::GetStringFUTF16(
IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_CHANGED_NEW,
display_name, actual_display_size,
actual_refresh_rate, kTimeoutPlaceHolder)
: l10n_util::GetStringFUTF16(
IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_FALLBACK_NEW,
{display_name, actual_display_size,
actual_refresh_rate, requested_display_size,
requested_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::OnDisplaysRemoved(
const display::Displays& removed_displays) {
for (const auto& display : removed_displays) {
if (change_info_ && change_info_->display_id == display.id()) {
if (confirmation_dialog_) {
// Use CloseWithReason rather than CloseNow to make sure the screen
// doesn't stay dimmed after the widget is closed. b/288485093.
confirmation_dialog_->GetWidget()->CloseWithReason(
views::Widget::ClosedReason::kLostFocus);
}
RevertResolutionChange(/*display_was_removed=*/true);
break;
}
}
}
void ResolutionNotificationController::OnDidApplyDisplayChanges() {
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