blob: f4da9eb7400e3bcff28d1caf3a45d67f34d15f97 [file]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <atk/atk.h>
#include <array>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#if defined(USE_GIO)
#include <gio/gio.h>
#endif
#include "base/environment.h"
#include "base/memory/singleton.h"
#include "base/no_destructor.h"
#include "ui/accessibility/platform/atk_util_auralinux.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
#include "ui/base/glib/gsettings.h"
namespace {
constexpr auto kAccessibilityEnabledVariables =
std::to_array<base::cstring_view>({
"ACCESSIBILITY_ENABLED",
"GNOME_ACCESSIBILITY",
"QT_ACCESSIBILITY",
});
//
// AtkUtilAuraLinux definition and implementation.
//
struct AtkUtilAuraLinux {
AtkUtil parent;
};
struct AtkUtilAuraLinuxClass {
AtkUtilClass parent_class;
};
// SAFETY: Usage of third-party library macro is outside our control.
UNSAFE_BUFFERS(G_DEFINE_TYPE(AtkUtilAuraLinux,
atk_util_auralinux,
ATK_TYPE_UTIL))
static void atk_util_auralinux_init(AtkUtilAuraLinux *ax_util) {
}
static AtkObject* AtkUtilAuraLinuxGetRoot() {
ui::AXPlatformNode* application = ui::AXPlatformNodeAuraLinux::application();
if (application) {
return application->GetNativeViewAccessible();
}
return nullptr;
}
using KeySnoopFuncMap = std::map<guint, std::pair<AtkKeySnoopFunc, gpointer>>;
static KeySnoopFuncMap& GetActiveKeySnoopFunctions() {
static base::NoDestructor<KeySnoopFuncMap> active_key_snoop_functions;
return *active_key_snoop_functions;
}
using AXPlatformNodeSet = std::set<ui::AXPlatformNodeAuraLinux*>;
static AXPlatformNodeSet& GetNodesWithPostponedEvents() {
static base::NoDestructor<AXPlatformNodeSet> nodes_with_postponed_events_list;
return *nodes_with_postponed_events_list;
}
static void RunPostponedEvents() {
for (ui::AXPlatformNodeAuraLinux* entry : GetNodesWithPostponedEvents()) {
entry->RunPostponedEvents();
}
GetNodesWithPostponedEvents().clear();
}
static guint AtkUtilAuraLinuxAddKeyEventListener(
AtkKeySnoopFunc key_snoop_function,
gpointer data) {
static guint current_key_event_listener_id = 0;
// AtkUtilAuraLinuxAddKeyEventListener is called by
// spi_atk_register_event_listeners in the at-spi2-atk library after it has
// initialized all its other listeners. Our internal knowledge of the
// internals of the AT-SPI/ATK bridge allows us to use this call as an
// indication of AT-SPI being ready, so we can finally run any events that had
// been waiting.
ui::AtkUtilAuraLinux::GetInstance()->SetAtSpiReady(true);
current_key_event_listener_id++;
GetActiveKeySnoopFunctions()[current_key_event_listener_id] =
std::make_pair(key_snoop_function, data);
return current_key_event_listener_id;
}
static void AtkUtilAuraLinuxRemoveKeyEventListener(guint listener_id) {
GetActiveKeySnoopFunctions().erase(listener_id);
}
static void atk_util_auralinux_class_init(AtkUtilAuraLinuxClass *klass) {
AtkUtilClass* atk_class = ATK_UTIL_CLASS(g_type_class_peek(ATK_TYPE_UTIL));
atk_class->get_root = AtkUtilAuraLinuxGetRoot;
atk_class->get_toolkit_name = []() { return "Chromium"; };
atk_class->get_toolkit_version = []() { return "1.0"; };
atk_class->add_key_event_listener = AtkUtilAuraLinuxAddKeyEventListener;
atk_class->remove_key_event_listener = AtkUtilAuraLinuxRemoveKeyEventListener;
}
} // namespace
namespace ui {
// static
AtkUtilAuraLinux* AtkUtilAuraLinux::GetInstance() {
return base::Singleton<AtkUtilAuraLinux>::get();
}
bool AtkUtilAuraLinux::ShouldEnableAccessibility() {
// Check enabled/disabled accessibility based on env variable
std::unique_ptr<base::Environment> env(base::Environment::Create());
for (base::cstring_view variable : kAccessibilityEnabledVariables) {
std::string enable_accessibility =
env->GetVar(variable).value_or(std::string());
if (enable_accessibility == "1")
return true;
if (enable_accessibility == "0") {
return false;
}
}
#if defined(USE_GIO)
// Do not run additional checks when Chrome runs in headless mode, which means
// we are in a test environment
std::string chrome_headless =
env->GetVar("CHROME_HEADLESS").value_or(std::string());
if (chrome_headless == "1") {
return false;
}
// Check enabled accessibility based on a11y DBus interface
GDBusConnection* connection =
g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
if (connection) {
GVariant* result = g_dbus_connection_call_sync(
connection, "org.a11y.Bus", "/org/a11y/bus",
"org.freedesktop.DBus.Properties", "Get",
g_variant_new("(ss)", "org.a11y.Status", "IsEnabled"), nullptr,
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr);
g_object_unref(connection);
if (result) {
GVariant* property;
g_variant_get(result, "(v)", &property);
const bool accessibilityEnabled = g_variant_get_boolean(property);
g_variant_unref(result);
g_variant_unref(property);
return accessibilityEnabled;
}
}
// Check enabled accessibility based on GSettings
auto settings = ui::GSettingsNew("org.gnome.desktop.interface");
if (settings) {
return g_settings_get_boolean(settings, "toolkit-accessibility");
}
#endif
return false;
}
void AtkUtilAuraLinux::InitializeAsync() {
static bool initialized = false;
if (initialized || !ShouldEnableAccessibility())
return;
initialized = true;
// Register our util class.
g_type_class_unref(g_type_class_ref(atk_util_auralinux_get_type()));
PlatformInitializeAsync();
}
void AtkUtilAuraLinux::InitializeForTesting() {
std::unique_ptr<base::Environment> env(base::Environment::Create());
env->SetVar(kAccessibilityEnabledVariables[0], "1");
InitializeAsync();
}
// static
// Disable CFI-icall since the key snooping function could be in another DSO.
__attribute__((no_sanitize("cfi-icall")))
DiscardAtkKeyEvent AtkUtilAuraLinux::HandleAtkKeyEvent(
AtkKeyEventStruct* key_event) {
DCHECK(key_event);
if (!AXPlatform::GetInstance().GetMode().has_mode(AXMode::kNativeAPIs)) {
return DiscardAtkKeyEvent::Retain;
}
GetInstance()->InitializeAsync();
bool discard = false;
for (auto& entry : GetActiveKeySnoopFunctions()) {
AtkKeySnoopFunc key_snoop_function = entry.second.first;
gpointer data = entry.second.second;
// We want to ensure that all functions are called. We will discard this
// event if at least one function suggests that we do it, but we still
// need to call the functions that follow it in the map iterator.
if (key_snoop_function(key_event, data) != 0)
discard = true;
}
return discard ? DiscardAtkKeyEvent::Discard : DiscardAtkKeyEvent::Retain;
}
bool AtkUtilAuraLinux::IsAtSpiReady() {
return at_spi_ready_;
}
void AtkUtilAuraLinux::SetAtSpiReady(bool ready) {
at_spi_ready_ = ready;
if (ready)
RunPostponedEvents();
}
void AtkUtilAuraLinux::PostponeEventsFor(AXPlatformNodeAuraLinux* node) {
GetNodesWithPostponedEvents().insert(node);
}
void AtkUtilAuraLinux::CancelPostponedEventsFor(AXPlatformNodeAuraLinux* node) {
GetNodesWithPostponedEvents().erase(node);
}
} // namespace ui