diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 2a18c75..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Bug report -about: Something in dwl isn't working correctly -title: '' -labels: 'Type: bug' -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/enhancement-idea.md b/.github/ISSUE_TEMPLATE/enhancement-idea.md deleted file mode 100644 index 92c6c8c..0000000 --- a/.github/ISSUE_TEMPLATE/enhancement-idea.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Enhancement idea -about: Suggest a feature or improvement -title: '' -labels: 'Type: enhancement' -assignees: '' - ---- - - diff --git a/Makefile b/Makefile index 41d2def..cee8963 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include config.mk -CFLAGS += -I. -DWLR_USE_UNSTABLE -std=c99 -Werror=declaration-after-statement +CFLAGS += -I. -DWLR_USE_UNSTABLE -std=c99 WAYLAND_PROTOCOLS=$(shell pkg-config --variable=pkgdatadir wayland-protocols) WAYLAND_SCANNER=$(shell pkg-config --variable=wayland_scanner wayland-scanner) @@ -33,12 +33,22 @@ wlr-layer-shell-unstable-v1-protocol.c: 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 cp config.def.h $@ -dwl.o: config.h xdg-shell-protocol.h wlr-layer-shell-unstable-v1-protocol.h +dwl.o: config.h xdg-shell-protocol.h wlr-layer-shell-unstable-v1-protocol.h idle-protocol.h -dwl: xdg-shell-protocol.o wlr-layer-shell-unstable-v1-protocol.o +dwl: xdg-shell-protocol.o wlr-layer-shell-unstable-v1-protocol.o idle-protocol.o clean: rm -f dwl *.o *-protocol.h *-protocol.c diff --git a/README.md b/README.md index 172bec8..9596ac1 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,77 @@ # dwl - dwm for Wayland -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](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: - 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 - 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. Since wlroots provides a number of features that are more complicated to accomplish with Xlib and select extensions, dwl can be in some ways more featureful than dwm *while remaining just as simple.* Intended default features are: - -- Any features provided by dwm/Xlib: simple window borders, tags, keybindings, client rules, mouse move/resize (see below for why the built-in status bar is a possible exception) +- Any features provided by dwm/Xlib: simple window borders, tags, keybindings, client rules, mouse move/resize. The built-in status bar is an exception to avoid taking a dependency on FreeType or Pango and increasing the SLOC - Configurable multi-monitor layout support, including position and rotation - Configurable HiDPI/multi-DPI support -- Wayland protocols needed for daily life in the tiling world: at a minimum, xdg-shell and layer-shell (for bars/menus). Protocols trivially provided by wlroots may also be added. +- Various Wayland protocols - XWayland support as provided by wlroots - Zero flickering - Wayland users naturally expect that "every frame is perfect" -- Basic yes/no damage tracking to avoid needless redraws (if it can be done simply and has an impact on power consumption) +Features yet to be implemented are: -Other features under consideration are: - -- Additional Wayland compositor protocols which are trivially provided by wlroots or can be conditionally included via `config.h` settings (e.g. screen capture) -- External bar support instead of a built-in status bar, to avoid taking a dependency on FreeType or Pango -- Buffering of input when spawning a client so you don't have to wait for the window (use `wl_client_get_credentials` to get the PID) - would this require passing through something like dmenu? Extension protocol? -- More in-depth damage region tracking - +- Write a dwl-status protocol that bars can implement to show tags. You can already use Waybar or yambar, but without tag information +- Implement the input-inhibitor protocol to support screen lockers +- Implement the idle-inhibit protocol which the lets application such as mpv disable idle monitoring, and distribute it as a patch +- Layer shell popups (used by Waybar) +- Basic yes/no damage tracking to avoid needless redraws +- More in-depth damage region tracking (this should be worth it according to 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) Feature *non-goals* 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 - ## Building dwl -dwl has only two dependencies: wlroots (git version currently required) and wayland-protocols. Simply install these and run `make`. +dwl has only two dependencies: wlroots 0.12 and wayland-protocols. Simply install these and run `sudo make install`. +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. ## 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 as-is, with no arguments. In an existing Wayland, 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. -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`. +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`. 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.: export XDG_RUNTIME_DIR=/tmp/xdg-runtime-$(id -u) mkdir -p $XDG_RUNTIME_DIR +## Replacements for X applications -## Known limitations and issues +You can find a [list of Wayland applications on the sway wiki](https://github.com/swaywm/sway/wiki/i3-Migration-Guide). dwl is a work in progress, and it has not yet reached its feature goals in a number of ways: - A window's texture is scaled for its "home" monitor only (noticeable when window sits across a monitor boundary) -- XWayland support is new and could use testing - Urgent/attention/focus-request ([not yet supported](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/9) by xdg-shell protocol) -- Statusbar support (built-in or external) - Damage tracking +## IRC channel + +dwl's IRC channel is #dwl on irc.freenode.net. ## 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. Speaking of which, many thanks to suckless.org and the dwm developers and community for the inspiration. +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: + +- Alexander Courtis for the XWayland implementation +- Guido Cella for the layer-shell protocol implementation and for helping to keep the project running diff --git a/config.def.h b/config.def.h index 76e3c36..64dfea8 100644 --- a/config.def.h +++ b/config.def.h @@ -1,5 +1,5 @@ /* appearance */ -static const int sloppyfocus = 1; /* focus follows mouse */ +static const bool sloppyfocus = true; /* 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}; @@ -24,7 +24,9 @@ static const Layout layouts[] = { { "[M]", monocle }, }; -/* monitors */ +/* monitors + * The order in which monitors are defined determines their position. + * Non-configured monitors are always added to the left. */ static const MonitorRule monrules[] = { /* name mfact nmaster scale layout rotate/reflect */ /* example of a HiDPI laptop monitor: @@ -42,13 +44,13 @@ static const struct xkb_rule_names xkb_rules = { */ }; -/* Trackpad */ -int tap_to_click = 1; -int natural_scrolling = 1; - static const int repeat_rate = 25; static const int repeat_delay = 600; +/* Trackpad */ +static const bool tap_to_click = 1; +static const bool natural_scrolling = 1; + #define MODKEY WLR_MODIFIER_ALT #define TAGKEYS(KEY,SKEY,TAG) \ { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ diff --git a/dwl.c b/dwl.c index 1ffae9c..336c939 100644 --- a/dwl.c +++ b/dwl.c @@ -16,21 +16,25 @@ #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 @@ -95,6 +99,7 @@ typedef struct { } surface; #ifdef XWAYLAND struct wl_listener activate; + struct wl_listener configure; #endif struct wl_listener commit; struct wl_listener map; @@ -171,6 +176,7 @@ struct Monitor { double mfact; int nmaster; Client *fullscreenclient; + int position; }; typedef struct { @@ -206,7 +212,7 @@ static void applyexclusive(struct wlr_box *usable_area, uint32_t anchor, static void applyrules(Client *c); static void arrange(Monitor *m); static void arrangelayer(Monitor *m, struct wl_list *list, - struct wlr_box *usable_area, bool exclusive); + struct wlr_box *usable_area, int exclusive); static void arrangelayers(Monitor *m); static void axisnotify(struct wl_listener *listener, void *data); static void buttonpress(struct wl_listener *listener, void *data); @@ -214,6 +220,7 @@ static void chvt(const Arg *arg); 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); @@ -227,7 +234,7 @@ 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(int dir); -static void focusclient(Client *old, Client *c, int lift); +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); @@ -240,13 +247,16 @@ static void keypress(struct wl_listener *listener, void *data); 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 maprequest(struct wl_listener *listener, void *data); +static void mapnotify(struct wl_listener *listener, void *data); static void maximizeclient(Client *c); static void monocle(Monitor *m); static void motionabsolute(struct wl_listener *listener, void *data); static void motionnotify(uint32_t time); static void motionrelative(struct wl_listener *listener, void *data); static void moveresize(const Arg *arg); +static void outputmgrapply(struct wl_listener *listener, void *data); +static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); +static void outputmgrtest(struct wl_listener *listener, void *data); static void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time); static void quit(const Arg *arg); @@ -268,7 +278,6 @@ static void setmfact(const Arg *arg); static void setmon(Client *c, Monitor *m, unsigned int newtags); static void setup(void); static void sigchld(int unused); -static bool shouldfocusclients(); static void spawn(const Arg *arg); static void tag(const Arg *arg); static void tagmon(const Arg *arg); @@ -282,6 +291,7 @@ static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); static void unmapnotify(struct wl_listener *listener, void *data); static void updatemons(); 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); @@ -300,8 +310,11 @@ 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_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; @@ -328,16 +341,20 @@ 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 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_cursor = {.notify = setcursor}; static struct wl_listener request_set_psel = {.notify = setpsel}; static struct wl_listener request_set_sel = {.notify = setsel}; #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); @@ -379,53 +396,61 @@ 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) { + if (exclusive <= 0) + return; + struct { uint32_t singular_anchor; uint32_t anchor_triplet; int *positive_axis; int *negative_axis; int margin; - } edges[4]; - - if (exclusive <= 0) - return; - - // Top - edges[0].singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - edges[0].anchor_triplet = - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - edges[0].positive_axis = &usable_area->y; - edges[0].negative_axis = &usable_area->height; - edges[0].margin = margin_top; - // Bottom - edges[1].singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - edges[1].anchor_triplet = - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - edges[1].positive_axis = NULL; - edges[1].negative_axis = &usable_area->height; - edges[1].margin = margin_bottom; - // Left - edges[2].singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; - edges[2].anchor_triplet = - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - edges[2].positive_axis = &usable_area->x; - edges[2].negative_axis = &usable_area->width; - edges[2].margin = margin_left; - // Right - edges[3].singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - edges[3].anchor_triplet = - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - edges[3].positive_axis = NULL; - edges[3].negative_axis = &usable_area->width; - edges[3].margin = margin_right; + } 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) @@ -442,13 +467,8 @@ applyexclusive(struct wlr_box *usable_area, void applyrules(Client *c) { - const char *appid, *title; - unsigned int i, newtags = 0; - const Rule *r; - Monitor *mon = selmon, *m; - /* rule matching */ - c->isfloating = 0; + const char *appid, *title; #ifdef XWAYLAND if (c->type != XDGShell) { updatewindowtype(c); @@ -465,6 +485,9 @@ applyrules(Client *c) if (!title) title = broken; + unsigned int i, newtags = 0; + const Rule *r; + Monitor *mon = selmon, *m; for (r = rules; r < END(rules); r++) { if ((!r->title || strstr(title, r->title)) && (!r->id || strstr(appid, r->id))) { @@ -486,11 +509,11 @@ arrange(Monitor *m) m->lt[m->sellt]->arrange(m); else if (m->fullscreenclient) maximizeclient(m->fullscreenclient); - /* XXX recheck pointer focus here... or in resize()? */ + /* TODO recheck pointer focus here... or in resize()? */ } void -arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, bool exclusive) +arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) { LayerSurface *layersurface; struct wlr_box full_area = m->m; @@ -498,22 +521,20 @@ arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, bool 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; + struct wlr_box bounds = state->exclusive_zone == -1 ? + full_area : *usable_area; // Horizontal axis + const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; if ((state->anchor & both_horiz) && box.width == 0) { box.x = bounds.x; box.width = bounds.width; @@ -525,6 +546,8 @@ arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, bool box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); } // Vertical axis + const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; if ((state->anchor & both_vert) && box.height == 0) { box.y = bounds.y; box.height = bounds.height; @@ -569,23 +592,16 @@ void arrangelayers(Monitor *m) { struct wlr_box usable_area = m->m; - uint32_t layers_above_shell[] = { - ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - ZWLR_LAYER_SHELL_V1_LAYER_TOP, - }; - size_t nlayers = LENGTH(layers_above_shell); - LayerSurface *layersurface; - struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); // Arrange exclusive surfaces from top->bottom arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - &usable_area, true); + &usable_area, 1); arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - &usable_area, true); + &usable_area, 1); arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - &usable_area, true); + &usable_area, 1); arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - &usable_area, true); + &usable_area, 1); if (memcmp(&usable_area, &m->w, sizeof(struct wlr_box))) { m->w = usable_area; @@ -594,20 +610,29 @@ arrangelayers(Monitor *m) // Arrange non-exlusive surfaces from top->bottom arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - &usable_area, false); + &usable_area, 0); arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - &usable_area, false); + &usable_area, 0); arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - &usable_area, false); + &usable_area, 0); arrangelayer(m, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - &usable_area, false); + &usable_area, 0); // Find topmost keyboard interactive layer, if such a layer exists + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + size_t nlayers = LENGTH(layers_above_shell); + LayerSurface *layersurface; + struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); for (size_t i = 0; i < nlayers; ++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. + focusclient(NULL, 0); wlr_seat_keyboard_notify_enter(seat, layersurface->layer_surface->surface, kb->keycodes, kb->num_keycodes, &kb->modifiers); return; @@ -622,6 +647,7 @@ 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); /* Notify the client with pointer focus of the axis event. */ wlr_seat_pointer_notify_axis(seat, event->time_msec, event->orientation, event->delta, @@ -632,19 +658,19 @@ void buttonpress(struct wl_listener *listener, void *data) { struct wlr_event_pointer_button *event = data; - struct wlr_keyboard *keyboard; - uint32_t mods; - Client *c; - const Button *b; + + wlr_idle_notify_activity(idle, seat); switch (event->state) { case WLR_BUTTON_PRESSED:; /* Change focus if the button was _pressed_ over a client */ + Client *c; if ((c = xytoclient(cursor->x, cursor->y))) - focusclient(selclient(), c, 1); + focusclient(c, 1); - keyboard = wlr_seat_get_keyboard(seat); - mods = wlr_keyboard_get_modifiers(keyboard); + struct wlr_keyboard* keyboard = wlr_seat_get_keyboard(seat); + uint32_t mods = wlr_keyboard_get_modifiers(keyboard); + const Button *b; for (b = buttons; b < END(buttons); b++) { if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && b->func) { @@ -655,7 +681,7 @@ buttonpress(struct wl_listener *listener, void *data) break; case WLR_BUTTON_RELEASED: /* If you released any buttons, we exit interactive move/resize mode. */ - /* XXX should reset to the pointer focus's current setcursor */ + /* 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); @@ -699,6 +725,9 @@ cleanupkeyboard(struct wl_listener *listener, void *data) struct wlr_input_device *device = data; Keyboard *kb = device->data; + wl_list_remove(&kb->link); + wl_list_remove(&kb->modifiers.link); + wl_list_remove(&kb->key.link); wl_list_remove(&kb->destroy.link); free(kb); } @@ -710,9 +739,33 @@ cleanupmon(struct wl_listener *listener, void *data) Monitor *m = wlr_output->data; wl_list_remove(&m->destroy.link); - free(m); - + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wlr_output_layout_remove(output_layout, m->wlr_output); updatemons(); + + int nmons = wl_list_length(&mons), i = 0; + 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); +} + +void +closemon(Monitor *m) +{ + // move closed monitor's clients to the focused one + Client *c; + + 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); + if (c->mon == m) + setmon(c, selmon, c->tags); + } } void @@ -721,12 +774,11 @@ 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) return; - m = wlr_output->data; + Monitor *m = wlr_output->data; arrangelayers(m); if (layersurface->layer != wlr_layer_surface->current.layer) { @@ -750,16 +802,12 @@ commitnotify(struct wl_listener *listener, void *data) void createkeyboard(struct wlr_input_device *device) { - struct xkb_context *context; - struct xkb_keymap *keymap; - Keyboard *kb; - - kb = device->data = calloc(1, sizeof(*kb)); + Keyboard *kb = device->data = calloc(1, sizeof(*kb)); kb->device = device; /* 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, + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap = xkb_map_new_from_names(context, &xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS); wlr_keyboard_set_keymap(device->keyboard, keymap); @@ -787,9 +835,6 @@ createmon(struct wl_listener *listener, void *data) /* This event is raised by the backend when a new output (aka a display or * monitor) becomes available. */ struct wlr_output *wlr_output = data; - Monitor *m; - const MonitorRule *r; - size_t nlayers = LENGTH(m->layers); /* The mode is a tuple of (width, height, refresh rate), and each * monitor supports only a specific set of modes. We just pick the @@ -798,49 +843,77 @@ createmon(struct wl_listener *listener, void *data) wlr_output_set_mode(wlr_output, wlr_output_preferred_mode(wlr_output)); /* Allocates and configures monitor state using configured rules */ - m = wlr_output->data = calloc(1, sizeof(*m)); + Monitor *m = wlr_output->data = calloc(1, sizeof(*m)); m->wlr_output = wlr_output; m->tagset[0] = m->tagset[1] = 1; - for (r = monrules; r < END(monrules); r++) { + m->position = -1; + for (const MonitorRule *r = monrules; r < END(monrules); r++) { if (!r->name || strstr(wlr_output->name, r->name)) { m->mfact = r->mfact; m->nmaster = r->nmaster; wlr_output_set_scale(wlr_output, r->scale); wlr_xcursor_manager_load(cursor_mgr, r->scale); - m->lt[0] = m->lt[1] = r->lt; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; wlr_output_set_transform(wlr_output, r->rr); + m->position = r - monrules; break; } } + wlr_output_enable_adaptive_sync(wlr_output, 1); /* Set up event listeners */ m->frame.notify = rendermon; wl_signal_add(&wlr_output->events.frame, &m->frame); m->destroy.notify = cleanupmon; wl_signal_add(&wlr_output->events.destroy, &m->destroy); - wl_list_insert(&mons, &m->link); + Monitor *moni, *insertmon = NULL; + int x = 0; + wl_list_for_each(moni, &mons, link) + if (m->position > moni->position) + insertmon = moni; + if (insertmon) { + x = insertmon->w.x + insertmon->w.width; + wl_list_insert(&insertmon->link, &m->link); + } else { + wl_list_insert(&mons, &m->link); + } wlr_output_enable(wlr_output, 1); if (!wlr_output_commit(wlr_output)) return; - /* Adds this to the output layout. The add_auto function arranges outputs - * from left-to-right in the order they appear. A more sophisticated - * compositor would let the user configure the arrangement of outputs in the - * layout. + /* 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_auto(output_layout, wlr_output); + wlr_output_layout_add(output_layout, wlr_output, x, 0); + wl_list_for_each_reverse(moni, &mons, link) { + /* All monitors to the right of the new one must be moved */ + if (moni == m) + break; + wlr_output_layout_move(output_layout, moni->wlr_output, moni->w.x + m->wlr_output->width, 0); + } sgeom = *wlr_output_layout_get_box(output_layout, NULL); + size_t nlayers = LENGTH(m->layers); for (size_t i = 0; i < nlayers; ++i) wl_list_init(&m->layers[i]); /* When adding monitors, the geometries of all monitors must be updated */ updatemons(); + 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; + } } void @@ -853,6 +926,9 @@ createnotify(struct wl_listener *listener, void *data) if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) return; + wl_list_for_each(c, &clients, link) + if (c->isfullscreen && VISIBLEON(c, c->mon)) + setfullscreen(c, 0); /* Allocate a Client for this surface */ c = xdg_surface->data = calloc(1, sizeof(*c)); @@ -866,7 +942,7 @@ createnotify(struct wl_listener *listener, void *data) /* Listen to the various events it can emit */ c->commit.notify = commitnotify; wl_signal_add(&xdg_surface->surface->events.commit, &c->commit); - c->map.notify = maprequest; + c->map.notify = mapnotify; wl_signal_add(&xdg_surface->events.map, &c->map); c->unmap.notify = unmapnotify; wl_signal_add(&xdg_surface->events.unmap, &c->unmap); @@ -882,15 +958,12 @@ void createlayersurface(struct wl_listener *listener, void *data) { 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)); + LayerSurface *layersurface = calloc(1, sizeof(LayerSurface)); layersurface->surface_commit.notify = commitlayersurfacenotify; wl_signal_add(&wlr_layer_surface->surface->events.commit, @@ -905,14 +978,14 @@ createlayersurface(struct wl_listener *listener, void *data) layersurface->layer_surface = wlr_layer_surface; wlr_layer_surface->data = layersurface; - m = wlr_layer_surface->output->data; + Monitor *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; + struct wlr_layer_surface_v1_state old_state = wlr_layer_surface->current; wlr_layer_surface->current = wlr_layer_surface->client_pending; arrangelayers(m); wlr_layer_surface->current = old_state; @@ -968,7 +1041,6 @@ void destroylayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *layersurface = wl_container_of(listener, layersurface, destroy); - Monitor *m; if (layersurface->layer_surface->mapped) unmaplayersurface(layersurface); @@ -978,7 +1050,7 @@ destroylayersurfacenotify(struct wl_listener *listener, void *data) wl_list_remove(&layersurface->unmap.link); wl_list_remove(&layersurface->surface_commit.link); if (layersurface->layer_surface->output) { - m = layersurface->layer_surface->output->data; + Monitor *m = layersurface->layer_surface->output->data; if (m) arrangelayers(m); layersurface->layer_surface->output = NULL; @@ -1082,31 +1154,53 @@ dirtomon(int dir) } void -focusclient(Client *old, Client *c, int lift) +focusclient(Client *c, int lift) { - struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); - /* Raise client in stacking order if requested */ if (c && lift) { wl_list_remove(&c->slink); wl_list_insert(&stack, &c->slink); } - /* Nothing else to do? */ - if (c == old) + struct wlr_surface *old = seat->keyboard_state.focused_surface; + + if (c && WLR_SURFACE(c) == old) return; - /* Deactivate old client if focus is changing */ - if (c != old && old) { -#ifdef XWAYLAND - if (old->type != XDGShell) - wlr_xwayland_surface_activate(old->surface.xwayland, 0); - else -#endif - wlr_xdg_toplevel_set_activated(old->surface.xdg, 0); + /* Put the new client atop the focus stack and select its monitor */ + if (c) { + wl_list_remove(&c->flink); + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || WLR_SURFACE(c) != old)) { + if (wlr_surface_is_xdg_surface(old)) + wlr_xdg_toplevel_set_activated( + wlr_xdg_surface_from_wlr_surface(old), 0); +#ifdef XWAYLAND + else if (wlr_surface_is_xwayland_surface(old)) + wlr_xwayland_surface_activate( + wlr_xwayland_surface_from_wlr_surface(old), 0); +#endif + /* 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. */ + else 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 + )) + return; + } } - /* Update wlroots' keyboard focus */ if (!c) { /* With no client, all we have left is to clear focus */ wlr_seat_keyboard_notify_clear_focus(seat); @@ -1114,14 +1208,9 @@ focusclient(Client *old, Client *c, int lift) } /* Have a client, so focus its top-level wlr_surface */ - if (shouldfocusclients(c->mon)) - wlr_seat_keyboard_notify_enter(seat, WLR_SURFACE(c), - kb->keycodes, kb->num_keycodes, &kb->modifiers); - - /* Put the new client atop the focus stack and select its monitor */ - wl_list_remove(&c->flink); - wl_list_insert(&fstack, &c->flink); - selmon = c->mon; + struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); + wlr_seat_keyboard_notify_enter(seat, WLR_SURFACE(c), + kb->keycodes, kb->num_keycodes, &kb->modifiers); /* Activate the new client */ #ifdef XWAYLAND @@ -1135,10 +1224,10 @@ focusclient(Client *old, Client *c, int lift) void focusmon(const Arg *arg) { - Client *sel = selclient(); - - selmon = dirtomon(arg->i); - focusclient(sel, focustop(selmon), 1); + do + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled); + focusclient(focustop(selmon), 1); } void @@ -1164,7 +1253,7 @@ focusstack(const Arg *arg) } } /* If only one client is visible on selmon, then c == sel */ - focusclient(sel, c, 1); + focusclient(c, 1); } Client * @@ -1198,7 +1287,6 @@ inputdevice(struct wl_listener *listener, void *data) /* This event is raised by the backend when a new input device becomes * available. */ struct wlr_input_device *device = data; - uint32_t caps; switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: createkeyboard(device); @@ -1207,14 +1295,14 @@ inputdevice(struct wl_listener *listener, void *data) createpointer(device); break; default: - /* XXX handle other input device types */ + /* TODO handle other input device types */ break; } /* We need to let the wlr_seat know what our capabilities are, which is * communiciated to the client. In dwl we always have a cursor, even if * there are no pointer devices, so we always include that capability. */ - /* XXX do we actually require a cursor? */ - caps = WL_SEAT_CAPABILITY_POINTER; + /* TODO do we actually require a cursor? */ + uint32_t caps = WL_SEAT_CAPABILITY_POINTER; if (!wl_list_empty(&keyboards)) caps |= WL_SEAT_CAPABILITY_KEYBOARD; wlr_seat_set_capabilities(seat, caps); @@ -1243,10 +1331,10 @@ keybinding(uint32_t mods, xkb_keysym_t sym) void 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; - int i; /* Translate libinput keycode -> xkbcommon */ uint32_t keycode = event->keycode + 8; @@ -1257,6 +1345,9 @@ keypress(struct wl_listener *listener, void *data) int handled = 0; uint32_t mods = wlr_keyboard_get_modifiers(kb->device->keyboard); + + wlr_idle_notify_activity(idle, seat); + /* On _press_, attempt to process a compositor keybinding. */ if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) for (i = 0; i < nsyms; i++) @@ -1312,7 +1403,7 @@ maplayersurfacenotify(struct wl_listener *listener, void *data) } void -maprequest(struct wl_listener *listener, void *data) +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), *oldfocus = selclient(); @@ -1350,7 +1441,7 @@ maprequest(struct wl_listener *listener, void *data) if (c->mon->fullscreenclient && c->mon->fullscreenclient == oldfocus && !c->isfloating && c->mon->lt[c->mon->sellt]->arrange) { maximizeclient(c->mon->fullscreenclient); - focusclient(c, c->mon->fullscreenclient, 1); + focusclient(c->mon->fullscreenclient, 1); /* give the focus back the fullscreen client on that monitor if exists, * is focused and the new client isn't floating */ } @@ -1388,18 +1479,14 @@ motionabsolute(struct wl_listener *listener, void *data) void motionnotify(uint32_t time) { - double sx = 0, sy = 0; - struct wlr_surface *surface = NULL; - Client *c = NULL; - struct timespec now; - if (!time) { - clock_gettime(CLOCK_MONOTONIC, &now); - time = now.tv_sec * 1000 + now.tv_nsec / 1000000; - } + // time is 0 in internal calls meant to restore pointer focus. + if (time) { + wlr_idle_notify_activity(idle, seat); - /* Update selmon (even while dragging a window) */ - if (sloppyfocus) - selmon = xytomon(cursor->x, cursor->y); + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + } /* If we are currently grabbing the mouse, handle and return */ if (cursor_mode == CurMove) { @@ -1414,6 +1501,9 @@ motionnotify(uint32_t time) return; } + struct wlr_surface *surface = NULL; + double sx = 0, sy = 0; + Client *c = NULL; if ((surface = xytolayersurface(&selmon->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], cursor->x, cursor->y, &sx, &sy))) ; @@ -1452,7 +1542,7 @@ motionnotify(uint32_t time) /* 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) + if (!surface && time) wlr_xcursor_manager_set_cursor_image(cursor_mgr, "left_ptr", cursor); @@ -1502,6 +1592,72 @@ moveresize(const Arg *arg) } } +void +outputmgrapply(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 0); +} + +void +outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) +{ + struct wlr_output_configuration_head_v1 *config_head; + int ok = 1; + + wl_list_for_each(config_head, &config->heads, link) { + struct wlr_output *wlr_output = config_head->state.output; + + 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); + + 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); + } else if (wl_list_length(&mons) > 1) { + Monitor *m; + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->name == wlr_output->name) { + // focus the left monitor (relative to the current focus) + m->wlr_output->enabled = !m->wlr_output->enabled; + Arg ar = {.i = -1}; + focusmon(&ar); + closemon(m); + m->wlr_output->enabled = !m->wlr_output->enabled; + } + } + } + + 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); + if (!test) + updatemons(); + } else + wlr_output_configuration_v1_send_failed(config); + wlr_output_configuration_v1_destroy(config); +} + +void +outputmgrtest(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 1); +} + void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) @@ -1516,6 +1672,13 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, return; } + int internal_call = !time; + if (!time) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + 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); @@ -1534,8 +1697,8 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, return; #endif - if (sloppyfocus) - focusclient(selclient(), c, 0); + if (sloppyfocus && !internal_call) + focusclient(c, 0); } void @@ -1550,10 +1713,6 @@ render(struct wlr_surface *surface, int sx, int sy, void *data) /* 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 @@ -1568,14 +1727,17 @@ render(struct wlr_surface *surface, int sx, int sy, void *data) * 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). */ + double ox = 0, oy = 0; 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; + struct wlr_box obox = { + .x = ox + rdata->x + sx, + .y = oy + rdata->y + sy, + .width = surface->current.width, + .height = surface->current.height, + }; scalebox(&obox, output->scale); /* @@ -1589,7 +1751,9 @@ render(struct wlr_surface *surface, int sx, int sy, void *data) * Naturally you can do this any way you like, for example to make a 3D * compositor. */ - transform = wlr_output_transform_invert(surface->current.transform); + float matrix[9]; + enum wl_output_transform transform = wlr_output_transform_invert( + surface->current.transform); wlr_matrix_project_box(matrix, &obox, transform, 0, output->transform_matrix); @@ -1662,13 +1826,14 @@ renderclients(Monitor *m, struct timespec *now) void renderlayer(struct wl_list *layer_surfaces, struct timespec *now) { - struct render_data rdata; LayerSurface *layersurface; wl_list_for_each(layersurface, layer_surfaces, link) { - rdata.output = layersurface->layer_surface->output; - rdata.when = now; - rdata.x = layersurface->geo.x; - rdata.y = layersurface->geo.y; + 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); @@ -1678,9 +1843,6 @@ renderlayer(struct wl_list *layer_surfaces, struct timespec *now) 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); @@ -1689,6 +1851,8 @@ rendermon(struct wl_listener *listener, void *data) clock_gettime(CLOCK_MONOTONIC, &now); /* Do not render if any XDG clients have an outstanding resize. */ + Client *c; + int render = 1; wl_list_for_each(c, &stack, slink) { if (c->resize) { wlr_surface_send_frame_done(WLR_SURFACE(c), &now); @@ -1738,11 +1902,11 @@ resize(Client *c, int x, int y, int w, int h, int interact) * 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; + struct wlr_box *bbox = interact ? &sgeom : &c->mon->w; applybounds(c, bbox); /* wlroots makes this a no-op if size hasn't changed */ #ifdef XWAYLAND @@ -1759,8 +1923,6 @@ 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) @@ -1775,7 +1937,7 @@ run(char *startup_cmd) * cursor position, and set default cursor image */ selmon = xytomon(cursor->x, cursor->y); - /* XXX hack to get cursor to display in its initial location (100, 100) + /* TODO hack to get cursor to display in its initial location (100, 100) * instead of (0, 0) and then jumping. still may not be fully * initialized, as the image/coordinates are not transformed for the * monitor when displayed here */ @@ -1785,8 +1947,10 @@ run(char *startup_cmd) /* Set the WAYLAND_DISPLAY environment variable to our socket and run the * startup command if requested. */ setenv("WAYLAND_DISPLAY", socket, 1); + + pid_t startup_pid = -1; if (startup_cmd) { - startup_pid = fork(); + pid_t startup_pid = fork(); if (startup_pid < 0) EBARF("startup: fork"); if (startup_pid == 0) { @@ -1794,6 +1958,7 @@ run(char *startup_cmd) EBARF("startup: execl"); } } + /* 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 @@ -1830,7 +1995,7 @@ 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 */ - /* XXX still need to save the provided surface to restore later */ + /* TODO still need to save the provided surface to restore later */ if (cursor_mode != CurNormal) return; /* This can be sent by any client, so we check to make sure this one is @@ -1857,7 +2022,7 @@ setlayout(const Arg *arg) selmon->sellt ^= 1; if (arg && arg->v) selmon->lt[selmon->sellt] = (Layout *)arg->v; - /* XXX change layout symbol? */ + /* TODO change layout symbol? */ arrange(selmon); } @@ -1865,11 +2030,9 @@ setlayout(const Arg *arg) void setmfact(const Arg *arg) { - float f; - if (!arg || !selmon->lt[selmon->sellt]->arrange) return; - f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + float f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; if (f < 0.1 || f > 0.9) return; selmon->mfact = f; @@ -1880,13 +2043,12 @@ void setmon(Client *c, Monitor *m, unsigned int newtags) { Monitor *oldmon = c->mon; - Client *oldsel = selclient(); if (oldmon == m) return; c->mon = m; - /* XXX leave/enter is not optimal but works */ + /* TODO leave/enter is not optimal but works */ if (oldmon) { wlr_surface_send_leave(WLR_SURFACE(c), oldmon->wlr_output); arrange(oldmon); @@ -1898,7 +2060,7 @@ setmon(Client *c, Monitor *m, unsigned int newtags) c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ arrange(m); } - focusclient(oldsel, focustop(selmon), 1); + focusclient(focustop(selmon), 1); } void @@ -1959,6 +2121,7 @@ setup(void) compositor = wlr_compositor_create(dpy, drw); wlr_export_dmabuf_manager_v1_create(dpy); wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); wlr_data_device_manager_create(dpy); wlr_gamma_control_manager_v1_create(dpy); wlr_primary_selection_v1_device_manager_create(dpy); @@ -1985,6 +2148,8 @@ setup(void) wl_list_init(&stack); wl_list_init(&independents); + idle = wlr_idle_create(dpy); + layer_shell = wlr_layer_shell_v1_create(dpy); wl_signal_add(&layer_shell->events.new_surface, &new_layer_shell_surface); @@ -2035,6 +2200,9 @@ setup(void) */ wl_list_init(&keyboards); wl_signal_add(&backend->events.new_input, &new_input); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + 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); @@ -2043,12 +2211,16 @@ setup(void) wl_signal_add(&seat->events.request_set_primary_selection, &request_set_psel); + 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); + #ifdef XWAYLAND /* * Initialise the XWayland X server. * It will be started when the first X client is started. */ - xwayland = wlr_xwayland_create(dpy, compositor, true); + xwayland = wlr_xwayland_create(dpy, compositor, 1); if (xwayland) { wl_signal_add(&xwayland->events.ready, &xwayland_ready); wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); @@ -2067,7 +2239,7 @@ setup(void) xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); } - setenv("DISPLAY", xwayland->display_name, true); + setenv("DISPLAY", xwayland->display_name, 1); } else { fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); } @@ -2083,22 +2255,6 @@ sigchld(int unused) ; } -bool -shouldfocusclients(Monitor *m) -{ - LayerSurface *layersurface; - uint32_t layers_above_shell[] = { - ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - ZWLR_LAYER_SHELL_V1_LAYER_TOP, - }; - for (size_t i = 0; i < LENGTH(layers_above_shell); ++i) - wl_list_for_each(layersurface, &m->layers[layers_above_shell[i]], link) - if (layersurface->layer_surface->current.keyboard_interactive && - layersurface->layer_surface->mapped) - return false; - return true; -} - void spawn(const Arg *arg) { @@ -2115,7 +2271,7 @@ tag(const Arg *arg) Client *sel = selclient(); if (sel && arg->ui & TAGMASK) { sel->tags = arg->ui & TAGMASK; - focusclient(sel, focustop(selmon), 1); + focusclient(focustop(selmon), 1); arrange(selmon); } } @@ -2177,14 +2333,13 @@ togglefloating(const Arg *arg) void toggletag(const Arg *arg) { - unsigned int newtags; Client *sel = selclient(); if (!sel) return; - newtags = sel->tags ^ (arg->ui & TAGMASK); + unsigned int newtags = sel->tags ^ (arg->ui & TAGMASK); if (newtags) { sel->tags = newtags; - focusclient(sel, focustop(selmon), 1); + focusclient(focustop(selmon), 1); arrange(selmon); } } @@ -2192,12 +2347,11 @@ toggletag(const Arg *arg) void toggleview(const Arg *arg) { - Client *sel = selclient(); unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); if (newtagset) { selmon->tagset[selmon->seltags] = newtagset; - focusclient(sel, focustop(selmon), 1); + focusclient(focustop(selmon), 1); arrange(selmon); } } @@ -2205,10 +2359,10 @@ toggleview(const Arg *arg) void unmaplayersurface(LayerSurface *layersurface) { - layersurface->layer_surface->mapped = false; + layersurface->layer_surface->mapped = 0; if (layersurface->layer_surface->surface == seat->keyboard_state.focused_surface) - focusclient(NULL, selclient(), 1); + focusclient(selclient(), 1); motionnotify(0); } @@ -2237,30 +2391,50 @@ unmapnotify(struct wl_listener *listener, void *data) void updatemons() { + struct wlr_output_configuration_v1 *config = + wlr_output_configuration_v1_create(); 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); + /* Get the effective monitor geometry to use for surfaces */ m->m = m->w = *wlr_output_layout_get_box(output_layout, m->wlr_output); /* 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; + config_head->state.mode = m->wlr_output->current_mode; + config_head->state.x = m->m.x; + config_head->state.y = m->m.y; } + + wlr_output_manager_v1_set_configuration(output_mgr, config); } void view(const Arg *arg) { - Client *sel = selclient(); if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) return; selmon->seltags ^= 1; /* toggle sel tagset */ if (arg->ui & TAGMASK) selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; - focusclient(sel, focustop(selmon), 1); + focusclient(focustop(selmon), 1); arrange(selmon); } +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) { @@ -2304,7 +2478,7 @@ xytomon(double x, double y) void zoom(const Arg *arg) { - Client *c, *sel = selclient(), *oldsel = sel; + Client *c, *sel = selclient(); if (!sel || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) return; @@ -2329,7 +2503,7 @@ zoom(const Arg *arg) wl_list_remove(&sel->link); wl_list_insert(&clients, &sel->link); - focusclient(oldsel, sel, 1); + focusclient(sel, 1); arrange(selmon); } @@ -2344,25 +2518,39 @@ activatex11(struct wl_listener *listener, void *data) wlr_xwayland_surface_activate(c->surface.xwayland, 1); } +void +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); +} + void createnotifyx11(struct wl_listener *listener, void *data) { - struct wlr_xwayland_surface *xwayland_surface = 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 */ + struct wlr_xwayland_surface *xwayland_surface = data; c = xwayland_surface->data = calloc(1, sizeof(*c)); c->surface.xwayland = xwayland_surface; c->type = xwayland_surface->override_redirect ? X11Unmanaged : X11Managed; c->bw = borderpx; /* Listen to the various events it can emit */ - c->map.notify = maprequest; + c->map.notify = mapnotify; wl_signal_add(&xwayland_surface->events.map, &c->map); c->unmap.notify = unmapnotify; wl_signal_add(&xwayland_surface->events.unmap, &c->unmap); c->activate.notify = activatex11; wl_signal_add(&xwayland_surface->events.request_activate, &c->activate); + c->configure.notify = configurex11; + wl_signal_add(&xwayland_surface->events.request_configure, &c->configure); c->destroy.notify = destroynotify; wl_signal_add(&xwayland_surface->events.destroy, &c->destroy); @@ -2375,10 +2563,8 @@ Atom getatom(xcb_connection_t *xc, const char *name) { Atom atom = 0; - xcb_intern_atom_cookie_t cookie; xcb_intern_atom_reply_t *reply; - - cookie = xcb_intern_atom(xc, 0, strlen(name), name); + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(xc, 0, strlen(name), name); if ((reply = xcb_intern_atom_reply(xc, cookie, NULL))) atom = reply->atom; free(reply); @@ -2390,23 +2576,25 @@ void renderindependents(struct wlr_output *output, struct timespec *now) { 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; + struct wlr_box geom = { + .x = c->surface.xwayland->x, + .y = c->surface.xwayland->y, + .width = c->surface.xwayland->width, + .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; + struct render_data rdata = { + .output = output, + .when = now, + .x = c->surface.xwayland->x, + .y = c->surface.xwayland->y, + }; wlr_surface_for_each_surface(c->surface.xwayland->surface, render, &rdata); } @@ -2415,8 +2603,7 @@ renderindependents(struct wlr_output *output, struct timespec *now) void updatewindowtype(Client *c) { - size_t i; - for (i = 0; i < c->surface.xwayland->window_type_len; i++) + 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] || @@ -2444,6 +2631,13 @@ xwaylandready(struct wl_listener *listener, void *data) /* assign the one and only seat */ wlr_xwayland_set_seat(xwayland, seat); + /* Set the default XWayland cursor to match the rest of dwl. */ + struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(cursor_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); + xcb_disconnect(xc); } @@ -2456,12 +2650,13 @@ xytoindependent(double x, double y) * client loses focus, which ensures that unmanaged are only visible on * the current tag. */ Client *c; - 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; + 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; } diff --git a/protocols/idle.xml b/protocols/idle.xml new file mode 100644 index 0000000..92d9989 --- /dev/null +++ b/protocols/idle.xml @@ -0,0 +1,49 @@ + + + . + ]]> + + + 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.. + + + + + + + + + + + + + + + + + + + + + +