diff --git a/.gitea/issue_template/bug_report.yml b/.gitea/issue_template/bug_report.yml new file mode 100644 index 0000000..77ce108 --- /dev/null +++ b/.gitea/issue_template/bug_report.yml @@ -0,0 +1,62 @@ +name: Bug Report +about: Something in dwl isn't working correctly +title: +labels: + - 'Kind/Bug' +body: + - type: markdown + attributes: + value: | + - Only report bugs that can be reproduced on the main (or wlroots-next) branch without patches. + - Proprietary graphics drivers, including nvidia, are not supported. Please use the open source equivalents, such as nouveau, if you would like to use dwl. + - Report patch issues to their respective authors. + + - type: input + id: dwl_version + attributes: + label: 'dwl version:' + placeholder: '`dwl -v`' + validations: + required: true + + - type: input + id: wlroots_version + attributes: + label: 'wlroots version:' + validations: + required: true + + - type: input + id: distro + attributes: + label: What distro (and version) are you using? + validations: + required: false + + - type: textarea + attributes: + label: Description + value: | + The steps you took to reproduce the problem. + validations: + required: false + + - type: textarea + id: debug_log + attributes: + label: Debug Log + value: | + Run `dwl -d 2> ~/dwl.log` from a TTY and attach the **full** (do not truncate it) file here, or upload it to a pastebin. + Please try to keep the reproduction as brief as possible and exit dwl. + validations: + required: false + + - type: textarea + id: backtrace + attributes: + label: Stack Trace + value: | + - Only required if dwl crashes. + - If the lines mentioning dwl or wlroots have `??`. Please compile both dwl and wlroots from source (enabling debug symbols) and try to reproduce. + validations: + required: false diff --git a/.gitea/issue_template/enhancement-idea.yml b/.gitea/issue_template/enhancement-idea.yml new file mode 100644 index 0000000..be1bbf2 --- /dev/null +++ b/.gitea/issue_template/enhancement-idea.yml @@ -0,0 +1,9 @@ +name: Enhancement idea +about: Suggest a feature or improvement +title: +labels: + - 'Kind/Feature' +body: + - type: textarea + attributes: + label: Description diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 6b60803..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Bug report -about: Something in dwl isn't working correctly -title: '' -labels: 'A: bug' -assignees: '' - ---- - -## Info -dwl version: -wlroots version: -## Description - diff --git a/.github/ISSUE_TEMPLATE/enhancement-idea.md b/.github/ISSUE_TEMPLATE/enhancement-idea.md deleted file mode 100644 index 0ac096d..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: 'A: enhancement' -assignees: '' - ---- - - diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..1778cb9 --- /dev/null +++ b/.mailmap @@ -0,0 +1,3 @@ +Lennart Jablonka +Leonardo Hernández Hernández +Leonardo Hernández Hernández diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..07c9ee4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,217 @@ +# Changelog + +* [Unreleased](#unreleased) +* [0.7](#0.7) +* [0.6](#0.6) +* [0.5](#0.5) + + +## Unreleased +### Added + +* Support for the linux-drm-syncobj-v1 protocol ([wlroots!4715][wlroots!4715], [#685][685]) +* Allow the use of non-system wlroots library ([#646][646]) + +[wlroots!4715]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4715 +[685]: https://codeberg.org/dwl/dwl/pulls/685 +[646]: https://codeberg.org/dwl/dwl/pulls/646 + + +### Changed +### Deprecated +### Removed +### Fixed + +* Crash when a client is created while all outputs are disabled. + +### Security +### Contributors + + +## 0.7 + +This version is just 0.6 with wlroots 0.18 compatibility. + +### Added + +* Add support for the alpha-modifier-v1 protocol ([wlroots!4616][wlroots!4616]). +* dwl now will survive GPU resets ([#601][601]). + +[wlroots!4616]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4616 +[601]: https://codeberg.org/dwl/dwl/issues/601 + + +### Contributors + +Guido Cella + + +## 0.6 + +### Added + +* Add `rootcolor` to change the default background color ([#544][544]). +* Implement the wlr-virtual-pointer-unstable-v1 protocol ([#574][574]). +* Implement the pointer-constraints and relative-pointer protocols ([#317][317]) +* Implement the wlr-output-power-management protocol ([#599][599]) + +[544]: https://codeberg.org/dwl/dwl/pulls/544 +[574]: https://codeberg.org/dwl/dwl/pulls/574 +[317]: https://codeberg.org/dwl/dwl/issues/317 +[599]: https://codeberg.org/dwl/dwl/issues/559 + + +### Changed + +* Keyboards are now managed through keyboard groups ([#549][549]). +* Only the first matched keybinding is executed. +* Allow toggling the layout before selecting a different one ([#570][570]). +* Fullscreen clients are now rendered above wlr_layer_surfaces in the top layer + ([#609][609]). +* The default menu was changed from `bemenu-run` to `wmenu-run` ([#553][553]). +* The option `sloppyfocus` now replicates the dwm behavior ([#599][599]). +* Allow configure position of monitors with negative values. (-1, -1) is + used to auto-configure them ([#635][635]). +* dwl now kills the entire process group of `startup_cmd` +* The O_NONBLOCK flag is set for stdout. + +[549]: https://codeberg.org/dwl/dwl/pulls/549 +[570]: https://codeberg.org/dwl/dwl/pulls/570 +[609]: https://codeberg.org/dwl/dwl/pulls/609 +[553]: https://codeberg.org/dwl/dwl/issues/553 +[599]: https://codeberg.org/dwl/dwl/pulls/599 +[635]: https://codeberg.org/dwl/dwl/pulls/635 + + +### Removed + +* The SLOC limit is now removed ([#497][497]). + +[497]: https://codeberg.org/dwl/dwl/pulls/497 + + +### Fixed + +* Clients not having the correct border color when mapping. +* Compliance with the xdg-decoration-unstable-v1 ([#546][546]). +* dwl no longer sends negative values in xdg_toplevel.configure events. +* Crashes with disabled monitors ([#472][472]). + +[546]: https://codeberg.org/dwl/dwl/pulls/546 +[472]: https://codeberg.org/dwl/dwl/issues/472 + + +### Contributors + +Ben Jargowsky +Benjamin Chausse +David Donahue +Devin J. Pohly +Dima Krasner +Emil Miler +Forrest Bushstone +Guido Cella +Peter Hofmann +Rutherther +Squibid +choc +fictitiousexistence +korei999 +sewn +thanatos + + +## 0.5 + +### Added + +* Allow configure x and y position of outputs ([#301][301]) +* Implement repeatable keybindings ([#368][368]) +* Print app id in printstatus() output ([#381][381]) +* Display client count in monocle symbol ([#387][387]) +* Export XCURSOR_SIZE to fix apps using an older version of Qt ([#425][425]) +* Support for wp-fractional-scale-v1 (through wlr_scene: [wlroots!3511][wlroots!3511]) +* dwl now sends `wl_surface.preferred_buffer_scale` (through wlr_scene: [wlroots!4269][wlroots!4269]) +* Add support for xdg-shell v6 ([#465][465]) +* Add support for wp-cursor-shape-v1 ([#444][444]) +* Add desktop file ([#484][484]) +* Add macro to easily configure colors ([#466][466]) +* Color of urgent clients are now red ([#494][494]) +* New flag `-d` and option `log_level` to change the wlroots debug level +* Add CHANGELOG.md ([#501][501]) + +[301]: https://github.com/djpohly/dwl/pull/301 +[368]: https://github.com/djpohly/dwl/pull/368 +[381]: https://github.com/djpohly/dwl/pull/381 +[387]: https://github.com/djpohly/dwl/issues/387 +[425]: https://github.com/djpohly/dwl/pull/425 +[wlroots!4269]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4269 +[wlroots!3511]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/3511 +[465]: https://github.com/djpohly/dwl/pull/465 +[444]: https://github.com/djpohly/dwl/pull/444 +[484]: https://github.com/djpohly/dwl/pull/484 +[466]: https://github.com/djpohly/dwl/issues/466 +[494]: https://github.com/djpohly/dwl/pull/494 +[501]: https://github.com/djpohly/dwl/pull/501 + + +### Changed + +* Replace `tags` with `TAGCOUNT` in config.def.h ([#403][403]) +* Pop ups are now destroyed when focusing another client ([#408][408]) +* dwl does not longer respect size hints, instead clip windows if they are + larger than they should be ([#455][455]) +* The version of wlr-layer-shell-unstable-v1 was lowered to 3 (from 4) +* Use the same border color as dwm ([#494][494]) + +[403]: https://github.com/djpohly/dwl/pull/403 +[408]: https://github.com/djpohly/dwl/pull/409 +[455]: https://github.com/djpohly/dwl/pull/455 +[494]: https://github.com/djpohly/dwl/pull/494 + + +### Removed + +* Remove unused `rootcolor` option ([#401][401]) +* Remove support for wlr-input-inhibitor-unstable-v1 ([#430][430]) +* Remove support for KDE idle protocol ([#431][431]) + +[401]: https://github.com/djpohly/dwl/pull/401 +[430]: https://github.com/djpohly/dwl/pull/430 +[431]: https://github.com/djpohly/dwl/pull/431 + + +### Fixed + +* Fix crash when creating a layer surface with all outputs disabled + ([#421][421]) +* Fix other clients being shown as focused if the focused client have pop ups + open ([#408][408]) +* Resize fullscreen clients when updating monitor mode +* dwl no longer crash at exit like sometimes did +* Fullscreen background appearing above clients ([#487][487]) +* Fix a segfault when user provides invalid xkb_rules ([#518][518]) + +[421]: https://github.com/djpohly/dwl/pull/421 +[408]: https://github.com/djpohly/dwl/issues/408 +[487]: https://github.com/djpohly/dwl/issues/487 +[518]: https://github.com/djpohly/dwl/pull/518 + + +### Contributors + +* A Frederick Christensen +* Angelo Antony +* Ben Collerson +* Devin J. Pohly +* Forrest Bushstone +* gan-of-culture +* godalming123 +* Job79 +* link2xt +* Micah Gorrell +* Nikita Ivanov +* Palanix +* pino-desktop +* Weiseguy +* Yves Zoundi diff --git a/Makefile b/Makefile index ccca079..578194f 100644 --- a/Makefile +++ b/Makefile @@ -4,19 +4,24 @@ include config.mk # 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 +DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L \ + -DVERSION=\"$(VERSION)\" $(XWAYLAND) +DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wno-unused-parameter -Wshadow -Wunused-macros -Werror=strict-prototypes \ + -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types \ + -Wfloat-conversion # CFLAGS / LDFLAGS -PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) -DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) -LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) +PKGS = wayland-server xkbcommon libinput $(XLIBS) +DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) +LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) all: dwl dwl: dwl.o util.o - $(CC) dwl.o util.o $(LDLIBS) $(LDFLAGS) $(DWLCFLAGS) -o $@ -dwl.o: dwl.c config.mk config.h client.h xdg-shell-protocol.h wlr-layer-shell-unstable-v1-protocol.h + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h util.o: util.c util.h # wayland-scanner is a tool which generates C headers and rigging for Wayland @@ -25,12 +30,21 @@ util.o: util.c util.h WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner` WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols` +cursor-shape-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + $(WAYLAND_PROTOCOLS)/staging/cursor-shape/cursor-shape-v1.xml $@ +pointer-constraints-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + $(WAYLAND_PROTOCOLS)/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml $@ +wlr-layer-shell-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + protocols/wlr-layer-shell-unstable-v1.xml $@ +wlr-output-power-management-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) server-header \ + protocols/wlr-output-power-management-unstable-v1.xml $@ xdg-shell-protocol.h: $(WAYLAND_SCANNER) server-header \ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ -wlr-layer-shell-unstable-v1-protocol.h: - $(WAYLAND_SCANNER) server-header \ - protocols/wlr-layer-shell-unstable-v1.xml $@ config.h: cp config.def.h $@ @@ -39,22 +53,27 @@ clean: 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\ + cp -R LICENSE* Makefile CHANGELOG.md README.md client.h config.def.h \ + config.mk protocols dwl.1 dwl.c util.c util.h dwl.desktop \ dwl-$(VERSION) tar -caf dwl-$(VERSION).tar.gz dwl-$(VERSION) rm -rf dwl-$(VERSION) install: dwl mkdir -p $(DESTDIR)$(PREFIX)/bin + rm -f $(DESTDIR)$(PREFIX)/bin/dwl 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 + mkdir -p $(DESTDIR)$(DATADIR)/wayland-sessions + cp -f dwl.desktop $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + chmod 644 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 + rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 \ + $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop .SUFFIXES: .c .o .c.o: - $(CC) $(CPPFLAGS) $(DWLCFLAGS) -c $< + $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -c $< diff --git a/README.md b/README.md index 05f149c..390788d 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,82 @@ # dwl - dwm for Wayland -Join us on our [Discord server] or at [#dwl] on irc.libera.chat. +Join us on our IRC channel: [#dwl on Libera Chat] +Or on the community-maintained [Discord server]. 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: +intended to fill the same space in the Wayland world that [dwm] does in X11, +primarily in terms of functionality, and secondarily in terms of +philosophy. 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 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: +## Getting Started: -- 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 (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 +### Latest semi-stable [release] +This is probably where you want to start. This builds against the [wlroots] +versions currently shipping in major distributions. If your +distribution's `wlroots` version is older, use an earlier dwl [release]. +The `wlroots` version against which a given `dwl` release builds is specified +with each release on the [release] page -Features under consideration (possibly as patches) are: +### Development branch [main] +Active development progresses on the `main` branch. The `main` branch is built +against a late (and often changing) git commit of wlroots. While the adventurous +are welcome to use `main`, it is a rocky road. Using `main` requires that the +user be willing to chase git commits of wlroots. Testing development pull +requests may involve merging unmerged pull requests in [wlroots]' git repository +and/or git commits of wayland. + +### Building dwl +dwl has the following dependencies: +- libinput +- wayland +- wlroots (compiled with the libinput backend) +- xkbcommon +- wayland-protocols (compile-time only) +- pkg-config (compile-time only) -- Protocols made trivial by wlroots -- Implement the text-input and input-method protocols to support IME once ibus - implements input-method v2 (see https://github.com/ibus/ibus/pull/2256 and - https://github.com/djpohly/dwl/pull/235) +dwl has the following additional dependencies if XWayland support is enabled: +- libxcb +- libxcb-wm +- wlroots (compiled with X11 support) +- Xwayland (runtime only) -Feature *non-goals* for the main codebase include: +Install these (and their `-devel` versions if your distro has separate +development packages) and run `make`. If you wish to build against a released +version of wlroots (*you probably do*), use a [release] or a [0.x branch]. If +you want to use the unstable development `main` branch, you need to use the git +version of [wlroots]. -- 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 -- Animations and visual effects - -## Building dwl - -dwl has only two dependencies: `wlroots` and `wayland-protocols`. - -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`. +To enable XWayland, you should uncomment its flags 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 +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]! +As in the [dwm] community, we encourage users to share patches they have +created. Check out the [dwl-patches] repository! ## Running dwl 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. +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. 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. +decoration initially; these are instead clients that can be run within the +Wayland session. Do note that the default background color is grey. This can be +modified in `config.h`. 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 @@ -89,7 +84,8 @@ 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`]. +a user service manager like [s6], [anopa], [runit], [dinit], 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 @@ -105,13 +101,13 @@ automatically, you will need to configure it prior to launching `dwl`, e.g.: ### Status information -Information about selected layouts, current window title, and +Information about selected layouts, current window title, app-id, 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: +the `STATUS INFORMATION` section in `_dwl_(1)`). 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 <&-' @@ -122,10 +118,62 @@ script with the line To get a list of status bars that work with dwl consult our [wiki]. +### (Known) Java nonreparenting WM issue +Certain IDEs don't display correctly unless an environmental variable for Java AWT +indicates that the WM is nonreparenting. + +For some Java AWT-based IDEs, such as Xilinx Vivado and Microchip MPLAB X, the +following environment variable needs to be set before running the IDE or dwl: + + export _JAVA_AWT_WM_NONREPARENTING=1 + ## Replacements for X applications You can find a [list of useful resources on our wiki]. +## Background + +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. +- 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 ext-session-lock-v1 protocol +- Various Wayland protocols +- 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 + +Given the Wayland architecture, dwl has to implement features from [dwm] **and** +the xorg-server. Because of this, it is impossible to maintain the original +project goal of 2000 SLOC and have a reasonably complete compositor with +features comparable to [dwm]. However, this does not mean that the code will grow +indiscriminately. We will try to keep the code as small as possible. + +Features under consideration (possibly as patches) are: + +- Protocols made trivial by wlroots +- Implement the text-input and input-method protocols to support IME once ibus + implements input-method v2 (see https://github.com/ibus/ibus/pull/2256 and + https://codeberg.org/dwl/dwl/pulls/235) + +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 +- Animations and visual effects + ## Acknowledgements dwl began by extending the TinyWL example provided (CC0) by the sway/wlroots @@ -133,25 +181,31 @@ 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 +Many thanks to suckless.org and the [dwm] developers and community for the inspiration, and to the various contributors to the project, including: +- **Devin J. Pohly for creating and nurturing the fledgling project** - Alexander Courtis for the XWayland implementation - 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 +[wlroots]: https://gitlab.freedesktop.org/wlroots +[dwm]: https://dwm.suckless.org/ [`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 +[#dwl on Libera Chat]: https://web.libera.chat/?channels=#dwl +[0.7-rc1]: https://codeberg.org/dwl/dwl/releases/tag/v0.7-rc1 +[0.x branch]: https://codeberg.org/dwl/dwl/branches +[anopa]: https://jjacky.com/anopa/ +[dinit]: https://davmac.org/projects/dinit/ +[dwl-patches]: https://codeberg.org/dwl/dwl-patches +[list of useful resources on our wiki]: https://codeberg.org/dwl/dwl/wiki/Home#migrating-from-x +[main]: https://codeberg.org/dwl/dwl/src/branch/main +[release]: https://codeberg.org/dwl/dwl/releases +[runit]: http://smarden.org/runit/faq.html#userservices +[s6]: https://skarnet.org/software/s6/ +[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots/ +[wiki]: https://codeberg.org/dwl/dwl/wiki/Home#compatible-status-bars +[Discord server]: https://discord.gg/jJxZnrGPWN +[Wayland]: https://wayland.freedesktop.org/ diff --git a/client.h b/client.h index 5a45edc..d9f90bb 100644 --- a/client.h +++ b/client.h @@ -10,35 +10,9 @@ static inline int client_is_x11(Client *c) { #ifdef XWAYLAND - return c->type == X11Managed || c->type == X11Unmanaged; -#else + return c->type == X11; +#endif return 0; -#endif -} - -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 * @@ -54,7 +28,7 @@ client_surface(Client *c) static inline int toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) { - struct wlr_xdg_surface *xdg_surface; + struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; struct wlr_surface *root_surface; struct wlr_layer_surface_v1 *layer_surface; Client *c = NULL; @@ -65,44 +39,44 @@ toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) #endif if (!s) - return type; + return -1; root_surface = wlr_surface_get_root_surface(s); #ifdef XWAYLAND - if (wlr_surface_is_xwayland_surface(root_surface) - && (xsurface = wlr_xwayland_surface_from_wlr_surface(root_surface))) { + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { c = xsurface->data; type = c->type; goto end; } #endif - if (wlr_surface_is_layer_surface(root_surface) - && (layer_surface = wlr_layer_surface_v1_from_wlr_surface(root_surface))) { + if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) { l = layer_surface->data; type = LayerShell; goto end; } - if (wlr_surface_is_xdg_surface(root_surface) - && (xdg_surface = wlr_xdg_surface_from_wlr_surface(root_surface))) { - while (1) { - switch (xdg_surface->role) { - case WLR_XDG_SURFACE_ROLE_POPUP: - if (!xdg_surface->popup->parent) - return -1; - else if (!wlr_surface_is_xdg_surface(xdg_surface->popup->parent)) - return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); - - xdg_surface = wlr_xdg_surface_from_wlr_surface(xdg_surface->popup->parent); - break; - case WLR_XDG_SURFACE_ROLE_TOPLEVEL: - c = xdg_surface->data; - type = c->type; - goto end; - case WLR_XDG_SURFACE_ROLE_NONE: + xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface); + while (xdg_surface) { + tmp_xdg_surface = NULL; + switch (xdg_surface->role) { + case WLR_XDG_SURFACE_ROLE_POPUP: + if (!xdg_surface->popup || !xdg_surface->popup->parent) return -1; - } + + tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); + + if (!tmp_xdg_surface) + return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); + + xdg_surface = tmp_xdg_surface; + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + c = xdg_surface->data; + type = c->type; + goto end; + case WLR_XDG_SURFACE_ROLE_NONE: + return -1; } } @@ -118,19 +92,16 @@ end: static inline void client_activate_surface(struct wlr_surface *s, int activated) { - struct wlr_xdg_surface *surface; + struct wlr_xdg_toplevel *toplevel; #ifdef XWAYLAND struct wlr_xwayland_surface *xsurface; - if (wlr_surface_is_xwayland_surface(s) - && (xsurface = wlr_xwayland_surface_from_wlr_surface(s))) { + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { wlr_xwayland_surface_activate(xsurface, activated); return; } #endif - 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); + if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s))) + wlr_xdg_toplevel_set_activated(toplevel, activated); } static inline uint32_t @@ -140,31 +111,43 @@ client_set_bounds(Client *c, int32_t width, int32_t height) if (client_is_x11(c)) return 0; #endif - if (c->surface.xdg->client->shell->version >= - XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION && width >= 0 && height >= 0) + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >= + XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION && width >= 0 && height >= 0 + && (c->bounds.width != width || c->bounds.height != height)) { + c->bounds.width = width; + c->bounds.height = height; 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)) - return; -#endif - wlr_xdg_surface_for_each_popup_surface(c->surface.xdg, fn, data); -} - static inline const char * client_get_appid(Client *c) { #ifdef XWAYLAND if (client_is_x11(c)) - return c->surface.xwayland->class; + return c->surface.xwayland->class ? c->surface.xwayland->class : "broken"; #endif - return c->surface.xdg->toplevel->app_id; + return c->surface.xdg->toplevel->app_id ? c->surface.xdg->toplevel->app_id : "broken"; +} + +static inline void +client_get_clip(Client *c, struct wlr_box *clip) +{ + *clip = (struct wlr_box){ + .x = 0, + .y = 0, + .width = c->geom.width - c->bw, + .height = c->geom.height - c->bw, + }; + +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + clip->x = c->surface.xdg->geometry.x; + clip->y = c->surface.xdg->geometry.y; } static inline void @@ -179,7 +162,7 @@ client_get_geometry(Client *c, struct wlr_box *geom) return; } #endif - wlr_xdg_surface_get_geometry(c->surface.xdg, geom); + *geom = c->surface.xdg->geometry; } static inline Client * @@ -187,57 +170,70 @@ client_get_parent(Client *c) { Client *p = NULL; #ifdef XWAYLAND - if (client_is_x11(c) && c->surface.xwayland->parent) - toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); + if (client_is_x11(c)) { + if (c->surface.xwayland->parent) + toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); + return p; + } #endif if (c->surface.xdg->toplevel->parent) toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL); - return p; } +static inline int +client_has_children(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return !wl_list_empty(&c->surface.xwayland->children); +#endif + /* surface.xdg->link is never empty because it always contains at least the + * surface itself. */ + return wl_list_length(&c->surface.xdg->link) > 1; +} + static inline const char * client_get_title(Client *c) { #ifdef XWAYLAND if (client_is_x11(c)) - return c->surface.xwayland->title; + return c->surface.xwayland->title ? c->surface.xwayland->title : "broken"; #endif - return c->surface.xdg->toplevel->title; + return c->surface.xdg->toplevel->title ? c->surface.xdg->toplevel->title : "broken"; } static inline int client_is_float_type(Client *c) { - struct wlr_box min = {0}, max = {0}; - client_get_size_hints(c, &max, &min); + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_toplevel_state state; #ifdef XWAYLAND if (client_is_x11(c)) { struct wlr_xwayland_surface *surface = c->surface.xwayland; + xcb_size_hints_t *size_hints = surface->size_hints; 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; + if (wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_SPLASH) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLBAR) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_UTILITY)) { + return 1; + } + + return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0 + && (size_hints->max_width == size_hints->min_width + || size_hints->max_height == size_hints->min_height); } #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)) - return c->surface.xwayland->mapped; -#endif - return c->surface.xdg->mapped; + toplevel = c->surface.xdg->toplevel; + state = toplevel->current; + return toplevel->parent || (state.min_width != 0 && state.min_height != 0 + && (state.min_width == state.max_width + || state.min_height == state.max_height)); } static inline int @@ -247,7 +243,8 @@ client_is_rendered_on_mon(Client *c, Monitor *m) * but rather actual displaying of the pixels. * Usually VISIBLEON suffices and is also faster. */ struct wlr_surface_output *s; - if (!c->scene->node.enabled) + int unused_lx, unused_ly; + if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly)) return 0; wl_list_for_each(s, &client_surface(c)->current_outputs, link) if (s->output == m->wlr_output) @@ -267,8 +264,8 @@ client_is_stopped(Client *c) wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) { - /* This process is not our child process, while is very unluckely that - * it is stopped, in order to do not skip frames assume that it is. */ + /* This process is not our child process, while is very unlikely that + * it is stopped, in order to do not skip frames, assume that it is. */ if (errno == ECHILD) return 1; } else if (in.si_pid) { @@ -285,7 +282,8 @@ static inline int client_is_unmanaged(Client *c) { #ifdef XWAYLAND - return c->type == X11Unmanaged; + if (client_is_x11(c)) + return c->surface.xwayland->override_redirect; #endif return 0; } @@ -300,17 +298,6 @@ client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) 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) { @@ -323,6 +310,14 @@ client_send_close(Client *c) wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); } +static inline void +client_set_border_color(Client *c, const float color[static 4]) +{ + int i; + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], color); +} + static inline void client_set_fullscreen(Client *c, int fullscreen) { @@ -335,41 +330,56 @@ client_set_fullscreen(Client *c, int fullscreen) wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); } +static inline void +client_set_scale(struct wlr_surface *s, float scale) +{ + wlr_fractional_scale_v1_notify_scale(s, scale); + wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(scale)); +} + static inline uint32_t client_set_size(Client *c, uint32_t width, uint32_t height) { #ifdef XWAYLAND if (client_is_x11(c)) { wlr_xwayland_surface_configure(c->surface.xwayland, - c->geom.x, c->geom.y, width, height); + c->geom.x + c->bw, c->geom.y + c->bw, width, height); return 0; } #endif - if (width == c->surface.xdg->toplevel->current.width - && height ==c->surface.xdg->toplevel->current.height) + if ((int32_t)width == c->surface.xdg->toplevel->current.width + && (int32_t)height == c->surface.xdg->toplevel->current.height) return 0; - return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, width, height); + return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); } static inline void client_set_tiled(Client *c, uint32_t edges) { #ifdef XWAYLAND - if (client_is_x11(c)) + if (client_is_x11(c)) { + wlr_xwayland_surface_set_maximized(c->surface.xwayland, + edges != WLR_EDGE_NONE, edges != WLR_EDGE_NONE); return; + } #endif - wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) + >= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) { + wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); + } else { + wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE); + } } -static inline struct wlr_surface * -client_surface_at(Client *c, double cx, double cy, double *sx, double *sy) +static inline void +client_set_suspended(Client *c, int suspended) { #ifdef XWAYLAND if (client_is_x11(c)) - return wlr_surface_surface_at(c->surface.xwayland->surface, - cx, cy, sx, sy); + return; #endif - return wlr_xdg_surface_surface_at(c->surface.xdg, cx, cy, sx, sy); + + wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); } static inline int @@ -377,8 +387,8 @@ 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; + && wlr_xwayland_surface_override_redirect_wants_focus(c->surface.xwayland) + && wlr_xwayland_surface_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; #endif return 0; } diff --git a/config.def.h b/config.def.h index a4f7c13..95c2afa 100644 --- a/config.def.h +++ b/config.def.h @@ -1,22 +1,31 @@ +/* Taken from https://github.com/djpohly/dwl/issues/466 */ +#define COLOR(hex) { ((hex >> 24) & 0xFF) / 255.0f, \ + ((hex >> 16) & 0xFF) / 255.0f, \ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } /* appearance */ 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}; +static const float rootcolor[] = COLOR(0x222222ff); +static const float bordercolor[] = COLOR(0x444444ff); +static const float focuscolor[] = COLOR(0x005577ff); +static const float urgentcolor[] = COLOR(0xff0000ff); +/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ -/* tagging */ -static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +/* tagging - TAGCOUNT must be no greater than 31 */ +#define TAGCOUNT (9) +/* logging */ +static int log_level = WLR_ERROR; + +/* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ static const Rule rules[] = { - /* app_id title tags mask isfloating monitor */ - /* examples: - { "Gimp", NULL, 0, 1, -1 }, - */ - { "firefox", NULL, 1 << 8, 0, -1 }, + /* app_id title tags mask isfloating monitor */ + /* examples: */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ }; /* layout(s) */ @@ -28,13 +37,18 @@ static const Layout layouts[] = { }; /* monitors */ +/* (x=-1, y=-1) is reserved as an "autoconfigure" monitor position indicator + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 +*/ +/* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ static const MonitorRule monrules[] = { - /* name mfact nmaster scale layout rotate/reflect */ + /* name mfact nmaster scale layout rotate/reflect x y */ /* example of a HiDPI laptop monitor: - { "eDP-1", 0.5, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL }, + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, */ /* defaults */ - { NULL, 0.55, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL }, + { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, }; /* keyboard */ @@ -66,9 +80,9 @@ 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 +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; @@ -85,6 +99,7 @@ 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 @@ -105,7 +120,7 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA /* commands */ static const char *termcmd[] = { "foot", NULL }; -static const char *menucmd[] = { "bemenu-run", NULL }; +static const char *menucmd[] = { "wmenu-run", NULL }; static const Key keys[] = { /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ @@ -116,8 +131,8 @@ static const Key keys[] = { { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, - { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05} }, - { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, { MODKEY, XKB_KEY_Return, zoom, {0} }, { MODKEY, XKB_KEY_Tab, view, {0} }, { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, @@ -146,6 +161,9 @@ static const Key keys[] = { /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ #define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), diff --git a/config.mk b/config.mk index f50156f..eb08a05 100644 --- a/config.mk +++ b/config.mk @@ -1,4 +1,4 @@ -_VERSION = 0.4 +_VERSION = 0.8-dev VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)` PKG_CONFIG = pkg-config @@ -6,9 +6,31 @@ PKG_CONFIG = pkg-config # paths PREFIX = /usr/local MANDIR = $(PREFIX)/share/man +DATADIR = $(PREFIX)/share + +WLR_INCS = `$(PKG_CONFIG) --cflags wlroots-0.19` +WLR_LIBS = `$(PKG_CONFIG) --libs wlroots-0.19` + +# Allow using an alternative wlroots installation +# This has to have all the includes required by wlroots, e.g: +# Assuming wlroots git repo is "${PWD}/wlroots" and you only ran "meson setup build && ninja -C build" +#WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \ +# -I$(PWD)/wlroots/include +# Set -rpath to avoid using the wrong library. +#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/build -L$(PWD)/wlroots/build -lwlroots-0.19 + +# Assuming you ran "meson setup --prefix ${PWD}/0.19 build && ninja -C build install" +#WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \ +# -I$(PWD)/wlroots/0.19/include/wlroots-0.19 +#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/0.19/lib64 -L$(PWD)/wlroots/0.19/lib64 -lwlroots-0.19 XWAYLAND = XLIBS = # Uncomment to build XWayland support #XWAYLAND = -DXWAYLAND #XLIBS = xcb xcb-icccm + +# dwl itself only uses C99 features, but wlroots' headers use anonymous unions (C11). +# To avoid warnings about them, we do not use -std=c99 and instead of using the +# gmake default 'CC=c99', we use cc. +CC = cc diff --git a/dwl.1 b/dwl.1 index cae1036..7fee870 100644 --- a/dwl.1 +++ b/dwl.1 @@ -7,6 +7,7 @@ .Sh SYNOPSIS .Nm .Op Fl v +.Op Fl d .Op Fl s Ar startup command .Sh DESCRIPTION .Nm @@ -22,6 +23,12 @@ option, writes its name and version to standard error and exits unsuccessfully. .Pp When given the +.Fl d +option, +.Nm +enables full wlroots logging, including debug information. +.Pp +When given the .Fl s option, .Nm @@ -30,7 +37,7 @@ starts a shell process running when starting. When stopping, it sends .Dv SIGTERM -to the child process and waits for it to exit. +to the child process group and waits for it to exit. .Pp Users are encouraged to customize .Nm @@ -48,10 +55,10 @@ Move window to a single tag. Toggle tag for window. .It Mod-p Spawn -.Nm bemenu-run . +.Xr wmenu-run 1 . .It Mod-Shift-Return Spawn -.Nm foot . +.Xr foot 1 . .It Mod-[jk] Move focus down/up the stack. .It Mod-[id] @@ -93,6 +100,114 @@ Quit .Nm . .El These might differ depending on your keyboard layout. +.Ss Mouse commands +.Bl -tag -width 20n -offset indent -compact +.It Mod-Button1 +Move focused window while dragging. +Tiled windows will be toggled to the floating state. +.It Mod-Button2 +Toggle focused window between floating and tiled state. +.It Mod-Button3 +Resize focused window while dragging. +Tiled windows will be toggled to the floating state. +.El +.Sh STATUS INFORMATION +.Nm +writes its status information to standard output. +If the +.Fl s +option is given, the status information is written to the standard input of the +child process instead. +.Pp +Said information has the following format: +.Bd -ragged -offset indent +.Ar +.Ar +.Ar +.Ed +.Pp +.Bl -tag -width 11n -offset 0 -compact +.It Ar +is the name given to the output. +.It Ar +is one of (in order) +.Em title , +.Em appid , +.Em fullscreen , +.Em floating , +.Em selmon , +.Em tags , +.Em layout . +.It Ar +changes depending on +.Ar . +.Bl -tag -width 10n -compact +.It Em title +The title of the focused window on +.Ar +or nothing if there is no focused window. +.It Em appid +The app_id of the focused window on +.Ar +or nothing if there is no focused window. +.It Em fullscreen +Prints 1 if the focused window on +.Ar +is in fullscreen state, otherwise prints 0. If there is no focused +window it prints nothing. +.It Em floating +Prints 1 if the focused window on +.Ar +is in floating state, otherwise prints 0. If there is no focused +window it prints nothing. +.It Em selmon +Prints 1 if +.Ar +is the selected monitor, otherwise prints 0. +.It Em tags +Prints four bitmasks in the following order: +.Bl -bullet -width 2n -compact +.It +Occupied tags of +.Ar . +.It +Selected tags of +.Ar . +.It +Tags of the focused window on +.Ar . +.It +Tags where a window on +.Ar +requested activation or has urgency hints. +.El +The bitmasks are 32-bit unsigned decimal integers. +.It Em layout +Prints the symbol of the current layout. +.El +.El +.Ss Examples +When there is a selected window: +.Bd -literal -offset indent +HDMI\-A\-1 title \(ti/source/repos/dwl > man \-l dwl.1 +HDMI\-A\-1 appid footclient +HDMI\-A\-1 fullscreen 0 +HDMI\-A\-1 floating 0 +HDMI\-A\-1 selmon 1 +HDMI\-A\-1 tags 271 4 4 0 +HDMI\-A\-1 layout [T] +.Ed +.Pp +When there is no selected window: +.Bd -literal -offset indent +HDMI\-A\-1 title +HDMI\-A\-1 appid +HDMI\-A\-1 fullscreen +HDMI\-A\-1 floating +HDMI\-A\-1 selmon 1 +HDMI\-A\-1 tags 271 512 0 0 +HDMI\-A\-1 layout [T] +.Ed .Sh ENVIRONMENT These environment variables are used by .Nm : @@ -101,7 +216,7 @@ These environment variables are used by 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 +A directory containing 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. @@ -133,19 +248,11 @@ server. Start .Nm with s6 in the background: -.Dl dwl -s 's6-svscan <&-' +.Dl dwl \-s \(aqs6\-svscan <&\-\(aq .Sh SEE ALSO -.Xr foot 1 , -.Xr bemenu 1 , .Xr dwm 1 , +.Xr foot 1 , +.Xr wmenu 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 19bb6ce..12f441e 100644 --- a/dwl.c +++ b/dwl.c @@ -3,8 +3,8 @@ */ #include #include -#include #include +#include #include #include #include @@ -16,26 +16,34 @@ #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 @@ -45,16 +53,18 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #ifdef XWAYLAND #include -#include +#include #include #endif @@ -67,22 +77,18 @@ #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 TAGMASK ((1u << TAGCOUNT) - 1) #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) +#define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) /* enums */ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ -enum { XDGShell, LayerShell, X11Managed, X11Unmanaged }; /* client types */ -enum { LyrBg, LyrBottom, LyrTop, LyrOverlay, LyrTile, LyrFloat, LyrFS, LyrDragIcon, LyrBlock, NUM_LAYERS }; /* scene layers */ -#ifdef XWAYLAND -enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, - NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ -#endif +enum { XDGShell, LayerShell, X11 }; /* client types */ +enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ typedef union { int i; - unsigned int ui; + uint32_t ui; float f; const void *v; } Arg; @@ -96,19 +102,23 @@ typedef struct { typedef struct Monitor Monitor; typedef struct { - /* Must keep these three elements in this order */ + /* Must keep this field first */ 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 wlr_box geom; /* layout-relative, includes border */ + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; /* only width and height are used */ union { struct wlr_xdg_surface *xdg; struct wlr_xwayland_surface *xwayland; } surface; + struct wlr_xdg_toplevel_decoration_v1 *decoration; struct wl_listener commit; struct wl_listener map; struct wl_listener maximize; @@ -116,14 +126,17 @@ typedef struct { struct wl_listener destroy; struct wl_listener set_title; struct wl_listener fullscreen; - struct wlr_box prev; /* layout-relative, includes border */ + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; #ifdef XWAYLAND struct wl_listener activate; + struct wl_listener associate; + struct wl_listener dissociate; struct wl_listener configure; struct wl_listener set_hints; #endif unsigned int bw; - unsigned int tags; + uint32_t tags; int isfloating, isurgent, isfullscreen; uint32_t resize; /* configure serial of a pending resize */ } Client; @@ -136,18 +149,22 @@ typedef struct { } Key; typedef struct { - struct wl_list link; - struct wlr_keyboard *wlr_keyboard; + struct wlr_keyboard_group *wlr_group; + + int nsyms; + const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ + uint32_t mods; /* invalid if nsyms == 0 */ + struct wl_event_source *key_repeat_source; struct wl_listener modifiers; struct wl_listener key; struct wl_listener destroy; -} Keyboard; +} KeyboardGroup; typedef struct { - /* Must keep these three elements in this order */ + /* Must keep this field first */ unsigned int type; /* LayerShell */ - struct wlr_box geom; + Monitor *mon; struct wlr_scene_tree *scene; struct wlr_scene_tree *popups; @@ -157,7 +174,6 @@ typedef struct { struct wlr_layer_surface_v1 *layer_surface; struct wl_listener destroy; - struct wl_listener map; struct wl_listener unmap; struct wl_listener surface_commit; } LayerSurface; @@ -174,17 +190,21 @@ struct Monitor { struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ struct wl_listener frame; struct wl_listener destroy; + struct wl_listener request_state; struct wl_listener destroy_lock_surface; struct wlr_session_lock_surface_v1 *lock_surface; - struct wlr_box m; /* monitor area, layout-relative */ - struct wlr_box w; /* window area, layout-relative */ - struct wl_list layers[4]; /* LayerSurface::link */ + struct wlr_box m; /* monitor area, layout-relative */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ const Layout *lt[2]; unsigned int seltags; unsigned int sellt; - unsigned int tagset[2]; - double mfact; + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; int nmaster; + char ltsymbol[16]; + int asleep; }; typedef struct { @@ -194,12 +214,18 @@ typedef struct { float scale; const Layout *lt; enum wl_output_transform rr; + int x, y; } MonitorRule; +typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +} PointerConstraint; + typedef struct { const char *id; const char *title; - unsigned int tags; + uint32_t tags; int isfloating; int monitor; } Rule; @@ -225,47 +251,58 @@ 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 cleanuplisteners(void); 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 commitpopup(struct wl_listener *listener, void *data); static void createdecoration(struct wl_listener *listener, void *data); static void createidleinhibitor(struct wl_listener *listener, void *data); static void createkeyboard(struct wlr_keyboard *keyboard); +static KeyboardGroup *createkeyboardgroup(void); static void createlayersurface(struct wl_listener *listener, void *data); static void createlocksurface(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 createpointer(struct wlr_pointer *pointer); +static void createpointerconstraint(struct wl_listener *listener, void *data); +static void createpopup(struct wl_listener *listener, void *data); +static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); static void cursorframe(struct wl_listener *listener, void *data); +static void cursorwarptohint(void); +static void destroydecoration(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 destroylock(SessionLock *lock, int unlocked); static void destroylocksurface(struct wl_listener *listener, void *data); static void destroynotify(struct wl_listener *listener, void *data); +static void destroypointerconstraint(struct wl_listener *listener, void *data); static void destroysessionlock(struct wl_listener *listener, void *data); -static void destroysessionmgr(struct wl_listener *listener, void *data); +static void destroykeyboardgroup(struct wl_listener *listener, void *data); static Monitor *dirtomon(enum wlr_direction dir); static void focusclient(Client *c, int lift); static void focusmon(const Arg *arg); static void focusstack(const Arg *arg); static Client *focustop(Monitor *m); static void fullscreennotify(struct wl_listener *listener, void *data); +static void gpureset(struct wl_listener *listener, void *data); +static void handlesig(int signo); 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); static void keypress(struct wl_listener *listener, void *data); static void keypressmod(struct wl_listener *listener, void *data); +static int keyrepeat(void *data); static void killclient(const Arg *arg); static void locksession(struct wl_listener *listener, void *data); -static void maplayersurfacenotify(struct wl_listener *listener, void *data); static void mapnotify(struct wl_listener *listener, void *data); static void maximizenotify(struct wl_listener *listener, void *data); static void monocle(Monitor *m); static void motionabsolute(struct wl_listener *listener, void *data); -static void motionnotify(uint32_t time); +static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); static void motionrelative(struct wl_listener *listener, void *data); static void moveresize(const Arg *arg); static void outputmgrapply(struct wl_listener *listener, void *data); @@ -274,18 +311,21 @@ 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 printstatus(void); +static void powermgrsetmode(struct wl_listener *listener, void *data); static void quit(const Arg *arg); -static void quitsignal(int signo); static void rendermon(struct wl_listener *listener, void *data); +static void requestdecorationmode(struct wl_listener *listener, void *data); static void requeststartdrag(struct wl_listener *listener, void *data); +static void requestmonstate(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 setcursor(struct wl_listener *listener, void *data); +static void setcursorshape(struct wl_listener *listener, void *data); static void setfloating(Client *c, int floating); static void setfullscreen(Client *c, int fullscreen); static void setlayout(const Arg *arg); static void setmfact(const Arg *arg); -static void setmon(Client *c, Monitor *m, unsigned int newtags); +static void setmon(Client *c, Monitor *m, uint32_t newtags); static void setpsel(struct wl_listener *listener, void *data); static void setsel(struct wl_listener *listener, void *data); static void setup(void); @@ -306,47 +346,57 @@ 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 void virtualpointer(struct wl_listener *listener, void *data); static Monitor *xytomon(double x, double y); -static struct wlr_scene_node *xytonode(double x, double y, struct wlr_surface **psurface, +static void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny); static void zoom(const Arg *arg); /* variables */ -static const char broken[] = "broken"; -static const char *cursor_image = "left_ptr"; static pid_t child_pid = -1; static int locked; static void *exclusive_focus; static struct wl_display *dpy; +static struct wl_event_loop *event_loop; static struct wlr_backend *backend; static struct wlr_scene *scene; static struct wlr_scene_tree *layers[NUM_LAYERS]; +static struct wlr_scene_tree *drag_icon; +/* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ +static const int layermap[] = { LyrBg, LyrBottom, LyrTop, LyrOverlay }; static struct wlr_renderer *drw; static struct wlr_allocator *alloc; static struct wlr_compositor *compositor; +static struct wlr_session *session; static struct wlr_xdg_shell *xdg_shell; static struct wlr_xdg_activation_v1 *activation; static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; static struct wl_list clients; /* tiling order */ static struct wl_list fstack; /* focus order */ -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_output_manager_v1 *output_mgr; static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; +static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; +static struct wlr_output_power_manager_v1 *power_mgr; + +static struct wlr_pointer_constraints_v1 *pointer_constraints; +static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +static struct wlr_pointer_constraint_v1 *active_constraint; static struct wlr_cursor *cursor; static struct wlr_xcursor_manager *cursor_mgr; +static struct wlr_scene_rect *root_bg; static struct wlr_session_lock_manager_v1 *session_lock_mgr; static struct wlr_scene_rect *locked_bg; static struct wlr_session_lock_v1 *cur_lock; static struct wlr_seat *seat; -static struct wl_list keyboards; +static KeyboardGroup *kb_group; static unsigned int cursor_mode; static Client *grabc; static int grabcx, grabcy; /* client-relative */ @@ -362,39 +412,41 @@ 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 gpu_reset = {.notify = gpureset}; static struct wl_listener layout_change = {.notify = updatemons}; -static struct wl_listener new_input = {.notify = inputdevice}; +static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor}; +static struct wl_listener new_input_device = {.notify = inputdevice}; static struct wl_listener new_virtual_keyboard = {.notify = virtualkeyboard}; +static struct wl_listener new_virtual_pointer = {.notify = virtualpointer}; +static struct wl_listener new_pointer_constraint = {.notify = createpointerconstraint}; static struct wl_listener new_output = {.notify = createmon}; -static struct wl_listener new_xdg_surface = {.notify = createnotify}; +static struct wl_listener new_xdg_toplevel = {.notify = createnotify}; +static struct wl_listener new_xdg_popup = {.notify = createpopup}; static struct wl_listener new_xdg_decoration = {.notify = createdecoration}; -static struct wl_listener new_layer_shell_surface = {.notify = createlayersurface}; +static struct wl_listener new_layer_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 output_power_mgr_set_mode = {.notify = powermgrsetmode}; 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_set_cursor_shape = {.notify = setcursorshape}; static struct wl_listener request_start_drag = {.notify = requeststartdrag}; static struct wl_listener start_drag = {.notify = startdrag}; -static struct wl_listener session_lock_create_lock = {.notify = locksession}; -static struct wl_listener session_lock_mgr_destroy = {.notify = destroysessionmgr}; +static struct wl_listener new_session_lock = {.notify = locksession}; #ifdef XWAYLAND static void activatex11(struct wl_listener *listener, void *data); +static void associatex11(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 dissociatex11(struct wl_listener *listener, void *data); static void sethints(struct wl_listener *listener, void *data); -static void sigchld(int unused); static void xwaylandready(struct wl_listener *listener, void *data); static struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; static struct wl_listener xwayland_ready = {.notify = xwaylandready}; static struct wlr_xwayland *xwayland; -static Atom netatom[NetLast]; #endif /* configuration, allows nested code to access above variables */ @@ -403,34 +455,21 @@ static Atom netatom[NetLast]; /* attempt to encapsulate suck into one file */ #include "client.h" -/* compile-time check if all tags fit into an unsigned int bit array. */ -struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; - /* function implementations */ void applybounds(Client *c, struct wlr_box *bbox) { - 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); - } + /* set minimum possible */ + c->geom.width = MAX(1 + 2 * (int)c->bw, c->geom.width); + c->geom.height = MAX(1 + 2 * (int)c->bw, c->geom.height); if (c->geom.x >= bbox->x + bbox->width) c->geom.x = bbox->x + bbox->width - c->geom.width; if (c->geom.y >= bbox->y + bbox->height) c->geom.y = bbox->y + bbox->height - c->geom.height; - if (c->geom.x + c->geom.width + 2 * c->bw <= bbox->x) + if (c->geom.x + c->geom.width <= bbox->x) c->geom.x = bbox->x; - if (c->geom.y + c->geom.height + 2 * c->bw <= bbox->y) + if (c->geom.y + c->geom.height <= bbox->y) c->geom.y = bbox->y; } @@ -439,15 +478,13 @@ applyrules(Client *c) { /* rule matching */ const char *appid, *title; - unsigned int i, newtags = 0; + uint32_t newtags = 0; + int i; const Rule *r; Monitor *mon = selmon, *m; - c->isfloating = client_is_float_type(c); - if (!(appid = client_get_appid(c))) - appid = broken; - if (!(title = client_get_title(c))) - title = broken; + appid = client_get_appid(c); + title = client_get_title(c); for (r = rules; r < END(rules); r++) { if ((!r->title || strstr(title, r->title)) @@ -455,12 +492,14 @@ applyrules(Client *c) c->isfloating = r->isfloating; newtags |= r->tags; i = 0; - wl_list_for_each(m, &mons, link) + wl_list_for_each(m, &mons, link) { if (r->monitor == i++) mon = m; + } } } - wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrFloat : LyrTile]); + + c->isfloating |= client_is_float_type(c); setmon(c, mon, newtags); } @@ -468,37 +507,59 @@ void arrange(Monitor *m) { Client *c; - wl_list_for_each(c, &clients, link) - if (c->mon == m) + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + client_set_suspended(c, !VISIBLEON(c, m)); + } + } wlr_scene_node_set_enabled(&m->fullscreen_bg->node, (c = focustop(m)) && c->isfullscreen); - if (m && m->lt[m->sellt]->arrange) + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ + wl_list_for_each(c, &clients, link) { + if (c->mon != m || c->scene->node.parent == layers[LyrFS]) + continue; + + wlr_scene_node_reparent(&c->scene->node, + (!m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrTile] + : (m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrFloat] + : c->scene->node.parent); + } + + if (m->lt[m->sellt]->arrange) m->lt[m->sellt]->arrange(m); - motionnotify(0); + motionnotify(0, NULL, 0, 0, 0, 0); checkidleinhibitor(NULL); } void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) { - LayerSurface *layersurface; + LayerSurface *l; struct wlr_box full_area = m->m; - 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; + wl_list_for_each(l, list, link) { + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; - if (exclusive != (state->exclusive_zone > 0)) + if (!layer_surface->initialized) continue; - 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; + if (exclusive != (layer_surface->current.exclusive_zone > 0)) + continue; + + wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area, usable_area); + wlr_scene_node_set_position(&l->popups->node, l->scene->node.x, l->scene->node.y); } } @@ -507,11 +568,11 @@ arrangelayers(Monitor *m) { int i; struct wlr_box usable_area = m->m; + LayerSurface *l; uint32_t layers_above_shell[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, }; - LayerSurface *layersurface; if (!m->wlr_output->enabled) return; @@ -519,7 +580,7 @@ arrangelayers(Monitor *m) for (i = 3; i >= 0; i--) arrangelayer(m, &m->layers[i], &usable_area, 1); - if (memcmp(&usable_area, &m->w, sizeof(struct wlr_box))) { + if (!wlr_box_equal(&usable_area, &m->w)) { m->w = usable_area; arrange(m); } @@ -529,17 +590,15 @@ arrangelayers(Monitor *m) arrangelayer(m, &m->layers[i], &usable_area, 0); /* 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 (!locked && layersurface->layer_surface->current.keyboard_interactive - && layersurface->mapped) { - /* Deactivate the focused client. */ - focusclient(NULL, 0); - exclusive_focus = layersurface; - client_notify_enter(layersurface->layer_surface->surface, wlr_seat_get_keyboard(seat)); - return; - } + for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { + wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + if (locked || !l->layer_surface->current.keyboard_interactive || !l->mapped) + continue; + /* Deactivate the focused client. */ + focusclient(NULL, 0); + exclusive_focus = l; + client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); + return; } } } @@ -550,13 +609,13 @@ 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_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 */ + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented + * by 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, - event->delta_discrete, event->source); + event->delta_discrete, event->source, event->relative_direction); } void @@ -568,11 +627,12 @@ buttonpress(struct wl_listener *listener, void *data) Client *c; const Button *b; - IDLE_NOTIFY_ACTIVITY; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); switch (event->state) { - case WLR_BUTTON_PRESSED: + case WL_POINTER_BUTTON_STATE_PRESSED: cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); if (locked) break; @@ -591,22 +651,19 @@ buttonpress(struct wl_listener *listener, void *data) } } break; - case WLR_BUTTON_RELEASED: + case WL_POINTER_BUTTON_STATE_RELEASED: /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO: should reset to the pointer focus's current setcursor */ if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); 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); + grabc = NULL; return; - } else { - cursor_mode = CurNormal; } + cursor_mode = CurNormal; break; } /* If the event wasn't handled by the compositor, notify the client with @@ -618,59 +675,52 @@ buttonpress(struct wl_listener *listener, void *data) void chvt(const Arg *arg) { - wlr_session_change_vt(wlr_backend_get_session(backend), arg->ui); + wlr_session_change_vt(session, arg->ui); } void checkidleinhibitor(struct wlr_surface *exclude) { - int inhibited = 0; + int inhibited = 0, unused_lx, unused_ly; 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 (exclude != surface && (bypass_surface_visibility || (!tree - || tree->node.enabled))) { + || wlr_scene_node_coords(&tree->node, &unused_lx, &unused_ly)))) { inhibited = 1; break; } } - wlr_idle_set_enabled(idle, NULL, !inhibited); wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); } void cleanup(void) { + cleanuplisteners(); #ifdef XWAYLAND wlr_xwayland_destroy(xwayland); + xwayland = NULL; #endif wl_display_destroy_clients(dpy); if (child_pid > 0) { - kill(child_pid, SIGTERM); + 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); - wlr_seat_destroy(seat); + + destroykeyboardgroup(&kb_group->destroy, NULL); + + /* If it's not destroyed manually, it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed on the wlroots side */ + wlr_backend_destroy(backend); + wl_display_destroy(dpy); -} - -void -cleanupkeyboard(struct wl_listener *listener, void *data) -{ - Keyboard *kb = wl_container_of(listener, kb, destroy); - - 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); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); } void @@ -678,43 +728,88 @@ cleanupmon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, destroy); LayerSurface *l, *tmp; - int i; + size_t i; - for (i = 0; i <= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; i++) + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { wl_list_for_each_safe(l, tmp, &m->layers[i], link) 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); + wl_list_remove(&m->request_state.link); + if (m->lock_surface) + destroylocksurface(&m->destroy_lock_surface, NULL); 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); closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); free(m); } +void +cleanuplisteners(void) +{ + wl_list_remove(&cursor_axis.link); + wl_list_remove(&cursor_button.link); + wl_list_remove(&cursor_frame.link); + wl_list_remove(&cursor_motion.link); + wl_list_remove(&cursor_motion_absolute.link); + wl_list_remove(&gpu_reset.link); + wl_list_remove(&new_idle_inhibitor.link); + wl_list_remove(&layout_change.link); + wl_list_remove(&new_input_device.link); + wl_list_remove(&new_virtual_keyboard.link); + wl_list_remove(&new_virtual_pointer.link); + wl_list_remove(&new_pointer_constraint.link); + wl_list_remove(&new_output.link); + wl_list_remove(&new_xdg_toplevel.link); + wl_list_remove(&new_xdg_decoration.link); + wl_list_remove(&new_xdg_popup.link); + wl_list_remove(&new_layer_surface.link); + wl_list_remove(&output_mgr_apply.link); + wl_list_remove(&output_mgr_test.link); + wl_list_remove(&output_power_mgr_set_mode.link); + wl_list_remove(&request_activate.link); + wl_list_remove(&request_cursor.link); + wl_list_remove(&request_set_psel.link); + wl_list_remove(&request_set_sel.link); + wl_list_remove(&request_set_cursor_shape.link); + wl_list_remove(&request_start_drag.link); + wl_list_remove(&start_drag.link); + wl_list_remove(&new_session_lock.link); +#ifdef XWAYLAND + wl_list_remove(&new_xwayland_surface.link); + wl_list_remove(&xwayland_ready.link); +#endif +} + void closemon(Monitor *m) { /* update selmon if needed and * move closed monitor's clients to the focused one */ Client *c; - if (wl_list_empty(&mons)) { + int i = 0, nmons = wl_list_length(&mons); + if (!nmons) { 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); + + if (!selmon->wlr_output->enabled) + selmon = NULL; } wl_list_for_each(c, &clients, link) { if (c->isfloating && c->geom.x > m->m.width) 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); + .width = c->geom.width, .height = c->geom.height}, 0); if (c->mon == m) setmon(c, selmon, c->tags); } @@ -725,63 +820,119 @@ closemon(Monitor *m) void 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; + LayerSurface *l = wl_container_of(listener, l, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->current.layer]]; + struct wlr_layer_surface_v1_state old_state; - /* 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)) + if (l->layer_surface->initial_commit) { + client_set_scale(layer_surface->surface, l->mon->wlr_output->scale); + + /* Temporarily set the layer's current state to pending + * so that we can easily arrange it */ + old_state = l->layer_surface->current; + l->layer_surface->current = l->layer_surface->pending; + arrangelayers(l->mon); + l->layer_surface->current = old_state; return; - - 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(&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) + if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) return; - layersurface->mapped = wlr_layer_surface->mapped; + l->mapped = layer_surface->surface->mapped; - arrangelayers(layersurface->mon); + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); + wlr_scene_node_reparent(&l->popups->node, (layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer)); + } + + arrangelayers(l->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); - if (c->mon && !wlr_box_empty(&box) && (box.width != c->geom.width - 2 * c->bw - || box.height != c->geom.height - 2 * c->bw)) - c->isfloating ? resize(c, c->geom, 1) : arrange(c->mon); + if (c->surface.xdg->initial_commit) { + /* + * Get the monitor this client will be rendered on + * Note that if the user set a rule in which the client is placed on + * a different monitor based on its title, this will likely select + * a wrong monitor. + */ + applyrules(c); + if (c->mon) { + client_set_scale(client_surface(c), c->mon->wlr_output->scale); + } + setmon(c, NULL, 0); /* Make sure to reapply rules in mapnotify() */ + + wlr_xdg_toplevel_set_wm_capabilities(c->surface.xdg->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + if (c->decoration) + requestdecorationmode(&c->set_decoration_mode, c->decoration); + wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, 0, 0); + return; + } + + resize(c, c->geom, (c->isfloating && !c->isfullscreen)); /* mark a pending resize as completed */ if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) c->resize = 0; } +void +commitpopup(struct wl_listener *listener, void *data) +{ + struct wlr_surface *surface = data; + struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); + LayerSurface *l = NULL; + Client *c = NULL; + struct wlr_box box; + int type = -1; + + if (!popup->base->initial_commit) + return; + + type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); + if (!popup->parent || type < 0) + return; + popup->base->surface->data = wlr_scene_xdg_surface_create( + popup->parent->data, popup->base); + if ((l && !l->mon) || (c && !c->mon)) { + wlr_xdg_popup_destroy(popup); + return; + } + box = type == LayerShell ? l->mon->m : c->mon->w; + box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); + box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); + wl_list_remove(&listener->link); + free(listener); +} + void createdecoration(struct wl_listener *listener, void *data) { - struct wlr_xdg_toplevel_decoration_v1 *dec = data; - wlr_xdg_toplevel_decoration_v1_set_mode(dec, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + struct wlr_xdg_toplevel_decoration_v1 *deco = data; + Client *c = deco->toplevel->base->data; + c->decoration = deco; + + LISTEN(&deco->events.request_mode, &c->set_decoration_mode, requestdecorationmode); + LISTEN(&deco->events.destroy, &c->destroy_decoration, destroydecoration); + + requestdecorationmode(&c->set_decoration_mode, deco); } void 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); + LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); checkidleinhibitor(NULL); } @@ -789,79 +940,80 @@ createidleinhibitor(struct wl_listener *listener, void *data) void createkeyboard(struct wlr_keyboard *keyboard) { + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); +} + +KeyboardGroup * +createkeyboardgroup(void) +{ + KeyboardGroup *group = ecalloc(1, sizeof(*group)); struct xkb_context *context; struct xkb_keymap *keymap; - Keyboard *kb = keyboard->data = ecalloc(1, sizeof(*kb)); - kb->wlr_keyboard = keyboard; - /* Prepare an XKB keymap and assign it to the keyboard. */ + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + + /* Prepare an XKB keymap and assign it to the keyboard group. */ context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - keymap = xkb_keymap_new_from_names(context, &xkb_rules, - XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + XKB_KEYMAP_COMPILE_NO_FLAGS))) + die("failed to compile keymap"); - wlr_keyboard_set_keymap(keyboard, keymap); + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); - wlr_keyboard_set_repeat_info(keyboard, repeat_rate, repeat_delay); - /* Here we set up listeners for keyboard events. */ - LISTEN(&keyboard->events.modifiers, &kb->modifiers, keypressmod); - LISTEN(&keyboard->events.key, &kb->key, keypress); - LISTEN(&keyboard->base.events.destroy, &kb->destroy, cleanupkeyboard); + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); - wlr_seat_set_keyboard(seat, keyboard); + /* Set up listeners for keyboard events */ + LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); + LISTEN(&group->wlr_group->keyboard.events.modifiers, &group->modifiers, keypressmod); - /* And add the keyboard to our list of keyboards */ - wl_list_insert(&keyboards, &kb->link); + group->key_repeat_source = wl_event_loop_add_timer(event_loop, keyrepeat, group); + + /* A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same wlr_keyboard_group, which provides a single wlr_keyboard interface for + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + return group; } 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; + struct wlr_layer_surface_v1 *layer_surface = data; + LayerSurface *l; + struct wlr_surface *surface = layer_surface->surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->pending.layer]]; - if (!wlr_layer_surface->output) - wlr_layer_surface->output = selmon ? selmon->wlr_output : NULL; + if (!layer_surface->output + && !(layer_surface->output = selmon ? selmon->wlr_output : NULL)) { + wlr_layer_surface_v1_destroy(layer_surface); + return; + } - if (!wlr_layer_surface->output) - wlr_layer_surface_v1_destroy(wlr_layer_surface); + l = layer_surface->data = ecalloc(1, sizeof(*l)); + l->type = LayerShell; + LISTEN(&surface->events.commit, &l->surface_commit, commitlayersurfacenotify); + LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify); + LISTEN(&layer_surface->events.destroy, &l->destroy, destroylayersurfacenotify); - 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); + l->layer_surface = layer_surface; + l->mon = layer_surface->output->data; + l->scene_layer = wlr_scene_layer_surface_v1_create(scene_layer, layer_surface); + l->scene = l->scene_layer->tree; + l->popups = surface->data = wlr_scene_tree_create(layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer); + l->scene->node.data = l->popups->node.data = l; - 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; + wl_list_insert(&l->mon->layers[layer_surface->pending.layer],&l->link); + wlr_surface_send_enter(surface, layer_surface->output); } void @@ -870,8 +1022,8 @@ createlocksurface(struct wl_listener *listener, void *data) SessionLock *lock = wl_container_of(listener, lock, new_surface); struct wlr_session_lock_surface_v1 *lock_surface = data; Monitor *m = lock_surface->output->data; - struct wlr_scene_tree *scene_tree = lock_surface->surface->data = - wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); + struct wlr_scene_tree *scene_tree = lock_surface->surface->data + = wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); m->lock_surface = lock_surface; wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); @@ -891,23 +1043,32 @@ createmon(struct wl_listener *listener, void *data) struct wlr_output *wlr_output = data; const MonitorRule *r; size_t i; - Monitor *m = wlr_output->data = ecalloc(1, sizeof(*m)); + struct wlr_output_state state; + Monitor *m; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; + + 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 (i = 0; i < LENGTH(m->layers); i++) wl_list_init(&m->layers[i]); + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ m->tagset[0] = m->tagset[1] = 1; for (r = monrules; r < END(monrules); r++) { if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; 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; - wlr_output_set_transform(wlr_output, r->rr); + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); break; } } @@ -916,20 +1077,16 @@ createmon(struct wl_listener *listener, void *data) * monitor supports only a specific set of modes. We just pick the * 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_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); /* Set up event listeners */ LISTEN(&wlr_output->events.frame, &m->frame, rendermon); LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); + LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); - 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); + wlr_output_state_set_enabled(&state, 1); + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); wl_list_insert(&mons, &m->link); printstatus(); @@ -946,108 +1103,119 @@ createmon(struct wl_listener *listener, void *data) 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. + /* Adds this to the output layout in the order it was configured. * * 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). */ m->scene_output = wlr_scene_output_create(scene, wlr_output); - wlr_output_layout_add_auto(output_layout, wlr_output); + if (m->m.x == -1 && m->m.y == -1) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); } 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, - * 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; + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; Client *c = NULL; - LayerSurface *l = NULL; - - if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { - struct wlr_box box; - int type = toplevel_from_wlr_surface(xdg_surface->surface, &c, &l); - if (!xdg_surface->popup->parent || type < 0) - return; - xdg_surface->surface->data = wlr_scene_xdg_surface_create( - xdg_surface->popup->parent->data, xdg_surface); - if ((l && !l->mon) || (c && !c->mon)) - return; - box = type == LayerShell ? l->mon->m : c->mon->w; - box.x -= (type == LayerShell ? l->geom.x : c->geom.x); - box.y -= (type == LayerShell ? l->geom.y : c->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 = ecalloc(1, sizeof(*c)); - c->surface.xdg = xdg_surface; + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; c->bw = borderpx; - 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); - LISTEN(&xdg_surface->toplevel->events.request_maximize, &c->maximize, - maximizenotify); + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&toplevel->events.destroy, &c->destroy, destroynotify); + LISTEN(&toplevel->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&toplevel->events.request_maximize, &c->maximize, maximizenotify); + LISTEN(&toplevel->events.set_title, &c->set_title, updatetitle); } void createpointer(struct wlr_pointer *pointer) { - if (wlr_input_device_is_libinput(&pointer->base)) { - struct libinput_device *libinput_device = (struct libinput_device*) - wlr_libinput_get_device_handle(&pointer->base); + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { - 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_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); } - if (libinput_device_config_scroll_has_natural_scroll(libinput_device)) - libinput_device_config_scroll_set_natural_scroll_enabled(libinput_device, natural_scrolling); + if (libinput_device_config_scroll_has_natural_scroll(device)) + libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); - if (libinput_device_config_dwt_is_available(libinput_device)) - libinput_device_config_dwt_set_enabled(libinput_device, disable_while_typing); + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(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_left_handed_is_available(device)) + libinput_device_config_left_handed_set(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_middle_emulation_is_available(device)) + libinput_device_config_middle_emulation_set_enabled(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_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method(device, scroll_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_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method(device, click_method); - 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); + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, accel_profile); + libinput_device_config_accel_set_speed(device, accel_speed); } } wlr_cursor_attach_input_device(cursor, &pointer->base); } +void +createpointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = ecalloc(1, sizeof(*pointer_constraint)); + pointer_constraint->constraint = data; + LISTEN(&pointer_constraint->constraint->events.destroy, + &pointer_constraint->destroy, destroypointerconstraint); +} + +void +createpopup(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client (either xdg-shell or layer-shell) + * creates a new popup. */ + struct wlr_xdg_popup *popup = data; + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); +} + +void +cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) +{ + if (active_constraint == constraint) + return; + + if (active_constraint) + wlr_pointer_constraint_v1_send_deactivated(active_constraint); + + active_constraint = constraint; + wlr_pointer_constraint_v1_send_activated(constraint); +} + void cursorframe(struct wl_listener *listener, void *data) { - /* This event is forwarded by the cursor when a pointer emits an frame + /* This event is forwarded by the cursor when a pointer emits a frame * event. Frame events are sent after regular pointer events to group * multiple events together. For instance, two axis events may happen at the * same time, in which case a frame event won't be sent in between. */ @@ -1055,14 +1223,37 @@ cursorframe(struct wl_listener *listener, void *data) wlr_seat_pointer_notify_frame(seat); } +void +cursorwarptohint(void) +{ + Client *c = NULL; + double sx = active_constraint->current.cursor_hint.x; + double sy = active_constraint->current.cursor_hint.y; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->current.cursor_hint.enabled) { + wlr_cursor_warp(cursor, NULL, sx + c->geom.x + c->bw, sy + c->geom.y + c->bw); + wlr_seat_pointer_warp(active_constraint->seat, sx, sy); + } +} + +void +destroydecoration(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, destroy_decoration); + + wl_list_remove(&c->destroy_decoration.link); + wl_list_remove(&c->set_decoration_mode.link); +} + 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(focustop(selmon), 1); - motionnotify(0); + motionnotify(0, NULL, 0, 0, 0, 0); + wl_list_remove(&listener->link); + free(listener); } void @@ -1071,20 +1262,22 @@ 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)); + wl_list_remove(&listener->link); + free(listener); } void destroylayersurfacenotify(struct wl_listener *listener, void *data) { - LayerSurface *layersurface = wl_container_of(listener, layersurface, destroy); + LayerSurface *l = wl_container_of(listener, l, destroy); - 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); - wlr_scene_node_destroy(&layersurface->scene->node); - free(layersurface); + wl_list_remove(&l->link); + wl_list_remove(&l->destroy.link); + wl_list_remove(&l->unmap.link); + wl_list_remove(&l->surface_commit.link); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); } void @@ -1097,7 +1290,7 @@ destroylock(SessionLock *lock, int unlock) wlr_scene_node_set_enabled(&locked_bg->node, 0); focusclient(focustop(selmon), 0); - motionnotify(0); + motionnotify(0, NULL, 0, 0, 0, 0); destroy: wl_list_remove(&lock->new_surface.link); @@ -1118,38 +1311,59 @@ destroylocksurface(struct wl_listener *listener, void *data) m->lock_surface = NULL; wl_list_remove(&m->destroy_lock_surface.link); - if (lock_surface->surface == seat->keyboard_state.focused_surface) { - if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { - surface = wl_container_of(cur_lock->surfaces.next, surface, link); - client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); - } else if (!locked) { - focusclient(focustop(selmon), 1); - } else { - wlr_seat_keyboard_clear_focus(seat); - } + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; + + if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { + focusclient(focustop(selmon), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); } } void destroynotify(struct wl_listener *listener, void *data) { - /* Called when the surface is destroyed and should never be shown again. */ + /* Called when the xdg_toplevel is destroyed. */ Client *c = wl_container_of(listener, c, destroy); - 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 != XDGShell) { - wl_list_remove(&c->configure.link); - wl_list_remove(&c->set_hints.link); wl_list_remove(&c->activate.link); - } + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else #endif + { + wl_list_remove(&c->commit.link); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + wl_list_remove(&c->maximize.link); + } free(c); } +void +destroypointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = wl_container_of(listener, pointer_constraint, destroy); + + if (active_constraint == pointer_constraint->constraint) { + cursorwarptohint(); + active_constraint = NULL; + } + + wl_list_remove(&pointer_constraint->destroy.link); + free(pointer_constraint); +} + void destroysessionlock(struct wl_listener *listener, void *data) { @@ -1158,10 +1372,15 @@ destroysessionlock(struct wl_listener *listener, void *data) } void -destroysessionmgr(struct wl_listener *listener, void *data) +destroykeyboardgroup(struct wl_listener *listener, void *data) { - wl_list_remove(&session_lock_create_lock.link); - wl_list_remove(&session_lock_mgr_destroy.link); + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); + wlr_keyboard_group_destroy(group->wlr_group); + free(group); } Monitor * @@ -1184,7 +1403,9 @@ void focusclient(Client *c, int lift) { struct wlr_surface *old = seat->keyboard_state.focused_surface; - int i; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; if (locked) return; @@ -1196,19 +1417,23 @@ focusclient(Client *c, int lift) if (c && client_surface(c) == old) return; + if ((old_client_type = toplevel_from_wlr_surface(old, &old_c, &old_l)) == XDGShell) { + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &old_c->surface.xdg->popups, link) + wlr_xdg_popup_destroy(popup); + } + /* Put the new client atop the focus stack and select its monitor */ 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); + client_set_border_color(c, focuscolor); } /* Deactivate old client if focus is changing */ @@ -1216,19 +1441,16 @@ focusclient(Client *c, int lift) /* 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. */ - Client *w = NULL; - LayerSurface *l = NULL; - int type = toplevel_from_wlr_surface(old, &w, &l); - if (type == LayerShell && l->scene->node.enabled - && l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) + && old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { return; - } else if (w && w == exclusive_focus && client_wants_focus(w)) { + } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { 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); + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); client_activate_surface(old, 0); } @@ -1242,7 +1464,7 @@ focusclient(Client *c, int lift) } /* Change cursor surface */ - motionnotify(0); + motionnotify(0, NULL, 0, 0, 0, 0); /* Have a client, so focus its top-level wlr_surface */ client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); @@ -1255,10 +1477,11 @@ void focusmon(const Arg *arg) { int i = 0, nmons = wl_list_length(&mons); - if (nmons) + if (nmons) { do /* don't switch to disabled mons */ selmon = dirtomon(arg->i); while (!selmon->wlr_output->enabled && i++ < nmons); + } focusclient(focustop(selmon), 1); } @@ -1267,37 +1490,38 @@ focusstack(const Arg *arg) { /* Focus the next or previous client (in tiling order) on selmon */ Client *c, *sel = focustop(selmon); - if (!sel || sel->isfullscreen) + if (!sel || (sel->isfullscreen && !client_has_children(sel))) return; if (arg->i > 0) { wl_list_for_each(c, &sel->link, link) { if (&c->link == &clients) - continue; /* wrap past the sentinel node */ + continue; /* wrap past the sentinel node */ if (VISIBLEON(c, selmon)) - break; /* found it */ + break; /* found it */ } } else { wl_list_for_each_reverse(c, &sel->link, link) { if (&c->link == &clients) - continue; /* wrap past the sentinel node */ + continue; /* wrap past the sentinel node */ if (VISIBLEON(c, selmon)) - break; /* found it */ + break; /* found it */ } } /* If only one client is visible on selmon, then c == sel */ focusclient(c, 1); } -/* We probably should change the name of this, it sounds like +/* We probably should change the name of this: it sounds like it * will focus the topmost client of this mon, when actually will * only return that client */ Client * focustop(Monitor *m) { Client *c; - wl_list_for_each(c, &fstack, flink) + wl_list_for_each(c, &fstack, flink) { if (VISIBLEON(c, m)) return c; + } return NULL; } @@ -1308,6 +1532,40 @@ fullscreennotify(struct wl_listener *listener, void *data) setfullscreen(c, client_wants_fullscreen(c)); } +void +gpureset(struct wl_listener *listener, void *data) +{ + struct wlr_renderer *old_drw = drw; + struct wlr_allocator *old_alloc = alloc; + struct Monitor *m; + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't recreate renderer"); + + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + + wl_list_remove(&gpu_reset.link); + wl_signal_add(&drw->events.lost, &gpu_reset); + + wlr_compositor_set_renderer(compositor, drw); + + wl_list_for_each(m, &mons, link) { + wlr_output_init_render(m->wlr_output, alloc, drw); + } + + wlr_allocator_destroy(old_alloc); + wlr_renderer_destroy(old_drw); +} + +void +handlesig(int signo) +{ + if (signo == SIGCHLD) + while (waitpid(-1, NULL, WNOHANG) > 0); + else if (signo == SIGINT || signo == SIGTERM) + quit(NULL); +} + void incnmaster(const Arg *arg) { @@ -1342,7 +1600,7 @@ inputdevice(struct wl_listener *listener, void *data) * there are no pointer devices, so we always include that capability. */ /* TODO do we actually require a cursor? */ caps = WL_SEAT_CAPABILITY_POINTER; - if (!wl_list_empty(&keyboards)) + if (!wl_list_empty(&kb_group->wlr_group->devices)) caps |= WL_SEAT_CAPABILITY_KEYBOARD; wlr_seat_set_capabilities(seat, caps); } @@ -1355,16 +1613,15 @@ keybinding(uint32_t mods, xkb_keysym_t sym) * processing keys, rather than passing them on to the client for its own * processing. */ - int handled = 0; const Key *k; for (k = keys; k < END(keys); k++) { - if (CLEANMASK(mods) == CLEANMASK(k->mod) && - sym == k->keysym && k->func) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { k->func(&k->arg); - handled = 1; + return 1; } } - return handled; + return 0; } void @@ -1372,7 +1629,7 @@ 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); + KeyboardGroup *group = wl_container_of(listener, group, key); struct wlr_keyboard_key_event *event = data; /* Translate libinput keycode -> xkbcommon */ @@ -1380,26 +1637,38 @@ keypress(struct wl_listener *listener, void *data) /* 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->wlr_keyboard->xkb_state, keycode, &syms); + group->wlr_group->keyboard.xkb_state, keycode, &syms); int handled = 0; - uint32_t mods = wlr_keyboard_get_modifiers(kb->wlr_keyboard); + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); - IDLE_NOTIFY_ACTIVITY; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); /* On _press_ if there is no active screen locker, * attempt to process a compositor keybinding. */ - if (!locked && !input_inhibit_mgr->active_inhibitor - && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) + if (!locked && 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->wlr_keyboard); - wlr_seat_keyboard_notify_key(seat, event->time_msec, - event->keycode, event->state); } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = syms; + group->nsyms = nsyms; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { + group->nsyms = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + + if (handled) + return; + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); } void @@ -1407,17 +1676,29 @@ keypressmod(struct wl_listener *listener, void *data) { /* This event is raised when a modifier key, such as shift or alt, is * pressed. We simply communicate this to the client. */ - Keyboard *kb = wl_container_of(listener, kb, modifiers); - /* - * A seat can only have one keyboard, but this is a limitation of the - * Wayland protocol - not wlroots. We assign all connected keyboards to the - * same seat. You can swap out the underlying wlr_keyboard like this and - * wlr_seat handles this transparently. - */ - wlr_seat_set_keyboard(seat, kb->wlr_keyboard); + KeyboardGroup *group = wl_container_of(listener, group, modifiers); + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); /* Send modifiers to the client. */ wlr_seat_keyboard_notify_modifiers(seat, - &kb->wlr_keyboard->modifiers); + &group->wlr_group->keyboard.modifiers); +} + +int +keyrepeat(void *data) +{ + KeyboardGroup *group = data; + int i; + if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) + keybinding(group->mods, group->keysyms[i]); + + return 0; } void @@ -1438,13 +1719,12 @@ locksession(struct wl_listener *listener, void *data) wlr_session_lock_v1_destroy(session_lock); return; } - lock = ecalloc(1, sizeof(*lock)); + lock = session_lock->data = ecalloc(1, sizeof(*lock)); focusclient(NULL, 0); lock->scene = wlr_scene_tree_create(layers[LyrBlock]); cur_lock = lock->lock = session_lock; locked = 1; - session_lock->data = lock; LISTEN(&session_lock->events.new_surface, &lock->new_surface, createlocksurface); LISTEN(&session_lock->events.destroy, &lock->destroy, destroysessionlock); @@ -1453,60 +1733,47 @@ locksession(struct wl_listener *listener, void *data) wlr_session_lock_v1_send_locked(session_lock); } -void -maplayersurfacenotify(struct wl_listener *listener, void *data) -{ - LayerSurface *l = wl_container_of(listener, l, map); - wlr_surface_send_enter(l->layer_surface->surface, l->mon->wlr_output); - motionnotify(0); -} - void mapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ - Client *p, *w, *c = wl_container_of(listener, c, map); + Client *p = NULL; + Client *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]); - wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + /* Enabled later by a call to arrange() */ + wlr_scene_node_set_enabled(&c->scene->node, client_is_unmanaged(c)); 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 + client_get_geometry(c, &c->geom); + /* Handle unmanaged clients first so we can return prior create borders */ if (client_is_unmanaged(c)) { - 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); + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + client_set_size(c, c->geom.width, c->geom.height); 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] = wlr_scene_rect_create(c->scene, 0, 0, + c->isurgent ? urgentcolor : bordercolor); c->border[i]->node.data = c; } /* 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; @@ -1516,12 +1783,10 @@ mapnotify(struct wl_listener *listener, void *data) /* 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))) { + * we set the same tags and monitor as its parent. + * If there is no parent, apply rules */ + if ((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); @@ -1530,9 +1795,10 @@ mapnotify(struct wl_listener *listener, void *data) 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 && m == w->mon && (w->tags & c->tags)) + wl_list_for_each(w, &clients, link) { + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) setfullscreen(w, 0); + } } void @@ -1542,21 +1808,31 @@ maximizenotify(struct wl_listener *listener, void *data) * typically because the user clicked on the maximize button on * client-side decorations. dwl doesn't support maximization, but * to conform to xdg-shell protocol we still must send a configure. + * Since xdg-shell protocol v5 we should ignore request of unsupported + * capabilities, just schedule a empty configure when the client uses <5 + * protocol version * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ Client *c = wl_container_of(listener, c, maximize); - wlr_xdg_surface_schedule_configure(c->surface.xdg); + if (c->surface.xdg->initialized + && wl_resource_get_version(c->surface.xdg->toplevel->resource) + < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + wlr_xdg_surface_schedule_configure(c->surface.xdg); } void monocle(Monitor *m) { Client *c; + int n = 0; wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) continue; resize(c, m->w, 0); + n++; } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); if ((c = focustop(m))) wlr_scene_node_raise_to_top(&c->scene->node); } @@ -1568,66 +1844,94 @@ motionabsolute(struct wl_listener *listener, void *data) * motion event, from 0..1 on each axis. This happens, for example, when * wlroots is running under a Wayland window rather than KMS+DRM, and you * 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. */ + * so we have to warp the mouse there. Also, some hardware emits these events. */ struct wlr_pointer_motion_absolute_event *event = data; - wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); - motionnotify(event->time_msec); + double lx, ly, dx, dy; + + if (!event->time_msec) /* this is 0 with virtual pointers */ + wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); + + wlr_cursor_absolute_to_layout_coords(cursor, &event->pointer->base, event->x, event->y, &lx, &ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); } void -motionnotify(uint32_t time) +motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) { - double sx = 0, sy = 0; + double sx = 0, sy = 0, sx_confined, sy_confined; Client *c = NULL, *w = NULL; LayerSurface *l = NULL; - int type; struct wlr_surface *surface = NULL; - struct wlr_drag_icon *icon; + struct wlr_pointer_constraint_v1 *constraint; + + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag + && surface != seat->pointer_state.focused_surface + && toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->scene->node.x : w->geom.x); + sy = cursor->y - (l ? l->scene->node.y : w->geom.y); + } /* time is 0 in internal calls meant to restore pointer focus. */ if (time) { - IDLE_NOTIFY_ACTIVITY; + wlr_relative_pointer_manager_v1_send_relative_motion( + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + + wl_list_for_each(constraint, &pointer_constraints->constraints, link) + cursorconstrain(constraint); + + if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->surface == seat->pointer_state.focused_surface) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + + if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + return; + } + } + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); /* 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); + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + /* If we are currently grabbing the mouse, handle and return */ if (cursor_mode == CurMove) { /* Move the grabbed client to the new position. */ - resize(grabc, (struct wlr_box){.x = cursor->x - grabcx, .y = cursor->y - grabcy, + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, .width = grabc->geom.width, .height = grabc->geom.height}, 1); return; } else if (cursor_mode == CurResize) { 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); + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); return; } - /* Find the client under the pointer and send the event along. */ - xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); - - if (cursor_mode == CurPressed && !seat->drag) { - if ((type = toplevel_from_wlr_surface( - seat->pointer_state.focused_surface, &w, &l)) >= 0) { - c = w; - surface = seat->pointer_state.focused_surface; - sx = cursor->x - (type == LayerShell ? l->geom.x : w->geom.x); - sy = cursor->y - (type == LayerShell ? l->geom.y : w->geom.y); - } - } - /* 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 && !seat->drag && (!cursor_image || strcmp(cursor_image, "left_ptr"))) - wlr_xcursor_manager_set_cursor_image(cursor_mgr, (cursor_image = "left_ptr"), cursor); + if (!surface && !seat->drag) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); pointerfocus(c, surface, sx, sy, time); } @@ -1643,8 +1947,8 @@ motionrelative(struct wl_listener *listener, void *data) * 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->pointer->base, event->delta_x, event->delta_y); - motionnotify(event->time_msec); + motionnotify(event->time_msec, &event->pointer->base, event->delta_x, event->delta_y, + event->unaccel_dx, event->unaccel_dy); } void @@ -1660,9 +1964,9 @@ moveresize(const Arg *arg) setfloating(grabc, 1); switch (cursor_mode = arg->ui) { case CurMove: - grabcx = cursor->x - grabc->geom.x; - grabcy = cursor->y - grabc->geom.y; - wlr_xcursor_manager_set_cursor_image(cursor_mgr, (cursor_image = "fleur"), cursor); + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); break; case CurResize: /* Doesn't work for X11 output - the next absolute motion event @@ -1670,8 +1974,7 @@ moveresize(const Arg *arg) wlr_cursor_warp_closest(cursor, NULL, grabc->geom.x + grabc->geom.width, grabc->geom.y + grabc->geom.height); - wlr_xcursor_manager_set_cursor_image(cursor_mgr, - (cursor_image = "bottom_right_corner"), cursor); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); break; } } @@ -1688,7 +1991,7 @@ outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) { /* * Called when a client such as wlr-randr requests a change in output - * configuration. This is only one way that the layout can be changed, + * configuration. This is only one way that the layout can be changed, * so any Monitor information should be updated by updatemons() after an * output_layout.change event, not here. */ @@ -1698,35 +2001,42 @@ 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; + struct wlr_output_state state; - wlr_output_enable(wlr_output, config_head->state.enabled); + /* Ensure displays previously disabled by wlr-output-power-management-v1 + * are properly handled*/ + m->asleep = 0; + + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, config_head->state.enabled); if (!config_head->state.enabled) goto apply_or_test; + if (config_head->state.mode) - wlr_output_set_mode(wlr_output, config_head->state.mode); + wlr_output_state_set_mode(&state, config_head->state.mode); else - wlr_output_set_custom_mode(wlr_output, + wlr_output_state_set_custom_mode(&state, 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_enable_adaptive_sync(wlr_output, + wlr_output_state_set_transform(&state, config_head->state.transform); + wlr_output_state_set_scale(&state, config_head->state.scale); + wlr_output_state_set_adaptive_sync_enabled(&state, config_head->state.adaptive_sync_enabled); apply_or_test: - if (test) { - ok &= wlr_output_test(wlr_output); - wlr_output_rollback(wlr_output); - } else { - ok &= wlr_output_commit(wlr_output); - } + ok &= test ? wlr_output_test_state(wlr_output, &state) + : wlr_output_commit_state(wlr_output, &state); + + /* Don't move monitors if position wouldn't change. This avoids + * wlroots marking the output as manually configured. + * wlr_output_layout_add does not like disabled outputs */ + if (!test && wlr_output->enabled && (m->m.x != config_head->state.x || m->m.y != config_head->state.y)) + wlr_output_layout_add(output_layout, wlr_output, + config_head->state.x, config_head->state.y); + + wlr_output_state_finish(&state); } if (ok) @@ -1735,7 +2045,7 @@ apply_or_test: wlr_output_configuration_v1_send_failed(config); wlr_output_configuration_v1_destroy(config); - /* TODO: use a wrapper function? */ + /* https://codeberg.org/dwl/dwl/issues/577 */ updatemons(NULL, NULL); } @@ -1751,9 +2061,9 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) { struct timespec now; - int internal_call = !time; - if (sloppyfocus && !internal_call && c && !client_is_unmanaged(c)) + if (surface != seat->pointer_state.focused_surface && + sloppyfocus && time && c && !client_is_unmanaged(c)) focusclient(c, 0); /* If surface is NULL, clear pointer focus */ @@ -1762,7 +2072,7 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, return; } - if (internal_call) { + if (!time) { clock_gettime(CLOCK_MONOTONIC, &now); time = now.tv_sec * 1000 + now.tv_nsec / 1000000; } @@ -1779,7 +2089,7 @@ printstatus(void) { Monitor *m = NULL; Client *c; - unsigned int occ, urg, sel; + uint32_t occ, urg, sel; wl_list_for_each(m, &mons, link) { occ = urg = 0; @@ -1792,36 +2102,50 @@ printstatus(void) } 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); + printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); + printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); + printf("%s floating %d\n", m->wlr_output->name, c->isfloating); sel = c->tags; } else { printf("%s title \n", m->wlr_output->name); + printf("%s appid \n", m->wlr_output->name); printf("%s fullscreen \n", m->wlr_output->name); printf("%s floating \n", m->wlr_output->name); sel = 0; } printf("%s selmon %u\n", m->wlr_output->name, m == selmon); - printf("%s tags %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); + printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", + m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); + printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); } fflush(stdout); } +void +powermgrsetmode(struct wl_listener *listener, void *data) +{ + struct wlr_output_power_v1_set_mode_event *event = data; + struct wlr_output_state state = {0}; + Monitor *m = event->output->data; + + if (!m) + return; + + m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the ouput */ + wlr_output_state_set_enabled(&state, event->mode); + wlr_output_commit_state(m->wlr_output, &state); + + m->asleep = !event->mode; + updatemons(NULL, NULL); +} + void quit(const Arg *arg) { wl_display_terminate(dpy); } -void -quitsignal(int signo) -{ - quit(NULL); -} - void rendermon(struct wl_listener *listener, void *data) { @@ -1829,19 +2153,32 @@ rendermon(struct wl_listener *listener, void *data) * generally at the output's refresh rate (e.g. 60Hz). */ Monitor *m = wl_container_of(listener, m, frame); Client *c; + struct wlr_output_state pending = {0}; struct timespec now; /* Render if no XDG clients have an outstanding resize and are visible on * this monitor. */ - wl_list_for_each(c, &clients, link) + wl_list_for_each(c, &clients, link) { if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) goto skip; - if (!wlr_scene_output_commit(m->scene_output)) - return; + } + + wlr_scene_output_commit(m->scene_output, NULL); + skip: /* Let clients know a frame has been rendered */ clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); +} + +void +requestdecorationmode(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_decoration_mode); + if (c->surface.xdg->initialized) + wlr_xdg_toplevel_decoration_v1_set_mode(c->decoration, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } void @@ -1856,10 +2193,25 @@ requeststartdrag(struct wl_listener *listener, void *data) wlr_data_source_destroy(event->drag->source); } +void +requestmonstate(struct wl_listener *listener, void *data) +{ + struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(event->output, event->state); + updatemons(NULL, NULL); +} + void resize(Client *c, struct wlr_box geo, int interact) { - struct wlr_box *bbox = interact ? &sgeom : &c->mon->w; + struct wlr_box *bbox; + struct wlr_box clip; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + client_set_bounds(c, geo.width, geo.height); c->geom = geo; applybounds(c, bbox); @@ -1878,6 +2230,8 @@ resize(Client *c, struct wlr_box geo, int interact) /* this is 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); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); } void @@ -1902,6 +2256,7 @@ run(char *startup_cmd) if ((child_pid = fork()) < 0) die("startup: fork:"); if (child_pid == 0) { + setsid(); dup2(piperw[0], STDIN_FILENO); close(piperw[0]); close(piperw[1]); @@ -1912,8 +2267,14 @@ run(char *startup_cmd) close(piperw[1]); close(piperw[0]); } - /* If nobody is reading the status output, don't terminate */ - signal(SIGPIPE, SIG_IGN); + + /* Mark stdout as non-blocking to avoid the startup script + * causing dwl to freeze when a user neither closes stdin + * nor consumes standard input in his startup script */ + + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + printstatus(); /* At this point the outputs are initialized, choose initial selmon based on @@ -1921,11 +2282,11 @@ run(char *startup_cmd) selmon = xytomon(cursor->x, cursor->y); /* 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 + * 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 */ wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); - wlr_xcursor_manager_set_cursor_image(cursor_mgr, cursor_image, cursor); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); /* Run the Wayland event loop. This does not return until you exit the * compositor. Starting the backend rigged up all of the necessary event @@ -1944,8 +2305,7 @@ setcursor(struct wl_listener *listener, void *data) * 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 + /* This can be sent by any client, so we check to make sure this one * 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 * hardware cursor on the output that it's currently on and continue to @@ -1955,11 +2315,31 @@ setcursor(struct wl_listener *listener, void *data) event->hotspot_x, event->hotspot_y); } +void +setcursorshape(struct wl_listener *listener, void *data) +{ + struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* This can be sent by any client, so we check to make sure this one + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided cursor shape. */ + if (event->seat_client == seat->pointer_state.focused_client) + wlr_cursor_set_xcursor(cursor, cursor_mgr, + wlr_cursor_shape_v1_name(event->shape)); +} + void setfloating(Client *c, int floating) { + Client *p = client_get_parent(c); c->isfloating = floating; - wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrFloat : LyrTile]); + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); arrange(c->mon); printstatus(); } @@ -1968,11 +2348,11 @@ void setfullscreen(Client *c, int fullscreen) { c->isfullscreen = fullscreen; - if (!c->mon) + if (!c->mon || !client_surface(c)->mapped) return; c->bw = fullscreen ? 0 : borderpx; client_set_fullscreen(c, fullscreen); - wlr_scene_node_reparent(&c->scene->node, layers[fullscreen + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); if (fullscreen) { @@ -1996,7 +2376,7 @@ setlayout(const Arg *arg) selmon->sellt ^= 1; if (arg && arg->v) selmon->lt[selmon->sellt] = (Layout *)arg->v; - /* TODO change layout symbol? */ + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); arrange(selmon); printstatus(); } @@ -2009,7 +2389,7 @@ setmfact(const Arg *arg) if (!arg || !selmon || !selmon->lt[selmon->sellt]->arrange) return; - f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; if (f < 0.1 || f > 0.9) return; selmon->mfact = f; @@ -2017,7 +2397,7 @@ setmfact(const Arg *arg) } void -setmon(Client *c, Monitor *m, unsigned int newtags) +setmon(Client *c, Monitor *m, uint32_t newtags) { Monitor *oldmon = c->mon; @@ -2026,17 +2406,15 @@ setmon(Client *c, Monitor *m, unsigned int newtags) c->mon = m; c->prev = c->geom; - /* TODO leave/enter is not optimal but works */ - if (oldmon) { - wlr_surface_send_leave(client_surface(c), oldmon->wlr_output); + /* Scene graph sends surface leave/enter events on move and resize */ + if (oldmon) arrange(oldmon); - } if (m) { /* Make sure window actually overlaps with the monitor */ 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 */ setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); } focusclient(focustop(selmon), 1); } @@ -2046,7 +2424,7 @@ setpsel(struct wl_listener *listener, void *data) { /* This event is raised by the seat when a client wants to set the selection, * usually when the user copies something. wlroots allows compositors to - * ignore such requests if they so choose, but in dwl we always honor + * ignore such requests if they so choose, but in dwl we always honor them */ struct wlr_seat_request_set_primary_selection_event *event = data; wlr_seat_set_primary_selection(seat, event->source, event->serial); @@ -2057,7 +2435,7 @@ setsel(struct wl_listener *listener, void *data) { /* This event is raised by the seat when a client wants to set the selection, * usually when the user copies something. wlroots allows compositors to - * ignore such requests if they so choose, but in dwl we always honor + * ignore such requests if they so choose, but in dwl we always honor them */ struct wlr_seat_request_set_selection_event *event = data; wlr_seat_set_selection(seat, event->source, event->serial); @@ -2066,48 +2444,64 @@ setsel(struct wl_listener *listener, void *data) void setup(void) { + int drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + + wlr_log_init(log_level, NULL); + /* 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(); - - /* Set up signal handlers */ -#ifdef XWAYLAND - sigchld(0); -#else - signal(SIGCHLD, SIG_IGN); -#endif - signal(SIGINT, quitsignal); - signal(SIGTERM, quitsignal); + event_loop = wl_display_get_event_loop(dpy); /* The backend is a wlroots feature which abstracts the underlying input and * output hardware. The autocreate option will choose the most suitable * backend based on the current environment, such as opening an X11 window - * if an X11 server is running. The NULL argument here optionally allows you - * to pass in a custom renderer if wlr_renderer doesn't meet your needs. The - * backend uses the renderer, for example, to fall back to software cursors - * if the backend does not support hardware cursors (some older GPUs - * don't). */ - if (!(backend = wlr_backend_autocreate(dpy))) + * if an X11 server is running. */ + if (!(backend = wlr_backend_autocreate(event_loop, &session))) die("couldn't create 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); - layers[LyrBlock] = wlr_scene_tree_create(&scene->tree); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); - /* Create a renderer with the default implementation */ + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ if (!(drw = wlr_renderer_autocreate(backend))) die("couldn't create renderer"); - wlr_renderer_init_wl_display(drw, dpy); + wl_signal_add(&drw->events.lost, &gpu_reset); - /* Create a default allocator */ + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is to call: + * wlr_renderer_init_wl_display(drw); + * but we need to create the linux_dmabuf interface manually to integrate it + * with wlr_scene. */ + wlr_renderer_init_wl_shm(drw, dpy); + + if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { + wlr_drm_create(dpy, drw); + wlr_scene_set_linux_dmabuf_v1(scene, + wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + } + + if ((drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && drw->features.timeline + && backend->features.timeline) + wlr_linux_drm_syncobj_manager_v1_create(dpy, 1, drm_fd); + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ if (!(alloc = wlr_allocator_autocreate(backend, drw))) die("couldn't create allocator"); @@ -2117,33 +2511,41 @@ setup(void) * to dig your fingers in and play with their behavior if you want. Note that * the clients cannot set the selection directly without compositor approval, * see the setsel() function. */ - compositor = wlr_compositor_create(dpy, drw); + compositor = wlr_compositor_create(dpy, 6, drw); + wlr_subcompositor_create(dpy); + wlr_data_device_manager_create(dpy); 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); wlr_viewporter_create(dpy); wlr_single_pixel_buffer_manager_v1_create(dpy); - wlr_subcompositor_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); + wlr_presentation_create(dpy, backend, 2); + wlr_alpha_modifier_v1_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 + wlr_scene_set_gamma_control_manager_v1(scene, wlr_gamma_control_manager_v1_create(dpy)); + + power_mgr = wlr_output_power_manager_v1_create(dpy); + wl_signal_add(&power_mgr->events.set_mode, &output_power_mgr_set_mode); + + /* Creates an output layout, which is a wlroots utility for working with an * arrangement of screens in a physical layout. */ - output_layout = wlr_output_layout_create(); + output_layout = wlr_output_layout_create(dpy); wl_signal_add(&output_layout->events.change, &layout_change); - wlr_xdg_output_manager_v1_create(dpy, output_layout); + + wlr_xdg_output_manager_v1_create(dpy, output_layout); /* Configure a listener to be notified when new outputs are available on the * backend. */ wl_list_init(&mons); wl_signal_add(&backend->events.new_output, &new_output); - /* Set up our client lists and the xdg-shell. The xdg-shell is a + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a * Wayland protocol which is used for application windows. For more * detail on shells, refer to the article: * @@ -2152,24 +2554,22 @@ setup(void) wl_list_init(&clients); wl_list_init(&fstack); - idle = wlr_idle_create(dpy); + xdg_shell = wlr_xdg_shell_create(dpy, 6); + wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel); + wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); + wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); + 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); + wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &new_idle_inhibitor); - 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, 4); - wl_signal_add(&xdg_shell->events.new_surface, &new_xdg_surface); - - input_inhibit_mgr = wlr_input_inhibit_manager_create(dpy); session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); - wl_signal_add(&session_lock_mgr->events.new_lock, &session_lock_create_lock); - wl_signal_add(&session_lock_mgr->events.destroy, &session_lock_mgr_destroy); + wl_signal_add(&session_lock_mgr->events.new_lock, &new_session_lock); locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, - (float [4]){0.1, 0.1, 0.1, 1.0}); + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); wlr_scene_node_set_enabled(&locked_bg->node, 0); /* Use decoration protocols to negotiate server-side decorations */ @@ -2179,6 +2579,11 @@ setup(void) xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); wl_signal_add(&xdg_decoration_mgr->events.new_toplevel_decoration, &new_xdg_decoration); + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); + wl_signal_add(&pointer_constraints->events.new_constraint, &new_pointer_constraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + /* * Creates a cursor, which is a wlroots utility for tracking the cursor * image shown on screen. @@ -2191,15 +2596,14 @@ setup(void) * images are available at all scale factors on the screen (necessary for * HiDPI support). Scaled cursors will be loaded with each output. */ cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + setenv("XCURSOR_SIZE", "24", 1); /* * wlr_cursor *only* displays an image on screen. It does not move around * when the pointer moves. However, we can attach input devices to it, and * it will generate aggregate events for all of them. In these events, we * can choose how we want to process them, forwarding them to clients and - * moving the cursor around. More detail on this process is described in my - * input handling blog post: - * + * moving the cursor around. More detail on this process is described in * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html * * And more comments are sprinkled throughout the notify functions above. @@ -2210,17 +2614,23 @@ setup(void) wl_signal_add(&cursor->events.axis, &cursor_axis); wl_signal_add(&cursor->events.frame, &cursor_frame); + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); + /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, * pointer, touch, and drawing tablet device. We also rig up a listener to * let us know when new input devices are available on the backend. */ - wl_list_init(&keyboards); - wl_signal_add(&backend->events.new_input, &new_input); + wl_signal_add(&backend->events.new_input, &new_input_device); virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard, &new_virtual_keyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + wl_signal_add(&virtual_pointer_mgr->events.new_virtual_pointer, + &new_virtual_pointer); + 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); @@ -2228,19 +2638,23 @@ setup(void) wl_signal_add(&seat->events.request_start_drag, &request_start_drag); wl_signal_add(&seat->events.start_drag, &start_drag); + kb_group = createkeyboardgroup(); + wl_list_init(&kb_group->destroy.link); + 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)); - + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ + unsetenv("DISPLAY"); #ifdef XWAYLAND /* * Initialise the XWayland X server. * It will be started when the first X client is started. */ - xwayland = wlr_xwayland_create(dpy, compositor, 1); - if (xwayland) { + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { wl_signal_add(&xwayland->events.ready, &xwayland_ready); wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); @@ -2266,24 +2680,23 @@ 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); + drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); } void tag(const Arg *arg) { Client *sel = focustop(selmon); - if (sel && arg->ui & TAGMASK) { - sel->tags = arg->ui & TAGMASK; - focusclient(focustop(selmon), 1); - arrange(selmon); - } + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); printstatus(); } @@ -2298,7 +2711,8 @@ tagmon(const Arg *arg) void tile(Monitor *m) { - unsigned int i, n = 0, mw, my, ty; + unsigned int mw, my, ty; + int i, n = 0; Client *c; wl_list_for_each(c, &clients, link) @@ -2308,7 +2722,7 @@ tile(Monitor *m) return; if (n > m->nmaster) - mw = m->nmaster ? m->w.width * m->mfact : 0; + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; else mw = m->w.width; i = my = ty = 0; @@ -2348,29 +2762,27 @@ togglefullscreen(const Arg *arg) void toggletag(const Arg *arg) { - unsigned int newtags; + uint32_t newtags; Client *sel = focustop(selmon); - if (!sel) + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) return; - newtags = sel->tags ^ (arg->ui & TAGMASK); - if (newtags) { - sel->tags = newtags; - focusclient(focustop(selmon), 1); - arrange(selmon); - } + + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); printstatus(); } void toggleview(const Arg *arg) { - unsigned int newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0; + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; - if (newtagset) { - selmon->tagset[selmon->seltags] = newtagset; - focusclient(focustop(selmon), 1); - arrange(selmon); - } + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); printstatus(); } @@ -2384,19 +2796,17 @@ unlocksession(struct wl_listener *listener, void *data) void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { - LayerSurface *layersurface = wl_container_of(listener, layersurface, unmap); + LayerSurface *l = wl_container_of(listener, l, unmap); - layersurface->mapped = 0; - wlr_scene_node_set_enabled(&layersurface->scene->node, 0); - if (layersurface == exclusive_focus) + l->mapped = 0; + wlr_scene_node_set_enabled(&l->scene->node, 0); + if (l == 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) + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) focusclient(focustop(selmon), 1); - motionnotify(0); + motionnotify(0, NULL, 0, 0, 0, 0); } void @@ -2410,20 +2820,19 @@ unmapnotify(struct wl_listener *listener, void *data) } if (client_is_unmanaged(c)) { - if (c == exclusive_focus) + if (c == exclusive_focus) { exclusive_focus = NULL; - if (client_surface(c) == seat->keyboard_state.focused_surface) focusclient(focustop(selmon), 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); + motionnotify(0, NULL, 0, 0, 0, 0); } void @@ -2431,38 +2840,41 @@ updatemons(struct wl_listener *listener, void *data) { /* * Called whenever the output layout changes: adding or removing a - * monitor, changing an output's mode or position, etc. This is where + * monitor, changing an output's mode or position, etc. This is where * the change officially happens and we update geometry, window * positions, focus, and the stored configuration in wlroots' * output-manager implementation. */ - struct wlr_output_configuration_v1 *config = - wlr_output_configuration_v1_create(); + struct wlr_output_configuration_v1 *config + = wlr_output_configuration_v1_create(); Client *c; struct wlr_output_configuration_head_v1 *config_head; Monitor *m; /* First remove from the layout the disabled monitors */ wl_list_for_each(m, &mons, link) { - if (m->wlr_output->enabled) + if (m->wlr_output->enabled || m->asleep) 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)); + m->m = m->w = (struct wlr_box){0}; } /* Insert outputs that need to */ - wl_list_for_each(m, &mons, link) + 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); + wlr_scene_node_set_position(&root_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(root_bg, sgeom.width, sgeom.height); + /* Make sure the clients are hidden when dwl is locked */ wlr_scene_node_set_position(&locked_bg->node, sgeom.x, sgeom.y); wlr_scene_rect_set_size(locked_bg, sgeom.width, sgeom.height); @@ -2473,8 +2885,8 @@ updatemons(struct wl_listener *listener, void *data) config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); /* Get the effective monitor geometry to use for surfaces */ - 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_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); @@ -2483,30 +2895,49 @@ updatemons(struct wl_listener *listener, void *data) if (m->lock_surface) { struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); - wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, - m->m.height); + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); } /* Calculate the effective monitor geometry to use for clients */ arrangelayers(m); /* Don't move clients to the left output when plugging monitors */ arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ + m->gamma_lut_changed = 1; - 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 = m; + } } if (selmon && selmon->wlr_output->enabled) { - wl_list_for_each(c, &clients, link) - if (!c->mon && client_is_mapped(c)) + wl_list_for_each(c, &clients, link) { + if (!c->mon && client_surface(c)->mapped) setmon(c, selmon, c->tags); - if (selmon->lock_surface) + } + focusclient(focustop(selmon), 1); + if (selmon->lock_surface) { client_notify_enter(selmon->lock_surface->surface, wlr_seat_get_keyboard(seat)); + client_activate_surface(selmon->lock_surface->surface, 1); + } } + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a + * wl_pointer.motion event for the clients, it's only the image what it's + * at the wrong position after all. */ + wlr_cursor_move(cursor, NULL, 0, 0); + wlr_output_manager_v1_set_configuration(output_mgr, config); } @@ -2524,10 +2955,14 @@ urgent(struct wl_listener *listener, void *data) struct wlr_xdg_activation_v1_request_activate_event *event = data; Client *c = NULL; toplevel_from_wlr_surface(event->surface, &c, NULL); - if (c && c != focustop(selmon)) { - c->isurgent = 1; - printstatus(); - } + if (!c || c == focustop(selmon)) + return; + + c->isurgent = 1; + printstatus(); + + if (client_surface(c)->mapped) + client_set_border_color(c, urgentcolor); } void @@ -2546,8 +2981,26 @@ view(const Arg *arg) void virtualkeyboard(struct wl_listener *listener, void *data) { - struct wlr_virtual_keyboard_v1 *keyboard = data; - createkeyboard(&keyboard->keyboard); + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ + KeyboardGroup *group = createkeyboardgroup(); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); + LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +} + +void +virtualpointer(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_pointer_v1_new_pointer_event *event = data; + struct wlr_input_device *device = &event->new_pointer->pointer.base; + + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); } Monitor * @@ -2557,7 +3010,7 @@ xytomon(double x, double y) return o ? o->data : NULL; } -struct wlr_scene_node * +void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny) { @@ -2565,30 +3018,27 @@ xytonode(double x, double y, struct wlr_surface **psurface, struct wlr_surface *surface = NULL; Client *c = NULL; LayerSurface *l = NULL; - const int *layer; - int focus_order[] = { LyrBlock, LyrOverlay, LyrTop, LyrFS, LyrFloat, LyrTile, LyrBottom, LyrBg }; + int layer; - 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; - } + for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + + if (node->type == WLR_SCENE_NODE_BUFFER) + surface = wlr_scene_surface_try_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 @@ -2601,12 +3051,13 @@ zoom(const Arg *arg) /* Search for the first tiled window that is not sel, marking sel as * NULL if we pass it along the way */ - wl_list_for_each(c, &clients, link) + wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, selmon) && !c->isfloating) { if (c != sel) break; sel = NULL; } + } /* Return if no other tiled window was found */ if (&c->link == &clients) @@ -2630,22 +3081,42 @@ activatex11(struct wl_listener *listener, void *data) Client *c = wl_container_of(listener, c, activate); /* Only "managed" windows can be activated */ - if (c->type == X11Managed) + if (!client_is_unmanaged(c)) wlr_xwayland_surface_activate(c->surface.xwayland, 1); } +void +associatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, associate); + + LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); + LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); +} + void configurex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, configure); struct wlr_xwayland_surface_configure_event *event = data; - if (!c->mon) + if (!client_surface(c) || !client_surface(c)->mapped) { + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); 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 + } + if (client_is_unmanaged(c)) { + wlr_scene_node_set_position(&c->scene->node, event->x, event->y); + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if ((c->isfloating && c != grabc) || !c->mon->lt[c->mon->sellt]->arrange) { + resize(c, (struct wlr_box){.x = event->x - c->bw, + .y = event->y - c->bw, .width = event->width + c->bw * 2, + .height = event->height + c->bw * 2}, 0); + } else { arrange(c->mon); + } } void @@ -2657,91 +3128,57 @@ createnotifyx11(struct wl_listener *listener, void *data) /* Allocate a Client for this surface */ c = xsurface->data = ecalloc(1, sizeof(*c)); c->surface.xwayland = xsurface; - c->type = xsurface->override_redirect ? X11Unmanaged : X11Managed; - c->bw = borderpx; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; /* Listen to the various events it can emit */ - LISTEN(&xsurface->events.map, &c->map, mapnotify); - LISTEN(&xsurface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&xsurface->events.associate, &c->associate, associatex11); + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); LISTEN(&xsurface->events.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 -getatom(xcb_connection_t *xc, const char *name) +void +dissociatex11(struct wl_listener *listener, void *data) { - Atom atom = 0; - xcb_intern_atom_reply_t *reply; - 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); - - return atom; + Client *c = wl_container_of(listener, c, dissociate); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); } void sethints(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, set_hints); - if (c != focustop(selmon)) { - c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); - printstatus(); - } -} + struct wlr_surface *surface = client_surface(c); + if (c == focustop(selmon) || !c->surface.xwayland->hints) + return; -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); + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + printstatus(); + + if (c->isurgent && surface && surface->mapped) + client_set_border_color(c, urgentcolor); } void xwaylandready(struct wl_listener *listener, void *data) { struct wlr_xcursor *xcursor; - xcb_connection_t *xc = xcb_connect(xwayland->display_name, NULL); - int err = xcb_connection_has_error(xc); - if (err) { - fprintf(stderr, "xcb_connect to X server failed with code %d\n. Continuing with degraded functionality.\n", err); - return; - } - - /* Collect atoms we are interested in. If getatom returns 0, we will - * not detect that window type. */ - netatom[NetWMWindowTypeDialog] = getatom(xc, "_NET_WM_WINDOW_TYPE_DIALOG"); - netatom[NetWMWindowTypeSplash] = getatom(xc, "_NET_WM_WINDOW_TYPE_SPLASH"); - netatom[NetWMWindowTypeToolbar] = getatom(xc, "_NET_WM_WINDOW_TYPE_TOOLBAR"); - netatom[NetWMWindowTypeUtility] = getatom(xc, "_NET_WM_WINDOW_TYPE_UTILITY"); /* assign the one and only seat */ wlr_xwayland_set_seat(xwayland, seat); /* Set the default XWayland cursor to match the rest of dwl. */ - if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "left_ptr", 1))) + if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "default", 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); } #endif @@ -2751,9 +3188,11 @@ main(int argc, char *argv[]) char *startup_cmd = NULL; int c; - while ((c = getopt(argc, argv, "s:hv")) != -1) { + while ((c = getopt(argc, argv, "s:hdv")) != -1) { if (c == 's') startup_cmd = optarg; + else if (c == 'd') + log_level = WLR_DEBUG; else if (c == 'v') die("dwl " VERSION); else @@ -2771,5 +3210,5 @@ main(int argc, char *argv[]) return EXIT_SUCCESS; usage: - die("Usage: %s [-v] [-s startup command]", argv[0]); + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); } diff --git a/dwl.desktop b/dwl.desktop new file mode 100644 index 0000000..e1380f7 --- /dev/null +++ b/dwl.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=dwl +Comment=dwm for Wayland +Exec=dwl +Type=Application diff --git a/protocols/wlr-output-power-management-unstable-v1.xml b/protocols/wlr-output-power-management-unstable-v1.xml new file mode 100644 index 0000000..a977839 --- /dev/null +++ b/protocols/wlr-output-power-management-unstable-v1.xml @@ -0,0 +1,128 @@ + + + + Copyright © 2019 Purism SPC + + 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 (including the next + paragraph) 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. + + + + This protocol allows clients to control power management modes + of outputs that are currently part of the compositor space. The + intent is to allow special clients like desktop shells to power + down outputs when the system is idle. + + To modify outputs not currently part of the compositor space see + wlr-output-management. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-output power + management mode controls. + + + + + Create a output power management mode control that can be used to + adjust the power management mode for a given output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object offers requests to set the power management mode of + an output. + + + + + + + + + + + + + + Set an output's power save mode to the given mode. The mode change + is effective immediately. If the output does not support the given + mode a failed event is sent. + + + + + + + Report the power management mode change of an output. + + The mode event is sent after an output changed its power + management mode. The reason can be a client using set_mode or the + compositor deciding to change an output's mode. + This event is also sent immediately when the object is created + so the client is informed about the current power management mode. + + + + + + + This event indicates that the output power management mode control + is no longer valid. This can happen for a number of reasons, + including: + - The output doesn't support power management + - Another client already has exclusive power management mode control + for this output + - The output disappeared + + Upon receiving this event, the client should destroy this object. + + + + + + Destroys the output power management mode control object. + + + + diff --git a/util.c b/util.c index cca7c19..b925987 100644 --- a/util.c +++ b/util.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "util.h" @@ -33,3 +34,18 @@ ecalloc(size_t nmemb, size_t size) die("calloc:"); return p; } + +int +fd_set_nonblock(int fd) { + int flags = fcntl(fd, F_GETFL); + if (flags < 0) { + perror("fcntl(F_GETFL):"); + return -1; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("fcntl(F_SETFL):"); + return -1; + } + + return 0; +} diff --git a/util.h b/util.h index 4c94117..226980d 100644 --- a/util.h +++ b/util.h @@ -2,3 +2,4 @@ void die(const char *fmt, ...); void *ecalloc(size_t nmemb, size_t size); +int fd_set_nonblock(int fd);