master ohmu/leeloo / src / client.h
ohmu  ·  2026-10-18
  1/*
  2 * Attempt to consolidate unavoidable suck into one file, away from leeloo.c.  This
  3 * file is not meant to be pretty.  We use a .h file with static inline
  4 * functions instead of a separate .c module, or function pointers like sway, so
  5 * that they will simply compile out if the chosen #defines leave them unused.
  6 */
  7
  8/* Leave these functions first; they're used in the others */
  9static inline int
 10client_is_x11(Client *c)
 11{
 12#ifdef XWAYLAND
 13	return c->type == X11;
 14#endif
 15	return 0;
 16}
 17
 18static inline struct wlr_surface *
 19client_surface(Client *c)
 20{
 21#ifdef XWAYLAND
 22	if (client_is_x11(c))
 23		return c->surface.xwayland->surface;
 24#endif
 25	return c->surface.xdg->surface;
 26}
 27
 28static inline int
 29toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl)
 30{
 31	struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface;
 32	struct wlr_surface *root_surface;
 33	struct wlr_layer_surface_v1 *layer_surface;
 34	Client *c = NULL;
 35	LayerSurface *l = NULL;
 36	int type = -1;
 37#ifdef XWAYLAND
 38	struct wlr_xwayland_surface *xsurface;
 39#endif
 40
 41	if (!s)
 42		return -1;
 43	root_surface = wlr_surface_get_root_surface(s);
 44
 45#ifdef XWAYLAND
 46	if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) {
 47		c = xsurface->data;
 48		type = c->type;
 49		goto end;
 50	}
 51#endif
 52
 53	if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) {
 54		l = layer_surface->data;
 55		type = LayerShell;
 56		goto end;
 57	}
 58
 59	xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface);
 60	while (xdg_surface) {
 61		tmp_xdg_surface = NULL;
 62		switch (xdg_surface->role) {
 63		case WLR_XDG_SURFACE_ROLE_POPUP:
 64			if (!xdg_surface->popup || !xdg_surface->popup->parent)
 65				return -1;
 66
 67			tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent);
 68
 69			if (!tmp_xdg_surface)
 70				return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl);
 71
 72			xdg_surface = tmp_xdg_surface;
 73			break;
 74		case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
 75			c = xdg_surface->data;
 76			type = c->type;
 77			goto end;
 78		case WLR_XDG_SURFACE_ROLE_NONE:
 79			return -1;
 80		}
 81	}
 82
 83end:
 84	if (pl)
 85		*pl = l;
 86	if (pc)
 87		*pc = c;
 88	return type;
 89}
 90
 91/* The others */
 92static inline void
 93client_activate_surface(struct wlr_surface *s, int activated)
 94{
 95	struct wlr_xdg_toplevel *toplevel;
 96#ifdef XWAYLAND
 97	struct wlr_xwayland_surface *xsurface;
 98	if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) {
 99		wlr_xwayland_surface_activate(xsurface, activated);
100		return;
101	}
102#endif
103	if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s)))
104		wlr_xdg_toplevel_set_activated(toplevel, activated);
105}
106
107static inline uint32_t
108client_set_bounds(Client *c, int32_t width, int32_t height)
109{
110#ifdef XWAYLAND
111	if (client_is_x11(c))
112		return 0;
113#endif
114	if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >=
115			XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION && width >= 0 && height >= 0
116			&& (c->bounds.width != width || c->bounds.height != height)) {
117		c->bounds.width = width;
118		c->bounds.height = height;
119		return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height);
120	}
121	return 0;
122}
123
124static inline const char *
125client_get_appid(Client *c)
126{
127#ifdef XWAYLAND
128	if (client_is_x11(c))
129		return c->surface.xwayland->class ? c->surface.xwayland->class : "broken";
130#endif
131	return c->surface.xdg->toplevel->app_id ? c->surface.xdg->toplevel->app_id : "broken";
132}
133
134static inline void
135client_get_clip(Client *c, struct wlr_box *clip)
136{
137	*clip = (struct wlr_box){
138		.x = 0,
139		.y = 0,
140		.width = c->geom.width - c->bw,
141		.height = c->geom.height - c->bw,
142	};
143
144#ifdef XWAYLAND
145	if (client_is_x11(c))
146		return;
147#endif
148
149	clip->x = c->surface.xdg->geometry.x;
150	clip->y = c->surface.xdg->geometry.y;
151}
152
153static inline void
154client_get_geometry(Client *c, struct wlr_box *geom)
155{
156#ifdef XWAYLAND
157	if (client_is_x11(c)) {
158		geom->x = c->surface.xwayland->x;
159		geom->y = c->surface.xwayland->y;
160		geom->width = c->surface.xwayland->width;
161		geom->height = c->surface.xwayland->height;
162		return;
163	}
164#endif
165	*geom = c->surface.xdg->geometry;
166}
167
168static inline Client *
169client_get_parent(Client *c)
170{
171	Client *p = NULL;
172#ifdef XWAYLAND
173	if (client_is_x11(c)) {
174		if (c->surface.xwayland->parent)
175			toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL);
176		return p;
177	}
178#endif
179	if (c->surface.xdg->toplevel->parent)
180		toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL);
181	return p;
182}
183
184static inline int
185client_has_children(Client *c)
186{
187#ifdef XWAYLAND
188	if (client_is_x11(c))
189		return !wl_list_empty(&c->surface.xwayland->children);
190#endif
191	/* surface.xdg->link is never empty because it always contains at least the
192	 * surface itself. */
193	return wl_list_length(&c->surface.xdg->link) > 1;
194}
195
196static inline const char *
197client_get_title(Client *c)
198{
199#ifdef XWAYLAND
200	if (client_is_x11(c))
201		return c->surface.xwayland->title ? c->surface.xwayland->title : "broken";
202#endif
203	return c->surface.xdg->toplevel->title ? c->surface.xdg->toplevel->title : "broken";
204}
205
206static inline int
207client_is_float_type(Client *c)
208{
209	struct wlr_xdg_toplevel *toplevel;
210	struct wlr_xdg_toplevel_state state;
211
212#ifdef XWAYLAND
213	if (client_is_x11(c)) {
214		struct wlr_xwayland_surface *surface = c->surface.xwayland;
215		xcb_size_hints_t *size_hints = surface->size_hints;
216		if (surface->modal)
217			return 1;
218
219		if (wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG)
220				|| wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_SPLASH)
221				|| wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLBAR)
222				|| wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_UTILITY)) {
223			return 1;
224		}
225
226		return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0
227			&& (size_hints->max_width == size_hints->min_width
228				|| size_hints->max_height == size_hints->min_height);
229	}
230#endif
231
232	toplevel = c->surface.xdg->toplevel;
233	state = toplevel->current;
234	return toplevel->parent || (state.min_width != 0 && state.min_height != 0
235		&& (state.min_width == state.max_width
236			|| state.min_height == state.max_height));
237}
238
239static inline int
240client_is_rendered_on_mon(Client *c, Monitor *m)
241{
242	/* This is needed for when you don't want to check formal assignment,
243	 * but rather actual displaying of the pixels.
244	 * Usually VISIBLEON suffices and is also faster. */
245	struct wlr_surface_output *s;
246	int unused_lx, unused_ly;
247	if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly))
248		return 0;
249	wl_list_for_each(s, &client_surface(c)->current_outputs, link)
250		if (s->output == m->wlr_output)
251			return 1;
252	return 0;
253}
254
255static inline int
256client_is_stopped(Client *c)
257{
258	int pid;
259	siginfo_t in = {0};
260#ifdef XWAYLAND
261	if (client_is_x11(c))
262		return 0;
263#endif
264
265	wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL);
266	if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) {
267		/* This process is not our child process, while is very unlikely that
268		 * it is stopped, in order to do not skip frames, assume that it is. */
269		if (errno == ECHILD)
270			return 1;
271	} else if (in.si_pid) {
272		if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED)
273			return 1;
274		if (in.si_code == CLD_CONTINUED)
275			return 0;
276	}
277
278	return 0;
279}
280
281static inline int
282client_is_unmanaged(Client *c)
283{
284#ifdef XWAYLAND
285	if (client_is_x11(c))
286		return c->surface.xwayland->override_redirect;
287#endif
288	return 0;
289}
290
291static inline void
292client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb)
293{
294	if (kb)
295		wlr_seat_keyboard_notify_enter(seat, s, kb->keycodes,
296				kb->num_keycodes, &kb->modifiers);
297	else
298		wlr_seat_keyboard_notify_enter(seat, s, NULL, 0, NULL);
299}
300
301static inline void
302client_send_close(Client *c)
303{
304#ifdef XWAYLAND
305	if (client_is_x11(c)) {
306		wlr_xwayland_surface_close(c->surface.xwayland);
307		return;
308	}
309#endif
310	wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel);
311}
312
313static inline void
314client_set_border_color(Client *c, const float inner[static 4], const float outer[static 4])
315{
316    int i;
317    for (i = 0; i < 4; i++) {
318        wlr_scene_rect_set_color(c->inner_border[i], inner);
319        wlr_scene_rect_set_color(c->outer_border[i], outer);
320    }
321}
322
323static inline void
324client_set_fullscreen(Client *c, int fullscreen)
325{
326#ifdef XWAYLAND
327	if (client_is_x11(c)) {
328		wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen);
329		return;
330	}
331#endif
332	wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen);
333}
334
335static inline void
336client_set_scale(struct wlr_surface *s, float scale)
337{
338	wlr_fractional_scale_v1_notify_scale(s, scale);
339	wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(scale));
340}
341
342static inline uint32_t
343client_set_size(Client *c, uint32_t width, uint32_t height)
344{
345#ifdef XWAYLAND
346	if (client_is_x11(c)) {
347		wlr_xwayland_surface_configure(c->surface.xwayland,
348				c->geom.x + c->bw, c->geom.y + c->bw, width, height);
349		return 0;
350	}
351#endif
352	if ((int32_t)width == c->surface.xdg->toplevel->current.width
353			&& (int32_t)height == c->surface.xdg->toplevel->current.height)
354		return 0;
355	return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height);
356}
357
358static inline void
359client_set_tiled(Client *c, uint32_t edges)
360{
361#ifdef XWAYLAND
362	if (client_is_x11(c)) {
363		wlr_xwayland_surface_set_maximized(c->surface.xwayland,
364				edges != WLR_EDGE_NONE, edges != WLR_EDGE_NONE);
365		return;
366  }
367#endif
368	if (wl_resource_get_version(c->surface.xdg->toplevel->resource)
369			>= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) {
370		wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges);
371	} else {
372		wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE);
373	}
374}
375
376static inline void
377client_set_suspended(Client *c, int suspended)
378{
379#ifdef XWAYLAND
380	if (client_is_x11(c))
381		return;
382#endif
383
384	wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended);
385}
386
387static inline int
388client_wants_focus(Client *c)
389{
390#ifdef XWAYLAND
391	return client_is_unmanaged(c)
392		&& wlr_xwayland_surface_override_redirect_wants_focus(c->surface.xwayland)
393		&& wlr_xwayland_surface_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE;
394#endif
395	return 0;
396}
397
398static inline int
399client_wants_fullscreen(Client *c)
400{
401#ifdef XWAYLAND
402	if (client_is_x11(c))
403		return c->surface.xwayland->fullscreen;
404#endif
405	return c->surface.xdg->toplevel->requested.fullscreen;
406}