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
|
// 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
|