From 9541c6507a374c41aa87299ef32aa1ca7c235bb6 Mon Sep 17 00:00:00 2001 From: qq33tt66 Date: Mon, 4 May 2026 19:37:22 +0530 Subject: [PATCH] feat: add window jumping mode to overview --- src/config/parse_config.h | 16 +++++++ src/data/jump_font.h | 35 ++++++++++++++ src/dispatch/bind_declare.h | 1 + src/dispatch/bind_define.h | 33 ++++++++++++++ src/layout/arrange.h | 5 ++ src/mango.c | 91 +++++++++++++++++++++++++++++++++++++ 6 files changed, 181 insertions(+) create mode 100644 src/data/jump_font.h diff --git a/src/config/parse_config.h b/src/config/parse_config.h index e02b5017..bd681137 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -245,6 +245,7 @@ typedef struct { uint32_t hotarea_corner; uint32_t enable_hotarea; uint32_t ov_tab_mode; + uint32_t jump_mode; int32_t overviewgappi; int32_t overviewgappo; uint32_t cursor_hide_timeout; @@ -960,6 +961,11 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "toggleoverview") == 0) { func = toggleoverview; (*arg).i = atoi(arg_value); + } else if (strcmp(func_name, "togglejump") == 0) { + func = togglejump; + (*arg).i = atoi(arg_value); + } else if (strcmp(func_name, "toggle_scratchpad") == 0) { + func = toggle_scratchpad; } else if (strcmp(func_name, "set_proportion") == 0) { func = set_proportion; (*arg).f = atof(arg_value); @@ -1611,6 +1617,8 @@ bool parse_option(Config *config, char *key, char *value) { config->enable_hotarea = atoi(value); } else if (strcmp(key, "ov_tab_mode") == 0) { config->ov_tab_mode = atoi(value); + } else if (strcmp(key, "jump_mode") == 0) { + config->jump_mode = atoi(value); } else if (strcmp(key, "overviewgappi") == 0) { config->overviewgappi = atoi(value); } else if (strcmp(key, "overviewgappo") == 0) { @@ -3154,6 +3162,13 @@ void override_config(void) { config.hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); config.enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); config.ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1); + config.jump_mode = CLAMP_INT(config.jump_mode, 0, 1); + + // 如果开启了 jump_mode,则自动关闭 ov_tab_mode,因为两者冲突 + if (config.jump_mode) { + config.ov_tab_mode = 0; + } + config.overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000); config.overviewgappo = CLAMP_INT(config.overviewgappo, 0, 1000); config.xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); @@ -3286,6 +3301,7 @@ void set_value_default() { config.capslock = 0; config.ov_tab_mode = 0; + config.jump_mode = 0; config.hotarea_size = 10; config.hotarea_corner = BOTTOM_LEFT; config.enable_hotarea = 1; diff --git a/src/data/jump_font.h b/src/data/jump_font.h new file mode 100644 index 00000000..cbf63a45 --- /dev/null +++ b/src/data/jump_font.h @@ -0,0 +1,35 @@ +#ifndef JUMP_FONT_H +#define JUMP_FONT_H + +#include + +static const uint8_t font5x7[26][7] = { + {0x04, 0x0A, 0x11, 0x11, 0x1F, 0x11, 0x11}, // A + {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // B + {0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E}, // C + {0x1E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1E}, // D + {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F}, // E + {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10}, // F + {0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F}, // G + {0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // H + {0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, // I + {0x01, 0x01, 0x01, 0x01, 0x01, 0x11, 0x0E}, // J + {0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11}, // K + {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F}, // L + {0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}, // M + {0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11}, // N + {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // O + {0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10}, // P + {0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D}, // Q + {0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11}, // R + {0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E}, // S + {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}, // T + {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // U + {0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04}, // V + {0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11}, // W + {0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11}, // X + {0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04}, // Y + {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F} // Z +}; + +#endif diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index dbeebd33..deb09154 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -3,6 +3,7 @@ int32_t restore_minimized(const Arg *arg); int32_t toggle_scratchpad(const Arg *arg); int32_t focusdir(const Arg *arg); int32_t toggleoverview(const Arg *arg); +int32_t togglejump(const Arg *arg); int32_t set_proportion(const Arg *arg); int32_t switch_proportion_preset(const Arg *arg); int32_t zoom(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index f5992e29..b60337b9 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1704,6 +1704,10 @@ int32_t toggleoverview(const Arg *arg) { overview_backup(c); } } else { + if (selmon->is_jump_mode) { + destroy_jump_hints(selmon); + selmon->is_jump_mode = 0; + } wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !c->iskilling && !client_is_unmanaged(c) && !c->isunglobal && @@ -1715,6 +1719,35 @@ int32_t toggleoverview(const Arg *arg) { view(&(Arg){.ui = target}, false); fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); + + if (selmon->isoverview && config.jump_mode) { + selmon->is_jump_mode = 1; + create_jump_hints(selmon); + } + + return 0; +} + +int32_t togglejump(const Arg *arg) { + if (!selmon) + return 0; + + if (selmon->is_jump_mode) { + destroy_jump_hints(selmon); + selmon->is_jump_mode = 0; + toggleoverview(arg); + return 0; + } + + if (!selmon->isoverview) { + toggleoverview(arg); + } + + if (selmon->isoverview) { + selmon->is_jump_mode = 1; + create_jump_hints(selmon); + } + return 0; } diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 04f4554b..4bd39f6a 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -929,5 +929,10 @@ arrange(Monitor *m, bool want_animation, bool from_view) { checkidleinhibitor(NULL); } + if (m->is_jump_mode) { + destroy_jump_hints(m); + create_jump_hints(m); + } + printstatus(); } diff --git a/src/mango.c b/src/mango.c index 85fc00ac..8c3719c0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -93,6 +93,7 @@ #include #endif #include "common/util.h" +#include "data/jump_font.h" /* macros */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) @@ -422,6 +423,8 @@ struct Client { bool isfocusing; struct Client *next_in_stack; struct Client *prev_in_stack; + char jump_char; + struct wlr_scene_tree *jump_overlay; }; typedef struct { @@ -531,6 +534,7 @@ struct Monitor { uint32_t ovbk_prev_tagset; Client *sel, *prevsel; int32_t isoverview; + int32_t is_jump_mode; int32_t is_in_hotarea; int32_t asleep; uint32_t visible_clients; @@ -810,6 +814,9 @@ static void client_pending_fullscreen_state(Client *c, int32_t isfullscreen); static void client_pending_maximized_state(Client *c, int32_t ismaximized); static void client_pending_minimized_state(Client *c, int32_t isminimized); +static void create_jump_hints(Monitor *m); +static void destroy_jump_hints(Monitor *m); + #include "data/static_keymap.h" #include "dispatch/bind_declare.h" #include "layout/layout.h" @@ -3901,6 +3908,27 @@ void keypress(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + if (selmon && selmon->is_jump_mode && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) { + xkb_keysym_t sym = xkb_keysym_to_lower(syms[i]); + if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) { + char c_char = 'A' + (sym - XKB_KEY_a); + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->mon == selmon && c->jump_char == c_char) { + focusclient(c, 1); + toggleoverview(NULL); + return; + } + } + } else if (sym == XKB_KEY_Escape) { + togglejump(NULL); + return; + } + } + } + // ov tab mode detect moe key release if (config.ov_tab_mode && !locked && group == kb_group && event->state == WL_KEYBOARD_KEY_STATE_RELEASED && @@ -6135,6 +6163,11 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->next_in_stack = NULL; c->prev_in_stack = NULL; + if (c->jump_overlay) { + wlr_scene_node_destroy(&c->jump_overlay->node); + c->jump_overlay = NULL; + } + wlr_scene_node_destroy(&c->scene->node); printstatus(); motionnotify(0, NULL, 0, 0, 0, 0); @@ -6660,6 +6693,64 @@ static void setgeometrynotify(struct wl_listener *listener, void *data) { } #endif +void create_jump_hints(Monitor *m) { + const char jump_labels[] = "HJKLASDFGQWERTYUIOPZXCVBNM"; + int label_idx = 0; + Client *c; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + if (label_idx >= 26) + break; + char c_char = jump_labels[label_idx]; + c->jump_char = c_char; + c->jump_overlay = wlr_scene_tree_create(layers[LyrOverlay]); + c->jump_overlay->node.data = c; + + float bg_color[] = {0.1f, 0.1f, 0.1f, 0.6f}; + struct wlr_scene_rect *bg = wlr_scene_rect_create( + c->jump_overlay, c->geom.width, c->geom.height, bg_color); + wlr_scene_node_set_position(&bg->node, c->geom.x, c->geom.y); + + int char_idx = c_char - 'A'; + if (char_idx >= 0 && char_idx < 26) { + int pixel_size = 6; + int char_w = 5 * pixel_size; + int char_h = 7 * pixel_size; + int start_x = c->geom.x + (c->geom.width - char_w) / 2; + int start_y = c->geom.y + (c->geom.height - char_h) / 2; + float fg_color[] = {1.0f, 1.0f, 1.0f, 1.0f}; + + for (int row = 0; row < 7; row++) { + for (int col = 0; col < 5; col++) { + if ((font5x7[char_idx][row] >> (4 - col)) & 1) { + struct wlr_scene_rect *pixel = + wlr_scene_rect_create(c->jump_overlay, + pixel_size, pixel_size, + fg_color); + wlr_scene_node_set_position( + &pixel->node, start_x + col * pixel_size, + start_y + row * pixel_size); + } + } + } + } + label_idx++; + } + } +} + +void destroy_jump_hints(Monitor *m) { + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->jump_overlay) { + wlr_scene_node_destroy(&c->jump_overlay->node); + c->jump_overlay = NULL; + } + c->jump_char = 0; + } +} + int32_t main(int32_t argc, char *argv[]) { char *startup_cmd = NULL; int32_t c;