#include "core/backend.h" #include #include #include #include #include #include #include #include #include #include #include "backend/output_utils.h" #include "base/log.h" #include "base/macros.h" #include "base/memory.h" #include "core/types.h" #include "internal.h" /* IWYU pragma: keep */ typedef struct atom_item_t { const char *name; size_t len; xcb_atom_t *atom; } atom_item_t; static void atoms_init(backend_t *backend) { xcb_connection_t *conn = backend->conn; atoms_t *atoms = &backend->atoms; atom_item_t atom_list[] = { {"COMPOUND_TEXT", sizeof("COMPOUND_TEXT") - 1, &atoms->COMPOUND_TEXT}, {"UTF8_STRING", sizeof("UTF8_STRING") - 1, &atoms->UTF8_STRING}, {"WM_WINDOW_ROLE", sizeof("WM_WINDOW_ROLE") - 1, &atoms->WM_WINDOW_ROLE}, {"WM_NAME", sizeof("WM_NAME") - 1, &atoms->WM_NAME}, {"_NET_WM_NAME", sizeof("_NET_WM_NAME") - 1, &atoms->_NET_WM_NAME}, {"_NET_WM_STATE", sizeof("_NET_WM_STATE") - 1, &atoms->_NET_WM_STATE}, {"_NET_WM_STATE_FULLSCREEN", sizeof("_NET_WM_STATE_FULLSCREEN") - 1, &atoms->_NET_WM_STATE_FULLSCREEN}, {"_NET_WM_STATE_ABOVE", sizeof("_NET_WM_STATE_ABOVE") - 1, &atoms->_NET_WM_STATE_ABOVE}, {"_NET_WM_STATE_STICKY", sizeof("_NET_WM_STATE_STICKY") - 1, &atoms->_NET_WM_STATE_STICKY}, {"_NET_WM_STATE_MODAL", sizeof("_NET_WM_STATE_MODAL") - 1, &atoms->_NET_WM_STATE_MODAL}, {"_NET_WM_STATE_SKIP_TASKBAR", sizeof("_NET_WM_STATE_SKIP_TASKBAR") - 1, &atoms->_NET_WM_STATE_SKIP_TASKBAR}, {"_NET_WM_STATE_MAXIMIZED_VERT", sizeof("_NET_WM_STATE_MAXIMIZED_VERT") - 1, &atoms->_NET_WM_STATE_MAXIMIZED_VERT}, {"_NET_WM_STATE_MAXIMIZED_HORZ", sizeof("_NET_WM_STATE_MAXIMIZED_HORZ") - 1, &atoms->_NET_WM_STATE_MAXIMIZED_HORZ}, {"_NET_WM_WINDOW_TYPE", sizeof("_NET_WM_WINDOW_TYPE") - 1, &atoms->_NET_WM_WINDOW_TYPE}, {"_NET_WM_WINDOW_TYPE_NORMAL", sizeof("_NET_WM_WINDOW_TYPE_NORMAL") - 1, &atoms->_NET_WM_WINDOW_TYPE_NORMAL}, {"_NET_WM_WINDOW_TYPE_DESKTOP", sizeof("_NET_WM_WINDOW_TYPE_DESKTOP") - 1, &atoms->_NET_WM_WINDOW_TYPE_DESKTOP}, {"_NET_WM_WINDOW_TYPE_DOCK", sizeof("_NET_WM_WINDOW_TYPE_DOCK") - 1, &atoms->_NET_WM_WINDOW_TYPE_DOCK}, {"_NET_WM_WINDOW_TYPE_TOOLBAR", sizeof("_NET_WM_WINDOW_TYPE_TOOLBAR") - 1, &atoms->_NET_WM_WINDOW_TYPE_TOOLBAR}, {"_NET_WM_WINDOW_TYPE_DIALOG", sizeof("_NET_WM_WINDOW_TYPE_DIALOG") - 1, &atoms->_NET_WM_WINDOW_TYPE_DIALOG}, {"_NET_WM_WINDOW_TYPE_UTILITY", sizeof("_NET_WM_WINDOW_TYPE_UTILITY") - 1, &atoms->_NET_WM_WINDOW_TYPE_UTILITY}, {"_NET_WM_WINDOW_TYPE_SPLASH", sizeof("_NET_WM_WINDOW_TYPE_SPLASH") - 1, &atoms->_NET_WM_WINDOW_TYPE_SPLASH}, {"_NET_WM_WINDOW_TYPE_MENU", sizeof("_NET_WM_WINDOW_TYPE_MENU") - 1, &atoms->_NET_WM_WINDOW_TYPE_MENU}, {"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", sizeof("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU") - 1, &atoms->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU}, {"_NET_WM_WINDOW_TYPE_POPUP_MENU", sizeof("_NET_WM_WINDOW_TYPE_POPUP_MENU") - 1, &atoms->_NET_WM_WINDOW_TYPE_POPUP_MENU}, {"_NET_WM_WINDOW_TYPE_TOOLTIP", sizeof("_NET_WM_WINDOW_TYPE_TOOLTIP") - 1, &atoms->_NET_WM_WINDOW_TYPE_TOOLTIP}, {"_NET_WM_WINDOW_TYPE_COMBO", sizeof("_NET_WM_WINDOW_TYPE_COMBO") - 1, &atoms->_NET_WM_WINDOW_TYPE_COMBO}, {"_NET_WM_WINDOW_TYPE_DND", sizeof("_NET_WM_WINDOW_TYPE_DND") - 1, &atoms->_NET_WM_WINDOW_TYPE_DND}, {"_NET_WM_WINDOW_TYPE_NOTIFICATION", sizeof("_NET_WM_WINDOW_TYPE_NOTIFICATION") - 1, &atoms->_NET_WM_WINDOW_TYPE_NOTIFICATION}, }; xcb_intern_atom_cookie_t cookies[countof(atom_list)]; for (size_t i = 0; i < countof(atom_list); ++i) { const atom_item_t *atom = &atom_list[i]; cookies[i] = xcb_intern_atom_unchecked(conn, false, atom->len, atom->name); } xcb_intern_atom_reply_t *reply = nullptr; for (size_t i = 0; i < countof(atom_list); ++i) { reply = xcb_intern_atom_reply(conn, cookies[i], nullptr); if (!reply) continue; atom_item_t *atom = &atom_list[i]; *atom->atom = reply->atom; p_delete(&reply); } } backend_t *backend_create(const char *display_name) { int screen_num = 0; xcb_connection_t *conn = xcb_connect(display_name, &screen_num); int xcb_conn_error = xcb_connection_has_error(conn); if (xcb_conn_error) { fatal("cannot open display %s, error %d", display_name, xcb_conn_error); } xcb_screen_t *screen = xcb_aux_get_screen(conn, screen_num); if (!screen) fatal("cannot get screen info"); xcb_window_t root = screen->root; uint32_t mask = XCB_CW_EVENT_MASK; const xcb_params_cw_t params = { .event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, }; xcb_void_cookie_t cookie = xcb_aux_change_window_attributes_checked(conn, root, mask, ¶ms); if (xcb_request_check(conn, cookie)) { fatal( "another window manager is already running (cannot select " "SubstructureRedirect)"); } backend_t *backend = p_new(backend_t, 1); backend->conn = conn; backend->screen = screen; backend->screenp = screen_num; xcb_prefetch_extension_data(conn, &xcb_xfixes_id); const xcb_query_extension_reply_t *query = xcb_get_extension_data(conn, &xcb_xfixes_id); if (query && query->present) { xcb_xfixes_query_version_cookie_t cookie = xcb_xfixes_query_version( conn, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION); xcb_xfixes_query_version_reply_t *reply = xcb_xfixes_query_version_reply(conn, cookie, nullptr); if (reply) { backend->have_xfixes = true; p_delete(&reply); } } else { backend->have_xfixes = false; } atoms_init(backend); return backend; } void backend_destroy(backend_t *backend) { if (!backend) return; xcb_disconnect(backend->conn); backend->conn = nullptr; free(backend); } static bool detect_monitor_by_randr(const backend_t *backend, output_info_t **outputs, size_t *count) { xcb_prefetch_extension_data(backend->conn, &xcb_randr_id); const xcb_query_extension_reply_t *query = xcb_get_extension_data(backend->conn, &xcb_randr_id); if (!query || !query->present) return false; xcb_randr_query_version_cookie_t cookie = xcb_randr_query_version( backend->conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION); xcb_randr_query_version_reply_t *reply = xcb_randr_query_version_reply(backend->conn, cookie, nullptr); if (!reply) return false; /* xcb_randr_get_monitors 接口是 1.5 引入的 */ uint32_t major_version = reply->major_version; uint32_t minor_version = reply->minor_version; if (major_version < 1 || (major_version == 1 && minor_version < 5)) { p_delete(&reply); return false; } p_delete(&reply); xcb_randr_get_monitors_cookie_t monitors_cookie = xcb_randr_get_monitors(backend->conn, backend->screen->root, 1); xcb_randr_get_monitors_reply_t *monitors_reply = xcb_randr_get_monitors_reply(backend->conn, monitors_cookie, nullptr); if (!monitors_reply) { warn("RandR get monitor failed"); return false; } int len = xcb_randr_get_monitors_monitors_length(monitors_reply); if (len <= 0) { p_delete(&monitors_reply); return false; } output_info_t *output_list = p_new(output_info_t, len); xcb_randr_monitor_info_iterator_t iter = xcb_randr_get_monitors_monitors_iterator(monitors_reply); for (int i = 0; iter.rem; xcb_randr_monitor_info_next(&iter), i++) { output_info_t *output = &output_list[i]; output->geometry.x = iter.data->x; output->geometry.y = iter.data->y; output->geometry.width = iter.data->width; output->geometry.height = iter.data->height; xcb_get_atom_name_cookie_t name_cookie = xcb_get_atom_name_unchecked(backend->conn, iter.data->name); xcb_get_atom_name_reply_t *name_reply = xcb_get_atom_name_reply(backend->conn, name_cookie, nullptr); if (name_reply) { char *name = xcb_get_atom_name_name(name_reply); int length = xcb_get_atom_name_name_length(name_reply); output->name = p_strndup(name, length); p_delete(&name_reply); } } p_delete(&monitors_reply); if (count) *count = (size_t)len; if (outputs) { *outputs = output_list; } else { for (size_t i = 0; i < (size_t)len; i++) p_delete(&output_list[i].name); p_delete(&output_list); } return true; } static bool detect_monitor_by_xinerama(const backend_t *backend, output_info_t **outputs, size_t *count) { xcb_prefetch_extension_data(backend->conn, &xcb_xinerama_id); const xcb_query_extension_reply_t *query = xcb_get_extension_data(backend->conn, &xcb_xinerama_id); if (!query || !query->present) return false; xcb_xinerama_query_version_cookie_t cookie = xcb_xinerama_query_version( backend->conn, XCB_XINERAMA_MAJOR_VERSION, XCB_XINERAMA_MINOR_VERSION); xcb_xinerama_query_version_reply_t *version_reply = xcb_xinerama_query_version_reply(backend->conn, cookie, nullptr); if (!version_reply) return false; p_delete(&version_reply); xcb_xinerama_is_active_cookie_t active_cookie = xcb_xinerama_is_active(backend->conn); xcb_xinerama_is_active_reply_t *active_reply = xcb_xinerama_is_active_reply(backend->conn, active_cookie, nullptr); bool active = active_reply && active_reply->state; if (active_reply) p_delete(&active_reply); if (!active) return false; xcb_xinerama_query_screens_cookie_t screens_cookie = xcb_xinerama_query_screens(backend->conn); xcb_xinerama_query_screens_reply_t *screens_reply = xcb_xinerama_query_screens_reply(backend->conn, screens_cookie, nullptr); if (!screens_reply) return false; int len = xcb_xinerama_query_screens_screen_info_length(screens_reply); if (len <= 0) { p_delete(&screens_reply); return false; } xcb_xinerama_screen_info_t *screen_info = xcb_xinerama_query_screens_screen_info(screens_reply); if (!screen_info) { p_delete(&screens_reply); return false; } output_info_t *output_list = p_new(output_info_t, len); for (int i = 0; i < len; i++) { output_info_t *output = &output_list[i]; output->geometry.x = screen_info[i].x_org; output->geometry.y = screen_info[i].y_org; output->geometry.width = screen_info[i].width; output->geometry.height = screen_info[i].height; } p_delete(&screens_reply); if (count) *count = (size_t)len; if (outputs) { *outputs = output_list; } else { for (size_t i = 0; i < (size_t)len; i++) p_delete(&output_list[i].name); p_delete(&output_list); } return true; } backend_detect_t *backend_detect(backend_t *backend) { output_info_t *outputs = nullptr; size_t count = 0; if (detect_monitor_by_randr(backend, &outputs, &count)) { backend_detect_t *detect = output_remove_duplication(outputs, count); p_delete(&outputs); return detect; } if (detect_monitor_by_xinerama(backend, &outputs, &count)) { backend_detect_t *detect = output_remove_duplication(outputs, count); p_delete(&outputs); return detect; } output_info_t *output = p_new(output_info_t, 1); output->geometry.x = 0; output->geometry.y = 0; output->geometry.width = backend->screen->width_in_pixels; output->geometry.height = backend->screen->height_in_pixels; backend_detect_t *detect = p_new(backend_detect_t, 1); detect->outputs = output; detect->output_count = 1; return detect; } void backend_detect_destroy(backend_detect_t *detect) { if (!detect) return; for (size_t i = 0; i < detect->output_count; i++) { p_delete(&detect->outputs[i].name); } p_delete(&detect->outputs); free(detect); }