feat: add window jumping mode to overview

This commit is contained in:
qq33tt66 2026-05-04 19:37:22 +05:30
parent b9c6a2c196
commit 9541c6507a
6 changed files with 181 additions and 0 deletions

View file

@ -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;

35
src/data/jump_font.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef JUMP_FONT_H
#define JUMP_FONT_H
#include <stdint.h>
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

View file

@ -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);

View file

@ -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;
}

View file

@ -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();
}

View file

@ -93,6 +93,7 @@
#include <xcb/xcb_icccm.h>
#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;