X11 剪切板
2024-12-18 21:18:31+08:00

X11 剪切板

参考:

首先在 X11 上剪切板被称作 Selections,系统上可以有任意多个 Selections,并且有三个预定义的标准 Selections:

  • primary - 当前选中的文本,例如终端上点击鼠标中键可以立即粘贴选中的文本
  • secondary - 没有被使用
  • clipboard - 通常意义上的剪切板,不同进程间交换数据时使用它

只有 Selection 的拥有者(owner)可以修改它,其它进程只能读取。owner 需要负责保存 Selection 的内容,并在其它进程请求读取的时候返回给对方。

当用户使用 Ctrl + C 进行复制时,当前应用就会申请成为 clipboard 的拥有者,之前的拥有者需要让出。

如果应用程序只希望在内部复制粘贴数据,不希望与其它进程交换信息,可以创建一个任意命名的属于自己的剪切板。

以下代码可以查看 clipboard 的当前 owner:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/xcb.h>

int main(void)
{
    // 建立连接
    xcb_connection_t* conn = xcb_connect(NULL, NULL);

    // 获取 screen
    const xcb_setup_t* setup = xcb_get_setup(conn);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t* screen = iter.data;

    // 获取 root 窗口
    xcb_window_t root = screen->root;

    // 创建 ATOM
    xcb_atom_t nameAtom;
    {
        const char* name = "PRIMARY";
        xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, 0, strlen(name), name);
        xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(conn, cookie, NULL);
        nameAtom = reply->atom;
        free(reply);
    }
    
    // 读取剪切板的 owner
    xcb_window_t owner;
    {
        xcb_get_selection_owner_cookie_t cookie =  xcb_get_selection_owner(conn, nameAtom);
        xcb_get_selection_owner_reply_t* reply = xcb_get_selection_owner_reply(conn, cookie, NULL);
        owner = reply->owner;
        free(reply);
    }

    // 读取窗口名称
    #define BUFFER_SIZE 64
    char name[BUFFER_SIZE] = {0};
    {
        xcb_get_property_cookie_t cookie = xcb_get_property(conn, 0, owner, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, BUFFER_SIZE/4);
        xcb_get_property_reply_t* reply = xcb_get_property_reply(conn, cookie, NULL);
        int len = xcb_get_property_value_length(reply);
        len = len > 64 ? 64 : len;
        strncpy(name, xcb_get_property_value(reply), len);
        free(reply);
    }
    #undef BUFFER_SIZE

    // 打印
    printf("Owner ID: 0x%x\n", owner);
    printf("Owner Name: %s\n", name);
}

以下代码可以将自己设为 clipboard 的 owner,并将所有的粘贴内容替换为当前的时间字符串:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <xcb/xcb.h>
#include <xcb/xfixes.h>

int main(void)
{
    // 建立连接
    xcb_connection_t* conn = xcb_connect(NULL, NULL);

    // 获取 screen
    const xcb_setup_t* setup = xcb_get_setup(conn);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t* screen = iter.data;

    // 获取 root 窗口
    xcb_window_t root = screen->root;

    // 创建一个窗口
    xcb_window_t window = xcb_generate_id(conn);
    {
        uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
        uint32_t values[2];
        values[0] = screen->white_pixel;
        values[1] = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE;
        xcb_create_window(conn, XCB_COPY_FROM_PARENT, window, root, 0, 0, 300, 200, 50, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, values);
        xcb_map_window(conn, window);
    }

    // 创建 ATOM
    xcb_atom_t nameAtom;
    {
        const char* name = "CLIPBOARD";
        xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, 0, strlen(name), name);
        xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(conn, cookie, NULL);
        nameAtom = reply->atom;
        free(reply);
    }

    // 创建 ATOM
    xcb_atom_t utf8StrAtom;
    {
        const char* name = "UTF8_STRING";
        xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, 0, strlen(name), name);
        xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(conn, cookie, NULL);
        utf8StrAtom = reply->atom;
        free(reply);
    }
    
    // 创建一个 selection 并将自己设为的 owner
    xcb_set_selection_owner(conn, window, nameAtom, XCB_TIME_CURRENT_TIME);

    // 检查是否成功
    xcb_window_t owner;
    {
        xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(conn, nameAtom);
        xcb_get_selection_owner_reply_t* reply = xcb_get_selection_owner_reply(conn, cookie, NULL);
        owner = reply->owner;
    }

    if (owner != window)
    {
        printf("failed to set selection owner, owner id is 0x%x\n", owner);
        return 1;
    }

    // 读取事件
    xcb_generic_event_t* event;
    while ((event = xcb_wait_for_event(conn)))
    {
        printf("event type: %d\n", event->response_type);
        // 其它进程请求读取这个 selection
        if(event->response_type == XCB_SELECTION_REQUEST)
        {
            xcb_selection_request_event_t* request = (xcb_selection_request_event_t*)event;

            xcb_selection_notify_event_t response; // 通知对端的事件
            response.response_type =XCB_SELECTION_NOTIFY;
            response.requestor = request->requestor;
            response.selection = request->selection;
            response.target = request->target;
            response.property = request->property;
            response.time = request->time;

            // 只处理 UTF8_STRING
            if (request->property == XCB_ATOM_NONE || request->target != utf8StrAtom)
            {
                response.property = XCB_ATOM_NONE;
            }
            else
            {
                // 返回当前时间
                time_t nowTime = time(NULL);
                char* nowStr = ctime(&nowTime);
                xcb_change_property(conn, XCB_PROP_MODE_REPLACE, request->requestor, request->property, request->target, 8, strlen(nowStr), nowStr);
            }
            
            xcb_send_event(conn, 1, request->requestor, 0, (char*)&response);
            xcb_flush(conn);

            printf("done\n");
        }
    }

    xcb_disconnect(conn);
    return 0;
}

