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
ash / system / power / power_button_screenshot_controller.cc [blame]
// Copyright 2017 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/system/power/power_button_screenshot_controller.h"
#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/system/power/power_button_controller.h"
#include "ash/wm/window_util.h"
#include "base/functional/bind.h"
#include "base/metrics/user_metrics.h"
#include "base/time/tick_clock.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
namespace ash {
namespace {
bool VolumeKeyMaybeUsedByApp() {
aura::Window* active = window_util::GetActiveWindow();
return active && active->GetProperty(ash::kCanConsumeSystemKeysKey);
}
} // namespace
constexpr base::TimeDelta
PowerButtonScreenshotController::kScreenshotChordDelay;
PowerButtonScreenshotController::PowerButtonScreenshotController(
const base::TickClock* tick_clock)
: tick_clock_(tick_clock) {
DCHECK(tick_clock_);
// Using prepend to make sure this event handler is put in front of
// AcceleratorFilter. See Shell::Init().
Shell::Get()->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
}
PowerButtonScreenshotController::~PowerButtonScreenshotController() {
Shell::Get()->RemovePreTargetHandler(this);
}
bool PowerButtonScreenshotController::OnPowerButtonEvent(
bool down,
const base::TimeTicks& timestamp) {
if (!display::Screen::GetScreen()->InTabletMode()) {
return false;
}
power_button_pressed_ = down;
if (power_button_pressed_) {
volume_down_timer_.Stop();
volume_up_timer_.Stop();
power_button_pressed_time_ = tick_clock_->NowTicks();
if (InterceptScreenshotChord())
return true;
}
if (!down)
return false;
// If volume key is pressed, mark power button as consumed. This invalidates
// other power button's behavior when user tries to operate screenshot.
return volume_down_key_pressed_ || volume_up_key_pressed_;
}
void PowerButtonScreenshotController::OnKeyEvent(ui::KeyEvent* event) {
if (!display::Screen::GetScreen()->InTabletMode()) {
return;
}
ui::KeyboardCode key_code = event->key_code();
if (key_code != ui::VKEY_VOLUME_DOWN && key_code != ui::VKEY_VOLUME_UP)
return;
bool did_consume_volume_keys =
volume_down_key_pressed_ || volume_up_key_pressed_;
bool volume_key_maybe_used_by_app = VolumeKeyMaybeUsedByApp();
// Even if the app is requesting to consume volume key, do not give it if
// 1) power button is already pressed. This should trigger screenshot.
// 2) if this is already handling volume key. We need to continue processing
// volume eve after power button is released, until volume keys are released.
if (volume_key_maybe_used_by_app && !power_button_pressed_ &&
!did_consume_volume_keys) {
return;
}
const bool is_volume_down = key_code == ui::VKEY_VOLUME_DOWN;
if (event->type() == ui::EventType::kKeyPressed) {
if (!did_consume_volume_keys) {
if (is_volume_down) {
volume_down_key_pressed_ = true;
volume_down_key_pressed_time_ = tick_clock_->NowTicks();
consume_volume_down_ = false;
InterceptScreenshotChord();
} else {
volume_up_key_pressed_ = true;
volume_up_key_pressed_time_ = tick_clock_->NowTicks();
consume_volume_up_ = false;
InterceptScreenshotChord();
}
}
// Do no pass the event to the app if the events are being
// processed
if (consume_volume_down_ || consume_volume_up_ ||
volume_key_maybe_used_by_app) {
event->StopPropagation();
}
} else {
is_volume_down ? volume_down_key_pressed_ = false
: volume_up_key_pressed_ = false;
}
// When volume key is pressed, cancel the ongoing power button behavior.
if (volume_down_key_pressed_ || volume_up_key_pressed_)
Shell::Get()->power_button_controller()->CancelPowerButtonEvent();
// On volume down/up key pressed while power button not pressed yet state, do
// not propagate volume down/up key pressed event for chord delay time. Start
// the timer to wait power button pressed for screenshot operation, and on
// timeout perform the delayed volume down/up operation.
if (power_button_pressed_)
return;
base::TimeTicks now = tick_clock_->NowTicks();
if (volume_down_key_pressed_ && is_volume_down &&
now <= volume_down_key_pressed_time_ + kScreenshotChordDelay) {
event->StopPropagation();
if (!volume_down_timer_.IsRunning()) {
volume_down_timer_.Start(
FROM_HERE, kScreenshotChordDelay,
base::BindOnce(
&PowerButtonScreenshotController::OnVolumeControlTimeout,
base::Unretained(this), ui::Accelerator(*event), /*down=*/true));
}
} else if (volume_up_key_pressed_ && !is_volume_down &&
now <= volume_up_key_pressed_time_ + kScreenshotChordDelay) {
event->StopPropagation();
if (!volume_up_timer_.IsRunning()) {
volume_up_timer_.Start(
FROM_HERE, kScreenshotChordDelay,
base::BindOnce(
&PowerButtonScreenshotController::OnVolumeControlTimeout,
base::Unretained(this), ui::Accelerator(*event), /*down=*/false));
}
}
}
bool PowerButtonScreenshotController::InterceptScreenshotChord() {
if (!power_button_pressed_ ||
(!volume_down_key_pressed_ && !volume_up_key_pressed_)) {
return false;
}
base::TimeTicks now = tick_clock_->NowTicks();
if (now > power_button_pressed_time_ + kScreenshotChordDelay)
return false;
consume_volume_down_ =
volume_down_key_pressed_ &&
now <= volume_down_key_pressed_time_ + kScreenshotChordDelay;
consume_volume_up_ =
volume_up_key_pressed_ &&
now <= volume_up_key_pressed_time_ + kScreenshotChordDelay;
if (consume_volume_down_ || consume_volume_up_) {
Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
AcceleratorAction::kTakeScreenshot, {});
base::RecordAction(base::UserMetricsAction("Accel_PowerButton_Screenshot"));
}
return consume_volume_down_ || consume_volume_up_;
}
void PowerButtonScreenshotController::OnVolumeControlTimeout(
const ui::Accelerator& accelerator,
bool down) {
Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
down ? AcceleratorAction::kVolumeDown : AcceleratorAction::kVolumeUp,
accelerator);
}
} // namespace ash