From b5ccb2621b4f149a215635e941b1ca3b7938cbac Mon Sep 17 00:00:00 2001 From: HuntedByTheIRS Date: Thu, 2 Jul 2026 01:24:16 -0400 Subject: [PATCH] Massive code refactor --- .gitignore | 3 + Makefile | 25 +- README.md | 4 +- include/client.h | 492 ++----- include/ext_workspace.h | 23 + include/ipc.h | 302 +--- include/ipc_socket.h | 1173 +--------------- include/monitor.h | 58 + parser/parser.c | 11 +- parser/parser.h | 6 +- peachmsg/peachmsg.c | 51 +- src/client.c | 426 ++++++ .../ext-workspace.h => src/ext_workspace.c | 58 +- src/ipc.c | 276 ++++ src/ipc_socket.c | 1237 +++++++++++++++++ src/layout.c | 519 +++++++ src/layout.h | 43 + src/peachwm.c | 1031 ++------------ src/scratchpad.c | 236 ++++ src/scratchpad.h | 60 + src/util.c | 1 + src/util.h | 2 + 22 files changed, 3277 insertions(+), 2760 deletions(-) create mode 100644 include/ext_workspace.h create mode 100644 include/monitor.h create mode 100644 src/client.c rename include/ext-workspace.h => src/ext_workspace.c (84%) create mode 100644 src/ipc.c create mode 100644 src/ipc_socket.c create mode 100644 src/layout.c create mode 100644 src/layout.h create mode 100644 src/scratchpad.c create mode 100644 src/scratchpad.h diff --git a/.gitignore b/.gitignore index 845cda6..50e225f 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ dkms.conf # debug information files *.dwo + +.opencode/ +.omo/ diff --git a/Makefile b/Makefile index b072c14..e93e92f 100644 --- a/Makefile +++ b/Makefile @@ -78,12 +78,12 @@ debug: peachwm peachmsg/peachmsg # Compositor -peachwm: src/peachwm.o src/util.o parser/parser.o \ - src/wlr_ext_workspace_v1.o $(PROTO_OBJS) +peachwm: src/peachwm.o src/scratchpad.o src/layout.o src/util.o src/client.o src/ipc.o src/ipc_socket.o \ + parser/parser.o src/ext_workspace.o src/wlr_ext_workspace_v1.o $(PROTO_OBJS) $(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ src/peachwm.o: src/peachwm.c include/client.h include/ipc.h \ - include/ipc_socket.h include/ext-workspace.h \ + include/ipc_socket.h include/ext_workspace.h \ src/wlr_ext_workspace_v1.h parser/parser.h \ protocols/peachwm-ipc-unstable-v2-protocol.h \ protocols/ext-workspace-v1-protocol.h \ @@ -94,13 +94,32 @@ src/peachwm.o: src/peachwm.c include/client.h include/ipc.h \ protocols/xdg-shell-protocol.h $(CC) $(CFLAGS) -o $@ -c $< +src/scratchpad.o: src/scratchpad.c src/scratchpad.h include/monitor.h include/client.h src/layout.h + $(CC) $(CFLAGS) -o $@ -c $< + +src/layout.o: src/layout.c src/layout.h include/monitor.h include/client.h + $(CC) $(CFLAGS) -o $@ -c $< + src/util.o: src/util.c src/util.h $(CC) $(CFLAGS) -o $@ -c $< +src/client.o: src/client.c include/client.h include/monitor.h + $(CC) $(CFLAGS) -o $@ -c $< + +src/ipc.o: src/ipc.c include/ipc.h include/monitor.h + $(CC) $(CFLAGS) -o $@ -c $< + +src/ipc_socket.o: src/ipc_socket.c include/ipc_socket.h \ + include/ipc.h include/client.h include/monitor.h src/util.h + $(CC) $(CFLAGS) -o $@ -c $< + src/wlr_ext_workspace_v1.o: src/wlr_ext_workspace_v1.c \ src/wlr_ext_workspace_v1.h protocols/ext-workspace-v1-protocol.h $(CC) $(CFLAGS) -o $@ -c $< +src/ext_workspace.o: src/ext_workspace.c include/ext_workspace.h include/monitor.h + $(CC) $(CFLAGS) -o $@ -c $< + parser/parser.o: parser/parser.c parser/parser.h $(CC) $(CFLAGS) -o $@ -c $< diff --git a/README.md b/README.md index f35c0f6..5c8b535 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ ## peachwm -peachwm is a fork of [swindle](https://github.com/kantiankant/swindle) +~~peachwm is a fork of [swindle](https://github.com/kantiankant/swindle)~~ +PeachWM is now a independent wayland compositor based on WLRoots that is designed to be similar to MangoWC, while remaining lightweight. -swindle is also fully GPLv3 licensed. PeachWM is maintained by [HuntedByTheIRS](https://github.com/HuntedByTheIRS). diff --git a/include/client.h b/include/client.h index 53840e9..3dcc3e8 100644 --- a/include/client.h +++ b/include/client.h @@ -1,404 +1,106 @@ -/* - * Attempt to consolidate unavoidable suck into one file, away from peachwm.c. This - * file is not meant to be pretty. We use a .h file with static inline - * functions instead of a separate .c module, or function pointers like sway, so - * that they will simply compile out if the chosen #defines leave them unused. - */ +#ifndef PEACHWM_CLIENT_H +#define PEACHWM_CLIENT_H -/* Leave these functions first; they're used in the others */ -static inline int -client_is_x11(Client *c) -{ +#include +#include +#include "monitor.h" + +/* Forward declarations for pointer-only types */ +struct wlr_surface; +struct wlr_keyboard; +struct wlr_scene_tree; +struct wlr_scene_rect; +struct wlr_scene_layer_surface_v1; +struct wlr_layer_surface_v1; +struct wlr_xdg_surface; +struct wlr_xwayland_surface; +struct wlr_xdg_toplevel_decoration_v1; + +/* Client types */ +enum { XDGShell, LayerShell, X11 }; + +typedef struct Client { + /* Must keep this field first */ + unsigned int type; /* XDGShell or X11* */ + + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; + struct wlr_box geom; /* layout-relative, includes border */ + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; /* only width and height are used */ + union { + struct wlr_xdg_surface *xdg; + struct wlr_xwayland_surface *xwayland; + } surface; + struct wlr_xdg_toplevel_decoration_v1 *decoration; + struct wl_listener commit; + struct wl_listener map; + struct wl_listener maximize; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener set_title; + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; #ifdef XWAYLAND - return c->type == X11; + struct wl_listener activate; + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; #endif - return 0; -} + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen, isscratchpad; + uint32_t resize; /* configure serial of a pending resize */ +} Client; -static inline struct wlr_surface * -client_surface(Client *c) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) - return c->surface.xwayland->surface; -#endif - return c->surface.xdg->surface; -} +typedef struct { + /* Must keep this field first */ + unsigned int type; /* LayerShell */ -static inline int -toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) -{ - struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; - struct wlr_surface *root_surface; + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_tree *popups; + struct wlr_scene_layer_surface_v1 *scene_layer; + struct wl_list link; + int mapped; struct wlr_layer_surface_v1 *layer_surface; - Client *c = NULL; - LayerSurface *l = NULL; - int type = -1; -#ifdef XWAYLAND - struct wlr_xwayland_surface *xsurface; -#endif - if (!s) - return -1; - root_surface = wlr_surface_get_root_surface(s); + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener surface_commit; +} LayerSurface; -#ifdef XWAYLAND - if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { - c = xsurface->data; - type = c->type; - goto end; - } -#endif +/* Function declarations */ +int client_is_x11(Client *c); +struct wlr_surface *client_surface(Client *c); +int toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl); +void client_activate_surface(struct wlr_surface *s, int activated); +uint32_t client_set_bounds(Client *c, int32_t width, int32_t height); +const char *client_get_appid(Client *c); +void client_get_clip(Client *c, struct wlr_box *clip); +void client_get_geometry(Client *c, struct wlr_box *geom); +Client *client_get_parent(Client *c); +int client_has_children(Client *c); +const char *client_get_title(Client *c); +int client_is_float_type(Client *c); +int client_is_rendered_on_mon(Client *c, Monitor *m); +int client_is_stopped(Client *c); +int client_is_unmanaged(Client *c); +void client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb); +void client_send_close(Client *c); +void client_set_border_color(Client *c, const float color[static 4]); +void client_set_fullscreen(Client *c, int fullscreen); +void client_set_scale(struct wlr_surface *s, float scale); +uint32_t client_set_size(Client *c, uint32_t width, uint32_t height); +void client_set_tiled(Client *c, uint32_t edges); +void client_set_suspended(Client *c, int suspended); +int client_wants_focus(Client *c); +int client_wants_fullscreen(Client *c); - if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) { - l = layer_surface->data; - type = LayerShell; - goto end; - } - - xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface); - while (xdg_surface) { - tmp_xdg_surface = NULL; - switch (xdg_surface->role) { - case WLR_XDG_SURFACE_ROLE_POPUP: - if (!xdg_surface->popup || !xdg_surface->popup->parent) - return -1; - - tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); - - if (!tmp_xdg_surface) - return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); - - xdg_surface = tmp_xdg_surface; - break; - case WLR_XDG_SURFACE_ROLE_TOPLEVEL: - c = xdg_surface->data; - type = c->type; - goto end; - case WLR_XDG_SURFACE_ROLE_NONE: - return -1; - } - } - -end: - if (pl) - *pl = l; - if (pc) - *pc = c; - return type; -} - -/* The others */ -static inline void -client_activate_surface(struct wlr_surface *s, int activated) -{ - struct wlr_xdg_toplevel *toplevel; -#ifdef XWAYLAND - struct wlr_xwayland_surface *xsurface; - if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { - wlr_xwayland_surface_activate(xsurface, activated); - return; - } -#endif - if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s))) - wlr_xdg_toplevel_set_activated(toplevel, activated); -} - -static inline uint32_t -client_set_bounds(Client *c, int32_t width, int32_t height) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) - return 0; -#endif - if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >= - XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE && width >= 0 && height >= 0 - && (c->bounds.width != width || c->bounds.height != height)) { - c->bounds.width = width; - c->bounds.height = height; - return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height); - } - return 0; -} - -static inline const char * -client_get_appid(Client *c) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) - return c->surface.xwayland->class ? c->surface.xwayland->class : "broken"; -#endif - return c->surface.xdg->toplevel->app_id ? c->surface.xdg->toplevel->app_id : "broken"; -} - -static inline void -client_get_clip(Client *c, struct wlr_box *clip) -{ - *clip = (struct wlr_box){ - .x = 0, - .y = 0, - .width = c->geom.width - c->bw, - .height = c->geom.height - c->bw, - }; - -#ifdef XWAYLAND - if (client_is_x11(c)) - return; -#endif - - clip->x = c->surface.xdg->geometry.x; - clip->y = c->surface.xdg->geometry.y; -} - -static inline void -client_get_geometry(Client *c, struct wlr_box *geom) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) { - geom->x = c->surface.xwayland->x; - geom->y = c->surface.xwayland->y; - geom->width = c->surface.xwayland->width; - geom->height = c->surface.xwayland->height; - return; - } -#endif - *geom = c->surface.xdg->geometry; -} - -static inline Client * -client_get_parent(Client *c) -{ - Client *p = NULL; -#ifdef XWAYLAND - if (client_is_x11(c)) { - if (c->surface.xwayland->parent) - toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); - return p; - } -#endif - if (c->surface.xdg->toplevel->parent) - toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL); - return p; -} - -static inline int -client_has_children(Client *c) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) - return !wl_list_empty(&c->surface.xwayland->children); -#endif - /* surface.xdg->link is never empty because it always contains at least the - * surface itself. */ - return wl_list_length(&c->surface.xdg->link) > 1; -} - -static inline const char * -client_get_title(Client *c) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) - return c->surface.xwayland->title ? c->surface.xwayland->title : "broken"; -#endif - return c->surface.xdg->toplevel->title ? c->surface.xdg->toplevel->title : "broken"; -} - -static inline int -client_is_float_type(Client *c) -{ - struct wlr_xdg_toplevel *toplevel; - struct wlr_xdg_toplevel_state state; - -#ifdef XWAYLAND - if (client_is_x11(c)) { - struct wlr_xwayland_surface *surface = c->surface.xwayland; - xcb_size_hints_t *size_hints = surface->size_hints; - if (surface->modal) - return 1; - - if (wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG) - || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_SPLASH) - || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLBAR) - || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_UTILITY)) { - return 1; - } - - return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0 - && (size_hints->max_width == size_hints->min_width - || size_hints->max_height == size_hints->min_height); - } -#endif - - toplevel = c->surface.xdg->toplevel; - state = toplevel->current; - return toplevel->parent || (state.min_width != 0 && state.min_height != 0 - && (state.min_width == state.max_width - || state.min_height == state.max_height)); -} - -static inline int -client_is_rendered_on_mon(Client *c, Monitor *m) -{ - /* This is needed for when you don't want to check formal assignment, - * but rather actual displaying of the pixels. - * Usually VISIBLEON suffices and is also faster. */ - struct wlr_surface_output *s; - int unused_lx, unused_ly; - if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly)) - return 0; - wl_list_for_each(s, &client_surface(c)->current_outputs, link) - if (s->output == m->wlr_output) - return 1; - return 0; -} - -static inline int -client_is_stopped(Client *c) -{ - int pid; - siginfo_t in = {0}; -#ifdef XWAYLAND - if (client_is_x11(c)) - return 0; -#endif - - wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); - if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) { - /* This process is not our child process, while is very unlikely that - * it is stopped, in order to do not skip frames, assume that it is. */ - if (errno == ECHILD) - return 1; - } else if (in.si_pid) { - if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) - return 1; - if (in.si_code == CLD_CONTINUED) - return 0; - } - - return 0; -} - -static inline int -client_is_unmanaged(Client *c) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) - return c->surface.xwayland->override_redirect; -#endif - return 0; -} - -static inline void -client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) -{ - if (kb) - wlr_seat_keyboard_notify_enter(seat, s, kb->keycodes, - kb->num_keycodes, &kb->modifiers); - else - wlr_seat_keyboard_notify_enter(seat, s, NULL, 0, NULL); -} - -static inline void -client_send_close(Client *c) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) { - wlr_xwayland_surface_close(c->surface.xwayland); - return; - } -#endif - wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); -} - -static inline void -client_set_border_color(Client *c, const float color[static 4]) -{ - int i; - for (i = 0; i < 4; i++) - wlr_scene_rect_set_color(c->border[i], color); -} - -static inline void -client_set_fullscreen(Client *c, int fullscreen) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) { - wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen); - return; - } -#endif - wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); -} - -static inline void -client_set_scale(struct wlr_surface *s, float scale) -{ - wlr_fractional_scale_v1_notify_scale(s, scale); - wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(scale)); -} - -static inline uint32_t -client_set_size(Client *c, uint32_t width, uint32_t height) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) { - wlr_xwayland_surface_configure(c->surface.xwayland, - c->geom.x + c->bw, c->geom.y + c->bw, width, height); - return 0; - } -#endif - if ((int32_t)width == c->surface.xdg->toplevel->current.width - && (int32_t)height == c->surface.xdg->toplevel->current.height) - return 0; - return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); -} - -static inline void -client_set_tiled(Client *c, uint32_t edges) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) { - wlr_xwayland_surface_set_maximized(c->surface.xwayland, - edges != WLR_EDGE_NONE, edges != WLR_EDGE_NONE); - return; - } -#endif - if (wl_resource_get_version(c->surface.xdg->toplevel->resource) - >= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) { - wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); - } else { - wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE); - } -} - -static inline void -client_set_suspended(Client *c, int suspended) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) - return; -#endif - - wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); -} - -static inline int -client_wants_focus(Client *c) -{ -#ifdef XWAYLAND - return client_is_unmanaged(c) - && wlr_xwayland_surface_override_redirect_wants_focus(c->surface.xwayland) - && wlr_xwayland_surface_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; -#endif - return 0; -} - -static inline int -client_wants_fullscreen(Client *c) -{ -#ifdef XWAYLAND - if (client_is_x11(c)) - return c->surface.xwayland->fullscreen; -#endif - return c->surface.xdg->toplevel->requested.fullscreen; -} +#endif /* PEACHWM_CLIENT_H */ diff --git a/include/ext_workspace.h b/include/ext_workspace.h new file mode 100644 index 0000000..8165c01 --- /dev/null +++ b/include/ext_workspace.h @@ -0,0 +1,23 @@ +#ifndef PEACHWM_EXT_WORKSPACE_H +#define PEACHWM_EXT_WORKSPACE_H + +#include "monitor.h" + +#define EXT_WORKSPACE_CAPS \ + (EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE | \ + EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) + +struct ext_workspace { + struct wl_list link; + uint32_t tag; + Monitor *m; + struct wlr_ext_workspace_handle_v1 *handle; +}; + +/* Functions */ +void ext_workspace_createmon(Monitor *m); +void ext_workspace_cleanupmon(Monitor *m); +void ext_workspace_printstatus(Monitor *m); +void workspaces_init(void); + +#endif /* PEACHWM_EXT_WORKSPACE_H */ diff --git a/include/ipc.h b/include/ipc.h index 8e855dd..f6df26f 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -1,18 +1,36 @@ +#pragma once #include "peachwm-ipc-unstable-v2-protocol.h" +#include "monitor.h" -/* Forward declarations for peachwm.c static functions */ +/* macros needed by IPC */ +#ifndef TAGCOUNT +#define TAGCOUNT 9 +#endif +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TAGMASK ((1u << TAGCOUNT) - 1) -static void arrange(Monitor *m); -static void focusclient(Client *c, int lift); -static Client *focustop(Monitor *m); -static void printstatus(void); +/* Forward declarations from peachwm.c */ -/* globals */ +extern struct wl_display *dpy; +extern struct wl_list mons; +extern Monitor *selmon; +extern struct wl_list clients; +extern const Layout layouts[]; +extern const unsigned int layout_count; -static struct wl_global *ipc_global = NULL; -/* All bound manager objects */ -static struct wl_list ipc_managers; /* IpcManager.link */ +/* Functions from peachwm.c called by IPC */ +void arrange(Monitor *m); +void focusclient(Client *c, int lift); +Client *focustop(Monitor *m); +void printstatus(void); +const Layout *curlayout(Monitor *m); +int current_tag_idx(Monitor *m); +/* IPC-specific globals */ +extern struct wl_global *ipc_global; +extern struct wl_list ipc_managers; + +/* Types */ typedef struct { struct wl_resource *resource; struct wl_list link; /* ipc_managers */ @@ -25,266 +43,6 @@ typedef struct { struct wl_list link; /* IpcManager.outputs */ } IpcOutput; -/* helpers */ - -static IpcOutput * -ipc_output_for_mon(IpcManager *mgr, Monitor *m) -{ - IpcOutput *o; - wl_list_for_each(o, &mgr->outputs, link) - if (o->mon == m) - return o; - return NULL; -} - -/* Send the full state of monitor m to one IpcOutput resource */ -static void -ipc_send_output_state(IpcOutput *ipc_out) -{ - Monitor *m = ipc_out->mon; - struct wl_resource *res = ipc_out->resource; - Client *c; - uint32_t occ = 0, urg = 0, sel_tags = 0; - int tag, focused; - - /* Compute occupancy/urgency (same logic as printstatus, I think) */ - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - occ |= c->tags; - if (c->isurgent) - urg |= c->tags; - } - - c = focustop(m); - sel_tags = c ? c->tags : 0; - - /* active */ - zpeachwm_ipc_output_v2_send_active(res, m == selmon ? 1 : 0); - - /* per-tag state */ - for (tag = 0; tag < TAGCOUNT; tag++) { - uint32_t mask = 1u << tag; - uint32_t state = ZPEACHWM_IPC_OUTPUT_V2_TAG_STATE_NONE; - uint32_t clients_on_tag = 0; - - if (m->tagset[m->seltags] & mask) - state = ZPEACHWM_IPC_OUTPUT_V2_TAG_STATE_ACTIVE; - if (urg & mask) - state = ZPEACHWM_IPC_OUTPUT_V2_TAG_STATE_URGENT; - if (occ & mask) - clients_on_tag = 1; /* report ≥1, not exact count */ - - focused = (sel_tags & mask) ? 1 : 0; - zpeachwm_ipc_output_v2_send_tag(res, tag, state, clients_on_tag, focused); - } - - /* layout index */ - { - uint32_t i; - for (i = 0; i < LENGTH(layouts); i++) - if (&layouts[i] == curlayout(m)) - break; - zpeachwm_ipc_output_v2_send_layout(res, i); - } - - /* layout symbol */ - zpeachwm_ipc_output_v2_send_layout_symbol(res, m->ltsymbol[current_tag_idx(m)]); - - /* title/appid/fullscreen/floating */ - if (c) { - zpeachwm_ipc_output_v2_send_title(res, client_get_title(c)); - zpeachwm_ipc_output_v2_send_appid(res, client_get_appid(c)); - if (wl_resource_get_version(res) >= 2) { - zpeachwm_ipc_output_v2_send_fullscreen(res, c->isfullscreen ? 1 : 0); - zpeachwm_ipc_output_v2_send_floating(res, c->isfloating ? 1 : 0); - } - } else { - zpeachwm_ipc_output_v2_send_title(res, ""); - zpeachwm_ipc_output_v2_send_appid(res, ""); - if (wl_resource_get_version(res) >= 2) { - zpeachwm_ipc_output_v2_send_fullscreen(res, 0); - zpeachwm_ipc_output_v2_send_floating(res, 0); - } - } - - /* frame — signals end of this batch of events */ - zpeachwm_ipc_output_v2_send_frame(res); -} - -/* Called instead of (or alongside) the old printstatus() */ -static void -ipc_printstatus(void) -{ - IpcManager *mgr; - Monitor *m; - - wl_list_for_each(mgr, &ipc_managers, link) { - wl_list_for_each(m, &mons, link) { - IpcOutput *out = ipc_output_for_mon(mgr, m); - if (out) - ipc_send_output_state(out); - } - } -} - -/* zpeachwm_ipc_output_v2 requests */ - -static void -ipc_output_handle_release(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -ipc_output_handle_set_tags(struct wl_client *client, struct wl_resource *resource, - uint32_t tagmask, uint32_t toggle_tagset) -{ - IpcOutput *out = wl_resource_get_user_data(resource); - if (!out) return; - Monitor *m = out->mon; - if (!m) return; - - if (toggle_tagset) - m->seltags ^= 1; - m->tagset[m->seltags] = tagmask & TAGMASK; - focusclient(focustop(m), 1); - arrange(m); - printstatus(); -} - -static void -ipc_output_handle_set_client_tags(struct wl_client *client, - struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags) -{ - IpcOutput *out = wl_resource_get_user_data(resource); - if (!out) return; - Monitor *m = out->mon; - Client *sel; - uint32_t newtags; - if (!m) return; - - sel = focustop(m); - if (!sel) return; - - newtags = (sel->tags & and_tags) ^ xor_tags; - if (!newtags) return; - sel->tags = newtags; - focusclient(focustop(m), 1); - arrange(m); - printstatus(); -} - -static void -ipc_output_handle_set_layout(struct wl_client *client, - struct wl_resource *resource, uint32_t idx) -{ - IpcOutput *out = wl_resource_get_user_data(resource); - if (!out) return; - Monitor *m = out->mon; - if (!m || idx >= LENGTH(layouts)) return; - - int ti = current_tag_idx(m); - if (m->lt[ti][m->sellt[ti]] != &layouts[idx]) - m->sellt[ti] ^= 1; - m->lt[ti][m->sellt[ti]] = &layouts[idx]; - strncpy(m->ltsymbol[ti], layouts[idx].symbol, LENGTH(m->ltsymbol[ti])); - arrange(m); - printstatus(); -} - -static const struct zpeachwm_ipc_output_v2_interface ipc_output_impl = { - .release = ipc_output_handle_release, - .set_tags = ipc_output_handle_set_tags, - .set_client_tags = ipc_output_handle_set_client_tags, - .set_layout = ipc_output_handle_set_layout, -}; - -static void -ipc_output_destroy(struct wl_resource *resource) -{ - IpcOutput *out = wl_resource_get_user_data(resource); - wl_list_remove(&out->link); - free(out); -} - -/* zpeachwm_ipc_manager_v2 requests */ - -static void -ipc_manager_handle_release(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -ipc_manager_handle_get_output(struct wl_client *client, struct wl_resource *resource, - uint32_t id, struct wl_resource *output_resource) -{ - IpcManager *mgr = wl_resource_get_user_data(resource); - struct wlr_output *wlr_out = wlr_output_from_resource(output_resource); - Monitor *m = (wlr_out && wlr_out->data) ? wlr_out->data : NULL; - - IpcOutput *out = ecalloc(1, sizeof(*out)); - out->mon = m; - out->resource = wl_resource_create(client, &zpeachwm_ipc_output_v2_interface, - wl_resource_get_version(resource), id); - if (!out->resource) { - free(out); - wl_client_post_no_memory(client); - return; - } - wl_resource_set_implementation(out->resource, &ipc_output_impl, - out, ipc_output_destroy); - wl_list_insert(&mgr->outputs, &out->link); - - if (m) - ipc_send_output_state(out); -} - -static const struct zpeachwm_ipc_manager_v2_interface ipc_manager_impl = { - .release = ipc_manager_handle_release, - .get_output = ipc_manager_handle_get_output, -}; - -static void -ipc_manager_destroy(struct wl_resource *resource) -{ - IpcManager *mgr = wl_resource_get_user_data(resource); - IpcOutput *out, *tmp; - wl_list_for_each_safe(out, tmp, &mgr->outputs, link) - wl_resource_destroy(out->resource); - wl_list_remove(&mgr->link); - free(mgr); -} - -/* global bind */ - -static void -ipc_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) -{ - IpcManager *mgr = ecalloc(1, sizeof(*mgr)); - wl_list_init(&mgr->outputs); - mgr->resource = wl_resource_create(client, &zpeachwm_ipc_manager_v2_interface, - version, id); - if (!mgr->resource) { - free(mgr); - wl_client_post_no_memory(client); - return; - } - wl_resource_set_implementation(mgr->resource, &ipc_manager_impl, - mgr, ipc_manager_destroy); - wl_list_insert(&ipc_managers, &mgr->link); - - /* Announce tags and layouts */ - zpeachwm_ipc_manager_v2_send_tags(mgr->resource, TAGCOUNT); - for (uint32_t i = 0; i < LENGTH(layouts); i++) - zpeachwm_ipc_manager_v2_send_layout(mgr->resource, layouts[i].symbol); -} - -static void -ipc_init(void) -{ - wl_list_init(&ipc_managers); - ipc_global = wl_global_create(dpy, &zpeachwm_ipc_manager_v2_interface, - 2, NULL, ipc_bind); -} +/* Public IPC API */ +void ipc_printstatus(void); +void ipc_init(void); diff --git a/include/ipc_socket.h b/include/ipc_socket.h index 797e1c0..4126715 100644 --- a/include/ipc_socket.h +++ b/include/ipc_socket.h @@ -2,9 +2,8 @@ * See LICENSE file for copyright and license details. * PeachWM IPC Socket - swaymsg-compatible Unix domain socket IPC * - * This is included directly in peachwm.c (similar to ipc.h / client.h). - * It provides a swaymsg-compatible JSON-based IPC over a Unix domain socket, - * allowing external tools like waybar to query peachwm state. + * This header declares the public API for the swaymsg-compatible + * JSON-based IPC over a Unix domain socket. * * Protocol (sway/i3-compatible): * Header: "i3-ipc" magic (6 bytes) @@ -21,18 +20,12 @@ * 0x80000002 = mode 0x80000003 = window */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#ifndef PEACHWM_IPC_SOCKET_H +#define PEACHWM_IPC_SOCKET_H + +#include +#include +#include /* ------------------------------------------------------------------ */ /* Protocol constants */ @@ -85,1152 +78,14 @@ struct ipc_client { int header_done; }; -/* ------------------------------------------------------------------ */ -/* Globals */ -/* ------------------------------------------------------------------ */ - -static int ipc_listen_fd = -1; -static struct wl_event_source *ipc_listen_src; -static struct wl_list ipc_clients; -static struct wl_event_loop *ipc_event_loop; -static char ipc_socket_path[256]; - -/* ------------------------------------------------------------------ */ -/* Simple JSON builder */ -/* ------------------------------------------------------------------ */ - -struct json_writer { - char *buf; - size_t len; - size_t cap; - int depth; - int need_comma[32]; -}; - -static void -json_init(struct json_writer *w) -{ - w->cap = 4096; - w->buf = ecalloc(1, w->cap); - w->len = 0; - w->depth = -1; - memset(w->need_comma, 0, sizeof(w->need_comma)); -} - -static void -json_finish(struct json_writer *w) -{ - free(w->buf); -} - -static void -json_grow(struct json_writer *w, size_t needed) -{ - if (w->len + needed <= w->cap) - return; - while (w->cap < w->len + needed) - w->cap *= 2; - w->buf = realloc(w->buf, w->cap); - if (!w->buf) - die("json_grow: realloc:"); -} - -static void -json_raw(struct json_writer *w, const char *s, size_t n) -{ - json_grow(w, n); - memcpy(w->buf + w->len, s, n); - w->len += n; -} - -static void -json_puts(struct json_writer *w, const char *s) -{ - json_raw(w, s, strlen(s)); -} - -__attribute__((__format__(__printf__, 2, 3))) -static void -json_printf(struct json_writer *w, const char *fmt, ...) -{ - va_list ap; - int n; - json_grow(w, 128); - va_start(ap, fmt); - n = vsnprintf(w->buf + w->len, w->cap - w->len, fmt, ap); - va_end(ap); - if ((size_t)n >= w->cap - w->len) { - json_grow(w, (size_t)n + 1); - va_start(ap, fmt); - n = vsnprintf(w->buf + w->len, w->cap - w->len, fmt, ap); - va_end(ap); - } - w->len += (size_t)n; -} - -static void -json_comma(struct json_writer *w) -{ - if (w->need_comma[w->depth + 1]) - json_puts(w, ","); - w->need_comma[w->depth + 1] = 1; -} - -static void -json_object_start(struct json_writer *w) -{ - json_comma(w); - json_puts(w, "{"); - ++w->depth; - w->need_comma[w->depth + 1] = 0; -} - -static void -json_object_end(struct json_writer *w) -{ - json_puts(w, "}"); - --w->depth; -} - -static void -json_array_start(struct json_writer *w) -{ - json_comma(w); - json_puts(w, "["); - ++w->depth; - w->need_comma[w->depth + 1] = 0; -} - -static void -json_array_end(struct json_writer *w) -{ - json_puts(w, "]"); - --w->depth; -} - -static void -json_key(struct json_writer *w, const char *key) -{ - json_comma(w); - json_printf(w, "\"%s\":", key); -} - -static void -json_string(struct json_writer *w, const char *s) -{ - json_comma(w); - json_puts(w, "\""); - for (; *s; s++) { - switch (*s) { - case '"': json_puts(w, "\\\""); break; - case '\\': json_puts(w, "\\\\"); break; - case '\n': json_puts(w, "\\n"); break; - case '\r': json_puts(w, "\\r"); break; - case '\t': json_puts(w, "\\t"); break; - default: - if ((unsigned char)*s < 0x20) - json_printf(w, "\\u%04x", - (unsigned char)*s); - else - json_raw(w, s, 1); - break; - } - } - json_puts(w, "\""); -} - -static void -json_integer(struct json_writer *w, int64_t val) -{ - json_comma(w); - json_printf(w, "%" PRId64, val); -} - -static void -json_bool(struct json_writer *w, int val) -{ - json_comma(w); - json_puts(w, val ? "true" : "false"); -} - -static void -json_float(struct json_writer *w, double val) -{ - json_comma(w); - json_printf(w, "%.2f", val); -} - -#define json_key_string(w, k, v) do { \ - json_key(w, k); json_string(w, v); } while(0) -#define json_key_int(w, k, v) do { \ - json_key(w, k); json_integer(w, v); } while(0) -#define json_key_bool(w, k, v) do { \ - json_key(w, k); json_bool(w, v); } while(0) -#define json_key_float(w, k, v) do { \ - json_key(w, k); json_float(w, v); } while(0) - -/* ------------------------------------------------------------------ */ -/* Helper: tag index (0-based) -> workspace name */ -/* ------------------------------------------------------------------ */ - -static const char * -ipc_tag_name(int idx) -{ - static const char *names[] = { - "1", "2", "3", "4", "5", "6", "7", "8", "9", - }; - if (idx >= 0 && idx < (int)LENGTH(names)) - return names[idx]; - return "?"; -} - -/* ------------------------------------------------------------------ */ -/* Send a raw message (header + payload) to a client fd */ -/* ------------------------------------------------------------------ */ - -static int -ipc_send_raw(int fd, uint32_t type, const char *payload, size_t len) -{ - uint8_t hdr[IPC_HEADER_SIZE]; - uint32_t nlen = (uint32_t)len; - - memcpy(hdr, IPC_MAGIC, IPC_MAGIC_LEN); - memcpy(hdr + IPC_MAGIC_LEN, &nlen, sizeof(nlen)); - memcpy(hdr + IPC_MAGIC_LEN + 4, &type, sizeof(type)); - - struct iovec iov[2]; - iov[0].iov_base = hdr; - iov[0].iov_len = IPC_HEADER_SIZE; - iov[1].iov_base = (void *)payload; - iov[1].iov_len = len; - - struct msghdr msg = {0}; - msg.msg_iov = iov; - msg.msg_iovlen = 2; - - ssize_t ret; - do { - ret = sendmsg(fd, &msg, MSG_NOSIGNAL); - } while (ret < 0 && errno == EINTR); - - if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK) - return -1; - return 0; -} - -static int -ipc_send_json(int fd, uint32_t type, struct json_writer *w) -{ - return ipc_send_raw(fd, type, w->buf, w->len); -} - -/* ------------------------------------------------------------------ */ -/* Broadcast an event to all subscribed clients */ -/* ------------------------------------------------------------------ */ - -static void -ipc_broadcast(uint32_t event_type, struct json_writer *w) -{ - struct ipc_client *c; - uint32_t flag; - - switch (event_type) { - case IPC_EVENT_WORKSPACE: flag = IPC_SUB_WORKSPACE; break; - case IPC_EVENT_OUTPUT: flag = IPC_SUB_OUTPUT; break; - case IPC_EVENT_WINDOW: flag = IPC_SUB_WINDOW; break; - case IPC_EVENT_MODE: flag = IPC_SUB_MODE; break; - default: flag = ~0u; break; - } - - wl_list_for_each(c, &ipc_clients, link) { - if (c->subscribed & flag) - ipc_send_json(c->fd, event_type, w); - } -} - -/* ------------------------------------------------------------------ */ -/* JSON response builders */ -/* ------------------------------------------------------------------ */ - -static void -ipc_build_workspace(struct json_writer *w, Monitor *m, uint32_t tag_bit, - int tag_idx) -{ - Client *c; - int is_urgent = 0; - int visible = !!(m->tagset[m->seltags] & tag_bit); - int num = 0; - - /* compute tag number (1-9) */ - { - uint32_t t = tag_bit; - while (t) { num++; t >>= 1; } - } - - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - if (c->tags & tag_bit) { - if (c->isurgent) - is_urgent = 1; - } - } - - json_object_start(w); - json_key_int(w, "id", (int64_t)tag_bit + - ((int64_t)(uintptr_t)m << 32)); - json_key_int(w, "num", num); - json_key_string(w, "name", ipc_tag_name(tag_idx)); - json_key_bool(w, "visible", visible); - json_key_bool(w, "focused", visible && m == selmon); - json_key_bool(w, "urgent", !!is_urgent); - - json_key(w, "rect"); - json_object_start(w); - json_key_int(w, "x", m->m.x); - json_key_int(w, "y", m->m.y); - json_key_int(w, "width", m->m.width); - json_key_int(w, "height", m->m.height); - json_object_end(w); - - json_key_string(w, "output", m->wlr_output->name); - json_key_string(w, "layout", m->ltsymbol[tag_idx]); - json_key_string(w, "representation", ""); - json_key_string(w, "type", "workspace"); - - json_key(w, "focus"); - json_array_start(w); - c = focustop(m); - if (c && (c->tags & tag_bit)) - json_integer(w, (int64_t)(uintptr_t)c); - json_array_end(w); - - json_key(w, "floating_nodes"); - json_array_start(w); - wl_list_for_each(c, &clients, link) { - if (c->mon == m && (c->tags & tag_bit) && c->isfloating && - !c->isfullscreen) - json_integer(w, (int64_t)(uintptr_t)c); - } - json_array_end(w); - - json_key(w, "nodes"); - json_array_start(w); - wl_list_for_each(c, &clients, link) { - if (c->mon == m && (c->tags & tag_bit) && !c->isfloating && - !c->isfullscreen) - json_integer(w, (int64_t)(uintptr_t)c); - } - json_array_end(w); - json_object_end(w); -} - -static void -ipc_build_output(struct json_writer *w, Monitor *m) -{ - struct wlr_output *o = m->wlr_output; - struct wlr_output_mode *mode = NULL; - int ti; - - if (o->current_mode) - mode = o->current_mode; - else - mode = wlr_output_preferred_mode(o); - - /* Determine current workspace name = first visible tag name */ - const char *ws_name = ""; - for (ti = 0; ti < TAGCOUNT; ti++) { - if (m->tagset[m->seltags] & (1u << ti)) { - ws_name = ipc_tag_name(ti); - break; - } - } - - json_object_start(w); - json_key_string(w, "name", o->name ? o->name : ""); - json_key_string(w, "make", o->make ? o->make : ""); - json_key_string(w, "model", o->model ? o->model : ""); - json_key_string(w, "serial", o->serial ? o->serial : ""); - json_key_bool(w, "active", o->enabled); - json_key_bool(w, "primary", - m == wl_container_of(mons.next, m, link)); - json_key_float(w, "scale", o->scale); - json_key_string(w, "subpixel_hinting", "rgb"); - json_key_string(w, "transform", - o->transform == WL_OUTPUT_TRANSFORM_NORMAL ? - "normal" : "90"); - json_key_string(w, "current_workspace", ws_name); - - json_key(w, "modes"); - json_array_start(w); - if (mode) { - json_object_start(w); - json_key_int(w, "width", mode->width); - json_key_int(w, "height", mode->height); - json_key_int(w, "refresh", mode->refresh); - json_object_end(w); - } - json_array_end(w); - - if (mode) { - json_key(w, "current_mode"); - json_object_start(w); - json_key_int(w, "width", mode->width); - json_key_int(w, "height", mode->height); - json_key_int(w, "refresh", mode->refresh); - json_object_end(w); - } - - json_key(w, "rect"); - json_object_start(w); - json_key_int(w, "x", m->m.x); - json_key_int(w, "y", m->m.y); - json_key_int(w, "width", m->m.width); - json_key_int(w, "height", m->m.height); - json_object_end(w); - json_object_end(w); -} - -static void -ipc_build_client(struct json_writer *w, Client *c) -{ - int floating = c->isfloating || - !curlayout(c->mon)->arrange; - - json_object_start(w); - json_key_int(w, "id", (int64_t)(uintptr_t)c); - json_key_string(w, "type", "con"); - json_key_string(w, "orientation", "none"); - json_key_int(w, "percent", -1); - json_key_bool(w, "urgent", !!c->isurgent); - json_key_bool(w, "focused", c == focustop(selmon)); - json_key_string(w, "layout", "none"); - json_key_string(w, "app_id", client_get_appid(c)); - json_key_string(w, "name", client_get_title(c)); - json_key_string(w, "title", client_get_title(c)); - json_key_bool(w, "fullscreen_mode", !!c->isfullscreen); - json_key_bool(w, "floating", !!floating); - - json_key(w, "window_properties"); - json_object_start(w); - json_key_string(w, "class", client_get_appid(c)); - json_key_string(w, "instance", client_get_appid(c)); - json_key_string(w, "title", client_get_title(c)); - json_key_bool(w, "transient", 0); - json_object_end(w); - - json_key(w, "marks"); - json_array_start(w); - json_array_end(w); - - json_key(w, "rect"); - json_object_start(w); - json_key_int(w, "x", c->geom.x); - json_key_int(w, "y", c->geom.y); - json_key_int(w, "width", c->geom.width); - json_key_int(w, "height", c->geom.height); - json_object_end(w); - - json_key(w, "window_rect"); - json_object_start(w); - json_key_int(w, "x", (int)c->bw); - json_key_int(w, "y", (int)c->bw); - json_key_int(w, "width", - c->geom.width - 2 * (int)c->bw); - json_key_int(w, "height", - c->geom.height - 2 * (int)c->bw); - json_object_end(w); - - json_key(w, "deco_rect"); - json_object_start(w); - json_key_int(w, "x", 0); - json_key_int(w, "y", 0); - json_key_int(w, "width", 0); - json_key_int(w, "height", 0); - json_object_end(w); - - json_key(w, "geometry"); - json_object_start(w); - json_key_int(w, "x", c->geom.x); - json_key_int(w, "y", c->geom.y); - json_key_int(w, "width", c->geom.width); - json_key_int(w, "height", c->geom.height); - json_object_end(w); - - json_key(w, "border"); - json_object_start(w); - json_key_int(w, "top", (int)c->bw); - json_key_int(w, "bottom", (int)c->bw); - json_key_int(w, "right", (int)c->bw); - json_key_int(w, "left", (int)c->bw); - json_object_end(w); - - json_key_int(w, "current_border_width", (int64_t)c->bw); - - json_key_int(w, "workspace", current_tag_idx(c->mon) + 1); - - json_key(w, "nodes"); - json_array_start(w); - json_array_end(w); - - json_key(w, "floating_nodes"); - json_array_start(w); - json_array_end(w); - json_object_end(w); -} - -/* ------------------------------------------------------------------ */ -/* Command handlers */ -/* ------------------------------------------------------------------ */ - -static void -ipc_handle_get_workspaces(struct ipc_client *client) -{ - struct json_writer w; - Monitor *m; - int i; - - json_init(&w); - json_array_start(&w); - wl_list_for_each(m, &mons, link) { - for (i = 0; i < TAGCOUNT; i++) { - uint32_t tag_bit = 1u << i; - if (m->tagset[m->seltags] & tag_bit) - ipc_build_workspace(&w, m, tag_bit, i); - } - } - json_array_end(&w); - ipc_send_json(client->fd, IPC_GET_WORKSPACES, &w); - json_finish(&w); -} - -static void -ipc_handle_get_outputs(struct ipc_client *client) -{ - struct json_writer w; - Monitor *m; - - json_init(&w); - json_array_start(&w); - wl_list_for_each(m, &mons, link) { - ipc_build_output(&w, m); - } - json_array_end(&w); - ipc_send_json(client->fd, IPC_GET_OUTPUTS, &w); - json_finish(&w); -} - -static void -ipc_handle_get_version(struct ipc_client *client) -{ - struct json_writer w; - - json_init(&w); - json_object_start(&w); - json_key_string(&w, "variant", "peachwm"); - json_key_string(&w, "human_readable", - "peachwm " VERSION); - json_key_int(&w, "major", 0); - json_key_int(&w, "minor", 2); - json_key_int(&w, "patch", 0); - json_key_string(&w, "loaded_config_file_name", ""); - json_object_end(&w); - ipc_send_json(client->fd, IPC_GET_VERSION, &w); - json_finish(&w); -} - -static void -ipc_handle_get_tree(struct ipc_client *client) -{ - struct json_writer w; - Monitor *m; - Client *c; - int i; - - json_init(&w); - - /* root node */ - json_object_start(&w); - json_key_string(&w, "type", "root"); - json_key_string(&w, "name", "root"); - - json_key(&w, "nodes"); - json_array_start(&w); - - wl_list_for_each(m, &mons, link) { - /* output node */ - json_object_start(&w); - json_key_string(&w, "type", "output"); - json_key_string(&w, "name", m->wlr_output->name); - json_key_bool(&w, "active", m->wlr_output->enabled); - - json_key(&w, "rect"); - json_object_start(&w); - json_key_int(&w, "x", m->m.x); - json_key_int(&w, "y", m->m.y); - json_key_int(&w, "width", m->m.width); - json_key_int(&w, "height", m->m.height); - json_object_end(&w); - - json_key(&w, "nodes"); - json_array_start(&w); - - for (i = 0; i < TAGCOUNT; i++) { - uint32_t tag_bit = 1u << i; - if (!(m->tagset[m->seltags] & tag_bit)) - continue; - - /* workspace node */ - json_object_start(&w); - json_key_string(&w, "type", "workspace"); - json_key_string(&w, "name", - ipc_tag_name(i)); - json_key_string(&w, "layout", m->ltsymbol[i]); - - json_key(&w, "rect"); - json_object_start(&w); - json_key_int(&w, "x", m->m.x); - json_key_int(&w, "y", m->m.y); - json_key_int(&w, "width", m->m.width); - json_key_int(&w, "height", m->m.height); - json_object_end(&w); - - /* tiled nodes */ - json_key(&w, "nodes"); - json_array_start(&w); - wl_list_for_each(c, &clients, link) { - if (c->mon == m && - (c->tags & tag_bit) && - !c->isfloating && - !c->isfullscreen) - ipc_build_client(&w, c); - } - json_array_end(&w); - - /* floating nodes */ - json_key(&w, "floating_nodes"); - json_array_start(&w); - wl_list_for_each(c, &clients, link) { - if (c->mon == m && - (c->tags & tag_bit) && - (c->isfloating || - c->isfullscreen)) - ipc_build_client(&w, c); - } - json_array_end(&w); - - json_object_end(&w); /* workspace */ - } - json_array_end(&w); /* output nodes */ - - json_key(&w, "floating_nodes"); - json_array_start(&w); - json_array_end(&w); - - json_object_end(&w); /* output */ - } - json_array_end(&w); /* root nodes */ - - json_key(&w, "floating_nodes"); - json_array_start(&w); - json_array_end(&w); - - json_object_end(&w); /* root */ - - ipc_send_json(client->fd, IPC_GET_TREE, &w); - json_finish(&w); -} - -static void -ipc_handle_command(struct ipc_client *client, const char *payload, - size_t len) -{ - struct json_writer w; - int success = 0; - char *cmd = NULL; - - if (len > 0) { - cmd = ecalloc(1, len + 1); - memcpy(cmd, payload, len); - cmd[len] = '\0'; - } - - if (cmd) { - if (!strncmp(cmd, "focus workspace", 15)) { - const char *ws = cmd + 15; - while (*ws == ' ') ws++; - if (*ws) { - int num = atoi(ws); - if (num >= 1 && num <= TAGCOUNT) { - view(&(Arg){.ui = - 1u << (num - 1)}); - success = 1; - } - } - } else if (!strcmp(cmd, "focus left")) { - focusdir(&(Arg){.i = WLR_DIRECTION_LEFT}); - success = 1; - } else if (!strcmp(cmd, "focus right")) { - focusdir(&(Arg){.i = WLR_DIRECTION_RIGHT}); - success = 1; - } else if (!strcmp(cmd, "focus up")) { - focusdir(&(Arg){.i = WLR_DIRECTION_UP}); - success = 1; - } else if (!strcmp(cmd, "focus down")) { - focusdir(&(Arg){.i = WLR_DIRECTION_DOWN}); - success = 1; - } else if (!strncmp(cmd, "move workspace", 14)) { - const char *ws = cmd + 14; - while (*ws == ' ') ws++; - if (*ws) { - int num = atoi(ws); - if (num >= 1 && num <= TAGCOUNT) { - tag(&(Arg){.ui = - 1u << (num - 1)}); - success = 1; - } - } - } else if (!strcmp(cmd, "floating toggle") || - !strcmp(cmd, "floating enable") || - !strcmp(cmd, "floating disable")) { - togglefloating(NULL); - success = 1; - } else if (!strcmp(cmd, "fullscreen toggle") || - !strcmp(cmd, "fullscreen enable") || - !strcmp(cmd, "fullscreen disable")) { - togglefullscreen(NULL); - success = 1; - } else if (!strcmp(cmd, "kill") || - !strcmp(cmd, "close")) { - killclient(NULL); - success = 1; - } else if (!strncmp(cmd, "workspace", 9)) { - const char *ws = cmd + 9; - while (*ws == ' ') ws++; - if (*ws) { - int num = atoi(ws); - if (num >= 1 && num <= TAGCOUNT) { - view(&(Arg){.ui = - 1u << (num - 1)}); - success = 1; - } - } - } else if (!strcmp(cmd, "exit") || - !strcmp(cmd, "quit")) { - quit(NULL); - success = 1; - } else if (!strcmp(cmd, "reload")) { - do_reload(); - success = 1; - } - } - - free(cmd); - - json_init(&w); - json_array_start(&w); - json_object_start(&w); - json_key_bool(&w, "success", success); - if (!success) - json_key_string(&w, "error", - "Unknown or unsupported command"); - json_object_end(&w); - json_array_end(&w); - ipc_send_json(client->fd, IPC_COMMAND, &w); - json_finish(&w); -} - -static void -ipc_handle_subscribe(struct ipc_client *client, const char *payload, - size_t len) -{ - struct json_writer w; - (void)len; - - client->subscribed = 0; - - if (strstr(payload, "\"workspace\"")) - client->subscribed |= IPC_SUB_WORKSPACE; - if (strstr(payload, "\"window\"")) - client->subscribed |= IPC_SUB_WINDOW; - if (strstr(payload, "\"output\"")) - client->subscribed |= IPC_SUB_OUTPUT; - if (strstr(payload, "\"mode\"")) - client->subscribed |= IPC_SUB_MODE; - - json_init(&w); - json_array_start(&w); - json_object_start(&w); - json_key_bool(&w, "success", 1); - json_object_end(&w); - json_array_end(&w); - ipc_send_json(client->fd, IPC_SUBSCRIBE, &w); - json_finish(&w); -} - -/* ------------------------------------------------------------------ */ -/* Client I/O */ -/* ------------------------------------------------------------------ */ - -static void -ipc_client_close(struct ipc_client *c) -{ - wl_list_remove(&c->link); - if (c->src) { - wl_event_source_remove(c->src); - c->src = NULL; - } - close(c->fd); - free(c->payload); - free(c); -} - -static int -ipc_client_handle_readable(int fd, uint32_t mask, void *data) -{ - struct ipc_client *c = data; - ssize_t n; - size_t want; - (void)fd; - - if (mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP)) { - ipc_client_close(c); - return 0; - } - - if (!(mask & WL_EVENT_READABLE)) - return 0; - - if (!c->header_done) { - want = IPC_HEADER_SIZE - c->hdr_len; - n = read(c->fd, c->hdr_buf + c->hdr_len, want); - if (n <= 0) { - if (n == 0 || - (errno != EAGAIN && errno != EWOULDBLOCK)) { - ipc_client_close(c); - return 0; - } - return 0; - } - c->hdr_len += (size_t)n; - if (c->hdr_len < IPC_HEADER_SIZE) - return 0; - - /* Validate magic */ - if (memcmp(c->hdr_buf, IPC_MAGIC, IPC_MAGIC_LEN) != 0) { - ipc_client_close(c); - return 0; - } - - /* Parse header */ - memcpy(&c->payload_size, - c->hdr_buf + IPC_MAGIC_LEN, sizeof(uint32_t)); - memcpy(&c->msg_type, - c->hdr_buf + IPC_MAGIC_LEN + 4, sizeof(uint32_t)); - - if (c->payload_size > 0) { - if (c->payload_size > c->payload_alloc) { - free(c->payload); - c->payload_alloc = c->payload_size + 1; - c->payload = ecalloc(1, c->payload_alloc); - } - c->payload_len = 0; - } - c->header_done = 1; - } - - /* Read payload */ - if (c->header_done && c->payload_size > 0) { - want = c->payload_size - c->payload_len; - if (want > 0) { - n = read(c->fd, - c->payload + c->payload_len, want); - if (n <= 0) { - if (n == 0 || - (errno != EAGAIN && - errno != EWOULDBLOCK)) { - ipc_client_close(c); - return 0; - } - return 0; - } - c->payload_len += (size_t)n; - } - if (c->payload_len < c->payload_size) - return 0; - } - - /* Full message received – dispatch */ - switch (c->msg_type) { - case IPC_COMMAND: - ipc_handle_command(c, c->payload, c->payload_size); - break; - case IPC_GET_WORKSPACES: - ipc_handle_get_workspaces(c); - break; - case IPC_GET_OUTPUTS: - ipc_handle_get_outputs(c); - break; - case IPC_GET_VERSION: - ipc_handle_get_version(c); - break; - case IPC_GET_TREE: - ipc_handle_get_tree(c); - break; - case IPC_SUBSCRIBE: - ipc_handle_subscribe(c, c->payload, c->payload_size); - break; - default: - ipc_send_raw(c->fd, c->msg_type, "{}", 2); - break; - } - - /* Reset for next message */ - c->hdr_len = 0; - c->header_done = 0; - c->payload_len = 0; - c->payload_size = 0; - - return 0; -} - -/* ------------------------------------------------------------------ */ -/* Accept new connections */ -/* ------------------------------------------------------------------ */ - -static int -ipc_handle_accept(int fd, uint32_t mask, void *data) -{ - struct sockaddr_un un; - socklen_t len = sizeof(un); - int cfd; - struct ipc_client *c; - struct wl_event_source *src; - int flags; - - (void)data; - - if (!(mask & WL_EVENT_READABLE)) - return 0; - - cfd = accept(fd, (struct sockaddr *)&un, &len); - if (cfd < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) - wlr_log(WLR_ERROR, "ipc: accept: %s", - strerror(errno)); - return 0; - } - - /* Set non-blocking and close-on-exec */ - flags = fcntl(cfd, F_GETFL); - if (flags < 0 || fcntl(cfd, F_SETFL, flags | O_NONBLOCK) < 0) { - close(cfd); - return 0; - } - - c = ecalloc(1, sizeof(*c)); - c->fd = cfd; - c->hdr_len = 0; - c->header_done = 0; - c->payload_size = 0; - c->payload_len = 0; - c->payload_alloc = 0; - c->payload = NULL; - c->subscribed = 0; - - src = wl_event_loop_add_fd(ipc_event_loop, cfd, - WL_EVENT_READABLE, - ipc_client_handle_readable, c); - if (!src) { - close(cfd); - free(c); - return 0; - } - c->src = src; - - wl_list_insert(&ipc_clients, &c->link); - - wlr_log(WLR_DEBUG, "ipc: client connected (fd %d)", cfd); - return 0; -} - /* ------------------------------------------------------------------ */ /* Public API */ /* ------------------------------------------------------------------ */ -static void -ipc_socket_init(void) -{ - const char *runtime_dir; - struct sockaddr_un un; - int fd, flags; - char sock_path[256]; - struct passwd *pw; +void ipc_socket_init(void); +void ipc_socket_finish(void); +void ipc_socket_send_workspace_event(const char *change); +void ipc_socket_send_window_event(const char *change); +void ipc_socket_send_output_event(void); - wl_list_init(&ipc_clients); - ipc_event_loop = event_loop; - - runtime_dir = getenv("XDG_RUNTIME_DIR"); - if (runtime_dir && runtime_dir[0]) { - snprintf(sock_path, sizeof(sock_path), - "%s/peachwm.sock", runtime_dir); - } else { - pw = getpwuid(getuid()); - if (pw) - snprintf(sock_path, sizeof(sock_path), - "/tmp/peachwm-%s.sock", pw->pw_name); - else - snprintf(sock_path, sizeof(sock_path), - "/tmp/peachwm-%d.sock", getuid()); - } - - unlink(sock_path); - - fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (fd < 0) { - wlr_log(WLR_ERROR, "ipc: socket: %s", strerror(errno)); - return; - } - - memset(&un, 0, sizeof(un)); - un.sun_family = AF_UNIX; - strncpy(un.sun_path, sock_path, sizeof(un.sun_path) - 1); - - if (bind(fd, (struct sockaddr *)&un, sizeof(un)) < 0) { - wlr_log(WLR_ERROR, "ipc: bind(%s): %s", - sock_path, strerror(errno)); - close(fd); - return; - } - - chmod(sock_path, 0700); - - if (listen(fd, 8) < 0) { - wlr_log(WLR_ERROR, "ipc: listen: %s", strerror(errno)); - close(fd); - unlink(sock_path); - return; - } - - flags = fcntl(fd, F_GETFL); - if (flags < 0 || - fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { - wlr_log(WLR_ERROR, "ipc: fcntl(O_NONBLOCK): %s", - strerror(errno)); - close(fd); - unlink(sock_path); - return; - } - - ipc_listen_fd = fd; - strncpy(ipc_socket_path, sock_path, - sizeof(ipc_socket_path) - 1); - ipc_socket_path[sizeof(ipc_socket_path) - 1] = '\0'; - - ipc_listen_src = wl_event_loop_add_fd( - ipc_event_loop, fd, WL_EVENT_READABLE, - ipc_handle_accept, NULL); - if (!ipc_listen_src) { - wlr_log(WLR_ERROR, - "ipc: wl_event_loop_add_fd failed"); - close(fd); - unlink(sock_path); - ipc_listen_fd = -1; - ipc_socket_path[0] = '\0'; - return; - } - - wlr_log(WLR_DEBUG, "ipc: listening on %s", sock_path); -} - -static void -ipc_socket_finish(void) -{ - struct ipc_client *c, *tmp; - - wl_list_for_each_safe(c, tmp, &ipc_clients, link) - ipc_client_close(c); - - if (ipc_listen_src) { - wl_event_source_remove(ipc_listen_src); - ipc_listen_src = NULL; - } - if (ipc_listen_fd >= 0) { - close(ipc_listen_fd); - if (ipc_socket_path[0]) - unlink(ipc_socket_path); - ipc_listen_fd = -1; - ipc_socket_path[0] = '\0'; - } -} - -/* ------------------------------------------------------------------ */ -/* Event helpers called from peachwm.c */ -/* ------------------------------------------------------------------ */ - -static void -ipc_socket_send_workspace_event(const char *change) -{ - struct json_writer w; - - if (wl_list_empty(&ipc_clients)) - return; - - json_init(&w); - json_object_start(&w); - json_key_string(&w, "change", change ? change : "focus"); - - if (selmon) { - int ti = current_tag_idx(selmon); - uint32_t tag_bit = 1u << ti; - if (selmon->tagset[selmon->seltags] & tag_bit) { - json_key(&w, "current"); - ipc_build_workspace(&w, selmon, tag_bit, ti); - } - } - - json_object_end(&w); - ipc_broadcast((uint32_t)IPC_EVENT_WORKSPACE, &w); - json_finish(&w); -} - -static void -ipc_socket_send_window_event(const char *change) -{ - struct json_writer w; - - if (wl_list_empty(&ipc_clients)) - return; - - json_init(&w); - json_object_start(&w); - json_key_string(&w, "change", change ? change : "focus"); - - if (selmon) { - Client *c = focustop(selmon); - if (c) { - json_key(&w, "container"); - ipc_build_client(&w, c); - } - } - - json_object_end(&w); - ipc_broadcast((uint32_t)IPC_EVENT_WINDOW, &w); - json_finish(&w); -} - -static void -ipc_socket_send_output_event(void) -{ - struct json_writer w; - - if (wl_list_empty(&ipc_clients)) - return; - - json_init(&w); - json_object_start(&w); - json_key_string(&w, "change", "unspecified"); - json_object_end(&w); - ipc_broadcast((uint32_t)IPC_EVENT_OUTPUT, &w); - json_finish(&w); -} +#endif /* PEACHWM_IPC_SOCKET_H */ diff --git a/include/monitor.h b/include/monitor.h new file mode 100644 index 0000000..d3a34de --- /dev/null +++ b/include/monitor.h @@ -0,0 +1,58 @@ +#ifndef PEACHWM_MONITOR_H +#define PEACHWM_MONITOR_H + +#include +#include +#include +#include "wlr_ext_workspace_v1.h" + +/* forward declarations */ +struct Client; +struct LayerSurface; +typedef struct DwindleNode DwindleNode; + +#ifndef TAGCOUNT +#define TAGCOUNT 9 +#endif + +typedef struct Monitor Monitor; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[TAGCOUNT][2]; /* per-tag layout slots */ + int gaps; + unsigned int seltags; + struct wlr_ext_workspace_group_handle_v1 *ext_group; + unsigned int sellt[TAGCOUNT]; /* per-tag layout toggle index */ + uint32_t tagset[2]; + float mfact; + int nmaster; + char ltsymbol[TAGCOUNT][16]; /* per-tag layout symbol */ + int asleep; + DwindleNode *dwindle_root[TAGCOUNT]; /* one tree per tag */ + struct Client *dwindle_focus[TAGCOUNT]; /* insertion anchor, one per tag */ + /* master/stack layout state */ + struct Client *master_master[TAGCOUNT]; /* the master client (NULL if none) */ + int master_side[TAGCOUNT]; /* 0 = master on left, 1 = master on right */ + int scratchpad_visible; /* whether scratchpad is shown */ + struct Client *scratchpad_prev_focus; /* client focused before scratchpad opened */ + struct Client *scratchpad_current; /* currently visible scratchpad client */ +}; + +#endif /* PEACHWM_MONITOR_H */ diff --git a/parser/parser.c b/parser/parser.c index ef66ef2..eabb31f 100644 --- a/parser/parser.c +++ b/parser/parser.c @@ -34,7 +34,7 @@ extern struct wl_list clients; /* Internal watcher state */ -typedef struct { +typedef struct WatchState { int inotify_fd; int watch_fd; char path[1024]; @@ -802,7 +802,7 @@ watch_dispatch(int fd, uint32_t mask, void *data) return 0; } -struct wl_event_source * +WatchState * config_watch_start(struct wl_event_loop *loop, const char *path, config_reload_cb cb, void *userdata) { @@ -845,14 +845,13 @@ config_watch_start(struct wl_event_loop *loop, const char *path, ws->event_src = src; fprintf(stderr, "peachwm config: watching '%s'\n", path); - return (struct wl_event_source *)ws; + return ws; } void -config_watch_stop(struct wl_event_source *src) +config_watch_stop(WatchState *ws) { - if (!src) return; - WatchState *ws = (WatchState *)src; + if (!ws) return; wl_event_source_remove(ws->event_src); close(ws->inotify_fd); free(ws); diff --git a/parser/parser.h b/parser/parser.h index c093ca4..a0d96d9 100644 --- a/parser/parser.h +++ b/parser/parser.h @@ -149,13 +149,15 @@ void config_autostart_run(const Config *cfg); typedef void (*config_reload_cb)(const Config *cfg, void *userdata); -struct wl_event_source *config_watch_start( +typedef struct WatchState WatchState; + +WatchState *config_watch_start( struct wl_event_loop *loop, const char *path, config_reload_cb cb, void *userdata ); -void config_watch_stop(struct wl_event_source *src); +void config_watch_stop(WatchState *ws); #endif diff --git a/peachmsg/peachmsg.c b/peachmsg/peachmsg.c index f9bcb4b..d4f9a5f 100644 --- a/peachmsg/peachmsg.c +++ b/peachmsg/peachmsg.c @@ -80,8 +80,8 @@ static void peachwm_ipc_output_toggle_visibility(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_output) { if (!vflag) return; - char *oname = data; - if (oname) printf("%s ", oname); + char *oname = data ? (char *)data : ""; + printf("%s ", oname); printf("toggle\n"); } @@ -89,15 +89,25 @@ static void peachwm_ipc_output_active(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_output, uint32_t active) { - if (active && (!output_name || !*output_name)) - output_name = strdup(data); + if (active && (!output_name || !*output_name)) { + if (data) { + output_name = strdup(data); + if (!output_name) + fprintf(stderr, "peachmsg: strdup failed\n"); + } + } if (active) { free(focused_output); - focused_output = data ? strdup(data) : NULL; + focused_output = NULL; + if (data) { + focused_output = strdup(data); + if (!focused_output) + fprintf(stderr, "peachmsg: strdup failed\n"); + } } if (!oflag) return; - char *oname = data; - if (oname) printf("%s ", oname); + char *oname = data ? (char *)data : ""; + printf("%s ", oname); printf("selmon %u\n", !!active); } @@ -110,7 +120,8 @@ peachwm_ipc_output_tag(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_ou if (state == ZPEACHWM_IPC_OUTPUT_V2_TAG_STATE_ACTIVE) urg |= 1< 0) occ |= 1<output_name = strdup(name); + if (!o->output_name) + fprintf(stderr, "peachmsg: strdup failed\n"); printf("+ "); } if (Oflag) printf("%s\n", name); @@ -340,7 +358,12 @@ global_add(void *data, struct wl_registry *wl_registry, if (!outputs) return; if (outputcount >= outputs_buflen) { outputs_buflen *= 2; - outputs = reallocarray(outputs, outputs_buflen, sizeof(struct output)); + struct output *new_outputs = reallocarray(outputs, outputs_buflen, sizeof(struct output)); + if (!new_outputs) { + fprintf(stderr, "peachmsg: reallocarray failed\n"); + } else { + outputs = new_outputs; + } } outputs[outputcount].name = name; outputcount++; diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..1bc5628 --- /dev/null +++ b/src/client.c @@ -0,0 +1,426 @@ +/* + * Client function implementations. + * Originally inline in include/client.h, moved here for separate compilation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XWAYLAND +#include +#include +#include +#endif + +#include "client.h" +#include "util.h" + +extern struct wlr_seat *seat; + +/* Leave these functions first; they're used in the others */ +int +client_is_x11(Client *c) +{ +#ifdef XWAYLAND + return c->type == X11; +#else + return 0; +#endif +} + +struct wlr_surface * +client_surface(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->surface; +#endif + return c->surface.xdg->surface; +} + +int +toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) +{ + struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; + struct wlr_surface *root_surface; + struct wlr_layer_surface_v1 *layer_surface; + Client *c = NULL; + LayerSurface *l = NULL; + int type = -1; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; +#endif + + if (!s) + return -1; + root_surface = wlr_surface_get_root_surface(s); + +#ifdef XWAYLAND + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { + c = xsurface->data; + type = c->type; + goto end; + } +#endif + + if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) { + l = layer_surface->data; + type = LayerShell; + goto end; + } + + xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface); + while (xdg_surface) { + tmp_xdg_surface = NULL; + switch (xdg_surface->role) { + case WLR_XDG_SURFACE_ROLE_POPUP: + if (!xdg_surface->popup || !xdg_surface->popup->parent) + return -1; + + tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); + + if (!tmp_xdg_surface) + return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); + + xdg_surface = tmp_xdg_surface; + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + c = xdg_surface->data; + type = c->type; + goto end; + case WLR_XDG_SURFACE_ROLE_NONE: + return -1; + } + } + +end: + if (pl) + *pl = l; + if (pc) + *pc = c; + return type; +} + +/* The others */ +void +client_activate_surface(struct wlr_surface *s, int activated) +{ + struct wlr_xdg_toplevel *toplevel; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { + wlr_xwayland_surface_activate(xsurface, activated); + return; + } +#endif + if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s))) + wlr_xdg_toplevel_set_activated(toplevel, activated); +} + +uint32_t +client_set_bounds(Client *c, int32_t width, int32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >= + XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE && width >= 0 && height >= 0 + && (c->bounds.width != width || c->bounds.height != height)) { + c->bounds.width = width; + c->bounds.height = height; + return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height); + } + return 0; +} + +const char * +client_get_appid(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->class ? c->surface.xwayland->class : "broken"; +#endif + return c->surface.xdg->toplevel->app_id ? c->surface.xdg->toplevel->app_id : "broken"; +} + +void +client_get_clip(Client *c, struct wlr_box *clip) +{ + *clip = (struct wlr_box){ + .x = 0, + .y = 0, + .width = c->geom.width - c->bw, + .height = c->geom.height - c->bw, + }; + +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + clip->x = c->surface.xdg->geometry.x; + clip->y = c->surface.xdg->geometry.y; +} + +void +client_get_geometry(Client *c, struct wlr_box *geom) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + geom->x = c->surface.xwayland->x; + geom->y = c->surface.xwayland->y; + geom->width = c->surface.xwayland->width; + geom->height = c->surface.xwayland->height; + return; + } +#endif + *geom = c->surface.xdg->geometry; +} + +Client * +client_get_parent(Client *c) +{ + Client *p = NULL; +#ifdef XWAYLAND + if (client_is_x11(c)) { + if (c->surface.xwayland->parent) + toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); + return p; + } +#endif + if (c->surface.xdg->toplevel->parent) + toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL); + return p; +} + +int +client_has_children(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return !wl_list_empty(&c->surface.xwayland->children); +#endif + /* surface.xdg->link is never empty because it always contains at least the + * surface itself. */ + return wl_list_length(&c->surface.xdg->link) > 1; +} + +const char * +client_get_title(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->title ? c->surface.xwayland->title : "broken"; +#endif + return c->surface.xdg->toplevel->title ? c->surface.xdg->toplevel->title : "broken"; +} + +int +client_is_float_type(Client *c) +{ + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_toplevel_state state; + +#ifdef XWAYLAND + if (client_is_x11(c)) { + struct wlr_xwayland_surface *surface = c->surface.xwayland; + xcb_size_hints_t *size_hints = surface->size_hints; + if (surface->modal) + return 1; + + if (wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_SPLASH) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLBAR) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_UTILITY)) { + return 1; + } + + return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0 + && (size_hints->max_width == size_hints->min_width + || size_hints->max_height == size_hints->min_height); + } +#endif + + toplevel = c->surface.xdg->toplevel; + state = toplevel->current; + return toplevel->parent || (state.min_width != 0 && state.min_height != 0 + && (state.min_width == state.max_width + || state.min_height == state.max_height)); +} + +int +client_is_rendered_on_mon(Client *c, Monitor *m) +{ + /* This is needed for when you don't want to check formal assignment, + * but rather actual displaying of the pixels. + * Usually VISIBLEON suffices and is also faster. */ + struct wlr_surface_output *s; + int unused_lx, unused_ly; + if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly)) + return 0; + wl_list_for_each(s, &client_surface(c)->current_outputs, link) + if (s->output == m->wlr_output) + return 1; + return 0; +} + +int +client_is_stopped(Client *c) +{ + int pid; + siginfo_t in = {0}; +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + + wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); + if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) { + /* This process is not our child process, while is very unlikely that + * it is stopped, in order to do not skip frames, assume that it is. */ + if (errno == ECHILD) + return 0; + } else if (in.si_pid) { + if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) + return 1; + if (in.si_code == CLD_CONTINUED) + return 0; + } + + return 0; +} + +int +client_is_unmanaged(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->override_redirect; +#endif + return 0; +} + +void +client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) +{ + if (kb) + wlr_seat_keyboard_notify_enter(seat, s, kb->keycodes, + kb->num_keycodes, &kb->modifiers); + else + wlr_seat_keyboard_notify_enter(seat, s, NULL, 0, NULL); +} + +void +client_send_close(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_close(c->surface.xwayland); + return; + } +#endif + wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); +} + +void +client_set_border_color(Client *c, const float color[static 4]) +{ + int i; + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], color); +} + +void +client_set_fullscreen(Client *c, int fullscreen) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen); + return; + } +#endif + wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); +} + +void +client_set_scale(struct wlr_surface *s, float scale) +{ + wlr_fractional_scale_v1_notify_scale(s, scale); + wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(scale)); +} + +uint32_t +client_set_size(Client *c, uint32_t width, uint32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_configure(c->surface.xwayland, + c->geom.x + c->bw, c->geom.y + c->bw, width, height); + return 0; + } +#endif + if ((int32_t)width == c->surface.xdg->toplevel->current.width + && (int32_t)height == c->surface.xdg->toplevel->current.height) + return 0; + return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); +} + +void +client_set_tiled(Client *c, uint32_t edges) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_set_maximized(c->surface.xwayland, + edges != WLR_EDGE_NONE, edges != WLR_EDGE_NONE); + return; + } +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) + >= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) { + wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); + } else { + wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE); + } +} + +void +client_set_suspended(Client *c, int suspended) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); +} + +int +client_wants_focus(Client *c) +{ +#ifdef XWAYLAND + return client_is_unmanaged(c) + && wlr_xwayland_surface_override_redirect_wants_focus(c->surface.xwayland) + && wlr_xwayland_surface_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; +#else + return 0; +#endif +} + +int +client_wants_fullscreen(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->fullscreen; +#endif + return c->surface.xdg->toplevel->requested.fullscreen; +} diff --git a/include/ext-workspace.h b/src/ext_workspace.c similarity index 84% rename from include/ext-workspace.h rename to src/ext_workspace.c index 2c015ec..b5bf777 100644 --- a/include/ext-workspace.h +++ b/src/ext_workspace.c @@ -1,36 +1,42 @@ -/* My shitty port of Mango's ext-workspace.h for my compositor; - * thanks DreamMaoMao for doing most of the heavy lifting - * by actually writing the thing, it didn't work the first - * few tries and I barely have any idea how this code works anyway. - * To say that it's the eighth wonder of the world +/* + * ext_workspace.c — protocol glue for wlr_ext_workspace_v1 + * + * Thanks DreamMaoMao for doing most of the heavy lifting + * by actually writing the thing, it didn't work the first + * few tries and I barely have any idea how this code works anyway. + * To say that it's the eighth wonder of the world * would be an understatement -*/ + */ +#include -#include "wlr_ext_workspace_v1.h" +#include "ext_workspace.h" +#include "client.h" +#include "ipc.h" +#include "monitor.h" +#include "util.h" -#define EXT_WORKSPACE_CAPS \ - (EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE | \ - EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TAGMASK ((1u << TAGCOUNT) - 1) -/* types */ +/* Forward declarations from peachwm.c */ +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; -struct ext_workspace { - struct wl_list link; - uint32_t tag; - Monitor *m; - struct wlr_ext_workspace_handle_v1 *handle; -}; +void view(const Arg *arg); +void toggleview(const Arg *arg); -/* global */ - -static struct wlr_ext_workspace_manager_v1 *ext_workspace_manager; -static struct wl_list ext_workspaces; +/* Global state */ +struct wlr_ext_workspace_manager_v1 *ext_workspace_manager; +struct wl_list ext_workspaces; static struct wl_listener ext_commit_listener; static struct wl_listener ext_destroy_listener; /* helpers */ - static int get_tag_status(uint32_t tag, Monitor *m) { @@ -90,7 +96,7 @@ ext_workspace_create(uint32_t tag, Monitor *m) /* monitor integration */ -static void +void ext_workspace_createmon(Monitor *m) { uint32_t i; @@ -104,7 +110,7 @@ ext_workspace_createmon(Monitor *m) ext_workspace_create(i, m); } -static void +void ext_workspace_cleanupmon(Monitor *m) { struct ext_workspace *ws, *tmp; @@ -120,7 +126,7 @@ ext_workspace_cleanupmon(Monitor *m) /* status */ -static void +void ext_workspace_printstatus(Monitor *m) { struct ext_workspace *ws; @@ -194,7 +200,7 @@ handle_ext_destroy(struct wl_listener *listener, void *data) wl_list_remove(&ext_destroy_listener.link); } -static void +void workspaces_init(void) { ext_workspace_manager = wlr_ext_workspace_manager_v1_create(dpy, 1); diff --git a/src/ipc.c b/src/ipc.c new file mode 100644 index 0000000..fa2f8b2 --- /dev/null +++ b/src/ipc.c @@ -0,0 +1,276 @@ +#include +#include +#include +#include + +#include "client.h" +#include "ipc.h" +#include "util.h" + +/* IPC globals */ +struct wl_global *ipc_global = NULL; +struct wl_list ipc_managers; /* IpcManager.link */ + +/* helpers */ + +static IpcOutput * +ipc_output_for_mon(IpcManager *mgr, Monitor *m) +{ + IpcOutput *o; + wl_list_for_each(o, &mgr->outputs, link) + if (o->mon == m) + return o; + return NULL; +} + +/* Send the full state of monitor m to one IpcOutput resource */ +static void +ipc_send_output_state(IpcOutput *ipc_out) +{ + Monitor *m = ipc_out->mon; + struct wl_resource *res = ipc_out->resource; + Client *c; + uint32_t occ = 0, urg = 0, sel_tags = 0; + int tag, focused; + + /* Compute occupancy/urgency (same logic as printstatus) */ + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + + c = focustop(m); + sel_tags = c ? c->tags : 0; + + /* active */ + zpeachwm_ipc_output_v2_send_active(res, m == selmon ? 1 : 0); + + /* per-tag state */ + for (tag = 0; tag < TAGCOUNT; tag++) { + uint32_t mask = 1u << tag; + uint32_t state = ZPEACHWM_IPC_OUTPUT_V2_TAG_STATE_NONE; + uint32_t clients_on_tag = 0; + + if (m->tagset[m->seltags] & mask) + state = ZPEACHWM_IPC_OUTPUT_V2_TAG_STATE_ACTIVE; + if (urg & mask) + state = ZPEACHWM_IPC_OUTPUT_V2_TAG_STATE_URGENT; + if (occ & mask) + clients_on_tag = 1; /* report >=1, not exact count */ + + focused = (sel_tags & mask) ? 1 : 0; + zpeachwm_ipc_output_v2_send_tag(res, tag, state, clients_on_tag, focused); + } + + /* layout index */ + { + uint32_t i; + for (i = 0; i < (uint32_t)layout_count; i++) + if (&layouts[i] == curlayout(m)) + break; + zpeachwm_ipc_output_v2_send_layout(res, i); + } + + /* layout symbol */ + zpeachwm_ipc_output_v2_send_layout_symbol(res, m->ltsymbol[current_tag_idx(m)]); + + /* title/appid/fullscreen/floating */ + if (c) { + zpeachwm_ipc_output_v2_send_title(res, client_get_title(c)); + zpeachwm_ipc_output_v2_send_appid(res, client_get_appid(c)); + if (wl_resource_get_version(res) >= 2) { + zpeachwm_ipc_output_v2_send_fullscreen(res, c->isfullscreen ? 1 : 0); + zpeachwm_ipc_output_v2_send_floating(res, c->isfloating ? 1 : 0); + } + } else { + zpeachwm_ipc_output_v2_send_title(res, ""); + zpeachwm_ipc_output_v2_send_appid(res, ""); + if (wl_resource_get_version(res) >= 2) { + zpeachwm_ipc_output_v2_send_fullscreen(res, 0); + zpeachwm_ipc_output_v2_send_floating(res, 0); + } + } + + /* frame -- signals end of this batch of events */ + zpeachwm_ipc_output_v2_send_frame(res); +} + +/* Called instead of (or alongside) the old printstatus() */ +void +ipc_printstatus(void) +{ + IpcManager *mgr; + Monitor *m; + + wl_list_for_each(mgr, &ipc_managers, link) { + wl_list_for_each(m, &mons, link) { + IpcOutput *out = ipc_output_for_mon(mgr, m); + if (out) + ipc_send_output_state(out); + } + } +} + +/* zpeachwm_ipc_output_v2 requests */ + +static void +ipc_output_handle_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +ipc_output_handle_set_tags(struct wl_client *client, struct wl_resource *resource, + uint32_t tagmask, uint32_t toggle_tagset) +{ + IpcOutput *out = wl_resource_get_user_data(resource); + if (!out) return; + Monitor *m = out->mon; + if (!m) return; + + if (toggle_tagset) + m->seltags ^= 1; + m->tagset[m->seltags] = tagmask & TAGMASK; + focusclient(focustop(m), 1); + arrange(m); + printstatus(); +} + +static void +ipc_output_handle_set_client_tags(struct wl_client *client, + struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags) +{ + IpcOutput *out = wl_resource_get_user_data(resource); + if (!out) return; + Monitor *m = out->mon; + Client *sel; + uint32_t newtags; + if (!m) return; + + sel = focustop(m); + if (!sel) return; + + newtags = (sel->tags & and_tags) ^ xor_tags; + if (!newtags) return; + sel->tags = newtags; + focusclient(focustop(m), 1); + arrange(m); + printstatus(); +} + +static void +ipc_output_handle_set_layout(struct wl_client *client, + struct wl_resource *resource, uint32_t idx) +{ + IpcOutput *out = wl_resource_get_user_data(resource); + if (!out) return; + Monitor *m = out->mon; + if (!m || idx >= (uint32_t)layout_count) return; + + int ti = current_tag_idx(m); + if (m->lt[ti][m->sellt[ti]] != &layouts[idx]) + m->sellt[ti] ^= 1; + m->lt[ti][m->sellt[ti]] = &layouts[idx]; + strncpy(m->ltsymbol[ti], layouts[idx].symbol, LENGTH(m->ltsymbol[ti])); + arrange(m); + printstatus(); +} + +static const struct zpeachwm_ipc_output_v2_interface ipc_output_impl = { + .release = ipc_output_handle_release, + .set_tags = ipc_output_handle_set_tags, + .set_client_tags = ipc_output_handle_set_client_tags, + .set_layout = ipc_output_handle_set_layout, +}; + +static void +ipc_output_destroy(struct wl_resource *resource) +{ + IpcOutput *out = wl_resource_get_user_data(resource); + wl_list_remove(&out->link); + free(out); +} + +/* zpeachwm_ipc_manager_v2 requests */ + +static void +ipc_manager_handle_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +ipc_manager_handle_get_output(struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *output_resource) +{ + IpcManager *mgr = wl_resource_get_user_data(resource); + struct wlr_output *wlr_out = wlr_output_from_resource(output_resource); + Monitor *m = (wlr_out && wlr_out->data) ? wlr_out->data : NULL; + + IpcOutput *out = ecalloc(1, sizeof(*out)); + out->mon = m; + out->resource = wl_resource_create(client, &zpeachwm_ipc_output_v2_interface, + wl_resource_get_version(resource), id); + if (!out->resource) { + free(out); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(out->resource, &ipc_output_impl, + out, ipc_output_destroy); + wl_list_insert(&mgr->outputs, &out->link); + + if (m) + ipc_send_output_state(out); +} + +static const struct zpeachwm_ipc_manager_v2_interface ipc_manager_impl = { + .release = ipc_manager_handle_release, + .get_output = ipc_manager_handle_get_output, +}; + +static void +ipc_manager_destroy(struct wl_resource *resource) +{ + IpcManager *mgr = wl_resource_get_user_data(resource); + IpcOutput *out, *tmp; + wl_list_for_each_safe(out, tmp, &mgr->outputs, link) + wl_resource_destroy(out->resource); + wl_list_remove(&mgr->link); + free(mgr); +} + +/* global bind */ + +static void +ipc_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + IpcManager *mgr = ecalloc(1, sizeof(*mgr)); + wl_list_init(&mgr->outputs); + mgr->resource = wl_resource_create(client, &zpeachwm_ipc_manager_v2_interface, + version, id); + if (!mgr->resource) { + free(mgr); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(mgr->resource, &ipc_manager_impl, + mgr, ipc_manager_destroy); + wl_list_insert(&ipc_managers, &mgr->link); + + /* Announce tags and layouts */ + zpeachwm_ipc_manager_v2_send_tags(mgr->resource, TAGCOUNT); + for (uint32_t i = 0; i < (uint32_t)layout_count; i++) + zpeachwm_ipc_manager_v2_send_layout(mgr->resource, layouts[i].symbol); +} + +void +ipc_init(void) +{ + wl_list_init(&ipc_managers); + ipc_global = wl_global_create(dpy, &zpeachwm_ipc_manager_v2_interface, + 2, NULL, ipc_bind); +} diff --git a/src/ipc_socket.c b/src/ipc_socket.c new file mode 100644 index 0000000..f143362 --- /dev/null +++ b/src/ipc_socket.c @@ -0,0 +1,1237 @@ +/* + * See LICENSE file for copyright and license details. + * PeachWM IPC Socket - swaymsg-compatible Unix domain socket IPC + * + * Function implementations for the IPC socket. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "client.h" +#include "ipc.h" +#include "ipc_socket.h" +#include "util.h" + +/* ------------------------------------------------------------------ */ +/* Types from peachwm.c needed by command handlers */ +/* ------------------------------------------------------------------ */ + +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; + +/* ------------------------------------------------------------------ */ +/* Functions from peachwm.c called by IPC command handler */ +/* ------------------------------------------------------------------ */ + +void view(const Arg *arg); +void focusdir(const Arg *arg); +void tag(const Arg *arg); +void togglefloating(const Arg *arg); +void togglefullscreen(const Arg *arg); +void killclient(const Arg *arg); +void quit(const Arg *arg); +void do_reload(void); + +/* ------------------------------------------------------------------ */ +/* Globals */ +/* ------------------------------------------------------------------ */ + +static int ipc_listen_fd = -1; +static struct wl_event_source *ipc_listen_src; +static struct wl_list ipc_clients; +static struct wl_event_loop *ipc_event_loop; +static char ipc_socket_path[256]; + +/* ------------------------------------------------------------------ */ +/* Simple JSON builder */ +/* ------------------------------------------------------------------ */ + +struct json_writer { + char *buf; + size_t len; + size_t cap; + int depth; + int need_comma[32]; +}; + +static void +json_init(struct json_writer *w) +{ + w->cap = 4096; + w->buf = ecalloc(1, w->cap); + w->len = 0; + w->depth = -1; + memset(w->need_comma, 0, sizeof(w->need_comma)); +} + +static void +json_finish(struct json_writer *w) +{ + free(w->buf); +} + +static void +json_grow(struct json_writer *w, size_t needed) +{ + if (w->len + needed <= w->cap) + return; + while (w->cap < w->len + needed) + w->cap *= 2; + char *newbuf = realloc(w->buf, w->cap); + if (!newbuf) { + fprintf(stderr, "peachwm: json_grow realloc failed\n"); + return; + } + w->buf = newbuf; +} + +static void +json_raw(struct json_writer *w, const char *s, size_t n) +{ + json_grow(w, n); + memcpy(w->buf + w->len, s, n); + w->len += n; +} + +static void +json_puts(struct json_writer *w, const char *s) +{ + json_raw(w, s, strlen(s)); +} + +__attribute__((__format__(__printf__, 2, 3))) +static void +json_printf(struct json_writer *w, const char *fmt, ...) +{ + va_list ap; + int n; + json_grow(w, 128); + va_start(ap, fmt); + n = vsnprintf(w->buf + w->len, w->cap - w->len, fmt, ap); + va_end(ap); + if ((size_t)n >= w->cap - w->len) { + json_grow(w, (size_t)n + 1); + va_start(ap, fmt); + n = vsnprintf(w->buf + w->len, w->cap - w->len, fmt, ap); + va_end(ap); + } + w->len += (size_t)n; +} + +static void +json_comma(struct json_writer *w) +{ + if (w->need_comma[w->depth + 1]) + json_puts(w, ","); + w->need_comma[w->depth + 1] = 1; +} + +static void +json_object_start(struct json_writer *w) +{ + if (w->depth + 1 >= 32) + return; + json_comma(w); + json_puts(w, "{"); + ++w->depth; + w->need_comma[w->depth + 1] = 0; +} + +static void +json_object_end(struct json_writer *w) +{ + json_puts(w, "}"); + --w->depth; +} + +static void +json_array_start(struct json_writer *w) +{ + if (w->depth + 1 >= 32) + return; + json_comma(w); + json_puts(w, "["); + ++w->depth; + w->need_comma[w->depth + 1] = 0; +} + +static void +json_array_end(struct json_writer *w) +{ + json_puts(w, "]"); + --w->depth; +} + +static void +json_key(struct json_writer *w, const char *key) +{ + json_comma(w); + json_printf(w, "\"%s\":", key); +} + +static void +json_string(struct json_writer *w, const char *s) +{ + json_comma(w); + json_puts(w, "\""); + for (; *s; s++) { + switch (*s) { + case '"': json_puts(w, "\\\""); break; + case '\\': json_puts(w, "\\\\"); break; + case '\n': json_puts(w, "\\n"); break; + case '\r': json_puts(w, "\\r"); break; + case '\t': json_puts(w, "\\t"); break; + default: + if ((unsigned char)*s < 0x20) + json_printf(w, "\\u%04x", + (unsigned char)*s); + else + json_raw(w, s, 1); + break; + } + } + json_puts(w, "\""); +} + +static void +json_integer(struct json_writer *w, int64_t val) +{ + json_comma(w); + json_printf(w, "%" PRId64, val); +} + +static void +json_bool(struct json_writer *w, int val) +{ + json_comma(w); + json_puts(w, val ? "true" : "false"); +} + +static void +json_float(struct json_writer *w, double val) +{ + json_comma(w); + json_printf(w, "%.2f", val); +} + +#define json_key_string(w, k, v) do { \ + json_key(w, k); json_string(w, v); } while(0) +#define json_key_int(w, k, v) do { \ + json_key(w, k); json_integer(w, v); } while(0) +#define json_key_bool(w, k, v) do { \ + json_key(w, k); json_bool(w, v); } while(0) +#define json_key_float(w, k, v) do { \ + json_key(w, k); json_float(w, v); } while(0) + +/* ------------------------------------------------------------------ */ +/* Helper: tag index (0-based) -> workspace name */ +/* ------------------------------------------------------------------ */ + +static const char * +ipc_tag_name(int idx) +{ + static const char *names[] = { + "1", "2", "3", "4", "5", "6", "7", "8", "9", + }; + if (idx >= 0 && idx < (int)LENGTH(names)) + return names[idx]; + return "?"; +} + +/* ------------------------------------------------------------------ */ +/* Send a raw message (header + payload) to a client fd */ +/* ------------------------------------------------------------------ */ + +static int +ipc_send_raw(int fd, uint32_t type, const char *payload, size_t len) +{ + uint8_t hdr[IPC_HEADER_SIZE]; + uint32_t nlen = (uint32_t)len; + + memcpy(hdr, IPC_MAGIC, IPC_MAGIC_LEN); + memcpy(hdr + IPC_MAGIC_LEN, &nlen, sizeof(nlen)); + memcpy(hdr + IPC_MAGIC_LEN + 4, &type, sizeof(type)); + + struct iovec iov[2]; + iov[0].iov_base = hdr; + iov[0].iov_len = IPC_HEADER_SIZE; + iov[1].iov_base = (void *)payload; + iov[1].iov_len = len; + + struct msghdr msg = {0}; + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + size_t total = IPC_HEADER_SIZE + len; + size_t sent = 0; + + while (sent < total) { + ssize_t ret = sendmsg(fd, &msg, MSG_NOSIGNAL); + if (ret < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) + return 0; + return -1; + } + sent += (size_t)ret; + + /* Advance iovecs past what was sent */ + size_t remain = sent; + while (remain > 0 && msg.msg_iovlen > 0) { + if (remain < msg.msg_iov[0].iov_len) { + msg.msg_iov[0].iov_base = + (void *)((uintptr_t)msg.msg_iov[0].iov_base + + remain); + msg.msg_iov[0].iov_len -= remain; + remain = 0; + } else { + remain -= msg.msg_iov[0].iov_len; + msg.msg_iov++; + msg.msg_iovlen--; + } + } + } + + return 0; +} + +static int +ipc_send_json(int fd, uint32_t type, struct json_writer *w) +{ + return ipc_send_raw(fd, type, w->buf, w->len); +} + +/* ------------------------------------------------------------------ */ +/* Broadcast an event to all subscribed clients */ +/* ------------------------------------------------------------------ */ + +static void +ipc_broadcast(uint32_t event_type, struct json_writer *w) +{ + struct ipc_client *c; + uint32_t flag; + + switch (event_type) { + case IPC_EVENT_WORKSPACE: flag = IPC_SUB_WORKSPACE; break; + case IPC_EVENT_OUTPUT: flag = IPC_SUB_OUTPUT; break; + case IPC_EVENT_WINDOW: flag = IPC_SUB_WINDOW; break; + case IPC_EVENT_MODE: flag = IPC_SUB_MODE; break; + default: flag = ~0u; break; + } + + wl_list_for_each(c, &ipc_clients, link) { + if (c->subscribed & flag) + ipc_send_json(c->fd, event_type, w); + } +} + +/* ------------------------------------------------------------------ */ +/* JSON response builders */ +/* ------------------------------------------------------------------ */ + +static void +ipc_build_workspace(struct json_writer *w, Monitor *m, uint32_t tag_bit, + int tag_idx) +{ + Client *c; + int is_urgent = 0; + int visible = !!(m->tagset[m->seltags] & tag_bit); + int num = 0; + + /* compute tag number (1-9) */ + { + uint32_t t = tag_bit; + while (t) { num++; t >>= 1; } + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (c->tags & tag_bit) { + if (c->isurgent) + is_urgent = 1; + } + } + + json_object_start(w); + json_key_int(w, "id", (int64_t)tag_bit + + ((int64_t)(uintptr_t)m << 32)); + json_key_int(w, "num", num); + json_key_string(w, "name", ipc_tag_name(tag_idx)); + json_key_bool(w, "visible", visible); + json_key_bool(w, "focused", visible && m == selmon); + json_key_bool(w, "urgent", !!is_urgent); + + json_key(w, "rect"); + json_object_start(w); + json_key_int(w, "x", m->m.x); + json_key_int(w, "y", m->m.y); + json_key_int(w, "width", m->m.width); + json_key_int(w, "height", m->m.height); + json_object_end(w); + + json_key_string(w, "output", m->wlr_output->name); + json_key_string(w, "layout", m->ltsymbol[tag_idx]); + json_key_string(w, "representation", ""); + json_key_string(w, "type", "workspace"); + + json_key(w, "focus"); + json_array_start(w); + c = focustop(m); + if (c && (c->tags & tag_bit)) + json_integer(w, (int64_t)(uintptr_t)c); + json_array_end(w); + + json_key(w, "floating_nodes"); + json_array_start(w); + wl_list_for_each(c, &clients, link) { + if (c->mon == m && (c->tags & tag_bit) && c->isfloating && + !c->isfullscreen) + json_integer(w, (int64_t)(uintptr_t)c); + } + json_array_end(w); + + json_key(w, "nodes"); + json_array_start(w); + wl_list_for_each(c, &clients, link) { + if (c->mon == m && (c->tags & tag_bit) && !c->isfloating && + !c->isfullscreen) + json_integer(w, (int64_t)(uintptr_t)c); + } + json_array_end(w); + json_object_end(w); +} + +static void +ipc_build_output(struct json_writer *w, Monitor *m) +{ + struct wlr_output *o = m->wlr_output; + struct wlr_output_mode *mode = NULL; + int ti; + + if (o->current_mode) + mode = o->current_mode; + else + mode = wlr_output_preferred_mode(o); + + /* Determine current workspace name = first visible tag name */ + const char *ws_name = ""; + for (ti = 0; ti < TAGCOUNT; ti++) { + if (m->tagset[m->seltags] & (1u << ti)) { + ws_name = ipc_tag_name(ti); + break; + } + } + + json_object_start(w); + json_key_string(w, "name", o->name ? o->name : ""); + json_key_string(w, "make", o->make ? o->make : ""); + json_key_string(w, "model", o->model ? o->model : ""); + json_key_string(w, "serial", o->serial ? o->serial : ""); + json_key_bool(w, "active", o->enabled); + json_key_bool(w, "primary", + m == wl_container_of(mons.next, m, link)); + json_key_float(w, "scale", o->scale); + json_key_string(w, "subpixel_hinting", "rgb"); + json_key_string(w, "transform", + o->transform == WL_OUTPUT_TRANSFORM_NORMAL ? + "normal" : "90"); + json_key_string(w, "current_workspace", ws_name); + + json_key(w, "modes"); + json_array_start(w); + if (mode) { + json_object_start(w); + json_key_int(w, "width", mode->width); + json_key_int(w, "height", mode->height); + json_key_int(w, "refresh", mode->refresh); + json_object_end(w); + } + json_array_end(w); + + if (mode) { + json_key(w, "current_mode"); + json_object_start(w); + json_key_int(w, "width", mode->width); + json_key_int(w, "height", mode->height); + json_key_int(w, "refresh", mode->refresh); + json_object_end(w); + } + + json_key(w, "rect"); + json_object_start(w); + json_key_int(w, "x", m->m.x); + json_key_int(w, "y", m->m.y); + json_key_int(w, "width", m->m.width); + json_key_int(w, "height", m->m.height); + json_object_end(w); + json_object_end(w); +} + +static void +ipc_build_client(struct json_writer *w, Client *c) +{ + int floating = c->isfloating || + !curlayout(c->mon)->arrange; + + json_object_start(w); + json_key_int(w, "id", (int64_t)(uintptr_t)c); + json_key_string(w, "type", "con"); + json_key_string(w, "orientation", "none"); + json_key_int(w, "percent", -1); + json_key_bool(w, "urgent", !!c->isurgent); + json_key_bool(w, "focused", c == focustop(selmon)); + json_key_string(w, "layout", "none"); + json_key_string(w, "app_id", client_get_appid(c)); + json_key_string(w, "name", client_get_title(c)); + json_key_string(w, "title", client_get_title(c)); + json_key_bool(w, "fullscreen_mode", !!c->isfullscreen); + json_key_bool(w, "floating", !!floating); + + json_key(w, "window_properties"); + json_object_start(w); + json_key_string(w, "class", client_get_appid(c)); + json_key_string(w, "instance", client_get_appid(c)); + json_key_string(w, "title", client_get_title(c)); + json_key_bool(w, "transient", 0); + json_object_end(w); + + json_key(w, "marks"); + json_array_start(w); + json_array_end(w); + + json_key(w, "rect"); + json_object_start(w); + json_key_int(w, "x", c->geom.x); + json_key_int(w, "y", c->geom.y); + json_key_int(w, "width", c->geom.width); + json_key_int(w, "height", c->geom.height); + json_object_end(w); + + json_key(w, "window_rect"); + json_object_start(w); + json_key_int(w, "x", (int)c->bw); + json_key_int(w, "y", (int)c->bw); + json_key_int(w, "width", + c->geom.width - 2 * (int)c->bw); + json_key_int(w, "height", + c->geom.height - 2 * (int)c->bw); + json_object_end(w); + + json_key(w, "deco_rect"); + json_object_start(w); + json_key_int(w, "x", 0); + json_key_int(w, "y", 0); + json_key_int(w, "width", 0); + json_key_int(w, "height", 0); + json_object_end(w); + + json_key(w, "geometry"); + json_object_start(w); + json_key_int(w, "x", c->geom.x); + json_key_int(w, "y", c->geom.y); + json_key_int(w, "width", c->geom.width); + json_key_int(w, "height", c->geom.height); + json_object_end(w); + + json_key(w, "border"); + json_object_start(w); + json_key_int(w, "top", (int)c->bw); + json_key_int(w, "bottom", (int)c->bw); + json_key_int(w, "right", (int)c->bw); + json_key_int(w, "left", (int)c->bw); + json_object_end(w); + + json_key_int(w, "current_border_width", (int64_t)c->bw); + + json_key_int(w, "workspace", current_tag_idx(c->mon) + 1); + + json_key(w, "nodes"); + json_array_start(w); + json_array_end(w); + + json_key(w, "floating_nodes"); + json_array_start(w); + json_array_end(w); + json_object_end(w); +} + +/* ------------------------------------------------------------------ */ +/* Command handlers */ +/* ------------------------------------------------------------------ */ + +static void +ipc_handle_get_workspaces(struct ipc_client *client) +{ + struct json_writer w; + Monitor *m; + int i; + + json_init(&w); + json_array_start(&w); + wl_list_for_each(m, &mons, link) { + for (i = 0; i < TAGCOUNT; i++) { + uint32_t tag_bit = 1u << i; + if (m->tagset[m->seltags] & tag_bit) + ipc_build_workspace(&w, m, tag_bit, i); + } + } + json_array_end(&w); + ipc_send_json(client->fd, IPC_GET_WORKSPACES, &w); + json_finish(&w); +} + +static void +ipc_handle_get_outputs(struct ipc_client *client) +{ + struct json_writer w; + Monitor *m; + + json_init(&w); + json_array_start(&w); + wl_list_for_each(m, &mons, link) { + ipc_build_output(&w, m); + } + json_array_end(&w); + ipc_send_json(client->fd, IPC_GET_OUTPUTS, &w); + json_finish(&w); +} + +static void +ipc_handle_get_version(struct ipc_client *client) +{ + struct json_writer w; + + json_init(&w); + json_object_start(&w); + json_key_string(&w, "variant", "peachwm"); + json_key_string(&w, "human_readable", + "peachwm " VERSION); + json_key_int(&w, "major", 0); + json_key_int(&w, "minor", 2); + json_key_int(&w, "patch", 0); + json_key_string(&w, "loaded_config_file_name", ""); + json_object_end(&w); + ipc_send_json(client->fd, IPC_GET_VERSION, &w); + json_finish(&w); +} + +static void +ipc_handle_get_tree(struct ipc_client *client) +{ + struct json_writer w; + Monitor *m; + Client *c; + int i; + + json_init(&w); + + /* root node */ + json_object_start(&w); + json_key_string(&w, "type", "root"); + json_key_string(&w, "name", "root"); + + json_key(&w, "nodes"); + json_array_start(&w); + + wl_list_for_each(m, &mons, link) { + /* output node */ + json_object_start(&w); + json_key_string(&w, "type", "output"); + json_key_string(&w, "name", m->wlr_output->name); + json_key_bool(&w, "active", m->wlr_output->enabled); + + json_key(&w, "rect"); + json_object_start(&w); + json_key_int(&w, "x", m->m.x); + json_key_int(&w, "y", m->m.y); + json_key_int(&w, "width", m->m.width); + json_key_int(&w, "height", m->m.height); + json_object_end(&w); + + json_key(&w, "nodes"); + json_array_start(&w); + + for (i = 0; i < TAGCOUNT; i++) { + uint32_t tag_bit = 1u << i; + if (!(m->tagset[m->seltags] & tag_bit)) + continue; + + /* workspace node */ + json_object_start(&w); + json_key_string(&w, "type", "workspace"); + json_key_string(&w, "name", + ipc_tag_name(i)); + json_key_string(&w, "layout", m->ltsymbol[i]); + + json_key(&w, "rect"); + json_object_start(&w); + json_key_int(&w, "x", m->m.x); + json_key_int(&w, "y", m->m.y); + json_key_int(&w, "width", m->m.width); + json_key_int(&w, "height", m->m.height); + json_object_end(&w); + + /* tiled nodes */ + json_key(&w, "nodes"); + json_array_start(&w); + wl_list_for_each(c, &clients, link) { + if (c->mon == m && + (c->tags & tag_bit) && + !c->isfloating && + !c->isfullscreen) + ipc_build_client(&w, c); + } + json_array_end(&w); + + /* floating nodes */ + json_key(&w, "floating_nodes"); + json_array_start(&w); + wl_list_for_each(c, &clients, link) { + if (c->mon == m && + (c->tags & tag_bit) && + (c->isfloating || + c->isfullscreen)) + ipc_build_client(&w, c); + } + json_array_end(&w); + + json_object_end(&w); /* workspace */ + } + json_array_end(&w); /* output nodes */ + + json_key(&w, "floating_nodes"); + json_array_start(&w); + json_array_end(&w); + + json_object_end(&w); /* output */ + } + json_array_end(&w); /* root nodes */ + + json_key(&w, "floating_nodes"); + json_array_start(&w); + json_array_end(&w); + + json_object_end(&w); /* root */ + + ipc_send_json(client->fd, IPC_GET_TREE, &w); + json_finish(&w); +} + +static void +ipc_handle_command(struct ipc_client *client, const char *payload, + size_t len) +{ + struct json_writer w; + int success = 0; + char *cmd = NULL; + + if (len > 0) { + cmd = ecalloc(1, len + 1); + memcpy(cmd, payload, len); + cmd[len] = '\0'; + } + + if (cmd) { + if (!strncmp(cmd, "focus workspace", 15)) { + const char *ws = cmd + 15; + while (*ws == ' ') ws++; + if (*ws) { + int num = atoi(ws); + if (num >= 1 && num <= TAGCOUNT) { + view(&(Arg){.ui = + 1u << (num - 1)}); + success = 1; + } + } + } else if (!strcmp(cmd, "focus left")) { + focusdir(&(Arg){.i = WLR_DIRECTION_LEFT}); + success = 1; + } else if (!strcmp(cmd, "focus right")) { + focusdir(&(Arg){.i = WLR_DIRECTION_RIGHT}); + success = 1; + } else if (!strcmp(cmd, "focus up")) { + focusdir(&(Arg){.i = WLR_DIRECTION_UP}); + success = 1; + } else if (!strcmp(cmd, "focus down")) { + focusdir(&(Arg){.i = WLR_DIRECTION_DOWN}); + success = 1; + } else if (!strncmp(cmd, "move workspace", 14)) { + const char *ws = cmd + 14; + while (*ws == ' ') ws++; + if (*ws) { + int num = atoi(ws); + if (num >= 1 && num <= TAGCOUNT) { + tag(&(Arg){.ui = + 1u << (num - 1)}); + success = 1; + } + } + } else if (!strcmp(cmd, "floating toggle") || + !strcmp(cmd, "floating enable") || + !strcmp(cmd, "floating disable")) { + togglefloating(NULL); + success = 1; + } else if (!strcmp(cmd, "fullscreen toggle") || + !strcmp(cmd, "fullscreen enable") || + !strcmp(cmd, "fullscreen disable")) { + togglefullscreen(NULL); + success = 1; + } else if (!strcmp(cmd, "kill") || + !strcmp(cmd, "close")) { + killclient(NULL); + success = 1; + } else if (!strncmp(cmd, "workspace", 9)) { + const char *ws = cmd + 9; + while (*ws == ' ') ws++; + if (*ws) { + int num = atoi(ws); + if (num >= 1 && num <= TAGCOUNT) { + view(&(Arg){.ui = + 1u << (num - 1)}); + success = 1; + } + } + } else if (!strcmp(cmd, "exit") || + !strcmp(cmd, "quit")) { + quit(NULL); + success = 1; + } else if (!strcmp(cmd, "reload")) { + do_reload(); + success = 1; + } + } + + free(cmd); + + json_init(&w); + json_array_start(&w); + json_object_start(&w); + json_key_bool(&w, "success", success); + if (!success) + json_key_string(&w, "error", + "Unknown or unsupported command"); + json_object_end(&w); + json_array_end(&w); + ipc_send_json(client->fd, IPC_COMMAND, &w); + json_finish(&w); +} + +static void +ipc_handle_subscribe(struct ipc_client *client, const char *payload, + size_t len) +{ + struct json_writer w; + (void)len; + + client->subscribed = 0; + + if (strstr(payload, "\"workspace\"")) + client->subscribed |= IPC_SUB_WORKSPACE; + if (strstr(payload, "\"window\"")) + client->subscribed |= IPC_SUB_WINDOW; + if (strstr(payload, "\"output\"")) + client->subscribed |= IPC_SUB_OUTPUT; + if (strstr(payload, "\"mode\"")) + client->subscribed |= IPC_SUB_MODE; + + json_init(&w); + json_array_start(&w); + json_object_start(&w); + json_key_bool(&w, "success", 1); + json_object_end(&w); + json_array_end(&w); + ipc_send_json(client->fd, IPC_SUBSCRIBE, &w); + json_finish(&w); +} + +/* ------------------------------------------------------------------ */ +/* Client I/O */ +/* ------------------------------------------------------------------ */ + +static void +ipc_client_close(struct ipc_client *c) +{ + wl_list_remove(&c->link); + if (c->src) { + wl_event_source_remove(c->src); + c->src = NULL; + } + close(c->fd); + free(c->payload); + free(c); +} + +static int +ipc_client_handle_readable(int fd, uint32_t mask, void *data) +{ + struct ipc_client *c = data; + ssize_t n; + size_t want; + (void)fd; + + if (mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP)) { + ipc_client_close(c); + return 0; + } + + if (!(mask & WL_EVENT_READABLE)) + return 0; + + if (!c->header_done) { + want = IPC_HEADER_SIZE - c->hdr_len; + n = read(c->fd, c->hdr_buf + c->hdr_len, want); + if (n <= 0) { + if (n == 0 || + (errno != EAGAIN && errno != EWOULDBLOCK)) { + ipc_client_close(c); + return 0; + } + return 0; + } + c->hdr_len += (size_t)n; + if (c->hdr_len < IPC_HEADER_SIZE) + return 0; + + /* Validate magic */ + if (memcmp(c->hdr_buf, IPC_MAGIC, IPC_MAGIC_LEN) != 0) { + ipc_client_close(c); + return 0; + } + + /* Parse header */ + memcpy(&c->payload_size, + c->hdr_buf + IPC_MAGIC_LEN, sizeof(uint32_t)); + memcpy(&c->msg_type, + c->hdr_buf + IPC_MAGIC_LEN + 4, sizeof(uint32_t)); + + if (c->payload_size > 0) { + if (c->payload_size > c->payload_alloc) { + free(c->payload); + c->payload_alloc = c->payload_size + 1; + c->payload = ecalloc(1, c->payload_alloc); + } + c->payload_len = 0; + } + c->header_done = 1; + } + + /* Read payload */ + if (c->header_done && c->payload_size > 0) { + want = c->payload_size - c->payload_len; + if (want > 0) { + n = read(c->fd, + c->payload + c->payload_len, want); + if (n <= 0) { + if (n == 0 || + (errno != EAGAIN && + errno != EWOULDBLOCK)) { + ipc_client_close(c); + return 0; + } + return 0; + } + c->payload_len += (size_t)n; + } + if (c->payload_len < c->payload_size) + return 0; + } + + /* Full message received – dispatch */ + switch (c->msg_type) { + case IPC_COMMAND: + ipc_handle_command(c, c->payload, c->payload_size); + break; + case IPC_GET_WORKSPACES: + ipc_handle_get_workspaces(c); + break; + case IPC_GET_OUTPUTS: + ipc_handle_get_outputs(c); + break; + case IPC_GET_VERSION: + ipc_handle_get_version(c); + break; + case IPC_GET_TREE: + ipc_handle_get_tree(c); + break; + case IPC_SUBSCRIBE: + ipc_handle_subscribe(c, c->payload, c->payload_size); + break; + default: + ipc_send_raw(c->fd, c->msg_type, "{}", 2); + break; + } + + /* Reset for next message */ + c->hdr_len = 0; + c->header_done = 0; + c->payload_len = 0; + c->payload_size = 0; + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Accept new connections */ +/* ------------------------------------------------------------------ */ + +static int +ipc_handle_accept(int fd, uint32_t mask, void *data) +{ + struct sockaddr_un un; + socklen_t len = sizeof(un); + int cfd; + struct ipc_client *c; + struct wl_event_source *src; + int flags; + + (void)data; + + if (!(mask & WL_EVENT_READABLE)) + return 0; + + cfd = accept(fd, (struct sockaddr *)&un, &len); + if (cfd < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) + wlr_log(WLR_ERROR, "ipc: accept: %s", + strerror(errno)); + return 0; + } + + /* Set non-blocking and close-on-exec */ + flags = fcntl(cfd, F_GETFL); + if (flags < 0 || fcntl(cfd, F_SETFL, flags | O_NONBLOCK) < 0) { + close(cfd); + return 0; + } + + c = ecalloc(1, sizeof(*c)); + c->fd = cfd; + c->hdr_len = 0; + c->header_done = 0; + c->payload_size = 0; + c->payload_len = 0; + c->payload_alloc = 0; + c->payload = NULL; + c->subscribed = 0; + + src = wl_event_loop_add_fd(ipc_event_loop, cfd, + WL_EVENT_READABLE, + ipc_client_handle_readable, c); + if (!src) { + close(cfd); + free(c); + return 0; + } + c->src = src; + + wl_list_insert(&ipc_clients, &c->link); + + wlr_log(WLR_DEBUG, "ipc: client connected (fd %d)", cfd); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +void +ipc_socket_init(void) +{ + const char *runtime_dir; + struct sockaddr_un un; + int fd, flags; + char sock_path[256]; + struct passwd *pw; + + wl_list_init(&ipc_clients); + ipc_event_loop = wl_display_get_event_loop(dpy); + + runtime_dir = getenv("XDG_RUNTIME_DIR"); + if (runtime_dir && runtime_dir[0]) { + snprintf(sock_path, sizeof(sock_path), + "%s/peachwm.sock", runtime_dir); + } else { + pw = getpwuid(getuid()); + if (pw) + snprintf(sock_path, sizeof(sock_path), + "/tmp/peachwm-%s.sock", pw->pw_name); + else + snprintf(sock_path, sizeof(sock_path), + "/tmp/peachwm-%d.sock", getuid()); + } + + unlink(sock_path); + + fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + wlr_log(WLR_ERROR, "ipc: socket: %s", strerror(errno)); + return; + } + + memset(&un, 0, sizeof(un)); + un.sun_family = AF_UNIX; + strncpy(un.sun_path, sock_path, sizeof(un.sun_path) - 1); + un.sun_path[sizeof(un.sun_path) - 1] = '\0'; + + if (bind(fd, (struct sockaddr *)&un, sizeof(un)) < 0) { + wlr_log(WLR_ERROR, "ipc: bind(%s): %s", + sock_path, strerror(errno)); + close(fd); + return; + } + + chmod(sock_path, 0700); + + if (listen(fd, 8) < 0) { + wlr_log(WLR_ERROR, "ipc: listen: %s", strerror(errno)); + close(fd); + unlink(sock_path); + return; + } + + flags = fcntl(fd, F_GETFL); + if (flags < 0 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + wlr_log(WLR_ERROR, "ipc: fcntl(O_NONBLOCK): %s", + strerror(errno)); + close(fd); + unlink(sock_path); + return; + } + + ipc_listen_fd = fd; + strncpy(ipc_socket_path, sock_path, + sizeof(ipc_socket_path) - 1); + ipc_socket_path[sizeof(ipc_socket_path) - 1] = '\0'; + + ipc_listen_src = wl_event_loop_add_fd( + ipc_event_loop, fd, WL_EVENT_READABLE, + ipc_handle_accept, NULL); + if (!ipc_listen_src) { + wlr_log(WLR_ERROR, + "ipc: wl_event_loop_add_fd failed"); + close(fd); + unlink(sock_path); + ipc_listen_fd = -1; + ipc_socket_path[0] = '\0'; + return; + } + + wlr_log(WLR_DEBUG, "ipc: listening on %s", sock_path); +} + +void +ipc_socket_finish(void) +{ + struct ipc_client *c, *tmp; + + wl_list_for_each_safe(c, tmp, &ipc_clients, link) + ipc_client_close(c); + + if (ipc_listen_src) { + wl_event_source_remove(ipc_listen_src); + ipc_listen_src = NULL; + } + if (ipc_listen_fd >= 0) { + close(ipc_listen_fd); + if (ipc_socket_path[0]) + unlink(ipc_socket_path); + ipc_listen_fd = -1; + ipc_socket_path[0] = '\0'; + } +} + +/* ------------------------------------------------------------------ */ +/* Event helpers called from peachwm.c */ +/* ------------------------------------------------------------------ */ + +void +ipc_socket_send_workspace_event(const char *change) +{ + struct json_writer w; + + if (wl_list_empty(&ipc_clients)) + return; + + json_init(&w); + json_object_start(&w); + json_key_string(&w, "change", change ? change : "focus"); + + if (selmon) { + int ti = current_tag_idx(selmon); + uint32_t tag_bit = 1u << ti; + if (selmon->tagset[selmon->seltags] & tag_bit) { + json_key(&w, "current"); + ipc_build_workspace(&w, selmon, tag_bit, ti); + } + } + + json_object_end(&w); + ipc_broadcast((uint32_t)IPC_EVENT_WORKSPACE, &w); + json_finish(&w); +} + +void +ipc_socket_send_window_event(const char *change) +{ + struct json_writer w; + + if (wl_list_empty(&ipc_clients)) + return; + + json_init(&w); + json_object_start(&w); + json_key_string(&w, "change", change ? change : "focus"); + + if (selmon) { + Client *c = focustop(selmon); + if (c) { + json_key(&w, "container"); + ipc_build_client(&w, c); + } + } + + json_object_end(&w); + ipc_broadcast((uint32_t)IPC_EVENT_WINDOW, &w); + json_finish(&w); +} + +void +ipc_socket_send_output_event(void) +{ + struct json_writer w; + + if (wl_list_empty(&ipc_clients)) + return; + + json_init(&w); + json_object_start(&w); + json_key_string(&w, "change", "unspecified"); + json_object_end(&w); + ipc_broadcast((uint32_t)IPC_EVENT_OUTPUT, &w); + json_finish(&w); +} diff --git a/src/layout.c b/src/layout.c new file mode 100644 index 0000000..72410bf --- /dev/null +++ b/src/layout.c @@ -0,0 +1,519 @@ +/* + * Layout algorithms extracted from peachwm.c + * Dwindle, Master/Stack, Monocle, and associated utilities. + */ +#include +#include + +#include "util.h" +#include "layout.h" +#include "parser/parser.h" + +/* macros (duplicated from peachwm.c) */ +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TAGMASK ((1u << TAGCOUNT) - 1) + +static inline int visibleon(Client *c, Monitor *m) { + return m && c->mon == m + ? (c->isscratchpad ? m->scratchpad_visible + : (int)(c->tags & m->tagset[m->seltags])) + : 0; +} +#define VISIBLEON(C, M) visibleon((C), (M)) + +/* globals from peachwm.c */ +extern struct wl_list clients; +extern struct wl_list fstack; +extern struct wl_list mons; +extern Monitor *selmon; +extern Config cfg; + +/* functions from peachwm.c */ +void resize(Client *c, struct wlr_box geo, int interact); +Client *focustop(Monitor *m); +void focusclient(Client *c, int lift); +void printstatus(void); +void client_set_suspended(Client *c, int suspended); + +/* ================================================================ + * layout table + * ================================================================ */ + +const Layout layouts[] = { + {"><>", NULL}, + {"[T]", dwindle}, + {"[M]", master}, + {"[]", monocle}, +}; +const unsigned int layout_count = LENGTH(layouts); + +/* ================================================================ + * helpers + * ================================================================ */ + +/* + * Returns the 0-based index of the lowest set tag bit for monitor m. + * For single-tag views this is the exact tag. For multi-tag views it + * picks the lowest bit. + */ +int +current_tag_idx(Monitor *m) +{ + uint32_t tags = m->tagset[m->seltags] & TAGMASK; + if (!tags) + return 0; + int idx = 0; + while (!(tags & 1u)) { + tags >>= 1; + idx++; + } + return idx < TAGCOUNT ? idx : 0; +} + +/* Helper: get current layout for the active tag on monitor m */ +const Layout * +curlayout(Monitor *m) +{ + int ti = current_tag_idx(m); + return m->lt[ti][m->sellt[ti]]; +} + +/* ================================================================ + * dwindle tree helpers + * ================================================================ */ + +static DwindleNode * +dwindle_new_node(void) +{ + DwindleNode *n = ecalloc(1, sizeof(*n)); + n->split_ratio = 1.0f; + return n; +} + +void +dwindle_free_tree(DwindleNode *n) +{ + if (!n) + return; + dwindle_free_tree(n->children[0]); + dwindle_free_tree(n->children[1]); + free(n); +} + +DwindleNode * +dwindle_find_leaf(DwindleNode *n, Client *c) +{ + if (!n) + return NULL; + if (!n->is_node) + return n->client == c ? n : NULL; + DwindleNode *r = dwindle_find_leaf(n->children[0], c); + return r ? r : dwindle_find_leaf(n->children[1], c); +} + +static DwindleNode * +dwindle_first_leaf(DwindleNode *n) +{ + if (!n) + return NULL; + while (n->is_node) + n = n->children[0]; + return n; +} + +/* + * Recursively distribute geometry downward from node n. + * gap is inner gap pixels between the two halves. + */ +void +dwindle_recalc(DwindleNode *n, int gap) +{ + if (!n) + return; + + if (!n->is_node) { + if (n->client && !n->client->isfullscreen) + resize(n->client, n->box, 0); + return; + } + + /* Wider than tall -> split left/right; taller -> split top/bottom. */ + n->split_top = (n->box.height > n->box.width); + + if (!n->split_top) { + int w1 = MAX(1, (int)(n->box.width / 2.0f * n->split_ratio) - gap / 2); + n->children[0]->box = + (struct wlr_box){n->box.x, n->box.y, w1, n->box.height}; + n->children[1]->box = + (struct wlr_box){n->box.x + w1 + gap, n->box.y, + MAX(1, n->box.width - w1 - gap), n->box.height}; + } else { + int h1 = MAX(1, (int)(n->box.height / 2.0f * n->split_ratio) - gap / 2); + n->children[0]->box = + (struct wlr_box){n->box.x, n->box.y, n->box.width, h1}; + n->children[1]->box = + (struct wlr_box){n->box.x, n->box.y + h1 + gap, n->box.width, + MAX(1, n->box.height - h1 - gap)}; + } + + dwindle_recalc(n->children[0], gap); + dwindle_recalc(n->children[1], gap); +} + +/* + * Insert new_c into the tree, bisecting the focused client's node. + * It falls back to the first leaf if focused is NULL or not in the tree. + */ +static void +dwindle_insert(DwindleNode **root, Client *new_c, Client *focused) +{ + DwindleNode *new_leaf = dwindle_new_node(); + new_leaf->client = new_c; + new_leaf->is_node = 0; + + if (!*root) { + *root = new_leaf; + return; + } + + DwindleNode *opening_on = focused ? dwindle_find_leaf(*root, focused) : NULL; + if (!opening_on) + opening_on = dwindle_first_leaf(*root); + + DwindleNode *new_parent = dwindle_new_node(); + new_parent->is_node = 1; + new_parent->box = opening_on->box; + new_parent->parent = opening_on->parent; + new_parent->split_top = (opening_on->box.height > opening_on->box.width); + new_parent->children[0] = opening_on; + new_parent->children[1] = new_leaf; + + opening_on->parent = new_parent; + new_leaf->parent = new_parent; + + if (new_parent->parent) { + if (new_parent->parent->children[0] == opening_on) + new_parent->parent->children[0] = new_parent; + else + new_parent->parent->children[1] = new_parent; + } else { + *root = new_parent; + } +} + +/* + * Remove client c from the tree, promoting its sibling upward. + */ +static void +dwindle_remove(DwindleNode **root, Client *c) +{ + DwindleNode *leaf = dwindle_find_leaf(*root, c); + if (!leaf) + return; + + DwindleNode *parent = leaf->parent; + if (!parent) { + free(leaf); + *root = NULL; + return; + } + + DwindleNode *sibling = + (parent->children[0] == leaf) ? parent->children[1] : parent->children[0]; + DwindleNode *grandparent = parent->parent; + + sibling->parent = grandparent; + if (grandparent) { + if (grandparent->children[0] == parent) + grandparent->children[0] = sibling; + else + grandparent->children[1] = sibling; + } else { + *root = sibling; + } + + free(leaf); + free(parent); +} + +/* Remove c from every monitor's per-tag tree. Called from unmapnotify(). */ +void +dwindle_remove_client(Client *c) +{ + Monitor *m = c->mon; + if (!m) + return; + for (int i = 0; i < TAGCOUNT; i++) + dwindle_remove(&m->dwindle_root[i], c); +} + +/* ================================================================ + * dwindle + * ================================================================ */ + +void +dwindle(Monitor *m) +{ + Client *c; + int n = 0, e; + + wl_list_for_each(c, &clients, link) + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + n++; + + if (n == 0) + return; + + e = (m->gaps && !(cfg.appearance.smart_gaps && n == 1)) + ? (int)cfg.appearance.gaps + : 0; + + int ti = current_tag_idx(m); + DwindleNode **root = &m->dwindle_root[ti]; + + /* prune leaves whose clients are no longer tiled here */ + { + DwindleNode *stack[512]; + Client *stale[512]; + int sp = 0, sc = 0; + + if (*root) + stack[sp++] = *root; + + while (sp > 0) { + DwindleNode *nd = stack[--sp]; + if (!nd->is_node) { + int found = 0; + wl_list_for_each(c, &clients, link) { + if (c == nd->client && VISIBLEON(c, m) && !c->isfloating) { + found = 1; + break; + } + } + if (!found && sc < 512) + stale[sc++] = nd->client; + } else { + if (nd->children[1]) + stack[sp++] = nd->children[1]; + if (nd->children[0]) + stack[sp++] = nd->children[0]; + } + } + + for (int i = 0; i < sc; i++) + dwindle_remove(root, stale[i]); + } + + /* + * Insert any newly visible client, splitting the focused node. + * Use m->dwindle_focus[ti] so it actually splits what the user + * was looking at when they spawned the window, rather than whatever + * focustop() happens to return. + */ + Client *focused = m->dwindle_focus[ti]; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (!dwindle_find_leaf(*root, c)) { + dwindle_insert(root, c, focused); + focused = c; + } + } + + /* assign root box and recurse */ + if (*root) { + (*root)->box = (struct wlr_box){ + m->w.x + e, + m->w.y + e, + MAX(1, m->w.width - 2 * e), + MAX(1, m->w.height - 2 * e), + }; + dwindle_recalc(*root, e); + } +} + +/* ================================================================ + * master / stack + * ================================================================ */ + +static void +master_arrange(Monitor *m, int ti) +{ + Client *c, *master_c = NULL; + Client *stack[256]; + int nstack = 0, n = 0; + + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && !c->isfloating) { + if (c == m->master_master[ti]) + master_c = c; + if (!c->isfullscreen) + n++; + } + } + + if (n == 0) + return; + + if (!master_c) { + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { + m->master_master[ti] = c; + master_c = c; + break; + } + } + } + + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && + c != master_c && nstack < 256) + stack[nstack++] = c; + } + + int e = (m->gaps && !(cfg.appearance.smart_gaps && n == 1)) + ? (int)cfg.appearance.gaps + : 0; + int aw = MAX(1, m->w.width - 2 * e); + int ah = MAX(1, m->w.height - 2 * e); + + if (n == 1) { + if (master_c && !master_c->isfullscreen) + resize(master_c, + (struct wlr_box){m->w.x + e, m->w.y + e, aw, ah}, 0); + return; + } + + int master_w = MAX(1, (int)(aw * m->mfact)); + + if (m->master_side[ti] == 0) { + int stack_x = m->w.x + e + master_w + e; + int stack_w = MAX(1, aw - master_w - e); + + if (master_c && !master_c->isfullscreen) + resize(master_c, + (struct wlr_box){m->w.x + e, m->w.y + e, master_w, ah}, + 0); + + int sh = MAX(1, (ah - (nstack - 1) * e) / nstack); + for (int i = 0; i < nstack; i++) + resize(stack[i], + (struct wlr_box){stack_x, m->w.y + e + i * (sh + e), + stack_w, sh}, + 0); + } else { + int stack_w = MAX(1, aw - master_w - e); + int sh = MAX(1, (ah - (nstack - 1) * e) / nstack); + + for (int i = 0; i < nstack; i++) + resize(stack[i], + (struct wlr_box){m->w.x + e, m->w.y + e + i * (sh + e), + stack_w, sh}, + 0); + + if (master_c && !master_c->isfullscreen) + resize(master_c, + (struct wlr_box){m->w.x + e + stack_w + e, m->w.y + e, + master_w, ah}, + 0); + } +} + +void +master(Monitor *m) +{ + int ti = current_tag_idx(m); + master_arrange(m, ti); +} + +/* Clear master references to client c on all tags of its monitor. */ +void +master_remove_client(Client *c) +{ + Monitor *m = c->mon; + if (!m) + return; + for (int i = 0; i < TAGCOUNT; i++) { + if (m->master_master[i] == c) + m->master_master[i] = NULL; + } +} + +/* ================================================================ + * monocle + * ================================================================ */ + +void +monocle(Monitor *m) +{ + Client *c, *sel = focustop(m); + int n = 0, e; + + wl_list_for_each(c, &clients, link) + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + n++; + + if (n == 0) + return; + + e = (m->gaps && !(cfg.appearance.smart_gaps && n == 1)) + ? (int)cfg.appearance.gaps + : 0; + + int aw = MAX(1, m->w.width - 2 * e); + int ah = MAX(1, m->w.height - 2 * e); + + /* Stack all windows at the same position and raise the focused one to + * top. */ + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + resize(c, (struct wlr_box){m->w.x + e, m->w.y + e, aw, ah}, 0); + wlr_scene_node_set_enabled(&c->scene->node, 0); + } + if (sel) { + wlr_scene_node_set_enabled(&sel->scene->node, 1); + wlr_scene_node_raise_to_top(&sel->scene->node); + } +} + +/* ================================================================ + * swaptiled (tile-drag swap) + * ================================================================ */ + +void +swaptiled(Client *a, Client *b) +{ + Monitor *m; + if (!a || !b || a == b) + return; + + wl_list_for_each(m, &mons, link) { + for (int i = 0; i < TAGCOUNT; i++) { + DwindleNode **root = &m->dwindle_root[i]; + if (!*root) + continue; + DwindleNode *la = dwindle_find_leaf(*root, a); + DwindleNode *lb = dwindle_find_leaf(*root, b); + if (!la || !lb) + continue; + la->client = b; + lb->client = a; + int e = (m->gaps && cfg.appearance.gaps) + ? (int)cfg.appearance.gaps + : 0; + (*root)->box = (struct wlr_box){ + m->w.x + e, + m->w.y + e, + MAX(1, m->w.width - 2 * e), + MAX(1, m->w.height - 2 * e), + }; + dwindle_recalc(*root, e); + } + } + + focusclient(a, 1); + printstatus(); +} diff --git a/src/layout.h b/src/layout.h new file mode 100644 index 0000000..837fa8e --- /dev/null +++ b/src/layout.h @@ -0,0 +1,43 @@ +#ifndef PEACHWM_LAYOUT_H +#define PEACHWM_LAYOUT_H + +#include "monitor.h" +#include "client.h" + +/* dwindle binary tree node */ +struct DwindleNode { + DwindleNode *children[2]; + DwindleNode *parent; + Client *client; + struct wlr_box box; + float split_ratio; + int split_top; + int is_node; +}; + +/* layout table and count */ +extern const Layout layouts[]; +extern const unsigned int layout_count; + +/* layout entry points (called via Layout.arrange function pointer) */ +void dwindle(Monitor *m); +void master(Monitor *m); +void monocle(Monitor *m); + +/* helpers used across modules */ +int current_tag_idx(Monitor *m); +const Layout *curlayout(Monitor *m); + +/* dwindle tree helpers (exposed for swapdir) */ +DwindleNode *dwindle_find_leaf(DwindleNode *n, Client *c); +void dwindle_recalc(DwindleNode *n, int gap); + +/* lifecycle helpers (called from peachwm.c unmap/cleanup) */ +void dwindle_free_tree(DwindleNode *n); +void dwindle_remove_client(Client *c); +void master_remove_client(Client *c); + +/* tile drag swap (called from buttonpress handler) */ +void swaptiled(Client *a, Client *b); + +#endif /* PEACHWM_LAYOUT_H */ diff --git a/src/peachwm.c b/src/peachwm.c index 4935db4..f6c698c 100644 --- a/src/peachwm.c +++ b/src/peachwm.c @@ -4,6 +4,7 @@ */ #include #include +#include #include #include #include @@ -74,15 +75,18 @@ #include "parser/parser.h" #include "util.h" +#include "client.h" +#include "monitor.h" +#include "layout.h" +#include "scratchpad.h" +#include "ext_workspace.h" +#include "ipc.h" +#include "ipc_socket.h" /* macros */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) -#define VISIBLEON(C, M) \ - ((M) && (C)->mon == (M) && \ - ((C)->isscratchpad ? (M)->scratchpad_visible : \ - ((C)->tags & (M)->tagset[(M)->seltags]))) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1u << TAGCOUNT) - 1) @@ -96,26 +100,6 @@ /* enums */ enum { CurNormal, CurPressed, CurMove, CurResize, CurTileDrag }; /* cursor */ -enum { XDGShell, LayerShell, X11 }; /* client types */ -enum { - LyrBg, - LyrBottom, - LyrTile, - LyrFloat, - LyrScratch, - LyrTop, - LyrFS, - LyrOverlay, - LyrBlock, - NUM_LAYERS -}; /* scene layers */ - -typedef union { - int i; - uint32_t ui; - float f; - const void *v; -} Arg; typedef struct { unsigned int mod; @@ -124,47 +108,6 @@ typedef struct { const Arg arg; } Button; -typedef struct Monitor Monitor; -typedef struct { - /* Must keep this field first */ - unsigned int type; /* XDGShell or X11* */ - - Monitor *mon; - struct wlr_scene_tree *scene; - struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ - struct wlr_scene_tree *scene_surface; - struct wl_list link; - struct wl_list flink; - struct wlr_box geom; /* layout-relative, includes border */ - struct wlr_box prev; /* layout-relative, includes border */ - struct wlr_box bounds; /* only width and height are used */ - union { - struct wlr_xdg_surface *xdg; - struct wlr_xwayland_surface *xwayland; - } surface; - struct wlr_xdg_toplevel_decoration_v1 *decoration; - struct wl_listener commit; - struct wl_listener map; - struct wl_listener maximize; - struct wl_listener unmap; - struct wl_listener destroy; - struct wl_listener set_title; - struct wl_listener fullscreen; - struct wl_listener set_decoration_mode; - struct wl_listener destroy_decoration; -#ifdef XWAYLAND - struct wl_listener activate; - struct wl_listener associate; - struct wl_listener dissociate; - struct wl_listener configure; - struct wl_listener set_hints; -#endif - unsigned int bw; - uint32_t tags; - int isfloating, isurgent, isfullscreen, isscratchpad; - uint32_t resize; /* configure serial of a pending resize */ -} Client; - typedef struct { uint32_t mod; xkb_keysym_t keysym; @@ -191,88 +134,10 @@ typedef struct { struct wl_listener destroy; } PointerDevice; -typedef struct { - /* Must keep this field first */ - unsigned int type; /* LayerShell */ - - Monitor *mon; - struct wlr_scene_tree *scene; - struct wlr_scene_tree *popups; - struct wlr_scene_layer_surface_v1 *scene_layer; - struct wl_list link; - int mapped; - struct wlr_layer_surface_v1 *layer_surface; - - struct wl_listener destroy; - struct wl_listener unmap; - struct wl_listener surface_commit; -} LayerSurface; - -typedef struct { - const char *symbol; - void (*arrange)(Monitor *); -} Layout; - #ifndef TAGCOUNT #define TAGCOUNT 9 #endif -static void dwindle(Monitor *m); -static void master(Monitor *m); -static void monocle(Monitor *m); - -static const Layout layouts[] = { - {"><>", NULL}, - {"[T]", dwindle}, - {"[M]", master}, - {"[]", monocle}, -}; - -/* dwindle tree */ -typedef struct DwindleNode DwindleNode; -struct DwindleNode { - DwindleNode *children[2]; - DwindleNode *parent; - Client *client; - struct wlr_box box; - float split_ratio; - int split_top; - int is_node; -}; - -struct Monitor { - struct wl_list link; - struct wlr_output *wlr_output; - struct wlr_scene_output *scene_output; - struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ - struct wl_listener frame; - struct wl_listener destroy; - struct wl_listener request_state; - struct wl_listener destroy_lock_surface; - struct wlr_session_lock_surface_v1 *lock_surface; - struct wlr_box m; /* monitor area, layout-relative */ - struct wlr_box w; /* window area, layout-relative */ - struct wl_list layers[4]; /* LayerSurface.link */ - const Layout *lt[TAGCOUNT][2]; /* per-tag layout slots */ - int gaps; - unsigned int seltags; - struct wlr_ext_workspace_group_handle_v1 *ext_group; - unsigned int sellt[TAGCOUNT]; /* per-tag layout toggle index */ - uint32_t tagset[2]; - float mfact; - int nmaster; - char ltsymbol[TAGCOUNT][16]; /* per-tag layout symbol */ - int asleep; - DwindleNode *dwindle_root[TAGCOUNT]; /* one tree per tag */ - Client *dwindle_focus[TAGCOUNT]; /* insertion anchor, one per tag */ - /* master/stack layout state */ - Client *master_master[TAGCOUNT]; /* the master client (NULL if none) */ - int master_side[TAGCOUNT]; /* 0 = master on left, 1 = master on right */ - int scratchpad_visible; /* whether scratchpad is shown */ - Client *scratchpad_prev_focus; /* client focused before scratchpad opened */ - Client *scratchpad_current; /* currently visible scratchpad client */ -}; - typedef struct { const char *name; float mfact; @@ -308,7 +173,6 @@ typedef struct { /* function declarations */ static void applybounds(Client *c, struct wlr_box *bbox); static void applyrules(Client *c); -static void arrange(Monitor *m); static void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive); static void arrangelayers(Monitor *m); @@ -320,11 +184,6 @@ static void cleanup(void); static void cleanupmon(struct wl_listener *listener, void *data); static void cleanuplisteners(void); static void closemon(Monitor *m); -static void dwindle_free_tree(DwindleNode *n); -static void dwindle_remove_client(Client *c); -static void master_remove_client(Client *c); -static inline int current_tag_idx(Monitor *m); -static inline const Layout *curlayout(Monitor *m); static void commitlayersurfacenotify(struct wl_listener *listener, void *data); static void commitnotify(struct wl_listener *listener, void *data); static void commitpopup(struct wl_listener *listener, void *data); @@ -353,12 +212,9 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); static void destroysessionlock(struct wl_listener *listener, void *data); static void destroykeyboardgroup(struct wl_listener *listener, void *data); static Monitor *dirtomon(enum wlr_direction dir); -static void focusclient(Client *c, int lift); static void focusmon(const Arg *arg); -static void focusdir(const Arg *arg); +void focusdir(const Arg *arg); static void swapdir(const Arg *arg); -static void swaptiled(Client *a, Client *b); -static Client *focustop(Monitor *m); static void fullscreennotify(struct wl_listener *listener, void *data); static void gpureset(struct wl_listener *listener, void *data); static void handlesig(int signo); @@ -367,7 +223,7 @@ static int keybinding(uint32_t mods, xkb_keysym_t sym); static void keypress(struct wl_listener *listener, void *data); static void keypressmod(struct wl_listener *listener, void *data); static int keyrepeat(void *data); -static void killclient(const Arg *arg); +void killclient(const Arg *arg); static void locksession(struct wl_listener *listener, void *data); static void mapnotify(struct wl_listener *listener, void *data); static void maximizenotify(struct wl_listener *listener, void *data); @@ -383,18 +239,17 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, static void outputmgrtest(struct wl_listener *listener, void *data); static void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time); -static void printstatus(void); static void powermgrsetmode(struct wl_listener *listener, void *data); -static void quit(const Arg *arg); +void quit(const Arg *arg); static void rendermon(struct wl_listener *listener, void *data); static void requestdecorationmode(struct wl_listener *listener, void *data); static void requeststartdrag(struct wl_listener *listener, void *data); static void requestmonstate(struct wl_listener *listener, void *data); -static void resize(Client *c, struct wlr_box geo, int interact); +void resize(Client *c, struct wlr_box geo, int interact); static void run(char *startup_cmd); static void setcursor(struct wl_listener *listener, void *data); static void setcursorshape(struct wl_listener *listener, void *data); -static void setfloating(Client *c, int floating); +void setfloating(Client *c, int floating); static void setfullscreen(Client *c, int fullscreen); static void setlayout(const Arg *arg); static void setmon(Client *c, Monitor *m, uint32_t newtags); @@ -403,42 +258,36 @@ static void setsel(struct wl_listener *listener, void *data); static void setup(void); static void spawn(const Arg *arg); static void startdrag(struct wl_listener *listener, void *data); -static void tag(const Arg *arg); +void tag(const Arg *arg); static void tagmon(const Arg *arg); -static void dwindle(Monitor *m); -static void master(Monitor *m); -static void monocle(Monitor *m); -static void togglefloating(const Arg *arg); -static void togglefullscreen(const Arg *arg); +void togglefloating(const Arg *arg); +void togglefullscreen(const Arg *arg); static void togglegaps(const Arg *arg); -static void togglescratchpad(const Arg *arg); -static void scratchpad_arrange(Monitor *m); -static void swapdirscratchpad(const Arg *arg); static void toggletag(const Arg *arg); -static void toggleview(const Arg *arg); +void toggleview(const Arg *arg); static void unlocksession(struct wl_listener *listener, void *data); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); static void unmapnotify(struct wl_listener *listener, void *data); static void updatemons(struct wl_listener *listener, void *data); static void updatetitle(struct wl_listener *listener, void *data); static void urgent(struct wl_listener *listener, void *data); -static void view(const Arg *arg); +void view(const Arg *arg); static void virtualkeyboard(struct wl_listener *listener, void *data); static void virtualpointer(struct wl_listener *listener, void *data); static Monitor *xytomon(double x, double y); static void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny); -static void do_reload(void); +void do_reload(void); /* variables */ static pid_t child_pid = -1; static int locked; static void *exclusive_focus; -static struct wl_display *dpy; +struct wl_display *dpy; static struct wl_event_loop *event_loop; static struct wlr_backend *backend; static struct wlr_scene *scene; -static struct wlr_scene_tree *layers[NUM_LAYERS]; +struct wlr_scene_tree *layers[NUM_LAYERS]; static struct wlr_scene_tree *drag_icon; /* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ static const int layermap[] = {LyrBg, LyrBottom, LyrTop, LyrOverlay}; @@ -450,8 +299,8 @@ static struct wlr_session *session; static struct wlr_xdg_shell *xdg_shell; static struct wlr_xdg_activation_v1 *activation; static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; -static struct wl_list clients; /* tiling order */ -static struct wl_list fstack; /* focus order */ +struct wl_list clients; /* tiling order */ +struct wl_list fstack; /* focus order */ static struct wlr_idle_notifier_v1 *idle_notifier; static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; static struct wlr_layer_shell_v1 *layer_shell; @@ -473,7 +322,7 @@ static struct wlr_session_lock_manager_v1 *session_lock_mgr; static struct wlr_scene_rect *locked_bg; static struct wlr_session_lock_v1 *cur_lock; -static struct wlr_seat *seat; +struct wlr_seat *seat; static KeyboardGroup *kb_group; static struct wl_list pointer_devices; static unsigned int cursor_mode; @@ -482,8 +331,8 @@ static int grabcx, grabcy; /* client-relative */ static struct wlr_output_layout *output_layout; static struct wlr_box sgeom; -static struct wl_list mons; -static Monitor *selmon; +struct wl_list mons; +Monitor *selmon; /* global event handlers */ static struct wl_listener cursor_axis = {.notify = axisnotify}; @@ -532,27 +381,13 @@ static struct wlr_xwayland *xwayland; /* Runtime config */ -static Config cfg; -static struct wl_event_source *cfg_watch_src; +Config cfg; +static WatchState *cfg_watch_src; static const char *custom_cfg_path; static char config_path[1024]; -/* attempt to encapsulate suck into one file */ -#include "client.h" -#include "ext-workspace.h" -#include "ipc.h" -#include "ipc_socket.h" - /* function implementations */ -/* Helper: get current layout for the active tag on monitor m */ -static inline const Layout * -curlayout(Monitor *m) -{ - int ti = current_tag_idx(m); - return m->lt[ti][m->sellt[ti]]; -} - /* Apply a workspace default layout for the given tag index (0-based) on monitor * m */ static void apply_workspace_layout(Monitor *m, int tag_idx) { @@ -562,7 +397,7 @@ static void apply_workspace_layout(Monitor *m, int tag_idx) { if (!layout_name[0]) return; - for (int li = 0; li < (int)LENGTH(layouts); li++) { + for (int li = 0; li < (int)layout_count; li++) { if (layouts[li].symbol && !strcmp(layout_name, layouts[li].symbol)) { m->lt[tag_idx][m->sellt[tag_idx]] = &layouts[li]; break; @@ -582,7 +417,7 @@ static void apply_workspace_layout(Monitor *m, int tag_idx) { } } -void applybounds(Client *c, struct wlr_box *bbox) { +static void applybounds(Client *c, struct wlr_box *bbox) { /* set minimum possible */ c->geom.width = MAX(1 + 2 * (int)c->bw, c->geom.width); c->geom.height = MAX(1 + 2 * (int)c->bw, c->geom.height); @@ -597,7 +432,7 @@ void applybounds(Client *c, struct wlr_box *bbox) { c->geom.y = bbox->y; } -void applyrules(Client *c) { +static void applyrules(Client *c) { /* rule matching */ const char *appid, *title; uint32_t newtags = 0; @@ -643,6 +478,7 @@ void arrange(Monitor *m) { int ti = current_tag_idx(m); strncpy(m->ltsymbol[ti], curlayout(m)->symbol, LENGTH(m->ltsymbol[ti])); + m->ltsymbol[ti][LENGTH(m->ltsymbol[ti]) - 1] = '\0'; /* We move all clients (except fullscreen and unmanaged) to LyrTile while * in floating layout to avoid "real" floating clients be always on top */ @@ -667,7 +503,7 @@ void arrange(Monitor *m) { checkidleinhibitor(NULL); } -void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, +static void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) { LayerSurface *l; struct wlr_box full_area = m->m; @@ -688,7 +524,7 @@ void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, } } -void arrangelayers(Monitor *m) { +static void arrangelayers(Monitor *m) { int i; struct wlr_box usable_area = m->m; LayerSurface *l; @@ -731,7 +567,7 @@ void arrangelayers(Monitor *m) { static void dispatch_action(const char *action, const char (*args)[CFG_MAX_STRLEN], int nargs); -void axisnotify(struct wl_listener *listener, void *data) { +static void axisnotify(struct wl_listener *listener, void *data) { /* This event is forwarded by the cursor when a pointer emits an axis event, * for example when you move the scroll wheel. */ struct wlr_pointer_axis_event *event = data; @@ -758,7 +594,7 @@ void axisnotify(struct wl_listener *listener, void *data) { event->source, event->relative_direction); } -void buttonpress(struct wl_listener *listener, void *data) { +static void buttonpress(struct wl_listener *listener, void *data) { struct wlr_pointer_button_event *event = data; struct wlr_keyboard *keyboard; uint32_t mods; @@ -820,7 +656,7 @@ void buttonpress(struct wl_listener *listener, void *data) { event->state); } -void chvt(const Arg *arg) { +static void chvt(const Arg *arg) { if (!session) { fprintf(stderr, "peachwm: chvt: no session (not running on DRM)\n"); return; @@ -828,7 +664,7 @@ void chvt(const Arg *arg) { wlr_session_change_vt(session, arg->ui); } -void checkidleinhibitor(struct wlr_surface *exclude) { +static void checkidleinhibitor(struct wlr_surface *exclude) { int inhibited = 0, unused_lx, unused_ly; struct wlr_idle_inhibitor_v1 *inhibitor; wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { @@ -847,7 +683,7 @@ void checkidleinhibitor(struct wlr_surface *exclude) { wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); } -void cleanup(void) { +static void cleanup(void) { config_watch_stop(cfg_watch_src); cfg_watch_src = NULL; ipc_socket_finish(); @@ -892,7 +728,7 @@ child_done: wlr_scene_node_destroy(&scene->tree.node); } -void cleanupmon(struct wl_listener *listener, void *data) { +static void cleanupmon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, destroy); LayerSurface *l, *tmp; size_t i; @@ -924,7 +760,7 @@ void cleanupmon(struct wl_listener *listener, void *data) { free(m); } -void cleanuplisteners(void) { +static void cleanuplisteners(void) { wl_list_remove(&cursor_axis.link); wl_list_remove(&cursor_button.link); wl_list_remove(&cursor_frame.link); @@ -959,7 +795,7 @@ void cleanuplisteners(void) { #endif } -void closemon(Monitor *m) { +static void closemon(Monitor *m) { /* update selmon if needed and * move closed monitor's clients to the focused one */ Client *c; @@ -991,7 +827,9 @@ void closemon(Monitor *m) { printstatus(); } -void commitlayersurfacenotify(struct wl_listener *listener, void *data) { +// ── Compositor Lifecycle ──────────────────────────────────── + +static void commitlayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, surface_commit); struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; struct wlr_scene_tree *scene_layer = @@ -1029,7 +867,7 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { arrangelayers(l->mon); } -void commitnotify(struct wl_listener *listener, void *data) { +static void commitnotify(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, commit); if (c->surface.xdg->initial_commit) { @@ -1060,7 +898,7 @@ void commitnotify(struct wl_listener *listener, void *data) { c->resize = 0; } -void commitpopup(struct wl_listener *listener, void *data) { +static void commitpopup(struct wl_listener *listener, void *data) { struct wlr_surface *surface = data; struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); LayerSurface *l = NULL; @@ -1088,7 +926,7 @@ void commitpopup(struct wl_listener *listener, void *data) { free(listener); } -void createdecoration(struct wl_listener *listener, void *data) { +static void createdecoration(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel_decoration_v1 *deco = data; Client *c = deco->toplevel->base->data; c->decoration = deco; @@ -1100,14 +938,14 @@ void createdecoration(struct wl_listener *listener, void *data) { requestdecorationmode(&c->set_decoration_mode, deco); } -void createidleinhibitor(struct wl_listener *listener, void *data) { +static void createidleinhibitor(struct wl_listener *listener, void *data) { struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); checkidleinhibitor(NULL); } -void createkeyboard(struct wlr_keyboard *keyboard) { +static void createkeyboard(struct wlr_keyboard *keyboard) { /* Set the keymap to match the group keymap */ wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); @@ -1115,7 +953,7 @@ void createkeyboard(struct wlr_keyboard *keyboard) { wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); } -KeyboardGroup *createkeyboardgroup(void) { +static KeyboardGroup *createkeyboardgroup(void) { KeyboardGroup *group = ecalloc(1, sizeof(*group)); struct xkb_context *context; struct xkb_keymap *keymap; @@ -1154,7 +992,7 @@ KeyboardGroup *createkeyboardgroup(void) { return group; } -void createlayersurface(struct wl_listener *listener, void *data) { +static void createlayersurface(struct wl_listener *listener, void *data) { struct wlr_layer_surface_v1 *layer_surface = data; LayerSurface *l; struct wlr_surface *surface = layer_surface->surface; @@ -1189,7 +1027,7 @@ void createlayersurface(struct wl_listener *listener, void *data) { wlr_surface_send_enter(surface, layer_surface->output); } -void createlocksurface(struct wl_listener *listener, void *data) { +static void createlocksurface(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, new_surface); struct wlr_session_lock_surface_v1 *lock_surface = data; Monitor *m = lock_surface->output->data; @@ -1207,7 +1045,7 @@ void createlocksurface(struct wl_listener *listener, void *data) { client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); } -void createmon(struct wl_listener *listener, void *data) { +static void createmon(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new output (aka a display or * monitor) becomes available. */ struct wlr_output *wlr_output = data; @@ -1239,7 +1077,7 @@ void createmon(struct wl_listener *listener, void *data) { m->nmaster = r->nmaster; /* Determine monitor's default layout */ const Layout *mon_layout = &layouts[0]; - for (int li = 0; li < (int)LENGTH(layouts); li++) { + for (int li = 0; li < (int)layout_count; li++) { if (layouts[li].symbol && !strcmp(r->layout, layouts[li].symbol)) { mon_layout = &layouts[li]; break; @@ -1257,7 +1095,7 @@ void createmon(struct wl_listener *listener, void *data) { break; } } - const Layout *alt_layout = &layouts[LENGTH(layouts) > 1 && mon_layout != &layouts[1]]; + const Layout *alt_layout = &layouts[layout_count > 1 && mon_layout != &layouts[1]]; /* Propagate monitor layout to all tags */ for (int ti = 0; ti < TAGCOUNT; ti++) { m->lt[ti][0] = mon_layout; @@ -1268,6 +1106,7 @@ void createmon(struct wl_listener *listener, void *data) { for (int ti = 0; ti < TAGCOUNT; ti++) apply_workspace_layout(m, ti); strncpy(m->ltsymbol[0], curlayout(m)->symbol, LENGTH(m->ltsymbol[0])); + m->ltsymbol[0][LENGTH(m->ltsymbol[0]) - 1] = '\0'; wlr_output_state_set_scale(&state, r->scale); wlr_output_state_set_transform(&state, r->transform); break; @@ -1322,7 +1161,7 @@ void createmon(struct wl_listener *listener, void *data) { wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); } -void createnotify(struct wl_listener *listener, void *data) { +static void createnotify(struct wl_listener *listener, void *data) { /* This event is raised when a client creates a new toplevel (application * window). */ struct wlr_xdg_toplevel *toplevel = data; @@ -1350,7 +1189,7 @@ static void pointer_device_destroy(struct wl_listener *listener, void *data) { free(pdev); } -void createpointer(struct wlr_pointer *pointer) { +static void createpointer(struct wlr_pointer *pointer) { struct libinput_device *device; PointerDevice *pdev = ecalloc(1, sizeof(*pdev)); pdev->device = &pointer->base; @@ -1407,7 +1246,7 @@ void createpointer(struct wlr_pointer *pointer) { wlr_cursor_attach_input_device(cursor, &pointer->base); } -void createpointerconstraint(struct wl_listener *listener, void *data) { +static void createpointerconstraint(struct wl_listener *listener, void *data) { PointerConstraint *pointer_constraint = ecalloc(1, sizeof(*pointer_constraint)); pointer_constraint->constraint = data; @@ -1415,14 +1254,14 @@ void createpointerconstraint(struct wl_listener *listener, void *data) { &pointer_constraint->destroy, destroypointerconstraint); } -void createpopup(struct wl_listener *listener, void *data) { +static void createpopup(struct wl_listener *listener, void *data) { /* This event is raised when a client (either xdg-shell or layer-shell) * creates a new popup. */ struct wlr_xdg_popup *popup = data; LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); } -void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { +static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { if (active_constraint == constraint) return; @@ -1433,7 +1272,7 @@ void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { wlr_pointer_constraint_v1_send_activated(constraint); } -void cursorframe(struct wl_listener *listener, void *data) { +static void cursorframe(struct wl_listener *listener, void *data) { /* This event is forwarded by the cursor when a pointer emits a frame * event. Frame events are sent after regular pointer events to group * multiple events together. For instance, two axis events may happen at the @@ -1442,7 +1281,9 @@ void cursorframe(struct wl_listener *listener, void *data) { wlr_seat_pointer_notify_frame(seat); } -void cursorwarptohint(void) { +static void cursorwarptohint(void) { + if (!active_constraint) + return; Client *c = NULL; double sx = active_constraint->current.cursor_hint.x; double sy = active_constraint->current.cursor_hint.y; @@ -1455,14 +1296,14 @@ void cursorwarptohint(void) { } } -void destroydecoration(struct wl_listener *listener, void *data) { +static void destroydecoration(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, destroy_decoration); wl_list_remove(&c->destroy_decoration.link); wl_list_remove(&c->set_decoration_mode.link); } -void destroydragicon(struct wl_listener *listener, void *data) { +static void destroydragicon(struct wl_listener *listener, void *data) { /* Focus enter isn't sent during drag, so refocus the focused node. */ focusclient(focustop(selmon), 1); motionnotify(0, NULL, 0, 0, 0, 0); @@ -1470,7 +1311,7 @@ void destroydragicon(struct wl_listener *listener, void *data) { free(listener); } -void destroyidleinhibitor(struct wl_listener *listener, void *data) { +static void destroyidleinhibitor(struct wl_listener *listener, void *data) { /* `data` is the wlr_surface of the idle inhibitor being destroyed, * at this point the idle inhibitor is still in the list of the manager */ checkidleinhibitor(wlr_surface_get_root_surface(data)); @@ -1478,7 +1319,7 @@ void destroyidleinhibitor(struct wl_listener *listener, void *data) { free(listener); } -void destroylayersurfacenotify(struct wl_listener *listener, void *data) { +static void destroylayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, destroy); wl_list_remove(&l->link); @@ -1490,7 +1331,7 @@ void destroylayersurfacenotify(struct wl_listener *listener, void *data) { free(l); } -void destroylock(SessionLock *lock, int unlock) { +static void destroylock(SessionLock *lock, int unlock) { wlr_seat_keyboard_notify_clear_focus(seat); if ((locked = !unlock)) goto destroy; @@ -1510,7 +1351,7 @@ destroy: free(lock); } -void destroylocksurface(struct wl_listener *listener, void *data) { +static void destroylocksurface(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, destroy_lock_surface); struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; @@ -1530,7 +1371,7 @@ void destroylocksurface(struct wl_listener *listener, void *data) { } } -void destroynotify(struct wl_listener *listener, void *data) { +static void destroynotify(struct wl_listener *listener, void *data) { /* Called when the xdg_toplevel is destroyed. */ Client *c = wl_container_of(listener, c, destroy); wl_list_remove(&c->destroy.link); @@ -1554,7 +1395,7 @@ void destroynotify(struct wl_listener *listener, void *data) { free(c); } -void destroypointerconstraint(struct wl_listener *listener, void *data) { +static void destroypointerconstraint(struct wl_listener *listener, void *data) { PointerConstraint *pointer_constraint = wl_container_of(listener, pointer_constraint, destroy); @@ -1567,12 +1408,12 @@ void destroypointerconstraint(struct wl_listener *listener, void *data) { free(pointer_constraint); } -void destroysessionlock(struct wl_listener *listener, void *data) { +static void destroysessionlock(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, destroy); destroylock(lock, 0); } -void destroykeyboardgroup(struct wl_listener *listener, void *data) { +static void destroykeyboardgroup(struct wl_listener *listener, void *data) { KeyboardGroup *group = wl_container_of(listener, group, destroy); wl_event_source_remove(group->key_repeat_source); wl_list_remove(&group->key.link); @@ -1582,7 +1423,7 @@ void destroykeyboardgroup(struct wl_listener *listener, void *data) { free(group); } -Monitor *dirtomon(enum wlr_direction dir) { +static Monitor *dirtomon(enum wlr_direction dir) { struct wlr_output *next; if (!wlr_output_layout_get(output_layout, selmon->wlr_output)) return selmon; @@ -1680,7 +1521,7 @@ void focusclient(Client *c, int lift) { c->geom.y + c->geom.height / 2); } -void focusmon(const Arg *arg) { +static void focusmon(const Arg *arg) { int i = 0, nmons = wl_list_length(&mons); if (nmons) { do /* don't switch to disabled mons */ @@ -1713,51 +1554,7 @@ void focusdir(const Arg *arg) { /* Scratchpad monocle cycling */ if (sel->isscratchpad && selmon && selmon->scratchpad_visible) { - Client *first = NULL, *next = NULL, *prev = NULL; - int found = 0; - - wl_list_for_each(c, &clients, link) { - if (!c->isscratchpad || c->mon != selmon) - continue; - if (!first) - first = c; - if (found) { - next = c; - break; - } - if (c == sel) { - found = 1; - continue; - } - prev = c; - } - - Client *target = NULL; - if (arg->i == WLR_DIRECTION_DOWN || arg->i == WLR_DIRECTION_RIGHT) { - if (next) - target = next; - else if (first) - target = first; - } else { - if (prev) - target = prev; - else { - /* wrap to last */ - wl_list_for_each(c, &clients, link) - if (c->isscratchpad && c->mon == selmon) - target = c; - } - } - - if (target && target != sel) { - if (selmon->scratchpad_current) - wlr_scene_node_set_enabled( - &selmon->scratchpad_current->scene->node, 0); - wlr_scene_node_set_enabled(&target->scene->node, 1); - client_set_suspended(target, 0); - selmon->scratchpad_current = target; - focusclient(target, 1); - } + scratchpad_focusdir_cycle(arg); return; } @@ -1861,12 +1658,12 @@ Client *focustop(Monitor *m) { return NULL; } -void fullscreennotify(struct wl_listener *listener, void *data) { +static void fullscreennotify(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, fullscreen); setfullscreen(c, client_wants_fullscreen(c)); } -void gpureset(struct wl_listener *listener, void *data) { +static void gpureset(struct wl_listener *listener, void *data) { struct wlr_renderer *old_drw = drw; struct wlr_allocator *old_alloc = alloc; struct Monitor *m; @@ -1889,7 +1686,7 @@ void gpureset(struct wl_listener *listener, void *data) { wlr_renderer_destroy(old_drw); } -void handlesig(int signo) { +static void handlesig(int signo) { if (signo == SIGCHLD) while (waitpid(-1, NULL, WNOHANG) > 0) ; @@ -1897,7 +1694,7 @@ void handlesig(int signo) { quit(NULL); } -void inputdevice(struct wl_listener *listener, void *data) { +static void inputdevice(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new input device becomes * available. */ struct wlr_input_device *device = data; @@ -1925,6 +1722,8 @@ void inputdevice(struct wl_listener *listener, void *data) { wlr_seat_set_capabilities(seat, caps); } +// ── Input Handling ────────────────────────────────────────── + /* dispatch_action maps a CfgKeybind/CfgButton action string to the * corresponding compositor function and calls it. args[0] is used where * needed (spawn argv, tag mask, direction, etc). */ @@ -2069,10 +1868,10 @@ static void dispatch_action(const char *action, } if (!strcmp(action, "switchlayout")) { uint32_t i; - for (i = 0; i < LENGTH(layouts); i++) + for (i = 0; i < layout_count; i++) if (curlayout(selmon) == &layouts[i]) break; - arg.ui = (i + 1) % LENGTH(layouts); + arg.ui = (i + 1) % layout_count; setlayout(&arg); return; } @@ -2110,7 +1909,7 @@ static void dispatch_action(const char *action, fprintf(stderr, "peachwm: unknown action '%s'\n", action); } -int keybinding(uint32_t mods, xkb_keysym_t sym) { +static int keybinding(uint32_t mods, xkb_keysym_t sym) { for (int i = 0; i < cfg.nkeybinds; i++) { const CfgKeybind *k = &cfg.keybinds[i]; if (CLEANMASK(mods) == CLEANMASK(k->mods) && @@ -2122,7 +1921,7 @@ int keybinding(uint32_t mods, xkb_keysym_t sym) { return 0; } -void keypress(struct wl_listener *listener, void *data) { +static void keypress(struct wl_listener *listener, void *data) { int i; /* This event is raised when a key is pressed or released. */ KeyboardGroup *group = wl_container_of(listener, group, key); @@ -2205,7 +2004,7 @@ void keypress(struct wl_listener *listener, void *data) { event->state); } -void keypressmod(struct wl_listener *listener, void *data) { +static void keypressmod(struct wl_listener *listener, void *data) { /* This event is raised when a modifier key, such as shift or alt, is * pressed. We simply communicate this to the client. */ KeyboardGroup *group = wl_container_of(listener, group, modifiers); @@ -2216,7 +2015,7 @@ void keypressmod(struct wl_listener *listener, void *data) { &group->wlr_group->keyboard.modifiers); } -int keyrepeat(void *data) { +static int keyrepeat(void *data) { KeyboardGroup *group = data; int i; if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) @@ -2238,7 +2037,9 @@ void killclient(const Arg *arg) { client_send_close(sel); } -void locksession(struct wl_listener *listener, void *data) { +// ── Session Lock ──────────────────────────────────────────── + +static void locksession(struct wl_listener *listener, void *data) { struct wlr_session_lock_v1 *session_lock = data; SessionLock *lock; wlr_scene_node_set_enabled(&locked_bg->node, 1); @@ -2261,7 +2062,7 @@ void locksession(struct wl_listener *listener, void *data) { wlr_session_lock_v1_send_locked(session_lock); } -void mapnotify(struct wl_listener *listener, void *data) { +static void mapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ Client *p = NULL; Client *w, *c = wl_container_of(listener, c, map); @@ -2351,7 +2152,7 @@ unset_fullscreen: } } -void maximizenotify(struct wl_listener *listener, void *data) { +static void maximizenotify(struct wl_listener *listener, void *data) { /* This event is raised when a client would like to maximize itself, * typically because the user clicked on the maximize button on * client-side decorations. peachwm doesn't support maximization, but @@ -2367,7 +2168,7 @@ void maximizenotify(struct wl_listener *listener, void *data) { wlr_xdg_surface_schedule_configure(c->surface.xdg); } -void motionabsolute(struct wl_listener *listener, void *data) { +static void motionabsolute(struct wl_listener *listener, void *data) { /* This event is forwarded by the cursor when a pointer emits an _absolute_ * motion event, from 0..1 on each axis. This happens, for example, when * wlroots is running under a Wayland window rather than KMS+DRM, and you @@ -2387,7 +2188,7 @@ void motionabsolute(struct wl_listener *listener, void *data) { motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); } -void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, +static void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { double sx = 0, sy = 0, sx_confined, sy_confined; Client *c = NULL, *w = NULL; @@ -2476,7 +2277,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, pointerfocus(c, surface, sx, sy, time); } -void motionrelative(struct wl_listener *listener, void *data) { +static void motionrelative(struct wl_listener *listener, void *data) { /* This event is forwarded by the cursor when a pointer emits a _relative_ * pointer motion event (i.e. a delta) */ struct wlr_pointer_motion_event *event = data; @@ -2489,7 +2290,7 @@ void motionrelative(struct wl_listener *listener, void *data) { event->delta_y, event->unaccel_dx, event->unaccel_dy); } -void moveresize(const Arg *arg) { +static void moveresize(const Arg *arg) { if (cursor_mode != CurNormal && cursor_mode != CurPressed) return; xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); @@ -2523,12 +2324,12 @@ void moveresize(const Arg *arg) { } } -void outputmgrapply(struct wl_listener *listener, void *data) { +static void outputmgrapply(struct wl_listener *listener, void *data) { struct wlr_output_configuration_v1 *config = data; outputmgrapplyortest(config, 0); } -void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, +static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) { /* * Called when a client such as wlr-randr requests a change in output @@ -2590,12 +2391,12 @@ void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, updatemons(NULL, NULL); } -void outputmgrtest(struct wl_listener *listener, void *data) { +static void outputmgrtest(struct wl_listener *listener, void *data) { struct wlr_output_configuration_v1 *config = data; outputmgrapplyortest(config, 1); } -void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, +static void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) { struct timespec now; @@ -2664,7 +2465,7 @@ void printstatus(void) { ipc_socket_send_workspace_event("focus"); } -void powermgrsetmode(struct wl_listener *listener, void *data) { +static void powermgrsetmode(struct wl_listener *listener, void *data) { struct wlr_output_power_v1_set_mode_event *event = data; struct wlr_output_state state; wlr_output_state_init(&state); @@ -2682,7 +2483,7 @@ void powermgrsetmode(struct wl_listener *listener, void *data) { void quit(const Arg *arg) { wl_display_terminate(dpy); } -void rendermon(struct wl_listener *listener, void *data) { +static void rendermon(struct wl_listener *listener, void *data) { /* This function is called every time an output is ready to display a frame, * generally at the output's refresh rate (e.g. 60Hz). */ Monitor *m = wl_container_of(listener, m, frame); @@ -2707,14 +2508,14 @@ skip: wlr_output_state_finish(&pending); } -void requestdecorationmode(struct wl_listener *listener, void *data) { +static void requestdecorationmode(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, set_decoration_mode); if (c->surface.xdg->initialized) wlr_xdg_toplevel_decoration_v1_set_mode( c->decoration, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } -void requeststartdrag(struct wl_listener *listener, void *data) { +static void requeststartdrag(struct wl_listener *listener, void *data) { struct wlr_seat_request_start_drag_event *event = data; if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, event->serial)) @@ -2723,7 +2524,7 @@ void requeststartdrag(struct wl_listener *listener, void *data) { wlr_data_source_destroy(event->drag->source); } -void requestmonstate(struct wl_listener *listener, void *data) { +static void requestmonstate(struct wl_listener *listener, void *data) { struct wlr_output_event_request_state *event = data; wlr_output_commit_state(event->output, event->state); updatemons(NULL, NULL); @@ -2761,7 +2562,7 @@ void resize(Client *c, struct wlr_box geo, int interact) { wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); } -void run(char *startup_cmd) { +static void run(char *startup_cmd) { /* Add a Unix socket to the Wayland display. */ const char *socket = wl_display_add_socket_auto(dpy); if (!socket) @@ -2822,7 +2623,7 @@ void run(char *startup_cmd) { wl_display_run(dpy); } -void setcursor(struct wl_listener *listener, void *data) { +static void setcursor(struct wl_listener *listener, void *data) { /* This event is raised by the seat when a client provides a cursor image */ struct wlr_seat_pointer_request_set_cursor_event *event = data; /* If we're "grabbing" the cursor, don't use the client's image, we will @@ -2840,7 +2641,7 @@ void setcursor(struct wl_listener *listener, void *data) { event->hotspot_y); } -void setcursorshape(struct wl_listener *listener, void *data) { +static void setcursorshape(struct wl_listener *listener, void *data) { struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; if (cursor_mode != CurNormal && cursor_mode != CurPressed) return; @@ -2871,7 +2672,7 @@ void setfloating(Client *c, int floating) { printstatus(); } -void setfullscreen(Client *c, int fullscreen) { +static void setfullscreen(Client *c, int fullscreen) { c->isfullscreen = fullscreen; if (!c->mon || !client_surface(c)->mapped) return; @@ -2897,12 +2698,12 @@ void setfullscreen(Client *c, int fullscreen) { printstatus(); } -void setlayout(const Arg *arg) { +static void setlayout(const Arg *arg) { if (!selmon) return; int ti = current_tag_idx(selmon); /* arg->ui is an index into layouts[]. If out of range, just toggle. */ - if (arg && arg->ui < LENGTH(layouts)) { + if (arg && arg->ui < layout_count) { if (selmon->lt[ti][selmon->sellt[ti]] != &layouts[arg->ui]) selmon->sellt[ti] ^= 1; selmon->lt[ti][selmon->sellt[ti]] = &layouts[arg->ui]; @@ -2911,6 +2712,7 @@ void setlayout(const Arg *arg) { } strncpy(selmon->ltsymbol[ti], selmon->lt[ti][selmon->sellt[ti]]->symbol, LENGTH(selmon->ltsymbol[ti])); + selmon->ltsymbol[ti][LENGTH(selmon->ltsymbol[ti]) - 1] = '\0'; /* Convert windows when switching to master: set focused client as master */ if (curlayout(selmon)->arrange == master) { @@ -2934,7 +2736,7 @@ void setlayout(const Arg *arg) { /* arg > 1.0 will set mfact absolutely */ -void setmon(Client *c, Monitor *m, uint32_t newtags) { +static void setmon(Client *c, Monitor *m, uint32_t newtags) { Monitor *oldmon = c->mon; if (oldmon == m) @@ -2957,7 +2759,7 @@ void setmon(Client *c, Monitor *m, uint32_t newtags) { focusclient(focustop(selmon), 1); } -void setpsel(struct wl_listener *listener, void *data) { +static void setpsel(struct wl_listener *listener, void *data) { /* This event is raised by the seat when a client wants to set the selection, * usually when the user copies something. wlroots allows compositors to * ignore such requests if they so choose, but we always honor them @@ -2966,7 +2768,7 @@ void setpsel(struct wl_listener *listener, void *data) { wlr_seat_set_primary_selection(seat, event->source, event->serial); } -void setsel(struct wl_listener *listener, void *data) { +static void setsel(struct wl_listener *listener, void *data) { /* This event is raised by the seat when a client wants to set the selection, * usually when the user copies something. wlroots allows compositors to * ignore such requests if they so choose, but we always honor them @@ -2975,6 +2777,8 @@ void setsel(struct wl_listener *listener, void *data) { wlr_seat_set_selection(seat, event->source, event->serial); } +// ── Config Reload ─────────────────────────────────────────── + /* Re-apply input settings from newcfg to all connected input devices */ static void reapply_input_config(const Config *newcfg) { PointerDevice *pdev; @@ -3055,7 +2859,7 @@ reapply_monitor_config(void) m->nmaster = r->nmaster; /* Map layout name */ const Layout *mon_layout = &layouts[0]; - for (int li = 0; li < (int)LENGTH(layouts); li++) { + for (int li = 0; li < (int)layout_count; li++) { if (layouts[li].symbol && !strcmp(r->layout, layouts[li].symbol)) { mon_layout = &layouts[li]; break; @@ -3073,7 +2877,7 @@ reapply_monitor_config(void) break; } } - const Layout *alt_layout = &layouts[LENGTH(layouts) > 1 && mon_layout != &layouts[1]]; + const Layout *alt_layout = &layouts[layout_count > 1 && mon_layout != &layouts[1]]; for (int ti = 0; ti < TAGCOUNT; ti++) { m->lt[ti][0] = mon_layout; m->lt[ti][1] = alt_layout; @@ -3168,7 +2972,7 @@ on_config_reload(const Config *newcfg, void *ud) /* Triggered by the IPC 'reload' command - re-reads config from disk */ -static void +void do_reload(void) { Config fresh; @@ -3191,7 +2995,7 @@ do_reload(void) } } -void setup(void) { +static void setup(void) { int drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; sigemptyset(&sa.sa_mask); @@ -3216,6 +3020,7 @@ void setup(void) { if (debug_override) cfg.log_level = WLR_DEBUG; strncpy(config_path, path, sizeof(config_path) - 1); + config_path[sizeof(config_path) - 1] = '\0'; } wlr_log_init(cfg.log_level, NULL); @@ -3457,7 +3262,7 @@ void setup(void) { #endif } -void spawn(const Arg *arg) { +static void spawn(const Arg *arg) { if (fork() == 0) { dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); @@ -3466,7 +3271,7 @@ void spawn(const Arg *arg) { } } -void startdrag(struct wl_listener *listener, void *data) { +static void startdrag(struct wl_listener *listener, void *data) { struct wlr_drag *drag = data; if (!drag->icon) return; @@ -3489,390 +3294,12 @@ void tag(const Arg *arg) { printstatus(); } -void tagmon(const Arg *arg) { +static void tagmon(const Arg *arg) { Client *sel = focustop(selmon); if (sel) setmon(sel, dirtomon(arg->i), 0); } -/* dwindle helpers */ - -/* - * Returns the 0-based index of the lowest set tag bit for monitor m. - * For single-tag views this is the exact tag. For multi-tag views it - * picks the lowest bit */ - -static inline int current_tag_idx(Monitor *m) { - uint32_t tags = m->tagset[m->seltags] & TAGMASK; - if (!tags) - return 0; - int idx = 0; - while (!(tags & 1u)) { - tags >>= 1; - idx++; - } - return idx < TAGCOUNT ? idx : 0; -} - -static DwindleNode *dwindle_new_node(void) { - DwindleNode *n = ecalloc(1, sizeof(*n)); - n->split_ratio = 1.0f; - return n; -} - -void dwindle_free_tree(DwindleNode *n) { - if (!n) - return; - dwindle_free_tree(n->children[0]); - dwindle_free_tree(n->children[1]); - free(n); -} - -static DwindleNode *dwindle_find_leaf(DwindleNode *n, Client *c) { - if (!n) - return NULL; - if (!n->is_node) - return n->client == c ? n : NULL; - DwindleNode *r = dwindle_find_leaf(n->children[0], c); - return r ? r : dwindle_find_leaf(n->children[1], c); -} - -static DwindleNode *dwindle_first_leaf(DwindleNode *n) { - if (!n) - return NULL; - while (n->is_node) - n = n->children[0]; - return n; -} - -/* - * Recursively distribute geometry downward from node n - * gap is inner gap pixels between the two halves - */ - -static void dwindle_recalc(DwindleNode *n, int gap) { - if (!n) - return; - - if (!n->is_node) { - if (n->client && !n->client->isfullscreen) - resize(n->client, n->box, 0); - return; - } - - /* Wider than tall -> split left/right; taller -> split top/bottom. */ - n->split_top = (n->box.height > n->box.width); - - if (!n->split_top) { - int w1 = MAX(1, (int)(n->box.width / 2.0f * n->split_ratio) - gap / 2); - n->children[0]->box = - (struct wlr_box){n->box.x, n->box.y, w1, n->box.height}; - n->children[1]->box = - (struct wlr_box){n->box.x + w1 + gap, n->box.y, - MAX(1, n->box.width - w1 - gap), n->box.height}; - } else { - int h1 = MAX(1, (int)(n->box.height / 2.0f * n->split_ratio) - gap / 2); - n->children[0]->box = - (struct wlr_box){n->box.x, n->box.y, n->box.width, h1}; - n->children[1]->box = - (struct wlr_box){n->box.x, n->box.y + h1 + gap, n->box.width, - MAX(1, n->box.height - h1 - gap)}; - } - - dwindle_recalc(n->children[0], gap); - dwindle_recalc(n->children[1], gap); -} - -/* - * Insert new_c into the tree, bisecting the focused client's node - * It falls back to the first leaf if focused is NULL or not in the tree - */ - -static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused) { - DwindleNode *new_leaf = dwindle_new_node(); - new_leaf->client = new_c; - new_leaf->is_node = 0; - - if (!*root) { - *root = new_leaf; - return; - } - - DwindleNode *opening_on = focused ? dwindle_find_leaf(*root, focused) : NULL; - if (!opening_on) - opening_on = dwindle_first_leaf(*root); - - DwindleNode *new_parent = dwindle_new_node(); - new_parent->is_node = 1; - new_parent->box = opening_on->box; - new_parent->parent = opening_on->parent; - new_parent->split_top = (opening_on->box.height > opening_on->box.width); - new_parent->children[0] = opening_on; - new_parent->children[1] = new_leaf; - - opening_on->parent = new_parent; - new_leaf->parent = new_parent; - - if (new_parent->parent) { - if (new_parent->parent->children[0] == opening_on) - new_parent->parent->children[0] = new_parent; - else - new_parent->parent->children[1] = new_parent; - } else { - *root = new_parent; - } -} - -/* - * Remove client c from the tree, promoting its sibling upward. - */ - -static void dwindle_remove(DwindleNode **root, Client *c) { - DwindleNode *leaf = dwindle_find_leaf(*root, c); - if (!leaf) - return; - - DwindleNode *parent = leaf->parent; - if (!parent) { - free(leaf); - *root = NULL; - return; - } - - DwindleNode *sibling = - (parent->children[0] == leaf) ? parent->children[1] : parent->children[0]; - DwindleNode *grandparent = parent->parent; - - sibling->parent = grandparent; - if (grandparent) { - if (grandparent->children[0] == parent) - grandparent->children[0] = sibling; - else - grandparent->children[1] = sibling; - } else { - *root = sibling; - } - - free(leaf); - free(parent); -} - -/* Remove c from every monitor's per-tag tree and call from unmapnotify(). */ - -static void dwindle_remove_client(Client *c) { - Monitor *m = c->mon; - if (!m) - return; - for (int i = 0; i < TAGCOUNT; i++) - dwindle_remove(&m->dwindle_root[i], c); -} - -/* layout entry point */ - -void dwindle(Monitor *m) { - Client *c; - int n = 0, e; - - wl_list_for_each(c, &clients, link) if (VISIBLEON(c, m) && !c->isfloating && - !c->isfullscreen) n++; - - if (n == 0) - return; - - e = (m->gaps && !(cfg.appearance.smart_gaps && n == 1)) - ? (int)cfg.appearance.gaps - : 0; - - int ti = current_tag_idx(m); - DwindleNode **root = &m->dwindle_root[ti]; - - /* prune leaves whose clients are no longer tiled here */ - { - DwindleNode *stack[512]; - Client *stale[512]; - int sp = 0, sc = 0; - - if (*root) - stack[sp++] = *root; - - while (sp > 0) { - DwindleNode *nd = stack[--sp]; - if (!nd->is_node) { - int found = 0; - wl_list_for_each(c, &clients, link) { - if (c == nd->client && VISIBLEON(c, m) && !c->isfloating) { - found = 1; - break; - } - } - if (!found && sc < 512) - stale[sc++] = nd->client; - } else { - if (nd->children[1]) - stack[sp++] = nd->children[1]; - if (nd->children[0]) - stack[sp++] = nd->children[0]; - } - } - - for (int i = 0; i < sc; i++) - dwindle_remove(root, stale[i]); - } - - /* insert any newly visible client, splitting the focused node. - * Use m->dwindle_focus[ti] so it actually splits what the user - * was looking at when they spawned the window, rather than whatever - * focustop() happens to return */ - - Client *focused = m->dwindle_focus[ti]; - - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) - continue; - if (!dwindle_find_leaf(*root, c)) { - dwindle_insert(root, c, focused); - focused = c; - } - } - - /* assign root box and recurse */ - if (*root) { - (*root)->box = (struct wlr_box){ - m->w.x + e, - m->w.y + e, - MAX(1, m->w.width - 2 * e), - MAX(1, m->w.height - 2 * e), - }; - dwindle_recalc(*root, e); - } -} - -/* master/stack layout */ -static void master_arrange(Monitor *m, int ti) { - Client *c, *master_c = NULL; - Client *stack[256]; - int nstack = 0, n = 0; - - wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && !c->isfloating) { - if (c == m->master_master[ti]) - master_c = c; - if (!c->isfullscreen) - n++; - } - } - - if (n == 0) - return; - - if (!master_c) { - wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { - m->master_master[ti] = c; - master_c = c; - break; - } - } - } - - wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && - c != master_c && nstack < 256) - stack[nstack++] = c; - } - - int e = (m->gaps && !(cfg.appearance.smart_gaps && n == 1)) - ? (int)cfg.appearance.gaps - : 0; - int aw = MAX(1, m->w.width - 2 * e); - int ah = MAX(1, m->w.height - 2 * e); - - if (n == 1) { - if (master_c && !master_c->isfullscreen) - resize(master_c, (struct wlr_box){m->w.x + e, m->w.y + e, aw, ah}, 0); - return; - } - - int master_w = MAX(1, (int)(aw * m->mfact)); - - if (m->master_side[ti] == 0) { - int stack_x = m->w.x + e + master_w + e; - int stack_w = MAX(1, aw - master_w - e); - - if (master_c && !master_c->isfullscreen) - resize(master_c, (struct wlr_box){m->w.x + e, m->w.y + e, master_w, ah}, - 0); - - int sh = MAX(1, (ah - (nstack - 1) * e) / nstack); - for (int i = 0; i < nstack; i++) - resize(stack[i], - (struct wlr_box){stack_x, m->w.y + e + i * (sh + e), stack_w, sh}, - 0); - } else { - int stack_w = MAX(1, aw - master_w - e); - int sh = MAX(1, (ah - (nstack - 1) * e) / nstack); - - for (int i = 0; i < nstack; i++) - resize( - stack[i], - (struct wlr_box){m->w.x + e, m->w.y + e + i * (sh + e), stack_w, sh}, - 0); - - if (master_c && !master_c->isfullscreen) - resize( - master_c, - (struct wlr_box){m->w.x + e + stack_w + e, m->w.y + e, master_w, ah}, - 0); - } -} - -/* layout entry point */ - -void master(Monitor *m) { - int ti = current_tag_idx(m); - master_arrange(m, ti); -} -void monocle(Monitor *m) { - Client *c, *sel = focustop(m); - int n = 0, e; - - wl_list_for_each(c, &clients, link) if (VISIBLEON(c, m) && !c->isfloating && - !c->isfullscreen) n++; - - if (n == 0) - return; - - e = (m->gaps && !(cfg.appearance.smart_gaps && n == 1)) - ? (int)cfg.appearance.gaps - : 0; - - int aw = MAX(1, m->w.width - 2 * e); - int ah = MAX(1, m->w.height - 2 * e); - - /* Stack all windows at the same position and raise the focused one to top */ - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) - continue; - resize(c, (struct wlr_box){m->w.x + e, m->w.y + e, aw, ah}, 0); - wlr_scene_node_set_enabled(&c->scene->node, 0); - } - if (sel) { - wlr_scene_node_set_enabled(&sel->scene->node, 1); - wlr_scene_node_raise_to_top(&sel->scene->node); - } -} -/* Clear master references to client c on all tags of its monitor */ - -static void master_remove_client(Client *c) { - Monitor *m = c->mon; - if (!m) - return; - for (int i = 0; i < TAGCOUNT; i++) { - if (m->master_master[i] == c) - m->master_master[i] = NULL; - } -} - void togglefloating(const Arg *arg) { Client *sel = focustop(selmon); /* return if fullscreen or scratchpad (strictly floating) */ @@ -3886,145 +3313,16 @@ void togglefullscreen(const Arg *arg) { setfullscreen(sel, !sel->isfullscreen); } -void togglegaps(const Arg *arg) { +static void togglegaps(const Arg *arg) { selmon->gaps = !selmon->gaps; arrange(selmon); } -static void -scratchpad_arrange(Monitor *m) -{ - Client *c; - int sw = m->w.width * 0.8; - int sh = m->w.height * 0.8; - int sx = m->w.x + (m->w.width - sw) / 2; - int sy = m->w.y + (m->w.height - sh) / 2; - wl_list_for_each(c, &clients, link) { - if (c->mon != m || !c->isscratchpad) - continue; - wlr_scene_node_reparent(&c->scene->node, layers[LyrScratch]); - resize(c, (struct wlr_box){sx, sy, sw, sh}, 0); - } -} - -void togglescratchpad(const Arg *arg) { - Monitor *m = selmon; - Client *c, *focus = NULL; - - if (!m) - return; - - if (!m->scratchpad_visible) { - /* Save current focus before showing scratchpad */ - m->scratchpad_prev_focus = focustop(m); - } - - m->scratchpad_visible = !m->scratchpad_visible; - - if (m->scratchpad_visible) { - /* Find the most recently focused scratchpad client */ - scratchpad_arrange(m); - wl_list_for_each(c, &fstack, flink) { - if (c->mon == m && c->isscratchpad) { - focus = c; - break; - } - } - /* Show only that one (monocle-like) */ - wl_list_for_each(c, &clients, link) { - if (c->mon == m && c->isscratchpad) { - wlr_scene_node_set_enabled(&c->scene->node, c == focus); - client_set_suspended(c, c != focus); - } - } - m->scratchpad_current = focus; - if (focus) - focusclient(focus, 1); - } else { - /* Hide all scratchpad clients */ - wl_list_for_each(c, &clients, link) { - if (c->mon == m && c->isscratchpad) { - wlr_scene_node_set_enabled(&c->scene->node, 0); - client_set_suspended(c, 1); - } - } - m->scratchpad_current = NULL; - /* Restore focus to previous client */ - if (m->scratchpad_prev_focus && VISIBLEON(m->scratchpad_prev_focus, m)) - focusclient(m->scratchpad_prev_focus, 1); - else - focusclient(focustop(m), 1); - } - printstatus(); -} - -void swapdirscratchpad(const Arg *arg) { - Client *sel = focustop(selmon); - - if (!sel || sel->isfullscreen || !selmon) - return; - - if (sel->isscratchpad) { - /* Move FROM scratchpad to current workspace */ - Client *next = NULL; - sel->isscratchpad = 0; - sel->isfloating = 0; - sel->tags = selmon->tagset[selmon->seltags]; - if (selmon->scratchpad_visible) { - /* Find another scratchpad client to show */ - wl_list_for_each(next, &fstack, flink) { - if (next != sel && next->isscratchpad && next->mon == selmon) { - scratchpad_arrange(selmon); - break; - } - } - if (!next) - selmon->scratchpad_current = NULL; - else - selmon->scratchpad_current = next; - } else { - selmon->scratchpad_current = NULL; - } - /* setfloating reparents + arranges; let it manage visibility */ - setfloating(sel, 0); - if (next) - focusclient(next, 1); - else - focusclient(focustop(selmon), 1); - } else { - /* Move TO scratchpad */ - Client *old = selmon->scratchpad_current; - dwindle_remove_client(sel); - master_remove_client(sel); - sel->isscratchpad = 1; - sel->isfloating = 1; - setfloating(sel, 1); - if (selmon->scratchpad_visible) { - /* Hide previous scratchpad client if different */ - if (old && old != sel) { - wlr_scene_node_set_enabled(&old->scene->node, 0); - client_set_suspended(old, 1); - } - scratchpad_arrange(selmon); - wlr_scene_node_set_enabled(&sel->scene->node, 1); - client_set_suspended(sel, 0); - selmon->scratchpad_current = sel; - focusclient(sel, 1); - } else { - wlr_scene_node_set_enabled(&sel->scene->node, 0); - client_set_suspended(sel, 1); - arrange(selmon); - focusclient(focustop(selmon), 1); - } - } - - printstatus(); -} /* swapdir is essentially focusdir but swap */ -void swapdir(const Arg *arg) { +static void swapdir(const Arg *arg) { Client *c, *other = NULL; Client *sel = focustop(selmon); int sx, sy, cx, cy; @@ -4131,38 +3429,7 @@ void swapdir(const Arg *arg) { printstatus(); } -void swaptiled(Client *a, Client *b) { - Monitor *m; - if (!a || !b || a == b) - return; - - wl_list_for_each(m, &mons, link) { - for (int i = 0; i < TAGCOUNT; i++) { - DwindleNode **root = &m->dwindle_root[i]; - if (!*root) - continue; - DwindleNode *la = dwindle_find_leaf(*root, a); - DwindleNode *lb = dwindle_find_leaf(*root, b); - if (!la || !lb) - continue; - la->client = b; - lb->client = a; - int e = (m->gaps && cfg.appearance.gaps) ? (int)cfg.appearance.gaps : 0; - (*root)->box = (struct wlr_box){ - m->w.x + e, - m->w.y + e, - MAX(1, m->w.width - 2 * e), - MAX(1, m->w.height - 2 * e), - }; - dwindle_recalc(*root, e); - } - } - - focusclient(a, 1); - printstatus(); -} - -void toggletag(const Arg *arg) { +static void toggletag(const Arg *arg) { uint32_t newtags; Client *sel = focustop(selmon); if (!sel || sel->isscratchpad || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) @@ -4188,12 +3455,12 @@ void toggleview(const Arg *arg) { printstatus(); } -void unlocksession(struct wl_listener *listener, void *data) { +static void unlocksession(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, unlock); destroylock(lock, 1); } -void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { +static void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, unmap); l->mapped = 0; @@ -4207,7 +3474,7 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { motionnotify(0, NULL, 0, 0, 0, 0); } -void unmapnotify(struct wl_listener *listener, void *data) { +static void unmapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is unmapped, and should no longer be shown. */ Client *c = wl_container_of(listener, c, unmap); if (c == grabc) { @@ -4259,7 +3526,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { motionnotify(0, NULL, 0, 0, 0, 0); } -void updatemons(struct wl_listener *listener, void *data) { +static void updatemons(struct wl_listener *listener, void *data) { /* * Called whenever the output layout changes: adding or removing a * monitor, changing an output's mode or position, etc. This is where @@ -4357,7 +3624,7 @@ void updatemons(struct wl_listener *listener, void *data) { wlr_output_manager_v1_set_configuration(output_mgr, config); } -void updatetitle(struct wl_listener *listener, void *data) { +static void updatetitle(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, set_title); if (c == focustop(c->mon)) { printstatus(); @@ -4365,7 +3632,7 @@ void updatetitle(struct wl_listener *listener, void *data) { } } -void urgent(struct wl_listener *listener, void *data) { +static void urgent(struct wl_listener *listener, void *data) { struct wlr_xdg_activation_v1_request_activate_event *event = data; Client *c = NULL; toplevel_from_wlr_surface(event->surface, &c, NULL); @@ -4390,7 +3657,7 @@ void view(const Arg *arg) { printstatus(); } -void virtualkeyboard(struct wl_listener *listener, void *data) { +static void virtualkeyboard(struct wl_listener *listener, void *data) { struct wlr_virtual_keyboard_v1 *kb = data; /* virtual keyboards shouldn't share keyboard group */ KeyboardGroup *group = createkeyboardgroup(); @@ -4403,7 +3670,7 @@ void virtualkeyboard(struct wl_listener *listener, void *data) { wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); } -void virtualpointer(struct wl_listener *listener, void *data) { +static void virtualpointer(struct wl_listener *listener, void *data) { struct wlr_virtual_pointer_v1_new_pointer_event *event = data; struct wlr_input_device *device = &event->new_pointer->pointer.base; @@ -4417,7 +3684,7 @@ Monitor *xytomon(double x, double y) { return o ? o->data : NULL; } -void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, +static void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny) { struct wlr_scene_node *node, *pnode; struct wlr_surface *surface = NULL; @@ -4451,7 +3718,7 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, } #ifdef XWAYLAND -void activatex11(struct wl_listener *listener, void *data) { +static void activatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, activate); /* Only "managed" windows can be activated */ @@ -4459,14 +3726,14 @@ void activatex11(struct wl_listener *listener, void *data) { wlr_xwayland_surface_activate(c->surface.xwayland, 1); } -void associatex11(struct wl_listener *listener, void *data) { +static void associatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, associate); LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); } -void configurex11(struct wl_listener *listener, void *data) { +static void configurex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, configure); struct wlr_xwayland_surface_configure_event *event = data; if (!client_surface(c) || !client_surface(c)->mapped) { @@ -4492,7 +3759,7 @@ void configurex11(struct wl_listener *listener, void *data) { } } -void createnotifyx11(struct wl_listener *listener, void *data) { +static void createnotifyx11(struct wl_listener *listener, void *data) { struct wlr_xwayland_surface *xsurface = data; Client *c; @@ -4514,13 +3781,13 @@ void createnotifyx11(struct wl_listener *listener, void *data) { LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); } -void dissociatex11(struct wl_listener *listener, void *data) { +static void dissociatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, dissociate); wl_list_remove(&c->map.link); wl_list_remove(&c->unmap.link); } -void sethints(struct wl_listener *listener, void *data) { +static void sethints(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, set_hints); struct wlr_surface *surface = client_surface(c); if (c == focustop(selmon) || !c->surface.xwayland->hints) @@ -4533,7 +3800,7 @@ void sethints(struct wl_listener *listener, void *data) { client_set_border_color(c, cfg.appearance.urgent_color); } -void xwaylandready(struct wl_listener *listener, void *data) { +static void xwaylandready(struct wl_listener *listener, void *data) { struct wlr_xcursor *xcursor; struct wlr_xcursor_image *image; @@ -4549,6 +3816,8 @@ void xwaylandready(struct wl_listener *listener, void *data) { } #endif +// ── main() ────────────────────────────────────────────────── + int main(int argc, char *argv[]) { char *startup_cmd = NULL; int c; diff --git a/src/scratchpad.c b/src/scratchpad.c new file mode 100644 index 0000000..00309b7 --- /dev/null +++ b/src/scratchpad.c @@ -0,0 +1,236 @@ +/* + * See LICENSE file for copyright and license details. + * PeachWM is a fork of swindle, which is also GPLv3 licensed. + * + * Scratchpad workspace — a hidden layer of clients that can be toggled + * as a group, with monocle-style cycling within the scratchpad. + */ +#include +#include +#include + +#include "client.h" +#include "layout.h" +#include "monitor.h" +#include "scratchpad.h" + +/* ------------------------------------------------------------------ */ +/* Visibility helper — duplicated from layout.c / peachwm.c */ +/* ------------------------------------------------------------------ */ + +int visibleon(Client *c, Monitor *m) { + return m && c->mon == m + ? (c->isscratchpad ? m->scratchpad_visible + : (int)(c->tags & m->tagset[m->seltags])) + : 0; +} + +/* ------------------------------------------------------------------ */ +/* Extern functions from peachwm.c / layout.c / ipc client */ +/* ------------------------------------------------------------------ */ + +void arrange(Monitor *m); +void focusclient(Client *c, int lift); +Client *focustop(Monitor *m); +void printstatus(void); +void resize(Client *c, struct wlr_box geo, int interact); +void setfloating(Client *c, int floating); +void client_set_suspended(Client *c, int suspended); +void dwindle_remove_client(Client *c); +void master_remove_client(Client *c); + +/* ------------------------------------------------------------------ */ +/* scratchpad_arrange — centre & resize all scratchpad clients */ +/* ------------------------------------------------------------------ */ + +void scratchpad_arrange(Monitor *m) { + Client *c; + int sw = m->w.width * 0.8; + int sh = m->w.height * 0.8; + int sx = m->w.x + (m->w.width - sw) / 2; + int sy = m->w.y + (m->w.height - sh) / 2; + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !c->isscratchpad) + continue; + wlr_scene_node_reparent(&c->scene->node, layers[LyrScratch]); + resize(c, (struct wlr_box){sx, sy, sw, sh}, 0); + } +} + +/* ------------------------------------------------------------------ */ +/* togglescratchpad — show / hide the scratchpad workspace */ +/* ------------------------------------------------------------------ */ + +void togglescratchpad(const Arg *arg) { + Monitor *m = selmon; + Client *c, *focus = NULL; + + if (!m) + return; + + if (!m->scratchpad_visible) { + /* Save current focus before showing scratchpad */ + m->scratchpad_prev_focus = focustop(m); + } + + m->scratchpad_visible = !m->scratchpad_visible; + + if (m->scratchpad_visible) { + /* Find the most recently focused scratchpad client */ + scratchpad_arrange(m); + wl_list_for_each(c, &fstack, flink) { + if (c->mon == m && c->isscratchpad) { + focus = c; + break; + } + } + /* Show only that one (monocle-like) */ + wl_list_for_each(c, &clients, link) { + if (c->mon == m && c->isscratchpad) { + wlr_scene_node_set_enabled(&c->scene->node, c == focus); + client_set_suspended(c, c != focus); + } + } + m->scratchpad_current = focus; + if (focus) + focusclient(focus, 1); + } else { + /* Hide all scratchpad clients */ + wl_list_for_each(c, &clients, link) { + if (c->mon == m && c->isscratchpad) { + wlr_scene_node_set_enabled(&c->scene->node, 0); + client_set_suspended(c, 1); + } + } + m->scratchpad_current = NULL; + /* Restore focus to previous client */ + if (m->scratchpad_prev_focus && VISIBLEON(m->scratchpad_prev_focus, m)) + focusclient(m->scratchpad_prev_focus, 1); + else + focusclient(focustop(m), 1); + } + printstatus(); +} + +/* ------------------------------------------------------------------ */ +/* swapdirscratchpad — move client into / out of scratchpad */ +/* ------------------------------------------------------------------ */ + +void swapdirscratchpad(const Arg *arg) { + Client *sel = focustop(selmon); + + if (!sel || sel->isfullscreen || !selmon) + return; + + if (sel->isscratchpad) { + /* Move FROM scratchpad to current workspace */ + Client *next = NULL; + sel->isscratchpad = 0; + sel->isfloating = 0; + sel->tags = selmon->tagset[selmon->seltags]; + if (selmon->scratchpad_visible) { + /* Find another scratchpad client to show */ + wl_list_for_each(next, &fstack, flink) { + if (next != sel && next->isscratchpad && next->mon == selmon) { + scratchpad_arrange(selmon); + break; + } + } + if (!next) + selmon->scratchpad_current = NULL; + else + selmon->scratchpad_current = next; + } else { + selmon->scratchpad_current = NULL; + } + /* setfloating reparents + arranges; let it manage visibility */ + setfloating(sel, 0); + if (next) + focusclient(next, 1); + else + focusclient(focustop(selmon), 1); + } else { + /* Move TO scratchpad */ + Client *old = selmon->scratchpad_current; + dwindle_remove_client(sel); + master_remove_client(sel); + sel->isscratchpad = 1; + sel->isfloating = 1; + setfloating(sel, 1); + if (selmon->scratchpad_visible) { + /* Hide previous scratchpad client if different */ + if (old && old != sel) { + wlr_scene_node_set_enabled(&old->scene->node, 0); + client_set_suspended(old, 1); + } + scratchpad_arrange(selmon); + wlr_scene_node_set_enabled(&sel->scene->node, 1); + client_set_suspended(sel, 0); + selmon->scratchpad_current = sel; + focusclient(sel, 1); + } else { + wlr_scene_node_set_enabled(&sel->scene->node, 0); + client_set_suspended(sel, 1); + arrange(selmon); + focusclient(focustop(selmon), 1); + } + } + + printstatus(); +} + +/* ------------------------------------------------------------------ */ +/* scratchpad_focusdir_cycle — cycle focus among scratchpad clients */ +/* Called from focusdir() when the focused client is a scratchpad and */ +/* the scratchpad workspace is visible. */ +/* ------------------------------------------------------------------ */ + +void scratchpad_focusdir_cycle(const Arg *arg) { + Client *sel = focustop(selmon); + Client *c, *first = NULL, *next = NULL, *prev = NULL; + int found = 0; + + wl_list_for_each(c, &clients, link) { + if (!c->isscratchpad || c->mon != selmon) + continue; + if (!first) + first = c; + if (found) { + next = c; + break; + } + if (c == sel) { + found = 1; + continue; + } + prev = c; + } + + Client *target = NULL; + if (arg->i == WLR_DIRECTION_DOWN || arg->i == WLR_DIRECTION_RIGHT) { + if (next) + target = next; + else if (first) + target = first; + } else { + if (prev) + target = prev; + else { + /* wrap to last */ + wl_list_for_each(c, &clients, link) + if (c->isscratchpad && c->mon == selmon) + target = c; + } + } + + if (target && target != sel) { + if (selmon->scratchpad_current) + wlr_scene_node_set_enabled( + &selmon->scratchpad_current->scene->node, 0); + wlr_scene_node_set_enabled(&target->scene->node, 1); + client_set_suspended(target, 0); + selmon->scratchpad_current = target; + focusclient(target, 1); + } +} diff --git a/src/scratchpad.h b/src/scratchpad.h new file mode 100644 index 0000000..54292e7 --- /dev/null +++ b/src/scratchpad.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include "client.h" +#include "monitor.h" + +/* Forward declarations from peachwm.c */ +extern struct wl_list clients; +extern struct wl_list fstack; +extern Monitor *selmon; + +/* The scene layer trees — defined in peachwm.c. + * We declare the full size so callers can index with + * any Lyr* constant. */ +#define NUM_LAYERS 9 +extern struct wlr_scene_tree *layers[]; + +/* Scene layer indices */ +enum { + LyrBg, /* 0 */ + LyrBottom, /* 1 */ + LyrTile, /* 2 */ + LyrFloat, /* 3 */ + LyrScratch, /* 4 */ + LyrTop, /* 5 */ + LyrFS, /* 6 */ + LyrOverlay, /* 7 */ + LyrBlock, /* 8 */ + /* NUM_LAYERS = 9 */ +}; + +/* The argument union used by keybind functions. + * Both peachwm.c and the scratchpad module need to agree on this, + * so it sits here rather than privately in each .c file. */ +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; + +/* Scoped visibility helper — also defined (identically) in layout.c */ +int visibleon(Client *c, Monitor *m); +#define VISIBLEON(C, M) visibleon((C), (M)) + +/* ----------- Extracted scratchpad functions ----------- */ + +/* Reparent + resize every scratchpad client of @m to a centred 80% box */ +void scratchpad_arrange(Monitor *m); + +/* Toggle the scratchpad workspace on/off */ +void togglescratchpad(const Arg *arg); + +/* Move focused client into/out-of scratchpad. + * When inside scratchpad, pops it out to the current workspace. + * When outside, moves it into the scratchpad. */ +void swapdirscratchpad(const Arg *arg); + +/* Cycle focus among scratchpad clients (used by focusdir). */ +void scratchpad_focusdir_cycle(const Arg *arg); diff --git a/src/util.c b/src/util.c index 702805a..db23223 100644 --- a/src/util.c +++ b/src/util.c @@ -30,6 +30,7 @@ ecalloc(size_t nmemb, size_t size) { void *p; + if (!nmemb || !size) { nmemb = 1; size = 1; } if (!(p = calloc(nmemb, size))) die("calloc:"); return p; diff --git a/src/util.h b/src/util.h index 5a631e4..e26866e 100644 --- a/src/util.h +++ b/src/util.h @@ -1,5 +1,7 @@ /* Copyright and license details as described in the LICENSE file. */ +#include + _Noreturn void die(const char *fmt, ...); void *ecalloc(size_t nmemb, size_t size); int fd_set_nonblock(int fd);