diff --git a/src/action.h b/src/action.h index e67de41..32547f9 100644 --- a/src/action.h +++ b/src/action.h @@ -11,3 +11,5 @@ void focus_monitor(const user_action_arg_t *arg); void raise_or_run(const user_action_arg_t *arg); void toggle_mute(const user_action_arg_t *arg); void change_volume(const user_action_arg_t *arg); +void move_to_monitor(const user_action_arg_t *arg); +void move_to_tag(const user_action_arg_t *arg); diff --git a/src/config.h b/src/config.h index 220f818..22df637 100644 --- a/src/config.h +++ b/src/config.h @@ -96,6 +96,7 @@ const keybinding_t keys[] = { {ALT, XK_k, focus_stack, {.b = false}}, {SUPER | SHIFT, XK_j, focus_monitor, {.b = true}}, {SUPER | SHIFT, XK_k, focus_monitor, {.b = false}}, + {SUPER, XK_o, move_to_monitor, {.b = true}}, { SUPER, @@ -128,6 +129,16 @@ const keybinding_t keys[] = { {SUPER, XK_7, select_tag, {.ui = 1 << 6}}, {SUPER, XK_8, select_tag, {.ui = 1 << 7}}, {SUPER, XK_9, select_tag, {.ui = 1 << 8}}, + + {SUPER | SHIFT, XK_1, move_to_tag, {.ui = 1 << 0}}, + {SUPER | SHIFT, XK_2, move_to_tag, {.ui = 1 << 1}}, + {SUPER | SHIFT, XK_3, move_to_tag, {.ui = 1 << 2}}, + {SUPER | SHIFT, XK_4, move_to_tag, {.ui = 1 << 3}}, + {SUPER | SHIFT, XK_5, move_to_tag, {.ui = 1 << 4}}, + {SUPER | SHIFT, XK_6, move_to_tag, {.ui = 1 << 5}}, + {SUPER | SHIFT, XK_7, move_to_tag, {.ui = 1 << 6}}, + {SUPER | SHIFT, XK_8, move_to_tag, {.ui = 1 << 7}}, + {SUPER | SHIFT, XK_9, move_to_tag, {.ui = 1 << 8}}, }; const char *const bar_bg = "#222222"; diff --git a/src/include/xcb.h b/src/include/xcb.h index ad26399..0ed6dd1 100644 --- a/src/include/xcb.h +++ b/src/include/xcb.h @@ -128,6 +128,7 @@ typedef enum atom_t { atom_net_wm_fullscreen, atom__xrootpmap_id, atom_esetroot_pmap_id, + atom_zswm_client_info, /* 自定义属性,用于存储所在 monitor 和 tags */ } atom_t; xcb_atom_t get_xcb_atom(atom_t atom); bool send_event(xcb_window_t window, atom_t atom); @@ -139,6 +140,13 @@ typedef enum wm_state_t { int32_t get_window_state(xcb_window_t window); void set_window_state(xcb_window_t window, wm_state_t state); void set_window_list(xcb_window_t *list, uint32_t length); +typedef struct client_info_t { + uint32_t tags; + uint32_t monitor_index; +} client_tag_info_t; +bool get_window_tag(xcb_window_t window, client_tag_info_t *client_info); +void set_window_tag(xcb_window_t window, client_tag_info_t *client_info); +void remove_window_tag(xcb_window_t window); bool get_pointer_position(int32_t *x, int32_t *y); diff --git a/src/main.c b/src/main.c index 60f092f..e385cce 100644 --- a/src/main.c +++ b/src/main.c @@ -70,9 +70,12 @@ 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 apply_initial_client_tag(client_t *client); static gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition, gpointer userdata); -static void cleanup(void); +static void cleanup(bool will_restart); static monitor_t *monitors = NULL; static monitor_t *current_monitor = NULL; @@ -194,6 +197,31 @@ 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"); @@ -365,6 +393,9 @@ void init_wallpaper(void) { } 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; @@ -805,11 +836,13 @@ void manage_window(xcb_window_t window) { if (tw != WINDOW_NONE && (tc = get_client_of_window(tw))) { c->monitor = tc->monitor; c->tags = tc->tags; - } else { + } else if (!apply_initial_client_tag(c)) { 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); @@ -915,8 +948,12 @@ void apply_rules(client_t *client) { } if (!client->tags) { - client->monitor = current_monitor; - client->tags = current_monitor->selected_tags; + client_tag_info_t client_info; + if (get_window_tag(client->window, &client_info)) { + } else { + client->monitor = current_monitor; + client->tags = current_monitor->selected_tags; + } } if (switch_to_tag) { current_monitor = client->monitor; @@ -972,6 +1009,7 @@ void arrange(monitor_t *monitor) { apply_monitor_layout(monitor); } } + flush_xcb_connection(); } void restack(monitor_t *monitor) { @@ -1121,6 +1159,53 @@ monitor_t *get_monitor_by_direction(bool forward) { 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 apply_initial_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; +} + gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition, gpointer userdata) { if (condition & (G_IO_HUP | G_IO_ERR)) { @@ -1132,7 +1217,7 @@ gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition, return true; } -void cleanup(void) { +void cleanup(bool will_restart) { clean_status(); g_main_loop_unref(loop); @@ -1142,6 +1227,7 @@ void cleanup(void) { 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; @@ -1181,6 +1267,7 @@ int main(int argc, char *argv[]) { loop = g_main_loop_new(NULL, FALSE); + logger("========================== ZSWM START ==========================\n"); setup_xcb(); init_monitors(); init_color_set(); @@ -1215,7 +1302,7 @@ int main(int argc, char *argv[]) { g_main_loop_run(loop); - cleanup(); + cleanup(need_restart); if (need_restart) { need_restart = false; diff --git a/src/xcb/wm-extension.c b/src/xcb/wm-extension.c index 1e1d2ce..b0f0adf 100644 --- a/src/xcb/wm-extension.c +++ b/src/xcb/wm-extension.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -30,6 +31,7 @@ static atom_map_t atom_map[] = { {.atom = atom_net_wm_fullscreen, .name = "_NET_WM_STATE_FULLSCREEN"}, {.atom = atom__xrootpmap_id, .name = "_XROOTPMAP_ID"}, {.atom = atom_esetroot_pmap_id, .name = "ESETROOT_PMAP_ID"}, + {.atom = atom_zswm_client_info, .name = "ZSWM_CLIENT_INFO"}, }; void init_icccm_extension(void) { @@ -84,6 +86,7 @@ void init_ewmh_extension(void) { xcb_change_property(conn, mode, screen->root, ewmh->_NET_SUPPORTED, atom, 32, LENGTH(supported), supported); xcb_delete_property(conn, screen->root, ewmh->_NET_CLIENT_LIST); + xcb_delete_property(conn, screen->root, get_xcb_atom(atom_zswm_client_info)); } void clean_ewmh_extension(void) { @@ -171,3 +174,29 @@ void set_window_list(xcb_window_t *list, uint32_t length) { } xcb_ewmh_set_client_list(ewmh, screen_nbr, length, list); } + +bool get_window_tag(xcb_window_t window, client_tag_info_t *client_info) { + xcb_get_property_cookie_t cookie = + xcb_get_property(conn, 0, window, get_xcb_atom(atom_zswm_client_info), + XCB_ATOM_CARDINAL, 0, 2); + xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, cookie, NULL); + int length = xcb_get_property_value_length(reply); + if (length >= 2) { + uint32_t *data = xcb_get_property_value(reply); + client_info->tags = data[0]; + client_info->monitor_index = data[1]; + return true; + } else { + return false; + } +} + +void set_window_tag(xcb_window_t window, client_tag_info_t *client_info) { + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, + get_xcb_atom(atom_zswm_client_info), XCB_ATOM_CARDINAL, + 32, 2, client_info); +} + +void remove_window_tag(xcb_window_t window) { + xcb_delete_property(conn, window, get_xcb_atom(atom_zswm_client_info)); +}