From 801d9e0cdc1e414a9d2f927d4ce3a4a7b1f82c84 Mon Sep 17 00:00:00 2001 From: FabricSoul Date: Mon, 4 Aug 2025 21:20:17 -0400 Subject: [PATCH] Apply swallow patch and resolve conflicts --- client.h | 12 ++ client.h.orig | 404 ++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 11 +- config.def.h.orig | 183 +++++++++++++++++++++ dwl.c | 152 ++++++++++++++++- dwl.c.orig | 58 ++++++- 6 files changed, 810 insertions(+), 10 deletions(-) create mode 100644 client.h.orig create mode 100644 config.def.h.orig diff --git a/client.h b/client.h index d9f90bb..83e1c57 100644 --- a/client.h +++ b/client.h @@ -131,6 +131,18 @@ client_get_appid(Client *c) return c->surface.xdg->toplevel->app_id ? c->surface.xdg->toplevel->app_id : "broken"; } +static inline int +client_get_pid(Client *c) +{ + pid_t pid; +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->pid; +#endif + wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); + return pid; +} + static inline void client_get_clip(Client *c, struct wlr_box *clip) { diff --git a/client.h.orig b/client.h.orig new file mode 100644 index 0000000..d9f90bb --- /dev/null +++ b/client.h.orig @@ -0,0 +1,404 @@ +/* + * Attempt to consolidate unavoidable suck into one file, away from dwl.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. + */ + +/* Leave these functions first; they're used in the others */ +static inline int +client_is_x11(Client *c) +{ +#ifdef XWAYLAND + return c->type == X11; +#endif + return 0; +} + +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; +} + +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; + 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 */ +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_CONFIGURE_BOUNDS_SINCE_VERSION && 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; +} diff --git a/config.def.h b/config.def.h index 5311c3c..e24819c 100644 --- a/config.def.h +++ b/config.def.h @@ -13,6 +13,8 @@ static const float focuscolor[] = COLOR(0x005577ff); static const float urgentcolor[] = COLOR(0xff0000ff); /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ +static int enableautoswallow = 1; /* enables autoswallowing newly spawned clients */ +static float swallowborder = 1.0f; /* add this multiplied by borderpx to border when a client is swallowed */ /* tagging - TAGCOUNT must be no greater than 31 */ #define TAGCOUNT (9) @@ -29,10 +31,11 @@ static const char *const autostart[] = { /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ static const Rule rules[] = { - /* app_id title tags mask isfloating monitor */ + /* app_id title tags mask isfloating isterm noswallow monitor */ /* examples: */ - { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ - { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + { "foot", NULL, 0, 0, 1, 1, -1 }, + { "Gimp_EXAMPLE", NULL, 0, 1, 0, 0, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, 0, -1 }, /* Start on ONLY tag "9" */ }; /* layout(s) */ @@ -149,6 +152,8 @@ static const Key keys[] = { { MODKEY, XKB_KEY_space, setlayout, {0} }, { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_a, toggleswallow, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_A, toggleautoswallow,{0} }, { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, diff --git a/config.def.h.orig b/config.def.h.orig new file mode 100644 index 0000000..5311c3c --- /dev/null +++ b/config.def.h.orig @@ -0,0 +1,183 @@ +/* Taken from https://github.com/djpohly/dwl/issues/466 */ +#define COLOR(hex) { ((hex >> 24) & 0xFF) / 255.0f, \ + ((hex >> 16) & 0xFF) / 255.0f, \ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } +/* appearance */ +static const int sloppyfocus = 1; /* focus follows mouse */ +static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +static const unsigned int borderpx = 1; /* border pixel of windows */ +static const float rootcolor[] = COLOR(0x222222ff); +static const float bordercolor[] = COLOR(0x444444ff); +static const float focuscolor[] = COLOR(0x005577ff); +static const float urgentcolor[] = COLOR(0xff0000ff); +/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ + +/* tagging - TAGCOUNT must be no greater than 31 */ +#define TAGCOUNT (9) + +/* logging */ +static int log_level = WLR_ERROR; + +/* Autostart */ +static const char *const autostart[] = { + "wbg", "/path/to/your/image", NULL, + NULL /* terminate */ +}; + + +/* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ +static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ +}; + +/* layout(s) */ +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* monitors */ +/* (x=-1, y=-1) is reserved as an "autoconfigure" monitor position indicator + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 +*/ +/* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ +static const MonitorRule monrules[] = { + /* name mfact nmaster scale layout rotate/reflect x y */ + /* example of a HiDPI laptop monitor: + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ + { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, +}; + +/* keyboard */ +static const struct xkb_rule_names xkb_rules = { + /* can specify fields: rules, model, layout, variant, options */ + /* example: + .options = "ctrl:nocaps", + */ + .options = NULL, +}; + +static const int repeat_rate = 25; +static const int repeat_delay = 600; + +/* Trackpad */ +static const int tap_to_click = 1; +static const int tap_and_drag = 1; +static const int drag_lock = 1; +static const int natural_scrolling = 0; +static const int disable_while_typing = 1; +static const int left_handed = 0; +static const int middle_button_emulation = 0; +/* You can choose between: +LIBINPUT_CONFIG_SCROLL_NO_SCROLL +LIBINPUT_CONFIG_SCROLL_2FG +LIBINPUT_CONFIG_SCROLL_EDGE +LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN +*/ +static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + +/* You can choose between: +LIBINPUT_CONFIG_CLICK_METHOD_NONE +LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS +LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER +*/ +static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + +/* You can choose between: +LIBINPUT_CONFIG_SEND_EVENTS_ENABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +*/ +static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +/* You can choose between: +LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT +LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +*/ +static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +static const double accel_speed = 0.0; + +/* You can choose between: +LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right +*/ +static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; + +/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ +#define MODKEY WLR_MODIFIER_ALT + +#define TAGKEYS(KEY,SKEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *termcmd[] = { "foot", NULL }; +static const char *menucmd[] = { "wmenu-run", NULL }; + +static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), + TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), + TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), + TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), +}; + +static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, +}; diff --git a/dwl.c b/dwl.c index 792e661..54b40be 100644 --- a/dwl.c +++ b/dwl.c @@ -74,12 +74,13 @@ #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)->tags & (M)->tagset[(M)->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) +#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags]) && !(C)->swallowedby) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1u << TAGCOUNT) - 1) #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) #define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) +#define BORDERPX(C) (borderpx + ((C)->swallowing ? (int)ceilf(swallowborder * (C)->swallowing->bw) : 0)) /* enums */ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ @@ -101,7 +102,8 @@ typedef struct { } Button; typedef struct Monitor Monitor; -typedef struct { +typedef struct Client Client; +struct Client { /* Must keep this field first */ unsigned int type; /* XDGShell or X11* */ @@ -138,8 +140,12 @@ typedef struct { unsigned int bw; uint32_t tags; int isfloating, isurgent, isfullscreen; + int isterm, noswallow; uint32_t resize; /* configure serial of a pending resize */ -} Client; + pid_t pid; + Client *swallowing; /* client being hidden */ + Client *swallowedby; +}; typedef struct { uint32_t mod; @@ -227,6 +233,8 @@ typedef struct { const char *title; uint32_t tags; int isfloating; + int isterm; + int noswallow; int monitor; } Rule; @@ -309,6 +317,7 @@ static void moveresize(const Arg *arg); static void outputmgrapply(struct wl_listener *listener, void *data); static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); static void outputmgrtest(struct wl_listener *listener, void *data); +static pid_t parentpid(pid_t pid); static void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time); static void printstatus(void); @@ -332,11 +341,15 @@ 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 swallow(Client *c, Client *toswallow); static void tag(const Arg *arg); static void tagmon(const Arg *arg); +static Client *termforwin(Client *c); static void tile(Monitor *m); static void togglefloating(const Arg *arg); static void togglefullscreen(const Arg *arg); +static void toggleswallow(const Arg *arg); +static void toggleautoswallow(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); static void unlocksession(struct wl_listener *listener, void *data); @@ -490,11 +503,15 @@ applyrules(Client *c) appid = client_get_appid(c); title = client_get_title(c); + c->pid = client_get_pid(c); + for (r = rules; r < END(rules); r++) { if ((!r->title || strstr(title, r->title)) && (!r->id || strstr(appid, r->id))) { c->isfloating = r->isfloating; newtags |= r->tags; + c->isterm = r->isterm; + c->noswallow = r->noswallow; i = 0; wl_list_for_each(m, &mons, link) { if (r->monitor == i++) @@ -504,6 +521,12 @@ applyrules(Client *c) } c->isfloating |= client_is_float_type(c); + if (enableautoswallow && !c->noswallow && !c->isfloating && + !c->surface.xdg->initial_commit) { + Client *p = termforwin(c); + if (p) + swallow(c, p); + } setmon(c, mon, newtags); } @@ -2107,6 +2130,20 @@ outputmgrtest(struct wl_listener *listener, void *data) outputmgrapplyortest(config, 1); } +pid_t +parentpid(pid_t pid) +{ + unsigned int v = 0; + FILE *f; + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)pid); + if (!(f = fopen(buf, "r"))) + return 0; + fscanf(f, "%*u %*s %*c %u", &v); + fclose(f); + return (pid_t)v; +} + void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) @@ -2402,7 +2439,7 @@ setfullscreen(Client *c, int fullscreen) c->isfullscreen = fullscreen; if (!c->mon || !client_surface(c)->mapped) return; - c->bw = fullscreen ? 0 : borderpx; + c->bw = fullscreen ? 0 : BORDERPX(c); client_set_fullscreen(c, fullscreen); wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); @@ -2469,6 +2506,9 @@ setmon(Client *c, Monitor *m, uint32_t newtags) setfloating(c, c->isfloating); } focusclient(focustop(selmon), 1); + + if (c->swallowing) + setmon(c->swallowing, m, newtags); } void @@ -2739,6 +2779,44 @@ startdrag(struct wl_listener *listener, void *data) LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); } +void +swallow(Client *c, Client *toswallow) +{ + /* Do not allow a client to swallow itself */ + if (c == toswallow) + return; + + /* Swallow */ + if (toswallow && !c->swallowing) { + c->swallowing = toswallow; + toswallow->swallowedby = c; + toswallow->mon = c->mon; + toswallow->mon = c->mon; + wl_list_remove(&c->link); + wl_list_insert(&c->swallowing->link, &c->link); + wl_list_remove(&c->flink); + wl_list_insert(&c->swallowing->flink, &c->flink); + c->bw = BORDERPX(c); + c->tags = toswallow->tags; + c->isfloating = toswallow->isfloating; + c->geom = toswallow->geom; + setfullscreen(toswallow, 0); + } + + /* Unswallow */ + else if (c->swallowing) { + wl_list_remove(&c->swallowing->link); + wl_list_insert(&c->link, &c->swallowing->link); + wl_list_remove(&c->swallowing->flink); + wl_list_insert(&c->flink, &c->swallowing->flink); + c->swallowing->tags = c->tags; + c->swallowing->swallowedby = NULL; + c->swallowing = NULL; + c->bw = BORDERPX(c); + setfullscreen(c, 0); + } +} + void tag(const Arg *arg) { @@ -2760,6 +2838,40 @@ tagmon(const Arg *arg) setmon(sel, dirtomon(arg->i), 0); } +Client * +termforwin(Client *c) +{ + Client *p; + pid_t pid; + pid_t pids[32]; + size_t i, pids_len; + + if (!c->pid || c->isterm) + return NULL; + + /* Get all parent pids */ + pids_len = 0; + pid = c->pid; + while (pids_len < LENGTH(pids)) { + pid = parentpid(pid); + if (!pid) + break; + pids[pids_len++] = pid; + } + + /* Find closest parent */ + for (i = 0; i < pids_len; i++) { + wl_list_for_each(p, &clients, link) { + if (!p->pid || !p->isterm || p->swallowedby) + continue; + if (pids[i] == p->pid) + return p; + } + } + + return NULL; +} + void tile(Monitor *m) { @@ -2811,6 +2923,32 @@ togglefullscreen(const Arg *arg) setfullscreen(sel, !sel->isfullscreen); } +void +toggleswallow(const Arg *arg) +{ + Client *c, *sel = focustop(selmon); + if (!sel) + return; + + if (sel->swallowing) { + swallow(sel, NULL); + } else { + wl_list_for_each(c, &sel->flink, flink) { + if (&c->flink == &fstack) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + swallow(sel, c); + } +} + +void +toggleautoswallow(const Arg *arg) +{ + enableautoswallow = !enableautoswallow; +} + void toggletag(const Arg *arg) { @@ -2871,6 +3009,12 @@ unmapnotify(struct wl_listener *listener, void *data) grabc = NULL; } + if (c->swallowing) { + swallow(c, NULL); + } else if (c->swallowedby) { + swallow(c->swallowedby, NULL); + } + if (client_is_unmanaged(c)) { if (c == exclusive_focus) { exclusive_focus = NULL; diff --git a/dwl.c.orig b/dwl.c.orig index 12f441e..792e661 100644 --- a/dwl.c.orig +++ b/dwl.c.orig @@ -246,6 +246,7 @@ 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); +static void autostartexec(void); static void axisnotify(struct wl_listener *listener, void *data); static void buttonpress(struct wl_listener *listener, void *data); static void chvt(const Arg *arg); @@ -455,6 +456,9 @@ static struct wlr_xwayland *xwayland; /* attempt to encapsulate suck into one file */ #include "client.h" +static pid_t *autostart_pids; +static size_t autostart_len; + /* function implementations */ void applybounds(Client *c, struct wlr_box *bbox) @@ -603,6 +607,27 @@ arrangelayers(Monitor *m) } } +void +autostartexec(void) { + const char *const *p; + size_t i = 0; + + /* count entries */ + for (p = autostart; *p; autostart_len++, p++) + while (*++p); + + autostart_pids = calloc(autostart_len, sizeof(pid_t)); + for (p = autostart; *p; i++, p++) { + if ((autostart_pids[i] = fork()) == 0) { + setsid(); + execvp(*p, (char *const *)p); + die("dwl: execvp %s:", *p); + } + /* skip arguments */ + while (*++p); + } +} + void axisnotify(struct wl_listener *listener, void *data) { @@ -699,12 +724,23 @@ checkidleinhibitor(struct wlr_surface *exclude) void cleanup(void) { + size_t i; + cleanuplisteners(); #ifdef XWAYLAND wlr_xwayland_destroy(xwayland); xwayland = NULL; #endif wl_display_destroy_clients(dpy); + + /* kill child processes */ + for (i = 0; i < autostart_len; i++) { + if (0 < autostart_pids[i]) { + kill(autostart_pids[i], SIGTERM); + waitpid(autostart_pids[i], NULL, 0); + } + } + if (child_pid > 0) { kill(-child_pid, SIGTERM); waitpid(child_pid, NULL, 0); @@ -1560,10 +1596,25 @@ gpureset(struct wl_listener *listener, void *data) void handlesig(int signo) { - if (signo == SIGCHLD) - while (waitpid(-1, NULL, WNOHANG) > 0); - else if (signo == SIGINT || signo == SIGTERM) + if (signo == SIGCHLD) { + pid_t pid, *p, *lim; + while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { + if (pid == child_pid) + child_pid = -1; + if (!(p = autostart_pids)) + continue; + lim = &p[autostart_len]; + + for (; p < lim; p++) { + if (*p == pid) { + *p = -1; + break; + } + } + } + } else if (signo == SIGINT || signo == SIGTERM) { quit(NULL); + } } void @@ -2249,6 +2300,7 @@ run(char *startup_cmd) die("startup: backend_start"); /* Now that the socket exists and the backend is started, run the startup command */ + autostartexec(); if (startup_cmd) { int piperw[2]; if (pipe(piperw) < 0)