C语言-基于AT-SPI无障碍服务操作工具

AT-SPI(Assistive Technology Service Provider Interface)是一个用于创建无障碍技术服务的接口。基于AT-SPI的无障碍操作工具可以帮助用户使用计算机和应用程序,特别是对于那些有视觉、听觉或运动障碍的用户来说尤为重要。

使用基于AT-SPI的无障碍操作工具,可以实现以下功能:

  1. 屏幕阅读器:通过AT-SPI接口可以获取应用程序的文本内容,并将其朗读给用户,帮助视觉障碍用户浏览网页、阅读文档等。

  2. 屏幕放大器:利用AT-SPI接口可以获取应用程序的界面元素,对其进行放大显示,帮助视力有限的用户更容易地查看屏幕上的内容。

  3. 辅助键盘:通过AT-SPI接口可以模拟键盘输入,帮助运动障碍用户使用计算机和应用程序。

  4. 软件导航:通过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
相关推荐
yuanManGan6 分钟前
C++入门小馆 :多态
开发语言·c++
坐吃山猪14 分钟前
Python-JsonRPC
开发语言·python
刃神太酷啦15 分钟前
C++入门(下)--《Hello C++ World!》(2)(C/C++)
java·c语言·c++·git·算法·github
小毛驴85018 分钟前
Windows环境,Python实现对本机处于监听状态的端口,打印出端口,进程ID,程序名称
开发语言·windows·python
MyhEhud26 分钟前
Kotlin zip 函数的作用和使用场景
开发语言·windows·kotlin
火龙谷27 分钟前
【工具推荐】Code2Prompt
开发语言
code monkey.34 分钟前
【探寻C++之旅】第十三章:红黑树
开发语言·数据结构·c++
黄雪超1 小时前
JVM——Java内存模型
java·开发语言·jvm
一匹电信狗1 小时前
【C++】手搓一个STL风格的string容器
c语言·数据结构·c++·算法·leetcode·stl·visual studio
南玖yy1 小时前
C++ 工具链与开发实践:构建安全、高效与创新的开发生态
开发语言·c++·人工智能·后端·安全·架构·交互