diff --git a/Makefile b/Makefile index 8a1b6eb..f62af4e 100644 --- a/Makefile +++ b/Makefile @@ -17,13 +17,16 @@ DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CF LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) all: dwl -dwl: dwl.o util.o - $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +dwl: dwl.o util.o dwl-ipc-unstable-v2-protocol.o + $(CC) dwl.o util.o dwl-ipc-unstable-v2-protocol.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ - wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h wlr-foreign-toplevel-management-unstable-v1-protocol.h + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ + wlr-foreign-toplevel-management-unstable-v1-protocol.h \ + dwl-ipc-unstable-v2-protocol.h util.o: util.c util.h ++dwl-ipc-unstable-v2-protocol.o: dwl-ipc-unstable-v2-protocol.c dwl-ipc-unstable-v2-protocol.h # wayland-scanner is a tool which generates C headers and rigging for Wayland # protocols, which are specified in XML. wlroots requires you to rig these up @@ -46,6 +49,12 @@ wlr-output-power-management-unstable-v1-protocol.h: xdg-shell-protocol.h: $(WAYLAND_SCANNER) server-header \ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ +dwl-ipc-unstable-v2-protocol.h: + $(WAYLAND_SCANNER) server-header \ + protocols/dwl-ipc-unstable-v2.xml $@ +dwl-ipc-unstable-v2-protocol.c: + $(WAYLAND_SCANNER) private-code \ + protocols/dwl-ipc-unstable-v2.xml $@ wlr-foreign-toplevel-management-unstable-v1-protocol.h: $(WAYLAND_SCANNER) server-header \ protocols/wlr-foreign-toplevel-management-unstable-v1.xml $@ diff --git a/client.h b/client.h index 42f225f..bc9cad2 100644 --- a/client.h +++ b/client.h @@ -131,6 +131,18 @@ client_get_appid(Client *c) return c->surface.xdg->toplevel->app_id; } +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/config.def.h b/config.def.h index 47b0896..b4f549a 100644 --- a/config.def.h +++ b/config.def.h @@ -20,14 +20,30 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca /* logging */ static int log_level = WLR_ERROR; -/* 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" */ +/* Autostart */ +static const char *const autostart[] = { + "dbus-update-activation-environment", "DISPLAY", "MOZ_DBUS_REMOTE", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", NULL, + "dinit", "-q", NULL, + "pkexec", "swayosd-libinput-backend", NULL, + "swayosd-server", 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 isterm noswallow monitor */ + /* examples: */ + { "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" */ + { "zoom", NULL, 0, 1, 0, 0, -1 }, + { "teams", NULL, 0, 1, 0, 0, -1 }, + { "wezterm", NULL, 0, 0, 1, 1, -1 }, /* make wezterm swallow clients that are not wezterm */ + { "foot", NULL, 0, 0, 1, 1, -1 }, /* make foot swallow clients that are not foot */ + { NULL, "wev", 0, 0, 0, 1, -1 }, /* xev */ + { NULL, "Event Tester", 0, 0, 0, 1, -1 }, /* xev */ + }; + /* layout(s) */ static const Layout layouts[] = { /* symbol arrange function */ @@ -118,47 +134,115 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } /* commands */ -// static const char *termcmd[] = { "kitty", "--single-instance", NULL }; -static const char *termcmd[] = { "wezterm", "-e", NULL }; -static const char *menucmd[] = { "wmenu-run", NULL }; +#define TERM_CMD "wezterm", "-e" +#define TERM(cmd) { .v = (const char*[]){ TERM_CMD, cmd, NULL } } +static const char *termcmd[] = { "wezterm", "-e", NULL }; +static const char *menucmd[] = { "wofi", "--show=drun", NULL }; +static const char *swaynccmd[] = { "swaync-client", "-t", "-sw", NULL }; +// volume +// static const char *volmicmutecmd[] = { "/usr/bin/pactl", "set-source-mute", "@DEFAULT_SOURCE@", "toggle", NULL }; +// static const char *volsoundmutecmd[] = { "/usr/bin/wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle", NULL }; +// static const char *voldecreasecmd[] = { "/usr/bin/wpctl", "set-volume", "-l", "1.0", "@DEFAULT_AUDIO_SINK@", "5%-", NULL }; +// static const char *volincreasecmd[] = { "/usr/bin/wpctl", "set-volume", "-l", "1.0", "@DEFAULT_AUDIO_SINK@", "5%+", NULL }; +static const char *volsoundmutecmd[] = { "swayosd-client", "--output-volume=mute-toggle", NULL }; +static const char *voldecreasecmd[] = { "swayosd-client", "--output-volume=lower", NULL }; +static const char *volincreasecmd[] = { "swayosd-client", "--output-volume=raise", NULL }; + +// mic +static const char *micmutecmd[] = { "swayosd-client", "--input=volume=mute-toggle", NULL }; +static const char *micdecreasecmd[] = { "swayosd-client", "--input=volume=lower", NULL }; +static const char *micincreasecmd[] = { "swayosd-client", "--input=volume=raise", NULL }; + +// brightness +static const char *increasebrightnesscmd[] = { "/usr/bin/brightnessctl", "s", "+10%", NULL }; +static const char *decreasebrightnesscmd[] = { "/usr/bin/brightnessctl", "s", "10%-", NULL }; +// media +static const char *mediaplaypausecmd[] = { "/usr/bin/playerctl", "play-pause", NULL }; +// static const char *mediaplay[] = { "/usr/bin/playerctl", "play", NULL }; +// static const char *mediapause[] = { "/usr/bin/playerctl", "pause", NULL }; +static const char *mediaplaynextcmd[] = { "/usr/bin/playerctl", "next", NULL }; +static const char *mediaplayprevcmd[] = { "/usr/bin/playerctl", "previous", NULL }; +// screenshot +#define SCREENSHOTDIR_CMD SHCMD("grim -g \"$(slurp)\" \"$(xdg-user-dir)/Screenshots/$(date +%Y-%m-%d-%H-%M-%S).png\"") +#define SCREENSHOTCLIPBOARD_CMD SHCMD("grim -g \"$(slurp)]\" -t png - | wl-copy") static const Key keys[] = { /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ - /* modifier key function argument */ - { MODKEY, XKB_KEY_space, spawn, {.v = menucmd} }, - { MODKEY, 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_Q, 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_p, setlayout, {0} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_p, 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), - TAGKEYS( XKB_KEY_0, XKB_KEY_parenright, 9), - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, quit, {0} }, + /* modifier key function argument */ + { MODKEY, XKB_KEY_space, spawn, {.v = menucmd} }, + { MODKEY, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_b, togglebar, {0} }, + // { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + // { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_h, focusdir, {.ui = 0} }, + { MODKEY, XKB_KEY_l, focusdir, {.ui = 1} }, + { MODKEY, XKB_KEY_k, focusdir, {.ui = 2} }, + { MODKEY, XKB_KEY_j, focusdir, {.ui = 3} }, + { 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_Q, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_P, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_p, togglefloating, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[2]} }, + // { MODKEY, XKB_KEY_p, setlayout, {0} }, + { MODKEY, XKB_KEY_f, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_F, togglefakefullscreen, {0} }, + { MODKEY, XKB_KEY_BackSpace, togglesticky, {0} }, + // { MODKEY, XKB_KEY_grave, view, {.ui = ~0} }, + // { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_grave, 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), + TAGKEYS( XKB_KEY_0, XKB_KEY_parenright, 9), + // { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, quit, {0} }, + + // volume + { 0, XKB_KEY_XF86AudioMute, spawn, {.v = volsoundmutecmd } }, + { 0, XKB_KEY_XF86AudioLowerVolume, spawn, {.v = voldecreasecmd } }, + { 0, XKB_KEY_XF86AudioRaiseVolume, spawn, {.v = volincreasecmd } }, + + // mic + { 0, XKB_KEY_XF86AudioMicMute, spawn, {.v = micmutecmd } }, + { WLR_MODIFIER_SHIFT, XKB_KEY_XF86AudioMute, spawn, {.v = micmutecmd } }, + { WLR_MODIFIER_SHIFT, XKB_KEY_XF86AudioLowerVolume, spawn, {.v = micdecreasecmd } }, + { WLR_MODIFIER_SHIFT, XKB_KEY_XF86AudioRaiseVolume, spawn, {.v = micincreasecmd } }, + + // media + { 0, XKB_KEY_XF86AudioPlay, spawn, {.v = mediaplaypausecmd } }, + { 0, XKB_KEY_XF86AudioPause, spawn, {.v = mediaplaypausecmd } }, + { 0, XKB_KEY_XF86AudioNext, spawn, {.v = mediaplaynextcmd } }, + { 0, XKB_KEY_XF86AudioPrev, spawn, {.v = mediaplayprevcmd } }, + + // brightness + { 0, XKB_KEY_XF86MonBrightnessUp, spawn, {.v = increasebrightnesscmd } }, + { 0, XKB_KEY_XF86MonBrightnessDown, spawn, {.v = decreasebrightnesscmd } }, + + // screenshot + { MODKEY, XKB_KEY_Print, spawn, SCREENSHOTDIR_CMD }, + { MODKEY, XKB_KEY_o, spawn, SCREENSHOTCLIPBOARD_CMD }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_o, spawn, SCREENSHOTCLIPBOARD_CMD }, + + // Apps + { MODKEY, XKB_KEY_m, spawn, TERM("neomutt") }, + { MODKEY, XKB_KEY_b, spawn, TERM("yazi") }, + { MODKEY, XKB_KEY_semicolon, spawn, TERM("btop") }, + { MODKEY, XKB_KEY_n, spawn, {.v = swaynccmd}}, + { 0, XKB_KEY_F12, spawn, SHCMD("~/bin/dwm-man") }, /* 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} }, diff --git a/dwl.c b/dwl.c index 2dfbf72..7cbb179 100644 --- a/dwl.c +++ b/dwl.c @@ -1,6 +1,7 @@ /* * See LICENSE file for copyright and license details. */ +#include #include #include #include @@ -68,13 +69,14 @@ #include #endif +#include "dwl-ipc-unstable-v2-protocol.h" #include "util.h" /* macros */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) -#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) +#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && (((C)->tags & (M)->tagset[(M)->seltags]) || C->issticky)) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1u << TAGCOUNT) - 1) @@ -106,7 +108,8 @@ typedef struct { typedef struct Pertag Pertag; typedef struct Monitor Monitor; -typedef struct { +typedef struct Client Client; +struct Client { /* Must keep these three elements in this order */ unsigned int type; /* XDGShell or X11* */ struct wlr_box geom; /* layout-relative, includes border */ @@ -146,9 +149,17 @@ typedef struct { #endif unsigned int bw; uint32_t tags; - int isfloating, isurgent, isfullscreen; + int isfloating, isurgent, isfullscreen, isterm, noswallow, isfakefullscreen, issticky; uint32_t resize; /* configure serial of a pending resize */ -} Client; + pid_t pid; + Client *swallowing, *swallowedby; +}; + +typedef struct { + struct wl_list link; + struct wl_resource *resource; + Monitor *mon; +} DwlIpcOutput; typedef struct { uint32_t mod; @@ -195,6 +206,7 @@ typedef struct { struct Monitor { struct wl_list link; + struct wl_list dwl_ipc_outputs; struct wlr_output *wlr_output; struct wlr_scene_output *scene_output; struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ @@ -238,6 +250,8 @@ typedef struct { const char *title; uint32_t tags; int isfloating; + int isterm; + int noswallow; int monitor; } Rule; @@ -257,6 +271,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); @@ -293,9 +308,21 @@ static void destroysessionlock(struct wl_listener *listener, void *data); static void destroysessionmgr(struct wl_listener *listener, void *data); static void destroykeyboardgroup(struct wl_listener *listener, void *data); static Monitor *dirtomon(enum wlr_direction dir); +static void dwl_ipc_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id); +static void dwl_ipc_manager_destroy(struct wl_resource *resource); +static void dwl_ipc_manager_get_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *output); +static void dwl_ipc_manager_release(struct wl_client *client, struct wl_resource *resource); +static void dwl_ipc_output_destroy(struct wl_resource *resource); +static void dwl_ipc_output_printstatus(Monitor *monitor); +static void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output); +static void dwl_ipc_output_set_client_tags(struct wl_client *client, struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags); +static void dwl_ipc_output_set_layout(struct wl_client *client, struct wl_resource *resource, uint32_t index); +static void dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, uint32_t tagmask, uint32_t toggle_tagset); +static void dwl_ipc_output_release(struct wl_client *client, struct wl_resource *resource); static void focusclient(Client *c, int lift); static void focusmon(const Arg *arg); static void focusstack(const Arg *arg); +static void focusdir(const Arg *arg); static Client *focustop(Monitor *m); static void fullscreennotify(struct wl_listener *listener, void *data); static void gpureset(struct wl_listener *listener, void *data); @@ -334,6 +361,8 @@ static void setcursor(struct wl_listener *listener, void *data); static void setcursorshape(struct wl_listener *listener, void *data); static void setfloating(Client *c, int floating); static void setfullscreen(Client *c, int fullscreen); +static void setsticky(Client *c, int sticky); +static void setfakefullscreen(Client *c, int fullscreen); static void setgamma(struct wl_listener *listener, void *data); static void setlayout(const Arg *arg); static void setmfact(const Arg *arg); @@ -346,8 +375,11 @@ static void startdrag(struct wl_listener *listener, void *data); static void tag(const Arg *arg); static void tagmon(const Arg *arg); static void tile(Monitor *m); +static void togglebar(const Arg *arg); static void togglefloating(const Arg *arg); +static void togglesticky(const Arg *arg); static void togglefullscreen(const Arg *arg); +static void togglefakefullscreen(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); static void unlocksession(struct wl_listener *listener, void *data); @@ -363,6 +395,10 @@ static Monitor *xytomon(double x, double y); static void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny); static void zoom(const Arg *arg); +static pid_t getparentprocess(pid_t p); +static int isdescprocess(pid_t p, pid_t c); +static Client *termforwin(Client *w); +static void swallow(Client *c, Client *w); static void createforeigntoplevel(Client *c); static void factivatenotify(struct wl_listener *listener, void *data); static void fclosenotify(struct wl_listener *listener, void *data); @@ -428,6 +464,9 @@ static struct wlr_box sgeom; static struct wl_list mons; static Monitor *selmon; +static struct zdwl_ipc_manager_v2_interface dwl_manager_implementation = {.release = dwl_ipc_manager_release, .get_output = dwl_ipc_manager_get_output}; +static struct zdwl_ipc_output_v2_interface dwl_output_implementation = {.release = dwl_ipc_output_release, .set_tags = dwl_ipc_output_set_tags, .set_layout = dwl_ipc_output_set_layout, .set_client_tags = dwl_ipc_output_set_client_tags}; + #ifdef XWAYLAND static void activatex11(struct wl_listener *listener, void *data); static void associatex11(struct wl_listener *listener, void *data); @@ -455,6 +494,9 @@ struct Pertag { const Layout *ltidxs[TAGCOUNT + 1][2]; /* matrix of tags and layouts indexes */ }; +static pid_t *autostart_pids; +static size_t autostart_len; + /* function implementations */ void applybounds(Client *c, struct wlr_box *bbox) @@ -494,10 +536,14 @@ applyrules(Client *c) wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); } + 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; + c->isterm = r->isterm; + c->noswallow = r->noswallow; newtags |= r->tags; i = 0; wl_list_for_each(m, &mons, link) { @@ -506,6 +552,25 @@ applyrules(Client *c) } } } + if (!c->noswallow && !client_is_float_type(c) + && !c->surface.xdg->initial_commit) { + Client *p = termforwin(c); + if (p) { + c->swallowedby = p; + p->swallowing = c; + wl_list_remove(&c->link); + wl_list_remove(&c->flink); + swallow(c, p); + wl_list_remove(&p->link); + wl_list_remove(&p->flink); + mon = p->mon; + newtags = p->tags; + } + } + if (mon) { + c->geom.x = (mon->w.width - c->geom.width) / 2 + mon->m.x; + c->geom.y = (mon->w.height - c->geom.height) / 2 + mon->m.y; + } setmon(c, mon, newtags); } @@ -608,6 +673,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) { @@ -704,11 +790,21 @@ checkidleinhibitor(struct wlr_surface *exclude) void cleanup(void) { + size_t i; #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); @@ -734,6 +830,10 @@ cleanupmon(struct wl_listener *listener, void *data) LayerSurface *l, *tmp; size_t i; + DwlIpcOutput *ipc_output, *ipc_output_tmp; + wl_list_for_each_safe(ipc_output, ipc_output_tmp, &m->dwl_ipc_outputs, link) + wl_resource_destroy(ipc_output->resource); + /* m->layers[i] are intentionally not unlinked */ for (i = 0; i < LENGTH(m->layers); i++) { wl_list_for_each_safe(l, tmp, &m->layers[i], link) @@ -1015,6 +1115,8 @@ createmon(struct wl_listener *listener, void *data) m = wlr_output->data = ecalloc(1, sizeof(*m)); m->wlr_output = wlr_output; + wl_list_init(&m->dwl_ipc_outputs); + for (i = 0; i < LENGTH(m->layers); i++) wl_list_init(&m->layers[i]); @@ -1124,11 +1226,10 @@ createpointer(struct wlr_pointer *pointer) libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); libinput_device_config_tap_set_button_map(device, button_map); + if (libinput_device_config_scroll_has_natural_scroll(device)) + libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); } - if (libinput_device_config_scroll_has_natural_scroll(device)) - libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); - if (libinput_device_config_dwt_is_available(device)) libinput_device_config_dwt_set_enabled(device, disable_while_typing); @@ -1377,6 +1478,212 @@ dirtomon(enum wlr_direction dir) return selmon; } +void +dwl_ipc_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *manager_resource = wl_resource_create(client, &zdwl_ipc_manager_v2_interface, version, id); + if (!manager_resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(manager_resource, &dwl_manager_implementation, NULL, dwl_ipc_manager_destroy); + + zdwl_ipc_manager_v2_send_tags(manager_resource, TAGCOUNT); + + for (unsigned int i = 0; i < LENGTH(layouts); i++) + zdwl_ipc_manager_v2_send_layout(manager_resource, layouts[i].symbol); +} + +void +dwl_ipc_manager_destroy(struct wl_resource *resource) +{ + /* No state to destroy */ +} + +void +dwl_ipc_manager_get_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *output) +{ + DwlIpcOutput *ipc_output; + Monitor *monitor = wlr_output_from_resource(output)->data; + struct wl_resource *output_resource = wl_resource_create(client, &zdwl_ipc_output_v2_interface, wl_resource_get_version(resource), id); + if (!output_resource) + return; + + ipc_output = ecalloc(1, sizeof(*ipc_output)); + ipc_output->resource = output_resource; + ipc_output->mon = monitor; + wl_resource_set_implementation(output_resource, &dwl_output_implementation, ipc_output, dwl_ipc_output_destroy); + wl_list_insert(&monitor->dwl_ipc_outputs, &ipc_output->link); + dwl_ipc_output_printstatus_to(ipc_output); +} + +void +dwl_ipc_manager_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +dwl_ipc_output_destroy(struct wl_resource *resource) +{ + DwlIpcOutput *ipc_output = wl_resource_get_user_data(resource); + wl_list_remove(&ipc_output->link); + free(ipc_output); +} + +void +dwl_ipc_output_printstatus(Monitor *monitor) +{ + DwlIpcOutput *ipc_output; + wl_list_for_each(ipc_output, &monitor->dwl_ipc_outputs, link) + dwl_ipc_output_printstatus_to(ipc_output); +} + +void +dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) +{ + Monitor *monitor = ipc_output->mon; + Client *c, *focused; + int tagmask, state, numclients, focused_client, tag; + const char *title, *appid; + focused = focustop(monitor); + zdwl_ipc_output_v2_send_active(ipc_output->resource, monitor == selmon); + + for (tag = 0 ; tag < TAGCOUNT; tag++) { + numclients = state = focused_client = 0; + tagmask = 1 << tag; + if ((tagmask & monitor->tagset[monitor->seltags]) != 0) + state |= ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE; + + wl_list_for_each(c, &clients, link) { + if (c->mon != monitor) + continue; + if (!(c->tags & tagmask)) + continue; + if (c == focused) + focused_client = 1; + if (c->isurgent) + state |= ZDWL_IPC_OUTPUT_V2_TAG_STATE_URGENT; + + numclients++; + } + zdwl_ipc_output_v2_send_tag(ipc_output->resource, tag, state, numclients, focused_client); + } + title = focused ? client_get_title(focused) : ""; + appid = focused ? client_get_appid(focused) : ""; + + zdwl_ipc_output_v2_send_layout(ipc_output->resource, monitor->lt[monitor->sellt] - layouts); + zdwl_ipc_output_v2_send_title(ipc_output->resource, title); + zdwl_ipc_output_v2_send_appid(ipc_output->resource, appid); + zdwl_ipc_output_v2_send_layout_symbol(ipc_output->resource, monitor->ltsymbol); + if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_FULLSCREEN_SINCE_VERSION) { + zdwl_ipc_output_v2_send_fullscreen(ipc_output->resource, focused ? focused->isfullscreen : 0); + } + if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_FLOATING_SINCE_VERSION) { + zdwl_ipc_output_v2_send_floating(ipc_output->resource, focused ? focused->isfloating : 0); + } + zdwl_ipc_output_v2_send_frame(ipc_output->resource); +} + +void +dwl_ipc_output_set_client_tags(struct wl_client *client, struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags) +{ + DwlIpcOutput *ipc_output; + Monitor *monitor; + Client *selected_client; + unsigned int newtags = 0; + + ipc_output = wl_resource_get_user_data(resource); + if (!ipc_output) + return; + + monitor = ipc_output->mon; + selected_client = focustop(monitor); + if (!selected_client) + return; + + newtags = (selected_client->tags & and_tags) ^ xor_tags; + if (!newtags) + return; + + selected_client->tags = newtags; + if (selmon == monitor) + focusclient(focustop(monitor), 1); + arrange(selmon); + printstatus(); +} + +void +dwl_ipc_output_set_layout(struct wl_client *client, struct wl_resource *resource, uint32_t index) +{ + DwlIpcOutput *ipc_output; + Client *c = NULL; + Monitor *monitor = NULL; + + ipc_output = wl_resource_get_user_data(resource); + if (!ipc_output) + return; + monitor = ipc_output->mon; + + if (monitor != selmon) + c = focustop(selmon); + + if (index >= LENGTH(layouts)) + return; + + if (c) { + monitor = selmon; + selmon = ipc_output->mon; + } + setlayout(&(Arg){.v = &layouts[index]}); + if (c) { + selmon = monitor; + focusclient(c, 0); + } +} + +void +dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, uint32_t tagmask, uint32_t toggle_tagset) +{ + DwlIpcOutput *ipc_output; + Client *c = NULL; + Monitor *monitor = NULL; + unsigned int newtags = tagmask & TAGMASK; + + ipc_output = wl_resource_get_user_data(resource); + if (!ipc_output) + return; + monitor = ipc_output->mon; + + if (monitor != selmon) + c = focustop(selmon); + + if (!newtags) + return; + + /* view toggles seltags for us so we un-toggle it */ + if (!toggle_tagset) { + monitor->seltags ^= 1; + monitor->tagset[monitor->seltags] = 0; + } + + if (c) { + monitor = selmon; + selmon = ipc_output->mon; + } + view(&(Arg){.ui = newtags}); + if (c) { + selmon = monitor; + focusclient(c, 0); + } +} + +void +dwl_ipc_output_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + void focusclient(Client *c, int lift) { @@ -1494,6 +1801,49 @@ focusstack(const Arg *arg) focusclient(c, 1); } +void focusdir(const Arg *arg) +{ + /* Focus the left, right, up, down client relative to the current focused client on selmon */ + Client *c, *sel = focustop(selmon); + if (!sel || sel->isfullscreen) + return; + + int dist=INT_MAX; + Client *newsel = NULL; + int newdist=INT_MAX; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, selmon)) + continue; /* skip non visible windows */ + + if (arg->ui == 0 && sel->geom.x <= c->geom.x) { + /* Client isn't on our left */ + continue; + } + if (arg->ui == 1 && sel->geom.x >= c->geom.x) { + /* Client isn't on our right */ + continue; + } + if (arg->ui == 2 && sel->geom.y <= c->geom.y) { + /* Client isn't above us */ + continue; + } + if (arg->ui == 3 && sel->geom.y >= c->geom.y) { + /* Client isn't below us */ + continue; + } + + dist=abs(sel->geom.x-c->geom.x)+abs(sel->geom.y-c->geom.y); + if (dist < newdist){ + newdist = dist; + newsel=c; + } + } + if (newsel != NULL){ + focusclient(newsel, 1); + } +} + + /* We probably should change the name of this, it sounds like * will focus the topmost client of this mon, when actually will * only return that client */ @@ -1543,23 +1893,93 @@ void handlesig(int signo) { if (signo == SIGCHLD) { -#ifdef XWAYLAND siginfo_t in; /* wlroots expects to reap the XWayland process itself, so we * use WNOWAIT to keep the child waitable until we know it's not * XWayland. */ while (!waitid(P_ALL, 0, &in, WEXITED|WNOHANG|WNOWAIT) && in.si_pid - && (!xwayland || in.si_pid != xwayland->server->pid)) - waitpid(in.si_pid, NULL, 0); -#else - while (waitpid(-1, NULL, WNOHANG) > 0); +#ifdef XWAYLAND + && (!xwayland || in.si_pid != xwayland->server->pid) #endif + ) { + pid_t *p, *lim; + waitpid(in.si_pid, NULL, 0); + if (in.si_pid == child_pid) + child_pid = -1; + if (!(p = autostart_pids)) + continue; + lim = &p[autostart_len]; + + for (; p < lim; p++) { + if (*p == in.si_pid) { + *p = -1; + break; + } + } + } } else if (signo == SIGINT || signo == SIGTERM) { quit(NULL); } } +pid_t +getparentprocess(pid_t p) +{ + unsigned int v = 0; + + FILE *f; + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p); + + if (!(f = fopen(buf, "r"))) + return 0; + + fscanf(f, "%*u %*s %*c %u", &v); + fclose(f); + + return (pid_t)v; +} + +int +isdescprocess(pid_t p, pid_t c) +{ + while (p != c && c != 0) + c = getparentprocess(c); + + return (int)c; +} + +Client * +termforwin(Client *w) +{ + Client *c; + + if (!w->pid || w->isterm || w->noswallow) + return NULL; + + wl_list_for_each(c, &fstack, flink) + if (c->isterm && !c->swallowing && c->pid && isdescprocess(c->pid, w->pid)) + return c; + + return NULL; +} + +void +swallow(Client *c, Client *w) +{ + c->bw = w->bw; + c->isfloating = w->isfloating; + c->isurgent = w->isurgent; + c->isfullscreen = w->isfullscreen; + c->tags = w->tags; + c->geom = w->geom; + wl_list_insert(&w->link, &c->link); + wl_list_insert(&w->flink, &c->flink); + wlr_scene_node_set_enabled(&w->scene->node, 0); + wlr_scene_node_set_enabled(&c->scene->node, 1); +} + void incnmaster(const Arg *arg) { @@ -1781,6 +2201,10 @@ mapnotify(struct wl_listener *listener, void *data) * try to apply rules for them */ if ((p = client_get_parent(c))) { c->isfloating = 1; + if (p->mon) { + c->geom.x = (p->mon->w.width - c->geom.width) / 2 + p->mon->m.x; + c->geom.y = (p->mon->w.height - c->geom.height) / 2 + p->mon->m.y; + } setmon(c, p->mon, p->tags); } else { applyrules(c); @@ -2083,41 +2507,9 @@ void printstatus(void) { Monitor *m = NULL; - Client *c; - uint32_t occ, urg, sel; - const char *appid, *title; - wl_list_for_each(m, &mons, link) { - occ = urg = 0; - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - occ |= c->tags; - if (c->isurgent) - urg |= c->tags; - } - if ((c = focustop(m))) { - title = client_get_title(c); - appid = client_get_appid(c); - printf("%s title %s\n", m->wlr_output->name, title ? title : broken); - printf("%s appid %s\n", m->wlr_output->name, appid ? appid : broken); - printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); - printf("%s floating %d\n", m->wlr_output->name, c->isfloating); - sel = c->tags; - } else { - printf("%s title \n", m->wlr_output->name); - printf("%s appid \n", m->wlr_output->name); - printf("%s fullscreen \n", m->wlr_output->name); - printf("%s floating \n", m->wlr_output->name); - sel = 0; - } - - printf("%s selmon %u\n", m->wlr_output->name, m == selmon); - printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", - m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); - printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); - } - fflush(stdout); + wl_list_for_each(m, &mons, link) + dwl_ipc_output_printstatus(m); } void @@ -2272,6 +2664,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) @@ -2388,6 +2781,17 @@ setfullscreen(Client *c, int fullscreen) printstatus(); } +void +setfakefullscreen(Client *c, int fullscreen) +{ + c->isfakefullscreen = fullscreen; + if (!c->mon) + return; + if (c->isfullscreen) + setfullscreen(c, 0); + client_set_fullscreen(c, fullscreen); +} + void setgamma(struct wl_listener *listener, void *data) { @@ -2399,6 +2803,17 @@ setgamma(struct wl_listener *listener, void *data) wlr_output_schedule_frame(m->wlr_output); } +void +setsticky(Client *c, int sticky) +{ + if(sticky && !c->issticky) { + c->issticky = 1; + } else if(!sticky && c->issticky) { + c->issticky = 0; + arrange(c->mon); + } +} + void setlayout(const Arg *arg) { @@ -2680,6 +3095,8 @@ setup(void) LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + wl_global_create(dpy, &zdwl_ipc_manager_v2_interface, 2, NULL, dwl_ipc_manager_bind); + /* Make sure XWayland clients don't connect to the parent X server, * e.g when running in the x11 backend or the wayland backend and the * compositor has Xwayland support */ @@ -2777,6 +3194,13 @@ tile(Monitor *m) } } +void +togglebar(const Arg *arg) { + DwlIpcOutput *ipc_output; + wl_list_for_each(ipc_output, &selmon->dwl_ipc_outputs, link) + zdwl_ipc_output_v2_send_toggle_visibility(ipc_output->resource); +} + void togglefloating(const Arg *arg) { @@ -2794,6 +3218,24 @@ togglefullscreen(const Arg *arg) setfullscreen(sel, !sel->isfullscreen); } +void +togglefakefullscreen(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setfakefullscreen(sel, !sel->isfakefullscreen); +} + +void +togglesticky(const Arg *arg) +{ + Client *c = focustop(selmon); + if(!c) + return; + + setsticky(c, !c->issticky); +} + void toggletag(const Arg *arg) { @@ -2874,15 +3316,32 @@ unmapnotify(struct wl_listener *listener, void *data) grabc = NULL; } + if (c->swallowedby) + swallow(c->swallowedby, c); + if (client_is_unmanaged(c)) { if (c == exclusive_focus) { exclusive_focus = NULL; focusclient(focustop(selmon), 1); } } else { - wl_list_remove(&c->link); + if (!c->swallowing) + wl_list_remove(&c->link); setmon(c, NULL, 0); - wl_list_remove(&c->flink); + if (!c->swallowing) + wl_list_remove(&c->flink); + } + + if (c->swallowedby) { + c->swallowedby->prev = c->geom; + setfullscreen(c->swallowedby, c->isfullscreen); + c->swallowedby->swallowing = NULL; + c->swallowedby = NULL; + } + + if (c->swallowing) { + c->swallowing->swallowedby = NULL; + c->swallowing = NULL; } if (c->foreign_toplevel) { diff --git a/protocols/dwl-ipc-unstable-v2.xml b/protocols/dwl-ipc-unstable-v2.xml new file mode 100644 index 0000000..0a6e7e5 --- /dev/null +++ b/protocols/dwl-ipc-unstable-v2.xml @@ -0,0 +1,181 @@ + + + + + This protocol allows clients to update and get updates from dwl. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible + changes may be added together with the corresponding interface + version bump. + Backward incompatible changes are done by bumping the version + number in the protocol and interface names and resetting the + interface version. Once the protocol is to be declared stable, + the 'z' prefix and the version number in the protocol and + interface names are removed and the interface version number is + reset. + + + + + This interface is exposed as a global in wl_registry. + + Clients can use this interface to get a dwl_ipc_output. + After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events. + The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client. + + + + + Indicates that the client will not the dwl_ipc_manager object anymore. + Objects created through this instance are not affected. + + + + + + Get a dwl_ipc_outout for the specified wl_output. + + + + + + + + This event is sent after binding. + A roundtrip after binding guarantees the client recieved all tags. + + + + + + + This event is sent after binding. + A roundtrip after binding guarantees the client recieved all layouts. + + + + + + + + Observe and control a dwl output. + + Events are double-buffered: + Clients should cache events and redraw when a dwl_ipc_output.frame event is sent. + + Request are not double-buffered: + The compositor will update immediately upon request. + + + + + + + + + + + Indicates to that the client no longer needs this dwl_ipc_output. + + + + + + Indicates the client should hide or show themselves. + If the client is visible then hide, if hidden then show. + + + + + + Indicates if the output is active. Zero is invalid, nonzero is valid. + + + + + + + Indicates that a tag has been updated. + + + + + + + + + + Indicates a new layout is selected. + + + + + + + Indicates the title has changed. + + + + + + + Indicates the appid has changed. + + + + + + + Indicates the layout has changed. Since layout symbols are dynamic. + As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying. + You can ignore the zdwl_ipc_output.layout event. + + + + + + + Indicates that a sequence of status updates have finished and the client should redraw. + + + + + + + + + + + + The tags are updated as follows: + new_tags = (current_tags AND and_tags) XOR xor_tags + + + + + + + + + + + + + + Indicates if the selected client on this output is fullscreen. + + + + + + + Indicates if the selected client on this output is floating. + + + + +