diff --git a/Makefile b/Makefile index a67fdd3..e0e1260 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) all: dwl dwl: dwl.o util.o $(CC) dwl.o util.o $(LDLIBS) $(LDFLAGS) $(DWLCFLAGS) -o $@ -dwl.o: dwl.c config.mk config.h client.h cursor-shape-v1-protocol.h pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h xdg-shell-protocol.h +dwl.o: dwl.c config.mk config.h client.h cursor-shape-v1-protocol.h pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h xdg-shell-protocol.h wlr-foreign-toplevel-management-unstable-v1-protocol.h util.o: util.c util.h # wayland-scanner is a tool which generates C headers and rigging for Wayland @@ -37,6 +37,9 @@ wlr-layer-shell-unstable-v1-protocol.h: xdg-shell-protocol.h: $(WAYLAND_SCANNER) server-header \ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ +wlr-foreign-toplevel-management-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) server-header \ + protocols/wlr-foreign-toplevel-management-unstable-v1.xml $@ config.h: cp config.def.h $@ diff --git a/client.h b/client.h index 800b867..db6090d 100644 --- a/client.h +++ b/client.h @@ -96,6 +96,9 @@ client_activate_surface(struct wlr_surface *s, int activated) #ifdef XWAYLAND struct wlr_xwayland_surface *xsurface; if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { + if (activated && xsurface->minimized) { + wlr_xwayland_surface_set_minimized(xsurface, false); + } wlr_xwayland_surface_activate(xsurface, activated); return; } @@ -344,6 +347,19 @@ client_set_fullscreen(Client *c, int fullscreen) wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); } +static inline void +client_set_minimized(struct wlr_surface *s, int minimized) +{ +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { + int focused = seat->keyboard_state.focused_surface == xsurface->surface; + wlr_xwayland_surface_set_minimized(xsurface, !focused && minimized); + return; + } +#endif +} + static inline uint32_t client_set_size(Client *c, uint32_t width, uint32_t height) { diff --git a/config.def.h b/config.def.h index 8847e58..76b767b 100644 --- a/config.def.h +++ b/config.def.h @@ -137,6 +137,7 @@ 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_z, toggleminimized, {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/dwl.c b/dwl.c index bf763df..28926ff 100644 --- a/dwl.c +++ b/dwl.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -71,7 +72,7 @@ #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define ROUND(X) ((int)((X < 0) ? (X - 0.5) : (X + 0.5))) #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)->isminimized)) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1u << TAGCOUNT) - 1) @@ -120,12 +121,19 @@ typedef struct { struct wl_listener commit; struct wl_listener map; struct wl_listener maximize; + struct wl_listener minimize; 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; + struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; + struct wl_listener foreign_activate_request; + struct wl_listener foreign_close_request; + struct wl_listener foreign_fullscreen_request; + struct wl_listener foreign_destroy; + struct wl_listener foreign_minimize; struct wlr_box prev; /* layout-relative, includes border */ struct wlr_box bounds; #ifdef XWAYLAND @@ -137,7 +145,7 @@ typedef struct { #endif unsigned int bw; uint32_t tags; - int isfloating, isurgent, isfullscreen; + int isfloating, isurgent, isfullscreen, isminimized; uint32_t resize; /* configure serial of a pending resize */ } Client; @@ -295,6 +303,7 @@ static void locksession(struct wl_listener *listener, void *data); static void maplayersurfacenotify(struct wl_listener *listener, void *data); static void mapnotify(struct wl_listener *listener, void *data); static void maximizenotify(struct wl_listener *listener, void *data); +static void minimizenotify(struct wl_listener *listener, void *data); static void monocle(Monitor *m); static void motionabsolute(struct wl_listener *listener, void *data); static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, @@ -318,6 +327,7 @@ 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 setminimized(Client *c, int minimized); static void setgamma(struct wl_listener *listener, void *data); static void setlayout(const Arg *arg); static void setmfact(const Arg *arg); @@ -332,6 +342,7 @@ static void tagmon(const Arg *arg); static void tile(Monitor *m); static void togglefloating(const Arg *arg); static void togglefullscreen(const Arg *arg); +static void toggleminimized(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); static void unlocksession(struct wl_listener *listener, void *data); @@ -347,6 +358,11 @@ 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 void handle_foreign_activate_request(struct wl_listener *listener, void *data); +static void handle_foreign_close_request(struct wl_listener *listener, void *data); +static void handle_foreign_destroy(struct wl_listener *listener, void *data); +static void handle_foreign_fullscreen_request(struct wl_listener *listener, void *data); +static void handle_foreign_minimize(struct wl_listener *listener, void *data); /* variables */ static const char broken[] = "broken"; @@ -392,6 +408,8 @@ static struct wlr_scene_rect *locked_bg; static struct wlr_session_lock_v1 *cur_lock; static struct wl_listener lock_listener = {.notify = locksession}; +static struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; + static struct wlr_seat *seat; static KeyboardGroup kb_group = {0}; static KeyboardGroup vkb_group = {0}; @@ -458,6 +476,9 @@ applyrules(Client *c) if (!(title = client_get_title(c))) title = broken; + wlr_foreign_toplevel_handle_v1_set_app_id(c->foreign_toplevel, appid); + wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); + for (r = rules; r < END(rules); r++) { if ((!r->title || strstr(title, r->title)) && (!r->id || strstr(appid, r->id))) { @@ -975,7 +996,8 @@ createnotify(struct wl_listener *listener, void *data) c->bw = borderpx; wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, - WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN | + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE); LISTEN(&xdg_surface->events.destroy, &c->destroy, destroynotify); LISTEN(&xdg_surface->surface->events.commit, &c->commit, commitnotify); @@ -985,6 +1007,8 @@ createnotify(struct wl_listener *listener, void *data) fullscreennotify); LISTEN(&xdg_surface->toplevel->events.request_maximize, &c->maximize, maximizenotify); + LISTEN(&xdg_surface->toplevel->events.request_minimize, &c->minimize, + minimizenotify); LISTEN(&xdg_surface->toplevel->events.set_title, &c->set_title, updatetitle); } @@ -1288,6 +1312,8 @@ focusclient(Client *c, int lift) client_set_border_color(old_c, bordercolor); client_activate_surface(old, 0); + if (old_c->foreign_toplevel) + wlr_foreign_toplevel_handle_v1_set_activated(old_c->foreign_toplevel, 0); } } printstatus(); @@ -1306,6 +1332,8 @@ focusclient(Client *c, int lift) /* Activate the new client */ client_activate_surface(client_surface(c), 1); + if (c->foreign_toplevel) + wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, 1); } void @@ -1599,6 +1627,20 @@ mapnotify(struct wl_listener *listener, void *data) c->border[i]->node.data = c; } + c->foreign_toplevel = + wlr_foreign_toplevel_handle_v1_create(foreign_toplevel_manager); + + LISTEN(&c->foreign_toplevel->events.request_activate, &c->foreign_activate_request, + handle_foreign_activate_request); + LISTEN(&c->foreign_toplevel->events.request_close, &c->foreign_close_request, + handle_foreign_close_request); + LISTEN(&c->foreign_toplevel->events.request_fullscreen, &c->foreign_fullscreen_request, + handle_foreign_fullscreen_request); + LISTEN(&c->foreign_toplevel->events.destroy, &c->foreign_destroy, + handle_foreign_destroy); + LISTEN(&c->foreign_toplevel->events.request_minimize, &c->foreign_minimize, + handle_foreign_minimize); + /* Initialize client geometry with room for border */ client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); c->geom.width += 2 * c->bw; @@ -1645,6 +1687,24 @@ maximizenotify(struct wl_listener *listener, void *data) wlr_xdg_surface_schedule_configure(c->surface.xdg); } +void +minimizenotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on + * client-side decorations. dwl doesn't support maximization, but + * to conform to xdg-shell protocol we still must send a configure. + * Since xdg-shell protocol v5 we should ignore request of unsupported + * capabilities, just schedule a empty configure when the client uses <5 + * protocol version + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + Client *c = wl_container_of(listener, c, minimize); + setminimized(c, !c->isminimized); + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) + < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + wlr_xdg_surface_schedule_configure(c->surface.xdg); +} + void monocle(Monitor *m) { @@ -1928,6 +1988,7 @@ printstatus(void) 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); + printf("%s minimized %d\n", m->wlr_output->name, c->isminimized); sel = c->tags; } else { printf("%s title \n", m->wlr_output->name); @@ -2181,6 +2242,25 @@ setfullscreen(Client *c, int fullscreen) printstatus(); } +void +setminimized(Client *c, int minimized) +{ + if (c->isminimized == minimized) + return; + + c->isminimized = minimized; + if (!c->mon) + return; + client_set_minimized(client_surface(c), minimized); + // wlr_scene_node_reparent(&c->scene->node, + // layers[c->isminimized ? LyrBottom + // : c->isfullscreen ? LyrFS + // : c->isfloating ? LyrFloat + // : LyrTile]); + focusclient(focustop(c->mon), 1); + arrange(c->mon); +} + void setgamma(struct wl_listener *listener, void *data) { @@ -2232,14 +2312,20 @@ setmon(Client *c, Monitor *m, uint32_t newtags) c->prev = c->geom; /* Scene graph sends surface leave/enter events on move and resize */ - if (oldmon) + if (oldmon) { + if (c->foreign_toplevel) + wlr_foreign_toplevel_handle_v1_output_leave(c->foreign_toplevel, oldmon->wlr_output); arrange(oldmon); + } if (m) { /* Make sure window actually overlaps with the monitor */ resize(c, c->geom, 0); c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + if (c->foreign_toplevel) + wlr_foreign_toplevel_handle_v1_output_enter(c->foreign_toplevel, m->wlr_output); setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ setfloating(c, c->isfloating); + setminimized(c, c->isminimized); } focusclient(focustop(selmon), 1); } @@ -2351,6 +2437,9 @@ setup(void) gamma_control_mgr = wlr_gamma_control_manager_v1_create(dpy); LISTEN_STATIC(&gamma_control_mgr->events.set_gamma, setgamma); + /* Initializes foreign toplevel management */ + foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(dpy); + /* Creates an output layout, which a wlroots utility for working with an * arrangement of screens in a physical layout. */ output_layout = wlr_output_layout_create(); @@ -2620,6 +2709,14 @@ togglefullscreen(const Arg *arg) setfullscreen(sel, !sel->isfullscreen); } +void +toggleminimized(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setminimized(sel, !sel->isminimized); +} + void toggletag(const Arg *arg) { @@ -2691,6 +2788,11 @@ unmapnotify(struct wl_listener *listener, void *data) wl_list_remove(&c->flink); } + if (c->foreign_toplevel) { + wlr_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel); + c->foreign_toplevel = NULL; + } + wlr_scene_node_destroy(&c->scene->node); printstatus(); motionnotify(0, NULL, 0, 0, 0, 0); @@ -2802,6 +2904,12 @@ void updatetitle(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, set_title); + if (c->foreign_toplevel) { + const char *title; + if (!(title = client_get_title(c))) + title = broken; + wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); + } if (c == focustop(c->mon)) printstatus(); } @@ -2816,6 +2924,8 @@ urgent(struct wl_listener *listener, void *data) return; c->isurgent = 1; + if (c->isminimized) + setminimized(c, 0); printstatus(); if (client_surface(c)->mapped) @@ -2929,6 +3039,64 @@ zoom(const Arg *arg) arrange(selmon); } +void +handle_foreign_activate_request(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, foreign_activate_request); + + if (!c) + return; + if (c->isminimized) + setminimized(c, 0); + if (c->mon == selmon) { + c->tags = c->mon->tagset[c->mon->seltags]; + } else { + setmon(c, selmon, 0); + } + focusclient(c, 1); + arrange(c->mon); +} + +void +handle_foreign_close_request(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, foreign_close_request); + + if (c) + client_send_close(c); +} + +void +handle_foreign_fullscreen_request(struct wl_listener *listener, void *data) { + Client *c = wl_container_of(listener, c, foreign_fullscreen_request); + struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; + + if (c) + setfullscreen(c, event->fullscreen); +} + +void +handle_foreign_destroy(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, foreign_destroy); + + wl_list_remove(&c->foreign_activate_request.link); + wl_list_remove(&c->foreign_close_request.link); + wl_list_remove(&c->foreign_fullscreen_request.link); + wl_list_remove(&c->foreign_destroy.link); + wl_list_remove(&c->foreign_minimize.link); +} + +void +handle_foreign_minimize(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, foreign_minimize); + struct wlr_foreign_toplevel_handle_v1_minimized_event *event = data; + + if (c) + setminimized(c, event->minimized); +} + #ifdef XWAYLAND void activatex11(struct wl_listener *listener, void *data) @@ -2936,8 +3104,11 @@ activatex11(struct wl_listener *listener, void *data) Client *c = wl_container_of(listener, c, activate); /* Only "managed" windows can be activated */ - if (!client_is_unmanaged(c)) + if (!client_is_unmanaged(c)) { + if (c->isminimized) + setminimized(c, 0); wlr_xwayland_surface_activate(c->surface.xwayland, 1); + } } void @@ -2986,6 +3157,7 @@ createnotifyx11(struct wl_listener *listener, void *data) LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.request_minimize, &c->foreign_minimize, handle_foreign_minimize); LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); } diff --git a/protocols/wlr-foreign-toplevel-management-unstable-v1.xml b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 0000000..44505bb --- /dev/null +++ b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + +