Merge pull request #967 from mangowm/newipc

feat: better ipc support
This commit is contained in:
DreamMaoMao 2026-05-25 11:31:30 +08:00 committed by GitHub
commit a1a119e2fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1206 additions and 1017 deletions

View file

@ -57,4 +57,7 @@ void client_tile_resize(Client *c, struct wlr_box geo, int32_t interact) {
if (!c->isfullscreen && !c->ismaximizescreen) {
resize(c, geo, interact);
}
}
}
static uint32_t next_client_id = 0;
uint32_t generate_client_id(void) { return ++next_client_id; }

View file

@ -967,6 +967,8 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value,
} else if (strcmp(func_name, "focusdir") == 0) {
func = focusdir;
(*arg).i = parse_direction(arg_value);
} else if (strcmp(func_name, "focusid") == 0) {
func = focusid;
} else if (strcmp(func_name, "incnmaster") == 0) {
func = incnmaster;
(*arg).i = atoi(arg_value);
@ -2443,6 +2445,7 @@ bool parse_option(Config *config, char *key, char *value) {
binding->arg.v = NULL;
binding->arg.v2 = NULL;
binding->arg.v3 = NULL;
binding->arg.tc = NULL;
binding->func =
parse_func_name(func_name, &binding->arg, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5);
@ -2524,6 +2527,7 @@ bool parse_option(Config *config, char *key, char *value) {
binding->arg.v = NULL;
binding->arg.v2 = NULL;
binding->arg.v3 = NULL;
binding->arg.tc = NULL;
// TODO: remove this in next version
if (binding->mod == 0 &&
@ -2609,6 +2613,7 @@ bool parse_option(Config *config, char *key, char *value) {
binding->arg.v = NULL;
binding->arg.v2 = NULL;
binding->arg.v3 = NULL;
binding->arg.tc = NULL;
binding->func =
parse_func_name(func_name, &binding->arg, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5);
@ -2759,6 +2764,7 @@ bool parse_option(Config *config, char *key, char *value) {
binding->arg.v = NULL;
binding->arg.v2 = NULL;
binding->arg.v3 = NULL;
binding->arg.tc = NULL;
binding->func =
parse_func_name(func_name, &binding->arg, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5);

View file

@ -73,4 +73,5 @@ int32_t scroller_stack(const Arg *arg);
int32_t toggle_all_floating(const Arg *arg);
int32_t dwindle_toggle_split_direction(const Arg *arg);
int32_t dwindle_split_horizontal(const Arg *arg);
int32_t dwindle_split_vertical(const Arg *arg);
int32_t dwindle_split_vertical(const Arg *arg);
int32_t focusid(const Arg *arg);

View file

@ -34,10 +34,8 @@ int32_t bind_to_view(const Arg *arg) {
int32_t chvt(const Arg *arg) {
struct timespec ts;
// prevent the animation to rquest the new frame
allow_frame_scheduling = false;
// backup current tag and monitor name
if (selmon) {
chvt_backup_tag = selmon->pertag->curtag;
strncpy(chvt_backup_selmon, selmon->wlr_output->name,
@ -46,19 +44,15 @@ int32_t chvt(const Arg *arg) {
wlr_session_change_vt(session, arg->ui);
// wait for DRM device to stabilize and ensure the session state is inactive
ts.tv_sec = 0;
ts.tv_nsec = 100000000; // 200ms
ts.tv_nsec = 100000000;
nanosleep(&ts, NULL);
// allow frame scheduling,
// because session state is now inactive, rendermon will not enter
allow_frame_scheduling = true;
return 1;
}
int32_t create_virtual_output(const Arg *arg) {
if (!wlr_backend_is_multi(backend)) {
wlr_log(WLR_ERROR, "Expected a multi backend");
return 0;
@ -77,7 +71,6 @@ int32_t create_virtual_output(const Arg *arg) {
}
int32_t destroy_all_virtual_output(const Arg *arg) {
if (!wlr_backend_is_multi(backend)) {
wlr_log(WLR_ERROR, "Expected a multi backend");
return 0;
@ -86,8 +79,6 @@ int32_t destroy_all_virtual_output(const Arg *arg) {
Monitor *m, *tmp;
wl_list_for_each_safe(m, tmp, &mons, link) {
if (wlr_output_is_headless(m->wlr_output)) {
// if(selmon == m)
// selmon = NULL;
wlr_output_destroy(m->wlr_output);
wlr_log(WLR_INFO, "Virtual output destroyed");
}
@ -103,7 +94,7 @@ int32_t defaultgaps(const Arg *arg) {
int32_t exchange_client(const Arg *arg) {
if (!selmon)
return 0;
Client *c = selmon->sel;
Client *c = arg->tc ? arg->tc : selmon->sel;
if (!c || c->isfloating)
return 0;
@ -111,7 +102,7 @@ int32_t exchange_client(const Arg *arg) {
return 0;
Client *tc = direction_select(arg);
tc = get_focused_stack_client(tc);
tc = get_focused_stack_client(tc, arg->tc);
if (!tc)
return 0;
@ -124,7 +115,7 @@ int32_t exchange_stack_client(const Arg *arg) {
if (!selmon)
return 0;
Client *c = selmon->sel;
Client *c = arg->tc ? arg->tc : selmon->sel;
Client *tc = NULL;
if (!c || c->isfloating || c->isfullscreen || c->ismaximizescreen)
return 0;
@ -141,7 +132,7 @@ int32_t exchange_stack_client(const Arg *arg) {
int32_t focusdir(const Arg *arg) {
Client *c = NULL;
c = direction_select(arg);
c = get_focused_stack_client(c);
c = get_focused_stack_client(c, arg->tc);
if (c) {
focusclient(c, 1);
if (config.warpcursor)
@ -160,7 +151,6 @@ int32_t focusdir(const Arg *arg) {
}
int32_t focuslast(const Arg *arg) {
Client *c = NULL;
Client *tc = NULL;
bool begin = false;
@ -232,7 +222,7 @@ int32_t focusmon(const Arg *arg) {
if (config.warpcursor) {
warp_cursor_to_selmon(selmon);
}
c = focustop(selmon);
c = arg->tc ? arg->tc : focustop(selmon);
if (!c) {
selmon->sel = NULL;
wlr_seat_pointer_notify_clear_focus(seat);
@ -245,8 +235,7 @@ int32_t focusmon(const Arg *arg) {
}
int32_t focusstack(const Arg *arg) {
/* Focus the next or previous client (in tiling order) on selmon */
Client *sel = focustop(selmon);
Client *sel = arg->tc ? arg->tc : focustop(selmon);
Client *tc = NULL;
if (!sel)
@ -256,7 +245,6 @@ int32_t focusstack(const Arg *arg) {
} else {
tc = get_next_stack_client(sel, true);
}
/* If only one client is visible on selmon, then c == sel */
if (!tc)
return 0;
@ -355,10 +343,7 @@ int32_t setmfact(const Arg *arg) {
}
int32_t killclient(const Arg *arg) {
Client *c = NULL;
if (!selmon)
return 0;
c = selmon->sel;
Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL);
if (c) {
pending_kill_client(c);
}
@ -377,7 +362,6 @@ int32_t moveresize(const Arg *arg) {
grabc = NULL;
return 0;
}
/* Float the window and tell motionnotify to grab it */
if (grabc->isfloating == 0 && arg->ui == CurMove) {
grabc->drag_to_tile = true;
exit_scroller_stack(grabc);
@ -398,20 +382,16 @@ int32_t moveresize(const Arg *arg) {
switch (cursor_mode = arg->ui) {
case CurMove:
grabcx = cursor->x - grabc->geom.x;
grabcy = cursor->y - grabc->geom.y;
wlr_cursor_set_xcursor(cursor, cursor_mgr, "grab");
break;
case CurResize:
/* Doesn't work for X11 output - the next absolute motion event
* returns the cursor to where it started */
if (grabc->isfloating) {
rzcorner = config.drag_corner;
grabcx = (int)round(cursor->x);
grabcy = (int)round(cursor->y);
if (rzcorner == 4)
/* identify the closest corner index */
rzcorner = (grabcx - grabc->geom.x <
grabc->geom.x + grabc->geom.width - grabcx
? 0
@ -439,14 +419,11 @@ int32_t moveresize(const Arg *arg) {
}
int32_t movewin(const Arg *arg) {
Client *c = NULL;
if (!selmon)
return 0;
c = selmon->sel;
Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL);
if (!c || c->isfullscreen)
return 0;
if (!c->isfloating)
togglefloating(NULL);
setfloating(c, 1);
switch (arg->ui) {
case NUM_TYPE_MINUS:
@ -484,10 +461,7 @@ int32_t quit(const Arg *arg) {
}
int32_t resizewin(const Arg *arg) {
Client *c = NULL;
if (!selmon)
return 0;
c = selmon->sel;
Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL);
int32_t offsetx = 0, offsety = 0;
if (!c || c->isfullscreen || c->ismaximizescreen)
@ -558,18 +532,17 @@ int32_t resizewin(const Arg *arg) {
}
int32_t restore_minimized(const Arg *arg) {
Client *c = NULL;
Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL);
if (selmon && selmon->isoverview)
return 0;
if (selmon && selmon->sel && selmon->sel->is_in_scratchpad &&
selmon->sel->is_scratchpad_show) {
client_pending_minimized_state(selmon->sel, 0);
selmon->sel->is_scratchpad_show = 0;
selmon->sel->is_in_scratchpad = 0;
selmon->sel->isnamedscratchpad = 0;
setborder_color(selmon->sel);
if (c && c->is_in_scratchpad && c->is_scratchpad_show) {
client_pending_minimized_state(c, 0);
c->is_scratchpad_show = 0;
c->is_in_scratchpad = 0;
c->isnamedscratchpad = 0;
setborder_color(c);
return 0;
}
@ -628,11 +601,10 @@ int32_t set_proportion(const Arg *arg) {
!config.scroller_ignore_proportion_single)
return 0;
Client *tc = selmon->sel;
Client *tc = arg->tc ? arg->tc : selmon->sel;
if (!tc)
return 0;
/* 获取堆叠头部客户端 */
tc = scroll_get_stack_head_client(tc);
if (!tc)
return 0;
@ -645,12 +617,10 @@ int32_t set_proportion(const Arg *arg) {
if (st)
node = find_scroller_node(st, tc);
/* 同时更新节点和客户端字段 */
if (node)
node->scroller_proportion = arg->f;
tc->scroller_proportion = arg->f;
/* 可选的即时几何更新arrange 时会重新计算 */
uint32_t max_client_width =
m->w.width - 2 * config.scroller_structs - config.gappih;
tc->geom.width = max_client_width * arg->f;
@ -674,7 +644,7 @@ int32_t switch_proportion_preset(const Arg *arg) {
!config.scroller_ignore_proportion_single)
return 0;
Client *tc = selmon->sel;
Client *tc = arg->tc ? arg->tc : selmon->sel;
if (!tc)
return 0;
@ -690,11 +660,9 @@ int32_t switch_proportion_preset(const Arg *arg) {
if (st)
node = find_scroller_node(st, tc);
/* 优先从节点读取当前比例,以确保切换基于正确的值 */
float current_proportion =
node ? node->scroller_proportion : tc->scroller_proportion;
/* 查找预设目标 */
for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) {
if (config.scroller_proportion_preset[i] == current_proportion) {
if (arg->i == NEXT) {
@ -719,7 +687,6 @@ int32_t switch_proportion_preset(const Arg *arg) {
if (target_proportion == 0.0f)
target_proportion = config.scroller_proportion_preset[0];
/* 更新节点和客户端 */
if (node)
node->scroller_proportion = target_proportion;
tc->scroller_proportion = target_proportion;
@ -738,11 +705,11 @@ int32_t smartmovewin(const Arg *arg) {
int32_t buttom, top, left, right, tar;
if (!selmon)
return 0;
c = selmon->sel;
c = arg->tc ? arg->tc : selmon->sel;
if (!c || c->isfullscreen)
return 0;
if (!c->isfloating)
setfloating(selmon->sel, true);
setfloating(c, true);
nx = c->geom.x;
ny = c->geom.y;
@ -841,7 +808,7 @@ int32_t smartresizewin(const Arg *arg) {
int32_t buttom, top, left, right, tar;
if (!selmon)
return 0;
c = selmon->sel;
c = arg->tc ? arg->tc : selmon->sel;
if (!c || c->isfullscreen)
return 0;
if (!c->isfloating)
@ -908,10 +875,7 @@ int32_t smartresizewin(const Arg *arg) {
}
int32_t centerwin(const Arg *arg) {
Client *c = NULL;
if (!selmon)
return 0;
c = selmon->sel;
Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL);
if (!c || c->isfullscreen || c->ismaximizescreen)
return 0;
@ -944,7 +908,6 @@ int32_t spawn_shell(const Arg *arg) {
return 0;
if (fork() == 0) {
// 1. 忽略可能导致 coredump 的信号
signal(SIGSEGV, SIG_IGN);
signal(SIGABRT, SIG_IGN);
signal(SIGILL, SIG_IGN);
@ -953,11 +916,8 @@ int32_t spawn_shell(const Arg *arg) {
setsid();
execlp("sh", "sh", "-c", arg->v, (char *)NULL);
// fallback to bash
execlp("bash", "bash", "-c", arg->v, (char *)NULL);
// if execlp fails, we should not reach here
wlr_log(WLR_DEBUG,
"mango: failed to execute command '%s' with shell: %s\n",
arg->v, strerror(errno));
@ -971,7 +931,6 @@ int32_t spawn(const Arg *arg) {
return 0;
if (fork() == 0) {
// 1. 忽略可能导致 coredump 的信号
signal(SIGSEGV, SIG_IGN);
signal(SIGABRT, SIG_IGN);
signal(SIGILL, SIG_IGN);
@ -979,20 +938,17 @@ int32_t spawn(const Arg *arg) {
dup2(STDERR_FILENO, STDOUT_FILENO);
setsid();
// 2. 对整个参数字符串进行单词展开
wordexp_t p;
if (wordexp(arg->v, &p, 0) != 0) {
wlr_log(WLR_DEBUG, "mango: wordexp failed for '%s'\n", arg->v);
_exit(EXIT_FAILURE);
}
// 3. 执行命令p.we_wordv 已经是 argv 数组)
execvp(p.we_wordv[0], p.we_wordv);
// 4. execvp 失败时:打印错误,释放 wordexp 资源,然后退出
wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", p.we_wordv[0],
strerror(errno));
wordfree(&p); // 释放 wordexp 分配的内存
wordfree(&p);
_exit(EXIT_FAILURE);
}
return 0;
@ -1030,7 +986,6 @@ int32_t switch_keyboard_layout(const Arg *arg) {
return 0;
}
// 1. 获取当前布局和计算下一个布局
xkb_layout_index_t current = xkb_state_serialize_layout(
keyboard->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE);
const int32_t num_layouts = xkb_keymap_num_layouts(keyboard->keymap);
@ -1046,14 +1001,12 @@ int32_t switch_keyboard_layout(const Arg *arg) {
next = (current + 1) % num_layouts;
}
// 6. 应用新 keymap
uint32_t depressed = keyboard->modifiers.depressed;
uint32_t latched = keyboard->modifiers.latched;
uint32_t locked = keyboard->modifiers.locked;
wlr_keyboard_notify_modifiers(keyboard, depressed, latched, locked, next);
// 7. 更新 seat
wlr_seat_set_keyboard(seat, keyboard);
wlr_seat_keyboard_notify_modifiers(seat, &keyboard->modifiers);
@ -1066,7 +1019,6 @@ int32_t switch_keyboard_layout(const Arg *arg) {
struct wlr_keyboard *tkb = (struct wlr_keyboard *)id->device_data;
wlr_keyboard_notify_modifiers(tkb, depressed, latched, locked, next);
// 7. 更新 seat
wlr_seat_set_keyboard(seat, tkb);
wlr_seat_keyboard_notify_modifiers(seat, &tkb->modifiers);
}
@ -1076,7 +1028,6 @@ int32_t switch_keyboard_layout(const Arg *arg) {
}
int32_t switch_layout(const Arg *arg) {
int32_t jk, ji;
char *target_layout_name = NULL;
uint32_t len;
@ -1086,7 +1037,6 @@ int32_t switch_layout(const Arg *arg) {
if (config.circle_layout_count != 0) {
for (jk = 0; jk < config.circle_layout_count; jk++) {
len = MAX(
strlen(config.circle_layout[jk]),
strlen(selmon->pertag->ltidxs[selmon->pertag->curtag]->name));
@ -1109,7 +1059,6 @@ int32_t switch_layout(const Arg *arg) {
len = MAX(strlen(layouts[ji].name), strlen(target_layout_name));
if (strncmp(layouts[ji].name, target_layout_name, len) == 0) {
selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[ji];
break;
}
}
@ -1136,7 +1085,7 @@ int32_t switch_layout(const Arg *arg) {
int32_t tag(const Arg *arg) {
if (!selmon)
return 0;
Client *target_client = selmon->sel;
Client *target_client = arg->tc ? arg->tc : selmon->sel;
tag_client(arg, target_client);
return 0;
}
@ -1145,7 +1094,7 @@ int32_t tagmon(const Arg *arg) {
Monitor *m = NULL, *cm = NULL, *oldmon = NULL;
if (!selmon)
return 0;
Client *c = focustop(selmon);
Client *c = arg->tc ? arg->tc : focustop(selmon);
if (!c)
return 0;
@ -1179,8 +1128,8 @@ int32_t tagmon(const Arg *arg) {
return 0;
}
if (c == selmon->sel) {
selmon->sel = NULL;
if (c == oldmon->sel) {
oldmon->sel = NULL;
}
setmon(c, m, newtags, true);
@ -1195,8 +1144,6 @@ int32_t tagmon(const Arg *arg) {
selmon = c->mon;
c->float_geom = setclient_coordinate_center(c, c->mon, c->float_geom, 0, 0);
// 重新计算居中的坐标
// 重新计算居中的坐标
if (c->isfloating) {
c->geom = c->float_geom;
target = get_tags_first_tag(c->tags);
@ -1218,12 +1165,11 @@ int32_t tagmon(const Arg *arg) {
int32_t tagsilent(const Arg *arg) {
Client *fc = NULL;
Client *target_client = NULL;
Client *target_client = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL);
if (!selmon || !selmon->sel)
if (!target_client)
return 0;
target_client = selmon->sel;
target_client->tags = arg->ui & TAGMASK;
wl_list_for_each(fc, &clients, link) {
if (fc && fc != target_client && target_client->tags & fc->tags &&
@ -1240,7 +1186,8 @@ int32_t tagtoleft(const Arg *arg) {
if (!selmon)
return 0;
if (selmon->sel != NULL &&
Client *sel = arg->tc ? arg->tc : selmon->sel;
if (sel != NULL &&
__builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1) {
uint32_t target = selmon->tagset[selmon->seltags] >> 1;
@ -1251,7 +1198,8 @@ int32_t tagtoleft(const Arg *arg) {
selmon->carousel_anim_dir = -1;
}
tag(&(Arg){.ui = target & TAGMASK, .i = arg->i});
Arg a = {.ui = target & TAGMASK, .i = arg->i, .tc = sel};
tag(&a);
selmon->carousel_anim_dir = 0;
}
return 0;
@ -1261,7 +1209,8 @@ int32_t tagtoright(const Arg *arg) {
if (!selmon)
return 0;
if (selmon->sel != NULL &&
Client *sel = arg->tc ? arg->tc : selmon->sel;
if (sel != NULL &&
__builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1) {
uint32_t target = selmon->tagset[selmon->seltags] << 1;
@ -1272,7 +1221,8 @@ int32_t tagtoright(const Arg *arg) {
selmon->carousel_anim_dir = 1;
}
tag(&(Arg){.ui = target & TAGMASK, .i = arg->i});
Arg a = {.ui = target & TAGMASK, .i = arg->i, .tc = sel};
tag(&a);
selmon->carousel_anim_dir = 0;
}
return 0;
@ -1295,7 +1245,6 @@ int32_t toggle_named_scratchpad(const Arg *arg) {
}
target_client->isnamedscratchpad = 1;
apply_named_scratchpad(target_client);
return 0;
}
@ -1341,7 +1290,7 @@ int32_t toggle_scratchpad(const Arg *arg) {
int32_t togglefakefullscreen(const Arg *arg) {
if (!selmon)
return 0;
Client *sel = focustop(selmon);
Client *sel = arg->tc ? arg->tc : focustop(selmon);
if (sel)
setfakefullscreen(sel, !sel->isfakefullscreen);
return 0;
@ -1351,7 +1300,7 @@ int32_t togglefloating(const Arg *arg) {
if (!selmon)
return 0;
Client *sel = focustop(selmon);
Client *sel = arg->tc ? arg->tc : focustop(selmon);
if (selmon && selmon->isoverview)
return 0;
@ -1375,7 +1324,7 @@ int32_t togglefullscreen(const Arg *arg) {
if (!selmon)
return 0;
Client *sel = focustop(selmon);
Client *sel = arg->tc ? arg->tc : focustop(selmon);
if (!sel)
return 0;
@ -1394,15 +1343,17 @@ int32_t toggleglobal(const Arg *arg) {
if (!selmon)
return 0;
if (!selmon->sel)
Client *c = arg->tc ? arg->tc : selmon->sel;
if (!c)
return 0;
if (selmon->sel->is_in_scratchpad) {
selmon->sel->is_in_scratchpad = 0;
selmon->sel->is_scratchpad_show = 0;
selmon->sel->isnamedscratchpad = 0;
if (c->is_in_scratchpad) {
c->is_in_scratchpad = 0;
c->is_scratchpad_show = 0;
c->isnamedscratchpad = 0;
}
selmon->sel->isglobal ^= 1;
setborder_color(selmon->sel);
c->isglobal ^= 1;
setborder_color(c);
return 0;
}
@ -1419,7 +1370,7 @@ int32_t togglemaximizescreen(const Arg *arg) {
if (!selmon)
return 0;
Client *sel = focustop(selmon);
Client *sel = arg->tc ? arg->tc : focustop(selmon);
if (!sel)
return 0;
@ -1440,23 +1391,23 @@ int32_t toggleoverlay(const Arg *arg) {
if (!selmon)
return 0;
if (!selmon->sel || !selmon->sel->mon || selmon->sel->isfullscreen) {
Client *c = arg->tc ? arg->tc : selmon->sel;
if (!c || !c->mon || c->isfullscreen) {
return 0;
}
selmon->sel->isoverlay ^= 1;
c->isoverlay ^= 1;
if (selmon->sel->isoverlay) {
wlr_scene_node_reparent(&selmon->sel->scene->node, layers[LyrOverlay]);
wlr_scene_node_raise_to_top(&selmon->sel->scene->node);
} else if (client_should_overtop(selmon->sel) && selmon->sel->isfloating) {
wlr_scene_node_reparent(&selmon->sel->scene->node, layers[LyrTop]);
if (c->isoverlay) {
wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]);
wlr_scene_node_raise_to_top(&c->scene->node);
} else if (client_should_overtop(c) && c->isfloating) {
wlr_scene_node_reparent(&c->scene->node, layers[LyrTop]);
} else {
wlr_scene_node_reparent(
&selmon->sel->scene->node,
layers[selmon->sel->isfloating ? LyrTop : LyrTile]);
wlr_scene_node_reparent(&c->scene->node,
layers[c->isfloating ? LyrTop : LyrTile]);
}
setborder_color(selmon->sel);
setborder_color(c);
return 0;
}
@ -1465,7 +1416,7 @@ int32_t toggletag(const Arg *arg) {
return 0;
uint32_t newtags;
Client *sel = focustop(selmon);
Client *sel = arg->tc ? arg->tc : focustop(selmon);
if (!sel)
return 0;
@ -1648,15 +1599,20 @@ int32_t viewcrossmon(const Arg *arg) {
}
int32_t tagcrossmon(const Arg *arg) {
if (!selmon || !selmon->sel)
if (!selmon)
return 0;
Client *c = arg->tc ? arg->tc : selmon->sel;
if (!c)
return 0;
if (match_monitor_spec(arg->v, selmon)) {
tag_client(arg, selmon->sel);
tag_client(arg, c);
return 0;
}
tagmon(&(Arg){.ui = arg->ui, .i = UNDIR, .v = arg->v});
Arg a = {.ui = arg->ui, .i = UNDIR, .v = arg->v, .tc = c};
tagmon(&a);
return 0;
}
@ -1680,15 +1636,13 @@ int32_t comboview(const Arg *arg) {
}
int32_t zoom(const Arg *arg) {
Client *c = NULL, *sel = focustop(selmon);
Client *c = NULL, *sel = arg->tc ? arg->tc : focustop(selmon);
if (!sel || !selmon ||
!selmon->pertag->ltidxs[selmon->pertag->curtag]->arrange ||
sel->isfloating)
return 0;
/* Search for the first tiled window that is not sel, marking sel as
* NULL if we pass it along the way */
wl_list_for_each(c, &clients,
link) if (VISIBLEON(c, selmon) && !c->isfloating) {
if (c != sel)
@ -1696,12 +1650,9 @@ int32_t zoom(const Arg *arg) {
sel = NULL;
}
/* Return if no other tiled window was found */
if (&c->link == &clients)
return 0;
/* If we passed sel, move c to the front; otherwise, move sel to the
* front */
if (!sel)
sel = c;
wl_list_remove(&sel->link);
@ -1726,8 +1677,9 @@ int32_t minimized(const Arg *arg) {
if (selmon && selmon->isoverview)
return 0;
if (selmon->sel && !selmon->sel->isminimized) {
set_minimized(selmon->sel);
Client *c = arg->tc ? arg->tc : selmon->sel;
if (c && !c->isminimized) {
set_minimized(c);
}
return 0;
}
@ -1747,8 +1699,9 @@ int32_t toggleoverview(const Arg *arg) {
if (!selmon)
return 0;
if (selmon->isoverview && config.ov_tab_mode && arg->i != 1 &&
selmon->sel) {
Client *sel = arg->tc ? arg->tc : selmon->sel;
if (selmon->isoverview && config.ov_tab_mode && arg->i != 1 && sel) {
focusstack(&(Arg){.i = 1});
return 0;
}
@ -1773,9 +1726,9 @@ int32_t toggleoverview(const Arg *arg) {
selmon->isoverview ^= 1;
return 0;
}
} else if (!selmon->isoverview && selmon->sel) {
target = get_tags_first_tag(selmon->sel->tags);
} else if (!selmon->isoverview && !selmon->sel) {
} else if (!selmon->isoverview && sel) {
target = get_tags_first_tag(sel->tags);
} else if (!selmon->isoverview && !sel) {
target = (1 << (selmon->pertag->prevtag - 1));
view(&(Arg){.ui = target}, false);
fix_mon_tagset_from_overview(selmon);
@ -1783,11 +1736,7 @@ int32_t toggleoverview(const Arg *arg) {
return 0;
}
// 正常视图到overview,退出所有窗口的浮动和全屏状态参与平铺,
// overview到正常视图,还原之前退出的浮动和全屏窗口状态
if (selmon->isoverview) {
// 让游戏窗口无法强制约束鼠标
wlr_seat_pointer_clear_focus(seat);
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
@ -1878,7 +1827,6 @@ int32_t scroller_apply_stack(Client *c, Client *target_client,
struct TagScrollerState *st = ensure_scroller_state(m, tag);
/* 获取当前节点 */
struct ScrollerStackNode *cnode = find_scroller_node(st, c);
if (!cnode)
@ -1887,20 +1835,16 @@ int32_t scroller_apply_stack(Client *c, Client *target_client,
struct ScrollerStackNode *tnode =
target_client ? find_scroller_node(st, target_client) : NULL;
/* 若方向为 UNDIR 且有目标,直接插入到目标尾部 */
if (direction == UNDIR && target_client && target_client->mon == c->mon) {
scroller_insert_stack(c, target_client, false);
return 0;
}
/* 处理从堆叠中移出的情况(方向 LEFT/UP 或 RIGHT/DOWN */
if (cnode->prev_in_stack || cnode->next_in_stack) {
struct ScrollerStackNode *move_out_refer_node =
cnode->prev_in_stack ? cnode->prev_in_stack : cnode->next_in_stack;
scroller_node_remove(st, cnode);
// 必须先更新不然里面节点还存着的是cnode的信息
// 会造成stach_head/stack_tail指向的客户端不对
update_scroller_state(c->mon);
Client *stack_head =
@ -1927,12 +1871,10 @@ int32_t scroller_apply_stack(Client *c, Client *target_client,
if (!tnode || target_client->mon != c->mon)
return 0;
/* 找到目标堆叠的尾部节点 */
struct ScrollerStackNode *tail = tnode;
while (tail->next_in_stack)
tail = tail->next_in_stack;
/* 通过封装好的插入函数实现(尾部插入) */
scroller_insert_stack(c, tail->client, false);
if (c != tail->client) {
@ -1945,7 +1887,7 @@ int32_t scroller_apply_stack(Client *c, Client *target_client,
int32_t scroller_stack(const Arg *arg) {
if (!selmon)
return 0;
Client *c = selmon->sel;
Client *c = arg->tc ? arg->tc : selmon->sel;
if (!c || !c->mon || c->isfloating || !is_scroller_layout(selmon))
return 0;
@ -1955,15 +1897,18 @@ int32_t scroller_stack(const Arg *arg) {
}
int32_t toggle_all_floating(const Arg *arg) {
if (!selmon || !selmon->sel)
if (!selmon)
return 0;
Client *c = NULL;
bool should_floating = !selmon->sel->isfloating;
Client *ref = arg->tc ? arg->tc : selmon->sel;
if (!ref)
return 0;
bool should_floating = !ref->isfloating;
Client *c;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, selmon)) {
if (c->isfloating && !should_floating) {
c->old_master_inner_per = 0.0f;
c->old_stack_inner_per = 0.0f;
@ -1979,7 +1924,6 @@ int32_t toggle_all_floating(const Arg *arg) {
}
int32_t dwindle_set_split_direction(Client *c, bool istoggle, bool horizontal) {
const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag];
if (layout->id != DWINDLE)
@ -2004,31 +1948,40 @@ int32_t dwindle_set_split_direction(Client *c, bool istoggle, bool horizontal) {
}
int32_t dwindle_toggle_split_direction(const Arg *arg) {
if (!selmon || !selmon->sel)
if (!selmon)
return 0;
Client *c = selmon->sel;
Client *c = arg->tc ? arg->tc : selmon->sel;
if (!c || !c->mon || c->isfloating)
return 0;
return dwindle_set_split_direction(selmon->sel, true, false);
return dwindle_set_split_direction(c, true, false);
}
int32_t dwindle_split_horizontal(const Arg *arg) {
if (!selmon || !selmon->sel)
if (!selmon)
return 0;
Client *c = selmon->sel;
Client *c = arg->tc ? arg->tc : selmon->sel;
if (!c || !c->mon || c->isfloating)
return 0;
return dwindle_set_split_direction(selmon->sel, false, true);
return dwindle_set_split_direction(c, false, true);
}
int32_t dwindle_split_vertical(const Arg *arg) {
if (!selmon || !selmon->sel)
if (!selmon)
return 0;
Client *c = selmon->sel;
Client *c = arg->tc ? arg->tc : selmon->sel;
if (!c || !c->mon || c->isfloating)
return 0;
return dwindle_set_split_direction(selmon->sel, false, false);
return dwindle_set_split_direction(c, false, false);
}
int32_t focusid(const Arg *arg) {
if (!selmon || !arg->tc)
return 0;
Client *c = arg->tc;
focusclient(c, 1);
return 0;
}

View file

@ -192,7 +192,7 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) {
if (wl_resource_get_version(ipc_output->resource) >=
ZDWL_IPC_OUTPUT_V2_LAST_LAYER_SINCE_VERSION) {
zdwl_ipc_output_v2_send_last_layer(ipc_output->resource,
monitor->last_surface_ws_name);
monitor->last_open_surface);
}
if (wl_resource_get_version(ipc_output->resource) >=

View file

@ -452,7 +452,7 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating,
Client *direction_select(const Arg *arg) {
Client *tc = selmon->sel;
Client *tc = arg->tc ? arg->tc : selmon->sel;
if (!tc)
return NULL;
@ -593,12 +593,12 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
return false;
}
Client *get_focused_stack_client(Client *sc) {
Client *get_focused_stack_client(Client *sc, Client *custom_focus_client) {
if (!sc || sc->isfloating)
return sc;
Client *tc = NULL;
Client *fc = focustop(sc->mon);
Client *fc = custom_focus_client ? custom_focus_client : focustop(sc->mon);
if (fc->isfloating || sc->isfloating)
return sc;
@ -616,4 +616,4 @@ Client *get_focused_stack_client(Client *sc) {
}
}
return sc;
}
}

970
src/ipc/ipc.h Normal file
View file

@ -0,0 +1,970 @@
#include <cjson/cJSON.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
enum ipc_watch_type {
IPC_WATCH_NONE,
IPC_WATCH_MONITOR,
IPC_WATCH_CLIENT,
IPC_WATCH_TAGS,
IPC_WATCH_ALL_MONITORS,
IPC_WATCH_ALL_TAGS,
IPC_WATCH_ALL_CLIENTS,
IPC_WATCH_KEYMODE,
IPC_WATCH_KB_LAYOUT,
IPC_WATCH_LAST_OPEN_SURFACE,
};
struct ipc_watch_client {
struct wl_list link;
int fd;
struct wl_event_source *source;
enum ipc_watch_type type;
union {
struct {
char name[64];
} monitor;
struct {
uint32_t id;
} client;
struct {
char mon_name[64];
} tags;
} target;
};
static struct wl_list watch_clients;
struct ipc_client_state {
int fd;
struct wl_event_source *source;
struct wl_event_loop *loop;
char *buf;
size_t buf_len;
size_t buf_cap;
};
static void ipc_remove_watch_client(struct ipc_watch_client *wc);
static void ipc_notify_json_to_fd(int fd, cJSON *json);
/* ---------- 工具函数 ---------- */
static Monitor *monitor_by_name(const char *name) {
Monitor *m;
wl_list_for_each(m, &mons, link) {
if (strcmp(m->wlr_output->name, name) == 0)
return m;
}
return NULL;
}
static Client *client_by_id(uint32_t id) {
Client *c;
wl_list_for_each(c, &clients, link) {
if (c->id == id)
return c;
}
return NULL;
}
static const char *ipc_get_layout_str(void) {
struct wlr_keyboard *keyboard = &kb_group->wlr_group->keyboard;
xkb_layout_index_t current = xkb_state_serialize_layout(
keyboard->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE);
static char layout[32];
get_layout_abbr(layout,
xkb_keymap_layout_get_name(keyboard->keymap, current));
return layout;
}
static cJSON *tags_mask_to_array(uint32_t tagmask) {
cJSON *arr = cJSON_CreateArray();
for (int i = 0; i < LENGTH(tags); i++)
if (tagmask & (1 << i))
cJSON_AddItemToArray(arr, cJSON_CreateNumber(i + 1));
return arr;
}
static cJSON *build_tags_json(Monitor *m) {
cJSON *tags_array = cJSON_CreateArray();
Client *c = NULL;
for (int tag = 1; tag <= LENGTH(tags); tag++) {
int numclients = 0;
bool is_active = false, is_urgent = false;
uint32_t tagmask = 1 << (tag - 1);
if (tagmask & m->tagset[m->seltags])
is_active = true;
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (!(c->tags & tagmask & TAGMASK))
continue;
if (c->isurgent)
is_urgent = true;
numclients++;
}
cJSON *tag_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(tag_obj, "index", tag);
cJSON_AddBoolToObject(tag_obj, "is_active", is_active);
cJSON_AddBoolToObject(tag_obj, "is_urgent", is_urgent);
cJSON_AddStringToObject(tag_obj, "layout",
m->pertag->ltidxs[tag]->symbol);
cJSON_AddNumberToObject(tag_obj, "client_count", numclients);
cJSON_AddItemToArray(tags_array, tag_obj);
}
return tags_array;
}
static cJSON *monitor_active_client(Monitor *m) {
cJSON *obj = cJSON_CreateObject();
if (!m->sel) {
cJSON_AddNullToObject(obj, "id");
cJSON_AddNullToObject(obj, "title");
cJSON_AddNullToObject(obj, "appid");
return obj;
}
Client *c = m->sel;
cJSON_AddNumberToObject(obj, "id", c->id);
cJSON_AddStringToObject(obj, "title", client_get_title(c));
cJSON_AddStringToObject(obj, "appid", client_get_appid(c));
return obj;
}
static cJSON *monitor_active_tags(Monitor *m) {
cJSON *arr = cJSON_CreateArray();
uint32_t tagset;
if (m->isoverview) {
cJSON_AddItemToArray(arr, cJSON_CreateNumber(0));
return arr;
}
tagset = m->tagset[m->seltags];
for (int i = 0; i < LENGTH(tags); i++)
if (tagset & (1 << i))
cJSON_AddItemToArray(arr, cJSON_CreateNumber(i + 1));
return arr;
}
static cJSON *build_client_json(Client *c) {
cJSON *obj = cJSON_CreateObject();
cJSON_AddNumberToObject(obj, "id", c->id);
cJSON_AddNumberToObject(obj, "pid", c->pid);
cJSON_AddStringToObject(obj, "title", client_get_title(c));
cJSON_AddStringToObject(obj, "appid", client_get_appid(c));
cJSON_AddStringToObject(obj, "monitor", c->mon->wlr_output->name);
cJSON_AddItemToObject(obj, "tags", tags_mask_to_array(c->tags));
cJSON_AddBoolToObject(obj, "is_focused", c == focustop(c->mon));
cJSON_AddBoolToObject(obj, "is_fullscreen", c->isfullscreen);
cJSON_AddBoolToObject(obj, "is_floating", c->isfloating);
cJSON_AddBoolToObject(obj, "is_maximized", c->ismaximizescreen);
cJSON_AddBoolToObject(obj, "is_global", c->isglobal);
cJSON_AddBoolToObject(obj, "is_unglobal", c->isunglobal);
cJSON_AddBoolToObject(obj, "is_overlay", c->isoverlay);
cJSON_AddBoolToObject(obj, "is_fakefullscreen", c->isfakefullscreen);
cJSON_AddBoolToObject(obj, "is_minimized", c->isminimized);
cJSON_AddBoolToObject(obj, "is_urgent", c->isurgent);
cJSON_AddBoolToObject(obj, "is_scratchpad", c->is_in_scratchpad);
cJSON_AddBoolToObject(obj, "is_namedscratchpad", c->isnamedscratchpad);
cJSON_AddNumberToObject(obj, "x", c->geom.x);
cJSON_AddNumberToObject(obj, "y", c->geom.y);
cJSON_AddNumberToObject(obj, "width", c->geom.width);
cJSON_AddNumberToObject(obj, "height", c->geom.height);
return obj;
}
static cJSON *build_monitor_json(Monitor *m) {
cJSON *resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "name", m->wlr_output->name);
cJSON_AddBoolToObject(resp, "active", m == selmon);
cJSON_AddNumberToObject(resp, "x", m->m.x);
cJSON_AddNumberToObject(resp, "y", m->m.y);
cJSON_AddNumberToObject(resp, "width", m->m.width);
cJSON_AddNumberToObject(resp, "height", m->m.height);
cJSON_AddNumberToObject(resp, "scale", m->wlr_output->scale);
cJSON_AddNumberToObject(resp, "layout_index",
m->pertag->ltidxs[m->pertag->curtag] - layouts);
cJSON_AddStringToObject(resp, "layout_symbol",
m->pertag->ltidxs[m->pertag->curtag]->symbol);
cJSON_AddStringToObject(resp, "last_open_surface", m->last_open_surface);
cJSON_AddItemToObject(resp, "tags", build_tags_json(m));
cJSON_AddItemToObject(resp, "active_tags", monitor_active_tags(m));
cJSON_AddItemToObject(resp, "active_client", monitor_active_client(m));
cJSON_AddItemToObject(resp, "keymode", cJSON_CreateString(keymode.mode));
cJSON_AddItemToObject(resp, "keyboardlayout",
cJSON_CreateString(ipc_get_layout_str()));
return resp;
}
static cJSON *build_all_tags_entry(Monitor *m) {
cJSON *entry = cJSON_CreateObject();
cJSON_AddStringToObject(entry, "monitor", m->wlr_output->name);
cJSON_AddItemToObject(entry, "tags", build_tags_json(m));
return entry;
}
static cJSON *build_all_tags_response(void) {
cJSON *arr = cJSON_CreateArray();
Monitor *m;
wl_list_for_each(m, &mons, link)
cJSON_AddItemToArray(arr, build_all_tags_entry(m));
cJSON *resp = cJSON_CreateObject();
cJSON_AddItemToObject(resp, "all_tags", arr);
return resp;
}
static cJSON *build_monitor_tags_response(Monitor *m) {
cJSON *resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name);
cJSON_AddItemToObject(resp, "tags", build_tags_json(m));
cJSON_AddItemToObject(resp, "active_tags", monitor_active_tags(m));
return resp;
}
static void send_static_json(int fd, const char *json_str) {
size_t len = strlen(json_str);
send(fd, json_str, len, MSG_NOSIGNAL);
}
/* ---------- 一次性命令处理 ---------- */
static void handle_command(int client_fd, const char *cmd_raw) {
cJSON *resp = NULL;
char *json_str = NULL;
char cmd[1024];
strncpy(cmd, cmd_raw, sizeof(cmd) - 1);
cmd[sizeof(cmd) - 1] = '\0';
for (char *p = cmd; *p; p++)
if (*p == ',')
*p = ' ';
if (strcmp(cmd, "get version") == 0) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "version", VERSION);
} else if (strcmp(cmd, "get keymode") == 0) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "keymode", keymode.mode);
} else if (strcmp(cmd, "get keyboardlayout") == 0) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "layout", ipc_get_layout_str());
} else if (strncmp(cmd, "get last_open_surface ", 25) == 0) {
const char *name = cmd + 25;
Monitor *m = monitor_by_name(name);
if (!m) {
send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n");
return;
}
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name);
cJSON_AddStringToObject(resp, "last_open_surface",
m->last_open_surface);
} else if (strncmp(cmd, "get monitor ", 12) == 0) {
Monitor *m = monitor_by_name(cmd + 12);
if (!m) {
send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n");
return;
}
resp = build_monitor_json(m);
} else if (strncmp(cmd, "get client ", 11) == 0) {
Client *c = client_by_id((uint32_t)atoi(cmd + 11));
if (!c) {
send_static_json(client_fd, "{\"error\":\"client not found\"}\n");
return;
}
resp = build_client_json(c);
} else if (strncmp(cmd, "get tag ", 8) == 0) {
char mon_name[64];
int ext_tag_idx;
if (sscanf(cmd + 8, "%63s %d", mon_name, &ext_tag_idx) != 2) {
send_static_json(
client_fd,
"{\"error\":\"usage: get tag <monitor> <index>\"}\n");
return;
}
int tag_idx = ext_tag_idx - 1;
Monitor *m = monitor_by_name(mon_name);
if (!m || tag_idx < 0 || tag_idx >= LENGTH(tags)) {
send_static_json(client_fd,
"{\"error\":\"invalid monitor or tag index\"}\n");
return;
}
uint32_t tagmask = 1 << tag_idx;
int numclients = 0, focused_client = 0;
bool is_active = false, is_urgent = false;
if (tagmask & m->tagset[m->seltags])
is_active = true;
Client *c, *focused = focustop(m);
wl_list_for_each(c, &clients, link) {
if (c->mon != m || !(c->tags & tagmask))
continue;
if (c == focused)
focused_client = 1;
if (c->isurgent)
is_urgent = true;
numclients++;
}
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name);
cJSON_AddNumberToObject(resp, "tag_index", ext_tag_idx);
cJSON_AddBoolToObject(resp, "is_active", is_active);
cJSON_AddBoolToObject(resp, "is_urgent", is_urgent);
cJSON_AddNumberToObject(resp, "client_count", numclients);
cJSON_AddBoolToObject(resp, "focused_client", focused_client);
} else if (strcmp(cmd, "get all-clients") == 0) {
cJSON *arr = cJSON_CreateArray();
Client *c;
wl_list_for_each(c, &clients, link)
cJSON_AddItemToArray(arr, build_client_json(c));
resp = cJSON_CreateObject();
cJSON_AddItemToObject(resp, "clients", arr);
} else if (strcmp(cmd, "get all-monitors") == 0) {
cJSON *arr = cJSON_CreateArray();
Monitor *m;
wl_list_for_each(m, &mons, link)
cJSON_AddItemToArray(arr, build_monitor_json(m));
resp = cJSON_CreateObject();
cJSON_AddItemToObject(resp, "monitors", arr);
} else if (strcmp(cmd, "get all-tags") == 0) {
resp = build_all_tags_response();
} else if (strncmp(cmd, "get tags ", 9) == 0) {
Monitor *m = monitor_by_name(cmd + 9);
if (!m) {
send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n");
return;
}
resp = build_monitor_tags_response(m);
} else if (strncmp(cmd, "dispatch ", 9) == 0) {
char *dispatch_copy = strdup(cmd_raw + 9);
char *out = dispatch_copy, *ptr = dispatch_copy;
int client_id = -1;
while (*ptr) {
while (*ptr == ' ' || *ptr == '\t')
*out++ = *ptr++;
if (strncmp(ptr, "client,", 7) == 0) {
char *end;
long id = strtol(ptr + 7, &end, 10);
if (id > 0 && end > ptr + 7 && (*end == '\0' || *end == ',')) {
client_id = (int)id;
ptr = end;
if (*ptr == ',')
ptr++;
continue;
}
}
*out++ = *ptr++;
}
*out = '\0';
char *tokens[6] = {NULL};
int token_count = 0;
char *saveptr;
char *token = strtok_r(dispatch_copy, ",", &saveptr);
while (token && token_count < 6) {
while (*token == ' ' || *token == '\t')
token++;
char *end = token + strlen(token) - 1;
while (end >= token && (*end == ' ' || *end == '\t'))
*end-- = '\0';
tokens[token_count++] = token;
token = strtok_r(NULL, ",", &saveptr);
}
Arg arg = {0};
int32_t (*func)(const Arg *) =
parse_func_name(token_count > 0 ? tokens[0] : "", &arg,
token_count > 1 ? tokens[1] : NULL,
token_count > 2 ? tokens[2] : NULL,
token_count > 3 ? tokens[3] : NULL,
token_count > 4 ? tokens[4] : NULL,
token_count > 5 ? tokens[5] : NULL);
if (func && client_id > 0)
arg.tc = client_by_id((uint32_t)client_id);
if (func) {
func(&arg);
send_static_json(client_fd, "{\"success\":true}\n");
} else {
send_static_json(client_fd, "{\"error\":\"unknown function\"}\n");
}
if (arg.v)
free(arg.v);
if (arg.v2)
free(arg.v2);
if (arg.v3)
free(arg.v3);
free(dispatch_copy);
return; // Fast path exit
} else {
send_static_json(client_fd, "{\"error\":\"unknown command\"}\n");
return;
}
if (resp) {
json_str = cJSON_PrintUnformatted(resp);
if (json_str) {
size_t len = strlen(json_str);
char *msg = malloc(len + 2);
if (msg) {
snprintf(msg, len + 2, "%s\n", json_str);
send(client_fd, msg, len + 1, MSG_NOSIGNAL);
free(msg);
}
free(json_str);
}
cJSON_Delete(resp);
}
}
/* ---------- Watch 模式支持 ---------- */
static void ipc_notify_json_to_fd(int fd, cJSON *json) {
char *str = cJSON_PrintUnformatted(json);
if (!str)
return;
size_t len = strlen(str);
char *msg = malloc(len + 2);
if (!msg) {
free(str);
return;
}
snprintf(msg, len + 2, "%s\n", str);
if (send(fd, msg, len + 1, MSG_NOSIGNAL) < 0) {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->fd == fd) {
ipc_remove_watch_client(wc);
break;
}
}
}
free(msg);
free(str);
}
static void ipc_remove_watch_client(struct ipc_watch_client *wc) {
wl_list_remove(&wc->link);
wl_event_source_remove(wc->source);
close(wc->fd);
free(wc);
}
static int ipc_watch_data_handler(int fd, uint32_t mask, void *data) {
struct ipc_watch_client *wc = data;
if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) {
ipc_remove_watch_client(wc);
return 0;
}
if (mask & WL_EVENT_READABLE) {
char buf[64];
ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
ipc_remove_watch_client(wc);
}
}
return 0;
}
static bool handle_watch_command(int fd, const char *cmd,
struct ipc_client_state *client) {
enum ipc_watch_type type = IPC_WATCH_NONE;
const char *arg = NULL;
uint32_t client_id = 0;
if (strncmp(cmd, "watch monitor ", 14) == 0) {
type = IPC_WATCH_MONITOR;
arg = cmd + 14;
} else if (strncmp(cmd, "watch client ", 13) == 0) {
type = IPC_WATCH_CLIENT;
client_id = (uint32_t)atoi(cmd + 13);
} else if (strncmp(cmd, "watch tags ", 11) == 0) {
type = IPC_WATCH_TAGS;
arg = cmd + 11;
} else if (strcmp(cmd, "watch all-monitors") == 0) {
type = IPC_WATCH_ALL_MONITORS;
} else if (strcmp(cmd, "watch all-tags") == 0) {
type = IPC_WATCH_ALL_TAGS;
} else if (strcmp(cmd, "watch all-clients") == 0) {
type = IPC_WATCH_ALL_CLIENTS;
} else if (strcmp(cmd, "watch keymode") == 0) {
type = IPC_WATCH_KEYMODE;
} else if (strcmp(cmd, "watch keyboardlayout") == 0) {
type = IPC_WATCH_KB_LAYOUT;
} else if (strncmp(cmd, "watch last_open_surface ", 27) == 0) {
type = IPC_WATCH_LAST_OPEN_SURFACE;
arg = cmd + 27;
}
if (type == IPC_WATCH_NONE)
return false;
struct ipc_watch_client *wc = calloc(1, sizeof(*wc));
wc->fd = fd;
wc->type = type;
if ((type == IPC_WATCH_MONITOR || type == IPC_WATCH_LAST_OPEN_SURFACE) &&
arg)
snprintf(wc->target.monitor.name, sizeof(wc->target.monitor.name), "%s",
arg);
else if (type == IPC_WATCH_TAGS && arg)
snprintf(wc->target.tags.mon_name, sizeof(wc->target.tags.mon_name),
"%s", arg);
else if (type == IPC_WATCH_CLIENT)
wc->target.client.id = client_id;
wl_event_source_remove(client->source);
wc->source = wl_event_loop_add_fd(
client->loop, fd, WL_EVENT_READABLE | WL_EVENT_HANGUP | WL_EVENT_ERROR,
ipc_watch_data_handler, wc);
wl_list_insert(&watch_clients, &wc->link);
/* 推送初始状态 */
cJSON *json = NULL;
switch (type) {
case IPC_WATCH_MONITOR: {
Monitor *m = monitor_by_name(arg);
if (m)
json = build_monitor_json(m);
break;
}
case IPC_WATCH_LAST_OPEN_SURFACE: {
Monitor *m = monitor_by_name(arg);
if (m) {
json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "monitor", m->wlr_output->name);
cJSON_AddStringToObject(json, "last_open_surface",
m->last_open_surface);
}
break;
}
case IPC_WATCH_CLIENT: {
Client *c = client_by_id(client_id);
if (c)
json = build_client_json(c);
break;
}
case IPC_WATCH_TAGS: {
Monitor *m = monitor_by_name(arg);
if (m)
json = build_monitor_tags_response(m);
break;
}
case IPC_WATCH_ALL_MONITORS: {
cJSON *arr = cJSON_CreateArray();
Monitor *m;
wl_list_for_each(m, &mons, link)
cJSON_AddItemToArray(arr, build_monitor_json(m));
json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "monitors", arr);
break;
}
case IPC_WATCH_ALL_TAGS: {
json = build_all_tags_response();
break;
}
case IPC_WATCH_ALL_CLIENTS: {
cJSON *arr = cJSON_CreateArray();
Client *c;
wl_list_for_each(c, &clients, link)
cJSON_AddItemToArray(arr, build_client_json(c));
json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "clients", arr);
break;
}
case IPC_WATCH_KEYMODE: {
json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "keymode", keymode.mode);
break;
}
case IPC_WATCH_KB_LAYOUT: {
json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "layout", ipc_get_layout_str());
break;
}
default:
break;
}
if (json) {
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
}
free(client->buf);
free(client);
return true;
}
/* ---------- Socket 事件处理 ---------- */
static int ipc_handle_client_data(int fd, uint32_t mask, void *data) {
struct ipc_client_state *client = data;
if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR))
goto cleanup;
if (mask & WL_EVENT_READABLE) {
size_t available = client->buf_cap - client->buf_len;
if (available < 4096) {
size_t new_cap = client->buf_cap ? client->buf_cap * 2 : 8192;
char *new_buf = realloc(client->buf, new_cap);
if (!new_buf) {
wlr_log(WLR_ERROR, "IPC: out of memory");
goto cleanup;
}
client->buf = new_buf;
client->buf_cap = new_cap;
available = client->buf_cap - client->buf_len;
}
// 直接读取到 client->buf 尾部,跳过临时数组
ssize_t n = recv(fd, client->buf + client->buf_len, available - 1,
MSG_DONTWAIT);
if (n <= 0)
goto cleanup;
client->buf_len += n;
client->buf[client->buf_len] = '\0';
char *nl = memchr(client->buf, '\n', client->buf_len);
if (!nl) {
if (client->buf_len > 1024 * 1024)
goto cleanup; // 防御过长命令
return 0;
}
*nl = '\0';
char *cmd = client->buf;
bool is_watch = handle_watch_command(fd, cmd, client);
if (is_watch)
return 0;
handle_command(fd, cmd);
goto cleanup;
}
return 0;
cleanup:
close(client->fd);
wl_event_source_remove(client->source);
free(client->buf);
free(client);
return 0;
}
static int ipc_handle_connection(int fd, uint32_t mask, void *data) {
struct wl_event_loop *loop = data;
int client_fd = accept(fd, NULL, NULL);
if (client_fd < 0)
return 0;
int flags = fcntl(client_fd, F_GETFL, 0);
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
struct ipc_client_state *client = calloc(1, sizeof(*client));
client->fd = client_fd;
client->loop = loop;
client->source = wl_event_loop_add_fd(
loop, client_fd, WL_EVENT_READABLE | WL_EVENT_HANGUP | WL_EVENT_ERROR,
ipc_handle_client_data, client);
return 0;
}
/* ---------- 外部通知接口 ---------- */
void ipc_notify_monitor(Monitor *m) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_MONITOR &&
strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) {
if (!json_str) {
cJSON *json = build_monitor_json(m);
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
void ipc_notify_last_surface_ws_name(Monitor *m) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_LAST_OPEN_SURFACE &&
strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) {
if (!json_str) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "monitor", m->wlr_output->name);
cJSON_AddStringToObject(json, "last_open_surface",
m->last_open_surface);
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
void ipc_notify_client(Client *c) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_CLIENT && c->id == wc->target.client.id) {
if (!json_str) {
cJSON *json = build_client_json(c);
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
void ipc_notify_tags(Monitor *m) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_TAGS &&
strcmp(m->wlr_output->name, wc->target.tags.mon_name) == 0) {
if (!json_str) {
cJSON *json = build_monitor_tags_response(m);
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
void ipc_notify_all_monitors(void) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_ALL_MONITORS) {
if (!json_str) {
cJSON *arr = cJSON_CreateArray();
Monitor *m;
wl_list_for_each(m, &mons, link)
cJSON_AddItemToArray(arr, build_monitor_json(m));
cJSON *json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "monitors", arr);
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
void ipc_notify_all_clients(void) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_ALL_CLIENTS) {
if (!json_str) {
cJSON *arr = cJSON_CreateArray();
Client *c;
wl_list_for_each(c, &clients, link)
cJSON_AddItemToArray(arr, build_client_json(c));
cJSON *json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "clients", arr);
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
void ipc_notify_all_tags(void) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_ALL_TAGS) {
if (!json_str) {
cJSON *json = build_all_tags_response();
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
void ipc_notify_keymode(void) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_KEYMODE) {
if (!json_str) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "keymode", keymode.mode);
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
void ipc_notify_kb_layout(void) {
char *json_str = NULL;
size_t len = 0;
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_KB_LAYOUT) {
if (!json_str) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "layout", ipc_get_layout_str());
char *raw = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!raw)
return;
len = strlen(raw);
json_str = malloc(len + 2);
snprintf(json_str, len + 2, "%s\n", raw);
free(raw);
}
if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0)
ipc_remove_watch_client(wc);
}
}
if (json_str)
free(json_str);
}
/* ---------- 初始化与清理 ---------- */
static int ipc_sock_fd = -1;
static struct wl_event_source *ipc_event_source = NULL;
static char ipc_socket_path[256];
void ipc_init(struct wl_event_loop *event_loop) {
wl_list_init(&watch_clients);
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (!xdg_runtime)
return;
snprintf(ipc_socket_path, sizeof(ipc_socket_path), "%s/mango-%d.sock",
xdg_runtime, getpid());
ipc_sock_fd =
socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
if (ipc_sock_fd < 0)
return;
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, ipc_socket_path, sizeof(addr.sun_path) - 1);
unlink(ipc_socket_path);
if (bind(ipc_sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(ipc_sock_fd);
return;
}
listen(ipc_sock_fd, 16);
setenv("MANGO_INSTANCE_SIGNATURE", ipc_socket_path, 1);
ipc_event_source =
wl_event_loop_add_fd(event_loop, ipc_sock_fd, WL_EVENT_READABLE,
ipc_handle_connection, event_loop);
}
void ipc_cleanup(void) {
if (ipc_event_source)
wl_event_source_remove(ipc_event_source);
if (ipc_sock_fd >= 0)
close(ipc_sock_fd);
unlink(ipc_socket_path);
unsetenv("MANGO_INSTANCE_SIGNATURE");
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link)
ipc_remove_watch_client(wc);
}

View file

@ -197,28 +197,6 @@ enum seat_config_shortcuts_inhibit {
SHORTCUTS_INHIBIT_ENABLE,
};
// 事件掩码枚举
enum print_event_type {
PRINT_ACTIVE = 1 << 0,
PRINT_TAG = 1 << 1,
PRINT_LAYOUT = 1 << 2,
PRINT_TITLE = 1 << 3,
PRINT_APPID = 1 << 4,
PRINT_LAYOUT_SYMBOL = 1 << 5,
PRINT_FULLSCREEN = 1 << 6,
PRINT_FLOATING = 1 << 7,
PRINT_X = 1 << 8,
PRINT_Y = 1 << 9,
PRINT_WIDTH = 1 << 10,
PRINT_HEIGHT = 1 << 11,
PRINT_LAST_LAYER = 1 << 12,
PRINT_KB_LAYOUT = 1 << 13,
PRINT_KEYMODE = 1 << 14,
PRINT_SCALEFACTOR = 1 << 15,
PRINT_FRAME = 1 << 16,
PRINT_ALL = (1 << 17) - 1 // 所有位都设为1
};
typedef struct Pertag Pertag;
typedef struct Monitor Monitor;
typedef struct Client Client;
@ -241,6 +219,7 @@ typedef struct {
char *v3;
uint32_t ui;
uint32_t ui2;
Client *tc;
} Arg;
typedef struct {
@ -441,6 +420,7 @@ struct Client {
float old_grid_row_per;
int32_t grid_col_idx;
int32_t grid_row_idx;
uint32_t id;
};
typedef struct {
@ -557,7 +537,7 @@ struct Monitor {
uint32_t visible_scroll_tiling_clients;
uint32_t visible_fake_tiling_clients;
struct wlr_scene_optimized_blur *blur;
char last_surface_ws_name[256];
char last_open_surface[256];
struct wlr_ext_workspace_group_handle_v1 *ext_group;
bool iscleanuping;
int8_t carousel_anim_dir;
@ -857,7 +837,8 @@ static Client *find_client_by_direction(Client *tc, const Arg *arg,
static void exit_scroller_stack(Client *c);
static Client *scroll_get_stack_head_client(Client *c);
static bool client_only_in_one_tag(Client *c);
static Client *get_focused_stack_client(Client *sc);
static Client *get_focused_stack_client(Client *sc,
Client *custom_focus_client);
static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc);
static void monitor_stop_skip_frame_timer(Monitor *m);
static int monitor_skip_frame_timeout_callback(void *data);
@ -1005,6 +986,7 @@ static char *env_vars[] = {"DISPLAY",
"XDG_SESSION_TYPE",
"XCURSOR_THEME",
"XCURSOR_SIZE",
"MANGO_INSTANCE_SIGNATURE",
NULL};
static struct {
enum wp_cursor_shape_device_v1_shape shape;
@ -1094,6 +1076,7 @@ static struct wl_event_source *sync_keymap;
#include "dispatch/bind_define.h"
#include "ext-protocol/all.h"
#include "fetch/fetch.h"
#include "ipc/ipc.h"
#include "layout/arrange.h"
#include "layout/dwindle.h"
#include "layout/horizontal.h"
@ -2511,6 +2494,7 @@ void cleanuplisteners(void) {
}
void cleanup(void) {
ipc_cleanup();
cleanuplisteners();
#ifdef XWAYLAND
wlr_xwayland_destroy(xwayland);
@ -2668,9 +2652,9 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) {
if (!l->mon)
return;
strncpy(l->mon->last_surface_ws_name, layer_surface->namespace,
sizeof(l->mon->last_surface_ws_name) - 1); // 最多拷贝255个字符
l->mon->last_surface_ws_name[sizeof(l->mon->last_surface_ws_name) - 1] =
strncpy(l->mon->last_open_surface, layer_surface->namespace,
sizeof(l->mon->last_open_surface) - 1); // 最多拷贝255个字符
l->mon->last_open_surface[sizeof(l->mon->last_open_surface) - 1] =
'\0'; // 确保字符串以null结尾
// 初始化几何位置
@ -4418,6 +4402,9 @@ mapnotify(struct wl_listener *listener, void *data) {
Client *at_client = NULL;
Client *c = wl_container_of(listener, c, map);
int32_t i = 0;
c->id = generate_client_id();
/* Create scene tree for this client and its border */
c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]);
wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell);
@ -5721,13 +5708,30 @@ void create_output(struct wlr_backend *backend, void *data) {
// 修改信号处理函数,接收掩码参数
void handle_print_status(struct wl_listener *listener, void *data) {
ipc_notify_keymode();
ipc_notify_kb_layout();
ipc_notify_all_tags();
ipc_notify_all_clients();
ipc_notify_all_monitors();
Client *c = NULL;
wl_list_for_each(c, &clients, link) {
if (c->iskilling)
continue;
ipc_notify_client(c);
}
Monitor *m = NULL;
wl_list_for_each(m, &mons, link) {
if (!m->wlr_output->enabled) {
continue;
}
dwl_ext_workspace_printstatus(m);
ipc_notify_monitor(m);
ipc_notify_tags(m);
ipc_notify_last_surface_ws_name(m);
dwl_ext_workspace_printstatus(m);
dwl_ipc_output_printstatus(m);
}
}
@ -5756,6 +5760,9 @@ void setup(void) {
* clients from the Unix socket, manging Wayland globals, and so on. */
dpy = wl_display_create();
event_loop = wl_display_get_event_loop(dpy);
ipc_init(event_loop);
tablet_mgr = wlr_tablet_v2_create(dpy);
/* The backend is a wlroots feature which abstracts the underlying input
* and output hardware. The autocreate option will choose the most