From 42fb79fc06d2f71f453aa82ecca08718b537098b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 24 May 2026 13:31:31 +0800 Subject: [PATCH] feat: new ipc impl --- format.sh | 2 +- meson.build | 2 + mmsg/arg.h | 65 ---- mmsg/dynarr.h | 30 -- mmsg/mmsg.c | 789 +++-------------------------------------- src/action/client.h | 5 +- src/fetch/client.h | 2 +- src/ipc/ipc.h | 836 ++++++++++++++++++++++++++++++++++++++++++++ src/mango.c | 50 +-- 9 files changed, 925 insertions(+), 856 deletions(-) delete mode 100644 mmsg/arg.h delete mode 100644 mmsg/dynarr.h create mode 100644 src/ipc/ipc.h diff --git a/format.sh b/format.sh index 1291ff8f..6b204b00 100644 --- a/format.sh +++ b/format.sh @@ -1,3 +1,3 @@ #!/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 diff --git a/meson.build b/meson.build index 899439c8..270c4688 100644 --- a/meson.build +++ b/meson.build @@ -40,6 +40,7 @@ libwayland_client_dep = dependency('wayland-client') pcre2_dep = dependency('libpcre2-8') libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1') pixman_dep = dependency('pixman-1') +cjson_dep = dependency('libcjson') # 获取版本信息 @@ -112,6 +113,7 @@ executable('mango', libwayland_client_dep, pcre2_dep, pixman_dep, + cjson_dep, ], install : true, c_args : c_args, diff --git a/mmsg/arg.h b/mmsg/arg.h deleted file mode 100644 index c3b0d7b9..00000000 --- a/mmsg/arg.h +++ /dev/null @@ -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 diff --git a/mmsg/dynarr.h b/mmsg/dynarr.h deleted file mode 100644 index 45cd356a..00000000 --- a/mmsg/dynarr.h +++ /dev/null @@ -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 diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 69f1d1d0..d6540867 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -1,754 +1,73 @@ -#include "arg.h" -#include "dwl-ipc-unstable-v2-protocol.h" -#include "dynarr.h" -#include -#include +#include #include #include #include +#include +#include #include -#include -#include -#define die(fmt, ...) \ - do { \ - fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ - exit(EXIT_FAILURE); \ - } while (0) - -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'; +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: mmsg [args...]\n"); + fprintf(stderr, " get ... one-shot request\n"); + fprintf(stderr, " watch ... persistent stream\n"); + return EXIT_FAILURE; } - *buf = '\0'; // 字符串结尾 -} -static void dwl_ipc_tags(void *data, - struct zdwl_ipc_manager_v2 *dwl_ipc_manager, - uint32_t count) { - tagcount = count; - 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; + const char *socket_path = getenv("MANGO_INSTANCE_SIGNATURE"); + if (!socket_path) { + fprintf(stderr, "Error: MANGO_INSTANCE_SIGNATURE not set.\n"); + return EXIT_FAILURE; } - 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, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - uint32_t tag, uint32_t state, uint32_t clients, - uint32_t focused) { - 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; + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + return EXIT_FAILURE; + } - // 累计所有 tag 的 clients 总数 - total_clients += clients; + struct sockaddr_un addr = {.sun_family = AF_UNIX}; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); - if (!(mode & GET)) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("tag %u %u %u %u\n", tag + 1, state, clients, focused); -} + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("connect"); + close(sock); + return EXIT_FAILURE; + } -static void dwl_ipc_output_layout(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - uint32_t layout) {} + /* 拼接命令 */ + char cmd[1024] = {0}; + 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( - 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); -} + send(sock, cmd, strlen(cmd), 0); -static void dwl_ipc_output_title(void *data, - 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); -} + bool is_watch = (strncmp(argv[1], "watch", 5) == 0); -static void dwl_ipc_output_appid(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - const char *appid) { - if (!(cflag && mode & GET)) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - 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 (is_watch) { + /* watch 模式:持续接收 JSON 并输出,直到连接断开 */ + char buf[4096]; + ssize_t n; + while ((n = recv(sock, buf, sizeof(buf) - 1, 0)) > 0) { + buf[n] = '\0'; + printf("%s", buf); + fflush(stdout); } - if (lflag) { - zdwl_ipc_output_v2_set_layout(dwl_ipc_output, layout_idx); - } - 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); + close(sock); + return EXIT_SUCCESS; } else { - if (tflag) { - char *output_name = data; - - printf("%s clients %u\n", output_name, total_clients); - - char occ_str[10], seltags_str[10], urg_str[10]; - - 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; + /* 一次性命令:读取所有数据,然后关闭输出 */ + char buf[4096]; + ssize_t n; + while ((n = recv(sock, buf, sizeof(buf) - 1, 0)) > 0) { + buf[n] = '\0'; + printf("%s", buf); + fflush(stdout); } + 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 ] -s [-t ] [-l ] [-c ] [-d " - ",,,,,]\n" - "\tmmsg [-o ] (-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 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 Select output (monitor)\n" - "\t-t Set selected tags (can be used with [+-^.] " - "modifiers)\n" - "\t-l Set current layout\n" - "\t-c Get title and appid of focused client\n" - "\t-d , 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, ®istry_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; -} +} \ No newline at end of file diff --git a/src/action/client.h b/src/action/client.h index ce71a5c6..43eb2c7b 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -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); } -} \ No newline at end of file +} + +static uint32_t next_client_id = 0; +uint32_t generate_client_id(void) { return ++next_client_id; } \ No newline at end of file diff --git a/src/fetch/client.h b/src/fetch/client.h index e39e39b3..e091cf50 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -616,4 +616,4 @@ Client *get_focused_stack_client(Client *sc) { } } return sc; -} +} \ No newline at end of file diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h new file mode 100644 index 00000000..570247e2 --- /dev/null +++ b/src/ipc/ipc.h @@ -0,0 +1,836 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); + } 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); +} \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 43eecf6d..8b1cb3b1 100644 --- a/src/mango.c +++ b/src/mango.c @@ -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; @@ -441,6 +419,7 @@ struct Client { float old_grid_row_per; int32_t grid_col_idx; int32_t grid_row_idx; + uint32_t id; }; typedef struct { @@ -1094,6 +1073,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 +2491,7 @@ void cleanuplisteners(void) { } void cleanup(void) { + ipc_cleanup(); cleanuplisteners(); #ifdef XWAYLAND wlr_xwayland_destroy(xwayland); @@ -4418,6 +4399,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 +5705,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 +5757,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