如何正确处理 xcb 客户端消息

How to handle xcb client messages properly

以下代码使用 opengl 渲染上下文创建 window。 一切正常,但是 window 关闭事件与 wm_delete_window 原子不相关。

static int
hnd_connect_to_xcb
(
  hnd_linux_window_t *_window
)
{
  _window->renderer.display = XOpenDisplay((char *)NULL);
  if (!hnd_assert(_window->renderer.display != NULL, "Could not open display"))
    return HND_NK;

  _window->renderer.default_screen = DefaultScreen(_window->renderer.display);
  _window->connection = XGetXCBConnection(_window->renderer.display);
  if (!hnd_assert(_window->connection != NULL, "Could not connect to X display"))
    return HND_NK;

  XSetEventQueueOwner(_window->renderer.display, XCBOwnsEventQueue);

  /* Get screen data */
  xcb_screen_iterator_t screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(_window->connection));
  for (int i = _window->renderer.default_screen; i > 0; --i)
    xcb_screen_next(&screen_iterator);
  _window->screen_data = screen_iterator.data;

  if (!hnd_set_fb_configs(&_window->renderer))
    return HND_NK;

  return HND_OK;
}

static void
hnd_set_window_properties
(
  hnd_linux_window_t *_window
)
{
  /* Set title */
  xcb_intern_atom_cookie_t utf8_string_cookie =
    hnd_create_intern_atom_cookie(_window->connection, HND_NK, "UTF8_STRING");
  _window->utf8_string = hnd_create_intern_atom_reply(_window->connection, utf8_string_cookie);

  xcb_intern_atom_cookie_t wm_name_cookie =
    hnd_create_intern_atom_cookie(_window->connection, HND_NK, "WM_NAME");
  _window->wm_name = hnd_create_intern_atom_reply(_window->connection, wm_name_cookie);

  xcb_change_property(_window->connection,
                      XCB_PROP_MODE_REPLACE,
                      _window->id,
                      _window->wm_name->atom,
                      _window->utf8_string->atom,
                      8,
                      strlen(_window->title),
                      _window->title);

  /* Add listener for close event */
  xcb_intern_atom_cookie_t wm_protocols_cookie =
    hnd_create_intern_atom_cookie(_window->connection, HND_OK, "WM_PROTOCOLS");
  _window->wm_protocols = hnd_create_intern_atom_reply(_window->connection, wm_protocols_cookie);

  xcb_intern_atom_cookie_t wm_delete_cookie =
    hnd_create_intern_atom_cookie(_window->connection, HND_NK, "WM_DELETE_WINDOW");
  _window->wm_delete_window = hnd_create_intern_atom_reply(_window->connection, wm_delete_cookie);

  xcb_change_property(_window->connection,
                      XCB_PROP_MODE_REPLACE,
                      _window->id,
                      _window->wm_protocols->atom,
                      XCB_ATOM_ATOM,
                      32,
                      1,
                      &_window->wm_delete_window->atom);
}

hnd_linux_window_t *
hnd_create_window
(
  const char   *_title,
  unsigned int  _left,
  unsigned int  _top,
  unsigned int  _width,
  unsigned int  _height,
  unsigned int _decoration
)
{
  hnd_linux_window_t *new_window = malloc(sizeof(hnd_linux_window_t));
  if (!hnd_assert(new_window != NULL, NULL))
    return NULL;

  if (!hnd_connect_to_xcb(new_window))
    return NULL;

  new_window->title = (char *)_title;
  new_window->left = _left;
  new_window->top = _top;
  new_window->width = _width;
  new_window->height = _height;
  new_window->running = HND_OK;

  if (!hnd_init_renderer(&new_window->renderer))
  {
    hnd_destroy_window(new_window);

    return NULL;
  }

  new_window->colormap_id = xcb_generate_id(new_window->connection);
  xcb_create_colormap(new_window->connection,
                      XCB_COLORMAP_ALLOC_NONE,
                      new_window->colormap_id,
                      new_window->screen_data->root,
                      new_window->renderer.visual_id);
  
  new_window->value_mask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;

  new_window->event_mask = XCB_EVENT_MASK_EXPOSURE       |
                           XCB_EVENT_MASK_BUTTON_PRESS   |
                           XCB_EVENT_MASK_BUTTON_RELEASE |
                           XCB_EVENT_MASK_POINTER_MOTION |
                           XCB_EVENT_MASK_BUTTON_MOTION  |
                           XCB_EVENT_MASK_ENTER_WINDOW   |
                           XCB_EVENT_MASK_LEAVE_WINDOW   |
                           XCB_EVENT_MASK_KEY_PRESS      |
                           XCB_EVENT_MASK_KEY_RELEASE;

  new_window->value_list[0] = new_window->event_mask;
  new_window->value_list[1] = new_window->colormap_id;

  /* Create window */
  new_window->id = xcb_generate_id(new_window->connection);
  xcb_create_window(new_window->connection,
                    XCB_COPY_FROM_PARENT,
                    new_window->id,
                    new_window->screen_data->root,
                    new_window->left,
                    new_window->top,
                    new_window->width,
                    new_window->height,
                    0,
                    XCB_WINDOW_CLASS_INPUT_OUTPUT,
                    new_window->renderer.visual_id,
                    new_window->value_mask,
                    new_window->value_list);

  hnd_set_window_properties(new_window);
  hnd_set_window_decoration(new_window, _decoration);

  xcb_map_window(new_window->connection, new_window->id);
  xcb_flush(new_window->connection);

  /* Finish opengl binding */
  new_window->renderer.gl_window = glXCreateWindow(new_window->renderer.display,
                                                   new_window->renderer.current_fb_config,
                                                   new_window->id,
                                                   0);
  if (!hnd_assert(new_window->renderer.gl_window, "Could not create OpenGL window"))
  {
    hnd_destroy_window(new_window);
    
    return NULL;
  }

  if (!hnd_assert(glXMakeContextCurrent(new_window->renderer.display,
                                        new_window->renderer.gl_window,
                                        new_window->renderer.gl_window,
                                        new_window->renderer.gl_context),
                                        "Could not set OpenGL current rendering context"))
  {
    hnd_destroy_window(new_window);
    
    return NULL;
  }
  
  hnd_print_debug(HND_LOG, HND_CREATED("OpenGL context"), HND_SUCCESS);

  hnd_print_debug(HND_LOG, HND_CREATED("window"), HND_SUCCESS);
  return new_window;
}

