#include #include #include #include #include #include #include #include #include #include #include #include #include #include "action.h" #include "app.h" #include "config.h" #include "event-adapter.h" #include "layout.h" #include "renderer.h" #include "status.h" #include "types.h" #include "utils.h" #include "xcb.h" static void init_monitors(void); static char **get_monitor_tags(uint32_t monitor_count, uint32_t monitor_index); static void init_wallpaper(void); static gboolean update_wallpaper(gpointer user_data); static void set_wallpaper_by_index(uint32_t index, wallpaper_config_t *config, cairo_t *cr); static wallpaper_config_t *parse_wallpaper_config(void); static void free_wallpaper_config(wallpaper_config_t *wallpaper_config); static void init_icons(void); static void clean_icons(void); static void init_color_set(void); static void parse_color(const char *hex, color_t *color); static void init_monitor_bar(void); static void get_xresource_config(void); static void update_status(status_t *status); static void draw_all_monitor_bars(void); static void draw_monitor_bar(monitor_t *monitor); static void init_xcb_event_loop(void); static void xcb_event_handler(generic_event_t *event, void *user_data); static void key_press(key_press_event_t *event); static void configure_request(configure_request_event_t *event); static void map_request(map_request_event_t *event); static void unmap_notify(unmap_notify_event_t *event); static void destroy_notify(destroy_notify_event_t *event); static void expose(expose_event_t *event); static void motion_notify(motion_notify_event_t *event); static void enter_notify(enter_notify_event_t *event); static void focus_in(focus_in_event_t *event); static void property_notify(property_notify_event_t *event); static void client_message(client_message_event_t *event); static void manage_window(xcb_window_t window); static void unmanage_client(client_t *client, bool destroyed); static void update_client_list(void); static void update_client_name(client_t *client); static void apply_rules(client_t *client); static uint32_t get_tags_mask_of_monitor(monitor_t *monitor); static void attach_client(client_t *client); static void detach_client(client_t *client); static void attach_stack_client(client_t *client); static void detach_stack_client(client_t *client); static void arrange(monitor_t *monitor); static void show_hide_client(client_t *client); static void restack(monitor_t *monitor); static void focus(client_t *client); static void unfocus(client_t *client, bool set_focus); static void set_client_fullscreen(client_t *client, bool fullscreen); static void apply_monitor_layout(monitor_t *monitor); static client_t *get_client_of_window(xcb_window_t window); static monitor_t *get_monitor_of_window(xcb_window_t window); static monitor_t *get_monitor_by_direction(bool forward); static void send_client_to_monitor(client_t *client, monitor_t *monitor); static void set_client_tag_prop(client_t *client); static bool restore_client_tag(client_t *client); static client_t *get_next_client_with_class(const char *class); static gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition, gpointer userdata); static void cleanup(bool will_restart); static monitor_t *monitors = NULL; static monitor_t *current_monitor = NULL; static cairo_t *wallpaper_cr = NULL; static wallpaper_config_t *wallpaper_config = NULL; static icons_t *icons = NULL; static color_set_t *color_set = NULL; static status_t *status = NULL; static GMainLoop *loop = NULL; static event_adapter_t *adapter = NULL; static char *font_family = NULL; static uint32_t font_size = default_font_size; static uint32_t dpi = dpi_fallback; static uint32_t bar_y_pad = bar_y_padding; static uint32_t tag_x_pad = tag_x_padding; static uint32_t layout_symbol_x_pad = layout_symbol_x_padding; static uint32_t client_name_x_pad = client_name_x_padding; static uint32_t status_x_pad = status_x_padding; static uint32_t status_icon_pad = status_icon_padding; static uint32_t bar_height = 0; static uint32_t border_px = border_width; static bool need_restart = false; void spawn(const user_action_arg_t *arg) { g_spawn_command_line_async(arg->ptr, NULL); } void quit(const user_action_arg_t *arg) { need_restart = arg->b; g_main_loop_quit(loop); } void select_tag(const user_action_arg_t *arg) { uint32_t tags_mask = get_tags_mask_of_monitor(current_monitor); uint32_t target_tag = arg->ui & tags_mask; if (!target_tag || current_monitor->selected_tags == target_tag) return; current_monitor->selected_tags = target_tag; focus(NULL); arrange(current_monitor); } void kill_client(const user_action_arg_t *arg) { client_t *client = current_monitor->selected_client; if (!client) return; if (!send_event(client->window, get_xcb_atom(atom_wm_delete))) { kill_window(client->window); } } void focus_stack(const user_action_arg_t *arg) { if (!current_monitor->selected_client) return; bool forward = arg->b; client_t *client = NULL; if (forward) { for (client = current_monitor->selected_client->next; client && !CLIENT_VISIBLE(client); client = client->next); if (!client) { for (client = current_monitor->clients; client && !CLIENT_VISIBLE(client); client = client->next); } } else { client_t *c = NULL; for (c = current_monitor->clients; c != current_monitor->selected_client; c = c->next) { if (CLIENT_VISIBLE(c)) client = c; } if (!client) { for (; c; c = c->next) { if (CLIENT_VISIBLE(c)) client = c; } } } if (client) { focus(client); restack(current_monitor); } } void focus_monitor(const user_action_arg_t *arg) { if (!monitors->next) return; bool forward = arg->b; monitor_t *monitor = get_monitor_by_direction(forward); if (monitor == current_monitor) return; unfocus(current_monitor->selected_client, false); current_monitor = monitor; focus(NULL); /* TODO: 调整鼠标光标 */ } void raise_or_run(const user_action_arg_t *arg) { const char *class = ((const char **)arg->ptr)[0]; client_t *client = get_next_client_with_class(class); if (client && client == current_monitor->selected_client) return; user_action_arg_t argument; if (client) { logger("==== raise client: %s, window: 0x%x\n", client->name, client->window); current_monitor = client->monitor; argument.ui = client->tags; select_tag(&argument); focus(client); raise_window(client->window); return; } const char *command = ((const char **)arg->ptr)[1]; argument.ptr = command; spawn(&argument); return; } void toggle_mute(const user_action_arg_t *arg) { toggle_pulse_mute(); } void change_volume(const user_action_arg_t *arg) { change_pulse_volume(arg->i); } void move_to_monitor(const user_action_arg_t *arg) { if (!current_monitor->selected_client || !monitors->next) return; bool forward = arg->b; monitor_t *monitor = get_monitor_by_direction(forward); send_client_to_monitor(current_monitor->selected_client, monitor); } void move_to_tag(const user_action_arg_t *arg) { if (!current_monitor || !current_monitor->selected_client) return; uint32_t tag_mask = 0; for (uint32_t i = 0; i < g_strv_length(current_monitor->tags); i++) { tag_mask |= 1 << i; } uint32_t target_tag = arg->ui & tag_mask; if (!target_tag) return; client_t *client = current_monitor->selected_client; client->tags = target_tag; set_client_tag_prop(client); focus(NULL); arrange(current_monitor); } /* 调试函数,开发完成后删除 */ static void print_monitor_list_info(monitor_t *monitor_list) { logger("========================= monitor info =========================\n"); for (monitor_t *m = monitor_list; m; m = m->next) { logger("========== mon[%u]:\n", m->index); logger("x: %d, y: %d, width: %u, height: %u\n", m->monitor_x, m->monitor_y, m->monitor_width, m->monitor_height); logger("mon[%u] tags: ", m->index); for (int i = 0; m->tags[i]; i++) { logger("%s%s", i > 0 ? ", " : "", m->tags[i]); } logger("\n"); } logger("======================= monitor info end =======================\n"); } static void print_color(const char *prompt, const color_t *color) { const char *p = strlen(prompt) ? " " : ""; logger("%s%srgba: #%08x, argb: #%08x\n", prompt, p, color->rgba, color->argb); } static void print_color_set(color_set_t *set) { print_color("== bar bg:", &set->bar_bg); print_color("== tag bg:", &set->tag_bg); print_color("== active tag bg:", &set->active_tag_bg); print_color("== tag color:", &set->tag_color); print_color("== active tag color:", &set->active_tag_color); print_color("== layout bg:", &set->layout_color); print_color("== client name bg:", &set->client_name_bg); print_color("== client name color:", &set->client_name_color); print_color("== active client name bg:", &set->active_client_name_bg); print_color("== active client name color:", &set->active_client_name_color); print_color("== status net recv color:", &set->status_net_recv_color); print_color("== status net send color:", &set->status_net_send_color); print_color("== status volume color:", &set->status_volume_color); print_color("== status memory color:", &set->status_memory_color); print_color("== status cpu color:", &set->status_cpu_color); print_color("== status datetime color:", &set->status_datetime_color); print_color("== border color:", &set->border_color); print_color("== active border color:", &set->active_border_color); } static void print_wallpaper_config(wallpaper_config_t *config) { if (!config) { logger("no wallpaper\n"); return; } logger("wallpaper count: %u\n", config->count); for (uint32_t i = 0; i < config->count; i++) { logger("wallpaper[%02u]: %s\n", i, config->path_list[i]); } } static void print_window_list(window_list_t *window_list) { if (!window_list) { logger("no window\n"); return; } for (uint32_t i = 0; i < window_list->normal_count; i++) { logger("normal window[%u]: 0x%x\n", i, window_list->normal_list[i]); } for (uint32_t i = 0; i < window_list->transient_count; i++) { logger("transient window[%u]: 0x%x\n", i, window_list->transient_list[i]); } } static char *bool2string(bool b) { return b ? "true" : "false"; } static void print_client(client_t *c) { if (!c) return; logger("======= client info ========\n"); logger("window: 0x%x, x: %d, y: %d, w: %u, h: %u, bw: %u\n", c->window, c->x, c->y, c->width, c->height, c->border_width); logger("floating: %s, fullscreen: %s, maximize: %s\n", bool2string(c->floating), bool2string(c->fullscreen), bool2string(c->maximize)); logger("tags: %b, monitor selected tags: %b\n", c->tags, c->monitor->selected_tags); logger("name: %s\n", c->name); logger("===== client info end ======\n"); } static void print_rule(const rule_t *r) { logger("==================== rule ====================\n"); logger("class: %s, instance: %s, title: %s\n", r->class, r->instance, r->title); logger("floating: %s, maximize: %s, fullscreen: %s, switch to tag: %s\n", bool2string(r->action.floating), bool2string(r->action.maximize), bool2string(r->action.fullscreen), bool2string(r->action.switch_to_tag)); for (uint32_t i = 0; r->tag && r->tag[i].tags; i++) { const rule_tag_t *t = &r->tag[i]; logger("monitor amount: %u, monitor index: %u, tags: %b\n", t->monitor_amount, t->monitor_index, t->tags); } logger("================== rule end ==================\n"); } /* 调试函数结束 */ void init_monitors(void) { monitor_rect_t *mr = detect_monitor_rect(); uint32_t i = 0; monitor_t *prev_monitor = NULL; while (mr) { monitor_t *m = ecalloc(1, sizeof(monitor_t)); m->monitor_x = mr->x; m->monitor_y = mr->y; m->monitor_width = mr->width; m->monitor_height = mr->height; m->index = i; m->layout = &layouts[0]; if (prev_monitor) { prev_monitor->next = m; } else { monitors = m; current_monitor = m; } prev_monitor = m; i++; monitor_rect_t *next_mr = mr->next; free(mr); mr = next_mr; } for (monitor_t *m = monitors; m; m = m->next) { m->tags = get_monitor_tags(i, m->index); m->selected_tags = 0x1; } } char **get_monitor_tags(uint32_t monitor_count, uint32_t monitor_index) { GStrvBuilder *builder = g_strv_builder_new(); const char *const *tags = NULL; /* 倒序遍历,达到后面的配置覆盖前面相同配置的效果 */ for (int i = LENGTH(monitor_tags) - 1; i >= 0; i--) { const char *const *const *config = monitor_tags[i]; uint32_t monitor_count_in_tags_config = 0; while (config[monitor_count_in_tags_config]) monitor_count_in_tags_config++; if (monitor_count_in_tags_config != monitor_count) continue; tags = config[monitor_index]; break; const char *const *const tags = config[monitor_index]; for (int j = 0; tags[j]; j++) g_strv_builder_add(builder, tags[j]); return g_strv_builder_unref_to_strv(builder); } if (tags == NULL) tags = default_monitor_tags; for (int i = 0; tags[i]; i++) g_strv_builder_add(builder, tags[i]); return g_strv_builder_unref_to_strv(builder); } void init_wallpaper(void) { bar_cairo_params_t *p = prepare_wallpaper_surface_params(); rect_size_t *screen_size = get_screen_size(); if (wallpaper_cr) cairo_destroy(wallpaper_cr); cairo_surface_t *surface = cairo_xcb_surface_create( p->conn, p->window, p->visual, screen_size->width, screen_size->height); free(p); free(screen_size); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { cairo_surface_destroy(surface); return; } cairo_t *cr = cairo_create(surface); if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) { cairo_destroy(cr); return; } wallpaper_cr = cr; cairo_surface_destroy(surface); cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_paint(cr); refresh_root_window(); wallpaper_config = parse_wallpaper_config(); if (!wallpaper_config) return; update_wallpaper(wallpaper_config); if (!wallpaper_interval) return; g_timeout_add_seconds(wallpaper_interval, update_wallpaper, wallpaper_config); } gboolean update_wallpaper(gpointer user_data) { wallpaper_config_t *wc = user_data; if (!wc || !wallpaper_cr) return false; static uint32_t index = 0; set_wallpaper_by_index(index, wc, wallpaper_cr); uint32_t monitor_count = 0; for (monitor_t *m = monitors; m; m = m->next) monitor_count++; /* 屏幕比壁纸多则不动态切换 */ if (!monitor_count || monitor_count >= wc->count) return false; if (monitor_count) { index += monitor_count; index %= wc->count; } return true; } void set_wallpaper_by_index(uint32_t index, wallpaper_config_t *config, cairo_t *cr) { rect_size_t *screen_size = get_screen_size(); uint32_t width = screen_size->width; uint32_t height = screen_size->height; free(screen_size); uint32_t *wallpaper = generate_wallpaper(config, monitors, index, width, height); if (!wallpaper) return; double start = get_time(); uint8_t *data = (uint8_t *)wallpaper; int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); cairo_surface_t *s = cairo_image_surface_create_for_data( data, CAIRO_FORMAT_ARGB32, width, height, stride); cairo_set_source_surface(cr, s, 0, 0); cairo_paint(cr); refresh_root_window(); free(wallpaper); double end = get_time(); logger("set wallpaper cost: %fs\n", end - start); } wallpaper_config_t *parse_wallpaper_config(void) { if (!LENGTH(wallpapers)) return NULL; wallpaper_config_t *wc = ecalloc(1, sizeof(wallpaper_config_t)); GStrvBuilder *builder = g_strv_builder_new(); for (int i = 0; i < LENGTH(wallpapers); i++) { wordexp_t p; if (wordexp(wallpapers[i], &p, 0)) continue; for (size_t i = 0; i < p.we_wordc; i++) { size_t len = strlen(p.we_wordv[i]) + 1; char *realpath = ecalloc(len, sizeof(char)); strncpy(realpath, p.we_wordv[i], len); g_strstrip(realpath); if (file_exist(realpath)) g_strv_builder_add(builder, realpath); free(realpath); } wordfree(&p); } wc->path_list = g_strv_builder_unref_to_strv(builder); wc->count = g_strv_length(wc->path_list); if (wc->count) return wc; free_wallpaper_config(wc); return NULL; } void free_wallpaper_config(wallpaper_config_t *wallpaper_config) { if (!wallpaper_config) return; g_strfreev(wallpaper_config->path_list); free(wallpaper_config); } void init_icons(void) { typedef struct { const char *img_path; icon_res_t *icon_res; } icon_path_surface_map_t; if (icons) return; icons = ecalloc(1, sizeof(icons_t)); icon_path_surface_map_t map[] = { {NET_DOWN_ICON, &icons->net_down}, {NET_UP_ICON, &icons->net_up}, {SPEAKER_ICON, &icons->volume}, {MEM_ICON, &icons->memory}, {CPU_ICON, &icons->cpu}, {CLOCK_ICON, &icons->clock}, }; for (int i = 0; i < LENGTH(map); i++) { const char *path = map[i].img_path; icon_res_t *res = map[i].icon_res; if (path && strlen(path)) { uint32_t *pixels = generate_icon_pixels(path, bar_height, bar_height, status_icon_pad); cairo_format_t format = CAIRO_FORMAT_ARGB32; int stride = cairo_format_stride_for_width(format, bar_height); cairo_surface_t *surface = cairo_image_surface_create_for_data( (uint8_t *)pixels, format, bar_height, bar_height, stride); if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) { res->pixels = pixels; res->surface = surface; } else { cairo_surface_destroy(surface); free(pixels); res->pixels = NULL; res->surface = NULL; } } } } void clean_icons(void) { if (!icons) return; icon_res_t icon_res_list[] = { icons->net_down, icons->net_up, icons->volume, icons->memory, icons->cpu, icons->clock, }; for (int i = 0; i < LENGTH(icon_res_list); i++) { icon_res_t res = icon_res_list[i]; if (res.surface) { cairo_surface_finish(res.surface); free(res.pixels); cairo_surface_destroy(res.surface); res.pixels = NULL; res.surface = NULL; } } free(icons); icons = NULL; } void init_color_set(void) { color_set = ecalloc(1, sizeof(color_set_t)); char *bar_background = NULL; char *tag_background = NULL; char *active_tag_background = NULL; char *tag = NULL; char *active_tag = NULL; char *layout = NULL; char *client_name_background = NULL; char *client_name = NULL; char *active_client_name_background = NULL; char *active_client_name = NULL; char *status_net_recv = NULL; char *status_net_send = NULL; char *status_volume = NULL; char *status_memory = NULL; char *status_cpu = NULL; char *status_datetime = NULL; char *border = NULL; char *active_border = NULL; get_xresource_string("zswm.color.bar_bg", &bar_background, bar_bg); get_xresource_string("zswm.color.tag_bg", &tag_background, tag_bg); get_xresource_string("zswm.color.active_tag_bg", &active_tag_background, active_tag_bg); get_xresource_string("zswm.color.tag", &tag, tag_color); get_xresource_string("zswm.color.active_tag", &active_tag, active_tag_color); get_xresource_string("zswm.color.layout", &layout, layout_color); get_xresource_string("zswm.color.client_name_bg", &client_name_background, client_name_bg); get_xresource_string("zswm.color.client_name_color", &client_name, client_name_color); get_xresource_string("zswm.color.active_client_name_bg", &active_client_name_background, active_client_name_bg); get_xresource_string("zswm.color.active_client_name_color", &active_client_name, active_client_name_color); get_xresource_string("zswm.color.status_net_recv", &status_net_recv, status_net_recv_color); get_xresource_string("zswm.color.status_net_send", &status_net_send, status_net_send_color); get_xresource_string("zswm.color.status_volume", &status_volume, status_volume_color); get_xresource_string("zswm.color.status_memory", &status_memory, status_memory_color); get_xresource_string("zswm.color.status_cpu", &status_cpu, status_cpu_color); get_xresource_string("zswm.color.status_datetime", &status_datetime, status_datetime_color); get_xresource_string("zswm.color.border", &border, border_color); get_xresource_string("zswm.color.active_border", &active_border, active_border_color); parse_color(bar_background, &color_set->bar_bg); parse_color(tag_background, &color_set->tag_bg); parse_color(active_tag_background, &color_set->active_tag_bg); parse_color(tag, &color_set->tag_color); parse_color(active_tag, &color_set->active_tag_color); parse_color(layout, &color_set->layout_color); parse_color(client_name_background, &color_set->client_name_bg); parse_color(client_name, &color_set->client_name_color); parse_color(active_client_name_background, &color_set->active_client_name_bg); parse_color(active_client_name, &color_set->active_client_name_color); parse_color(status_net_recv, &color_set->status_net_recv_color); parse_color(status_net_send, &color_set->status_net_send_color); parse_color(status_volume, &color_set->status_volume_color); parse_color(status_memory, &color_set->status_memory_color); parse_color(status_cpu, &color_set->status_cpu_color); parse_color(status_datetime, &color_set->status_datetime_color); parse_color(border, &color_set->border_color); parse_color(active_border, &color_set->active_border_color); free(bar_background); free(tag_background); free(active_tag_background); free(tag); free(active_tag); free(layout); free(client_name_background); free(client_name); free(active_client_name_background); free(active_client_name); free(status_net_recv); free(status_net_send); free(status_volume); free(status_memory); free(status_cpu); free(status_datetime); free(border); free(active_border); } void parse_color(const char *hex, color_t *color) { bool success = hex_color_to_rgba(hex, &color->rgba); if (!success) die("invalid hex color: %s", hex); color->argb = rgba_to_argb(color->rgba); extract_color_channel(color->rgba, &color->red, &color->green, &color->blue, &color->alpha); } void init_monitor_bar(void) { get_xresource_config(); uint32_t font_height = get_font_height(font_family, font_size, dpi); bar_height = font_height + bar_y_pad * 2; for (monitor_t *m = monitors; m; m = m->next) { bar_cairo_params_t *p = create_bar_window(m->monitor_x, m->monitor_y, m->monitor_width, bar_height, color_set->bar_bg.argb, cursor_normal); m->window_x = m->monitor_x; m->window_width = m->monitor_width; m->window_y = m->monitor_y + bar_height; m->window_height = m->monitor_height - bar_height; m->bar_window = p->window; cairo_surface_t *surface = cairo_xcb_surface_create( p->conn, p->window, p->visual, m->monitor_width, bar_height); cairo_t *cr = cairo_create(surface); cairo_surface_destroy(surface); free(p); cairo_status_t status = cairo_status(cr); if (status != CAIRO_STATUS_SUCCESS) { die("cannot create cairo context: %s\n", cairo_status_to_string(status)); } m->cr = cr; } logger("font height: %u, bar height: %u\n", font_height, bar_height); } void get_xresource_config(void) { get_xresource_uint32("Xft.dpi", &dpi); get_xresource_string("zswm.font_family", &font_family, default_font_family); get_xresource_uint32("zswm.font_size", &font_size); get_xresource_uint32("zswm.bar_y_padding", &bar_y_pad); get_xresource_uint32("zswm.tag_x_padding", &tag_x_pad); get_xresource_uint32("zswm.layout_symbol_x_padding", &layout_symbol_x_pad); get_xresource_uint32("zswm.client_name_x_padding", &client_name_x_pad); get_xresource_uint32("zswm.status_x_padding", &status_x_pad); get_xresource_uint32("zswm.status_icon_padding", &status_icon_pad); get_xresource_uint32("zswm.border_width", &border_px); logger("dpi: %u, font family: %s, font size: %u\n", dpi, font_family, font_size); clean_xresource(); } static void update_status(status_t *s) { status = s; draw_all_monitor_bars(); } void draw_all_monitor_bars(void) { for (monitor_t *m = monitors; m; m = m->next) draw_monitor_bar(m); } void draw_monitor_bar(monitor_t *m) { draw_bar(m, m == current_monitor, status, icons, color_set, bar_height, tag_x_pad, layout_symbol_x_pad, client_name_x_pad, status_x_pad); flush_xcb_connection(); } void init_xcb_event_loop(void) { adapter = create_event_adapter(); event_adapter_set_handler(adapter, xcb_event_handler, NULL); GIOChannel *channel = g_io_channel_unix_new(get_xcb_connection_fd()); g_io_channel_set_encoding(channel, NULL, NULL); GIOCondition cond = G_IO_IN | G_IO_HUP | G_IO_ERR; g_io_add_watch(channel, cond, handle_xcb_event, NULL); g_io_channel_unref(channel); } void xcb_event_handler(generic_event_t *event, void *user_data) { switch (event->type) { #define EVENT(type, callback) \ case type: \ callback((void *)event); \ break EVENT(EVENT_TYPE_KEY_PRESS, key_press); EVENT(EVENT_TYPE_CONFIGURE_REQUEST, configure_request); EVENT(EVENT_TYPE_MAP_REQUEST, map_request); EVENT(EVENT_TYPE_UNMAP_NOTIFY, unmap_notify); EVENT(EVENT_TYPE_DESTROY_NOTIFY, destroy_notify); EVENT(EVENT_TYPE_EXPOSE, expose); EVENT(EVENT_TYPE_MOTION_NOTIFY, motion_notify); EVENT(EVENT_TYPE_ENTER_NOTIFY, enter_notify); EVENT(EVENT_TYPE_FOCUS_IN, focus_in); EVENT(EVENT_TYPE_PROPERTY_NOTIFY, property_notify); EVENT(EVENT_TYPE_CLIENT_MESSAGE, client_message); #undef EVENT default: break; } } void key_press(key_press_event_t *event) { const keybinding_t *key = NULL; for (int i = 0; i < LENGTH(keys); i++) { key = &keys[i]; if (key->key_symbol == event->key_symbol && key->modifiers == event->modifiers && key->func) { key->func(&key->arg); } } } void configure_request(configure_request_event_t *event) { client_t *client = get_client_of_window(event->window); if (!client) { configure_window(event->window, event->x, event->y, event->width, event->height, event->border_width); } else { configure_window(client->window, client->x, client->y, client->width, client->height, client->border_width); logger("configure request: %s\n", client->name); } flush_xcb_connection(); } void map_request(map_request_event_t *event) { if (!get_window_visible(event->window)) return; if (get_client_of_window(event->window)) return; if (event->parent != get_root_window()) return; manage_window(event->window); } void unmap_notify(unmap_notify_event_t *event) { client_t *c = get_client_of_window(event->window); logger("unmap window: 0x%x, send event: %s\n", event->window, event->send_event ? "true" : "false"); if (c) { print_client(c); if (event->send_event) { set_window_state(event->window, wm_state_withdrawn); } else { unmanage_client(c, false); } } } void destroy_notify(destroy_notify_event_t *event) { client_t *c = get_client_of_window(event->window); if (c) unmanage_client(c, true); } void expose(expose_event_t *event) { monitor_t *m = NULL; if (event->count == 0 && (m = get_monitor_of_window(event->window))) { draw_monitor_bar(m); } } void motion_notify(motion_notify_event_t *event) { if (event->window != get_root_window()) return; static monitor_t *mon = NULL; monitor_t *m = rect_to_monitor(monitors, event->root_x, event->root_y, 1, 1); if (mon && m != mon) { unfocus(current_monitor->selected_client, true); current_monitor = m; focus(NULL); } mon = m; } void enter_notify(enter_notify_event_t *event) { if ((event->mode != notify_mode_normal || event->detail == notify_detail_inferior) && event->window != get_root_window()) { return; } client_t *c = get_client_of_window(event->window); monitor_t *m = c ? c->monitor : get_monitor_of_window(event->window); if (m != current_monitor) { unfocus(current_monitor->selected_client, true); current_monitor = m; } else if (!c || c == current_monitor->selected_client) { return; } focus(c); } void focus_in(focus_in_event_t *event) { if (current_monitor->selected_client && event->window != current_monitor->selected_client->window) { focus_window(event->window); send_event(event->window, atom_wm_take_focus); } } void property_notify(property_notify_event_t *event) { logger("window: 0x%x state: %u, atom: %u\n", event->window, event->state, event->xcb_atom); if (event->state == property_delete) return; client_t *c = get_client_of_window(event->window); if (!c) return; print_client(c); switch (event->xcb_atom) { case XCB_ATOM_WM_TRANSIENT_FOR: { xcb_window_t transient_for; if (!c->floating && ((transient_for = get_transient_window_for(event->window)) != WINDOW_NONE) && (c->floating = (get_client_of_window(transient_for) != NULL))) { arrange(c->monitor); } break; } default: break; } if (event->xcb_atom == XCB_ATOM_WM_NAME || event->xcb_atom == get_xcb_atom(atom_net_wm_name)) { update_client_name(c); if (c->tags & c->monitor->selected_tags) draw_monitor_bar(c->monitor); } } void client_message(client_message_event_t *event) { client_t *c = get_client_of_window(event->window); if (!c) return; /** * _NET_WM_STATE_FULLSCREEN 协议数据格式为 * data.data32 = { * action, // 操作类型(添加/移除/切换) * property1, // 第一个状态原子(如_NET_WM_STATE_FULLSCREEN) * property2, // 第二个状态原子(可选,通常为0) * 0, // 预留字段(固定为0) * 0 // 预留字段(固定为0) * }; * action 值可能为 * _NET_WM_STATE_ADD(1)​​:添加全屏状态,窗口进入全屏模式 * _NET_WM_STATE_REMOVE(0):移除全屏状态,窗口退出全屏模式 * _NET_WM_STATE_TOGGLE(2):切换全屏状态(若当前全屏则退出,反之进入) */ if (event->message_type == get_xcb_atom(atom_net_wm_state)) { uint32_t fullscreen_atom = get_xcb_atom(atom_net_wm_fullscreen); if (event->data.data32[1] == fullscreen_atom || event->data.data32[2] == fullscreen_atom) { bool set_fullscreen = event->data.data32[0] == 1; bool toggle_to_fullscreen = event->data.data32[0] == 2 && !c->fullscreen; set_client_fullscreen(c, set_fullscreen || toggle_to_fullscreen); } } else if (event->message_type == get_xcb_atom(atom_net_active_window)) { client_t *client = get_client_of_window(event->window); if (client) { logger("==== _NET_ACTIVE_WINDOW\n"); print_client(client); uint32_t tag_mask = 0; for (uint32_t i = 0; i < g_strv_length(client->monitor->tags); i++) { tag_mask |= 1 << i; } uint32_t target_tag = tag_mask & client->tags; const user_action_arg_t arg = {.ui = target_tag}; current_monitor = client->monitor; select_tag(&arg); focus(client); restack(current_monitor); } } } void manage_window(xcb_window_t window) { window_geometry_t *geometry = get_window_geometry(window); client_t *c = ecalloc(1, sizeof(client_t)); c->window = window; c->x = c->old_x = geometry->x; c->y = c->old_y = geometry->y; c->width = c->old_width = geometry->width; c->height = c->old_height = geometry->height; c->old_border_width = geometry->border_width; c->border_width = border_px; update_client_name(c); logger("== manage window: 0x%x\n", window); logger("== x: %d, y: %d, w: %u, h: %u, bw: %u\n", c->x, c->y, c->width, c->height, c->border_width); xcb_window_t tw = get_transient_window_for(window); client_t *tc = NULL; if (tw != WINDOW_NONE && (tc = get_client_of_window(tw))) { c->monitor = tc->monitor; c->tags = tc->tags; } else { c->monitor = current_monitor; apply_rules(c); } set_client_tag_prop(c); set_window_event_mask(window, false); change_window_border_color(window, color_set->border_color.argb); attach_client(c); attach_stack_client(c); update_client_list(); set_window_state(window, wm_state_normal); if (c->monitor == current_monitor) { unfocus(current_monitor->selected_client, false); } c->monitor->selected_client = c; arrange(c->monitor); map_window(window, false); focus(NULL); } void unmanage_client(client_t *client, bool destroyed) { monitor_t *monitor = client->monitor; detach_client(client); detach_stack_client(client); if (!destroyed) { set_window_event_mask(client->window, true); change_window_border_width(client->window, client->old_border_width); set_window_state(client->window, wm_state_withdrawn); flush_xcb_connection(); } free(client); focus(NULL); update_client_list(); arrange(monitor); } void update_client_list(void) { uint32_t length = 0; for (monitor_t *m = monitors; m; m = m->next) { for (client_t *c = m->clients; c; c = c->next) length++; } if (!length) { set_window_list(NULL, length); return; } xcb_window_t *list = ecalloc(length, sizeof(xcb_window_t)); uint32_t i = 0; for (monitor_t *m = monitors; m; m = m->next) { for (client_t *c = m->clients; c; c = c->next) list[i++] = c->window; } set_window_list(list, length); free(list); } void update_client_name(client_t *client) { char *name = get_window_name(client->window); strncpy(client->name, name, sizeof(client->name) - 1); free(name); } void apply_rules(client_t *client) { uint32_t monitor_amount = 0; for (monitor_t *m = monitors; m; m = m->next) monitor_amount++; window_class_instance_t class_instance; get_window_class_instance(client->window, &class_instance); bool switch_to_tag = 0; for (int i = 0; i < LENGTH(rules); i++) { const rule_t *r = &rules[i]; if (!((!r->title || strstr(client->name, r->title)) && (!r->class || strstr(class_instance.class, r->class)) && (!r->instance || strstr(class_instance.instance, r->instance)))) { continue; } print_rule(r); switch_to_tag = r->action.switch_to_tag; client->fullscreen = r->action.fullscreen; client->maximize = r->action.maximize; client->floating = r->action.floating; if (!r->tag) continue; const rule_tag_t *tag = NULL; for (uint32_t i = 0;; i++) { const rule_tag_t *t = &r->tag[i]; if (!t->tags) break; if (!t->monitor_amount || t->monitor_amount == monitor_amount) { tag = t; } } if (tag) { monitor_t *m = monitors; for (uint32_t i = 0; i < tag->monitor_index; i++) m = m ? m->next : NULL; if (!m) continue; uint32_t tags_mask = get_tags_mask_of_monitor(m); client->tags = tag->tags & tags_mask; client->monitor = m; } } if (!client->tags && !restore_client_tag(client)) { client->monitor = current_monitor; client->tags = current_monitor->selected_tags; } if (switch_to_tag) { current_monitor = client->monitor; current_monitor->selected_tags = client->tags; } print_client(client); logger("apply rules done\n"); } uint32_t get_tags_mask_of_monitor(monitor_t *monitor) { uint32_t tags_mask = 0; for (uint32_t i = 0; monitor->tags[i]; i++) tags_mask |= (1 << i); return tags_mask; } void attach_client(client_t *client) { client->next = client->monitor->clients; client->monitor->clients = client; } void detach_client(client_t *client) { client_t **c = &client->monitor->clients; for (; *c && *c != client; c = &(*c)->next); *c = client->next; } void attach_stack_client(client_t *client) { client->stack_next = client->monitor->clients_stack; client->monitor->clients_stack = client; } void detach_stack_client(client_t *client) { client_t **tc = &client->monitor->clients_stack; for (; *tc && *tc != client; tc = &(*tc)->stack_next); *tc = client->stack_next; if (client == client->monitor->selected_client) { client_t *t = client->monitor->clients_stack; for (; t && !CLIENT_VISIBLE(t); t = t->stack_next); client->monitor->selected_client = t; } } void arrange(monitor_t *monitor) { if (monitor) { show_hide_client(monitor->clients_stack); apply_monitor_layout(monitor); restack(monitor); } else { for (monitor = monitors; monitor; monitor = monitor->next) { show_hide_client(monitor->clients_stack); apply_monitor_layout(monitor); } } flush_xcb_connection(); } void restack(monitor_t *monitor) { draw_monitor_bar(monitor); if (!monitor->selected_client) return; if (monitor->selected_client->floating || !monitor->layout->arrange) { raise_window(monitor->selected_client->window); } if (monitor->layout->arrange) { xcb_window_t sibling = monitor->bar_window; for (client_t *c = monitor->clients_stack; c; c = c->stack_next) { if (!CLIENT_VISIBLE(c)) continue; if (c->fullscreen) { place_window_above_sibling(c->window, monitor->bar_window); } else if (!c->floating) { place_window_below_sibling(c->window, sibling); sibling = c->window; } } } flush_xcb_connection(); while (xcb_check_mask_event(XCB_EVENT_MASK_ENTER_WINDOW)); } void focus(client_t *client) { if (!client || !CLIENT_VISIBLE(client)) { for (client = current_monitor->clients_stack; client && !CLIENT_VISIBLE(client); client = client->stack_next); } if (current_monitor->selected_client && current_monitor->selected_client != client) { unfocus(current_monitor->selected_client, false); } if (client) { if (client->monitor != current_monitor) current_monitor = client->monitor; detach_stack_client(client); attach_stack_client(client); change_window_border_color(client->window, color_set->active_border_color.argb); focus_window(client->window); } else { focus_window(WINDOW_NONE); } current_monitor->selected_client = client; draw_all_monitor_bars(); } void unfocus(client_t *client, bool set_focus) { if (!client) return; change_window_border_color(client->window, color_set->border_color.argb); if (set_focus) focus_window(WINDOW_NONE); } void set_client_fullscreen(client_t *client, bool fullscreen) { set_window_fullscreen_state(client->window, true); client->fullscreen = fullscreen; if (fullscreen) { fullscreen_client(client); configure_window(client->window, client->x, client->y, client->width, client->height, client->border_width); raise_window(client->window); } else { client->x = client->old_x; client->y = client->old_y; client->width = client->old_width; client->height = client->old_height; client->border_width = client->old_border_width; configure_window(client->window, client->x, client->y, client->width, client->height, client->border_width); arrange(client->monitor); } } void show_hide_client(client_t *client) { if (!client) return; if (CLIENT_VISIBLE(client)) { if (client->fullscreen) { fullscreen_client(client); resize_window(client->window, client->width, client->height); } else if (client->maximize) { maximize_client(client); resize_window(client->window, client->width, client->height); } else if (!client->monitor->layout->arrange || client->floating) { client->x = client->old_x; client->y = client->old_y; } move_window(client->window, client->x, client->y); show_hide_client(client->stack_next); } else { if (!client->monitor->layout->arrange || client->floating) { client->old_x = client->x; client->old_y = client->y; } client->x = client->monitor->monitor_x; client->y = client->monitor->monitor_y + client->monitor->monitor_height; show_hide_client(client->stack_next); move_window(client->window, client->x, client->y); } } void apply_monitor_layout(monitor_t *monitor) { if (!monitor || !monitor->layout || !monitor->layout->arrange) return; monitor->layout->arrange(monitor); for (client_t *c = next_visible_client(monitor->clients); c; c = next_visible_client(c->next)) { configure_window(c->window, c->x, c->y, c->width, c->height, c->border_width); } } client_t *get_client_of_window(xcb_window_t window) { for (monitor_t *m = monitors; m; m = m->next) { for (client_t *c = m->clients; c; c = c->next) { if (c->window == window) return c; } } return NULL; } monitor_t *get_monitor_of_window(xcb_window_t window) { int32_t x, y; if (window == get_root_window() && get_pointer_position(&x, &y)) { return rect_to_monitor(monitors, x, y, 1, 1); } for (monitor_t *m = monitors; m; m = m->next) { if (window == m->bar_window) return m; } client_t *c = get_client_of_window(window); if (c) return c->monitor; return current_monitor; } monitor_t *get_monitor_by_direction(bool forward) { monitor_t *m = NULL; if (forward) { if (!(m = current_monitor->next)) m = monitors; } else if (current_monitor == monitors) { for (m = monitors; m->next; m = m->next); } else { for (m = monitors; m->next != current_monitor; m = m->next); } return m; } void send_client_to_monitor(client_t *client, monitor_t *monitor) { if (client->monitor == monitor) return; unfocus(client, true); detach_client(client); detach_stack_client(client); client->monitor = monitor; client->tags = monitor->selected_tags; attach_client(client); attach_stack_client(client); set_client_tag_prop(client); focus(client); arrange(NULL); } void set_client_tag_prop(client_t *client) { client_tag_info_t info = { .tags = client->tags, .monitor_index = client->monitor->index, }; set_window_tag(client->window, &info); } /** * @brief 恢复 client 的 tag 信息 * @returns 成功返回 true 否则返回 false */ bool restore_client_tag(client_t *client) { client_tag_info_t client_tag_info; if (!get_window_tag(client->window, &client_tag_info)) return false; for (monitor_t *m = monitors; m; m = m->next) { if (client_tag_info.monitor_index == m->index) { uint32_t tag_mask = 0; for (uint32_t i = 0; i < g_strv_length(m->tags); i++) tag_mask |= 1 << i; uint32_t target_tag = tag_mask & client_tag_info.tags; if (target_tag) { client->monitor = m; client->tags = target_tag; return true; } } } return false; } client_t *get_next_client_with_class(const char *class) { monitor_t *m = NULL; client_t *c = NULL; window_class_instance_t class_instance; for (m = current_monitor; m; m = m->next) { if (m == current_monitor && m->selected_client) { c = m->selected_client->next; } else { c = m->clients; } for (; c; c = c->next) { get_window_class_instance(c->window, &class_instance); if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) { return c; } } } for (m = monitors; m && m != current_monitor; m = m->next) { for (c = m->clients; c; c = c->next) { get_window_class_instance(c->window, &class_instance); if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) { return c; } } } m = current_monitor; for (c = m->clients; c && m->selected_client && c != m->selected_client; c = c->next) { get_window_class_instance(c->window, &class_instance); if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) { return c; } } if (current_monitor->selected_client) { get_window_class_instance(c->window, &class_instance); if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) { return c; } } return NULL; } gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition, gpointer userdata) { if (condition & (G_IO_HUP | G_IO_ERR)) { g_main_loop_quit(loop); return false; } event_adapter_poll(adapter); return true; } void cleanup(bool will_restart) { clean_status(); g_main_loop_unref(loop); loop = NULL; monitor_t *m = monitors; client_t *c = m->clients; while (m) { while (c) { if (!will_restart) remove_window_tag(c->window); client_t *tc = c->next; unmanage_client(c, false); c = tc; } monitor_t *next_monitor = m->next; if (m->cr) cairo_destroy(m->cr); destroy_window(m->bar_window); g_strfreev(m->tags); free(m); m = next_monitor; } monitors = NULL; clean_pango_context(); free_wallpaper_config(wallpaper_config); cairo_destroy(wallpaper_cr); wallpaper_config = NULL; wallpaper_cr = NULL; free(color_set); color_set = NULL; clean_icons(); free(font_family); font_family = NULL; destroy_event_adapter(adapter); adapter = NULL; clean_xcb(); } int main(int argc, char *argv[]) { if (has_other_wm_running()) { die("another window manager is already running"); } loop = g_main_loop_new(NULL, FALSE); logger("========================== ZSWM START ==========================\n"); setup_xcb(); init_monitors(); init_color_set(); init_monitor_bar(); change_window_cursor(get_root_window(), cursor_normal); init_wallpaper(); init_icons(); init_status(g_main_loop_get_context(loop), update_status); draw_all_monitor_bars(); window_list_t *window_list = scan_window_list(); print_monitor_list_info(monitors); print_color_set(color_set); print_wallpaper_config(wallpaper_config); print_window_list(window_list); if (window_list) { for (uint32_t i = 0; i < window_list->normal_count; i++) { manage_window(window_list->normal_list[i]); } for (uint32_t i = 0; i < window_list->transient_count; i++) { manage_window(window_list->transient_list[i]); } } free_window_list(window_list); grab_keybindings(keys, LENGTH(keys)); init_xcb_event_loop(); g_main_loop_run(loop); cleanup(need_restart); if (need_restart) { need_restart = false; execvp(argv[0], argv); } return 0; }