AT-SPI(Assistive Technology Service Provider Interface)是一个用于创建无障碍技术服务的接口。基于AT-SPI的无障碍操作工具可以帮助用户使用计算机和应用程序,特别是对于那些有视觉、听觉或运动障碍的用户来说尤为重要。
使用基于AT-SPI的无障碍操作工具,可以实现以下功能:
-
屏幕阅读器:通过AT-SPI接口可以获取应用程序的文本内容,并将其朗读给用户,帮助视觉障碍用户浏览网页、阅读文档等。
-
屏幕放大器:利用AT-SPI接口可以获取应用程序的界面元素,对其进行放大显示,帮助视力有限的用户更容易地查看屏幕上的内容。
-
辅助键盘:通过AT-SPI接口可以模拟键盘输入,帮助运动障碍用户使用计算机和应用程序。
-
软件导航:通过AT-SPI接口可以获取应用程序的结构信息,帮助用户在应用程序中进行导航和操作。
总的来说,基于AT-SPI的无障碍操作工具可以提高用户对计算机和应用程序的可访问性,让更多的人能够享受到科技所带来的便利。
具体实现
头文件:
cpp
#ifndef ATSPI_TOOL_H
#define ATSPI_TOOL_H
#include <atspi/atspi.h>
// 初始化AT-SPI
int atspi_tool_init();
// 清理AT-SPI资源
void atspi_tool_cleanup();
// 获取桌面元素
AtspiAccessible* get_desktop();
// 获取元素树
void print_element_tree(AtspiAccessible* element, int depth);
// 查找元素
AtspiAccessible* find_element(AtspiAccessible* root, const char* name, const char* role);
// 点击元素
int click_element(AtspiAccessible* element);
// 在元素中输入文本
int input_text(AtspiAccessible* element, const char* text);
// 长按元素
int long_press_element(AtspiAccessible* element, int duration_ms);
#endif // ATSPI_TOOL_H
c文件:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <atspi/atspi.h>
#include "atspi_tool.h"
static AtspiAccessible *desktop = NULL;
// 查找指定应用程序的窗口
AtspiAccessible *find_application_window(const char *app_name)
{
if (!desktop)
return NULL;
// 获取所有应用程序
int child_count = atspi_accessible_get_child_count(desktop, NULL);
for (int i = 0; i < child_count; i++)
{
AtspiAccessible *app = atspi_accessible_get_child_at_index(desktop, i, NULL);
if (!app)
continue;
char *name = atspi_accessible_get_name(app, NULL);
if (name && strcasecmp(name, app_name) == 0)
{
g_free(name);
return app;
}
g_free(name);
g_object_unref(app);
}
return NULL;
}
int atspi_tool_init()
{
printf("开始初始化 AT-SPI...\n");
// 设置环境变量
if (!g_getenv("AT_SPI_BUS"))
{
g_setenv("AT_SPI_BUS", "session", TRUE);
printf("设置 AT_SPI_BUS=session\n");
}
if (!g_getenv("DBUS_SESSION_BUS_ADDRESS"))
{
// 尝试从用户目录读取 D-Bus 会话地址
char *dbus_file = g_build_filename(g_get_home_dir(), ".dbus/session-bus/51ac05f8e7262beaa28b48c05948f6e4-0", NULL);
if (g_file_test(dbus_file, G_FILE_TEST_EXISTS))
{
GError *error = NULL;
gchar *contents = NULL;
gsize length = 0;
if (g_file_get_contents(dbus_file, &contents, &length, &error))
{
gchar **lines = g_strsplit(contents, "\n", -1);
for (gchar **line = lines; *line; line++)
{
if (g_str_has_prefix(*line, "DBUS_SESSION_BUS_ADDRESS="))
{
gchar *address = g_strdup(*line + strlen("DBUS_SESSION_BUS_ADDRESS="));
g_setenv("DBUS_SESSION_BUS_ADDRESS", address, TRUE);
g_free(address);
break;
}
}
g_strfreev(lines);
}
g_free(contents);
}
g_free(dbus_file);
}
// 检查环境变量
const char *atspi_env = g_getenv("AT_SPI_BUS");
printf("AT_SPI_BUS 环境变量: %s\n", atspi_env ? atspi_env : "未设置");
const char *dbus_env = g_getenv("DBUS_SESSION_BUS_ADDRESS");
printf("DBUS_SESSION_BUS_ADDRESS 环境变量: %s\n", dbus_env ? dbus_env : "未设置");
// 尝试初始化
printf("调用 atspi_init()...\n");
if (atspi_init() < 0)
{
fprintf(stderr, "AT-SPI初始化失败\n");
return -1;
}
printf("atspi_init() 调用成功\n");
// 获取桌面
printf("获取桌面元素...\n");
desktop = atspi_get_desktop(0);
if (!desktop)
{
fprintf(stderr, "无法获取桌面元素\n");
return -1;
}
printf("成功获取桌面元素\n");
return 0;
}
void atspi_tool_cleanup()
{
if (desktop)
{
g_object_unref(desktop);
}
atspi_exit();
}
AtspiAccessible *get_desktop()
{
return desktop;
}
void print_element_tree(AtspiAccessible *element, int depth)
{
if (!element)
return;
char *name = atspi_accessible_get_name(element, NULL);
char *role = atspi_accessible_get_role_name(element, NULL);
for (int i = 0; i < depth; i++)
{
printf(" ");
}
printf("%s (%s)\n", name ? name : "unnamed", role);
g_free(name);
g_free(role);
int child_count = atspi_accessible_get_child_count(element, NULL);
for (int i = 0; i < child_count; i++)
{
AtspiAccessible *child = atspi_accessible_get_child_at_index(element, i, NULL);
if (child)
{
print_element_tree(child, depth + 1);
g_object_unref(child);
}
}
}
AtspiAccessible *find_element(AtspiAccessible *root, const char *name, const char *role)
{
if (!root)
return NULL;
char *element_name = atspi_accessible_get_name(root, NULL);
char *element_role = atspi_accessible_get_role_name(root, NULL);
if ((!name || (element_name && strcmp(element_name, name) == 0)) &&
(!role || (element_role && strcmp(element_role, role) == 0)))
{
g_free(element_name);
g_free(element_role);
return root;
}
g_free(element_name);
g_free(element_role);
int child_count = atspi_accessible_get_child_count(root, NULL);
for (int i = 0; i < child_count; i++)
{
AtspiAccessible *child = atspi_accessible_get_child_at_index(root, i, NULL);
if (child)
{
AtspiAccessible *found = find_element(child, name, role);
g_object_unref(child);
if (found)
{
return found;
}
}
}
return NULL;
}
int click_element(AtspiAccessible *element)
{
if (!element)
return -1;
AtspiAction *action = atspi_accessible_get_action_iface(element);
if (!action)
return -1;
GError *error = NULL;
if (!atspi_action_do_action(action, 0, &error))
{
fprintf(stderr, "点击元素失败: %s\n", error->message);
g_error_free(error);
g_object_unref(action);
return -1;
}
g_object_unref(action);
return 0;
}
int input_text(AtspiAccessible *element, const char *text)
{
if (!element || !text)
return -1;
AtspiEditableText *editable_text = atspi_accessible_get_editable_text_iface(element);
if (!editable_text)
return -1;
GError *error = NULL;
if (!atspi_editable_text_set_text_contents(editable_text, text, &error))
{
fprintf(stderr, "输入文本失败: %s\n", error->message);
g_error_free(error);
g_object_unref(editable_text);
return -1;
}
g_object_unref(editable_text);
return 0;
}
int long_press_element(AtspiAccessible *element, int duration_ms)
{
if (!element)
return -1;
// 获取元素的位置
AtspiComponent *component = atspi_accessible_get_component_iface(element);
if (!component)
{
fprintf(stderr, "获取元素位置失败\n");
g_object_unref(component);
return -1;
}
GError *error = NULL;
AtspiRect *rect = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, &error);
if (!rect)
{
fprintf(stderr, "获取元素位置失败: %s\n", error->message);
g_error_free(error);
g_object_unref(component);
return -1;
}
// 计算元素中心点
// int center_x = rect.x + rect.width / 2;
// int center_y = rect.y + rect.height / 2;
int center_x = rect->x + 10;
int center_y = rect->y + 10;
// 模拟鼠标按下
if (!atspi_generate_mouse_event(center_x, center_y, "b1p", &error))
{
fprintf(stderr, "模拟鼠标按下失败: %s\n", error->message);
g_error_free(error);
g_object_unref(component);
g_free(rect);
return -1;
}
// 等待指定时间
g_usleep(duration_ms * 1000);
// 模拟鼠标释放
if (!atspi_generate_mouse_event(center_x, center_y, "b1r", &error))
{
fprintf(stderr, "模拟鼠标释放失败: %s\n", error->message);
g_error_free(error);
g_object_unref(component);
g_free(rect);
return -1;
}
g_object_unref(component);
g_free(rect);
return 0;
}
int main()
{
if (atspi_tool_init() != 0)
{
return 1;
}
print_element_tree(desktop, 0);
// 查找 chargingDemo 窗口
printf("查找 chargingDemo 窗口...\n");
AtspiAccessible *chargingDemo = find_application_window("chargingDemo");
if (!chargingDemo)
{
fprintf(stderr, "未找到 chargingDemo 窗口,请确保 chargingDemo 已经运行\n");
atspi_tool_cleanup();
return 1;
}
printf("找到 chargingDemo 窗口,打印元素树:\n");
print_element_tree(chargingDemo, 0);
printf("长按左上角...\n");
long_press_element(chargingDemo, 5000);
printf("查找关闭按钮...\n");
AtspiAccessible *close_button = find_element(chargingDemo, "Back", "push button");
if (!close_button)
{
fprintf(stderr, "未找到关闭按钮\n");
g_object_unref(chargingDemo);
atspi_tool_cleanup();
return 1;
}
printf("点击关闭按钮...\n");
click_element(close_button);
g_object_unref(chargingDemo);
atspi_tool_cleanup();
return 0;
}
makefile文件
bash
# 编译器设置
# 'make PLATFORM=x86_64' to build x86_64 platform
# 'make PLATFORM=arm' to build arm platform
# 'make' with no arg default target platform is x86_64
ifeq ($(PLATFORM), arm)
# Yocto 交叉编译工具链
CC = arm-poky-linux-gnueabi-gcc --sysroot=$(SYSROOT)
CXX = arm-poky-linux-gnueabi-g++ --sysroot=$(SYSROOT)
# 设置交叉编译的 sysroot
SYSROOT ?= /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/cortexa9hf-neon-poky-linux-gnueabi
# 设置交叉编译的 pkg-config 路径
export PKG_CONFIG_PATH=$(SYSROOT)/usr/lib/pkgconfig:$(SYSROOT)/usr/share/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=$(SYSROOT)
export PKG_CONFIG_LIBDIR=$(SYSROOT)/usr/lib/pkgconfig
# 设置交叉编译的头文件路径
CFLAGS += -I$(SYSROOT)/usr/include
CFLAGS += -I$(SYSROOT)/usr/include/glib-2.0
CFLAGS += -I$(SYSROOT)/usr/lib/glib-2.0/include
CFLAGS += -I$(SYSROOT)/usr/include/at-spi2-atk/2.0
CFLAGS += -I$(SYSROOT)/usr/include/at-spi-2.0
CFLAGS += -I$(SYSROOT)/usr/include/dbus-1.0
CFLAGS += -I$(SYSROOT)/usr/lib/dbus-1.0/include
# 设置交叉编译的库路径
LDFLAGS += -L$(SYSROOT)/usr/lib
LDFLAGS += -L$(SYSROOT)/lib
# 设置编译标志
CFLAGS += -march=armv7-a -mfloat-abi=hard -mfpu=neon
else
PLATFORM = x86_64
CC = gcc
CXX = g++
endif
CFLAGS += -O0 -Wall -Wextra -g
# 使用 pkg-config 获取编译和链接参数
CFLAGS += $(shell pkg-config --cflags atspi-2 glib-2.0 gobject-2.0)
LDFLAGS += $(shell pkg-config --libs atspi-2 glib-2.0 gobject-2.0)
# 目录设置
BUILD_DIR = build
SRC_DIR = $(shell pwd)/
# 包含路径
# 源文件
SRCS += $(wildcard $(SRC_DIR)/atspi_tool.c)
# 依赖库
LIBS = -lpthread
# 修改 RPATH 设置,使用可执行文件同级的lib目录
LDFLAGS += -Wl,-rpath,'$$ORIGIN/lib'
# 目标文件
TARGET = $(BUILD_DIR)/atspi_tool
# 默认目标
all: $(BUILD_DIR) $(TARGET) install
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(TARGET): $(SRCS)
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
# 安装目标,复制所有必要的动态库
install: $(TARGET)
mkdir -p $(BUILD_DIR)/lib
# 复制 AT-SPI 相关库
cp $(SYSROOT)/usr/lib/libatspi.so* $(BUILD_DIR)/lib/
cp $(SYSROOT)/usr/lib/libglib-2.0.so* $(BUILD_DIR)/lib/
cp $(SYSROOT)/usr/lib/libgobject-2.0.so* $(BUILD_DIR)/lib/
cp $(SYSROOT)/usr/lib/libdbus-1.so* $(BUILD_DIR)/lib/
cp $(SYSROOT)/usr/lib/libX11.so* $(BUILD_DIR)/lib/
cp $(SYSROOT)/usr/lib/libXext.so* $(BUILD_DIR)/lib/
cp $(SYSROOT)/usr/lib/libpng16.so* $(BUILD_DIR)/lib/
# 复制 libffi 库
cp $(SYSROOT)/usr/lib/libffi.so* $(BUILD_DIR)/lib/
# 添加运行目标
run: all
./$(TARGET)
clean:
rm -rf $(BUILD_DIR)
.PHONY: all clean install run
编译代码需要安装
bash
sudo apt-get install libatspi2.0-dev