diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9b9eef4..6b60803 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,4 +7,16 @@ assignees: '' --- +## Info +dwl version: +wlroots version: +## Description + diff --git a/LICENSE b/LICENSE index e4bb015..658085a 100644 --- a/LICENSE +++ b/LICENSE @@ -2,7 +2,7 @@ dwl - dwm for Wayland Copyright © 2020 dwl team -See also the files LICENSE.tinywl and LICENSE.dwm. +See also the files LICENSE.tinywl, LICENSE.dwm and LICENSE.sway. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/LICENSE.sway b/LICENSE.sway new file mode 100644 index 0000000..3e0cacc --- /dev/null +++ b/LICENSE.sway @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Drew DeVault + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index 1701b63..078ced5 100644 --- a/Makefile +++ b/Makefile @@ -1,63 +1,60 @@ +.POSIX: +.SUFFIXES: + include config.mk -CFLAGS += -I. -DWLR_USE_UNSTABLE -std=c99 +# flags for compiling +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\ + -Werror=strict-prototypes -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types -WAYLAND_PROTOCOLS=$(shell pkg-config --variable=pkgdatadir wayland-protocols) -WAYLAND_SCANNER=$(shell pkg-config --variable=wayland_scanner wayland-scanner) - -PKGS = wlroots wayland-server xcb xkbcommon libinput -CFLAGS += $(foreach p,$(PKGS),$(shell pkg-config --cflags $(p))) -LDLIBS += $(foreach p,$(PKGS),$(shell pkg-config --libs $(p))) +# CFLAGS / LDFLAGS +PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) +DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) +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 push.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. +WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner` +WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols` + xdg-shell-protocol.h: $(WAYLAND_SCANNER) server-header \ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ - -xdg-shell-protocol.c: - $(WAYLAND_SCANNER) private-code \ - $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ - -xdg-shell-protocol.o: xdg-shell-protocol.h - wlr-layer-shell-unstable-v1-protocol.h: $(WAYLAND_SCANNER) server-header \ protocols/wlr-layer-shell-unstable-v1.xml $@ -wlr-layer-shell-unstable-v1-protocol.c: - $(WAYLAND_SCANNER) private-code \ - protocols/wlr-layer-shell-unstable-v1.xml $@ - -wlr-layer-shell-unstable-v1-protocol.o: wlr-layer-shell-unstable-v1-protocol.h - -idle-protocol.h: - $(WAYLAND_SCANNER) server-header \ - protocols/idle.xml $@ - -idle-protocol.c: - $(WAYLAND_SCANNER) private-code \ - protocols/idle.xml $@ - -idle-protocol.o: idle-protocol.h - -config.h: | config.def.h +config.h: cp config.def.h $@ - -dwl.o: config.h client.h xdg-shell-protocol.h wlr-layer-shell-unstable-v1-protocol.h idle-protocol.h - -dwl.o: push.c - -dwl: xdg-shell-protocol.o wlr-layer-shell-unstable-v1-protocol.o idle-protocol.o - clean: - rm -f dwl *.o *-protocol.h *-protocol.c + rm -f dwl *.o *-protocol.h + +dist: clean + mkdir -p dwl-$(VERSION) + cp -R LICENSE* Makefile README.md client.h config.def.h\ + config.mk protocols dwl.1 dwl.c util.c util.h\ + dwl-$(VERSION) + tar -caf dwl-$(VERSION).tar.gz dwl-$(VERSION) + rm -rf dwl-$(VERSION) install: dwl - install -D dwl $(PREFIX)/bin/dwl + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dwl $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl + mkdir -p $(DESTDIR)$(MANDIR)/man1 + cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1 + chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1 +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 -.DEFAULT_GOAL=dwl -.PHONY: clean +.SUFFIXES: .c .o +.c.o: + $(CC) $(CPPFLAGS) $(DWLCFLAGS) -c $< diff --git a/README.md b/README.md index 260280a..05f149c 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,157 @@ # dwl - dwm for Wayland -Join us on our [Discord server](https://discord.gg/jJxZnrGPWN)! +Join us on our [Discord server] or at [#dwl] on irc.libera.chat. -dwl is a compact, hackable compositor for Wayland based on [wlroots](https://github.com/swaywm/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: +dwl is a compact, hackable compositor for [Wayland] based on [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: - Easy to understand, hack on, and extend with patches - One C source file (or a very small number) configurable via `config.h` -- Limited to 2000 SLOC to promote hackability +- Limited to 2200 SLOC to promote hackability - Tied to as few external dependencies as possible -dwl is not meant to provide every feature under the sun. Instead, like dwm, it sticks to features which are necessary, simple, and straightforward to implement given the base on which it is built. Implemented default features are: +dwl is not meant to provide every feature under the sun. Instead, like dwm, it +sticks to features which are necessary, simple, and straightforward to implement +given the base on which it is built. Implemented default features are: -- Any features provided by dwm/Xlib: simple window borders, tags, keybindings, client rules, mouse move/resize. Providing a built-in status bar is an exception to this goal, to avoid dependencies on font rendering and/or drawing libraries when an external bar could work well. +- Any features provided by dwm/Xlib: simple window borders, tags, keybindings, + client rules, mouse move/resize. Providing a built-in status bar is an + exception to this goal, to avoid dependencies on font rendering and/or + drawing libraries when an external bar could work well. - Configurable multi-monitor layout support, including position and rotation - Configurable HiDPI/multi-DPI support +- Idle-inhibit protocol which lets applications such as mpv disable idle + monitoring +- Provide information to external status bars via stdout/stdin +- Urgency hints via xdg-activate protocol +- Support screen lockers via input-inhibitor protocol - Various Wayland protocols -- XWayland support as provided by wlroots +- XWayland support as provided by wlroots (can be enabled in `config.mk`) - Zero flickering - Wayland users naturally expect that "every frame is perfect" +- Layer shell popups (used by Waybar) +- Damage tracking provided by scenegraph API Features under consideration (possibly as patches) are: - Protocols made trivial by wlroots -- Provide information to external status bars via stdout or another file descriptor -- Implement the input-inhibitor protocol to support screen lockers -- Implement the idle-inhibit protocol which lets applications such as mpv disable idle monitoring -- Layer shell popups (used by Waybar) -- Basic yes/no damage tracking to avoid needless redraws -- More in-depth damage region tracking ([which may improve power usage](https://mozillagfx.wordpress.com/2019/10/22/dramatically-reduced-power-usage-in-firefox-70-on-macos-with-core-animation/)) -- 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 urgent/attention/focus-request once it's part of the xdg-shell protocol (https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/9) +- 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* include: +Feature *non-goals* for the main codebase include: - Client-side decoration (any more than is necessary to tell the clients not to) -- Client-initiated window management, such as move, resize, and close, which can be done through the compositor +- Client-initiated window management, such as move, resize, and close, which can + be done through the compositor +- Animations and visual effects ## Building dwl -dwl has only two dependencies: wlroots-git and wayland-protocols. Simply install these and run `make`. +dwl has only two dependencies: `wlroots` and `wayland-protocols`. -To enable XWayland, you should also install xorg-xwayland and uncomment its flag in `config.mk`. +Simply install these (and their `-devel` versions if your distro has separate +development packages) and run `make`. If you wish to build against a Git +version of wlroots, check out the [wlroots-next branch]. + +To enable XWayland, you should also install xorg-xwayland and uncomment its flag +in `config.mk`. ## Configuration -All configuration is done by editing `config.h` and recompiling, in the same manner as dwm. There is no way to separately restart the window manager in Wayland without restarting the entire display server, so any changes will take effect the next time dwl is executed. +All configuration is done by editing `config.h` and recompiling, in the same +manner as dwm. There is no way to separately restart the window manager in +Wayland without restarting the entire display server, so any changes will take +effect the next time dwl is executed. -As in the dwm community, we encourage users to share patches they have created. Check out the [patches page on our wiki](https://github.com/djpohly/dwl/wiki/Patches)! +As in the dwm community, we encourage users to share patches they have created. +Check out the [patches page on our wiki]! ## Running dwl -dwl can be run as-is, with no arguments. In an existing Wayland or X11 session, this will open a window to act as a virtual display. When run from a TTY, the Wayland server will take over the entire virtual terminal. Clients started by dwl will have `WAYLAND_DISPLAY` set in their environment, and other clients can be started from outside the session by setting this variable accordingly. +dwl can be run on any of the backends supported by wlroots. This means you can +run it as a separate window inside either an X11 or Wayland session, as well +as directly from a VT console. Depending on your distro's setup, you may need +to add your user to the `video` and `input` groups before you can run dwl on +a VT. If you are using `elogind` or `systemd-logind` you need to install +polkit; otherwise you need to add yourself in the `seat` group and +enable/start the seatd daemon. -You can also specify a startup program using the `-s` option. The argument to this option will be run at startup as a shell command (using `sh -c`) and can serve a similar function to `.xinitrc`: starting a service manager or other startup applications. Unlike `.xinitrc`, the display server will not shut down when this process terminates. Instead, as dwl is shutting down, it will send this process a SIGTERM and wait for it to terminate (if it hasn't already). This makes it ideal not only for initialization but also for execing into a user-level service manager like s6 or `systemd --user`. +When dwl is run with no arguments, it will launch the server and begin handling +any shortcuts configured in `config.h`. There is no status bar or other +decoration initially; these are instead clients that can be run within +the Wayland session. -Note: Wayland requires a valid `XDG_RUNTIME_DIR`, which is usually set up by a session manager such as `elogind` or `systemd-logind`. If your system doesn't do this automatically, you will need to configure it prior to launching `dwl`, e.g.: +If you would like to run a script or command automatically at startup, you can +specify the command using the `-s` option. This command will be executed as a +shell command using `/bin/sh -c`. It serves a similar function to `.xinitrc`, +but differs in that the display server will not shut down when this process +terminates. Instead, dwl will send this process a SIGTERM at shutdown and wait +for it to terminate (if it hasn't already). This makes it ideal for execing into +a user service manager like [s6], [anopa], [runit], or [`systemd --user`]. + +Note: The `-s` command is run as a *child process* of dwl, which means that it +does not have the ability to affect the environment of dwl or of any processes +that it spawns. If you need to set environment variables that affect the entire +dwl session, these must be set prior to running dwl. For example, Wayland +requires a valid `XDG_RUNTIME_DIR`, which is usually set up by a session manager +such as `elogind` or `systemd-logind`. If your system doesn't do this +automatically, you will need to configure it prior to launching `dwl`, e.g.: export XDG_RUNTIME_DIR=/tmp/xdg-runtime-$(id -u) mkdir -p $XDG_RUNTIME_DIR + dwl + +### Status information + +Information about selected layouts, current window title, and +selected/occupied/urgent tags is written to the stdin of the `-s` command (see +the `printstatus()` function for details). This information can be used to +populate an external status bar with a script that parses the information. +Failing to read this information will cause dwl to block, so if you do want to +run a startup command that does not consume the status information, you can +close standard input with the `<&-` shell redirection, for example: + + dwl -s 'foot --server <&-' + +If your startup command is a shell script, you can achieve the same inside the +script with the line + + exec <&- + +To get a list of status bars that work with dwl consult our [wiki]. ## 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.freenode.net. +You can find a [list of useful resources on our wiki]. ## Acknowledgements -dwl began by extending the TinyWL example provided (CC0) by the sway/wlroots developers. This was made possible in many cases by looking at how sway accomplished something, then trying to do the same in as suckless a way as possible. +dwl began by extending the TinyWL example provided (CC0) by the sway/wlroots +developers. This was made possible in many cases by looking at how sway +accomplished something, then trying to do the same in as suckless a way as +possible. -Many thanks to suckless.org and the dwm developers and community for the inspiration, and to the various contributors to the project, including: +Many thanks to suckless.org and the dwm developers and community for the +inspiration, and to the various contributors to the project, including: - Alexander Courtis for the XWayland implementation -- Guido Cella for the layer-shell protocol implementation, patch maintenance, and for helping to keep the project running +- Guido Cella for the layer-shell protocol implementation, patch maintenance, + and for helping to keep the project running - Stivvo for output management and fullscreen support, and patch maintenance + + +[Discord server]: https://discord.gg/jJxZnrGPWN +[#dwl]: https://web.libera.chat/?channels=#dwl +[Wayland]: https://wayland.freedesktop.org/ +[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots/ +[wlroots-next branch]: https://github.com/djpohly/dwl/tree/wlroots-next +[patches page on our wiki]: https://github.com/djpohly/dwl/wiki/Patches +[s6]: https://skarnet.org/software/s6/ +[anopa]: https://jjacky.com/anopa/ +[runit]: http://smarden.org/runit/faq.html#userservices +[`systemd --user`]: https://wiki.archlinux.org/title/Systemd/User +[wiki]: https://github.com/djpohly/dwl/wiki#compatible-status-bars +[list of useful resources on our wiki]: + https://github.com/djpohly/dwl/wiki#migrating-from-x diff --git a/client.h b/client.h index f4735c2..4dc9e1a 100644 --- a/client.h +++ b/client.h @@ -5,7 +5,7 @@ * that they will simply compile out if the chosen #defines leave them unused. */ -/* Leave this function first; it's used in the others */ +/* Leave these functions first; they're used in the others */ static inline int client_is_x11(Client *c) { @@ -16,33 +16,116 @@ client_is_x11(Client *c) #endif } +static inline Client * +client_from_wlr_surface(struct wlr_surface *s) +{ + struct wlr_xdg_surface *surface; + +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; + if (s && wlr_surface_is_xwayland_surface(s) + && (xsurface = wlr_xwayland_surface_from_wlr_surface(s))) + return xsurface->data; +#endif + if (s && wlr_surface_is_xdg_surface(s) + && (surface = wlr_xdg_surface_from_wlr_surface(s)) + && surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) + return surface->data; + + if (s && wlr_surface_is_subsurface(s)) + return client_from_wlr_surface(wlr_surface_get_root_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) { + struct wlr_xdg_surface *surface; #ifdef XWAYLAND - if (wlr_surface_is_xwayland_surface(s)) { - wlr_xwayland_surface_activate( - wlr_xwayland_surface_from_wlr_surface(s), activated); + struct wlr_xwayland_surface *xsurface; + if (wlr_surface_is_xwayland_surface(s) + && (xsurface = wlr_xwayland_surface_from_wlr_surface(s))) { + wlr_xwayland_surface_activate(xsurface, activated); return; } #endif - if (wlr_surface_is_xdg_surface(s)) - wlr_xdg_toplevel_set_activated( - wlr_xdg_surface_from_wlr_surface(s), activated); + if (wlr_surface_is_xdg_surface(s) + && (surface = wlr_xdg_surface_from_wlr_surface(s)) + && surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) + wlr_xdg_toplevel_set_activated(surface->toplevel, activated); +} + +static inline uint32_t +client_set_bounds(Client *c, int32_t width, int32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + if (c->surface.xdg->client->shell->version >= + XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) + return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height); + return 0; } static inline void client_for_each_surface(Client *c, wlr_surface_iterator_func_t fn, void *data) { + wlr_surface_for_each_surface(client_surface(c), fn, data); #ifdef XWAYLAND - if (client_is_x11(c)) { - wlr_surface_for_each_surface(c->surface.xwayland->surface, - fn, data); + if (client_is_x11(c)) return; - } #endif - wlr_xdg_surface_for_each_surface(c->surface.xdg, fn, data); + wlr_xdg_surface_for_each_popup_surface(c->surface.xdg, fn, data); } static inline const char * @@ -82,16 +165,50 @@ client_get_title(Client *c) static inline int client_is_float_type(Client *c) +{ + struct wlr_box min = {0}, max = {0}; + client_get_size_hints(c, &max, &min); + +#ifdef XWAYLAND + if (client_is_x11(c)) { + struct wlr_xwayland_surface *surface = c->surface.xwayland; + if (surface->modal) + return 1; + + for (size_t i = 0; i < surface->window_type_len; i++) + if (surface->window_type[i] == netatom[NetWMWindowTypeDialog] + || surface->window_type[i] == netatom[NetWMWindowTypeSplash] + || surface->window_type[i] == netatom[NetWMWindowTypeToolbar] + || surface->window_type[i] == netatom[NetWMWindowTypeUtility]) + return 1; + } +#endif + return ((min.width > 0 || min.height > 0 || max.width > 0 || max.height > 0) + && (min.width == max.width || min.height == max.height)); +} + +static inline int +client_is_mapped(Client *c) { #ifdef XWAYLAND if (client_is_x11(c)) - for (size_t i = 0; i < c->surface.xwayland->window_type_len; i++) - if (c->surface.xwayland->window_type[i] == netatom[NetWMWindowTypeDialog] || - c->surface.xwayland->window_type[i] == netatom[NetWMWindowTypeSplash] || - c->surface.xwayland->window_type[i] == netatom[NetWMWindowTypeToolbar] || - c->surface.xwayland->window_type[i] == netatom[NetWMWindowTypeUtility]) - return 1; + return c->surface.xwayland->mapped; #endif + return c->surface.xdg->mapped; +} + +static inline int +client_is_rendered_on_mon(Client *c, Monitor *m) +{ + /* This is needed for when you don't want to check formal assignment, + * but rather actual displaying of the pixels. + * Usually VISIBLEON suffices and is also faster. */ + struct wlr_surface_output *s; + if (!c->scene->node.enabled) + return 0; + wl_list_for_each(s, &client_surface(c)->current_outputs, link) + if (s->output == m->wlr_output) + return 1; return 0; } @@ -104,6 +221,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) { @@ -113,7 +251,7 @@ client_send_close(Client *c) return; } #endif - wlr_xdg_toplevel_send_close(c->surface.xdg); + wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); } static inline void @@ -125,7 +263,7 @@ client_set_fullscreen(Client *c, int fullscreen) return; } #endif - wlr_xdg_toplevel_set_fullscreen(c->surface.xdg, fullscreen); + wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); } static inline uint32_t @@ -138,17 +276,17 @@ client_set_size(Client *c, uint32_t width, uint32_t height) return 0; } #endif - return wlr_xdg_toplevel_set_size(c->surface.xdg, width, height); + return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, width, height); } -static inline struct wlr_surface * -client_surface(Client *c) +static inline void +client_set_tiled(Client *c, uint32_t edges) { #ifdef XWAYLAND if (client_is_x11(c)) - return c->surface.xwayland->surface; + return; #endif - return c->surface.xdg->surface; + wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); } static inline struct wlr_surface * @@ -161,3 +299,64 @@ client_surface_at(Client *c, double cx, double cy, double *sx, double *sy) #endif return wlr_xdg_surface_surface_at(c->surface.xdg, cx, cy, sx, sy); } + +static inline int +client_wants_focus(Client *c) +{ +#ifdef XWAYLAND + return client_is_unmanaged(c) + && wlr_xwayland_or_surface_wants_focus(c->surface.xwayland) + && wlr_xwayland_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; +#endif + return 0; +} + +static inline int +client_wants_fullscreen(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->fullscreen; +#endif + return c->surface.xdg->toplevel->requested.fullscreen; +} + +static inline void * +toplevel_from_popup(struct wlr_xdg_popup *popup) +{ + struct wlr_xdg_surface *surface = popup->base; + + while (1) { + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_POPUP: + if (!surface->popup->parent) + return NULL; + else if (wlr_surface_is_layer_surface(surface->popup->parent)) + return wlr_layer_surface_v1_from_wlr_surface(surface->popup->parent)->data; + else if (!wlr_surface_is_xdg_surface(surface->popup->parent)) + return NULL; + + surface = wlr_xdg_surface_from_wlr_surface(surface->popup->parent); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + return surface->data; + case WLR_XDG_SURFACE_ROLE_NONE: + return NULL; + } + } +} + +static inline void * +toplevel_from_wlr_layer_surface(struct wlr_surface *s) +{ + Client *c; + struct wlr_layer_surface_v1 *wlr_layer_surface; + + if ((c = client_from_wlr_surface(s))) + return c; + else if (s && wlr_surface_is_layer_surface(s) + && (wlr_layer_surface = wlr_layer_surface_v1_from_wlr_surface(s))) + return wlr_layer_surface->data; + + return NULL; +} diff --git a/config.def.h b/config.def.h index 089aa37..a4f7c13 100644 --- a/config.def.h +++ b/config.def.h @@ -1,9 +1,12 @@ /* appearance */ -static const int sloppyfocus = 1; /* focus follows mouse */ -static const unsigned int borderpx = 1; /* border pixel of windows */ -static const float rootcolor[] = {0.3, 0.3, 0.3, 1.0}; -static const float bordercolor[] = {0.5, 0.5, 0.5, 1.0}; -static const float focuscolor[] = {1.0, 0.0, 0.0, 1.0}; +static const int sloppyfocus = 1; /* focus follows mouse */ +static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +static const unsigned int borderpx = 1; /* border pixel of windows */ +static const float rootcolor[] = {0.3, 0.3, 0.3, 1.0}; +static const float bordercolor[] = {0.5, 0.5, 0.5, 1.0}; +static const float focuscolor[] = {1.0, 0.0, 0.0, 1.0}; +/* To conform the xdg-protocol, set the alpha to zero to restore the old behavior */ +static const float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; /* tagging */ static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; @@ -12,8 +15,8 @@ static const Rule rules[] = { /* app_id title tags mask isfloating monitor */ /* examples: { "Gimp", NULL, 0, 1, -1 }, - { "firefox", NULL, 1 << 8, 0, -1 }, */ + { "firefox", NULL, 1 << 8, 0, -1 }, }; /* layout(s) */ @@ -24,16 +27,14 @@ static const Layout layouts[] = { { "[M]", monocle }, }; -/* monitors - * The order in which monitors are defined determines their position. - * Non-configured monitors are always added to the left. */ +/* monitors */ static const MonitorRule monrules[] = { - /* name mfact nmaster scale layout rotate/reflect x y */ + /* name mfact nmaster scale layout rotate/reflect */ /* example of a HiDPI laptop monitor: - { "eDP-1", 0.5, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0 }, + { "eDP-1", 0.5, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL }, */ /* defaults */ - { NULL, 0.55, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0 }, + { NULL, 0.55, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL }, }; /* keyboard */ @@ -42,6 +43,7 @@ static const struct xkb_rule_names xkb_rules = { /* example: .options = "ctrl:nocaps", */ + .options = NULL, }; static const int repeat_rate = 25; @@ -49,9 +51,49 @@ static const int repeat_delay = 600; /* Trackpad */ static const int tap_to_click = 1; +static const int tap_and_drag = 1; +static const int drag_lock = 1; static const int natural_scrolling = 0; +static const int disable_while_typing = 1; +static const int left_handed = 0; +static const int middle_button_emulation = 0; +/* You can choose between: +LIBINPUT_CONFIG_SCROLL_NO_SCROLL +LIBINPUT_CONFIG_SCROLL_2FG +LIBINPUT_CONFIG_SCROLL_EDGE +LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN +*/ +static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; +/* You can choose between: +LIBINPUT_CONFIG_CLICK_METHOD_NONE +LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS +LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER +*/ +static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + +/* You can choose between: +LIBINPUT_CONFIG_SEND_EVENTS_ENABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +*/ +static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +/* You can choose between: +LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT +LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +*/ +static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +static const double accel_speed = 0.0; +/* You can choose between: +LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right +*/ +static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; + +/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ #define MODKEY WLR_MODIFIER_ALT + #define TAGKEYS(KEY,SKEY,TAG) \ { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ @@ -62,7 +104,7 @@ static const int natural_scrolling = 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[] = { @@ -84,7 +126,7 @@ static const Key keys[] = { { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, { MODKEY, XKB_KEY_space, setlayout, {0} }, { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, - { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_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} }, @@ -96,7 +138,7 @@ static const Key keys[] = { 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_caret, 5), + 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), diff --git a/config.mk b/config.mk index cd4e821..c2dd026 100644 --- a/config.mk +++ b/config.mk @@ -1,8 +1,14 @@ +_VERSION = 0.4-rc1 +VERSION = `git describe --long --tags --dirty 2>/dev/null || echo $(_VERSION)` + +PKG_CONFIG = pkg-config + # paths PREFIX = /usr/local +MANDIR = $(PREFIX)/share/man -# Default compile flags (overridable by environment) -CFLAGS ?= -g -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare -Wno-unused-function -Wno-unused-variable -Wdeclaration-after-statement - +XWAYLAND = +XLIBS = # Uncomment to build XWayland support -#CFLAGS += -DXWAYLAND +#XWAYLAND = -DXWAYLAND +#XLIBS = xcb xcb-icccm diff --git a/dwl.1 b/dwl.1 new file mode 100644 index 0000000..cae1036 --- /dev/null +++ b/dwl.1 @@ -0,0 +1,151 @@ +.Dd January 8, 2021 +.Dt DWL 1 +.Os +.Sh NAME +.Nm dwl +.Nd dwm for Wayland +.Sh SYNOPSIS +.Nm +.Op Fl v +.Op Fl s Ar startup command +.Sh DESCRIPTION +.Nm +is a Wayland compositor based on wlroots. +It is intended to fill the same space in the Wayland world that +.Nm dwm +does for X11. +.Pp +When given the +.Fl v +option, +.Nm +writes its name and version to standard error and exits unsuccessfully. +.Pp +When given the +.Fl s +option, +.Nm +starts a shell process running +.Ar command +when starting. +When stopping, it sends +.Dv SIGTERM +to the child process and waits for it to exit. +.Pp +Users are encouraged to customize +.Nm +by editing the sources, in particular +.Pa config.h . +The default key bindings are as follows: +.Bl -tag -width 20n -offset indent -compact +.It Mod-[1-9] +Show only all windows with a tag. +.It Mod-Ctrl-[1-9] +Show all windows with a tag. +.It Mod-Shift-[1-9] +Move window to a single tag. +.It Mod-Ctrl-Shift-[1-9] +Toggle tag for window. +.It Mod-p +Spawn +.Nm bemenu-run . +.It Mod-Shift-Return +Spawn +.Nm foot . +.It Mod-[jk] +Move focus down/up the stack. +.It Mod-[id] +Increase/decrease number of windows in master area. +.It Mod-[hl] +Decrease/increase master area. +.It Mod-Return +Move window on top of stack or switch top of stack with second window. +.It Mod-Tab +Show only all windows with previous tag. +.It Mod-Shift-c +Close window. +.It Mod-t +Switch to tabbed layout. +.It Mod-f +Switch to floating layout. +.It Mod-m +Switch to monocle layout. +.It Mod-Space +Switch to previous layout. +.It Mod-Shift-Space +Toggle floating state of window. +.It Mod-e +Toggle fullscreen state of window. +.It Mod-0 +Show all windows. +.It Mod-Shift-0 +Set all tags for window. +.It Mod-, +Move focus to previous monitor. +.It Mod-. +Move focus to next monitor. +.It Mod-Shift-, +Move window to previous monitor. +.It Mod-Shift-. +Move window to next monitor. +.It Mod-Shift-q +Quit +.Nm . +.El +These might differ depending on your keyboard layout. +.Sh ENVIRONMENT +These environment variables are used by +.Nm : +.Bl -tag -width XDG_RUNTIME_DIR +.It Ev XDG_RUNTIME_DIR +A directory where temporary user files, such as the Wayland socket, +are stored. +.It Ev XDG_CONFIG_DIR +A directory containung configuration of various programs and +libraries, including libxkbcommon. +.It Ev DISPLAY , WAYLAND_DISPLAY , WAYLAND_SOCKET +Tell how to connect to an underlying X11 or Wayland server. +.It Ev WLR_* +Various variables specific to wlroots. +.It Ev XKB_* , XLOCALEDIR , XCOMPOSEFILE +Various variables specific to libxkbcommon. +.It Ev XCURSOR_PATH +List of directories to search for XCursor themes in. +.It Ev HOME +A directory where there are always dear files there for you. +Waiting for you to clean them up. +.El +.Pp +These are set by +.Nm : +.Bl -tag -width WAYLAND_DISPLAY +.It Ev WAYLAND_DISPLAY +Tell how to connect to +.Nm . +.It Ev DISPLAY +If using +.Nm Xwayland , +tell how to connect to the +.Nm Xwayland +server. +.El +.Sh EXAMPLES +Start +.Nm +with s6 in the background: +.Dl dwl -s 's6-svscan <&-' +.Sh SEE ALSO +.Xr foot 1 , +.Xr bemenu 1 , +.Xr dwm 1 , +.Xr xkeyboard-config 7 +.Sh CAVEATS +The child process's standard input is connected with a pipe to +.Nm . +If the child process neither reads from the pipe nor closes its +standard input, +.Nm +will freeze after a while due to it blocking when writing to the full +pipe buffer. +.Sh BUGS +All of them. diff --git a/dwl.c b/dwl.c index 360993b..49b87c1 100644 --- a/dwl.c +++ b/dwl.c @@ -1,8 +1,9 @@ /* * See LICENSE file for copyright and license details. */ -#define _POSIX_C_SOURCE 200809L #include +#include +#include #include #include #include @@ -10,9 +11,10 @@ #include #include #include -#include #include #include +#include +#include #include #include #include @@ -20,52 +22,61 @@ #include #include #include -#include #include -#include +#include +#include +#include +#include #include -#include +#include #include #include #include #include +#include #include #include +#include #include #include +#include +#include +#include #include #include #include +#include #include #include #include -#include #include #include #ifdef XWAYLAND -#include #include +#include +#include #endif +#include "util.h" + /* macros */ -#define BARF(fmt, ...) do { fprintf(stderr, fmt "\n", ##__VA_ARGS__); exit(EXIT_FAILURE); } while (0) -#define EBARF(fmt, ...) BARF(fmt ": %s", ##__VA_ARGS__, strerror(errno)) #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) ((C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) +#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1 << LENGTH(tags)) - 1) -#define ROUND(X) ((int)((X)+0.5)) #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +#define IDLE_NOTIFY_ACTIVITY wlr_idle_notify_activity(idle, seat), wlr_idle_notifier_v1_notify_activity(idle_notifier, seat) /* 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, LyrFS, LyrDragIcon, NUM_LAYERS }; /* scene layers */ #ifdef XWAYLAND enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ -enum { XDGShell, X11Managed, X11Unmanaged }; /* client types */ #endif typedef union { @@ -84,41 +95,38 @@ typedef struct { typedef struct Monitor Monitor; typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; struct wl_list link; struct wl_list flink; - struct wl_list slink; union { struct wlr_xdg_surface *xdg; struct wlr_xwayland_surface *xwayland; } surface; struct wl_listener commit; struct wl_listener map; + struct wl_listener maximize; struct wl_listener unmap; struct wl_listener destroy; + struct wl_listener set_title; struct wl_listener fullscreen; - struct wlr_box geom; /* layout-relative, includes border */ - Monitor *mon; + struct wlr_box prev; /* layout-relative, includes border */ #ifdef XWAYLAND - unsigned int type; struct wl_listener activate; struct wl_listener configure; + struct wl_listener set_hints; #endif - int bw; + unsigned int bw; unsigned int tags; - int isfloating; + int isfloating, isurgent, isfullscreen; uint32_t resize; /* configure serial of a pending resize */ - int prevx; - int prevy; - int prevwidth; - int prevheight; - int isfullscreen; } Client; -typedef struct { - struct wl_listener request_mode; - struct wl_listener destroy; -} Decoration; - typedef struct { uint32_t mod; xkb_keysym_t keysym; @@ -128,7 +136,7 @@ typedef struct { typedef struct { struct wl_list link; - struct wlr_input_device *device; + struct wlr_keyboard *wlr_keyboard; struct wl_listener modifiers; struct wl_listener key; @@ -136,26 +144,23 @@ typedef struct { } Keyboard; typedef struct { - struct wlr_layer_surface_v1 *layer_surface; + /* Must keep these three elements in this order */ + unsigned int type; /* LayerShell */ + struct wlr_box geom; + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_tree *popups; + struct wlr_scene_layer_surface_v1 *scene_layer; struct wl_list link; + int mapped; + struct wlr_layer_surface_v1 *layer_surface; struct wl_listener destroy; struct wl_listener map; struct wl_listener unmap; struct wl_listener surface_commit; - - struct wlr_box geo; - enum zwlr_layer_shell_v1_layer layer; } LayerSurface; -typedef struct { - uint32_t singular_anchor; - uint32_t anchor_triplet; - int *positive_axis; - int *negative_axis; - int margin; -} Edge; - typedef struct { const char *symbol; void (*arrange)(Monitor *); @@ -164,11 +169,13 @@ typedef struct { struct Monitor { struct wl_list link; struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ struct wl_listener frame; struct wl_listener destroy; struct wlr_box m; /* monitor area, layout-relative */ struct wlr_box w; /* window area, layout-relative */ - struct wl_list layers[4]; // LayerSurface::link + struct wl_list layers[4]; /* LayerSurface::link */ const Layout *lt[2]; unsigned int seltags; unsigned int sellt; @@ -184,8 +191,6 @@ typedef struct { float scale; const Layout *lt; enum wl_output_transform rr; - int x; - int y; } MonitorRule; typedef struct { @@ -196,19 +201,8 @@ typedef struct { int monitor; } Rule; -/* Used to move all of the data necessary to render a surface from the top-level - * frame handler to the per-surface render function. */ -struct render_data { - struct wlr_output *output; - struct timespec *when; - int x, y; /* layout-relative */ -}; - /* function declarations */ static void applybounds(Client *c, struct wlr_box *bbox); -static void applyexclusive(struct wlr_box *usable_area, uint32_t anchor, - int32_t exclusive, int32_t margin_top, int32_t margin_right, - int32_t margin_bottom, int32_t margin_left); static void applyrules(Client *c); static void arrange(Monitor *m); static void arrangelayer(Monitor *m, struct wl_list *list, @@ -217,29 +211,30 @@ 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); static void closemon(Monitor *m); static void commitlayersurfacenotify(struct wl_listener *listener, void *data); static void commitnotify(struct wl_listener *listener, void *data); -static void createkeyboard(struct wlr_input_device *device); +static void createidleinhibitor(struct wl_listener *listener, void *data); +static void createkeyboard(struct wlr_keyboard *keyboard); +static void createlayersurface(struct wl_listener *listener, void *data); static void createmon(struct wl_listener *listener, void *data); static void createnotify(struct wl_listener *listener, void *data); -static void createlayersurface(struct wl_listener *listener, void *data); -static void createpointer(struct wlr_input_device *device); -static void createxdeco(struct wl_listener *listener, void *data); +static void createpointer(struct wlr_pointer *pointer); static void cursorframe(struct wl_listener *listener, void *data); +static void destroydragicon(struct wl_listener *listener, void *data); +static void destroyidleinhibitor(struct wl_listener *listener, void *data); static void destroylayersurfacenotify(struct wl_listener *listener, void *data); static void destroynotify(struct wl_listener *listener, void *data); -static void destroyxdeco(struct wl_listener *listener, void *data); static Monitor *dirtomon(enum wlr_direction dir); static void focusclient(Client *c, int lift); static void focusmon(const Arg *arg); static void focusstack(const Arg *arg); -static void fullscreennotify(struct wl_listener *listener, void *data); static Client *focustop(Monitor *m); -static void getxdecomode(struct wl_listener *listener, void *data); +static void fullscreennotify(struct wl_listener *listener, void *data); static void incnmaster(const Arg *arg); static void inputdevice(struct wl_listener *listener, void *data); static int keybinding(uint32_t mods, xkb_keysym_t sym); @@ -248,6 +243,7 @@ static void keypressmod(struct wl_listener *listener, void *data); static void killclient(const Arg *arg); 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 monocle(Monitor *m); static void motionabsolute(struct wl_listener *listener, void *data); static void motionnotify(uint32_t time); @@ -260,25 +256,23 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time); static void printstatus(void); static void quit(const Arg *arg); -static void render(struct wlr_surface *surface, int sx, int sy, void *data); -static void renderclients(Monitor *m, struct timespec *now); -static void renderlayer(struct wl_list *layer_surfaces, struct timespec *now); +static void quitsignal(int signo); static void rendermon(struct wl_listener *listener, void *data); -static void resize(Client *c, int x, int y, int w, int h, int interact); +static void requeststartdrag(struct wl_listener *listener, void *data); +static void resize(Client *c, struct wlr_box geo, int interact); static void run(char *startup_cmd); -static void scalebox(struct wlr_box *box, float scale); static Client *selclient(void); static void setcursor(struct wl_listener *listener, void *data); -static void setpsel(struct wl_listener *listener, void *data); -static void setsel(struct wl_listener *listener, void *data); static void setfloating(Client *c, int floating); static void setfullscreen(Client *c, int fullscreen); static void setlayout(const Arg *arg); static void setmfact(const Arg *arg); static void setmon(Client *c, Monitor *m, unsigned int newtags); +static void setpsel(struct wl_listener *listener, void *data); +static void setsel(struct wl_listener *listener, void *data); static void setup(void); -static void sigchld(int unused); static void spawn(const Arg *arg); +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); @@ -286,42 +280,45 @@ static void togglefloating(const Arg *arg); static void togglefullscreen(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); -static void unmaplayersurface(LayerSurface *layersurface); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); static void unmapnotify(struct wl_listener *listener, void *data); static void updatemons(struct wl_listener *listener, void *data); +static void updatetitle(struct wl_listener *listener, void *data); +static void urgent(struct wl_listener *listener, void *data); static void view(const Arg *arg); static void virtualkeyboard(struct wl_listener *listener, void *data); -static Client *xytoclient(double x, double y); -static struct wlr_surface *xytolayersurface(struct wl_list *layer_surfaces, - double x, double y, double *sx, double *sy); static Monitor *xytomon(double x, double y); +static struct wlr_scene_node *xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); 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 void *exclusive_focus; static struct wl_display *dpy; static struct wlr_backend *backend; +static struct wlr_scene *scene; +static struct wlr_scene_tree *layers[NUM_LAYERS]; static struct wlr_renderer *drw; +static struct wlr_allocator *alloc; static struct wlr_compositor *compositor; static struct wlr_xdg_shell *xdg_shell; +static struct wlr_xdg_activation_v1 *activation; static struct wl_list clients; /* tiling order */ static struct wl_list fstack; /* focus order */ -static struct wl_list stack; /* stacking z-order */ -static struct wl_list independents; static struct wlr_idle *idle; +static struct wlr_idle_notifier_v1 *idle_notifier; +static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; +static struct wlr_input_inhibit_manager *input_inhibit_mgr; static struct wlr_layer_shell_v1 *layer_shell; -static struct wlr_xdg_decoration_manager_v1 *xdeco_mgr; static struct wlr_output_manager_v1 *output_mgr; static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; static struct wlr_cursor *cursor; static struct wlr_xcursor_manager *cursor_mgr; -#ifdef XWAYLAND -static struct wlr_xcursor *xcursor; -static struct wlr_xcursor_manager *xcursor_mgr; -#endif static struct wlr_seat *seat; static struct wl_list keyboards; @@ -340,27 +337,32 @@ static struct wl_listener cursor_button = {.notify = buttonpress}; static struct wl_listener cursor_frame = {.notify = cursorframe}; static struct wl_listener cursor_motion = {.notify = motionrelative}; static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute}; +static struct wl_listener drag_icon_destroy = {.notify = destroydragicon}; +static struct wl_listener idle_inhibitor_create = {.notify = createidleinhibitor}; +static struct wl_listener idle_inhibitor_destroy = {.notify = destroyidleinhibitor}; static struct wl_listener layout_change = {.notify = updatemons}; static struct wl_listener new_input = {.notify = inputdevice}; static struct wl_listener new_virtual_keyboard = {.notify = virtualkeyboard}; static struct wl_listener new_output = {.notify = createmon}; -static struct wl_listener new_xdeco = {.notify = createxdeco}; static struct wl_listener new_xdg_surface = {.notify = createnotify}; static struct wl_listener new_layer_shell_surface = {.notify = createlayersurface}; static struct wl_listener output_mgr_apply = {.notify = outputmgrapply}; static struct wl_listener output_mgr_test = {.notify = outputmgrtest}; +static struct wl_listener request_activate = {.notify = urgent}; static struct wl_listener request_cursor = {.notify = setcursor}; static struct wl_listener request_set_psel = {.notify = setpsel}; static struct wl_listener request_set_sel = {.notify = setsel}; +static struct wl_listener request_start_drag = {.notify = requeststartdrag}; +static struct wl_listener start_drag = {.notify = startdrag}; #ifdef XWAYLAND static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data); static Atom getatom(xcb_connection_t *xc, const char *name); -static void renderindependents(struct wlr_output *output, struct timespec *now); +static void sethints(struct wl_listener *listener, void *data); +static void sigchld(int unused); static void xwaylandready(struct wl_listener *listener, void *data); -static Client *xytoindependent(double x, double y); static struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; static struct wl_listener xwayland_ready = {.notify = xwaylandready}; static struct wlr_xwayland *xwayland; @@ -382,9 +384,19 @@ struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; void applybounds(Client *c, struct wlr_box *bbox) { - /* set minimum possible */ - c->geom.width = MAX(1, c->geom.width); - c->geom.height = MAX(1, c->geom.height); + if (!c->isfullscreen) { + struct wlr_box min = {0}, max = {0}; + client_get_size_hints(c, &max, &min); + /* try to set size hints */ + c->geom.width = MAX(min.width + (2 * (int)c->bw), c->geom.width); + c->geom.height = MAX(min.height + (2 * (int)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 */ + c->geom.height = MIN(max.height + (2 * c->bw), c->geom.height); + } if (c->geom.x >= bbox->x + bbox->width) c->geom.x = bbox->x + bbox->width - c->geom.width; @@ -396,61 +408,6 @@ applybounds(Client *c, struct wlr_box *bbox) c->geom.y = bbox->y; } -void -applyexclusive(struct wlr_box *usable_area, - uint32_t anchor, int32_t exclusive, - int32_t margin_top, int32_t margin_right, - int32_t margin_bottom, int32_t margin_left) { - Edge edges[] = { - { // Top - .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, - .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, - .positive_axis = &usable_area->y, - .negative_axis = &usable_area->height, - .margin = margin_top, - }, - { // Bottom - .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, - .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, - .positive_axis = NULL, - .negative_axis = &usable_area->height, - .margin = margin_bottom, - }, - { // Left - .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, - .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, - .positive_axis = &usable_area->x, - .negative_axis = &usable_area->width, - .margin = margin_left, - }, - { // Right - .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, - .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, - .positive_axis = NULL, - .negative_axis = &usable_area->width, - .margin = margin_right, - } - }; - for (size_t i = 0; i < LENGTH(edges); i++) { - if ((anchor == edges[i].singular_anchor || anchor == edges[i].anchor_triplet) - && exclusive + edges[i].margin > 0) { - if (edges[i].positive_axis) - *edges[i].positive_axis += exclusive + edges[i].margin; - if (edges[i].negative_axis) - *edges[i].negative_axis -= exclusive + edges[i].margin; - break; - } - } -} - void applyrules(Client *c) { @@ -477,15 +434,24 @@ applyrules(Client *c) mon = m; } } + wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrFloat : LyrTile]); setmon(c, mon, newtags); } void arrange(Monitor *m) { - if (m->lt[m->sellt]->arrange) + Client *c; + wl_list_for_each(c, &clients, link) + if (c->mon == m) + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + + if (m && m->lt[m->sellt]->arrange) m->lt[m->sellt]->arrange(m); - /* TODO recheck pointer focus here... or in resize()? */ + motionnotify(0); } void @@ -497,120 +463,54 @@ arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int wl_list_for_each(layersurface, list, link) { struct wlr_layer_surface_v1 *wlr_layer_surface = layersurface->layer_surface; struct wlr_layer_surface_v1_state *state = &wlr_layer_surface->current; - struct wlr_box bounds; - struct wlr_box box = { - .width = state->desired_width, - .height = state->desired_height - }; - const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT - | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP - | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; if (exclusive != (state->exclusive_zone > 0)) continue; - bounds = state->exclusive_zone == -1 ? full_area : *usable_area; - - // Horizontal axis - if ((state->anchor & both_horiz) && box.width == 0) { - box.x = bounds.x; - box.width = bounds.width; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { - box.x = bounds.x; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { - box.x = bounds.x + (bounds.width - box.width); - } else { - box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); - } - // Vertical axis - if ((state->anchor & both_vert) && box.height == 0) { - box.y = bounds.y; - box.height = bounds.height; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { - box.y = bounds.y; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { - box.y = bounds.y + (bounds.height - box.height); - } else { - box.y = bounds.y + ((bounds.height / 2) - (box.height / 2)); - } - // Margin - if ((state->anchor & both_horiz) == both_horiz) { - box.x += state->margin.left; - box.width -= state->margin.left + state->margin.right; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { - box.x += state->margin.left; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { - box.x -= state->margin.right; - } - if ((state->anchor & both_vert) == both_vert) { - box.y += state->margin.top; - box.height -= state->margin.top + state->margin.bottom; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { - box.y += state->margin.top; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { - box.y -= state->margin.bottom; - } - if (box.width < 0 || box.height < 0) { - wlr_layer_surface_v1_close(wlr_layer_surface); - continue; - } - layersurface->geo = box; - - if (state->exclusive_zone > 0) - applyexclusive(usable_area, state->anchor, state->exclusive_zone, - state->margin.top, state->margin.right, - state->margin.bottom, state->margin.left); - wlr_layer_surface_v1_configure(wlr_layer_surface, box.width, box.height); + wlr_scene_layer_surface_v1_configure(layersurface->scene_layer, &full_area, usable_area); + wlr_scene_node_set_position(&layersurface->popups->node, + layersurface->scene->node.x, layersurface->scene->node.y); + layersurface->geom.x = layersurface->scene->node.x; + layersurface->geom.y = layersurface->scene->node.y; } } void arrangelayers(Monitor *m) { + int i; struct wlr_box usable_area = m->m; uint32_t layers_above_shell[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, 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 - arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - &usable_area, 1); - arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - &usable_area, 1); - arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - &usable_area, 1); - arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - &usable_area, 1); + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); if (memcmp(&usable_area, &m->w, sizeof(struct wlr_box))) { m->w = usable_area; arrange(m); } - // Arrange non-exlusive surfaces from top->bottom - arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - &usable_area, 0); - arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - &usable_area, 0); - arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - &usable_area, 0); - arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - &usable_area, 0); + /* Arrange non-exlusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + 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++) { + /* Find topmost keyboard interactive layer, if such a layer exists */ + 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) { - // Deactivate the focused client. + layersurface->mapped) { + /* Deactivate the focused client. */ focusclient(NULL, 0); - wlr_seat_keyboard_notify_enter(seat, layersurface->layer_surface->surface, - kb->keycodes, kb->num_keycodes, &kb->modifiers); + exclusive_focus = layersurface; + client_notify_enter(layersurface->layer_surface->surface, wlr_seat_get_keyboard(seat)); return; } } @@ -622,8 +522,10 @@ axisnotify(struct wl_listener *listener, void *data) { /* This event is forwarded by the cursor when a pointer emits an axis event, * for example when you move the scroll wheel. */ - struct wlr_event_pointer_axis *event = data; - wlr_idle_notify_activity(idle, seat); + struct wlr_pointer_axis_event *event = data; + IDLE_NOTIFY_ACTIVITY; + /* 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, @@ -633,22 +535,23 @@ axisnotify(struct wl_listener *listener, void *data) void buttonpress(struct wl_listener *listener, void *data) { - struct wlr_event_pointer_button *event = data; + struct wlr_pointer_button_event *event = data; struct wlr_keyboard *keyboard; uint32_t mods; Client *c; const Button *b; - wlr_idle_notify_activity(idle, seat); + IDLE_NOTIFY_ACTIVITY; switch (event->state) { - case WLR_BUTTON_PRESSED:; + case WLR_BUTTON_PRESSED: /* Change focus if the button was _pressed_ over a client */ - if ((c = xytoclient(cursor->x, cursor->y))) + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) focusclient(c, 1); keyboard = wlr_seat_get_keyboard(seat); - mods = wlr_keyboard_get_modifiers(keyboard); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; for (b = buttons; b < END(buttons); b++) { if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && b->func) { @@ -656,18 +559,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; } @@ -683,6 +591,25 @@ 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) { + struct wlr_surface *surface = wlr_surface_get_root_surface(inhibitor->surface); + struct wlr_scene_tree *tree = surface->data; + if (bypass_surface_visibility || (exclude != surface + && tree->node.enabled)) { + inhibited = 1; + break; + } + } + + wlr_idle_set_enabled(idle, NULL, !inhibited); + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); +} + void cleanup(void) { @@ -690,8 +617,13 @@ cleanup(void) wlr_xwayland_destroy(xwayland); #endif wl_display_destroy_clients(dpy); - + if (child_pid > 0) { + kill(child_pid, SIGTERM); + 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); @@ -702,8 +634,7 @@ cleanup(void) void cleanupkeyboard(struct wl_listener *listener, void *data) { - struct wlr_input_device *device = data; - Keyboard *kb = device->data; + Keyboard *kb = wl_container_of(listener, kb, destroy); wl_list_remove(&kb->link); wl_list_remove(&kb->modifiers.link); @@ -715,20 +646,22 @@ 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; - int nmons, i = 0; + Monitor *m = wl_container_of(listener, m, destroy); + LayerSurface *l, *tmp; + int i; + + for (i = 0; i <= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; i++) + wl_list_for_each_safe(l, tmp, &m->layers[i], link) + wlr_layer_surface_v1_destroy(l->layer_surface); 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); + wlr_scene_node_destroy(&m->fullscreen_bg->node); - nmons = wl_list_length(&mons); - do // don't switch to disabled mons - selmon = wl_container_of(mons.prev, selmon, link); - while (!selmon->wlr_output->enabled && i++ < nmons); - focusclient(focustop(selmon), 1); closemon(m); free(m); } @@ -736,16 +669,27 @@ cleanupmon(struct wl_listener *listener, void *data) void closemon(Monitor *m) { - // move closed monitor's clients to the focused one + /* update selmon if needed and + * move closed monitor's clients to the focused one */ Client *c; + if (wl_list_empty(&mons)) { + selmon = NULL; + } else if (m == selmon) { + int nmons = wl_list_length(&mons), i = 0; + do /* don't switch to disabled mons */ + selmon = wl_container_of(mons.next, selmon, link); + while (!selmon->wlr_output->enabled && i++ < nmons); + } wl_list_for_each(c, &clients, link) { if (c->isfloating && c->geom.x > m->m.width) - resize(c, c->geom.x - m->w.width, c->geom.y, - c->geom.width, c->geom.height, 0); + 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); } + focusclient(focustop(selmon), 1); + printstatus(); } void @@ -754,61 +698,132 @@ commitlayersurfacenotify(struct wl_listener *listener, void *data) LayerSurface *layersurface = wl_container_of(listener, layersurface, surface_commit); struct wlr_layer_surface_v1 *wlr_layer_surface = layersurface->layer_surface; struct wlr_output *wlr_output = wlr_layer_surface->output; - Monitor *m; - if (!wlr_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; - m = wlr_output->data; - arrangelayers(m); - - if (layersurface->layer != 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]); + wlr_scene_node_reparent(&layersurface->popups->node, + layers[wlr_layer_surface->current.layer]); wl_list_remove(&layersurface->link); - wl_list_insert(&m->layers[wlr_layer_surface->current.layer], - &layersurface->link); - layersurface->layer = wlr_layer_surface->current.layer; + wl_list_insert(&layersurface->mon->layers[wlr_layer_surface->current.layer], + &layersurface->link); } + if (wlr_layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) + wlr_scene_node_reparent(&layersurface->popups->node, layers[LyrTop]); + + if (wlr_layer_surface->current.committed == 0 + && layersurface->mapped == wlr_layer_surface->mapped) + return; + layersurface->mapped = wlr_layer_surface->mapped; + + arrangelayers(layersurface->mon); } void commitnotify(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, commit); + struct wlr_box box = {0}; + client_get_geometry(c, &box); + /* mark a pending resize as completed */ - if (c->resize && c->resize <= c->surface.xdg->configure_serial) + if (c->resize && (c->resize <= c->surface.xdg->current.configure_serial)) c->resize = 0; } void -createkeyboard(struct wlr_input_device *device) +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); + + checkidleinhibitor(NULL); +} + +void +createkeyboard(struct wlr_keyboard *keyboard) { struct xkb_context *context; struct xkb_keymap *keymap; - Keyboard *kb = device->data = calloc(1, sizeof(*kb)); - kb->device = device; + Keyboard *kb = keyboard->data = ecalloc(1, sizeof(*kb)); + kb->wlr_keyboard = keyboard; /* Prepare an XKB keymap and assign it to the keyboard. */ context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - keymap = xkb_map_new_from_names(context, &xkb_rules, + keymap = xkb_keymap_new_from_names(context, &xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS); - wlr_keyboard_set_keymap(device->keyboard, keymap); + wlr_keyboard_set_keymap(keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); - wlr_keyboard_set_repeat_info(device->keyboard, repeat_rate, repeat_delay); + wlr_keyboard_set_repeat_info(keyboard, repeat_rate, repeat_delay); /* Here we set up listeners for keyboard events. */ - LISTEN(&device->keyboard->events.modifiers, &kb->modifiers, keypressmod); - LISTEN(&device->keyboard->events.key, &kb->key, keypress); - LISTEN(&device->events.destroy, &kb->destroy, cleanupkeyboard); + LISTEN(&keyboard->events.modifiers, &kb->modifiers, keypressmod); + LISTEN(&keyboard->events.key, &kb->key, keypress); + LISTEN(&keyboard->base.events.destroy, &kb->destroy, cleanupkeyboard); - wlr_seat_set_keyboard(seat, device); + wlr_seat_set_keyboard(seat, keyboard); /* And add the keyboard to our list of keyboards */ wl_list_insert(&keyboards, &kb->link); } +void +createlayersurface(struct wl_listener *listener, void *data) +{ + struct wlr_layer_surface_v1 *wlr_layer_surface = data; + LayerSurface *layersurface; + struct wlr_layer_surface_v1_state old_state; + + 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); + LISTEN(&wlr_layer_surface->events.destroy, &layersurface->destroy, + destroylayersurfacenotify); + LISTEN(&wlr_layer_surface->events.map, &layersurface->map, + maplayersurfacenotify); + LISTEN(&wlr_layer_surface->events.unmap, &layersurface->unmap, + unmaplayersurfacenotify); + + layersurface->layer_surface = wlr_layer_surface; + 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); + layersurface->scene = layersurface->scene_layer->tree; + layersurface->popups = wlr_layer_surface->surface->data = + wlr_scene_tree_create(layers[wlr_layer_surface->pending.layer]); + + layersurface->scene->node.data = layersurface; + + wl_list_insert(&layersurface->mon->layers[wlr_layer_surface->pending.layer], + &layersurface->link); + + /* Temporarily set the layer's current state to pending + * so that we can easily arrange it + */ + 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; +} + void createmon(struct wl_listener *listener, void *data) { @@ -816,11 +831,14 @@ createmon(struct wl_listener *listener, void *data) * monitor) becomes available. */ struct wlr_output *wlr_output = data; const MonitorRule *r; - Monitor *m = wlr_output->data = calloc(1, sizeof(*m)); + 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++) { @@ -840,136 +858,132 @@ createmon(struct wl_listener *listener, void *data) * monitor's preferred mode; a more sophisticated compositor would let * the user configure it. */ wlr_output_set_mode(wlr_output, wlr_output_preferred_mode(wlr_output)); - wlr_output_enable_adaptive_sync(wlr_output, 1); /* Set up event listeners */ LISTEN(&wlr_output->events.frame, &m->frame, rendermon); LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); - wl_list_insert(&mons, &m->link); wlr_output_enable(wlr_output, 1); if (!wlr_output_commit(wlr_output)) return; + /* Try to enable adaptive sync, note that not all monitors support it. + * wlr_output_commit() will deactivate it in case it cannot be enabled */ + wlr_output_enable_adaptive_sync(wlr_output, 1); + wlr_output_commit(wlr_output); + + wl_list_insert(&mons, &m->link); + printstatus(); + + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + * + */ + /* updatemons() will resize and set correct position */ + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + /* Adds this to the output layout in the order it was configured in. * * The output layout utility automatically adds a wl_output global to the * display, which Wayland clients can see to find out information about the * output (such as DPI, scale factor, manufacturer, etc). */ - wlr_output_layout_add(output_layout, wlr_output, r->x, r->y); - sgeom = *wlr_output_layout_get_box(output_layout, NULL); - - /* When adding monitors, the geometries of all monitors must be updated */ - wl_list_for_each(m, &mons, link) { - /* The first monitor in the list is the most recently added */ - Client *c; - wl_list_for_each(c, &clients, link) { - if (c->isfloating) - resize(c, c->geom.x + m->w.width, c->geom.y, - c->geom.width, c->geom.height, 0); - } - return; - } + m->scene_output = wlr_scene_output_create(scene, wlr_output); + wlr_output_layout_add_auto(output_layout, wlr_output); } void createnotify(struct wl_listener *listener, void *data) { /* This event is raised when wlr_xdg_shell receives a new xdg surface from a - * client, either a toplevel (application window) or popup. */ + * client, either a toplevel (application window) or popup, + * or when wlr_layer_shell receives a new popup from a layer. + * If you want to do something tricky with popups you should check if + * its parent is wlr_xdg_shell or wlr_layer_shell */ struct wlr_xdg_surface *xdg_surface = data; Client *c; - if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) + if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { + struct wlr_box box; + LayerSurface *l = toplevel_from_popup(xdg_surface->popup); + if (!xdg_surface->popup->parent) + return; + xdg_surface->surface->data = wlr_scene_xdg_surface_create( + xdg_surface->popup->parent->data, xdg_surface); + /* 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; + box.x -= l->geom.x; + box.y -= l->geom.y; + wlr_xdg_popup_unconstrain_from_box(xdg_surface->popup, &box); + return; + } else if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_NONE) return; /* Allocate a Client for this surface */ - c = xdg_surface->data = calloc(1, sizeof(*c)); + c = xdg_surface->data = ecalloc(1, sizeof(*c)); c->surface.xdg = xdg_surface; c->bw = borderpx; - /* Tell the client not to try anything fancy */ - wlr_xdg_toplevel_set_tiled(c->surface.xdg, WLR_EDGE_TOP | - WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); - - LISTEN(&xdg_surface->surface->events.commit, &c->commit, commitnotify); LISTEN(&xdg_surface->events.map, &c->map, mapnotify); LISTEN(&xdg_surface->events.unmap, &c->unmap, unmapnotify); LISTEN(&xdg_surface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xdg_surface->toplevel->events.set_title, &c->set_title, updatetitle); LISTEN(&xdg_surface->toplevel->events.request_fullscreen, &c->fullscreen, fullscreennotify); - c->isfullscreen = 0; + LISTEN(&xdg_surface->toplevel->events.request_maximize, &c->maximize, + maximizenotify); } void -createlayersurface(struct wl_listener *listener, void *data) +createpointer(struct wlr_pointer *pointer) { - struct wlr_layer_surface_v1 *wlr_layer_surface = data; - LayerSurface *layersurface; - Monitor *m; - struct wlr_layer_surface_v1_state old_state; - - if (!wlr_layer_surface->output) { - wlr_layer_surface->output = selmon->wlr_output; - } - - layersurface = calloc(1, sizeof(LayerSurface)); - LISTEN(&wlr_layer_surface->surface->events.commit, - &layersurface->surface_commit, commitlayersurfacenotify); - LISTEN(&wlr_layer_surface->events.destroy, &layersurface->destroy, - destroylayersurfacenotify); - LISTEN(&wlr_layer_surface->events.map, &layersurface->map, - maplayersurfacenotify); - LISTEN(&wlr_layer_surface->events.unmap, &layersurface->unmap, - unmaplayersurfacenotify); - - layersurface->layer_surface = wlr_layer_surface; - wlr_layer_surface->data = layersurface; - - m = wlr_layer_surface->output->data; - wl_list_insert(&m->layers[wlr_layer_surface->client_pending.layer], - &layersurface->link); - - // Temporarily set the layer's current state to client_pending - // so that we can easily arrange it - old_state = wlr_layer_surface->current; - wlr_layer_surface->current = wlr_layer_surface->client_pending; - arrangelayers(m); - wlr_layer_surface->current = old_state; -} - -void -createpointer(struct wlr_input_device *device) -{ - if (wlr_input_device_is_libinput(device)) { + if (wlr_input_device_is_libinput(&pointer->base)) { struct libinput_device *libinput_device = (struct libinput_device*) - wlr_libinput_get_device_handle(device); + wlr_libinput_get_device_handle(&pointer->base); - if (tap_to_click && libinput_device_config_tap_get_finger_count(libinput_device)) - libinput_device_config_tap_set_enabled(libinput_device, LIBINPUT_CONFIG_TAP_ENABLED); + if (libinput_device_config_tap_get_finger_count(libinput_device)) { + libinput_device_config_tap_set_enabled(libinput_device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(libinput_device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(libinput_device, drag_lock); + libinput_device_config_tap_set_button_map(libinput_device, button_map); + } if (libinput_device_config_scroll_has_natural_scroll(libinput_device)) libinput_device_config_scroll_set_natural_scroll_enabled(libinput_device, natural_scrolling); + + if (libinput_device_config_dwt_is_available(libinput_device)) + libinput_device_config_dwt_set_enabled(libinput_device, disable_while_typing); + + if (libinput_device_config_left_handed_is_available(libinput_device)) + libinput_device_config_left_handed_set(libinput_device, left_handed); + + if (libinput_device_config_middle_emulation_is_available(libinput_device)) + libinput_device_config_middle_emulation_set_enabled(libinput_device, middle_button_emulation); + + if (libinput_device_config_scroll_get_methods(libinput_device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method (libinput_device, scroll_method); + + if (libinput_device_config_click_get_methods(libinput_device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method (libinput_device, click_method); + + if (libinput_device_config_send_events_get_modes(libinput_device)) + libinput_device_config_send_events_set_mode(libinput_device, send_events_mode); + + if (libinput_device_config_accel_is_available(libinput_device)) { + libinput_device_config_accel_set_profile(libinput_device, accel_profile); + libinput_device_config_accel_set_speed(libinput_device, accel_speed); + } } - /* We don't do anything special with pointers. All of our pointer handling - * is proxied through wlr_cursor. On another compositor, you might take this - * opportunity to do libinput configuration on the device to set - * acceleration, etc. */ - wlr_cursor_attach_input_device(cursor, device); -} - -void -createxdeco(struct wl_listener *listener, void *data) -{ - struct wlr_xdg_toplevel_decoration_v1 *wlr_deco = data; - Decoration *d = wlr_deco->data = calloc(1, sizeof(*d)); - - LISTEN(&wlr_deco->events.request_mode, &d->request_mode, getxdecomode); - LISTEN(&wlr_deco->events.destroy, &d->destroy, destroyxdeco); - - getxdecomode(&d->request_mode, wlr_deco); + wlr_cursor_attach_input_device(cursor, &pointer->base); } void @@ -983,24 +997,35 @@ cursorframe(struct wl_listener *listener, void *data) wlr_seat_pointer_notify_frame(seat); } +void +destroydragicon(struct wl_listener *listener, void *data) +{ + struct wlr_drag_icon *icon = data; + wlr_scene_node_destroy(icon->data); + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(selclient(), 1); + motionnotify(0); +} + +void +destroyidleinhibitor(struct wl_listener *listener, void *data) +{ + /* `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(wlr_surface_get_root_surface(data)); +} + void destroylayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *layersurface = wl_container_of(listener, layersurface, destroy); - if (layersurface->layer_surface->mapped) - unmaplayersurface(layersurface); wl_list_remove(&layersurface->link); wl_list_remove(&layersurface->destroy.link); wl_list_remove(&layersurface->map.link); wl_list_remove(&layersurface->unmap.link); wl_list_remove(&layersurface->surface_commit.link); - if (layersurface->layer_surface->output) { - Monitor *m = layersurface->layer_surface->output->data; - if (m) - arrangelayers(m); - layersurface->layer_surface->output = NULL; - } + wlr_scene_node_destroy(&layersurface->scene->node); free(layersurface); } @@ -1012,71 +1037,28 @@ destroynotify(struct wl_listener *listener, void *data) wl_list_remove(&c->map.link); wl_list_remove(&c->unmap.link); wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); wl_list_remove(&c->fullscreen.link); #ifdef XWAYLAND - if (c->type == X11Managed) + if (c->type != XDGShell) { + wl_list_remove(&c->configure.link); + wl_list_remove(&c->set_hints.link); wl_list_remove(&c->activate.link); - else if (c->type == XDGShell) -#endif - wl_list_remove(&c->commit.link); - free(c); -} - -void -destroyxdeco(struct wl_listener *listener, void *data) -{ - struct wlr_xdg_toplevel_decoration_v1 *wlr_deco = data; - Decoration *d = wlr_deco->data; - - wl_list_remove(&d->destroy.link); - wl_list_remove(&d->request_mode.link); - free(d); -} - -void -togglefullscreen(const Arg *arg) -{ - Client *sel = selclient(); - if (sel) - setfullscreen(sel, !sel->isfullscreen); -} - -void -setfullscreen(Client *c, int fullscreen) -{ - c->isfullscreen = fullscreen; - c->bw = (1 - fullscreen) * borderpx; - client_set_fullscreen(c, fullscreen); - - if (fullscreen) { - c->prevx = c->geom.x; - c->prevy = c->geom.y; - c->prevheight = c->geom.height; - c->prevwidth = c->geom.width; - resize(c, c->mon->m.x, c->mon->m.y, c->mon->m.width, c->mon->m.height, 0); - } else { - /* restore previous size instead of arrange for floating windows since - * client positions are set by the user and cannot be recalculated */ - resize(c, c->prevx, c->prevy, c->prevwidth, c->prevheight, 0); - arrange(c->mon); } -} - -void -fullscreennotify(struct wl_listener *listener, void *data) -{ - Client *c = wl_container_of(listener, c, fullscreen); - setfullscreen(c, !c->isfullscreen); +#endif + free(c); } Monitor * dirtomon(enum wlr_direction dir) { struct wlr_output *next; - if ((next = wlr_output_layout_adjacent_output(output_layout, + if (wlr_output_layout_get(output_layout, selmon->wlr_output) + && (next = wlr_output_layout_adjacent_output(output_layout, dir, selmon->wlr_output, selmon->m.x, selmon->m.y))) return next->data; - if ((next = wlr_output_layout_farthest_output(output_layout, + if (wlr_output_layout_get(output_layout, selmon->wlr_output) + && (next = wlr_output_layout_farthest_output(output_layout, dir ^ (WLR_DIRECTION_LEFT|WLR_DIRECTION_RIGHT), selmon->wlr_output, selmon->m.x, selmon->m.y))) return next->data; @@ -1087,56 +1069,70 @@ void focusclient(Client *c, int lift) { struct wlr_surface *old = seat->keyboard_state.focused_surface; - struct wlr_keyboard *kb; + int i; /* Raise client in stacking order if requested */ - if (c && lift) { - wl_list_remove(&c->slink); - wl_list_insert(&stack, &c->slink); - } + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); if (c && client_surface(c) == old) return; /* Put the new client atop the focus stack and select its monitor */ - if (c) { + if (c && !client_is_unmanaged(c)) { wl_list_remove(&c->flink); wl_list_insert(&fstack, &c->flink); selmon = c->mon; + c->isurgent = 0; + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], focuscolor); } - printstatus(); /* 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. */ + Client *w = client_from_wlr_surface(old); 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 { + } else if (w && w == exclusive_focus && client_wants_focus(w)) { + return; + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (w && !client_is_unmanaged(w) && (!c || !client_wants_focus(c))) { + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(w->border[i], bordercolor); + client_activate_surface(old, 0); } } + printstatus(); + checkidleinhibitor(NULL); + if (!c) { /* With no client, all we have left is to clear focus */ wlr_seat_keyboard_notify_clear_focus(seat); return; } + /* Change cursor surface */ + motionnotify(0); + /* Have a client, so focus its top-level wlr_surface */ - kb = wlr_seat_get_keyboard(seat); - wlr_seat_keyboard_notify_enter(seat, client_surface(c), - kb->keycodes, kb->num_keycodes, &kb->modifiers); + client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); /* Activate the new client */ client_activate_surface(client_surface(c), 1); @@ -1145,9 +1141,11 @@ focusclient(Client *c, int lift) void focusmon(const Arg *arg) { - do - selmon = dirtomon(arg->i); - while (!selmon->wlr_output->enabled); + int i = 0, nmons = wl_list_length(&mons); + if (nmons) + do /* don't switch to disabled mons */ + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); focusclient(focustop(selmon), 1); } @@ -1156,7 +1154,7 @@ focusstack(const Arg *arg) { /* Focus the next or previous client (in tiling order) on selmon */ Client *c, *sel = selclient(); - if (!sel) + if (!sel || sel->isfullscreen) return; if (arg->i > 0) { wl_list_for_each(c, &sel->link, link) { @@ -1177,6 +1175,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) { @@ -1188,16 +1189,17 @@ focustop(Monitor *m) } void -getxdecomode(struct wl_listener *listener, void *data) +fullscreennotify(struct wl_listener *listener, void *data) { - struct wlr_xdg_toplevel_decoration_v1 *wlr_deco = data; - wlr_xdg_toplevel_decoration_v1_set_mode(wlr_deco, - WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + Client *c = wl_container_of(listener, 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); } @@ -1212,10 +1214,10 @@ inputdevice(struct wl_listener *listener, void *data) switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: - createkeyboard(device); + createkeyboard(wlr_keyboard_from_input_device(device)); break; case WLR_INPUT_DEVICE_POINTER: - createpointer(device); + createpointer(wlr_pointer_from_input_device(device)); break; default: /* TODO handle other input device types */ @@ -1258,28 +1260,30 @@ keypress(struct wl_listener *listener, void *data) int i; /* This event is raised when a key is pressed or released. */ Keyboard *kb = wl_container_of(listener, kb, key); - struct wlr_event_keyboard_key *event = data; + struct wlr_keyboard_key_event *event = data; /* Translate libinput keycode -> xkbcommon */ uint32_t keycode = event->keycode + 8; /* Get a list of keysyms based on the keymap for this keyboard */ const xkb_keysym_t *syms; int nsyms = xkb_state_key_get_syms( - kb->device->keyboard->xkb_state, keycode, &syms); + kb->wlr_keyboard->xkb_state, keycode, &syms); int handled = 0; - uint32_t mods = wlr_keyboard_get_modifiers(kb->device->keyboard); + uint32_t mods = wlr_keyboard_get_modifiers(kb->wlr_keyboard); - wlr_idle_notify_activity(idle, seat); + IDLE_NOTIFY_ACTIVITY; - /* On _press_, attempt to process a compositor keybinding. */ - if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ + if (!input_inhibit_mgr->active_inhibitor + && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) for (i = 0; i < nsyms; i++) handled = keybinding(mods, syms[i]) || handled; if (!handled) { /* Pass unhandled keycodes along to the client. */ - wlr_seat_set_keyboard(seat, kb->device); + wlr_seat_set_keyboard(seat, kb->wlr_keyboard); wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, event->state); } @@ -1297,26 +1301,25 @@ keypressmod(struct wl_listener *listener, void *data) * same seat. You can swap out the underlying wlr_keyboard like this and * wlr_seat handles this transparently. */ - wlr_seat_set_keyboard(seat, kb->device); + wlr_seat_set_keyboard(seat, kb->wlr_keyboard); /* Send modifiers to the client. */ wlr_seat_keyboard_notify_modifiers(seat, - &kb->device->keyboard->modifiers); + &kb->wlr_keyboard->modifiers); } void killclient(const Arg *arg) { Client *sel = selclient(); - if (!sel) - return; - client_send_close(sel); + if (sel) + client_send_close(sel); } void maplayersurfacenotify(struct wl_listener *listener, void *data) { - LayerSurface *layersurface = wl_container_of(listener, layersurface, map); - wlr_surface_send_enter(layersurface->layer_surface->surface, layersurface->layer_surface->output); + LayerSurface *l = wl_container_of(listener, l, map); + wlr_surface_send_enter(l->layer_surface->surface, l->mon->wlr_output); motionnotify(0); } @@ -1324,25 +1327,86 @@ void mapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ - Client *c = wl_container_of(listener, c, map); + Client *p, *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + /* Create scene tree for this client and its border */ + c->scene = wlr_scene_tree_create(layers[LyrTile]); + c->scene_surface = c->type == XDGShell + ? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg) + : wlr_scene_subsurface_tree_create(c->scene, client_surface(c)); + if (client_surface(c)) { + client_surface(c)->data = c->scene; + /* Ideally we should do this in createnotify{,x11} but at that moment + * wlr_xwayland_surface doesn't have wlr_surface yet. */ + LISTEN(&client_surface(c)->events.commit, &c->commit, commitnotify); + } + 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)) { - /* Insert this independent into independents lists. */ - wl_list_insert(&independents, &c->link); - return; + client_get_geometry(c, &c->geom); + /* 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); + if (client_wants_focus(c)) { + focusclient(c, 1); + exclusive_focus = c; + } + goto unset_fullscreen; + } +#endif + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, bordercolor); + c->border[i]->node.data = c; + wlr_scene_rect_set_color(c->border[i], bordercolor); } - /* Insert this client into client lists. */ - wl_list_insert(&clients, &c->link); - wl_list_insert(&fstack, &c->flink); - wl_list_insert(&stack, &c->slink); - + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); client_get_geometry(c, &c->geom); c->geom.width += 2 * c->bw; c->geom.height += 2 * c->bw; - /* Set initial monitor, tags, floating status, and focus */ - applyrules(c); + /* Insert this client into client lists. */ + wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* 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 */ + /* TODO: https://github.com/djpohly/dwl/pull/334#issuecomment-1330166324 */ + if (c->type == XDGShell && (p = client_get_parent(c))) { + c->isfloating = 1; + wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); + setmon(c, p->mon, p->tags); + } else { + applyrules(c); + } + printstatus(); + +unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) + if (w != c && w->isfullscreen && VISIBLEON(w, m)) + setfullscreen(w, 0); +} + +void +maximizenotify(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. + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + Client *c = wl_container_of(listener, c, maximize); + wlr_xdg_surface_schedule_configure(c->surface.xdg); } void @@ -1353,8 +1417,10 @@ monocle(Monitor *m) wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) continue; - resize(c, m->w.x, m->w.y, m->w.width, m->w.height, 0); + resize(c, m->w, 0); } + if ((c = focustop(m))) + wlr_scene_node_raise_to_top(&c->scene->node); } void @@ -1366,8 +1432,8 @@ motionabsolute(struct wl_listener *listener, void *data) * move the mouse over the window. You could enter the window from any edge, * so we have to warp the mouse there. There is also some hardware which * emits these events. */ - struct wlr_event_pointer_motion_absolute *event = data; - wlr_cursor_warp_absolute(cursor, event->device, event->x, event->y); + struct wlr_pointer_motion_absolute_event *event = data; + wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); motionnotify(event->time_msec); } @@ -1375,64 +1441,53 @@ void motionnotify(uint32_t time) { double sx = 0, sy = 0; - struct wlr_surface *surface = NULL; Client *c = NULL; + LayerSurface *l; + struct wlr_surface *surface = NULL; + struct wlr_drag_icon *icon; - // time is 0 in internal calls meant to restore pointer focus. + /* time is 0 in internal calls meant to restore pointer focus. */ if (time) { - wlr_idle_notify_activity(idle, seat); + IDLE_NOTIFY_ACTIVITY; /* Update selmon (even while dragging a window) */ if (sloppyfocus) 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); /* If we are currently grabbing the mouse, handle and return */ if (cursor_mode == CurMove) { /* Move the grabbed client to the new position. */ - resize(grabc, cursor->x - grabcx, cursor->y - grabcy, - grabc->geom.width, grabc->geom.height, 1); + resize(grabc, (struct wlr_box){.x = cursor->x - grabcx, .y = cursor->y - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); return; } else if (cursor_mode == CurResize) { - resize(grabc, grabc->geom.x, grabc->geom.y, - cursor->x - grabc->geom.x, - cursor->y - grabc->geom.y, 1); + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = cursor->x - grabc->geom.x, .height = cursor->y - grabc->geom.y}, 1); return; } - if ((surface = xytolayersurface(&selmon->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - cursor->x, cursor->y, &sx, &sy))) - ; - else if ((surface = xytolayersurface(&selmon->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - cursor->x, cursor->y, &sx, &sy))) - ; -#ifdef XWAYLAND - /* Find an independent under the pointer and send the event along. */ - else if ((c = xytoindependent(cursor->x, cursor->y))) { - surface = wlr_surface_surface_at(c->surface.xwayland->surface, - cursor->x - c->surface.xwayland->x - c->bw, - cursor->y - c->surface.xwayland->y - c->bw, &sx, &sy); + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); - /* Otherwise, find the client under the pointer and send the event along. */ + if (cursor_mode == CurPressed && !seat->drag) { + if ((l = toplevel_from_wlr_layer_surface( + seat->pointer_state.focused_surface))) { + surface = seat->pointer_state.focused_surface; + sx = cursor->x - l->geom.x; + sy = cursor->y - l->geom.y; + } } -#endif - else if ((c = xytoclient(cursor->x, cursor->y))) { - surface = client_surface_at(c, cursor->x - c->geom.x - c->bw, - cursor->y - c->geom.y - c->bw, &sx, &sy); - } - else if ((surface = xytolayersurface(&selmon->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - cursor->x, cursor->y, &sx, &sy))) - ; - else - surface = xytolayersurface(&selmon->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - cursor->x, cursor->y, &sx, &sy); /* 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 && !seat->drag && (!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); } @@ -1442,21 +1497,23 @@ motionrelative(struct wl_listener *listener, void *data) { /* This event is forwarded by the cursor when a pointer emits a _relative_ * pointer motion event (i.e. a delta) */ - struct wlr_event_pointer_motion *event = data; + struct wlr_pointer_motion_event *event = data; /* The cursor doesn't move unless we tell it to. The cursor automatically * handles constraining the motion to the output layout, as well as any * special configuration applied for the specific input device which * generated the event. You can pass NULL for the device if you want to move * the cursor around without any input. */ - wlr_cursor_move(cursor, event->device, - event->delta_x, event->delta_y); + wlr_cursor_move(cursor, &event->pointer->base, event->delta_x, event->delta_y); motionnotify(event->time_msec); } void moveresize(const Arg *arg) { - if (cursor_mode != CurNormal || !(grabc = xytoclient(cursor->x, cursor->y))) + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) return; /* Float the window and tell motionnotify to grab it */ @@ -1465,7 +1522,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 @@ -1474,7 +1531,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; } } @@ -1500,37 +1557,46 @@ outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) wl_list_for_each(config_head, &config->heads, link) { struct wlr_output *wlr_output = config_head->state.output; + Monitor *m = wlr_output->data; wlr_output_enable(wlr_output, config_head->state.enabled); - if (config_head->state.enabled) { - if (config_head->state.mode) - wlr_output_set_mode(wlr_output, config_head->state.mode); - else - wlr_output_set_custom_mode(wlr_output, - config_head->state.custom_mode.width, - config_head->state.custom_mode.height, - config_head->state.custom_mode.refresh); + if (!config_head->state.enabled) + goto apply_or_test; + if (config_head->state.mode) + wlr_output_set_mode(wlr_output, config_head->state.mode); + else + wlr_output_set_custom_mode(wlr_output, + config_head->state.custom_mode.width, + config_head->state.custom_mode.height, + config_head->state.custom_mode.refresh); + /* 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); - } + wlr_output_set_transform(wlr_output, config_head->state.transform); + wlr_output_set_scale(wlr_output, config_head->state.scale); + wlr_output_enable_adaptive_sync(wlr_output, + config_head->state.adaptive_sync_enabled); - if (!(ok = wlr_output_test(wlr_output))) - break; - } - wl_list_for_each(config_head, &config->heads, link) { - if (ok && !test) - wlr_output_commit(config_head->state.output); - else - wlr_output_rollback(config_head->state.output); +apply_or_test: + if (test) { + ok &= wlr_output_test(wlr_output); + wlr_output_rollback(wlr_output); + } else { + ok &= wlr_output_commit(wlr_output); + } } + if (ok) wlr_output_configuration_v1_send_succeeded(config); else wlr_output_configuration_v1_send_failed(config); wlr_output_configuration_v1_destroy(config); + + /* TODO: use a wrapper function? */ + updatemons(NULL, NULL); } void @@ -1547,9 +1613,8 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, struct timespec now; int internal_call = !time; - /* Use top level surface if nothing more specific given */ - if (c && !surface) - surface = client_surface(c); + if (sloppyfocus && !internal_call && c && !client_is_unmanaged(c)) + focusclient(c, 0); /* If surface is NULL, clear pointer focus */ if (!surface) { @@ -1562,46 +1627,46 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, time = now.tv_sec * 1000 + now.tv_nsec / 1000000; } - /* If surface is already focused, only notify of motion */ - if (surface == seat->pointer_state.focused_surface) { - wlr_seat_pointer_notify_motion(seat, time, sx, sy); - return; - } - - /* Otherwise, let the client know that the mouse cursor has entered one - * of its surfaces, and make keyboard focus follow if desired. */ + /* Let the client know that the mouse cursor has entered one + * of its surfaces, and make keyboard focus follow if desired. + * wlroots makes this a no-op if surface is already focused */ wlr_seat_pointer_notify_enter(seat, surface, sx, sy); - - if (!c || client_is_unmanaged(c)) - return; - - if (sloppyfocus && !internal_call) - focusclient(c, 0); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); } void printstatus(void) { Monitor *m = NULL; - Client *c = NULL; - unsigned int activetags; + Client *c; + unsigned int occ, urg, sel; wl_list_for_each(m, &mons, link) { - activetags=0; + occ = urg = 0; wl_list_for_each(c, &clients, link) { - if (c->mon == m) - activetags |= c->tags; + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; } - if (focustop(m)) - printf("%s title %s\n", m->wlr_output->name, client_get_title(focustop(m))); - else + if ((c = focustop(m))) { + printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); + printf("%s fullscreen %u\n", m->wlr_output->name, c->isfullscreen); + printf("%s floating %u\n", m->wlr_output->name, c->isfloating); + sel = c->tags; + } else { printf("%s title \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 %u %u\n", m->wlr_output->name, activetags, m->tagset[m->seltags]); + printf("%s tags %u %u %u %u\n", m->wlr_output->name, occ, m->tagset[m->seltags], + sel, urg); printf("%s layout %s\n", m->wlr_output->name, m->lt[m->sellt]->symbol); } - fflush(stdout); } void @@ -1611,201 +1676,64 @@ quit(const Arg *arg) } void -render(struct wlr_surface *surface, int sx, int sy, void *data) +quitsignal(int signo) { - /* This function is called for every surface that needs to be rendered. */ - struct render_data *rdata = data; - struct wlr_output *output = rdata->output; - double ox = 0, oy = 0; - struct wlr_box obox; - float matrix[9]; - enum wl_output_transform transform; - - /* We first obtain a wlr_texture, which is a GPU resource. wlroots - * automatically handles negotiating these with the client. The underlying - * resource could be an opaque handle passed from the client, or the client - * could have sent a pixel buffer which we copied to the GPU, or a few other - * means. You don't have to worry about this, wlroots takes care of it. */ - struct wlr_texture *texture = wlr_surface_get_texture(surface); - if (!texture) - return; - - /* The client has a position in layout coordinates. If you have two displays, - * one next to the other, both 1080p, a client on the rightmost display might - * have layout coordinates of 2000,100. We need to translate that to - * output-local coordinates, or (2000 - 1920). */ - wlr_output_layout_output_coords(output_layout, output, &ox, &oy); - - /* We also have to apply the scale factor for HiDPI outputs. This is only - * part of the puzzle, dwl does not fully support HiDPI. */ - obox.x = ox + rdata->x + sx; - obox.y = oy + rdata->y + sy; - obox.width = surface->current.width; - obox.height = surface->current.height; - scalebox(&obox, output->scale); - - /* - * Those familiar with OpenGL are also familiar with the role of matrices - * in graphics programming. We need to prepare a matrix to render the - * client with. wlr_matrix_project_box is a helper which takes a box with - * a desired x, y coordinates, width and height, and an output geometry, - * then prepares an orthographic projection and multiplies the necessary - * transforms to produce a model-view-projection matrix. - * - * Naturally you can do this any way you like, for example to make a 3D - * compositor. - */ - transform = wlr_output_transform_invert(surface->current.transform); - wlr_matrix_project_box(matrix, &obox, transform, 0, - output->transform_matrix); - - /* This takes our matrix, the texture, and an alpha, and performs the actual - * rendering on the GPU. */ - wlr_render_texture_with_matrix(drw, texture, matrix, 1); - - /* This lets the client know that we've displayed that frame and it can - * prepare another one now if it likes. */ - wlr_surface_send_frame_done(surface, rdata->when); -} - -void -renderclients(Monitor *m, struct timespec *now) -{ - Client *c, *sel = selclient(); - const float *color; - double ox, oy; - int i, w, h; - struct render_data rdata; - struct wlr_box *borders; - struct wlr_surface *surface; - /* Each subsequent window we render is rendered on top of the last. Because - * our stacking list is ordered front-to-back, we iterate over it backwards. */ - wl_list_for_each_reverse(c, &stack, slink) { - /* Only render visible clients which show on this monitor */ - if (!VISIBLEON(c, c->mon) || !wlr_output_layout_intersects( - output_layout, m->wlr_output, &c->geom)) - continue; - - surface = client_surface(c); - ox = c->geom.x, oy = c->geom.y; - wlr_output_layout_output_coords(output_layout, m->wlr_output, - &ox, &oy); - - if (c->bw) { - w = surface->current.width; - h = surface->current.height; - borders = (struct wlr_box[4]) { - {ox, oy, w + 2 * c->bw, c->bw}, /* top */ - {ox, oy + c->bw, c->bw, h}, /* left */ - {ox + c->bw + w, oy + c->bw, c->bw, h}, /* right */ - {ox, oy + c->bw + h, w + 2 * c->bw, c->bw}, /* bottom */ - }; - - /* Draw window borders */ - color = (c == sel) ? focuscolor : bordercolor; - for (i = 0; i < 4; i++) { - scalebox(&borders[i], m->wlr_output->scale); - wlr_render_rect(drw, &borders[i], color, - m->wlr_output->transform_matrix); - } - } - - /* This calls our render function for each surface among the - * xdg_surface's toplevel and popups. */ - rdata.output = m->wlr_output; - rdata.when = now; - rdata.x = c->geom.x + c->bw; - rdata.y = c->geom.y + c->bw; - client_for_each_surface(c, render, &rdata); - } -} - -void -renderlayer(struct wl_list *layer_surfaces, struct timespec *now) -{ - LayerSurface *layersurface; - wl_list_for_each(layersurface, layer_surfaces, link) { - struct render_data rdata = { - .output = layersurface->layer_surface->output, - .when = now, - .x = layersurface->geo.x, - .y = layersurface->geo.y, - }; - - wlr_surface_for_each_surface(layersurface->layer_surface->surface, - render, &rdata); - } + quit(NULL); } void rendermon(struct wl_listener *listener, void *data) { - Client *c; - int render = 1; - /* This function is called every time an output is ready to display a frame, * generally at the output's refresh rate (e.g. 60Hz). */ Monitor *m = wl_container_of(listener, m, frame); - + Client *c; struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - /* Do not render if any XDG clients have an outstanding resize. */ - wl_list_for_each(c, &stack, slink) { - if (c->resize) { - wlr_surface_send_frame_done(client_surface(c), &now); - render = 0; - } - } - - /* wlr_output_attach_render makes the OpenGL context current. */ - if (!wlr_output_attach_render(m->wlr_output, NULL)) + /* Render if no XDG clients have an outstanding resize and are visible on + * this monitor. */ + wl_list_for_each(c, &clients, link) + if (client_is_rendered_on_mon(c, m) && (!c->isfloating && c->resize)) + goto skip; + if (!wlr_scene_output_commit(m->scene_output)) return; - - if (render) { - /* Begin the renderer (calls glViewport and some other GL sanity checks) */ - wlr_renderer_begin(drw, m->wlr_output->width, m->wlr_output->height); - wlr_renderer_clear(drw, rootcolor); - - renderlayer(&m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now); - renderlayer(&m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now); - renderclients(m, &now); -#ifdef XWAYLAND - renderindependents(m->wlr_output, &now); -#endif - renderlayer(&m->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now); - renderlayer(&m->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now); - - /* Hardware cursors are rendered by the GPU on a separate plane, and can be - * moved around without re-rendering what's beneath them - which is more - * efficient. However, not all hardware supports hardware cursors. For this - * reason, wlroots provides a software fallback, which we ask it to render - * here. wlr_cursor handles configuring hardware vs software cursors for you, - * and this function is a no-op when hardware cursors are in use. */ - wlr_output_render_software_cursors(m->wlr_output, NULL); - - /* Conclude rendering and swap the buffers, showing the final frame - * on-screen. */ - wlr_renderer_end(drw); - } - - wlr_output_commit(m->wlr_output); +skip: + /* Let clients know a frame has been rendered */ + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(m->scene_output, &now); } void -resize(Client *c, int x, int y, int w, int h, int interact) +requeststartdrag(struct wl_listener *listener, void *data) +{ + struct wlr_seat_request_start_drag_event *event = data; + + if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, + event->serial)) + wlr_seat_start_pointer_drag(seat, event->drag, event->serial); + else + wlr_data_source_destroy(event->drag->source); +} + +void +resize(Client *c, struct wlr_box geo, int interact) { - /* - * Note that I took some shortcuts here. In a more fleshed-out - * compositor, you'd wait for the client to prepare a buffer at - * the new size, then commit any movement that was prepared. - */ struct wlr_box *bbox = interact ? &sgeom : &c->mon->w; - c->geom.x = x; - c->geom.y = y; - c->geom.width = w; - c->geom.height = h; + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); + 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); + /* wlroots makes this a no-op if size hasn't changed */ c->resize = client_set_size(c, c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw); @@ -1814,19 +1742,40 @@ resize(Client *c, int x, int y, int w, int h, int interact) void run(char *startup_cmd) { - pid_t startup_pid = -1; - /* Add a Unix socket to the Wayland display. */ const char *socket = wl_display_add_socket_auto(dpy); if (!socket) - BARF("startup: display_add_socket_auto"); + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); /* Start the backend. This will enumerate outputs and inputs, become the DRM * master, etc */ if (!wlr_backend_start(backend)) - BARF("startup: backend_start"); + die("startup: backend_start"); - /* Now that outputs are initialized, choose initial selmon based on + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { + int piperw[2]; + if (pipe(piperw) < 0) + die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); + close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } + dup2(piperw[1], STDOUT_FILENO); + close(piperw[1]); + close(piperw[0]); + } + /* If nobody is reading the status output, don't terminate */ + signal(SIGPIPE, SIG_IGN); + printstatus(); + + /* 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); @@ -1835,42 +1784,13 @@ 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); - - /* Set the WAYLAND_DISPLAY environment variable to our socket and run the - * startup command if requested. */ - setenv("WAYLAND_DISPLAY", socket, 1); - - if (startup_cmd) { - startup_pid = fork(); - if (startup_pid < 0) - EBARF("startup: fork"); - if (startup_pid == 0) { - dup2(STDERR_FILENO, STDOUT_FILENO); - execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); - EBARF("startup: execl"); - } - } + 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 * loop configuration to listen to libinput events, DRM events, generate * frame events at the refresh rate, and so on. */ wl_display_run(dpy); - - if (startup_cmd) { - kill(startup_pid, SIGTERM); - waitpid(startup_pid, NULL, 0); - } -} - -void -scalebox(struct wlr_box *box, float scale) -{ - box->width = ROUND((box->x + box->width) * scale) - ROUND(box->x * scale); - box->height = ROUND((box->y + box->height) * scale) - ROUND(box->y * scale); - box->x = ROUND(box->x * scale); - box->y = ROUND(box->y * scale); } Client * @@ -1887,10 +1807,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 (cursor_mode != CurNormal) + /* 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 && cursor_mode != CurPressed) 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 @@ -1905,12 +1827,39 @@ void setfloating(Client *c, int floating) { c->isfloating = floating; + wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrFloat : LyrTile]); arrange(c->mon); + printstatus(); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + c->isfullscreen = fullscreen; + if (!c->mon) + return; + c->bw = fullscreen ? 0 : borderpx; + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[fullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); + + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->m, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } + arrange(c->mon); + printstatus(); } 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) @@ -1926,7 +1875,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) @@ -1943,6 +1892,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) { @@ -1951,10 +1901,10 @@ setmon(Client *c, Monitor *m, unsigned int newtags) } if (m) { /* Make sure window actually overlaps with the monitor */ - applybounds(c, &m->m); + 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); } @@ -1984,12 +1934,21 @@ setsel(struct wl_listener *listener, void *data) void setup(void) { + /* Force line-buffered stdout */ + setvbuf(stdout, NULL, _IOLBF, 0); + /* The Wayland display is managed by libwayland. It handles accepting * clients from the Unix socket, manging Wayland globals, and so on. */ dpy = wl_display_create(); - /* clean up child processes immediately */ + /* Set up signal handlers */ +#ifdef XWAYLAND sigchld(0); +#else + signal(SIGCHLD, SIG_IGN); +#endif + signal(SIGINT, quitsignal); + signal(SIGTERM, quitsignal); /* The backend is a wlroots feature which abstracts the underlying input and * output hardware. The autocreate option will choose the most suitable @@ -2000,14 +1959,28 @@ setup(void) * if the backend does not support hardware cursors (some older GPUs * don't). */ if (!(backend = wlr_backend_autocreate(dpy))) - BARF("couldn't create backend"); + die("couldn't create backend"); - /* If we don't provide a renderer, autocreate makes a GLES2 renderer for us. - * The renderer is responsible for defining the various pixel formats it - * supports for shared memory, this configures that for clients. */ - drw = wlr_backend_get_renderer(backend); + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); + layers[LyrBg] = wlr_scene_tree_create(&scene->tree); + layers[LyrBottom] = wlr_scene_tree_create(&scene->tree); + layers[LyrTile] = wlr_scene_tree_create(&scene->tree); + layers[LyrFloat] = wlr_scene_tree_create(&scene->tree); + layers[LyrFS] = wlr_scene_tree_create(&scene->tree); + layers[LyrTop] = wlr_scene_tree_create(&scene->tree); + layers[LyrOverlay] = wlr_scene_tree_create(&scene->tree); + layers[LyrDragIcon] = wlr_scene_tree_create(&scene->tree); + + /* Create a renderer with the default implementation */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); wlr_renderer_init_wl_display(drw, dpy); + /* Create a default allocator */ + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't create allocator"); + /* This creates some hands-off wlroots interfaces. The compositor is * necessary for clients to allocate surfaces and the data device manager * handles the clipboard. Each of these wlroots interfaces has room for you @@ -2022,6 +1995,12 @@ setup(void) wlr_gamma_control_manager_v1_create(dpy); wlr_primary_selection_v1_device_manager_create(dpy); wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_subcompositor_create(dpy); + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); + wl_signal_add(&activation->events.request_activate, &request_activate); /* Creates an output layout, which a wlroots utility for working with an * arrangement of screens in a physical layout. */ @@ -2042,20 +2021,26 @@ setup(void) */ wl_list_init(&clients); wl_list_init(&fstack); - wl_list_init(&stack); - wl_list_init(&independents); idle = wlr_idle_create(dpy); + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); + wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &idle_inhibitor_create); layer_shell = wlr_layer_shell_v1_create(dpy); wl_signal_add(&layer_shell->events.new_surface, &new_layer_shell_surface); - xdg_shell = wlr_xdg_shell_create(dpy); + xdg_shell = wlr_xdg_shell_create(dpy, 4); wl_signal_add(&xdg_shell->events.new_surface, &new_xdg_surface); - /* Use xdg_decoration protocol to negotiate server-side decorations */ - xdeco_mgr = wlr_xdg_decoration_manager_v1_create(dpy); - wl_signal_add(&xdeco_mgr->events.new_toplevel_decoration, &new_xdeco); + input_inhibit_mgr = wlr_input_inhibit_manager_create(dpy); + + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + wlr_xdg_decoration_manager_v1_create(dpy); /* * Creates a cursor, which is a wlroots utility for tracking the cursor @@ -2100,17 +2085,18 @@ setup(void) wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard, &new_virtual_keyboard); seat = wlr_seat_create(dpy, "seat0"); - wl_signal_add(&seat->events.request_set_cursor, - &request_cursor); - wl_signal_add(&seat->events.request_set_selection, - &request_set_sel); - wl_signal_add(&seat->events.request_set_primary_selection, - &request_set_psel); + wl_signal_add(&seat->events.request_set_cursor, &request_cursor); + wl_signal_add(&seat->events.request_set_selection, &request_set_sel); + wl_signal_add(&seat->events.request_set_primary_selection, &request_set_psel); + wl_signal_add(&seat->events.request_start_drag, &request_start_drag); + wl_signal_add(&seat->events.start_drag, &start_drag); output_mgr = wlr_output_manager_v1_create(dpy); wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); wl_signal_add(&output_mgr->events.test, &output_mgr_test); + wlr_scene_set_presentation(scene, wlr_presentation_create(dpy, backend)); + #ifdef XWAYLAND /* * Initialise the XWayland X server. @@ -2121,18 +2107,6 @@ setup(void) wl_signal_add(&xwayland->events.ready, &xwayland_ready); wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); - /* - * Create the XWayland cursor manager at scale 1, setting its default - * pointer to match the rest of dwl. - */ - xcursor_mgr = wlr_xcursor_manager_create(NULL, 24); - wlr_xcursor_manager_load(xcursor_mgr, 1); - if ((xcursor = wlr_xcursor_manager_get_xcursor(xcursor_mgr, "left_ptr", 1))) - wlr_xwayland_set_cursor(xwayland, - xcursor->images[0]->buffer, xcursor->images[0]->width * 4, - xcursor->images[0]->width, xcursor->images[0]->height, - xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); - setenv("DISPLAY", xwayland->display_name, 1); } else { fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); @@ -2140,20 +2114,6 @@ setup(void) #endif } -void -sigchld(int unused) -{ - /* We should be able to remove this function in favor of a simple - * signal(SIGCHLD, SIG_IGN); - * but the Xwayland implementation in wlroots currently prevents us from - * setting our own disposition for SIGCHLD. - */ - if (signal(SIGCHLD, sigchld) == SIG_ERR) - EBARF("can't install SIGCHLD handler"); - while (0 < waitpid(-1, NULL, WNOHANG)) - ; -} - void spawn(const Arg *arg) { @@ -2161,10 +2121,23 @@ spawn(const Arg *arg) dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); execvp(((char **)arg->v)[0], (char **)arg->v); - EBARF("dwl: execvp %s failed", ((char **)arg->v)[0]); + die("dwl: execvp %s failed:", ((char **)arg->v)[0]); } } +void +startdrag(struct wl_listener *listener, void *data) +{ + struct wlr_drag *drag = data; + + if (!drag->icon) + return; + + drag->icon->data = wlr_scene_subsurface_tree_create(layers[LyrDragIcon], drag->icon->surface); + motionnotify(0); + wl_signal_add(&drag->icon->events.destroy, &drag_icon_destroy); +} + void tag(const Arg *arg) { @@ -2181,19 +2154,18 @@ 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 tile(Monitor *m) { - unsigned int i, n = 0, h, mw, my, ty; + unsigned int i, n = 0, mw, my, ty; Client *c; wl_list_for_each(c, &clients, link) - if (VISIBLEON(c, m) && !c->isfloating) + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) n++; if (n == 0) return; @@ -2207,12 +2179,12 @@ tile(Monitor *m) if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) continue; if (i < m->nmaster) { - h = (m->w.height - my) / (MIN(n, m->nmaster) - i); - resize(c, m->w.x, m->w.y + my, mw, h, 0); + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, + .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); my += c->geom.height; } else { - h = (m->w.height - ty) / (n - i); - resize(c, m->w.x + mw, m->w.y + ty, m->w.width - mw, h, 0); + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, + .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ty += c->geom.height; } i++; @@ -2223,10 +2195,17 @@ void togglefloating(const Arg *arg) { Client *sel = selclient(); - if (!sel) - return; /* return if fullscreen */ - setfloating(sel, !sel->isfloating /* || sel->isfixed */); + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +} + +void +togglefullscreen(const Arg *arg) +{ + Client *sel = selclient(); + if (sel) + setfullscreen(sel, !sel->isfullscreen); } void @@ -2248,7 +2227,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; @@ -2259,34 +2238,48 @@ toggleview(const Arg *arg) } void -unmaplayersurface(LayerSurface *layersurface) +unmaplayersurfacenotify(struct wl_listener *listener, void *data) { - layersurface->layer_surface->mapped = 0; + LayerSurface *layersurface = wl_container_of(listener, layersurface, unmap); + + layersurface->mapped = 0; + wlr_scene_node_set_enabled(&layersurface->scene->node, 0); + 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); motionnotify(0); } -void -unmaplayersurfacenotify(struct wl_listener *listener, void *data) -{ - LayerSurface *layersurface = wl_container_of(listener, layersurface, unmap); - unmaplayersurface(layersurface); -} - void unmapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is unmapped, and should no longer be shown. */ Client *c = wl_container_of(listener, c, unmap); - wl_list_remove(&c->link); - if (client_is_unmanaged(c)) - return; + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; + } - setmon(c, NULL, 0); - wl_list_remove(&c->flink); - wl_list_remove(&c->slink); + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) + exclusive_focus = NULL; + if (client_surface(c) == seat->keyboard_state.focused_surface) + focusclient(selclient(), 1); + } else { + wl_list_remove(&c->link); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } + + wl_list_remove(&c->commit.link); + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0); } void @@ -2301,35 +2294,83 @@ updatemons(struct wl_listener *listener, void *data) */ struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create(); + Client *c; + struct wlr_output_configuration_head_v1 *config_head; Monitor *m; - sgeom = *wlr_output_layout_get_box(output_layout, NULL); - wl_list_for_each(m, &mons, link) { - struct wlr_output_configuration_head_v1 *config_head = - wlr_output_configuration_head_v1_create(config, m->wlr_output); - /* TODO: move clients off disabled monitors */ - /* TODO: move focus if selmon is disabled */ + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head->state.enabled = 0; + /* Remove this output from the layout to avoid cursor enter inside it */ + wlr_output_layout_remove(output_layout, m->wlr_output); + closemon(m); + memset(&m->m, 0, sizeof(m->m)); + memset(&m->w, 0, sizeof(m->w)); + } + /* Insert outputs that need to */ + wl_list_for_each(m, &mons, link) + if (m->wlr_output->enabled + && !wlr_output_layout_get(output_layout, m->wlr_output)) + wlr_output_layout_add_auto(output_layout, m->wlr_output); + /* Now that we update the output layout we can get its box */ + wlr_output_layout_get_box(output_layout, NULL, &sgeom); + wl_list_for_each(m, &mons, link) { + if (!m->wlr_output->enabled) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); /* Get the effective monitor geometry to use for surfaces */ - m->m = m->w = *wlr_output_layout_get_box(output_layout, m->wlr_output); + wlr_output_layout_get_box(output_layout, m->wlr_output, &(m->m)); + wlr_output_layout_get_box(output_layout, m->wlr_output, &(m->w)); + wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); /* Calculate the effective monitor geometry to use for clients */ arrangelayers(m); /* Don't move clients to the left output when plugging monitors */ arrange(m); - config_head->state.enabled = m->wlr_output->enabled; + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); + + config_head->state.enabled = 1; config_head->state.mode = m->wlr_output->current_mode; config_head->state.x = m->m.x; config_head->state.y = m->m.y; } + if (selmon && selmon->wlr_output->enabled) + wl_list_for_each(c, &clients, link) + if (!c->mon && client_is_mapped(c)) + setmon(c, selmon, c->tags); + wlr_output_manager_v1_set_configuration(output_mgr, config); } +void +updatetitle(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + printstatus(); +} + +void +urgent(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = client_from_wlr_surface(event->surface); + if (c && c != selclient()) { + c->isurgent = 1; + printstatus(); + } +} + 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) @@ -2343,41 +2384,7 @@ void virtualkeyboard(struct wl_listener *listener, void *data) { struct wlr_virtual_keyboard_v1 *keyboard = data; - struct wlr_input_device *device = &keyboard->input_device; - createkeyboard(device); -} - -Client * -xytoclient(double x, double y) -{ - /* Find the topmost visible client (if any) at point (x, y), including - * borders. This relies on stack being ordered from top to bottom. */ - Client *c; - wl_list_for_each(c, &stack, slink) - if (VISIBLEON(c, c->mon) && wlr_box_contains_point(&c->geom, x, y)) - return c; - return NULL; -} - -struct wlr_surface * -xytolayersurface(struct wl_list *layer_surfaces, double x, double y, - double *sx, double *sy) -{ - LayerSurface *layersurface; - wl_list_for_each_reverse(layersurface, layer_surfaces, link) { - struct wlr_surface *sub; - if (!layersurface->layer_surface->mapped) - continue; - sub = wlr_layer_surface_v1_surface_at( - layersurface->layer_surface, - x - layersurface->geo.x, - y - layersurface->geo.y, - sx, sy); - if (sub) - return sub; - - } - return NULL; + createkeyboard(&keyboard->keyboard); } Monitor * @@ -2387,12 +2394,46 @@ xytomon(double x, double y) return o ? o->data : NULL; } +struct wlr_scene_node * +xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +{ + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + const int *layer; + int focus_order[] = { LyrOverlay, LyrTop, LyrFS, LyrFloat, LyrTile, LyrBottom, LyrBg }; + + for (layer = focus_order; layer < END(focus_order); layer++) { + if ((node = wlr_scene_node_at(&layers[*layer]->node, x, y, nx, ny))) { + if (node->type == WLR_SCENE_NODE_BUFFER) + surface = wlr_scene_surface_from_buffer( + wlr_scene_buffer_from_node(node))->surface; + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; + if (c && c->type == LayerShell) { + c = NULL; + l = pnode->data; + } + } + if (surface) + break; + } + + if (psurface) *psurface = surface; + if (pc) *pc = c; + if (pl) *pl = l; + return node; +} + void 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 @@ -2435,36 +2476,36 @@ configurex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, configure); struct wlr_xwayland_surface_configure_event *event = data; - wlr_xwayland_surface_configure(c->surface.xwayland, - event->x, event->y, event->width, event->height); + if (!c->mon) + return; + if (c->isfloating || c->type == X11Unmanaged) + resize(c, (struct wlr_box){.x = event->x, .y = event->y, + .width = event->width, .height = event->height}, 0); + else + arrange(c->mon); } void createnotifyx11(struct wl_listener *listener, void *data) { - struct wlr_xwayland_surface *xwayland_surface = data; + struct wlr_xwayland_surface *xsurface = data; Client *c; - wl_list_for_each(c, &clients, link) - if (c->isfullscreen && VISIBLEON(c, c->mon)) - setfullscreen(c, 0); /* Allocate a Client for this surface */ - c = xwayland_surface->data = calloc(1, sizeof(*c)); - c->surface.xwayland = xwayland_surface; - c->type = xwayland_surface->override_redirect ? X11Unmanaged : X11Managed; + c = xsurface->data = ecalloc(1, sizeof(*c)); + c->surface.xwayland = xsurface; + c->type = xsurface->override_redirect ? X11Unmanaged : X11Managed; c->bw = borderpx; - c->isfullscreen = 0; /* Listen to the various events it can emit */ - LISTEN(&xwayland_surface->events.map, &c->map, mapnotify); - LISTEN(&xwayland_surface->events.unmap, &c->unmap, unmapnotify); - LISTEN(&xwayland_surface->events.request_activate, &c->activate, - activatex11); - LISTEN(&xwayland_surface->events.request_configure, &c->configure, - configurex11); - LISTEN(&xwayland_surface->events.destroy, &c->destroy, destroynotify); - LISTEN(&xwayland_surface->events.request_fullscreen, &c->fullscreen, - fullscreennotify); + LISTEN(&xsurface->events.map, &c->map, mapnotify); + LISTEN(&xsurface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); + LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); } Atom @@ -2481,30 +2522,34 @@ getatom(xcb_connection_t *xc, const char *name) } void -renderindependents(struct wlr_output *output, struct timespec *now) +sethints(struct wl_listener *listener, void *data) { - Client *c; - struct render_data rdata; - struct wlr_box geom; - - wl_list_for_each_reverse(c, &independents, link) { - geom.x = c->surface.xwayland->x; - geom.y = c->surface.xwayland->y; - geom.width = c->surface.xwayland->width; - geom.height = c->surface.xwayland->height; - - /* Only render visible clients which show on this output */ - if (!wlr_output_layout_intersects(output_layout, output, &geom)) - continue; - - rdata.output = output; - rdata.when = now; - rdata.x = c->surface.xwayland->x; - rdata.y = c->surface.xwayland->y; - wlr_surface_for_each_surface(c->surface.xwayland->surface, render, &rdata); + Client *c = wl_container_of(listener, c, set_hints); + if (c != selclient()) { + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + printstatus(); } } +void +sigchld(int unused) +{ + siginfo_t in; + /* We should be able to remove this function in favor of a simple + * signal(SIGCHLD, SIG_IGN); + * but the Xwayland implementation in wlroots currently prevents us from + * setting our own disposition for SIGCHLD. + */ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("can't install SIGCHLD handler:"); + /* WNOWAIT leaves the child in a waitable state, in case this is the + * XWayland process + */ + 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); +} + void xwaylandready(struct wl_listener *listener, void *data) { @@ -2535,28 +2580,6 @@ xwaylandready(struct wl_listener *listener, void *data) xcb_disconnect(xc); } - -Client * -xytoindependent(double x, double y) -{ - /* Find the topmost visible independent at point (x, y). - * For independents, the most recently created can be used as the "top". - * We rely on the X11 convention of unmapping unmanaged when the "owning" - * client loses focus, which ensures that unmanaged are only visible on - * the current tag. */ - Client *c; - wl_list_for_each_reverse(c, &independents, link) { - struct wlr_box geom = { - .x = c->surface.xwayland->x, - .y = c->surface.xwayland->y, - .width = c->surface.xwayland->width, - .height = c->surface.xwayland->height, - }; - if (wlr_box_contains_point(&geom, x, y)) - return c; - } - return NULL; -} #endif int @@ -2565,24 +2588,25 @@ main(int argc, char *argv[]) char *startup_cmd = NULL; int c; - while ((c = getopt(argc, argv, "s:h")) != -1) { + while ((c = getopt(argc, argv, "s:hv")) != -1) { if (c == 's') startup_cmd = optarg; + else if (c == 'v') + die("dwl " VERSION); else goto usage; } if (optind < argc) goto usage; - // Wayland requires XDG_RUNTIME_DIR for creating its communications - // socket + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ if (!getenv("XDG_RUNTIME_DIR")) - BARF("XDG_RUNTIME_DIR must be set"); + die("XDG_RUNTIME_DIR must be set"); setup(); run(startup_cmd); cleanup(); return EXIT_SUCCESS; usage: - BARF("Usage: %s [-s startup command]", argv[0]); + die("Usage: %s [-v] [-s startup command]", argv[0]); } diff --git a/protocols/idle.xml b/protocols/idle.xml deleted file mode 100644 index 92d9989..0000000 --- a/protocols/idle.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - . - ]]> - - - This interface allows to monitor user idle time on a given seat. The interface - allows to register timers which trigger after no user activity was registered - on the seat for a given interval. It notifies when user activity resumes. - - This is useful for applications wanting to perform actions when the user is not - interacting with the system, e.g. chat applications setting the user as away, power - management features to dim screen, etc.. - - - - - - - - - - - - - - - - - - - - - - diff --git a/util.c b/util.c new file mode 100644 index 0000000..cca7c19 --- /dev/null +++ b/util.c @@ -0,0 +1,35 @@ +/* See LICENSE.dwm file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..4c94117 --- /dev/null +++ b/util.h @@ -0,0 +1,4 @@ +/* See LICENSE.dwm file for copyright and license details. */ + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size);