65.9K
CodeProject 正在变化。 阅读更多。
Home

XCB 编程入门

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2016年4月5日

CPOL

16分钟阅读

viewsIcon

40127

downloadIcon

50

使用 XCB(X 协议 C 语言绑定)开发低级 Linux 应用程序

引言

多年前,我曾为各种平台编写过图形用户界面 (GUI)。对于一个 Linux 应用程序,我尝试使用 X Window System 库,通常称为 Xlib。我被完成简单任务所需的代码量震惊了,于是决定改用不同的工具集。

如今,开发者可以使用 XCB(代表 X protocol C-language Binding)为 X Window System 开发底层 GUI。XCB 程序可以执行与使用 Xlib 编写的程序相同的操作,但需要的代码量大大减少。此外,XCB 提供了“延迟隐藏、直接协议访问、改进的线程支持和可扩展性”。

本文的目的是解释 XCB 应用程序开发的基础。有三个核心主题:创建窗口、处理事件和绘制图形。对于每个主题,我都提供了一个源代码文件,演示了函数和数据结构如何在实践中使用。

1. 前期要求

理解本文内容需要具备 C 编程基础知识。要编译示例代码,您需要在开发系统上安装 XCB 库 (libxcb.so) 和头文件 (xcb.h)。

2. X Window System

尽管 WaylandMir 的普及度日益增长,但 X Window System (也称为 X Windows 或简称 X) 仍然是 Linux 计算机上管理窗口最常用的框架。它最初由 MIT 在 20 世纪 80 年代开发,已广泛应用于 UNIX 和 Linux 计算机,并且可以通过 Cygwin 在 Windows 计算机上访问。XCB 和 Xlib 的目标是使程序员能够在他们的应用程序中访问 X Window System。

在处理 X 时,首先要理解的是显示器、屏幕和窗口之间的区别。根据官方 文档,显示器是“共享通用键盘和指针的监视器集合”。屏幕标识一个物理监视器,而窗口是绘制在监视器上的图形元素。重要的是要理解,一个显示器可以有多个屏幕,每个屏幕可以有多个窗口。

关于 X 的第二点需要理解的是它使用客户端-服务器通信。X 支持远程显示,鉴于通常的客户端-服务器模型,您可能期望用户的系统从远程 X 服务器接收显示信息。但 X 的工作方式并非如此。X 服务器运行在用户的计算机上,并从客户端应用程序接收显示信息。在大多数情况下,客户端和服务器运行在同一系统上。

对于每个屏幕,X 服务器管理一个占据整个区域的窗口。这被称为根窗口。当客户端应用程序连接到服务器时,服务器允许它们创建子窗口。如果用户在子窗口获得焦点时执行操作,X 服务器将以事件的形式向客户端应用程序提供有关用户操作的信息。

X 服务器管理的每个资源都有一个标识符。每个显示器都有一个名称,每个屏幕都有一个编号。在应用程序可以创建新窗口或资源之前,它需要获得一个合适的 ID。

3. 创建窗口

大多数 XCB 应用程序都通过执行三个基本任务来启动:

  1. 连接到 X 服务器
  2. 访问屏幕
  3. 在屏幕上创建并显示窗口

接下来的讨论将解释如何完成这些任务。本节的最后部分将介绍一个显示一个简单窗口五秒钟的应用程序。

3.1 连接到 X 服务器

无论您是用 Xlib 还是 XCB 编程,应用程序的第一步通常都涉及连接到 X 服务器。在 XCB 中,使用的函数是 xcb_connect

xcb_connection_t* xcb_connect(const char *display, int *screen);

第一个参数标识 X 服务器的显示名称。如果此参数设置为 NULL,则该函数将使用 DISPLAY 环境变量的值。

第二个参数指向要连接的屏幕编号。如果此参数设置为 NULL,则该函数将屏幕编号设置为 0。这代表第一个可用屏幕。

xcb_connection_t 结构是不透明的,因此无法知道连接是否成功创建。为此,XCB 提供了 xcb_connection_has_error 函数,其声明如下:

int xcb_connection_has_error(xcb_connection_t *c);

此函数接受 xcb_connect 返回的 xcb_connection_t*,并返回一个标识连接是否建立的值。错误将导致非零值。

以下代码展示了这两个函数如何在实践中使用:

xcb_connection_t *conn;

conn = xcb_connect(NULL, NULL);

if (xcb_connection_has_error(conn)) {
  printf("Error opening display.\n");
  exit(1);
}

此代码创建到 X 服务器的连接,该服务器的名称由 DISPLAY 环境变量提供。屏幕编号为 0。

3.2 访问屏幕

在应用程序可以创建窗口之前,它需要访问屏幕。要做到这一点,第一步是访问 X 服务器及其显示环境的属性。这可以通过 xcb_get_setup 实现。

const xcb_setup_t* xcb_get_setup(xcb_connection_t *c);

返回值是一个 xcb_setup_t,它提供了许多有用的字段,包括:

  • roots_len — X 服务器管理的根窗口数量
  • bitmap_format_scanline_unit — 扫描行单位中的位数
  • bitmap_format_scanline_pad — 用于填充每行扫描线的位数
  • bitmap_format_bit_order — 标识屏幕中最左边的位是最低有效位还是最高有效位
  • protocol_major_version — 支持的 X Window System 协议的主版本
  • protocol_minor_version — 支持的 X Window System 协议的次版本

这个 xcb_setup_t 很重要,因为它允许我们访问 X 服务器的屏幕。这种访问可以通过另一个名为 xcb_setup_roots_iterator 的函数来实现。

xcb_screen_iterator_t xcb_setup_roots_iterator(xcb_setup_t *set);

xcb_screen_iterator_t 结构有一个类型为 xcb_screen_t*data 字段,它代表一个屏幕。此结构的字段包括:

  • root — 根窗口 ID
  • root_depth — 屏幕的每像素位数
  • root_visual — 指向 xcb_visualid_t 结构的指针,其中包含屏幕的颜色映射
  • width_in_pixels — 屏幕宽度(以像素为单位)
  • height_in_pixels — 屏幕高度(以像素为单位)
  • width_in_millimeters — 屏幕宽度(以毫米为单位)
  • height_in_millimeters — 屏幕高度(以毫米为单位)
  • black_pixel — 对应屏幕黑像素的值
  • white_pixel — 对应屏幕白像素的值

以下代码展示了这些结构的使用方法。它从现有连接 (conn) 获取 xcb_setup_t 结构,访问第一个屏幕,并打印屏幕的像素尺寸。

  const xcb_setup_t* setup;
  xcb_screen_t* screen;

  setup = xcb_get_setup(conn);
  screen = xcb_setup_roots_iterator(setup).data;
  printf("Screen dimensions: %d, %d\n", screen->width_in_pixels, screen->height_in_pixels);

获取屏幕是创建应用程序窗口的必要步骤。接下来的讨论将解释如何做到这一点。

3.3 创建并显示窗口

既然我们已经访问了屏幕,下一步就是创建窗口。主要使用的函数是 xcb_create_window

xcb_create_window(xcb_connection_t *conn,          // Connection to X server
                  uint8_t           depth,         // Screen depth
                  xcb_window_t      window_id,     // ID of the window
                  xcb_window_t      parent_id,     // ID of the parent window
                  int16_t           x,             // Top-left x-coordinate
                  int16_t           y,             // Top-left y-coordinate
                  uint16_t          width,         // Window width in pixels
                  uint16_t          height,        // Window height in pixels
                  uint16_t          border_width,  // Window border width in pixels
                  uint16_t          class,
                  xcb_visualid_t    visual,
                  uint32_t          value_mask,
                  const uint32_t   *value_list);

该函数的大部分参数都很直接。window_id 是窗口的唯一标识符,可以通过调用 xcb_generate_id 来获取,其签名如下:

uint32_t xcb_generate_id(xcb_connection_t* conn)

parent_id 可以设置为前面讨论的 xcb_screen_t 结构的 root 字段。同样,visual 参数可以设置为屏幕的 root_visual 字段。

该函数的 class 参数设置为 xcb_window_class_t 枚举类型的值。这并未被文档化,但通常将 class 设置为 XCB_WINDOW_CLASS_INPUT_OUTPUT 是安全的。

value_maskvalue_list 参数是相关的。value_mask 标识属性名称的 OR 组合,value_list 包含它们的值。属性标识符取自 xcb_cw_t 类型,其值可以在 此处 找到。一个重要的属性是 XCB_CW_BACK_PIXEL,它设置窗口的背景颜色。

创建窗口后,必须调用 xcb_map_window 来使其可见。该函数签名如下:

xcb_void_cookie_t xcb_map_window(xcb_connection_t *conn, xcb_window_t window) 

调用 xcb_map_window 后,通常会调用 xcb_flush 来强制将窗口请求发送到服务器。以下讨论中的代码演示了这一点的使用方法。

3.4 示例 - 简单窗口

simple_window.c 文件中的代码创建并显示了一个简单的 100x100 像素窗口。main 函数如下:

int main(void) {

  xcb_connection_t   *conn;
  const xcb_setup_t  *setup;
  xcb_screen_t       *screen;
  xcb_window_t       window_id;
  uint32_t           prop_name;
  uint32_t           prop_value;

  /* Connect to X server */
  conn = xcb_connect(NULL, NULL);
  if(xcb_connection_has_error(conn)) {
    printf("Error opening display.\n");
    exit(1);
  }

  /* Obtain setup info and access the screen */
  setup = xcb_get_setup(conn);
  screen = xcb_setup_roots_iterator(setup).data;

  /* Create window */
  window_id = xcb_generate_id(conn);
  prop_name = XCB_CW_BACK_PIXEL;
  prop_value = screen->white_pixel;

  xcb_create_window(conn, screen->root_depth, 
     window_id, screen->root, 0, 0, 100, 100, 1,
     XCB_WINDOW_CLASS_INPUT_OUTPUT, 
     screen->root_visual, prop_name, &prop_value);

  /* Display the window */
  xcb_map_window(conn, window_id);
  xcb_flush(conn);

  /* Wait for 5 seconds */
  sleep(5);

  /* Disconnect from X server */
  xcb_disconnect(conn);
  return 0;
}

这段代码至少有三个值得注意的地方:

  • 窗口的位置是 (0, 0),大小是 100x100 像素。这由 xcb_create_window 中的参数配置。
  • 窗口的背景颜色设置为白色,方法是将 XCB_CW_BACK_PIXEL 属性与 screen->white_pixel 的值关联起来。
  • 此代码没有显式关闭窗口。相反,它调用 xcb_disconnect 来终止与 X 服务器的连接。

如果开发系统上安装了 XCB,可以使用以下命令编译源代码文件:

gcc -o simple_window simple_window.c -lxcb

除非用户点击窗口的关闭按钮,否则由于 sleep 函数的作用,窗口将保持打开状态五秒钟。如果没有这个函数,窗口将立即关闭。大多数 XCB 应用程序都有一个事件循环,而不是调用 sleep。下面的讨论将解释事件的工作原理。

4. 处理事件

当用户执行涉及子窗口的操作时,例如单击鼠标或按下键盘,X 服务器会向客户端应用程序发送一条消息。应用程序可以通过调用 xcb_wait_for_event 来检索此消息。

xcb_generic_event_t *xcb_wait_for_event(xcb_connection_t *c);

xcb_generic_event_t 结构有一个 response_type 字段,用于标识发生了哪个事件。XCB 支持三十多种不同类型的事件,以下是六种事件代码:

  • XCB_BUTTON_PRESS — 按下鼠标按钮
  • XCB_BUTTON_RELEASE — 释放鼠标按钮
  • XCB_MOTION_NOTIFY — 鼠标移动
  • XCB_KEY_PRESS — 按下键盘按键
  • XCB_KEY_RELEASE — 释放键盘按键
  • XCB_EXPOSE — 窗口显示内容

要配置事件处理,xcb_create_windowvalue_maskvalue_list 参数必须标识感兴趣的事件。具体来说,value_mask 必须设置为包含 XCB_CW_EVENT_MASK 的 OR 组合。value_list 中的相应条目必须包含一个 OR 组合,用于标识应用程序应接收哪些事件。

例如,以下代码配置新窗口以关注鼠标单击、键盘按下和暴露事件:

value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;

value_list[0] = screen->white_pixel;

value_list[1] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_KEY_PRESS | 
                XCB_EVENT_MASK_EXPOSURE;

value_list 中的第一个条目标识背景颜色(对应于 XCB_BACK_PIXEL)。第二个条目包含一个 OR 组合值,用于标识感兴趣的事件。

在窗口配置为接收事件后,可以调用 xcb_wait_for_event 来接收来自 X 服务器的消息。此函数会暂停应用程序直到发生事件,通常在循环中调用。

xcb_generic_event_t event;

while((event = xcb_wait_for_event(conn)) ) {
  switch (event->response_type) {

  // Respond if the event is a key press
  case XCB_KEY_PRESS:
    ...
    break;
  
 // Respond if the event is a mouse click
  case XCB_BUTTON_PRESS:
    ...
    break;
  
  // Respond if the event is something else
  default:
    ...
    break;

  free (event);
}

在此代码中,while 循环在每个事件接收和处理之后调用 xcb_wait_for_event。这意味着循环永远不会终止。正如官方 XCB 文档所承诺的那样,有一个“特殊退出事件”会更好。但我从未见过它的使用。

相反,通常会在 while 循环的条件中插入一个布尔值。当该值返回 true 时,循环将终止。本节末尾的示例代码将演示这一点的工作原理。

4.1 响应鼠标单击

如果用户单击鼠标按钮,来自 X 服务器的消息可以作为 xcb_button_press_event_t 访问,其中包含与鼠标事件相关的字段。其字段包括:

  • detail — 按下的按钮
  • time — 鼠标单击的时间戳
  • event_x, event_y — 相对于客户端窗口的事件坐标
  • root_x, root_y — 相对于根窗口的事件坐标
  • root — 根窗口的 ID
  • child — 子窗口的 ID

detailxcb_button_index_t 枚举类型的值,它有六个值:

  1. XCB_BUTTON_INDEX_ANY — 任意鼠标按钮
  2. XCB_BUTTON_INDEX_1 — 左鼠标按钮
  3. XCB_BUTTON_INDEX_2 — 中间鼠标按钮
  4. XCB_BUTTON_INDEX_3 — 右鼠标按钮
  5. XCB_BUTTON_INDEX_4 — 鼠标向上滚动
  6. XCB_BUTTON_INDEX_5 — 鼠标向下滚动

以下代码展示了应用程序如何访问鼠标事件的信息:

while((event = xcb_wait_for_event(conn)) ) {
  switch(event->response_type) {
    ...
    case XCB_BUTTON_PRESS:
      printf("Button pressed: %d\n", ((xcb_button_press_event_t*)event)->detail);
      break;
  }
}

4.2 响应键盘按下

如果用户按下键盘按键,X 服务器的事件消息可以作为 xcb_key_press_event_t 访问,其字段提供了与按键相关的信息。这些字段包括:

  • detail — 标识按下键的值
  • time — 按键的时间戳
  • root — 根窗口的 ID
  • child — 子窗口的 ID

您可能期望 detail 使用 ASCII 或 Unicode 来标识按键。不幸的是,事实并非如此。要确定按下的键,应用程序需要调用 xcb_keysyms.h 头文件中的函数。这些函数未被文档化,我发现很少有它们使用的例子。因此,本文将不详细介绍它们。

4.3 示例 - 事件窗口

event_window.c 中的代码创建了与 simple_window.c 相同的白色窗口。不同之处在于应用程序配置窗口以接收事件,然后在事件循环中处理事件。以下代码展示了窗口的配置方式:

  /* Create window */
  window_id = xcb_generate_id(conn);
  value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
  value_list[0] = screen->white_pixel;
  value_list[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | 
                  XCB_EVENT_MASK_KEY_PRESS;

  xcb_create_window(conn, screen->root_depth, 
     window_id, screen->root, 0, 0, 100, 100, 1,
     XCB_WINDOW_CLASS_INPUT_OUTPUT, 
     screen->root_visual, value_mask, value_list);

如所示,value_mask 中的值指定将配置窗口的背景和事件处理。value_list 中的值指定背景应为白色,应用程序应接收与鼠标单击、键盘按下和暴露相关的事件。以下代码展示了事件循环中如何处理这些事件。

  /* Execute the event loop */
  while (!finished && (event = xcb_wait_for_event(conn))) {

    switch(event->response_type) {

      /* Respond to key presses */
      case XCB_KEY_PRESS:
        printf("Keycode: %d\n", ((xcb_key_press_event_t*)event)->detail);
        finished = 1;
        break;

      /* Respond to button presses */
      case XCB_BUTTON_PRESS:
        printf("Button pressed: %u\n", ((xcb_button_press_event_t*)event)->detail);
        printf("X-coordinate: %u\n", ((xcb_button_press_event_t*)event)->event_x);
        printf("Y-coordinate: %u\n", ((xcb_button_press_event_t*)event)->event_y);
        break;

      case XCB_EXPOSE:
        break;
    }
    free(event);
  }

如果用户单击鼠标按钮,应用程序将打印按钮编号和单击的坐标。如果用户按下键盘按键,应用程序将打印按键代码并将 finished 设置为 1。当 finished 从 0 变为 1 时,while 循环终止,应用程序断开与 X 服务器的连接。

5. 绘制图形

空白窗口并不特别有趣。XCB 使向窗口添加形状变得简单,该过程包括两个步骤:

  1. 创建图形上下文
  2. 调用一个或多个绘图图元。

本节将讨论这两个步骤。然后,我们将介绍一个向前面介绍的简单窗口添加形状的应用程序。

需要注意的是,XCB 没有创建按钮或文本框等 GUI 小部件的函数。如果您想构建传统的用户界面,最好使用 GTK+、FLTK 或 Qt 等工具。

5.1 创建图形上下文

图形上下文保存窗口的图形配置,例如字体、线条样式和前景色。此结构可以通过 xcb_create_gc 创建。

xcb_create_gc(xcb_connection_t *conn,
              xcb_gcontext_t    context_id,
              xcb_drawable_t    drawable,
              uint32_t          value_mask,
              const uint32_t   *value_list);

context_id 是上下文的唯一标识符。可以通过调用 xcb_generate_id 来获取。

drawable 参数可以设置为屏幕的根窗口。这由前面讨论的 xcb_screen_t 结构的 root 字段提供。

最后两个参数 value_maskvalue_list 类似于 xcb_create_windowvalue_maskvalue_list 参数。区别在于属性名称取自 xcb_gc_t 枚举类型,完整列表可以在 此处 找到。

图形上下文的两个重要属性涉及前景色和暴露事件。可以通过将 XCB_GC_FOREGROUND 添加到 value_mask 并将 value_list 的第一个值设置为适当的颜色来设置上下文的前景色。此外,XCB_GC_GRAPHICS_EXPOSURES 属性配置上下文是否生成暴露事件。可以通过将 value_list 的第二个值设置为 0 或 1 来打开或关闭此功能。以下代码展示了这一点的工作原理:

value_mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
value_list[0] = s->black_pixel;
value_list[1] = 0;

这会将上下文的前景色设置为黑色,并关闭暴露事件的生成。这些设置将在本文中一直使用。

5.2 绘图图元

XCB 提供了八个在窗口中绘制形状的函数。这些称为绘图图元,表 1 列出了每个函数的签名:

表 1: 绘图函数
函数 描述

xcb_poly_point(
  xcb_connection_t *conn,
  uint8_t coord_mode,
  xcb_drawable_t window,
  xcb_gcontext_t context_id,
  uint32_t num_points,
  const xcb_point_t *points)

绘制一个或多个点

xcb_poly_line(
  xcb_connection_t *conn,
  uint8_t coord_mode,
  xcb_drawable_t window,
  xcb_gcontext_t context_id,
  uint32_t num_points,
  const xcb_point_t *points)

在点之间绘制连接线

xcb_poly_segment(
  xcb_connection_t *conn,
  xcb_drawable_t window,
  xcb_gcontext_t context_id,
  uint32_t num_segments,
  const xcb_segment_t *segments)

绘制独立的线段

xcb_poly_rectangle(
  xcb_connection_t *conn,
  xcb_drawable_t window,
  xcb_gcontext_t context_id,
  uint32_t num_rects,
  const xcb_rectangle_t *rects)

绘制一个或多个矩形

xcb_poly_arc(
  xcb_connection_t *conn,
  xcb_drawable_t window,
  xcb_gcontext_t context_id,
  uint32_t num_arcs,
  const xcb_segment_t *arcs)

绘制一个或多个椭圆弧

xcb_fill_poly(
  xcb_connection_t *conn,
  xcb_drawable_t window,
  xcb_gcontext_t context_id,
  uint8_t shape,
  uint8_t coord_mode,
  uint32_t num_points
  const xcb_point_t *points)

绘制并填充多边形

xcb_poly_fill_rectangle(
  xcb_connection_t *conn,
  xcb_drawable_t window,
  xcb_gcontext_t context_id,
  uint32_t num_rects,
  const xcb_rectangle_t *rects)

绘制并填充一个或多个矩形

xcb_poly_fill_arc(
  xcb_connection_t *conn,
  xcb_drawable_t window,
  xcb_gcontext_t context_id,
  uint32_t num_arcs,
  const xcb_arc_t *arcs)

绘制并填充一个或多个椭圆弧

许多这些函数都有一个 coord_mode 参数,用于指定形状的坐标如何解释。它可以设置为以下两个值之一:

  • XCB_COORD_MODE_ORIGIN — 所有坐标对都相对于原点 (0, 0)
  • XCB_COORD_MODE_PREVIOUS — 每个坐标对都相对于前一个坐标对

每个绘图图元的最后一个参数包含一个结构数组,这些结构代表不同的形状(点、线段、矩形和弧)。下面的讨论将探讨这些形状。

5.2.1 点、线和多边形

许多函数使用点来定义它们的形状。每个点都必须作为 xcb_point_t 结构给出,该结构有两个整数字段:xy。例如,以下代码显示了如何调用 xcb_poly_point

xcb_point_t points[4] = { {40, 40}, {40, 80}, {80, 40}, {80, 80} };
xcb_poly_point(conn, XCB_COORD_MODE_ORIGIN, window_id, context_id, 4, points);

xcb_poly_linexcb_fill_poly 函数的工作方式基本相同。以下代码调用 xcb_fill_poly 来创建一个填充的五边形。

xcb_point_t points[5] = { {11, 24}, {30, 10}, {49, 24}, {42, 46}, {18, 46} }; 
xcb_fill_poly(conn, window_id, context_id, XCB_POLY_SHAPE_CONVEX, 
              XCB_COORD_MODE_ORIGIN, 5, points);

此代码将 xcb_fill_polyshape 参数设置为 XCB_POLY_SHAPE_CONVEX。其他值包括 XCB_POLY_SHAPE_NONCONVEXXCB_POLY_SHAPE_COMPLEX

5.2.2 线段

理解 xcb_poly_linexcb_poly_segment 之间的区别很重要。第一个接受一个点数组,并从每个点绘制一条线到下一个点。相比之下,xcb_poly_segment 绘制的线条不一定连接。

xcb_poly_segment 的最后一个参数是 xcb_segment_t 结构数组。该结构有四个整数字段:x1y1x2y2。因此,每个线段连接 (x1, y1) 到 (x2, y2)。以下代码展示了这一点的工作原理:

xcb_segment_t segments[2] = { {60, 20, 90, 40}, {60, 40, 90, 20} }; 
xcb_poly_segment(conn, window_id, context_id, 2, segments);

5.2.3 矩形

xcb_poly_rectanglexcb_poly_fill_rectangle 都接受 xcb_rectangle_t 结构数组。该结构有四个字段:xywidthheight。x 和 y 字段标识矩形左上角的坐标。例如,以下代码绘制并填充一个 30x20 像素的矩形,其左上角位于 (15, 65)。

xcb_rectangle_t rect = {15, 65, 30, 20};
xcb_poly_fill_rectangle(conn, window_id, context_id, 1, &rect);

5.2.4 弧

xcb_poly_arcxcb_poly_fill_arc 都接受 xcb_arc_t 结构。xcb_arc_t 结构表示椭圆的一个弧,并且有六个字段:

  • x — 包含椭圆的矩形左上角的 x 坐标
  • y — 包含椭圆的矩形左上角的 y 坐标
  • width — 椭圆的宽度
  • height — 椭圆的高度
  • angle1 — 弧的起始角度,以 1/64 度为单位
  • angle2 — 弧的结束角度,以 1/64 度为单位

以下代码绘制一个椭圆的上半部分,该椭圆的矩形左上角位于 (60, 70),尺寸为 30x20 像素。

xcb_arc_t arc = {60, 70, 30, 20, 0, 180 << 6};
xcb_poly_arc(conn, window_id, context_id, 1, &arc);

重要的是要注意角度以 1/64 度为单位。这就是为什么第二个角度(应为 180 度)被指定为 180 << 6

5.2.5 绘图图元和暴露事件

为了正常工作,绘图图元必须在窗口绘制之后执行。这可以通过在发生暴露事件后调用函数来实现。以下讨论中的代码将演示这一点的工作原理。

5.3 示例 - 图形窗口

graphic_window.c 中的代码通过定义一系列形状结构并调用绘图图元来扩展 event_window.c 的代码。形状定义如下:

xcb_point_t points[5] = { {11, 24}, {30, 10}, {49, 24}, {42, 46}, {18, 46} }; 
xcb_segment_t segments[2] = { {60, 20, 90, 40}, {60, 40, 90, 20} };
xcb_rectangle_t rect = {15, 65, 30, 20};
xcb_arc_t arc = {60, 70, 30, 20, 0, 180 << 6};

graphic_window.c 中的以下代码展示了如何在事件循环中调用绘图图元。每个暴露事件都会绘制一个五边形、两条线段、一个矩形和一个椭圆弧。

  /* Execute the event loop */
  while (!finished && (event = xcb_wait_for_event(conn))) {

    switch(event->response_type) {

      case XCB_KEY_PRESS:
        printf("Keycode: %d\n", ((xcb_key_press_event_t*)event)->detail);
        finished = 1;
        break;

      case XCB_BUTTON_PRESS:
        printf("Button pressed: %u\n", ((xcb_button_press_event_t*)event)->detail);
        printf("X-coordinate: %u\n", ((xcb_button_press_event_t*)event)->event_x);
        printf("Y-coordinate: %u\n", ((xcb_button_press_event_t*)event)->event_y);
        break;

      case XCB_EXPOSE:

        /* Draw polygon */
        xcb_fill_poly(conn, window_id, context_id, XCB_POLY_SHAPE_CONVEX, 
                      XCB_COORD_MODE_ORIGIN, 5, points);

        /* Draw line segments */
        xcb_poly_segment(conn, window_id, context_id, 2, segments);

        /* Draw rectangle */
        xcb_poly_fill_rectangle(conn, window_id, context_id, 1, &rect);

        /* Draw arc */
        xcb_poly_arc(conn, window_id, context_id, 1, &arc);

        xcb_flush(conn);
        break;

    }
    free(event);
  }

调用绘图图元后,事件循环调用 xcb_flush 来强制将绘图请求发送到 X 服务器。

Using the Code

XCB_examples.zip 存档包含本文提到的三个源文件。我没有提供任何 makefile,但可以使用以下命令编译代码:

  • gcc -o simple_window simple_window.c -lxcb
  • gcc -o event_window event_window.c -lxcb
  • gcc -o graphic_window graphic_window.c -lxcb

当然,只有当 XCB 已安装在系统上时,应用程序才能正常执行。

历史

2016/4/5 - 首次提交文章

© . All rights reserved.