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
相关推荐
qq_365911602 小时前
GPT-4、Grok 3与Gemini 2.0 Pro:三大AI模型的语气、风格与能力深度对比
开发语言
虔城散人3 小时前
C语言 |位域结构体
c语言
Susea&3 小时前
数据结构初阶:队列
c语言·开发语言·数据结构
慕容静漪3 小时前
如何本地安装Python Flask并结合内网穿透实现远程开发
开发语言·后端·golang
ErizJ3 小时前
Golang|锁相关
开发语言·后端·golang
GOTXX3 小时前
【Qt】Qt Creator开发基础:项目创建、界面解析与核心概念入门
开发语言·数据库·c++·qt·图形渲染·图形化界面·qt新手入门
搬砖工程师Cola3 小时前
<C#>在 .NET 开发中,依赖注入, 注册一个接口的多个实现
开发语言·c#·.net
巨龙之路3 小时前
Lua中的元表
java·开发语言·lua
徐行1103 小时前
C++核心机制-this 指针传递与内存布局分析
开发语言·c++
序属秋秋秋4 小时前
算法基础_数据结构【单链表 + 双链表 + 栈 + 队列 + 单调栈 + 单调队列】
c语言·数据结构·c++·算法