blob: a8e2471db51df14f3b2a23e18435322bde16f340 [file] [log] [blame]
// Copyright 2020 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 "chrome/browser/ui/webui/settings/chromeos/switch_access_handler.h"
#include <memory>
#include "ash/public/cpp/accessibility_controller.h"
#include "ash/public/cpp/ash_constants.h"
#include "ash/public/cpp/ash_pref_names.h"
#include "base/bind.h"
#include "base/no_destructor.h"
#include "base/values.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/web_contents.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_codes.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/ozone/layout/keyboard_layout_engine.h"
#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
namespace chromeos {
namespace settings {
namespace {
struct AssignmentInfo {
std::string action_name_for_js;
std::string pref_name;
};
std::string GetStringForKeyboardCode(ui::KeyboardCode key_code) {
ui::DomKey dom_key;
ui::KeyboardCode key_code_to_compare = ui::VKEY_UNKNOWN;
for (const auto& dom_code : ui::dom_codes) {
if (!ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine()->Lookup(
dom_code, /*flags=*/ui::EF_NONE, &dom_key, &key_code_to_compare)) {
continue;
}
if (key_code_to_compare != key_code || !dom_key.IsValid() ||
dom_key.IsDeadKey()) {
continue;
}
// Make sure the space key is rendered as "Space" instead of " ".
if (key_code == ui::VKEY_SPACE) {
return l10n_util::GetStringUTF8(IDS_SETTINGS_SWITCH_ASSIGN_OPTION_SPACE);
}
return ui::KeycodeConverter::DomKeyToKeyString(dom_key);
}
return std::string();
}
std::string GetSwitchAccessDevice(ui::InputDeviceType source_device_type) {
switch (source_device_type) {
case ui::INPUT_DEVICE_INTERNAL:
return ash::kSwitchAccessInternalDevice;
case ui::INPUT_DEVICE_USB:
return ash::kSwitchAccessUsbDevice;
case ui::INPUT_DEVICE_BLUETOOTH:
return ash::kSwitchAccessBluetoothDevice;
case ui::INPUT_DEVICE_UNKNOWN:
return ash::kSwitchAccessUnknownDevice;
}
}
} // namespace
SwitchAccessHandler::SwitchAccessHandler(PrefService* prefs) : prefs_(prefs) {}
SwitchAccessHandler::~SwitchAccessHandler() {
// Ensure we always leave Switch Access in a good state no matter what.
if (web_ui() && web_ui()->GetWebContents() &&
web_ui()->GetWebContents()->GetNativeView()) {
web_ui()->GetWebContents()->GetNativeView()->RemovePreTargetHandler(this);
}
if (ash::AccessibilityController::Get())
ash::AccessibilityController::Get()->SuspendSwitchAccessKeyHandling(false);
}
void SwitchAccessHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"refreshAssignmentsFromPrefs",
base::BindRepeating(
&SwitchAccessHandler::HandleRefreshAssignmentsFromPrefs,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"notifySwitchAccessActionAssignmentDialogAttached",
base::BindRepeating(
&SwitchAccessHandler::
HandleNotifySwitchAccessActionAssignmentDialogAttached,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"notifySwitchAccessActionAssignmentDialogDetached",
base::BindRepeating(
&SwitchAccessHandler::
HandleNotifySwitchAccessActionAssignmentDialogDetached,
base::Unretained(this)));
}
void SwitchAccessHandler::OnJavascriptAllowed() {
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(prefs_);
pref_change_registrar_->Add(
ash::prefs::kAccessibilitySwitchAccessSelectDeviceKeyCodes,
base::BindRepeating(
&SwitchAccessHandler::OnSwitchAccessAssignmentsUpdated,
base::Unretained(this)));
pref_change_registrar_->Add(
ash::prefs::kAccessibilitySwitchAccessNextDeviceKeyCodes,
base::BindRepeating(
&SwitchAccessHandler::OnSwitchAccessAssignmentsUpdated,
base::Unretained(this)));
pref_change_registrar_->Add(
ash::prefs::kAccessibilitySwitchAccessPreviousDeviceKeyCodes,
base::BindRepeating(
&SwitchAccessHandler::OnSwitchAccessAssignmentsUpdated,
base::Unretained(this)));
}
void SwitchAccessHandler::OnJavascriptDisallowed() {
pref_change_registrar_.reset();
}
void SwitchAccessHandler::OnKeyEvent(ui::KeyEvent* event) {
event->StopPropagation();
event->SetHandled();
if (event->type() == ui::ET_KEY_RELEASED)
return;
base::DictionaryValue response;
response.SetIntPath("keyCode", static_cast<int>(event->key_code()));
response.SetStringPath("key", GetStringForKeyboardCode(event->key_code()));
ui::InputDeviceType deviceType = ui::INPUT_DEVICE_UNKNOWN;
int source_device_id = event->source_device_id();
for (const auto& keyboard :
ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
if (source_device_id == keyboard.id) {
deviceType = keyboard.type;
break;
}
}
response.SetStringPath("device", GetSwitchAccessDevice(deviceType));
FireWebUIListener("switch-access-got-key-press-for-assignment", response);
}
void SwitchAccessHandler::HandleRefreshAssignmentsFromPrefs(
const base::ListValue* args) {
AllowJavascript();
OnSwitchAccessAssignmentsUpdated();
}
void SwitchAccessHandler::
HandleNotifySwitchAccessActionAssignmentDialogAttached(
const base::ListValue* args) {
AllowJavascript();
OnSwitchAccessAssignmentsUpdated();
web_ui()->GetWebContents()->GetNativeView()->AddPreTargetHandler(this);
ash::AccessibilityController::Get()->SuspendSwitchAccessKeyHandling(true);
}
void SwitchAccessHandler::
HandleNotifySwitchAccessActionAssignmentDialogDetached(
const base::ListValue* args) {
web_ui()->GetWebContents()->GetNativeView()->RemovePreTargetHandler(this);
ash::AccessibilityController::Get()->SuspendSwitchAccessKeyHandling(false);
}
void SwitchAccessHandler::OnSwitchAccessAssignmentsUpdated() {
base::DictionaryValue response;
static base::NoDestructor<std::vector<AssignmentInfo>> kAssignmentInfo({
{"select", ash::prefs::kAccessibilitySwitchAccessSelectDeviceKeyCodes},
{"next", ash::prefs::kAccessibilitySwitchAccessNextDeviceKeyCodes},
{"previous",
ash::prefs::kAccessibilitySwitchAccessPreviousDeviceKeyCodes},
});
for (const AssignmentInfo& info : *kAssignmentInfo) {
auto* keycodes = prefs_->GetDictionary(info.pref_name);
base::ListValue keys;
for (const auto& item : keycodes->DictItems()) {
int key_code;
if (!base::StringToInt(item.first, &key_code)) {
NOTREACHED();
return;
}
for (const base::Value& device_type : item.second.GetList()) {
base::DictionaryValue key;
key.SetStringPath("key", GetStringForKeyboardCode(
static_cast<ui::KeyboardCode>(key_code)));
key.SetStringPath("device", device_type.GetString());
keys.Append(std::move(key));
}
}
response.SetPath(info.action_name_for_js, std::move(keys));
}
FireWebUIListener("switch-access-assignments-changed", response);
}
} // namespace settings
} // namespace chromeos