AT-SPI(Assistive Technology Service Provider Interface)是一个用于创建无障碍技术服务的接口。基于AT-SPI的无障碍操作工具可以帮助用户使用计算机和应用程序,特别是对于那些有视觉、听觉或运动障碍的用户来说尤为重要。
使用基于AT-SPI的无障碍操作工具,可以实现以下功能:
-
屏幕阅读器:通过AT-SPI接口可以获取应用程序的文本内容,并将其朗读给用户,帮助视觉障碍用户浏览网页、阅读文档等。
-
屏幕放大器:利用AT-SPI接口可以获取应用程序的界面元素,对其进行放大显示,帮助视力有限的用户更容易地查看屏幕上的内容。
-
辅助键盘:通过AT-SPI接口可以模拟键盘输入,帮助运动障碍用户使用计算机和应用程序。
-
软件导航:通过AT-SPI接口可以获取应用程序的结构信息,帮助用户在应用程序中进行导航和操作。
总的来说,基于AT-SPI的无障碍操作工具可以提高用户对计算机和应用程序的可访问性,让更多的人能够享受到科技所带来的便利。
具体实现
头文件:
#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文件:
#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文件
# 编译器设置# '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_64ifeq ($(PLATFORM), arm)# Yocto 交叉编译工具链CC = arm-poky-linux-gnueabi-gcc --sysroot=$(SYSROOT)CXX = arm-poky-linux-gnueabi-g++ --sysroot=$(SYSROOT)# 设置交叉编译的 sysrootSYSROOT ?= /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/pkgconfigexport PKG_CONFIG_SYSROOT_DIR=$(SYSROOT)export PKG_CONFIG_LIBDIR=$(SYSROOT)/usr/lib/pkgconfig# 设置交叉编译的头文件路径CFLAGS += -I$(SYSROOT)/usr/includeCFLAGS += -I$(SYSROOT)/usr/include/glib-2.0CFLAGS += -I$(SYSROOT)/usr/lib/glib-2.0/includeCFLAGS += -I$(SYSROOT)/usr/include/at-spi2-atk/2.0CFLAGS += -I$(SYSROOT)/usr/include/at-spi-2.0CFLAGS += -I$(SYSROOT)/usr/include/dbus-1.0CFLAGS += -I$(SYSROOT)/usr/lib/dbus-1.0/include# 设置交叉编译的库路径LDFLAGS += -L$(SYSROOT)/usr/libLDFLAGS += -L$(SYSROOT)/lib# 设置编译标志CFLAGS += -march=armv7-a -mfloat-abi=hard -mfpu=neon
else PLATFORM = x86_64CC = gccCXX = g++
endifCFLAGS += -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
编译代码需要安装
sudo apt-get install libatspi2.0-dev