diff --git a/Makefile b/Makefile index 1bdc7d6..6605796 100644 --- a/Makefile +++ b/Makefile @@ -4,45 +4,38 @@ include config.mk # flags for compiling -DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -DVERSION=\"$(VERSION)\" - -# Wayland utils -WAYLAND_PROTOCOLS = `pkg-config --variable=pkgdatadir wayland-protocols` -WAYLAND_SCANNER = `pkg-config --variable=wayland_scanner wayland-scanner` +DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XWAYLAND) +DWLDEVCFLAGS = -pedantic -Wall -Wextra -Wdeclaration-after-statement -Wno-unused-parameter -Wno-sign-compare -Wshadow -Wunused-macros # CFLAGS / LDFLAGS PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) -DWLCFLAGS = `pkg-config --cflags $(PKGS)` $(DWLCPPFLAGS) $(CFLAGS) $(XWAYLAND) -LDLIBS = `pkg-config --libs $(PKGS)` $(LIBS) +DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) +LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) -# build rules +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 xdg-shell-protocol.h wlr-layer-shell-unstable-v1-protocol.h +util.o: util.c util.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 # to your build system yourself and provide them in the include path. -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 xdg-shell-protocol.h wlr-layer-shell-unstable-v1-protocol.h idle-protocol.h -util.o: util.c util.h +WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner` +WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols` -# wayland scanner rules to generate .h / .c files xdg-shell-protocol.h: $(WAYLAND_SCANNER) server-header \ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ wlr-layer-shell-unstable-v1-protocol.h: $(WAYLAND_SCANNER) server-header \ protocols/wlr-layer-shell-unstable-v1.xml $@ -idle-protocol.h: - $(WAYLAND_SCANNER) server-header \ - protocols/idle.xml $@ config.h: cp config.def.h $@ clean: rm -f dwl *.o *-protocol.h -# distribution archive dist: clean mkdir -p dwl-$(VERSION) cp -R LICENSE* Makefile README.md client.h config.def.h\ @@ -51,8 +44,6 @@ dist: clean tar -caf dwl-$(VERSION).tar.gz dwl-$(VERSION) rm -rf dwl-$(VERSION) -# install rules - install: dwl mkdir -p $(DESTDIR)$(PREFIX)/bin cp -f dwl $(DESTDIR)$(PREFIX)/bin diff --git a/README.md b/README.md index 887bf29..132fb32 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # dwl - dwm for Wayland -Join us on our [Discord server](https://discord.gg/jJxZnrGPWN)! +Join us on our [Discord server](https://discord.gg/jJxZnrGPWN) or at [#dwl](https://web.libera.chat/?channels=#dwl) on irc.libera.chat. dwl is a compact, hackable compositor for Wayland based on [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots/). It is intended to fill the same space in the Wayland world that dwm does in X11, primarily in terms of philosophy, and secondarily in terms of functionality. Like dwm, dwl is: @@ -27,7 +27,7 @@ dwl is not meant to provide every feature under the sun. Instead, like dwm, it s Features under consideration (possibly as patches) are: - Protocols made trivial by wlroots -- Implement the text-input and input-method protocols to support IME once ibus implements input-method v2 (see https://github.com/ibus/ibus/pull/2256 and https://github.com/djpohly/dwl/pull/12) +- Implement the text-input and input-method protocols to support IME once ibus implements input-method v2 (see https://github.com/ibus/ibus/pull/2256 and https://github.com/djpohly/dwl/pull/235) Feature *non-goals* for the main codebase include: @@ -73,21 +73,11 @@ If your startup command is a shell script, you can achieve the same inside the s exec <&- -Existing dwl-specific status bars and dwl-specific scripts for other status bars include: -- [somebar](https://sr.ht/~raphi/somebar/) status bar designed for dwl -- [dtaobarv2.sh](https://cdn.discordapp.com/attachments/792078050024095745/862428883423723560/dtaobarv2.sh) for use with [dtao](https://github.com/djpohly/dtao) (See "Pinned Messages" on the "customizations" channel of the [dwl Discord server](https://discord.gg/jJxZnrGPWN) for details.) -- [dwlbar.sh](https://cdn.discordapp.com/attachments/792078050024095745/810926218529472592/dwlbar.sh) for use with [waybar](https://github.com/Alexays/Waybar) (See "Pinned Messages" on the "customizations" channel of the [dwl Discord server](https://discord.gg/jJxZnrGPWN) for details.) -- [waybar-dwl](https://codeberg.org/fauxmight/waybar-dwl.git) for use with [waybar](https://github.com/Alexays/Waybar) -- [dwl-tags.sh](https://codeberg.org/novakane/yambar/src/branch/master/examples/scripts/dwl-tags.sh) for use with [yambar](https://codeberg.org/dnkl/yambar) -- [waybar-dwl.sh](https://gitee.com/guyuming76/personal/tree/dwl/gentoo/waybar-dwl) for use with [waybar](https://github.com/Alexays/Waybar) (ACCESS TO THIS SCRIPT REQUIRES gitee.com LOGIN!) +To get a list of status bars that work with dwl consult our [wiki](https://github.com/djpohly/dwl/wiki#compatible-status-bars). ## Replacements for X applications -You can find a [list of Wayland applications on the sway wiki](https://github.com/swaywm/sway/wiki/i3-Migration-Guide). - -## IRC channel - -dwl's IRC channel is #dwl on irc.libera.chat. +You can find a [list of useful resources on our wiki](https://github.com/djpohly/dwl/wiki#migrating-from-x). ## Acknowledgements diff --git a/client.h b/client.h index 17f9aff..b89a659 100644 --- a/client.h +++ b/client.h @@ -16,21 +16,10 @@ client_is_x11(Client *c) #endif } -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 Client * client_from_wlr_surface(struct wlr_surface *s) { struct wlr_xdg_surface *surface; - struct wlr_surface *parent; #ifdef XWAYLAND struct wlr_xwayland_surface *xsurface; @@ -48,6 +37,54 @@ client_from_wlr_surface(struct wlr_surface *s) return NULL; } +static inline Client * +client_get_parent(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c) && c->surface.xwayland->parent) + return client_from_wlr_surface(c->surface.xwayland->parent->surface); +#endif + if (c->surface.xdg->toplevel->parent) + return client_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface); + + return NULL; +} + +static inline void +client_get_size_hints(Client *c, struct wlr_box *max, struct wlr_box *min) +{ + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_toplevel_state *state; +#ifdef XWAYLAND + if (client_is_x11(c)) { + xcb_size_hints_t *size_hints = c->surface.xwayland->size_hints; + if (size_hints) { + max->width = size_hints->max_width; + max->height = size_hints->max_height; + min->width = size_hints->min_width; + min->height = size_hints->min_height; + } + return; + } +#endif + toplevel = c->surface.xdg->toplevel; + state = &toplevel->current; + max->width = state->max_width; + max->height = state->max_height; + min->width = state->min_width; + min->height = state->min_height; +} + +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; +} + /* The others */ static inline void client_activate_surface(struct wlr_surface *s, int activated) @@ -116,31 +153,6 @@ client_get_geometry(Client *c, struct wlr_box *geom) wlr_xdg_surface_get_geometry(c->surface.xdg, geom); } -static inline void -client_get_size_hints(Client *c, struct wlr_box *max, struct wlr_box *min) -{ - struct wlr_xdg_toplevel *toplevel; - struct wlr_xdg_toplevel_state *state; -#ifdef XWAYLAND - if (client_is_x11(c)) { - xcb_size_hints_t *size_hints = c->surface.xwayland->size_hints; - if (size_hints) { - max->width = size_hints->max_width; - max->height = size_hints->max_height; - min->width = size_hints->min_width; - min->height = size_hints->min_height; - } - return; - } -#endif - toplevel = c->surface.xdg->toplevel; - state = &toplevel->current; - max->width = state->max_width; - max->height = state->max_height; - min->width = state->min_width; - min->height = state->min_height; -} - static inline const char * client_get_title(Client *c) { @@ -151,20 +163,6 @@ client_get_title(Client *c) return c->surface.xdg->toplevel->title; } -static inline Client * -client_get_parent(Client *c) -{ - Client *p; -#ifdef XWAYLAND - if (client_is_x11(c) && c->surface.xwayland->parent) - return client_from_wlr_surface(c->surface.xwayland->parent->surface); -#endif - if (c->surface.xdg->toplevel->parent) - return client_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface); - - return NULL; -} - static inline int client_is_float_type(Client *c) { @@ -183,16 +181,11 @@ client_is_float_type(Client *c) || surface->window_type[i] == netatom[NetWMWindowTypeToolbar] || surface->window_type[i] == netatom[NetWMWindowTypeUtility]) return 1; - - return ((min.width > 0 || min.height > 0 || max.width > 0 || max.height > 0) - && (min.width == max.width || min.height == max.height)) - || c->surface.xwayland->parent; } #endif - return ((min.width > 0 || min.height > 0 || max.width > 0 || max.height > 0) && (min.width == max.width || min.height == max.height)) - || c->surface.xdg->toplevel->parent; + || client_get_parent(c); } static inline int @@ -205,16 +198,6 @@ client_is_mapped(Client *c) return c->surface.xdg->mapped; } -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; -} - static inline int client_is_unmanaged(Client *c) { @@ -224,6 +207,27 @@ client_is_unmanaged(Client *c) 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_restack_surface(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + wlr_xwayland_surface_restack(c->surface.xwayland, NULL, + XCB_STACK_MODE_ABOVE); +#endif + return; +} + static inline void client_send_close(Client *c) { @@ -282,15 +286,14 @@ client_surface_at(Client *c, double cx, double cy, double *sx, double *sy) return wlr_xdg_surface_surface_at(c->surface.xdg, cx, cy, sx, sy); } -static inline void -client_restack_surface(Client *c) +static inline int +client_wants_fullscreen(Client *c) { #ifdef XWAYLAND if (client_is_x11(c)) - wlr_xwayland_surface_restack(c->surface.xwayland, NULL, - XCB_STACK_MODE_ABOVE); + return c->surface.xwayland->fullscreen; #endif - return; + return c->surface.xdg->toplevel->requested.fullscreen; } static inline void * diff --git a/config.def.h b/config.def.h index 29c6dbf..ec1f052 100644 --- a/config.def.h +++ b/config.def.h @@ -98,7 +98,7 @@ static const double accel_speed = 0.0; #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } /* commands */ -static const char *termcmd[] = { "alacritty", NULL }; +static const char *termcmd[] = { "foot", NULL }; static const char *menucmd[] = { "bemenu-run", NULL }; static const Key keys[] = { diff --git a/config.mk b/config.mk index c7b5d16..3e21cc1 100644 --- a/config.mk +++ b/config.mk @@ -1,13 +1,12 @@ -_VERSION = 0.3.1 +_VERSION = 0.3.1-dev VERSION = `git describe --long --tags --dirty 2>/dev/null || echo $(_VERSION)` +PKG_CONFIG = pkg-config + # paths PREFIX = /usr/local MANDIR = $(PREFIX)/share/man -# Compile flags that can be used -#CFLAGS = -pedantic -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare -Wno-unused-function -Wno-unused-variable -Wno-unused-result -Wdeclaration-after-statement - XWAYLAND = XLIBS = # Uncomment to build XWayland support diff --git a/dwl.c b/dwl.c index b80f3aa..eb072bb 100644 --- a/dwl.c +++ b/dwl.c @@ -1,7 +1,6 @@ /* * See LICENSE file for copyright and license details. */ -#define _POSIX_C_SOURCE 200809L #include #include #include @@ -70,7 +69,7 @@ #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) /* enums */ -enum { CurNormal, CurMove, CurResize }; /* cursor */ +enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ enum { XDGShell, LayerShell, X11Managed, X11Unmanaged }; /* client types */ enum { LyrBg, LyrBottom, LyrTop, LyrOverlay, LyrTile, LyrFloat, LyrNoFocus, NUM_LAYERS }; /* scene layers */ #ifdef XWAYLAND @@ -210,6 +209,7 @@ static void arrangelayers(Monitor *m); static void axisnotify(struct wl_listener *listener, void *data); static void buttonpress(struct wl_listener *listener, void *data); static void chvt(const Arg *arg); +static void checkidleinhibitor(struct wlr_surface *exclude); static void cleanup(void); static void cleanupkeyboard(struct wl_listener *listener, void *data); static void cleanupmon(struct wl_listener *listener, void *data); @@ -292,8 +292,9 @@ static void zoom(const Arg *arg); /* variables */ static const char broken[] = "broken"; +static const char *cursor_image = "left_ptr"; static pid_t child_pid = -1; -static struct wlr_surface *exclusive_focus; +static void *exclusive_focus; static struct wl_display *dpy; static struct wlr_backend *backend; static struct wlr_scene *scene; @@ -384,6 +385,8 @@ applybounds(Client *c, struct wlr_box *bbox) /* try to set size hints */ c->geom.width = MAX(min.width + (2 * c->bw), c->geom.width); c->geom.height = MAX(min.height + (2 * c->bw), c->geom.height); + /* Some clients set them max size to INT_MAX, which does not violates + * the protocol but its innecesary, they can set them max size to zero. */ if (max.width > 0 && !(2 * c->bw > INT_MAX - max.width)) // Checks for overflow c->geom.width = MIN(max.width + (2 * c->bw), c->geom.width); if (max.height > 0 && !(2 * c->bw > INT_MAX - max.height)) // Checks for overflow @@ -435,9 +438,10 @@ arrange(Monitor *m) { Client *c; wl_list_for_each(c, &clients, link) - wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, c->mon)); + if (c->mon == m) + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); - if (m->lt[m->sellt]->arrange) + if (m && m->lt[m->sellt]->arrange) m->lt[m->sellt]->arrange(m); motionnotify(0); } @@ -452,7 +456,9 @@ arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int struct wlr_layer_surface_v1 *wlr_layer_surface = layersurface->layer_surface; struct wlr_layer_surface_v1_state *state = &wlr_layer_surface->current; - if (exclusive != (state->exclusive_zone > 0)) + /* Unmapped surfaces shouldn't have exclusive zone */ + if (!((LayerSurface *)wlr_layer_surface->data)->mapped + || exclusive != (state->exclusive_zone > 0)) continue; wlr_scene_layer_surface_v1_configure(layersurface->scene_layer, &full_area, usable_area); @@ -469,7 +475,8 @@ arrangelayers(Monitor *m) ZWLR_LAYER_SHELL_V1_LAYER_TOP, }; LayerSurface *layersurface; - struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); + if (!m->wlr_output->enabled) + return; /* Arrange exclusive surfaces from top->bottom */ for (i = 3; i >= 0; i--) @@ -485,19 +492,15 @@ arrangelayers(Monitor *m) arrangelayer(m, &m->layers[i], &usable_area, 0); /* Find topmost keyboard interactive layer, if such a layer exists */ - for (size_t i = 0; i < LENGTH(layers_above_shell); i++) { + for (i = 0; i < LENGTH(layers_above_shell); i++) { wl_list_for_each_reverse(layersurface, &m->layers[layers_above_shell[i]], link) { if (layersurface->layer_surface->current.keyboard_interactive && - layersurface->layer_surface->mapped) { + layersurface->mapped) { /* Deactivate the focused client. */ focusclient(NULL, 0); - exclusive_focus = layersurface->layer_surface->surface; - if (kb) - wlr_seat_keyboard_notify_enter(seat, exclusive_focus, - kb->keycodes, kb->num_keycodes, &kb->modifiers); - else - wlr_seat_keyboard_notify_enter(seat, exclusive_focus, NULL, 0, NULL); + exclusive_focus = layersurface; + client_notify_enter(layersurface->layer_surface->surface, wlr_seat_get_keyboard(seat)); return; } } @@ -511,6 +514,8 @@ axisnotify(struct wl_listener *listener, void *data) * for example when you move the scroll wheel. */ struct wlr_pointer_axis_event *event = data; wlr_idle_notify_activity(idle, seat); + /* TODO: allow usage of scroll whell for mousebindings, it can be implemented + * checking the event's orientation and the delta of the event */ /* Notify the client with pointer focus of the axis event. */ wlr_seat_pointer_notify_axis(seat, event->time_msec, event->orientation, event->delta, @@ -545,17 +550,23 @@ buttonpress(struct wl_listener *listener, void *data) return; } } + cursor_mode = CurPressed; break; case WLR_BUTTON_RELEASED: /* If you released any buttons, we exit interactive move/resize mode. */ - /* TODO should reset to the pointer focus's current setcursor */ - if (cursor_mode != CurNormal) { - wlr_xcursor_manager_set_cursor_image(cursor_mgr, "left_ptr", cursor); + if (cursor_mode != CurNormal && cursor_mode != CurPressed) { cursor_mode = CurNormal; + /* Clear the pointer focus, this way if the cursor is over a surface + * we will send an enter event after which the client will provide us + * a cursor surface */ + wlr_seat_pointer_clear_focus(seat); + motionnotify(0); /* Drop the window off on its new monitor */ selmon = xytomon(cursor->x, cursor->y); setmon(grabc, selmon, 0); return; + } else { + cursor_mode = CurNormal; } break; } @@ -571,6 +582,27 @@ chvt(const Arg *arg) wlr_session_change_vt(wlr_backend_get_session(backend), arg->ui); } +void +checkidleinhibitor(struct wlr_surface *exclude) +{ + int inhibited = 0; + struct wlr_idle_inhibitor_v1 *inhibitor; + wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { + Client *c; + if (exclude == inhibitor->surface) + continue; + /* In case we can't get a client from the surface assume that it is + * visible, for example a layer surface */ + if (!(c = client_from_wlr_surface(inhibitor->surface)) + || VISIBLEON(c, c->mon)) { + inhibited = 1; + break; + } + } + + wlr_idle_set_enabled(idle, NULL, !inhibited); +} + void cleanup(void) { @@ -583,6 +615,8 @@ cleanup(void) waitpid(child_pid, NULL, 0); } wlr_backend_destroy(backend); + wlr_renderer_destroy(drw); + wlr_allocator_destroy(alloc); wlr_xcursor_manager_destroy(cursor_mgr); wlr_cursor_destroy(cursor); wlr_output_layout_destroy(output_layout); @@ -593,7 +627,7 @@ cleanup(void) void cleanupkeyboard(struct wl_listener *listener, void *data) { - Keyboard *kb = wlr_keyboard_from_input_device(data)->data; + Keyboard *kb = wl_container_of(listener, kb, destroy); wl_list_remove(&kb->link); wl_list_remove(&kb->modifiers.link); @@ -605,13 +639,13 @@ cleanupkeyboard(struct wl_listener *listener, void *data) void cleanupmon(struct wl_listener *listener, void *data) { - struct wlr_output *wlr_output = data; - Monitor *m = wlr_output->data; + Monitor *m = wl_container_of(listener, m, destroy); int nmons, i = 0; wl_list_remove(&m->destroy.link); wl_list_remove(&m->frame.link); wl_list_remove(&m->link); + m->wlr_output->data = NULL; wlr_output_layout_remove(output_layout, m->wlr_output); wlr_scene_output_destroy(m->scene_output); @@ -636,7 +670,7 @@ closemon(Monitor *m) resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, .width = c->geom.width, .height = c->geom.height}, 0); if (c->mon == m) - setmon(c, selmon, c->tags); + setmon(c, selmon == m ? NULL : selmon, c->tags); } printstatus(); } @@ -648,23 +682,24 @@ commitlayersurfacenotify(struct wl_listener *listener, void *data) struct wlr_layer_surface_v1 *wlr_layer_surface = layersurface->layer_surface; struct wlr_output *wlr_output = wlr_layer_surface->output; + /* For some reason this layersurface have no monitor, this can be because + * its monitor has just been destroyed */ if (!wlr_output || !(layersurface->mon = wlr_output->data)) return; - wlr_scene_node_reparent(&layersurface->scene->node, - layers[wlr_layer_surface->current.layer]); + if (layers[wlr_layer_surface->current.layer] != layersurface->scene->node.parent) { + wlr_scene_node_reparent(&layersurface->scene->node, + layers[wlr_layer_surface->current.layer]); + wl_list_remove(&layersurface->link); + wl_list_insert(&layersurface->mon->layers[wlr_layer_surface->current.layer], + &layersurface->link); + } if (wlr_layer_surface->current.committed == 0 && layersurface->mapped == wlr_layer_surface->mapped) return; - layersurface->mapped = wlr_layer_surface->mapped; - if (layers[wlr_layer_surface->current.layer] != layersurface->scene) { - wl_list_remove(&layersurface->link); - wl_list_insert(&layersurface->mon->layers[wlr_layer_surface->current.layer], - &layersurface->link); - } arrangelayers(layersurface->mon); } @@ -692,7 +727,7 @@ createidleinhibitor(struct wl_listener *listener, void *data) struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; wl_signal_add(&idle_inhibitor->events.destroy, &idle_inhibitor_destroy); - wlr_idle_set_enabled(idle, seat, 0); + checkidleinhibitor(NULL); } void @@ -731,14 +766,16 @@ createlayersurface(struct wl_listener *listener, void *data) LayerSurface *layersurface; struct wlr_layer_surface_v1_state old_state; - if (!wlr_layer_surface->output) { - wlr_layer_surface->output = selmon->wlr_output; - } + if (!wlr_layer_surface->output) + wlr_layer_surface->output = selmon ? selmon->wlr_output : NULL; + + if (!wlr_layer_surface->output) + wlr_layer_surface_v1_destroy(wlr_layer_surface); layersurface = ecalloc(1, sizeof(LayerSurface)); layersurface->type = LayerShell; LISTEN(&wlr_layer_surface->surface->events.commit, - &layersurface->surface_commit, commitlayersurfacenotify); + &layersurface->surface_commit, commitlayersurfacenotify); LISTEN(&wlr_layer_surface->events.destroy, &layersurface->destroy, destroylayersurfacenotify); LISTEN(&wlr_layer_surface->events.map, &layersurface->map, @@ -747,8 +784,8 @@ createlayersurface(struct wl_listener *listener, void *data) unmaplayersurfacenotify); layersurface->layer_surface = wlr_layer_surface; - wlr_layer_surface->data = layersurface; layersurface->mon = wlr_layer_surface->output->data; + wlr_layer_surface->data = layersurface; layersurface->scene_layer = wlr_scene_layer_surface_v1_create( layers[wlr_layer_surface->pending.layer], wlr_layer_surface); @@ -765,6 +802,7 @@ createlayersurface(struct wl_listener *listener, void *data) */ old_state = wlr_layer_surface->current; wlr_layer_surface->current = wlr_layer_surface->pending; + layersurface->mapped = 1; arrangelayers(layersurface->mon); wlr_layer_surface->current = old_state; } @@ -776,14 +814,14 @@ createmon(struct wl_listener *listener, void *data) * monitor) becomes available. */ struct wlr_output *wlr_output = data; const MonitorRule *r; - Client *c; + size_t i; Monitor *m = wlr_output->data = ecalloc(1, sizeof(*m)); m->wlr_output = wlr_output; wlr_output_init_render(wlr_output, alloc, drw); /* Initialize monitor state using configured rules */ - for (size_t i = 0; i < LENGTH(m->layers); i++) + for (i = 0; i < LENGTH(m->layers); i++) wl_list_init(&m->layers[i]); m->tagset[0] = m->tagset[1] = 1; for (r = monrules; r < END(monrules); r++) { @@ -846,9 +884,14 @@ createnotify(struct wl_listener *listener, void *data) LayerSurface *l = toplevel_from_popup(xdg_surface->popup); xdg_surface->surface->data = wlr_scene_xdg_surface_create( xdg_surface->popup->parent->data, xdg_surface); + /* Raise to top layer if the inmediate parent of the popup is on + * bottom/background layer, which will cause popups appear below the + * x{dg,wayland} clients */ if (wlr_surface_is_layer_surface(xdg_surface->popup->parent) && l && l->layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) wlr_scene_node_reparent(xdg_surface->surface->data, layers[LyrTop]); + /* Probably the check of `l` is useless, the only thing that can be NULL + * is its monitor */ if (!l || !l->mon) return; box = l->type == LayerShell ? l->mon->m : l->mon->w; @@ -931,9 +974,9 @@ cursorframe(struct wl_listener *listener, void *data) void destroyidleinhibitor(struct wl_listener *listener, void *data) { - /* I've been testing and at this point the inhibitor has not yet been - * removed from the list, checking if it has at least one item. */ - wlr_idle_set_enabled(idle, seat, wl_list_length(&idle_inhibit_mgr->inhibitors) <= 1); + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(data); } void @@ -947,11 +990,6 @@ destroylayersurfacenotify(struct wl_listener *listener, void *data) wl_list_remove(&layersurface->unmap.link); wl_list_remove(&layersurface->surface_commit.link); wlr_scene_node_destroy(&layersurface->scene->node); - if (layersurface->layer_surface->output) { - if ((layersurface->mon = layersurface->layer_surface->output->data)) - arrangelayers(layersurface->mon); - layersurface->layer_surface->output = NULL; - } free(layersurface); } @@ -1003,11 +1041,7 @@ void focusclient(Client *c, int lift) { struct wlr_surface *old = seat->keyboard_state.focused_surface; - struct wlr_keyboard *kb; int i; - /* Do not focus clients if a layer surface is focused */ - if (exclusive_focus) - return; /* Raise client in stacking order if requested */ if (c && lift) @@ -1024,25 +1058,25 @@ focusclient(Client *c, int lift) c->isurgent = 0; client_restack_surface(c); - for (i = 0; i < 4; i++) - wlr_scene_rect_set_color(c->border[i], focuscolor); + /* Don't change border color if there is a exclusive focus + * (at this moment it means that a layer surface is focused) */ + if (!exclusive_focus) + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], focuscolor); } /* Deactivate old client if focus is changing */ if (old && (!c || client_surface(c) != old)) { /* If an overlay is focused, don't focus or activate the client, * but only update its position in fstack to render its border with focuscolor - * and focus it after the overlay is closed. - * It's probably pointless to check if old is a layer surface - * since it can't be anything else at this point. */ + * and focus it after the overlay is closed. */ if (wlr_surface_is_layer_surface(old)) { struct wlr_layer_surface_v1 *wlr_layer_surface = wlr_layer_surface_v1_from_wlr_surface(old); - if (wlr_layer_surface->mapped && ( - wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP || - wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY - )) + if (wlr_layer_surface && ((LayerSurface *)wlr_layer_surface->data)->mapped + && (wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP + || wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY)) return; } else { Client *w; @@ -1055,7 +1089,7 @@ focusclient(Client *c, int lift) } printstatus(); - wlr_idle_set_enabled(idle, seat, wl_list_empty(&idle_inhibit_mgr->inhibitors)); + checkidleinhibitor(NULL); if (!c) { /* With no client, all we have left is to clear focus */ @@ -1063,13 +1097,11 @@ focusclient(Client *c, int lift) return; } + /* Change cursor surface */ + motionnotify(0); + /* Have a client, so focus its top-level wlr_surface */ - kb = wlr_seat_get_keyboard(seat); - if (kb) - wlr_seat_keyboard_notify_enter(seat, client_surface(c), - kb->keycodes, kb->num_keycodes, &kb->modifiers); - else - wlr_seat_keyboard_notify_enter(seat, client_surface(c), NULL, 0, NULL); + client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); /* Activate the new client */ client_activate_surface(client_surface(c), 1); @@ -1112,6 +1144,9 @@ focusstack(const Arg *arg) focusclient(c, 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 */ Client * focustop(Monitor *m) { @@ -1126,19 +1161,14 @@ void fullscreennotify(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, fullscreen); - int fullscreen = client_wants_fullscreen(c); - - if (!c->mon) { - /* if the client is not mapped yet, let mapnotify() call setfullscreen() */ - c->isfullscreen = fullscreen; - return; - } - setfullscreen(c, fullscreen); + setfullscreen(c, client_wants_fullscreen(c)); } void incnmaster(const Arg *arg) { + if (!arg || !selmon) + return; selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); arrange(selmon); } @@ -1257,10 +1287,8 @@ killclient(const Arg *arg) void maplayersurfacenotify(struct wl_listener *listener, void *data) { - LayerSurface *layersurface = wl_container_of(listener, layersurface, map); - layersurface->mon = layersurface->layer_surface->output->data; - wlr_surface_send_enter(layersurface->layer_surface->surface, - layersurface->mon->wlr_output); + LayerSurface *l = wl_container_of(listener, l, map); + wlr_surface_send_enter(l->layer_surface->surface, l->mon->wlr_output); motionnotify(0); } @@ -1284,14 +1312,17 @@ mapnotify(struct wl_listener *listener, void *data) } c->scene->node.data = c->scene_surface->node.data = c; +#ifdef XWAYLAND + /* Handle unmanaged clients first so we can return prior create borders */ if (client_is_unmanaged(c)) { client_get_geometry(c, &c->geom); - /* Floating */ + /* Unmanaged clients always are floating */ wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); wlr_scene_node_set_position(&c->scene->node, c->geom.x + borderpx, c->geom.y + borderpx); return; } +#endif for (i = 0; i < 4; i++) { c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, bordercolor); @@ -1309,21 +1340,19 @@ mapnotify(struct wl_listener *listener, void *data) wl_list_insert(&clients, &c->link); wl_list_insert(&fstack, &c->flink); - /* Set initial monitor, tags, floating status, and focus */ + /* Set initial monitor, tags, floating status, and focus: + * we always consider floating, clients that have parent and thus + * we set the same tags and monitor than its parent, if not + * try to apply rules for them */ if ((p = client_get_parent(c))) { - /* Set the same monitor and tags than its parent */ c->isfloating = 1; wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); - /* TODO recheck if !p->mon is possible with wlroots 0.16.0 */ - setmon(c, p->mon ? p->mon : selmon, p->tags); + setmon(c, p->mon, p->tags); } else { applyrules(c); } printstatus(); - if (c->isfullscreen) - setfullscreen(c, 1); - c->mon->un_map = 1; } @@ -1383,6 +1412,7 @@ motionnotify(uint32_t time) selmon = xytomon(cursor->x, cursor->y); } + /* Update drag icon's position if any */ if (seat->drag && (icon = seat->drag->icon)) wlr_scene_node_set_position(icon->data, cursor->x + icon->surface->sx, cursor->y + icon->surface->sy); @@ -1401,11 +1431,18 @@ motionnotify(uint32_t time) /* Find the client under the pointer and send the event along. */ xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + if (cursor_mode == CurPressed) { + surface = seat->pointer_state.focused_surface; + c = client_from_wlr_surface(surface); + sx = c ? cursor->x - c->geom.x : 0; + sy = c ? cursor->y - c->geom.y : 0; + } + /* If there's no client surface under the cursor, set the cursor image to a * default. This is what makes the cursor image appear when you move it * off of a client or over its border. */ - if (!surface && time) - wlr_xcursor_manager_set_cursor_image(cursor_mgr, "left_ptr", cursor); + if (!surface && (!cursor_image || strcmp(cursor_image, "left_ptr"))) + wlr_xcursor_manager_set_cursor_image(cursor_mgr, (cursor_image = "left_ptr"), cursor); pointerfocus(c, surface, sx, sy, time); } @@ -1440,7 +1477,7 @@ moveresize(const Arg *arg) case CurMove: grabcx = cursor->x - grabc->geom.x; grabcy = cursor->y - grabc->geom.y; - wlr_xcursor_manager_set_cursor_image(cursor_mgr, "fleur", cursor); + wlr_xcursor_manager_set_cursor_image(cursor_mgr, (cursor_image = "fleur"), cursor); break; case CurResize: /* Doesn't work for X11 output - the next absolute motion event @@ -1449,7 +1486,7 @@ moveresize(const Arg *arg) grabc->geom.x + grabc->geom.width, grabc->geom.y + grabc->geom.height); wlr_xcursor_manager_set_cursor_image(cursor_mgr, - "bottom_right_corner", cursor); + (cursor_image = "bottom_right_corner"), cursor); break; } } @@ -1490,6 +1527,7 @@ outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) /* Then enable outputs that need to */ wl_list_for_each(config_head, &config->heads, link) { struct wlr_output *wlr_output = config_head->state.output; + Monitor *m = wlr_output->data; if (!config_head->state.enabled) continue; @@ -1502,8 +1540,11 @@ outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) config_head->state.custom_mode.height, config_head->state.custom_mode.refresh); - wlr_output_layout_move(output_layout, wlr_output, - config_head->state.x, config_head->state.y); + /* Don't move monitors if position wouldn't change, this to avoid + * wlroots marking the output as manually configured */ + if (m->m.x != config_head->state.x || m->m.y != config_head->state.y) + wlr_output_layout_move(output_layout, wlr_output, + config_head->state.x, config_head->state.y); wlr_output_set_transform(wlr_output, config_head->state.transform); wlr_output_set_scale(wlr_output, config_head->state.scale); @@ -1571,7 +1612,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, * wlroots makes this a no-op if surface is already focused */ wlr_seat_pointer_notify_enter(seat, surface, sx, sy); wlr_seat_pointer_notify_motion(seat, time, sx, sy); - } void @@ -1684,6 +1724,8 @@ resize(Client *c, struct wlr_box geo, int interact) wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + if (c->fullscreen_bg) + wlr_scene_rect_set_size(c->fullscreen_bg, c->geom.width, c->geom.height); /* wlroots makes this a no-op if size hasn't changed */ c->resize = client_set_size(c, c->geom.width - 2 * c->bw, @@ -1699,7 +1741,12 @@ run(char *startup_cmd) die("startup: display_add_socket_auto"); setenv("WAYLAND_DISPLAY", socket, 1); - /* Now that the socket exists, run the startup command */ + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ if (startup_cmd) { int piperw[2]; if (pipe(piperw) < 0) @@ -1721,12 +1768,7 @@ run(char *startup_cmd) signal(SIGPIPE, SIG_IGN); printstatus(); - /* Start the backend. This will enumerate outputs and inputs, become the DRM - * master, etc */ - if (!wlr_backend_start(backend)) - die("startup: backend_start"); - - /* Now that outputs are initialized, choose initial selmon based on + /* At this point the outputs are initialized, choose initial selmon based on * cursor position, and set default cursor image */ selmon = xytomon(cursor->x, cursor->y); @@ -1735,7 +1777,7 @@ run(char *startup_cmd) * initialized, as the image/coordinates are not transformed for the * monitor when displayed here */ wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); - wlr_xcursor_manager_set_cursor_image(cursor_mgr, "left_ptr", cursor); + wlr_xcursor_manager_set_cursor_image(cursor_mgr, cursor_image, cursor); /* Run the Wayland event loop. This does not return until you exit the * compositor. Starting the backend rigged up all of the necessary event @@ -1758,10 +1800,12 @@ setcursor(struct wl_listener *listener, void *data) { /* This event is raised by the seat when a client provides a cursor image */ struct wlr_seat_pointer_request_set_cursor_event *event = data; - /* If we're "grabbing" the cursor, don't use the client's image */ - /* TODO still need to save the provided surface to restore later */ + /* If we're "grabbing" the cursor, don't use the client's image, we will + * restore it after "grabbing" sending a leave event, followed by a enter + * event, which will result in the client requesting set the cursor surface */ if (cursor_mode != CurNormal) return; + cursor_image = NULL; /* This can be sent by any client, so we check to make sure this one is * actually has pointer focus first. If so, we can tell the cursor to * use the provided surface as the cursor image. It will set the @@ -1785,6 +1829,8 @@ void setfullscreen(Client *c, int fullscreen) { c->isfullscreen = fullscreen; + if (!c->mon) + return; c->bw = fullscreen ? 0 : borderpx; client_set_fullscreen(c, fullscreen); @@ -1821,6 +1867,8 @@ setfullscreen(Client *c, int fullscreen) void setlayout(const Arg *arg) { + if (!selmon) + return; if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) selmon->sellt ^= 1; if (arg && arg->v) @@ -1836,7 +1884,7 @@ setmfact(const Arg *arg) { float f; - if (!arg || !selmon->lt[selmon->sellt]->arrange) + if (!arg || !selmon || !selmon->lt[selmon->sellt]->arrange) return; f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; if (f < 0.1 || f > 0.9) @@ -1853,6 +1901,7 @@ setmon(Client *c, Monitor *m, unsigned int newtags) if (oldmon == m) return; c->mon = m; + c->prev = c->geom; /* TODO leave/enter is not optimal but works */ if (oldmon) { @@ -1864,7 +1913,7 @@ setmon(Client *c, Monitor *m, unsigned int newtags) resize(c, c->geom, 0); wlr_surface_send_enter(client_surface(c), m->wlr_output); c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ - arrange(m); + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ } focusclient(focustop(selmon), 1); } @@ -2112,9 +2161,8 @@ void tagmon(const Arg *arg) { Client *sel = selclient(); - if (!sel) - return; - setmon(sel, dirtomon(arg->i), 0); + if (sel) + setmon(sel, dirtomon(arg->i), 0); } void @@ -2186,7 +2234,7 @@ toggletag(const Arg *arg) void toggleview(const Arg *arg) { - unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + unsigned int newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0; if (newtagset) { selmon->tagset[selmon->seltags] = newtagset; @@ -2201,10 +2249,13 @@ unmaplayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *layersurface = wl_container_of(listener, layersurface, unmap); - layersurface->layer_surface->mapped = (layersurface->mapped = 0); + layersurface->mapped = 0; wlr_scene_node_set_enabled(&layersurface->scene->node, 0); - if (layersurface->layer_surface->surface == exclusive_focus) + if (layersurface == exclusive_focus) exclusive_focus = NULL; + if (layersurface->layer_surface->output + && (layersurface->mon = layersurface->layer_surface->output->data)) + arrangelayers(layersurface->mon); if (layersurface->layer_surface->surface == seat->keyboard_state.focused_surface) focusclient(selclient(), 1); @@ -2224,17 +2275,18 @@ unmapnotify(struct wl_listener *listener, void *data) if (c->mon) c->mon->un_map = 1; - if (client_is_unmanaged(c)) { - wlr_scene_node_destroy(&c->scene->node); - return; - } + if (client_is_unmanaged(c)) + goto end; wl_list_remove(&c->link); setmon(c, NULL, 0); wl_list_remove(&c->flink); + +end: wl_list_remove(&c->commit.link); wlr_scene_node_destroy(&c->scene->node); printstatus(); + motionnotify(0); } void @@ -2304,7 +2356,7 @@ urgent(struct wl_listener *listener, void *data) void view(const Arg *arg) { - if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) return; selmon->seltags ^= 1; /* toggle sel tagset */ if (arg->ui & TAGMASK) @@ -2367,7 +2419,7 @@ zoom(const Arg *arg) { Client *c, *sel = selclient(); - if (!sel || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) return; /* Search for the first tiled window that is not sel, marking sel as @@ -2419,6 +2471,7 @@ createnotifyx11(struct wl_listener *listener, void *data) { struct wlr_xwayland_surface *xwayland_surface = data; Client *c; + /* TODO: why we unset fullscreen when a xwayland client is created? */ wl_list_for_each(c, &clients, link) if (c->isfullscreen && VISIBLEON(c, c->mon)) setfullscreen(c, 0);