feat: new ipc impl

This commit is contained in:
DreamMaoMao 2026-05-24 13:31:31 +08:00
parent 7da47c9d94
commit 42fb79fc06
9 changed files with 925 additions and 856 deletions

View file

@ -1,3 +1,3 @@
#!/usr/bin/bash #!/usr/bin/bash
clang-format -i src/*/*.h -i src/*/*.c -i src/mango.c -i mmsg/mmsg.c -i mmsg/arg.h -i mmsg/dynarr.h clang-format -i src/*/*.h -i src/*/*.c -i src/mango.c -i mmsg/mmsg.c

View file

@ -40,6 +40,7 @@ libwayland_client_dep = dependency('wayland-client')
pcre2_dep = dependency('libpcre2-8') pcre2_dep = dependency('libpcre2-8')
libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1') libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1')
pixman_dep = dependency('pixman-1') pixman_dep = dependency('pixman-1')
cjson_dep = dependency('libcjson')
# 获取版本信息 # 获取版本信息
@ -112,6 +113,7 @@ executable('mango',
libwayland_client_dep, libwayland_client_dep,
pcre2_dep, pcre2_dep,
pixman_dep, pixman_dep,
cjson_dep,
], ],
install : true, install : true,
c_args : c_args, c_args : c_args,

View file

@ -1,65 +0,0 @@
/*
* Copy me if you can.
* by 20h
*/
#ifndef ARG_H__
#define ARG_H__
extern char *argv0;
/* use main(int32_t argc, char *argv[]) */
#define ARGBEGIN \
for (argv0 = *argv, argv++, argc--; \
argv[0] && argv[0][0] == '-' && argv[0][1]; argc--, argv++) { \
char argc_; \
char **argv_; \
int32_t brk_; \
if (argv[0][1] == '-' && argv[0][2] == '\0') { \
argv++; \
argc--; \
break; \
} \
for (brk_ = 0, argv[0]++, argv_ = argv; argv[0][0] && !brk_; \
argv[0]++) { \
if (argv_ != argv) \
break; \
argc_ = argv[0][0]; \
switch (argc_)
/* Handles obsolete -NUM syntax */
#define ARGNUM \
case '0': \
case '1': \
case '2': \
case '3': \
case '4': \
case '5': \
case '6': \
case '7': \
case '8': \
case '9'
#define ARGEND \
} \
}
#define ARGC() argc_
#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX))
#define EARGF(x) \
((argv[0][1] == '\0' && argv[1] == NULL) \
? ((x), abort(), (char *)0) \
: (brk_ = 1, \
(argv[0][1] != '\0') ? (&argv[0][1]) : (argc--, argv++, argv[0])))
#define ARGF() \
((argv[0][1] == '\0' && argv[1] == NULL) \
? (char *)0 \
: (brk_ = 1, \
(argv[0][1] != '\0') ? (&argv[0][1]) : (argc--, argv++, argv[0])))
#define LNGARG() &argv[0][0]
#endif

View file

@ -1,30 +0,0 @@
#ifndef DYNARR_H__
#define DYNARR_H__
#define DYNARR_DEF(t) \
struct { \
t *arr; \
size_t len, cap, size; \
}
#define DYNARR_INIT(p) \
((p)->arr = reallocarray((p)->arr, ((p)->cap = 1), \
((p)->size = sizeof(((p)->arr[0])))))
#define DYNARR_FINI(p) free((p)->arr)
#define DYNARR_PUSH(p, v) \
do { \
if ((p)->len >= (p)->cap) { \
while ((p)->len >= ((p)->cap *= 2)) \
; \
(p)->arr = reallocarray((p)->arr, (p)->cap, (p)->size); \
} \
(p)->arr[(p)->len++] = (v); \
} while (0)
#define DYNARR_POP(p) ((p)->arr[(p)->len--])
#define DYNARR_CLR(p) ((p)->len = 0)
#endif

View file

