#include "wm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "app.h" #include "atoms-extern.h" #include "atoms.h" #include "backtrace.h" #include "base.h" #include "buffer.h" #include "client.h" #include "color.h" #include "config.h" #include "default_config.h" #include "event.h" #include "image.h" #include "monitor.h" #include "status.h" #include "text.h" #include "tray.h" #include "types.h" #include "utils.h" #include "wallpaper.h" #include "xcursor.h" #include "xkb.h" #include "xres.h" #include "xwindow.h" static void wm_setup_signal(void); static void wm_check_other_wm(void); static void wm_check_xcb_extensions(void); static void wm_detect_monitor(void); static void wm_scan_clients(void); static void wm_clean(void); static void wm_setup(void); static void wm_init_wallpaper(void); static void wm_get_xres_config(void); static void wm_init_color_set(void); static void wm_setup_keybindings(void); static void wm_update_status(status_t *status); static void wm_run_autostart(const char *const commands[]); static void run_once(const char *command); static bool command_already_running(const char *command); static gboolean clear_ignore_enter_notify(gpointer data); wm_t wm; /** argv used to run wm */ static char **cmd_argv; static gboolean restart_on_signal(gpointer data) { wm_restart(); return TRUE; } static gboolean exit_on_signal(gpointer data) { g_main_loop_quit(wm.loop); return TRUE; } static void signal_fatal(int signal_number) { buffer_t buffer; backtrace_get(&buffer); fatal("signal %d, dumping backtrace\n%s", signal_number, buffer.s); } static guint sources[3] = {0}; static gboolean clear_ignore_enter_notify(gpointer data) { wm.ignore_enter_notify = false; wm.ignored_enter_notify_point = (point_t){0}; wm.clear_ignore_enter_notify_source = 0; return G_SOURCE_REMOVE; } void wm_ignore_enter_notify_at_point(point_t point) { wm.ignore_enter_notify = true; wm.ignored_enter_notify_point = point; if (wm.clear_ignore_enter_notify_source) { g_source_remove(wm.clear_ignore_enter_notify_source); } wm.clear_ignore_enter_notify_source = g_idle_add(clear_ignore_enter_notify, nullptr); } bool wm_should_ignore_enter_notify(const xcb_enter_notify_event_t *ev) { if (!wm.ignore_enter_notify) return false; if (ev->root_x != wm.ignored_enter_notify_point.x || ev->root_y != wm.ignored_enter_notify_point.y) { return false; } if (wm.clear_ignore_enter_notify_source) { g_source_remove(wm.clear_ignore_enter_notify_source); wm.clear_ignore_enter_notify_source = 0; } wm.ignore_enter_notify = false; wm.ignored_enter_notify_point = (point_t){0}; return true; } void wm_setup_signal(void) { sources[0] = g_unix_signal_add(SIGINT, exit_on_signal, nullptr); sources[1] = g_unix_signal_add(SIGTERM, exit_on_signal, nullptr); sources[2] = g_unix_signal_add(SIGHUP, restart_on_signal, nullptr); struct sigaction sa = {.sa_handler = signal_fatal, .sa_flags = SA_RESETHAND}; sigemptyset(&sa.sa_mask); sigaction(SIGABRT, &sa, 0); sigaction(SIGBUS, &sa, 0); sigaction(SIGFPE, &sa, 0); sigaction(SIGILL, &sa, 0); sigaction(SIGSEGV, &sa, 0); signal(SIGPIPE, SIG_IGN); } void wm_check_other_wm(void) { /* connect X server */ int default_screen; xcb_connection_t *conn = xcb_connect(nullptr, &default_screen); int xcb_conn_error = xcb_connection_has_error(conn); if (xcb_conn_error) fatal("cannot open display (error %d)", xcb_conn_error); xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen); xcb_window_t root = screen->root; /* check other window manager running */ 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)"); } wm.xcb_conn = conn; wm.default_screen = default_screen; wm.screen = screen; } void wm_check_xcb_extensions(void) { xcb_prefetch_extension_data(wm.xcb_conn, &xcb_xfixes_id); xcb_prefetch_extension_data(wm.xcb_conn, &xcb_xinerama_id); xcb_prefetch_extension_data(wm.xcb_conn, &xcb_randr_id); const xcb_query_extension_reply_t *query; query = xcb_get_extension_data(wm.xcb_conn, &xcb_xfixes_id); wm.have_xfixes = query && query->present; query = xcb_get_extension_data(wm.xcb_conn, &xcb_xinerama_id); wm.have_xinerama = query && query->present; query = xcb_get_extension_data(wm.xcb_conn, &xcb_randr_id); wm.have_randr = query && query->present; } static inline int32_t right(area_t a) { return (int32_t)a.x + (int32_t)a.width; } static inline int32_t bottom(area_t a) { return (int32_t)a.y + (int32_t)a.height; } static inline bool fully_contains(area_t outer, area_t inner) { return inner.x >= outer.x && inner.y >= outer.y && right(inner) <= right(outer) && bottom(inner) <= bottom(outer); } static inline void monitor_remove_duplication(monitor_t **monitor_list) { if (!monitor_list || !*monitor_list) return; int32_t count = 0; for (monitor_t *m = *monitor_list; m; m = m->next) count++; if (count <= 1) return; monitor_t **nodes = p_new(monitor_t *, count); bool *remove = p_new(bool, count); size_t index = 0; for (monitor_t *m = *monitor_list; m; m = m->next) nodes[index++] = m; for (int32_t i = count - 1; i >= 0; i--) { if (remove[i]) continue; for (int32_t j = i - 1; j >= 0; j--) { if (remove[j]) continue; if (fully_contains(nodes[j]->geometry, nodes[i]->geometry)) { remove[i] = true; } else if (fully_contains(nodes[i]->geometry, nodes[j]->geometry)) { remove[j] = true; } } } monitor_t *head = nullptr; monitor_t *tail = nullptr; for (int32_t i = 0; i < count; i++) { monitor_t *node = nodes[i]; if (remove[i]) { p_delete(&node->name); p_delete(&node); continue; } if (tail) { tail->next = node; } else { head = node; } node->next = nullptr; tail = node; } if (tail) tail->next = nullptr; *monitor_list = head; p_delete(&nodes); p_delete(&remove); } static bool wm_detect_monitor_by_randr(void) { xcb_randr_get_monitors_cookie_t monitors_cookie = xcb_randr_get_monitors(wm.xcb_conn, wm.screen->root, 1); xcb_randr_get_monitors_reply_t *monitors_reply = xcb_randr_get_monitors_reply(wm.xcb_conn, monitors_cookie, nullptr); if (monitors_reply == nullptr) { warn("RandR get monitor failed"); return false; } monitor_t *prev_monitor = nullptr; xcb_randr_monitor_info_iterator_t monitor_iter = xcb_randr_get_monitors_monitors_iterator(monitors_reply); for (; monitor_iter.rem; xcb_randr_monitor_info_next(&monitor_iter)) { monitor_t *monitor = p_new(monitor_t, 1); monitor->geometry.x = monitor_iter.data->x; monitor->geometry.y = monitor_iter.data->y; monitor->geometry.width = monitor_iter.data->width; monitor->geometry.height = monitor_iter.data->height; xcb_get_atom_name_cookie_t name_cookie = xcb_get_atom_name_unchecked(wm.xcb_conn, monitor_iter.data->name); xcb_get_atom_name_reply_t *name_reply = xcb_get_atom_name_reply(wm.xcb_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); monitor->name = memcpy(p_new(char, length + 1), name, length); monitor->name[length] = '\0'; p_delete(&name_reply); } else { monitor->name = strdup("unknown"); } if (prev_monitor) { prev_monitor->next = monitor; } else { wm.monitor_list = monitor; wm.current_monitor = monitor; } prev_monitor = monitor; } p_delete(&monitors_reply); monitor_remove_duplication(&wm.monitor_list); return true; } static bool wm_detect_monitor_by_xinerama(void) { xcb_xinerama_is_active_cookie_t active_cookie = xcb_xinerama_is_active(wm.xcb_conn); xcb_xinerama_is_active_reply_t *active_reply = xcb_xinerama_is_active_reply(wm.xcb_conn, active_cookie, nullptr); if (!active_reply || !active_reply->state) { p_delete(&active_reply); return false; } xcb_xinerama_query_screens_cookie_t screens_cookie = xcb_xinerama_query_screens(wm.xcb_conn); xcb_xinerama_query_screens_reply_t *screens_reply = xcb_xinerama_query_screens_reply(wm.xcb_conn, screens_cookie, nullptr); if (screens_reply == nullptr) return false; int count = xcb_xinerama_query_screens_screen_info_length(screens_reply); if (count <= 0) { p_delete(&screens_reply); return false; } xcb_xinerama_screen_info_t *screen_info = xcb_xinerama_query_screens_screen_info(screens_reply); monitor_t *prev_monitor = nullptr; for (int i = 0; i < count; i++) { monitor_t *monitor = p_new(monitor_t, 1); monitor->geometry.x = screen_info[i].x_org; monitor->geometry.y = screen_info[i].y_org; monitor->geometry.width = screen_info[i].width; monitor->geometry.height = screen_info[i].height; if (prev_monitor) { prev_monitor->next = monitor; } else { wm.monitor_list = monitor; wm.current_monitor = monitor; } prev_monitor = monitor; } p_delete(&screens_reply); monitor_remove_duplication(&wm.monitor_list); return true; } void wm_detect_monitor(void) { if (wm.have_randr && wm_detect_monitor_by_randr()) return; if (wm.have_xinerama && wm_detect_monitor_by_xinerama()) return; if (wm.screen == nullptr) fatal("cannot detect monitor info"); monitor_t *monitor = p_new(monitor_t, 1); monitor->geometry.x = 0; monitor->geometry.y = 0; monitor->geometry.width = wm.screen->width_in_pixels; monitor->geometry.height = wm.screen->height_in_pixels; wm.monitor_list = monitor; wm.current_monitor = monitor; } typedef struct window_list_t { xcb_window_t *normal_list; xcb_window_t *transient_list; uint32_t normal_count; uint32_t transient_count; } window_list_t; void wm_scan_clients(void) { xcb_query_tree_cookie_t cookie = xcb_query_tree(wm.xcb_conn, wm.screen->root); xcb_query_tree_reply_t *tree_reply = xcb_query_tree_reply(wm.xcb_conn, cookie, nullptr); if (!tree_reply) return; xcb_window_t *list = xcb_query_tree_children(tree_reply); int len = xcb_query_tree_children_length(tree_reply); if (!len) { p_delete(&tree_reply); return; } xcb_get_window_attributes_reply_t *wa_reply = nullptr; xcb_get_geometry_reply_t *geo_reply = nullptr; for (int i = 0; i < len; i++) { xcb_window_t window = list[i]; wa_reply = xwindow_get_attributes_reply(window); if (!wa_reply) continue; uint8_t override_redirect = wa_reply->override_redirect; uint8_t map_state = wa_reply->map_state; p_delete(&wa_reply); if (override_redirect || xwindow_get_transient_for(window)) continue; geo_reply = xwindow_get_geometry_reply(window); if (!geo_reply) continue; if (map_state == XCB_MAP_STATE_VIEWABLE || xwindow_get_state(window) == XCB_ICCCM_WM_STATE_ICONIC) { client_manage(window, geo_reply); } p_delete(&geo_reply); } for (int i = 0; i < len; i++) { xcb_window_t window = list[i]; wa_reply = xwindow_get_attributes_reply(window); if (!wa_reply) continue; uint8_t map_state = wa_reply->map_state; p_delete(&wa_reply); geo_reply = xwindow_get_geometry_reply(window); if (!geo_reply) continue; if (xwindow_get_transient_for(window) && (map_state == XCB_MAP_STATE_VIEWABLE || xwindow_get_state(window) == XCB_ICCCM_WM_STATE_ICONIC)) { client_manage(window, geo_reply); } p_delete(&geo_reply); } p_delete(&tree_reply); } void wm_clean(void) { wallpaper_clean(wm.wallpaper); wm.wallpaper = nullptr; clean_status(); text_clean_pango_layout(); image_cache_clean(); tray_cleanup(); { client_t *c = wm.client_stack_list; while (c) { client_t *next_client = c->stack_next; client_unmanage(c, false); c = next_client; } } monitor_clean(wm.monitor_list); for (int i = 0; i < countof(sources); i++) { guint source_id = sources[i]; if (source_id) g_source_remove(source_id); } if (wm.clear_ignore_enter_notify_source) { g_source_remove(wm.clear_ignore_enter_notify_source); wm.clear_ignore_enter_notify_source = 0; } wm.ignore_enter_notify = false; wm.ignored_enter_notify_point = (point_t){0}; xcb_delete_property(wm.xcb_conn, wm.screen->root, _NET_ACTIVE_WINDOW); xcb_delete_property(wm.xcb_conn, wm.screen->root, _NET_SUPPORTING_WM_CHECK); xcb_aux_sync(wm.xcb_conn); xcb_destroy_window(wm.xcb_conn, wm.wm_check_window); wm.wm_check_window = XCB_WINDOW_NONE; xcursor_clean(); xkb_free(); xcb_ungrab_key(wm.xcb_conn, XCB_GRAB_ANY, wm.screen->root, modifier_any); xcb_key_symbols_free(wm.key_symbols); xcb_aux_sync(wm.xcb_conn); xcb_disconnect(wm.xcb_conn); } static void wm_setup(void) { wm.config = init_config(); if (!wm.config) fatal("cannot init config"); wm.layout_list = layout_list; wm.layout_count = (uint16_t)countof(layout_list); wm.rules = rules; wm.rules_count = countof(rules); wm_get_xres_config(); wm_init_color_set(); { int font_height = text_init_pango_layout(wm.font_family, wm.font_size, wm.dpi); wm.bar_height = (uint16_t)font_height + 2 * wm.padding.bar_y; logger("font height: %d, bar height: %u\n", font_height, wm.bar_height); } uint32_t tag_count = 0; for (monitor_t *m = wm.monitor_list; m; m = m->next) { tag_count += monitor_initialize_tag(m, (const char **)tags, tag_count); monitor_init_bar(m); monitor_draw_bar(m); } uint32_t mask = XCB_CW_EVENT_MASK | XCB_CW_CURSOR | XCB_CW_BACK_PIXEL; xcb_params_cw_t params = { .back_pixel = wm.screen->black_pixel, .cursor = xcursor_get_xcb_cursor(cursor_normal), .event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_KEY_PRESS, }; xcb_aux_change_window_attributes(wm.xcb_conn, wm.screen->root, mask, ¶ms); wm_setup_keybindings(); atoms_init(wm.xcb_conn); tray_init(); { wm.wm_check_window = xcb_generate_id(wm.xcb_conn); xcb_create_window(wm.xcb_conn, wm.screen->root_depth, wm.wm_check_window, wm.screen->root, -1, -1, 1, 1, 0, XCB_COPY_FROM_PARENT, wm.screen->root_visual, XCB_NONE, nullptr); xwindow_set_class_instance(wm.wm_check_window); #define NAME APP_NAME "_wm_check" xwindow_set_name_static(wm.wm_check_window, NAME); const void *data = &wm.wm_check_window; xcb_atom_t type = XCB_ATOM_WINDOW; uint8_t mode = XCB_PROP_MODE_REPLACE; xcb_change_property(wm.xcb_conn, mode, wm.wm_check_window, _NET_WM_NAME, UTF8_STRING, 8, sizeof(NAME) - 1, NAME); xcb_change_property(wm.xcb_conn, mode, wm.wm_check_window, _NET_SUPPORTING_WM_CHECK, type, 32, 1, data); xcb_change_property(wm.xcb_conn, mode, wm.screen->root, _NET_SUPPORTING_WM_CHECK, type, 32, 1, data); #undef NAME } xkb_init(); xcb_flush(wm.xcb_conn); } static void wm_init_wallpaper(void) { wallpaper_config_t config = { .interval = wallpaper_interval, .path_count = countof(wallpapers), .paths = (char **)wallpapers, .screen = wm.screen, .conn = wm.xcb_conn, .monitors = wm.monitor_list, }; wm.wallpaper = wallpaper_init(config); } static void wm_get_xres_config(void) { xres_init_xrm_db(); wm.dpi = default_dpi; wm.font_size = font_size; xres_get_uint32("Xft.dpi", (uint32_t *)&wm.dpi); xres_get_uint32(APP_NAME ".font_size", (uint32_t *)&wm.font_size); xres_get_string(APP_NAME ".font_family", &wm.font_family, font_family); wm.border_width = border_width; xres_get_uint32(APP_NAME ".border_width", (uint32_t *)&wm.border_width); wm.padding.bar_y = bar_y_padding; wm.padding.tag_x = tag_x_padding; #define NAME APP_NAME ".padding." xres_get_uint32(NAME "bar_y", (uint32_t *)&wm.padding.bar_y); xres_get_uint32(NAME "tag_x", (uint32_t *)&wm.padding.tag_x); #undef NAME xres_clean(); } void wm_init_color_set(void) { color_parse(bar_bg, &wm.color_set.bar_bg); color_parse(tag_bg, &wm.color_set.tag_bg); color_parse(active_tag_bg, &wm.color_set.active_tag_bg); color_parse(tag_color, &wm.color_set.tag_color); color_parse(active_tag_color, &wm.color_set.active_tag_color); color_parse(border_color, &wm.color_set.border_color); color_parse(active_border_color, &wm.color_set.active_border_color); } void wm_setup_keybindings(void) { if (wm.key_symbols) { xcb_key_symbols_free(wm.key_symbols); p_delete(&wm.key_symbols); } wm.key_symbols = xcb_key_symbols_alloc(wm.xcb_conn); xwindow_grab_keys(wm.screen->root, key_list, countof(key_list)); } void wm_update_status(status_t *status) { wm.status = status; for (monitor_t *m = wm.monitor_list; m; m = m->next) monitor_draw_bar(m); xcb_flush(wm.xcb_conn); } /** * @brief 允许自启动程序 * @param commands 命令字符串数组(以 nullptr 结尾) */ void wm_run_autostart(const char *const commands[]) { for (int i = 0; commands[i]; i++) run_once(commands[i]); } void run_once(const char *command) { if (command_already_running(command)) return; g_spawn_command_line_async(command, nullptr); } bool command_already_running(const char *command) { char shell_cmd[1024]; const char *user = getenv("USER"); snprintf(shell_cmd, sizeof(shell_cmd), "pgrep -u %s -fx '%s'", user, command); char *output = nullptr; char *error = nullptr; g_spawn_command_line_sync(shell_cmd, &output, &error, nullptr, nullptr); bool result = (error == nullptr || !strlen(error)) && (output && strlen(output)); p_delete(&output); p_delete(&error); return result; } void wm_restart(void) { wm.need_restart = true; if (g_main_loop_is_running(wm.loop)) { g_main_loop_quit(wm.loop); } } void wm_quit(void) { wm.need_restart = false; if (g_main_loop_is_running(wm.loop)) { g_main_loop_quit(wm.loop); } } void wm_restack_clients(void) { xcb_window_t sibling_window = XCB_WINDOW_NONE; for (client_t *c = wm.client_stack_list; c; c = c->stack_next) { uint16_t mask = XCB_CONFIG_WINDOW_STACK_MODE; xcb_params_configure_window_t params = {}; if (sibling_window == XCB_WINDOW_NONE) { mask = XCB_CONFIG_WINDOW_STACK_MODE; params.stack_mode = XCB_STACK_MODE_ABOVE; } else { mask = XCB_CONFIG_WINDOW_STACK_MODE | XCB_CONFIG_WINDOW_SIBLING; params.stack_mode = XCB_STACK_MODE_BELOW; params.sibling = sibling_window; } sibling_window = c->window; xcb_aux_configure_window(wm.xcb_conn, c->window, mask, ¶ms); } xcb_flush(wm.xcb_conn); } void wm_set_current_monitor(monitor_t *monitor, bool restore_cursor) { if (!monitor || wm.current_monitor == monitor) return; point_t point = xcursor_query_pointer_position(); monitor_t *point_monitor = wm_get_monitor_by_point(point); if (point_monitor == wm.current_monitor) { point_monitor->cursor_position = point; point_monitor->position_inited = true; } wm.current_monitor = monitor; if (restore_cursor) monitor_restore_cursor_point(monitor); } static inline int intersect(area_t area, monitor_t *m) { return MAX(0, MIN(area.x + area.width, m->geometry.x + m->geometry.width) - MAX(area.x, m->geometry.x)) * MAX(0, MIN(area.y + area.height, m->geometry.y + m->geometry.height) - MAX(area.y, m->geometry.y)); } monitor_t *__attribute__((returns_nonnull)) wm_get_monitor_by_area( area_t area) { if (wm.current_monitor == nullptr) { fatal("wm_get_monitor_by_area: current monitor is nullptr"); } monitor_t *monitor = wm.current_monitor; int temp_area, max_area = 0; for (monitor_t *m = wm.monitor_list; m; m = m->next) { if ((temp_area = intersect(area, m)) > max_area) { max_area = temp_area; monitor = m; } } return monitor; } monitor_t *__attribute__((returns_nonnull)) wm_get_monitor_by_point( point_t point) { area_t area = {.x = point.x, .y = point.y, .width = 1, .height = 1}; return wm_get_monitor_by_area(area); } monitor_t *wm_get_monitor_by_window(xcb_window_t window) { if (window == wm.screen->root) { xcb_query_pointer_cookie_t cookie = xcb_query_pointer(wm.xcb_conn, window); xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(wm.xcb_conn, cookie, nullptr); if (reply) { area_t area = {reply->root_x, reply->root_y, 1, 1}; p_delete(&reply); return wm_get_monitor_by_area(area); } } for (monitor_t *m = wm.monitor_list; m; m = m->next) { if (window == m->bar_window) return m; } client_t *c = client_get_by_window(window); if (c) return c->monitor; return wm.current_monitor; } monitor_t *wm_get_next_monitor(monitor_t *monitor) { monitor_t *next_monitor = monitor->next; if (next_monitor == nullptr) next_monitor = wm.monitor_list; return next_monitor; } static void debug_show_monitor_list(void) { logger("\n========================== monitors ==========================\n"); for (monitor_t *m = wm.monitor_list; m; m = m->next) { logger("x: %4d, y: %4d, width: %4u, height: %4u -> monitor[%s]\n", m->geometry.x, m->geometry.y, m->geometry.width, m->geometry.height, m->name); logger("x: %4d, y: %4d, width: %4u, height: %4u -> workarea[%s]: %u\n", m->workarea.x, m->workarea.y, m->workarea.width, m->workarea.height, m->name, wm.bar_height); logger("tag extend: [%d, %d]\n", m->tag_extent.start, m->tag_extent.end); for (const tag_t *tag = m->tag_list; tag; tag = tag->next) { logger("tag[%u]: %4u, \"%s\", [%d, %d]\n", tag->index, tag->mask, tag->name, tag->bar_extent.start, tag->bar_extent.end); } } } int main(int argc, char *argv[]) { p_clear(&wm, 1); cmd_argv = argv; wm_setup_signal(); wm_check_other_wm(); wm_check_xcb_extensions(); wm_detect_monitor(); wm_setup(); wm_init_wallpaper(); setup_event_loop(); wm_scan_clients(); debug_show_monitor_list(); wm_run_autostart(autostart_list); if (wm.loop == nullptr) { wm.loop = g_main_loop_new(nullptr, FALSE); init_status(g_main_loop_get_context(wm.loop), wm_update_status); g_main_loop_run(wm.loop); } g_main_loop_unref(wm.loop); wm.loop = nullptr; wm_clean(); if (wm.need_restart) { execvp(cmd_argv[0], cmd_argv); fatal("execv() failed: %s", strerror(errno)); } return EXIT_SUCCESS; }