diff --git a/CMakeLists.txt b/CMakeLists.txt index b376c55..c10570a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,8 @@ PRIVATE base/platform/linux/base_linux_dbus_utilities.h base/platform/linux/base_linux_library.cpp base/platform/linux/base_linux_library.h + base/platform/linux/base_screen_reader_state_linux.cpp + base/platform/linux/base_screen_reader_state_linux.h base/platform/linux/base_linux_xcb_utilities.cpp base/platform/linux/base_linux_xcb_utilities.h base/platform/linux/base_linux_xdg_activation_token.cpp diff --git a/base/platform/linux/base_screen_reader_state_linux.cpp b/base/platform/linux/base_screen_reader_state_linux.cpp new file mode 100644 index 0000000..9a86ab5 --- /dev/null +++ b/base/platform/linux/base_screen_reader_state_linux.cpp @@ -0,0 +1,213 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "base/platform/linux/base_screen_reader_state_linux.h" + +#include "base/custom_delete.h" +#include "base/integration.h" + +#include + +#include + +namespace base::Platform { +namespace { + +constexpr auto kA11yBusName = "org.a11y.Bus"; +constexpr auto kA11yObjectPath = "/org/a11y/bus"; +constexpr auto kA11yStatusInterface = "org.a11y.Status"; +constexpr auto kScreenReaderEnabledProperty = "ScreenReaderEnabled"; +constexpr auto kAlwaysOnEnvironmentVariable + = "QT_LINUX_ACCESSIBILITY_ALWAYS_ON"; + +using ProxyPointer = std::unique_ptr< + GDBusProxy, + base::custom_delete>; +using VariantPointer = std::unique_ptr< + GVariant, + base::custom_delete>; +using ErrorPointer = std::unique_ptr< + GError, + base::custom_delete>; + +[[nodiscard]] bool AlwaysOn() { + return qEnvironmentVariableIsSet(kAlwaysOnEnvironmentVariable); +} + +[[nodiscard]] ProxyPointer CreateStatusProxy() { + GError *error = nullptr; + auto result = ProxyPointer(g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION, + nullptr, + kA11yBusName, + kA11yObjectPath, + kA11yStatusInterface, + nullptr, + &error)); + [[maybe_unused]] const auto errorGuard = ErrorPointer(error); + return result; +} + +[[nodiscard]] std::optional ReadBooleanVariant(GVariant *value) { + if (!value) { + return std::nullopt; + } else if (g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) { + return static_cast(g_variant_get_boolean(value)); + } else if (!g_variant_is_of_type(value, G_VARIANT_TYPE("(v)"))) { + return std::nullopt; + } + + auto nested = VariantPointer(g_variant_get_child_value(value, 0)); + if (!nested || !g_variant_is_of_type(nested.get(), G_VARIANT_TYPE_BOOLEAN)) { + return std::nullopt; + } + + return static_cast(g_variant_get_boolean(nested.get())); +} + +[[nodiscard]] std::optional ReadCachedBooleanProperty( + GDBusProxy *proxy, + const char *property) { + if (!proxy || !g_dbus_proxy_get_name_owner(proxy)) { + return std::nullopt; + } + + auto value = VariantPointer(g_dbus_proxy_get_cached_property( + proxy, + property)); + return ReadBooleanVariant(value.get()); +} + +[[nodiscard]] std::optional ReadBooleanProperty( + GDBusProxy *proxy, + const char *property) { + if (const auto cached = ReadCachedBooleanProperty(proxy, property)) { + return cached; + } else if (!proxy || !g_dbus_proxy_get_name_owner(proxy)) { + return std::nullopt; + } + + GError *error = nullptr; + auto value = VariantPointer(g_dbus_connection_call_sync( + g_dbus_proxy_get_connection(proxy), + kA11yBusName, + kA11yObjectPath, + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new("(ss)", kA11yStatusInterface, property), + G_VARIANT_TYPE("(v)"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + nullptr, + &error)); + [[maybe_unused]] const auto errorGuard = ErrorPointer(error); + return ReadBooleanVariant(value.get()); +} + +[[nodiscard]] bool ReadAccessibilityStatus(GDBusProxy *proxy) { + return ReadBooleanProperty( + proxy, + kScreenReaderEnabledProperty).value_or(false); +} + +void EnqueueUpdate(base::weak_ptr weak) { + auto update = [weak = std::move(weak)]() mutable { + if (weak) { + weak->updateActive(); + } + }; + if (Integration::Exists()) { + Integration::Instance().enterFromEventLoop(std::move(update)); + } else { + update(); + } +} + +void ProxyPropertiesChanged( + GDBusProxy*, + GVariant*, + const gchar * const*, + gpointer userData) { + EnqueueUpdate(make_weak(static_cast(userData))); +} + +void ProxyNameOwnerChanged( + GObject*, + GParamSpec*, + gpointer userData) { + EnqueueUpdate(make_weak(static_cast(userData))); +} + +} // namespace + +struct LinuxScreenReaderState::Private { + ProxyPointer proxy; + gulong propertiesChangedHandlerId = 0; + gulong nameOwnerChangedHandlerId = 0; +}; + +LinuxScreenReaderState::LinuxScreenReaderState() +: _private(std::make_unique()) +, _isActive(AlwaysOn()) { + if (AlwaysOn()) { + return; + } + + _private->proxy = CreateStatusProxy(); + if (!_private->proxy) { + return; + } + + _private->propertiesChangedHandlerId = g_signal_connect( + _private->proxy.get(), + "g-properties-changed", + G_CALLBACK(ProxyPropertiesChanged), + this); + _private->nameOwnerChangedHandlerId = g_signal_connect( + _private->proxy.get(), + "notify::g-name-owner", + G_CALLBACK(ProxyNameOwnerChanged), + this); + + updateActive(); +} + +LinuxScreenReaderState::~LinuxScreenReaderState() { + if (!_private->proxy) { + return; + } + + if (_private->propertiesChangedHandlerId) { + g_signal_handler_disconnect( + _private->proxy.get(), + _private->propertiesChangedHandlerId); + } + if (_private->nameOwnerChangedHandlerId) { + g_signal_handler_disconnect( + _private->proxy.get(), + _private->nameOwnerChangedHandlerId); + } +} + +void LinuxScreenReaderState::updateActive() { + if (AlwaysOn()) { + _isActive = true; + return; + } + + _isActive = ReadAccessibilityStatus(_private->proxy.get()); +} + +bool LinuxScreenReaderState::active() const { + return _isActive.current(); +} + +rpl::producer LinuxScreenReaderState::activeValue() const { + return _isActive.value(); +} + +} // namespace base::Platform diff --git a/base/platform/linux/base_screen_reader_state_linux.h b/base/platform/linux/base_screen_reader_state_linux.h new file mode 100644 index 0000000..b157b74 --- /dev/null +++ b/base/platform/linux/base_screen_reader_state_linux.h @@ -0,0 +1,32 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "base/screen_reader_state.h" +#include "base/weak_ptr.h" + +namespace base::Platform { + +class LinuxScreenReaderState final + : public ScreenReaderState + , public base::has_weak_ptr { +public: + LinuxScreenReaderState(); + ~LinuxScreenReaderState() override; + + bool active() const override; + rpl::producer activeValue() const override; + void updateActive(); + +private: + struct Private; + std::unique_ptr _private; + rpl::variable _isActive; + +}; + +} // namespace base::Platform diff --git a/base/screen_reader_state.cpp b/base/screen_reader_state.cpp index c266fbc..7058545 100644 --- a/base/screen_reader_state.cpp +++ b/base/screen_reader_state.cpp @@ -4,6 +4,8 @@ #include "base/platform/mac/base_screen_reader_state_mac.h" #elif defined(Q_OS_WIN) // Q_OS_MAC #include "base/platform/win/base_screen_reader_state_win.h" +#elif defined(Q_OS_LINUX) // Q_OS_MAC || Q_OS_WIN +#include "base/platform/linux/base_screen_reader_state_linux.h" #endif // Q_OS_MAC || Q_OS_WIN namespace base { @@ -15,13 +17,15 @@ ScreenReaderState *ScreenReaderState::Instance() { static auto instance = Platform::MacScreenReaderState(); #elif defined(Q_OS_WIN) // Q_OS_MAC static auto instance = Platform::WinScreenReaderState(); +#elif defined(Q_OS_LINUX) // Q_OS_MAC || Q_OS_WIN + static auto instance = Platform::LinuxScreenReaderState(); #else // Q_OS_MAC || Q_OS_WIN static auto instance = GeneralScreenReaderState(); #endif // Q_OS_MAC || Q_OS_WIN return &instance; } -#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) +#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) && !defined(Q_OS_LINUX) GeneralScreenReaderState::GeneralScreenReaderState() : _isActive(QAccessible::isActive()) { @@ -44,6 +48,6 @@ rpl::producer GeneralScreenReaderState::activeValue() const { return _isActive.value(); } -#endif // !Q_OS_MAC && !Q_OS_WIN +#endif // !Q_OS_MAC && !Q_OS_WIN && !Q_OS_LINUX } // namespace base diff --git a/base/screen_reader_state.h b/base/screen_reader_state.h index e255131..e2d261e 100644 --- a/base/screen_reader_state.h +++ b/base/screen_reader_state.h @@ -2,7 +2,7 @@ #include "base/basic_types.h" -#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) +#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) && !defined(Q_OS_LINUX) #include #endif @@ -23,7 +23,7 @@ class ScreenReaderState { }; -#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) +#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) && !defined(Q_OS_LINUX) class GeneralScreenReaderState final : public ScreenReaderState , public QAccessible::ActivationObserver {