@ -1,754 +1,73 @@
#include "arg.h" #include <stdbool.h>
#include "dwl-ipc-unstable-v2-protocol.h"
#include "dynarr.h"
#include <ctype.h>
#include <poll.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h> #include <unistd.h>
#include <wayland-client.h>
#include <wayland-util.h>
#define die(fmt, ...) \ int main(int argc, char *argv[]) {
do { \ if (argc < 2) {
fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ fprintf(stderr, "Usage: mmsg <command> [args...]\n");
exit(EXIT_FAILURE); \ fprintf(stderr, " get <type> ... one-shot request\n");
} while (0) fprintf(stderr, " watch <type> ... persistent stream\n");
return EXIT_FAILURE;
char *argv0;
static enum {
NONE = 0,
SET = 1 << 0,
GET = 1 << 1,
WATCH = 1 << 2 | GET,
} mode = NONE;
static int32_t Oflag;
static int32_t Tflag;
static int32_t Lflag;
static int32_t oflag;
static int32_t tflag;
static int32_t lflag;
static int32_t cflag;
static int32_t vflag;
static int32_t mflag;
static int32_t fflag;
static int32_t qflag;
static int32_t dflag;
static int32_t xflag;
static int32_t eflag;
static int32_t kflag;
static int32_t bflag;
static int32_t Aflag;
static uint32_t occ, seltags, total_clients, urg;
static char *output_name;
static int32_t tagcount;
static char *tagset;
static char *layout_name;
static int32_t layoutcount, layout_idx;
static char *client_tags;
static char *dispatch_cmd;
static char *dispatch_arg1;
static char *dispatch_arg2;
static char *dispatch_arg3;
static char *dispatch_arg4;
static char *dispatch_arg5;
struct output {
char *output_name;
uint32_t name;
};
static DYNARR_DEF(struct output) outputs;
static struct wl_display *display;
static struct zdwl_ipc_manager_v2 *dwl_ipc_manager;
// 为每个回调定义专用的空函数
static void noop_geometry(void *data, struct wl_output *wl_output, int32_t x,
int32_t y, int32_t physical_width,
int32_t physical_height, int32_t subpixel,
const char *make, const char *model,
int32_t transform) {}
static void noop_mode(void *data, struct wl_output *wl_output, uint32_t flags,
int32_t width, int32_t height, int32_t refresh) {}
static void noop_done(void *data, struct wl_output *wl_output) {}
static void noop_scale(void *data, struct wl_output *wl_output,
int32_t factor) {}
static void noop_description(void *data, struct wl_output *wl_output,
const char *description) {}
// 将 n 转换为 9 位二进制字符串,结果存入 buf至少长度 10
void bin_str_9bits(char *buf, uint32_t n) {
for (int32_t i = 8; i >= 0; i--) {
*buf++ = ((n >> i) & 1) ? '1' : '0';
} }
*buf = '\0'; // 字符串结尾
}
static void dwl_ipc_tags(void *data, const char *socket_path = getenv("MANGO_INSTANCE_SIGNATURE");
struct zdwl_ipc_manager_v2 *dwl_ipc_manager, if (!socket_path) {
uint32_t count) { fprintf(stderr, "Error: MANGO_INSTANCE_SIGNATURE not set.\n");
tagcount = count; return EXIT_FAILURE;
if (Tflag && mode & GET)
printf("%d\n", tagcount);
}
static void dwl_ipc_layout(void *data,
struct zdwl_ipc_manager_v2 *dwl_ipc_manager,
const char *name) {
if (lflag && mode & SET && strcmp(layout_name, name) == 0)
layout_idx = layoutcount;
if (Lflag && mode & GET)
printf("%s\n", name);
layoutcount++;
}
static const struct zdwl_ipc_manager_v2_listener dwl_ipc_listener = {
.tags = dwl_ipc_tags, .layout = dwl_ipc_layout};
static void
dwl_ipc_output_toggle_visibility(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output) {
if (!vflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("toggle\n");
}
static void dwl_ipc_output_active(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
uint32_t active) {
if (!oflag) {
if (mode & SET && !output_name && active)
output_name = strdup(data);
return;
} }
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("selmon %u\n", active ? 1 : 0);
}
static void dwl_ipc_output_tag(void *data, int sock = socket(AF_UNIX, SOCK_STREAM, 0);
struct zdwl_ipc_output_v2 *dwl_ipc_output, if (sock < 0) {
uint32_t tag, uint32_t state, uint32_t clients, perror("socket");
uint32_t focused) { return EXIT_FAILURE;
if (!tflag) }
return;
if (state == ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE)
seltags |= 1 << tag;
if (state == ZDWL_IPC_OUTPUT_V2_TAG_STATE_URGENT)
urg |= 1 << tag;
if (clients > 0)
occ |= 1 << tag;
// 累计所有 tag 的 clients 总数 struct sockaddr_un addr = {.sun_family = AF_UNIX};
total_clients += clients; strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (!(mode & GET)) if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
return; perror("connect");
char *output_name = data; close(sock);
if (output_name) return EXIT_FAILURE;
printf("%s ", output_name); }
printf("tag %u %u %u %u\n", tag + 1, state, clients, focused);
}
static void dwl_ipc_output_layout(void *data, /* 拼接命令 */
struct zdwl_ipc_output_v2 *dwl_ipc_output, char cmd[1024] = {0};
uint32_t layout) {} int offset = 0;
for (int i = 1; i < argc; i++) {
offset += snprintf(cmd + offset, sizeof(cmd) - offset, "%s%s", argv[i],
(i == argc - 1) ? "" : " ");
}
static void dwl_ipc_output_layout_symbol( send(sock, cmd, strlen(cmd), 0);
void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output, const char *layout) {
if (!(lflag && mode & GET))
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("layout %s\n", layout);
}
static void dwl_ipc_output_title(void *data, bool is_watch = (strncmp(argv[1], "watch", 5) == 0);
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *title) {
if (!(cflag && mode & GET))
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("title %s\n", title);
}
static void dwl_ipc_output_appid(void *data, if (is_watch) {
struct zdwl_ipc_output_v2 *dwl_ipc_output, /* watch 模式:持续接收 JSON 并输出,直到连接断开 */
const char *appid) { char buf[4096];
if (!(cflag && mode & GET)) ssize_t n;
return; while ((n = recv(sock, buf, sizeof(buf) - 1, 0)) > 0) {
char *output_name = data; buf[n] = '\0';
if (output_name) printf("%s", buf);
printf("%s ", output_name); fflush(stdout);
printf("appid %s\n", appid);
}
static void dwl_ipc_output_x(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
int32_t x) {
if (!xflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("x %d\n", x);
}
static void dwl_ipc_output_y(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
int32_t y) {
if (!xflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("y %d\n", y);
}
static void dwl_ipc_output_width(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
int32_t width) {
if (!xflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("width %d\n", width);
}
static void dwl_ipc_output_height(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
int32_t height) {
if (!xflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("height %d\n", height);
}
static void dwl_ipc_output_last_layer(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *last_layer) {
if (!eflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("last_layer %s\n", last_layer);
}
static void dwl_ipc_output_kb_layout(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *kb_layout) {
if (!kflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("kb_layout %s\n", kb_layout);
}
static void
dwl_ipc_output_scalefactor(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const uint32_t scalefactor) {
if (!Aflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("scale_factor %f\n", scalefactor / 100.0f);
}
static void dwl_ipc_output_keymode(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *keymode) {
if (!bflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("keymode %s\n", keymode);
}
static void dwl_ipc_output_fullscreen(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
uint32_t is_fullscreen) {
if (!mflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("fullscreen %u\n", is_fullscreen);
}
static void dwl_ipc_output_floating(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
uint32_t is_floating) {
if (!fflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("floating %u\n", is_floating);
}
static void dwl_ipc_output_frame(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output) {
if (mode & SET) {
if (data && (!output_name || strcmp(output_name, (char *)data)))
return;
if (qflag) {
zdwl_ipc_output_v2_quit(dwl_ipc_output);
} }
if (lflag) { close(sock);
zdwl_ipc_output_v2_set_layout(dwl_ipc_output, layout_idx); return EXIT_SUCCESS;
}
if (tflag) {
uint32_t mask = seltags;
char *t = tagset;
int32_t i = 0;
for (; *t && *t >= '0' && *t <= '9'; t++)
i = *t - '0' + i * 10;
if (!*t)
mask = 1 << (i - 1);
for (; *t; t++, i++) {
switch (*t) {
case '-':
mask &= ~(1 << (i - 1));
break;
case '+':
mask |= 1 << (i - 1);
break;
case '^':
mask ^= 1 << (i - 1);
break;
}
}
if ((i - 1) > tagcount)
die("bad tagset %s", tagset);
zdwl_ipc_output_v2_set_tags(dwl_ipc_output, mask, 0);
}
if (cflag) {
uint32_t and = ~0, xor = 0;
char *t = client_tags;
int32_t i = 0;
for (; *t && *t >= '0' && *t <= '9'; t++)
i = *t - '0' + i * 10;
if (!*t)
t = "+";
for (; *t; t++, i++) {
switch (*t) {
case '-':
and &= ~(1 << (i - 1));
break;
case '+':
and &= ~(1 << (i - 1));
xor |= 1 << (i - 1);
break;
case '^':
xor |= 1 << (i - 1);
break;
}
}
if ((i - 1) > tagcount)
die("bad client tagset %s", client_tags);
zdwl_ipc_output_v2_set_client_tags(dwl_ipc_output, and, xor);
}
if (dflag) {
zdwl_ipc_output_v2_dispatch(
dwl_ipc_output, dispatch_cmd, dispatch_arg1, dispatch_arg2,
dispatch_arg3, dispatch_arg4, dispatch_arg5);
}
wl_display_flush(display);
usleep(1000);
exit(0);
} else { } else {
if (tflag) { /* 一次性命令:读取所有数据,然后关闭输出 */
char *output_name = data; char buf[4096];
ssize_t n;
printf("%s clients %u\n", output_name, total_clients); while ((n = recv(sock, buf, sizeof(buf) - 1, 0)) > 0) {
buf[n] = '\0';
char occ_str[10], seltags_str[10], urg_str[10]; printf("%s", buf);
fflush(stdout);
bin_str_9bits(occ_str, occ);
bin_str_9bits(seltags_str, seltags);
bin_str_9bits(urg_str, urg);
printf("%s tags %u %u %u\n", output_name, occ, seltags, urg);
printf("%s tags %s %s %s\n", output_name, occ_str, seltags_str,
urg_str);
occ = seltags = total_clients = urg = 0;
} }
close(sock);
return EXIT_SUCCESS;
} }
fflush(stdout); }
}
static const struct zdwl_ipc_output_v2_listener dwl_ipc_output_listener = {
.toggle_visibility = dwl_ipc_output_toggle_visibility,
.active = dwl_ipc_output_active,
.tag = dwl_ipc_output_tag,
.layout = dwl_ipc_output_layout,
.title = dwl_ipc_output_title,
.appid = dwl_ipc_output_appid,
.layout_symbol = dwl_ipc_output_layout_symbol,
.fullscreen = dwl_ipc_output_fullscreen,
.floating = dwl_ipc_output_floating,
.x = dwl_ipc_output_x,
.y = dwl_ipc_output_y,
.width = dwl_ipc_output_width,
.height = dwl_ipc_output_height,
.last_layer = dwl_ipc_output_last_layer,
.kb_layout = dwl_ipc_output_kb_layout,
.keymode = dwl_ipc_output_keymode,
.scalefactor = dwl_ipc_output_scalefactor,
.frame = dwl_ipc_output_frame,
};
static void wl_output_name(void *data, struct wl_output *output,
const char *name) {
if (outputs.arr) {
struct output *o = (struct output *)data;
o->output_name = strdup(name);
printf("+ ");
}
if (Oflag)
printf("%s\n", name);
if (output_name && strcmp(output_name, name) != 0) {
wl_output_release(output);
return;
}
struct zdwl_ipc_output_v2 *dwl_ipc_output =
zdwl_ipc_manager_v2_get_output(dwl_ipc_manager, output);
zdwl_ipc_output_v2_add_listener(dwl_ipc_output, &dwl_ipc_output_listener,
output_name ? NULL : strdup(name));
}
static const struct wl_output_listener output_listener = {
.geometry = noop_geometry,
.mode = noop_mode,
.done = noop_done,
.scale = noop_scale,
.name = wl_output_name,
.description = noop_description,
};
static void global_add(void *data, struct wl_registry *wl_registry,
uint32_t name, const char *interface, uint32_t version) {
if (strcmp(interface, wl_output_interface.name) == 0) {
struct wl_output *o =
wl_registry_bind(wl_registry, name, &wl_output_interface,
WL_OUTPUT_NAME_SINCE_VERSION);
if (!outputs.arr) {
wl_output_add_listener(o, &output_listener, NULL);
} else {
DYNARR_PUSH(&outputs, (struct output){.name = name});
wl_output_add_listener(o, &output_listener,
&outputs.arr[outputs.len - 1]);
}
} else if (strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
dwl_ipc_manager = wl_registry_bind(wl_registry, name,
&zdwl_ipc_manager_v2_interface, 2);
zdwl_ipc_manager_v2_add_listener(dwl_ipc_manager, &dwl_ipc_listener,
NULL);
}
}
static void global_remove(void *data, struct wl_registry *wl_registry,
uint32_t name) {
if (!outputs.arr)
return;
struct output *o = outputs.arr;
for (size_t i = 0; i < outputs.len; i++, o++) {
if (o->name == name) {
printf("- %s\n", o->output_name);
free(o->output_name);
*o = DYNARR_POP(&outputs);
}
}
}
static const struct wl_registry_listener registry_listener = {
.global = global_add,
.global_remove = global_remove,
};
static void usage(void) {
fprintf(stderr,
"mmsg - MangoWM IPC\n"
"\n"
"SYNOPSIS:\n"
"\tmmsg [-OTLq]\n"
"\tmmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d "
"<cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>]\n"
"\tmmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA]\n"
"\n"
"OPERATION MODES:\n"
"\t-g Get values (tags, layout, focused client)\n"
"\t-s Set values (switch tags, layouts)\n"
"\t-w Watch mode (stream events)\n"
"\n"
"GENERAL OPTIONS:\n"
"\t-O Get all output (monitor) information\n"
"\t-T Get number of tags\n"
"\t-L Get all available layouts\n"
"\t-q Quit mango\n"
"\t-o <output> Select output (monitor)\n"
"\n"
"GET OPTIONS (used with -g or -w):\n"
"\t-O Get output name\n"
"\t-o Get output (monitor) focus information\n"
"\t-t Get selected tags\n"
"\t-l Get current layout\n"
"\t-c Get title and appid of focused clients\n"
"\t-v Get visibility of statusbar\n"
"\t-m Get fullscreen status\n"
"\t-f Get floating status\n"
"\t-x Get focused client geometry\n"
"\t-e Get name of last focused layer\n"
"\t-k Get current keyboard layout\n"
"\t-b Get current keybind mode\n"
"\t-A Get scale factor of monitor\n"
"\n"
"SET OPTIONS (used with -s):\n"
"\t-o <output> Select output (monitor)\n"
"\t-t <tags> Set selected tags (can be used with [+-^.] "
"modifiers)\n"
"\t-l <layout> Set current layout\n"
"\t-c <tags> Get title and appid of focused client\n"
"\t-d <cmd>,<args...> Dispatch internal command (max 5 args)\n");
exit(2);
}
int32_t main(int32_t argc, char *argv[]) {
ARGBEGIN {
case 'q':
qflag = 1;
if (!(mode & GET)) {
mode = SET;
}
break;
case 's':
if (mode != NONE)
usage();
mode = SET;
break;
case 'g':
if (mode != NONE)
usage();
mode = GET;
break;
case 'w':
if (mode != NONE)
usage();
mode = WATCH;
break;
case 'o':
if (mode == GET || mode == WATCH)
oflag = 1;
else if (mode == SET)
output_name = EARGF(usage());
else
output_name = ARGF();
break;
case 't':
tflag = 1;
if (!(mode & GET)) {
mode = SET;
tagset = EARGF(usage());
}
break;
case 'l':
lflag = 1;
if (!(mode & GET)) {
mode = SET;
layout_name = EARGF(usage());
}
break;
case 'c':
cflag = 1;
if (!(mode & GET)) {
mode = SET;
client_tags = EARGF(usage());
}
break;
case 'd':
dflag = 1;
if (!(mode & GET)) {
mode = SET;
char *arg = EARGF(usage());
dispatch_cmd = dispatch_arg1 = dispatch_arg2 = dispatch_arg3 =
dispatch_arg4 = dispatch_arg5 = "";
char *tokens[6] = {0};
int count = 0;
while (arg && count < 6) {
char *comma = (count < 5) ? strchr(arg, ',') : NULL;
if (comma) {
*comma = '\0';
tokens[count++] = arg;
arg = comma + 1;
} else {
tokens[count++] = arg;
break;
}
}
for (int i = 0; i < count; i++) {
char *str = tokens[i];
while (isspace((unsigned char)*str))
str++;
if (*str) {
char *end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end))
end--;
*(end + 1) = '\0';
}
tokens[i] = str;
}
if (count > 0)
dispatch_cmd = tokens[0];
if (count > 1)
dispatch_arg1 = tokens[1];
if (count > 2)
dispatch_arg2 = tokens[2];
if (count > 3)
dispatch_arg3 = tokens[3];
if (count > 4)
dispatch_arg4 = tokens[4];
if (count > 5)
dispatch_arg5 = tokens[5];
}
break;
case 'O':
Oflag = 1;
if (mode && !(mode & GET))
usage();
if (mode & WATCH)
DYNARR_INIT(&outputs);
else
mode = GET;
break;
case 'T':
Tflag = 1;
if (mode && mode != GET)
usage();
mode = GET;
break;
case 'L':
Lflag = 1;
if (mode && mode != GET)
usage();
mode = GET;
break;
case 'v':
vflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'm':
mflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'f':
fflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'x':
xflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'e':
eflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'k':
kflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'b':
bflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'A':
Aflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
default:
fprintf(stderr, "bad option %c\n", ARGC());
usage();
}
ARGEND
if (mode == NONE)
usage();
if (mode & GET && !output_name &&
!(oflag || tflag || lflag || Oflag || Tflag || Lflag || cflag ||
vflag || mflag || fflag || xflag || eflag || kflag || bflag ||
Aflag || dflag))
oflag = tflag = lflag = cflag = vflag = mflag = fflag = xflag = eflag =
kflag = bflag = Aflag = 1;
display = wl_display_connect(NULL);
if (!display)
die("bad display");
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL);
wl_display_dispatch(display);
wl_display_roundtrip(display);
if (!dwl_ipc_manager)
die("bad dwl-ipc protocol");
wl_display_roundtrip(display);
if (mode == WATCH)
while (wl_display_dispatch(display) != -1)
;
return 0;
}

View file

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

View file

@ -616,4 +616,4 @@ Client *get_focused_stack_client(Client *sc) {
} }
} }
return sc; return sc;
} }

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

@ -0,0 +1,836 @@
#include <cjson/cJSON.h>
#include <errno.h>
#include <fcntl.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_SURFACE_WS_NAME,
};
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; // watch 客户端链表
/* 一次性客户端状态 */
struct ipc_client_state {
int fd;
struct wl_event_source *source;
struct wl_event_loop *loop; // 用于重新添加 watch 事件
};
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 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, *focused = focustop(m);
for (int tag = 0; tag < LENGTH(tags); tag++) {
int numclients = 0, focused_client = 0;
bool is_active = false, is_urgent = false;
uint32_t tagmask = 1 << tag;
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))
continue;
if (c == focused)
focused_client = 1;
if (c->isurgent)
is_urgent = true;
numclients++;
}
cJSON *tag_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(tag_obj, "index", tag + 1);
cJSON_AddBoolToObject(tag_obj, "is_active", is_active);
cJSON_AddBoolToObject(tag_obj, "is_urgent", is_urgent);
cJSON_AddNumberToObject(tag_obj, "client_count", numclients);
cJSON_AddBoolToObject(tag_obj, "focused_client", focused_client);
cJSON_AddItemToArray(tags_array, tag_obj);
}
return tags_array;
}
static cJSON *monitor_active_tags(Monitor *m) {
cJSON *arr = cJSON_CreateArray();
uint32_t 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_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_fullscreen", c->isfullscreen);
cJSON_AddBoolToObject(obj, "is_floating", c->isfloating);
cJSON_AddBoolToObject(obj, "is_maximized", c->ismaximizescreen);
cJSON_AddBoolToObject(obj, "is_minimized", c->isminimized);
cJSON_AddBoolToObject(obj, "is_urgent", c->isurgent);
cJSON_AddBoolToObject(obj, "focused", c == focustop(c->mon));
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_client_brief_json(Client *c) {
cJSON *obj = cJSON_CreateObject();
cJSON_AddNumberToObject(obj, "id", c->id);
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, "focused", c == focustop(c->mon));
cJSON_AddBoolToObject(obj, "is_urgent", c->isurgent);
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->w.width);
cJSON_AddNumberToObject(resp, "height", m->w.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_surface_ws_name",
m->last_surface_ws_name);
cJSON_AddItemToObject(resp, "tags", build_tags_json(m));
cJSON_AddItemToObject(resp, "active_tags", monitor_active_tags(m));
int count = 0;
Client *c;
wl_list_for_each(c, &clients, link) if (c->mon == m) count++;
cJSON_AddNumberToObject(resp, "client_count", count);
Client *focused = focustop(m);
if (focused)
cJSON_AddNumberToObject(resp, "focused_client_id", focused->id);
else
cJSON_AddNullToObject(resp, "focused_client_id");
return resp;
}
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 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_surface_ws_name ", 25) == 0) {
const char *name = cmd + 25;
Monitor *m = monitor_by_name(name);
if (!m) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "error", "monitor not found");
} else {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name);
cJSON_AddStringToObject(resp, "last_surface_ws_name",
m->last_surface_ws_name);
}
} else if (strncmp(cmd, "get monitor ", 12) == 0) {
const char *name = cmd + 12;
Monitor *m = monitor_by_name(name);
if (!m) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "error", "monitor not found");
} else {
resp = build_monitor_json(m);
}
} else if (strncmp(cmd, "get client ", 11) == 0) {
uint32_t id = (uint32_t)atoi(cmd + 11);
Client *c = client_by_id(id);
if (!c) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "error", "client not found");
} else {
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) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "error",
"usage: get tag <monitor> <index>");
} else {
int tag_idx = ext_tag_idx - 1;
Monitor *m = monitor_by_name(mon_name);
if (!m || tag_idx < 0 || tag_idx >= LENGTH(tags)) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "error",
"invalid monitor or tag index");
} else {
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)
continue;
if (!(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_brief_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 *mobj = cJSON_CreateObject();
cJSON_AddStringToObject(mobj, "name", m->wlr_output->name);
cJSON_AddBoolToObject(mobj, "active", m == selmon);
cJSON_AddNumberToObject(mobj, "x", m->m.x);
cJSON_AddNumberToObject(mobj, "y", m->m.y);
cJSON_AddNumberToObject(mobj, "width", m->w.width);
cJSON_AddNumberToObject(mobj, "height", m->w.height);
cJSON_AddNumberToObject(mobj, "scale", m->wlr_output->scale);
cJSON_AddStringToObject(mobj, "last_surface_ws_name",
m->last_surface_ws_name);
cJSON_AddItemToObject(mobj, "active_tags", monitor_active_tags(m));
cJSON_AddItemToArray(arr, mobj);
}
resp = cJSON_CreateObject();
cJSON_AddItemToObject(resp, "monitors", arr);
} else if (strcmp(cmd, "get all-tags") == 0) {
cJSON *arr = cJSON_CreateArray();
Monitor *m;
wl_list_for_each(m, &mons, link) {
cJSON *entry = cJSON_CreateObject();
cJSON_AddStringToObject(entry, "monitor", m->wlr_output->name);
cJSON_AddItemToObject(entry, "tags", build_tags_json(m));
cJSON_AddItemToArray(arr, entry);
}
resp = cJSON_CreateObject();
cJSON_AddItemToObject(resp, "all_tags", arr);
} else if (strncmp(cmd, "get tags ", 9) == 0) {
const char *name = cmd + 9;
Monitor *m = monitor_by_name(name);
if (!m) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "error", "monitor not found");
} else {
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));
}
} else if (strncmp(cmd, "dispatch ", 9) == 0) {
const char *params = cmd + 9;
char *dispatch_copy = strdup(params);
char *saveptr;
char *token = strtok_r(dispatch_copy, " ", &saveptr);
char *fname = token;
char *a1 = strtok_r(NULL, " ", &saveptr);
char *a2 = strtok_r(NULL, " ", &saveptr);
char *a3 = strtok_r(NULL, " ", &saveptr);
char *a4 = strtok_r(NULL, " ", &saveptr);
char *a5 = strtok_r(NULL, " ", &saveptr);
Arg arg = {0};
int32_t (*func)(const Arg *) =
parse_func_name(fname, &arg, a1, a2, a3, a4, a5);
if (func) {
func(&arg);
resp = cJSON_CreateObject();
cJSON_AddBoolToObject(resp, "success", true);
} else {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "error", "unknown function");
}
if (arg.v)
free(arg.v);
if (arg.v2)
free(arg.v2);
if (arg.v3)
free(arg.v3);
free(dispatch_copy);
} else {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "error", "unknown command");
}
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);
}
/* ---------- Watch 数据回调 ---------- */
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];
recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
}
return 0;
}
/* 将一次性客户端转为 watch 客户端,返回 true 表示已转换 */
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_surface_ws_name ", 27) == 0) {
type = IPC_WATCH_LAST_SURFACE_WS_NAME;
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_SURFACE_WS_NAME) &&
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;
// 移除一次性事件源(但保留 fd
wl_event_source_remove(client->source);
// 注册新的 watch 事件源,并将 wc 作为最后的 data 参数传入
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);
// 立即推送初始状态
switch (type) {
case IPC_WATCH_MONITOR: {
Monitor *m = monitor_by_name(arg);
if (m) {
cJSON *json = build_monitor_json(m);
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
}
break;
}
case IPC_WATCH_LAST_SURFACE_WS_NAME: {
Monitor *m = monitor_by_name(arg);
if (m) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "monitor", m->wlr_output->name);
cJSON_AddStringToObject(json, "last_surface_ws_name",
m->last_surface_ws_name);
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
}
break;
}
case IPC_WATCH_CLIENT: {
Client *c = client_by_id(client_id);
if (c) {
cJSON *json = build_client_json(c);
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
}
break;
}
case IPC_WATCH_TAGS: {
Monitor *m = monitor_by_name(arg);
if (m) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "monitor", m->wlr_output->name);
cJSON_AddItemToObject(json, "tags", build_tags_json(m));
cJSON_AddItemToObject(json, "active_tags", monitor_active_tags(m));
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
}
break;
}
case IPC_WATCH_ALL_TAGS: {
cJSON *arr = cJSON_CreateArray();
Monitor *m;
wl_list_for_each(m, &mons, link) {
cJSON *entry = cJSON_CreateObject();
cJSON_AddStringToObject(entry, "monitor", m->wlr_output->name);
cJSON_AddItemToObject(entry, "tags", build_tags_json(m));
cJSON_AddItemToArray(arr, entry);
}
cJSON *json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "all_tags", arr);
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
break;
}
case IPC_WATCH_ALL_CLIENTS: {
cJSON *arr = cJSON_CreateArray();
Client *c;
wl_list_for_each(c, &clients, link) {
cJSON_AddItemToArray(arr, build_client_brief_json(c));
}
cJSON *json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "clients", arr);
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
break;
}
case IPC_WATCH_KEYMODE: {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "keymode", keymode.mode);
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
break;
}
case IPC_WATCH_KB_LAYOUT: {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "layout", ipc_get_layout_str());
ipc_notify_json_to_fd(fd, json);
cJSON_Delete(json);
break;
}
default:
break;
}
// 释放一次性客户端状态
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_READABLE) {
char buf[1024] = {0};
ssize_t n = recv(fd, buf, sizeof(buf) - 1, MSG_DONTWAIT);
if (n > 0) {
buf[strcspn(buf, "\r\n")] = 0;
if (handle_watch_command(fd, buf, client)) {
return 0;
}
handle_command(fd, buf);
}
}
close(client->fd);
wl_event_source_remove(client->source);
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) {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
bool match = false;
if (wc->type == IPC_WATCH_MONITOR &&
strcmp(m->wlr_output->name, wc->target.monitor.name) == 0)
match = true;
else if (wc->type == IPC_WATCH_ALL_TAGS)
match = true;
if (match) {
cJSON *json = build_monitor_json(m);
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
// 新增通知接口供 compositor 调用:当 last_surface_ws_name 变更时调用
void ipc_notify_last_surface_ws_name(Monitor *m) {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_LAST_SURFACE_WS_NAME &&
strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "monitor", m->wlr_output->name);
cJSON_AddStringToObject(json, "last_surface_ws_name",
m->last_surface_ws_name);
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
void ipc_notify_client(Client *c) {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
bool match = false;
if (wc->type == IPC_WATCH_CLIENT && c->id == wc->target.client.id)
match = true;
else if (wc->type == IPC_WATCH_ALL_CLIENTS)
match = true;
if (match) {
cJSON *json = build_client_json(c);
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
void ipc_notify_tags(Monitor *m) {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
bool match = false;
if (wc->type == IPC_WATCH_TAGS &&
strcmp(m->wlr_output->name, wc->target.tags.mon_name) == 0)
match = true;
else if (wc->type == IPC_WATCH_ALL_TAGS)
match = true;
if (match) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "monitor", m->wlr_output->name);
cJSON_AddItemToObject(json, "tags", build_tags_json(m));
cJSON_AddItemToObject(json, "active_tags", monitor_active_tags(m));
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
void ipc_notify_all_monitors() {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_ALL_MONITORS) {
cJSON *arr = cJSON_CreateArray();
Monitor *m;
wl_list_for_each(m, &mons, link) {
cJSON *mobj = cJSON_CreateObject();
cJSON_AddStringToObject(mobj, "name", m->wlr_output->name);
cJSON_AddBoolToObject(mobj, "active", m == selmon);
cJSON_AddNumberToObject(mobj, "x", m->m.x);
cJSON_AddNumberToObject(mobj, "y", m->m.y);
cJSON_AddNumberToObject(mobj, "width", m->w.width);
cJSON_AddNumberToObject(mobj, "height", m->w.height);
cJSON_AddNumberToObject(mobj, "scale", m->wlr_output->scale);
cJSON_AddNumberToObject(
mobj, "layout_index",
m->pertag->ltidxs[m->pertag->curtag]->id);
cJSON_AddStringToObject(
mobj, "layout_symbol",
m->pertag->ltidxs[m->pertag->curtag]->symbol);
cJSON_AddStringToObject(mobj, "last_surface_ws_name",
m->last_surface_ws_name);
cJSON_AddItemToObject(mobj, "tags", build_tags_json(m));
cJSON_AddItemToObject(mobj, "active_tags",
monitor_active_tags(m));
cJSON_AddItemToArray(arr, mobj);
}
cJSON *json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "monitors", arr);
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
void ipc_notify_all_clients() {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_ALL_CLIENTS) {
cJSON *arr = cJSON_CreateArray();
Client *c;
wl_list_for_each(c, &clients, link)
cJSON_AddItemToArray(arr, build_client_brief_json(c));
cJSON *json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "clients", arr);
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
void ipc_notify_all_tags() {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_ALL_TAGS) {
cJSON *arr = cJSON_CreateArray();
Monitor *m;
wl_list_for_each(m, &mons, link) {
cJSON *entry = cJSON_CreateObject();
cJSON_AddStringToObject(entry, "monitor", m->wlr_output->name);
cJSON_AddItemToObject(entry, "tags", build_tags_json(m));
cJSON_AddItemToArray(arr, entry);
}
cJSON *json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "all_tags", arr);
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
void ipc_notify_keymode(void) {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_KEYMODE) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "keymode", keymode.mode);
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
void ipc_notify_kb_layout(void) {
struct ipc_watch_client *wc, *tmp;
wl_list_for_each_safe(wc, tmp, &watch_clients, link) {
if (wc->type == IPC_WATCH_KB_LAYOUT) {
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "layout", ipc_get_layout_str());
ipc_notify_json_to_fd(wc->fd, json);
cJSON_Delete(json);
}
}
}
/* ---------- 初始化与清理 ---------- */
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, 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 Pertag Pertag;
typedef struct Monitor Monitor; typedef struct Monitor Monitor;
typedef struct Client Client; typedef struct Client Client;
@ -441,6 +419,7 @@ struct Client {
float old_grid_row_per; float old_grid_row_per;
int32_t grid_col_idx; int32_t grid_col_idx;
int32_t grid_row_idx; int32_t grid_row_idx;
uint32_t id;
}; };
typedef struct { typedef struct {
@ -1094,6 +1073,7 @@ static struct wl_event_source *sync_keymap;
#include "dispatch/bind_define.h" #include "dispatch/bind_define.h"
#include "ext-protocol/all.h" #include "ext-protocol/all.h"
#include "fetch/fetch.h" #include "fetch/fetch.h"
#include "ipc/ipc.h"
#include "layout/arrange.h" #include "layout/arrange.h"
#include "layout/dwindle.h" #include "layout/dwindle.h"
#include "layout/horizontal.h" #include "layout/horizontal.h"
@ -2511,6 +2491,7 @@ void cleanuplisteners(void) {
} }
void cleanup(void) { void cleanup(void) {
ipc_cleanup();
cleanuplisteners(); cleanuplisteners();
#ifdef XWAYLAND #ifdef XWAYLAND
wlr_xwayland_destroy(xwayland); wlr_xwayland_destroy(xwayland);
@ -4418,6 +4399,9 @@ mapnotify(struct wl_listener *listener, void *data) {
Client *at_client = NULL; Client *at_client = NULL;
Client *c = wl_container_of(listener, c, map); Client *c = wl_container_of(listener, c, map);
int32_t i = 0; int32_t i = 0;
c->id = generate_client_id();
/* Create scene tree for this client and its border */ /* Create scene tree for this client and its border */
c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]);
wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell);
@ -5721,13 +5705,30 @@ void create_output(struct wlr_backend *backend, void *data) {
// 修改信号处理函数,接收掩码参数 // 修改信号处理函数,接收掩码参数
void handle_print_status(struct wl_listener *listener, 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; Monitor *m = NULL;
wl_list_for_each(m, &mons, link) { wl_list_for_each(m, &mons, link) {
if (!m->wlr_output->enabled) { if (!m->wlr_output->enabled) {
continue; 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); dwl_ipc_output_printstatus(m);
} }
} }
@ -5756,6 +5757,9 @@ void setup(void) {
* clients from the Unix socket, manging Wayland globals, and so on. */ * clients from the Unix socket, manging Wayland globals, and so on. */
dpy = wl_display_create(); dpy = wl_display_create();
event_loop = wl_display_get_event_loop(dpy); event_loop = wl_display_get_event_loop(dpy);
ipc_init(event_loop);
tablet_mgr = wlr_tablet_v2_create(dpy); tablet_mgr = wlr_tablet_v2_create(dpy);
/* The backend is a wlroots feature which abstracts the underlying input /* The backend is a wlroots feature which abstracts the underlying input
* and output hardware. The autocreate option will choose the most * and output hardware. The autocreate option will choose the most