当一个应用进行复制时,实际上是将自己设为了 clipboard 的 owner,并在其它应用请求粘贴时返回数据。 为了避免复制的数据丢失,成为 owner 时要将之前的数据都保存过来。

如果 owner 退出,selection 会丢失。为了避免这一情况,需要一个 clipboard manager 进程作为 clipboard 的 owner。 当其它进程进行复制操作成为 owner 时,clipboard manager应当进行以下操作:

  1. 向新的 owner 询问所有数据,并将这些数据保存起来
  2. 重新将自己声明为 owner
xcb 截图
2024-12-18 21:18:31+08:00

xcb 截图

通过 xcb_get_image 可以进行截图:

xcb_get_image_cookie_t
xcb_get_image (xcb_connection_t *c,               // X连接
               uint8_t           format,          // 格式 
               xcb_drawable_t    drawable,        // 要截图的目标,例如窗口,截全屏则为 root 窗口
               int16_t           x,               // 截图的起点 x 坐标
               int16_t           y,               // 截图的起点 y 坐标
               uint16_t          width,           // 截图的宽度
               uint16_t          height,          // 截图的高度
               uint32_t          plane_mask);     // 色彩通道掩码,例如 0x00ff0000 表示只保留红色通道(小端)
#include <xcb/xcb.h>
#include <stdio.h>
#include <stdlib.h>

// 将 BGRX 转换为 RGB
static uint8_t* BGRX2RGB(const uint8_t* in, int16_t width, int32_t height)
{
    uint8_t* out = malloc(width * height *3);
    for (int16_t y = 0; y < height; y++)
    {
        for(int16_t x = 0; x < width; x++)
        {
            out[(y*width+x)*3] = in[(y*width+x)*4 + 2];
            out[(y*width+x)*3 + 1] = in[(y*width+x)*4 + 1];
            out[(y*width+x)*3 + 2] = in[(y*width+x)*4];
        }
    }
    return out;
}

int main()
{
    // 建立连接
    xcb_connection_t* conn = xcb_connect(NULL, NULL);

    // 获取 screen
    const xcb_setup_t* setup = xcb_get_setup(conn);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t* screen = iter.data;

    // 获取 root 窗口
    xcb_window_t root = screen->root;

    // 获取屏幕的宽高
    int16_t width = 0;
    int16_t height = 0;
    {
        xcb_get_geometry_cookie_t cookie = xcb_get_geometry(conn, root);
        xcb_get_geometry_reply_t* reply = xcb_get_geometry_reply(conn, cookie, NULL);
        width = reply->width;
        height = reply->height;
        free(reply);
    }

    // 读取屏幕图像
    uint8_t* data = NULL;
    {
        // XCB_IMAGE_FORMAT_Z_PIXMAP 是 BGRX 格式
        // 其中 plane_mask 用于过滤不需要的通道,例如 0x00ff0000 表示只保留红色通道(注意小端)
        xcb_get_image_cookie_t cookie = xcb_get_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, root, 0, 0, width, height, UINT32_MAX);
        xcb_get_image_reply_t* reply = xcb_get_image_reply(conn, cookie, NULL);
        data = BGRX2RGB(xcb_get_image_data(reply), width, height);
        free(reply);
    }

    FILE* fp = fopen("out.ppm", "wb");
    fprintf(fp, "P6\n");
    fprintf(fp, "%d %d 255\n", width, height);
    fwrite(data, width*height*3, 1, fp);
    fclose(fp);

    free(data);
    xcb_disconnect(conn);

    return EXIT_SUCCESS;
}
xcb 获取事件
2024-12-18 21:18:31+08:00

xcb 获取事件

获取窗口事件

// 获取窗口事件
#include <stdio.h>
#include <xcb/xcb.h>

