Massive code refactor

This commit is contained in:
2026-07-02 01:24:16 -04:00
parent c10722aa69
commit b5ccb2621b
22 changed files with 3277 additions and 2760 deletions
+3
View File
@@ -53,3 +53,6 @@ dkms.conf
# debug information files # debug information files
*.dwo *.dwo
.opencode/
.omo/
+22 -3
View File
@@ -78,12 +78,12 @@ debug: peachwm peachmsg/peachmsg
# Compositor # Compositor
peachwm: src/peachwm.o src/util.o parser/parser.o \ peachwm: src/peachwm.o src/scratchpad.o src/layout.o src/util.o src/client.o src/ipc.o src/ipc_socket.o \
src/wlr_ext_workspace_v1.o $(PROTO_OBJS) parser/parser.o src/ext_workspace.o src/wlr_ext_workspace_v1.o $(PROTO_OBJS)
$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ $(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
src/peachwm.o: src/peachwm.c include/client.h include/ipc.h \ 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 \ src/wlr_ext_workspace_v1.h parser/parser.h \
protocols/peachwm-ipc-unstable-v2-protocol.h \ protocols/peachwm-ipc-unstable-v2-protocol.h \
protocols/ext-workspace-v1-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 protocols/xdg-shell-protocol.h
$(CC) $(CFLAGS) -o $@ -c $< $(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 src/util.o: src/util.c src/util.h
$(CC) $(CFLAGS) -o $@ -c $< $(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.o: src/wlr_ext_workspace_v1.c \
src/wlr_ext_workspace_v1.h protocols/ext-workspace-v1-protocol.h src/wlr_ext_workspace_v1.h protocols/ext-workspace-v1-protocol.h
$(CC) $(CFLAGS) -o $@ -c $< $(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 parser/parser.o: parser/parser.c parser/parser.h
$(CC) $(CFLAGS) -o $@ -c $< $(CC) $(CFLAGS) -o $@ -c $<
+2 -2
View File
@@ -12,9 +12,9 @@
## peachwm ## 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. 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). PeachWM is maintained by [HuntedByTheIRS](https://github.com/HuntedByTheIRS).
+97 -395
View File
@@ -1,404 +1,106 @@
/* #ifndef PEACHWM_CLIENT_H
* Attempt to consolidate unavoidable suck into one file, away from peachwm.c. This #define PEACHWM_CLIENT_H
* 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.
*/
/* Leave these functions first; they're used in the others */ #include <wayland-server-core.h>
static inline int #include <wlr/util/box.h>
client_is_x11(Client *c) #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 #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 #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 * typedef struct {
client_surface(Client *c) /* Must keep this field first */
{ unsigned int type; /* LayerShell */
#ifdef XWAYLAND
if (client_is_x11(c))
return c->surface.xwayland->surface;
#endif
return c->surface.xdg->surface;
}
static inline int Monitor *mon;
toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) struct wlr_scene_tree *scene;
{ struct wlr_scene_tree *popups;
struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; struct wlr_scene_layer_surface_v1 *scene_layer;
struct wlr_surface *root_surface; struct wl_list link;
int mapped;
struct wlr_layer_surface_v1 *layer_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) struct wl_listener destroy;
return -1; struct wl_listener unmap;
root_surface = wlr_surface_get_root_surface(s); struct wl_listener surface_commit;
} LayerSurface;
#ifdef XWAYLAND /* Function declarations */
if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { int client_is_x11(Client *c);
c = xsurface->data; struct wlr_surface *client_surface(Client *c);
type = c->type; int toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl);
goto end; void client_activate_surface(struct wlr_surface *s, int activated);
} uint32_t client_set_bounds(Client *c, int32_t width, int32_t height);
#endif 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))) { #endif /* PEACHWM_CLIENT_H */
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;
}
+23
View File
@@ -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 */
+30 -272
View File
@@ -1,18 +1,36 @@
#pragma once
#include "peachwm-ipc-unstable-v2-protocol.h" #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); /* Forward declarations from peachwm.c */
static void focusclient(Client *c, int lift);
static Client *focustop(Monitor *m);
static void printstatus(void);
/* 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; /* Functions from peachwm.c called by IPC */
/* All bound manager objects */ void arrange(Monitor *m);
static struct wl_list ipc_managers; /* IpcManager.link */ 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 { typedef struct {
struct wl_resource *resource; struct wl_resource *resource;
struct wl_list link; /* ipc_managers */ struct wl_list link; /* ipc_managers */
@@ -25,266 +43,6 @@ typedef struct {
struct wl_list link; /* IpcManager.outputs */ struct wl_list link; /* IpcManager.outputs */
} IpcOutput; } IpcOutput;
/* helpers */ /* Public IPC API */
void ipc_printstatus(void);
static IpcOutput * void ipc_init(void);
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);
}
+14 -1159
View File
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
#ifndef PEACHWM_MONITOR_H
#define PEACHWM_MONITOR_H
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_session_lock_v1.h>
#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 */
+5 -6
View File
@@ -34,7 +34,7 @@ extern struct wl_list clients;
/* Internal watcher state */ /* Internal watcher state */
typedef struct { typedef struct WatchState {
int inotify_fd; int inotify_fd;
int watch_fd; int watch_fd;
char path[1024]; char path[1024];
@@ -802,7 +802,7 @@ watch_dispatch(int fd, uint32_t mask, void *data)
return 0; return 0;
} }
struct wl_event_source * WatchState *
config_watch_start(struct wl_event_loop *loop, const char *path, config_watch_start(struct wl_event_loop *loop, const char *path,
config_reload_cb cb, void *userdata) 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; ws->event_src = src;
fprintf(stderr, "peachwm config: watching '%s'\n", path); fprintf(stderr, "peachwm config: watching '%s'\n", path);
return (struct wl_event_source *)ws; return ws;
} }
void void
config_watch_stop(struct wl_event_source *src) config_watch_stop(WatchState *ws)
{ {
if (!src) return; if (!ws) return;
WatchState *ws = (WatchState *)src;
wl_event_source_remove(ws->event_src); wl_event_source_remove(ws->event_src);
close(ws->inotify_fd); close(ws->inotify_fd);
free(ws); free(ws);
+4 -2
View File
@@ -149,13 +149,15 @@ void config_autostart_run(const Config *cfg);
typedef void (*config_reload_cb)(const Config *cfg, void *userdata); 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, struct wl_event_loop *loop,
const char *path, const char *path,
config_reload_cb cb, config_reload_cb cb,
void *userdata void *userdata
); );
void config_watch_stop(struct wl_event_source *src); void config_watch_stop(WatchState *ws);
#endif #endif
+37 -14
View File
@@ -80,8 +80,8 @@ static void
peachwm_ipc_output_toggle_visibility(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_output) peachwm_ipc_output_toggle_visibility(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_output)
{ {
if (!vflag) return; if (!vflag) return;
char *oname = data; char *oname = data ? (char *)data : "";
if (oname) printf("%s ", oname); printf("%s ", oname);
printf("toggle\n"); printf("toggle\n");
} }
@@ -89,15 +89,25 @@ static void
peachwm_ipc_output_active(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_output, peachwm_ipc_output_active(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_output,
uint32_t active) uint32_t active)
{ {
if (active && (!output_name || !*output_name)) if (active && (!output_name || !*output_name)) {
output_name = strdup(data); if (data) {
output_name = strdup(data);
if (!output_name)
fprintf(stderr, "peachmsg: strdup failed\n");
}
}
if (active) { if (active) {
free(focused_output); 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; if (!oflag) return;
char *oname = data; char *oname = data ? (char *)data : "";
if (oname) printf("%s ", oname); printf("%s ", oname);
printf("selmon %u\n", !!active); 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<<tag; if (state == ZPEACHWM_IPC_OUTPUT_V2_TAG_STATE_ACTIVE) urg |= 1<<tag;
if (clients > 0) occ |= 1<<tag; if (clients > 0) occ |= 1<<tag;
if (!(mode & GET)) return; if (!(mode & GET)) return;
if (data) printf("%s ", (char *)data); char *oname = data ? (char *)data : "";
printf("%s ", oname);
printf("tag %u %u %u %u\n", tag, state, clients, focused); printf("tag %u %u %u %u\n", tag, state, clients, focused);
} }
@@ -125,7 +136,8 @@ peachwm_ipc_output_layout_symbol(void *data, struct zpeachwm_ipc_output_v2 *peac
const char *layout) const char *layout)
{ {
if (!(lflag && mode & GET)) return; if (!(lflag && mode & GET)) return;
if (data) printf("%s ", (char *)data); char *oname = data ? (char *)data : "";
printf("%s ", oname);
printf("layout %s\n", layout); printf("layout %s\n", layout);
} }
@@ -134,7 +146,8 @@ peachwm_ipc_output_title(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_
const char *title) const char *title)
{ {
if (!(cflag && mode & GET)) return; if (!(cflag && mode & GET)) return;
if (data) printf("%s ", (char *)data); char *oname = data ? (char *)data : "";
printf("%s ", oname);
printf("title %s\n", title); printf("title %s\n", title);
} }
@@ -143,7 +156,8 @@ peachwm_ipc_output_appid(void *data, struct zpeachwm_ipc_output_v2 *peachwm_ipc_
const char *appid) const char *appid)
{ {
if (!(cflag && mode & GET)) return; if (!(cflag && mode & GET)) return;
if (data) printf("%s ", (char *)data); char *oname = data ? (char *)data : "";
printf("%s ", oname);
printf("appid %s\n", appid); printf("appid %s\n", appid);
} }
@@ -152,7 +166,8 @@ peachwm_ipc_output_fullscreen(void *data, struct zpeachwm_ipc_output_v2 *peachwm
uint32_t is_fullscreen) uint32_t is_fullscreen)
{ {
if (!mflag) return; if (!mflag) return;
if (data) printf("%s ", (char *)data); char *oname = data ? (char *)data : "";
printf("%s ", oname);
printf("fullscreen %u\n", is_fullscreen); printf("fullscreen %u\n", is_fullscreen);
} }
@@ -161,7 +176,8 @@ peachwm_ipc_output_floating(void *data, struct zpeachwm_ipc_output_v2 *peachwm_i
uint32_t is_floating) uint32_t is_floating)
{ {
if (!fflag) return; if (!fflag) return;
if (data) printf("%s ", (char *)data); char *oname = data ? (char *)data : "";
printf("%s ", oname);
printf("floating %u\n", is_floating); printf("floating %u\n", is_floating);
} }
@@ -280,6 +296,8 @@ wl_output_name(void *data, struct wl_output *output, const char *name)
if (outputs) { if (outputs) {
struct output *o = (struct output *)data; struct output *o = (struct output *)data;
o->output_name = strdup(name); o->output_name = strdup(name);
if (!o->output_name)
fprintf(stderr, "peachmsg: strdup failed\n");
printf("+ "); printf("+ ");
} }
if (Oflag) printf("%s\n", name); if (Oflag) printf("%s\n", name);
@@ -340,7 +358,12 @@ global_add(void *data, struct wl_registry *wl_registry,
if (!outputs) return; if (!outputs) return;
if (outputcount >= outputs_buflen) { if (outputcount >= outputs_buflen) {
outputs_buflen *= 2; 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; outputs[outputcount].name = name;
outputcount++; outputcount++;
+426
View File
@@ -0,0 +1,426 @@
/*
* Client function implementations.
* Originally inline in include/client.h, moved here for separate compilation.
*/
#include <errno.h>
#include <math.h>
#include <signal.h>
#include <sys/wait.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_fractional_scale_v1.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/edges.h>
#ifdef XWAYLAND
#include <wlr/xwayland.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#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;
}
+32 -26
View File
@@ -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 * ext_workspace.c — protocol glue for wlr_ext_workspace_v1
* by actually writing the thing, it didn't work the first *
* few tries and I barely have any idea how this code works anyway. * Thanks DreamMaoMao for doing most of the heavy lifting
* To say that it's the eighth wonder of the world * 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 * would be an understatement
*/ */
#include <stdlib.h>
#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 \ #define LENGTH(X) (sizeof X / sizeof X[0])
(EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE | \ #define TAGMASK ((1u << TAGCOUNT) - 1)
EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE)
/* types */ /* Forward declarations from peachwm.c */
typedef union {
int i;
uint32_t ui;
float f;
const void *v;
} Arg;
struct ext_workspace { void view(const Arg *arg);
struct wl_list link; void toggleview(const Arg *arg);
uint32_t tag;
Monitor *m;
struct wlr_ext_workspace_handle_v1 *handle;
};
/* global */ /* Global state */
struct wlr_ext_workspace_manager_v1 *ext_workspace_manager;
static struct wlr_ext_workspace_manager_v1 *ext_workspace_manager; struct wl_list ext_workspaces;
static struct wl_list ext_workspaces;
static struct wl_listener ext_commit_listener; static struct wl_listener ext_commit_listener;
static struct wl_listener ext_destroy_listener; static struct wl_listener ext_destroy_listener;
/* helpers */ /* helpers */
static int static int
get_tag_status(uint32_t tag, Monitor *m) get_tag_status(uint32_t tag, Monitor *m)
{ {
@@ -90,7 +96,7 @@ ext_workspace_create(uint32_t tag, Monitor *m)
/* monitor integration */ /* monitor integration */
static void void
ext_workspace_createmon(Monitor *m) ext_workspace_createmon(Monitor *m)
{ {
uint32_t i; uint32_t i;
@@ -104,7 +110,7 @@ ext_workspace_createmon(Monitor *m)
ext_workspace_create(i, m); ext_workspace_create(i, m);
} }
static void void
ext_workspace_cleanupmon(Monitor *m) ext_workspace_cleanupmon(Monitor *m)
{ {
struct ext_workspace *ws, *tmp; struct ext_workspace *ws, *tmp;
@@ -120,7 +126,7 @@ ext_workspace_cleanupmon(Monitor *m)
/* status */ /* status */
static void void
ext_workspace_printstatus(Monitor *m) ext_workspace_printstatus(Monitor *m)
{ {
struct ext_workspace *ws; struct ext_workspace *ws;
@@ -194,7 +200,7 @@ handle_ext_destroy(struct wl_listener *listener, void *data)
wl_list_remove(&ext_destroy_listener.link); wl_list_remove(&ext_destroy_listener.link);
} }
static void void
workspaces_init(void) workspaces_init(void)
{ {
ext_workspace_manager = wlr_ext_workspace_manager_v1_create(dpy, 1); ext_workspace_manager = wlr_ext_workspace_manager_v1_create(dpy, 1);
+276
View File
@@ -0,0 +1,276 @@
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_output.h>
#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);
}
+1237
View File
File diff suppressed because it is too large Load Diff
+519
View File
@@ -0,0 +1,519 @@
/*
* Layout algorithms extracted from peachwm.c
* Dwindle, Master/Stack, Monocle, and associated utilities.
*/
#include <stddef.h>
#include <stdlib.h>
#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();
}
+43
View File
@@ -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 */
+150 -881
View File
File diff suppressed because it is too large Load Diff
+236
View File
@@ -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 <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h>
#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);
}
}
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <wlr/types/wlr_scene.h>
#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);
+1
View File
@@ -30,6 +30,7 @@ ecalloc(size_t nmemb, size_t size)
{ {
void *p; void *p;
if (!nmemb || !size) { nmemb = 1; size = 1; }
if (!(p = calloc(nmemb, size))) if (!(p = calloc(nmemb, size)))
die("calloc:"); die("calloc:");
return p; return p;
+2
View File
@@ -1,5 +1,7 @@
/* Copyright and license details as described in the LICENSE file. */ /* Copyright and license details as described in the LICENSE file. */
#include <stddef.h>
_Noreturn void die(const char *fmt, ...); _Noreturn void die(const char *fmt, ...);
void *ecalloc(size_t nmemb, size_t size); void *ecalloc(size_t nmemb, size_t size);
int fd_set_nonblock(int fd); int fd_set_nonblock(int fd);