void
hnd_destroy_window
(
  hnd_linux_window_t *_window
)
{
  if (!hnd_assert(_window != NULL, HND_SYNTAX))
    return;

  hnd_end_renderer(&_window->renderer);
  xcb_free_colormap(_window->connection, _window->colormap_id);

  hnd_print_debug(HND_LOG, HND_ENDED("window"), HND_SUCCESS);
}

void
hnd_poll_events
(
  hnd_linux_window_t *_window,
  hnd_event_t        *_event
)
{
  if (!hnd_assert(_window != NULL, HND_SYNTAX))
    return;
  if (!hnd_assert(_event != NULL, HND_SYNTAX))
    return;

  _event->xcb_event = xcb_poll_for_event(_window->connection);
  if (!_event->xcb_event)
    return;

  switch (_event->xcb_event->response_type & 0x7f)
  {
  case XCB_EXPOSE:
    xcb_flush(_window->connection);
    
    break;
  case XCB_KEY_PRESS:
    hnd_queue_key_event(_event->pressed_keys, _event->xcb_event);

    break;
  case XCB_KEY_RELEASE:
    hnd_queue_key_event(_event->released_keys, _event->xcb_event);
  
    break;
  case XCB_CLIENT_MESSAGE:
  {
    xcb_client_message_event_t *temp_client_message_event = (xcb_client_message_event_t *)_event;
    
    if (temp_client_message_event->data.data32[0] == _window->wm_delete_window->atom)
      _window->running = HND_NK;
    
  } break;
  default:
      break;
  }
}

除了这两个创建原子的助手之外,大多数渲染器函数都没有太大帮助:

xcb_intern_atom_cookie_t
hnd_create_intern_atom_cookie
(
  xcb_connection_t *_connection,
  unsigned int      _exists,
  const char       *_string
)
{
  return xcb_intern_atom(_connection, _exists, strlen(_string), _string);
}

xcb_intern_atom_reply_t *
hnd_create_intern_atom_reply
(
  xcb_connection_t         *_connection,
  xcb_intern_atom_cookie_t  _cookie
)
{
  xcb_intern_atom_reply_t *new_atom_reply = xcb_intern_atom_reply(_connection, _cookie, NULL);
  if (!hnd_assert(new_atom_reply != NULL, "Could not create atom"))
    return NULL;

  return new_atom_reply;
}

是的,我包括了外部文件。一切都正确编译。

问题基本上是错别字:

  _event->xcb_event = xcb_poll_for_event(_window->connection);
[...]
  switch (_event->xcb_event->response_type & 0x7f)
  {
[...]
  case XCB_CLIENT_MESSAGE:
  {
    xcb_client_message_event_t *temp_client_message_event = (xcb_client_message_event_t *)_event;

您正在 _event->xcb_event 中保存活动。但是您正在将 _event 转换为 xcb_client_message_event_t*。你应该投射 _event->xcb_event.

所以,最后一行应该是:

    xcb_client_message_event_t *temp_client_message_event = (xcb_client_message_event_t *)_event->xcb_event;