int main()
{
    // 连接到X11 Server
    xcb_connection_t* conn = xcb_connect(NULL, NULL);

    // 获取screen
    const xcb_setup_t* setup = xcb_get_setup(conn);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t* screen = iter.data;

    // 创建窗口
    xcb_window_t window = xcb_generate_id(conn);
    uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    uint32_t values[2];
    values[0] = screen->white_pixel;
    values[1] = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE;

    xcb_create_window(conn, XCB_COPY_FROM_PARENT, window, screen->root, 
                        0, 0, 500, 500, 10, XCB_WINDOW_CLASS_INPUT_OUTPUT, 
                        screen->root_visual, mask, values);

    xcb_map_window(conn, window);
    xcb_flush(conn);

    // 读取事件
    xcb_generic_event_t* event;
    while((event = xcb_wait_for_event(conn))) {
        if(event->response_type == XCB_KEY_PRESS)
        {
            xcb_key_press_event_t *press = (xcb_key_press_event_t*)event;
            printf("press %d\n", press->detail);
        }
        
        if(event->response_type == XCB_KEY_RELEASE)
        {
            xcb_key_press_event_t *press = (xcb_key_press_event_t*)event;
            printf("release %d\n", press->detail);
        }
    }

    xcb_disconnect(conn);
}

获取全局事件

// 获取全局事件
#include <stdio.h>
#include <xcb/xcb.h>


#define	ModMaskAlt          XCB_MOD_MASK_1
#define ModMaskNumLock      XCB_MOD_MASK_2 
#define ModMaskSuper        XCB_MOD_MASK_4 
#define ModMaskModeSwitch   XCB_MOD_MASK_5
#define ModMaskShift        XCB_MOD_MASK_SHIFT
#define ModMaskCapsLock     XCB_MOD_MASK_LOCK
#define ModMaskControl      XCB_MOD_MASK_CONTROL

int main()
{
    // 连接到X11 Server
    xcb_connection_t* conn = xcb_connect(NULL, NULL);

    // 获取screen
    const xcb_setup_t* setup = xcb_get_setup(conn);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t* screen = iter.data;

    // 捕获快捷键
    xcb_grab_key(conn, 1, screen->root,
                ModMaskControl, 46, // Ctrl + L
                XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
    xcb_grab_key(conn, 1, screen->root,
                ModMaskControl | ModMaskCapsLock, 46, // Ctrl + L with CapsLock
                XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
    xcb_grab_key(conn, 1, screen->root,
                ModMaskControl | ModMaskNumLock, 46, // Ctrl + L with NumLock
                XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
    xcb_grab_key(conn, 1, screen->root,
                ModMaskControl | ModMaskCapsLock | ModMaskNumLock, 46, // Ctrl + L with CapsLock and NumLock
                XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
    xcb_flush(conn);

    // 读取事件
    xcb_generic_event_t* event;
    while((event = xcb_wait_for_event(conn))) {
        if(event->response_type == XCB_KEY_PRESS)
        {
            xcb_key_press_event_t *press = (xcb_key_press_event_t*)event;
            printf("press %d\n", press->detail);
        }

        if(event->response_type == XCB_KEY_RELEASE)
        {
            xcb_key_press_event_t *press = (xcb_key_press_event_t*)event;
            printf("release %d\n", press->detail);
        }
    }

    xcb_disconnect(conn);
}
xcb 获取鼠标指针的坐标
2024-12-18 21:18:31+08:00

xcb 获取鼠标指针的坐标

typedef struct xcb_query_pointer_reply_t {
    uint8_t      response_type;
    uint8_t      same_screen;
    uint16_t     sequence;
    uint32_t     length;
    xcb_window_t root;
    xcb_window_t child;
    int16_t      root_x;
    int16_t      root_y;
    int16_t      win_x;
    int16_t      win_y;
    uint16_t     mask;
    uint8_t      pad0[2];
} xcb_query_pointer_reply_t;

xcb_query_pointer_cookie_t xcb_query_pointer(xcb_connection_t *conn, xcb_window_t window);
xcb_query_pointer_reply_t *xcb_query_pointer_reply(xcb_connection_t *conn, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t **e);
#include <stdio.h>
#include <xcb/xcb.h>

int main()
{
    // 连接到X11 Server
    xcb_connection_t* conn = xcb_connect(NULL, NULL);

    // 获取screen
    const xcb_setup_t* setup = xcb_get_setup(conn);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t* screen = iter.data;

    // 获取鼠标指针的坐标
    xcb_query_pointer_cookie_t cookie = xcb_query_pointer(conn, screen->root);
    xcb_query_pointer_reply_t* reply = xcb_query_pointer_reply(conn, cookie, NULL);
    printf("%d %d\n", reply->root_x, reply->root_y);
    free(reply);
    
    xcb_disconnect(conn);
    return 0;
}
显示 X11 的空闲时间
2024-12-18 21:18:31+08:00

显示 X11 的空闲时间

// gcc main.c -lX11 -lXss
#include <stdio.h>
#include <X11/extensions/scrnsaver.h>

int main(void) 
{
    Display* dpy = XOpenDisplay(NULL);
    if (!dpy) 
        return 1;

    XScreenSaverInfo* info = XScreenSaverAllocInfo();
    while(1)
    {
        XScreenSaverQueryInfo(dpy, DefaultRootWindow(dpy), info);
        printf("%u\n", info->idle);
    }

    return 0;
}