├── .gitignore ├── dwl.desktop ├── util.h ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── enhancement-idea.md ├── config.mk ├── util.c ├── LICENSE.sway ├── LICENSE.dwm ├── Makefile ├── CHANGELOG.md ├── dwl.1 ├── LICENSE.tinywl ├── README.md ├── config.def.h ├── client.h ├── protocols └── wlr-layer-shell-unstable-v1.xml ├── LICENSE └── dwl.c /.gitignore: -------------------------------------------------------------------------------- 1 | dwl 2 | *.o 3 | *-protocol.c 4 | *-protocol.h 5 | .ccls-cache 6 | config.h 7 | -------------------------------------------------------------------------------- /dwl.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=dwl 3 | Comment=dwm for Wayland 4 | Exec=dwl 5 | Type=Application 6 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE.dwm file for copyright and license details. */ 2 | 3 | void die(const char *fmt, ...); 4 | void *ecalloc(size_t nmemb, size_t size); 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something in dwl isn't working correctly 4 | title: '' 5 | labels: 'A: bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### ⚠️ Migrated to Codeberg 11 | 12 | This project has [migrated to Codeberg](https://codeberg.org/dwl/dwl). 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement-idea.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement idea 3 | about: Suggest a feature or improvement 4 | title: '' 5 | labels: 'A: enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### ⚠️ Migrated to Codeberg 11 | 12 | This project has [migrated to Codeberg](https://codeberg.org/dwl/dwl). 13 | 14 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | _VERSION = 0.5 2 | VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)` 3 | 4 | PKG_CONFIG = pkg-config 5 | 6 | # paths 7 | PREFIX = /usr/local 8 | MANDIR = $(PREFIX)/share/man 9 | DATADIR = $(PREFIX)/share 10 | 11 | XWAYLAND = 12 | XLIBS = 13 | # Uncomment to build XWayland support 14 | #XWAYLAND = -DXWAYLAND 15 | #XLIBS = xcb xcb-icccm 16 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE.dwm file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util.h" 8 | 9 | void 10 | die(const char *fmt, ...) { 11 | va_list ap; 12 | 13 | va_start(ap, fmt); 14 | vfprintf(stderr, fmt, ap); 15 | va_end(ap); 16 | 17 | if (fmt[0] && fmt[strlen(fmt)-1] == ':') { 18 | fputc(' ', stderr); 19 | perror(NULL); 20 | } else { 21 | fputc('\n', stderr); 22 | } 23 | 24 | exit(1); 25 | } 26 | 27 | void * 28 | ecalloc(size_t nmemb, size_t size) 29 | { 30 | void *p; 31 | 32 | if (!(p = calloc(nmemb, size))) 33 | die("calloc:"); 34 | return p; 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.sway: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2017 Drew DeVault 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /LICENSE.dwm: -------------------------------------------------------------------------------- 1 | Portions of dwl based on dwm code are used under the following license: 2 | 3 | MIT/X Consortium License 4 | 5 | © 2006-2019 Anselm R Garbe 6 | © 2006-2009 Jukka Salmi 7 | © 2006-2007 Sander van Dijk 8 | © 2007-2011 Peter Hartlich 9 | © 2007-2009 Szabolcs Nagy 10 | © 2007-2009 Christof Musik 11 | © 2007-2009 Premysl Hruby 12 | © 2007-2008 Enno Gottox Boland 13 | © 2008 Martin Hurton 14 | © 2008 Neale Pickett 15 | © 2009 Mate Nagy 16 | © 2010-2016 Hiltjo Posthuma 17 | © 2010-2012 Connor Lane Smith 18 | © 2011 Christoph Lohmann <20h@r-36.net> 19 | © 2015-2016 Quentin Rameau 20 | © 2015-2016 Eric Pruitt 21 | © 2016-2017 Markus Teich 22 | 23 | Permission is hereby granted, free of charge, to any person obtaining a 24 | copy of this software and associated documentation files (the "Software"), 25 | to deal in the Software without restriction, including without limitation 26 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 27 | and/or sell copies of the Software, and to permit persons to whom the 28 | Software is furnished to do so, subject to the following conditions: 29 | 30 | The above copyright notice and this permission notice shall be included in 31 | all copies or substantial portions of the Software. 32 | 33 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 36 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 38 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 39 | DEALINGS IN THE SOFTWARE. 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | .SUFFIXES: 3 | 4 | include config.mk 5 | 6 | # flags for compiling 7 | DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XWAYLAND) 8 | DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement -Wno-unused-parameter -Wno-sign-compare -Wshadow -Wunused-macros\ 9 | -Werror=strict-prototypes -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types 10 | 11 | # CFLAGS / LDFLAGS 12 | PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) 13 | DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) 14 | LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) 15 | 16 | all: dwl 17 | dwl: dwl.o util.o 18 | $(CC) dwl.o util.o $(LDLIBS) $(LDFLAGS) $(DWLCFLAGS) -o $@ 19 | dwl.o: dwl.c config.mk config.h client.h cursor-shape-v1-protocol.h xdg-shell-protocol.h wlr-layer-shell-unstable-v1-protocol.h 20 | util.o: util.c util.h 21 | 22 | # wayland-scanner is a tool which generates C headers and rigging for Wayland 23 | # protocols, which are specified in XML. wlroots requires you to rig these up 24 | # to your build system yourself and provide them in the include path. 25 | WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner` 26 | WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols` 27 | 28 | xdg-shell-protocol.h: 29 | $(WAYLAND_SCANNER) server-header \ 30 | $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ 31 | wlr-layer-shell-unstable-v1-protocol.h: 32 | $(WAYLAND_SCANNER) server-header \ 33 | protocols/wlr-layer-shell-unstable-v1.xml $@ 34 | cursor-shape-v1-protocol.h: 35 | $(WAYLAND_SCANNER) server-header \ 36 | $(WAYLAND_PROTOCOLS)/staging/cursor-shape/cursor-shape-v1.xml $@ 37 | 38 | config.h: 39 | cp config.def.h $@ 40 | clean: 41 | rm -f dwl *.o *-protocol.h 42 | 43 | dist: clean 44 | mkdir -p dwl-$(VERSION) 45 | cp -R LICENSE* Makefile README.md client.h config.def.h\ 46 | config.mk protocols dwl.1 dwl.c util.c util.h dwl.desktop\ 47 | dwl-$(VERSION) 48 | tar -caf dwl-$(VERSION).tar.gz dwl-$(VERSION) 49 | rm -rf dwl-$(VERSION) 50 | 51 | install: dwl 52 | mkdir -p $(DESTDIR)$(PREFIX)/bin 53 | cp -f dwl $(DESTDIR)$(PREFIX)/bin 54 | chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl 55 | mkdir -p $(DESTDIR)$(MANDIR)/man1 56 | cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1 57 | chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1 58 | mkdir -p $(DESTDIR)$(DATADIR)/wayland-sessions 59 | cp -f dwl.desktop $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop 60 | chmod 644 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop 61 | uninstall: 62 | rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop 63 | 64 | .SUFFIXES: .c .o 65 | .c.o: 66 | $(CC) $(CPPFLAGS) $(DWLCFLAGS) -c $< 67 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | * [0.5](#0.5) 4 | 5 | ## 0.5 6 | 7 | ### Added 8 | 9 | * Allow configure x and y position of outputs ([#301][301]) 10 | * Implement repeatable keybindings ([#368][368]) 11 | * Print app id in printstatus() output ([#381][381]) 12 | * Display client count in monocle symbol ([#387][387]) 13 | * Export XCURSOR_SIZE to fix apps using an older version of Qt ([#425][425]) 14 | * Support for wp-fractional-scale-v1 (through wlr_scene: [wlroots!3511][wlroots!3511]) 15 | * dwl now sends `wl_surface.preferred_buffer_scale` (through wlr_scene: [wlroots!4269][wlroots!4269]) 16 | * Add support for xdg-shell v6 ([#465][465]) 17 | * Add support for wp-cursor-shape-v1 ([#444][444]) 18 | * Add desktop file ([#484][484]) 19 | * Add macro to easily configure colors ([#466][466]) 20 | * Color of urgent clients are now red ([#494][494]) 21 | * New flag `-d` and option `log_level` to change the wlroots debug level 22 | * Add CHANGELOG.md ([#501][501]) 23 | 24 | [301]: https://github.com/djpohly/dwl/pull/301 25 | [368]: https://github.com/djpohly/dwl/pull/368 26 | [381]: https://github.com/djpohly/dwl/pull/381 27 | [387]: https://github.com/djpohly/dwl/issues/387 28 | [425]: https://github.com/djpohly/dwl/pull/425 29 | [wlroots!4269]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4269 30 | [wlroots!3511]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/3511 31 | [465]: https://github.com/djpohly/dwl/pull/465 32 | [444]: https://github.com/djpohly/dwl/pull/444 33 | [484]: https://github.com/djpohly/dwl/pull/484 34 | [466]: https://github.com/djpohly/dwl/issues/466 35 | [494]: https://github.com/djpohly/dwl/pull/494 36 | [501]: https://github.com/djpohly/dwl/pull/501 37 | 38 | 39 | ### Changed 40 | 41 | * Replace `tags` with `TAGCOUNT` in config.def.h ([#403][403]) 42 | * Pop ups are now destroyed when focusing another client ([#408][408]) 43 | * dwl does not longer respect size hints, instead clip windows if they are 44 | larger than they should be ([#455][455]) 45 | * The version of wlr-layer-shell-unstable-v1 was lowered to 3 (from 4) 46 | * Use the same border color as dwm ([#494][494]) 47 | 48 | [403]: https://github.com/djpohly/dwl/pull/403 49 | [408]: https://github.com/djpohly/dwl/pull/409 50 | [455]: https://github.com/djpohly/dwl/pull/455 51 | [494]: https://github.com/djpohly/dwl/pull/494 52 | 53 | 54 | ### Removed 55 | 56 | * Remove unused `rootcolor` option ([#401][401]) 57 | * Remove support for wlr-input-inhibitor-unstable-v1 ([#430][430]) 58 | * Remove support for KDE idle protocol ([#431][431]) 59 | 60 | [401]: https://github.com/djpohly/dwl/pull/401 61 | [430]: https://github.com/djpohly/dwl/pull/430 62 | [431]: https://github.com/djpohly/dwl/pull/431 63 | 64 | 65 | ### Fixed 66 | 67 | * Fix crash when creating a layer surface with all outputs disabled 68 | ([#421][421]) 69 | * Fix other clients being shown as focused if the focused client have pop ups 70 | open ([#408][408]) 71 | * Resize fullscreen clients when updating monitor mode 72 | * dwl no longer crash at exit like sometimes did 73 | * Fullscreen background appearing above clients ([#487][487]) 74 | * Fix a segfault when user provides invalid xkb_rules ([#518][518]) 75 | 76 | [421]: https://github.com/djpohly/dwl/pull/421 77 | [408]: https://github.com/djpohly/dwl/issues/408 78 | [487]: https://github.com/djpohly/dwl/issues/487 79 | [518]: https://github.com/djpohly/dwl/pull/518 80 | 81 | 82 | ### Contributors 83 | 84 | * A Frederick Christensen 85 | * Angelo Antony 86 | * Ben Collerson 87 | * Devin J. Pohly 88 | * Forrest Bushstone 89 | * gan-of-culture 90 | * godalming123 91 | * Job79 92 | * link2xt 93 | * Micah Gorrell 94 | * Nikita Ivanov 95 | * Palanix 96 | * pino-desktop 97 | * Weiseguy 98 | * Yves Zoundi 99 | -------------------------------------------------------------------------------- /dwl.1: -------------------------------------------------------------------------------- 1 | .Dd January 8, 2021 2 | .Dt DWL 1 3 | .Os 4 | .Sh NAME 5 | .Nm dwl 6 | .Nd dwm for Wayland 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl v 10 | .Op Fl d 11 | .Op Fl s Ar startup command 12 | .Sh DESCRIPTION 13 | .Nm 14 | is a Wayland compositor based on wlroots. 15 | It is intended to fill the same space in the Wayland world that 16 | .Nm dwm 17 | does for X11. 18 | .Pp 19 | When given the 20 | .Fl v 21 | option, 22 | .Nm 23 | writes its name and version to standard error and exits unsuccessfully. 24 | .Pp 25 | When given the 26 | .Fl d 27 | option, 28 | .Nm 29 | enables full wlroots logging, including debug information. 30 | .Pp 31 | When given the 32 | .Fl s 33 | option, 34 | .Nm 35 | starts a shell process running 36 | .Ar command 37 | when starting. 38 | When stopping, it sends 39 | .Dv SIGTERM 40 | to the child process and waits for it to exit. 41 | .Pp 42 | Users are encouraged to customize 43 | .Nm 44 | by editing the sources, in particular 45 | .Pa config.h . 46 | The default key bindings are as follows: 47 | .Bl -tag -width 20n -offset indent -compact 48 | .It Mod-[1-9] 49 | Show only all windows with a tag. 50 | .It Mod-Ctrl-[1-9] 51 | Show all windows with a tag. 52 | .It Mod-Shift-[1-9] 53 | Move window to a single tag. 54 | .It Mod-Ctrl-Shift-[1-9] 55 | Toggle tag for window. 56 | .It Mod-p 57 | Spawn 58 | .Nm bemenu-run . 59 | .It Mod-Shift-Return 60 | Spawn 61 | .Nm foot . 62 | .It Mod-[jk] 63 | Move focus down/up the stack. 64 | .It Mod-[id] 65 | Increase/decrease number of windows in master area. 66 | .It Mod-[hl] 67 | Decrease/increase master area. 68 | .It Mod-Return 69 | Move window on top of stack or switch top of stack with second window. 70 | .It Mod-Tab 71 | Show only all windows with previous tag. 72 | .It Mod-Shift-c 73 | Close window. 74 | .It Mod-t 75 | Switch to tabbed layout. 76 | .It Mod-f 77 | Switch to floating layout. 78 | .It Mod-m 79 | Switch to monocle layout. 80 | .It Mod-Space 81 | Switch to previous layout. 82 | .It Mod-Shift-Space 83 | Toggle floating state of window. 84 | .It Mod-e 85 | Toggle fullscreen state of window. 86 | .It Mod-0 87 | Show all windows. 88 | .It Mod-Shift-0 89 | Set all tags for window. 90 | .It Mod-, 91 | Move focus to previous monitor. 92 | .It Mod-. 93 | Move focus to next monitor. 94 | .It Mod-Shift-, 95 | Move window to previous monitor. 96 | .It Mod-Shift-. 97 | Move window to next monitor. 98 | .It Mod-Shift-q 99 | Quit 100 | .Nm . 101 | .El 102 | These might differ depending on your keyboard layout. 103 | .Sh ENVIRONMENT 104 | These environment variables are used by 105 | .Nm : 106 | .Bl -tag -width XDG_RUNTIME_DIR 107 | .It Ev XDG_RUNTIME_DIR 108 | A directory where temporary user files, such as the Wayland socket, 109 | are stored. 110 | .It Ev XDG_CONFIG_DIR 111 | A directory containing configuration of various programs and 112 | libraries, including libxkbcommon. 113 | .It Ev DISPLAY , WAYLAND_DISPLAY , WAYLAND_SOCKET 114 | Tell how to connect to an underlying X11 or Wayland server. 115 | .It Ev WLR_* 116 | Various variables specific to wlroots. 117 | .It Ev XKB_* , XLOCALEDIR , XCOMPOSEFILE 118 | Various variables specific to libxkbcommon. 119 | .It Ev XCURSOR_PATH 120 | List of directories to search for XCursor themes in. 121 | .It Ev HOME 122 | A directory where there are always dear files there for you. 123 | Waiting for you to clean them up. 124 | .El 125 | .Pp 126 | These are set by 127 | .Nm : 128 | .Bl -tag -width WAYLAND_DISPLAY 129 | .It Ev WAYLAND_DISPLAY 130 | Tell how to connect to 131 | .Nm . 132 | .It Ev DISPLAY 133 | If using 134 | .Nm Xwayland , 135 | tell how to connect to the 136 | .Nm Xwayland 137 | server. 138 | .El 139 | .Sh EXAMPLES 140 | Start 141 | .Nm 142 | with s6 in the background: 143 | .Dl dwl -s 's6-svscan <&-' 144 | .Sh SEE ALSO 145 | .Xr foot 1 , 146 | .Xr bemenu 1 , 147 | .Xr dwm 1 , 148 | .Xr xkeyboard-config 7 149 | .Sh CAVEATS 150 | The child process's standard input is connected with a pipe to 151 | .Nm . 152 | If the child process neither reads from the pipe nor closes its 153 | standard input, 154 | .Nm 155 | will freeze after a while due to it blocking when writing to the full 156 | pipe buffer. 157 | .Sh BUGS 158 | All of them. 159 | -------------------------------------------------------------------------------- /LICENSE.tinywl: -------------------------------------------------------------------------------- 1 | dwl is originally based on TinyWL, which is used under the following license: 2 | 3 | This work is licensed under CC0, which effectively puts it in the public domain. 4 | 5 | --- 6 | 7 | Creative Commons Legal Code 8 | 9 | CC0 1.0 Universal 10 | 11 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 12 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 13 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 14 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 15 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 16 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 17 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 18 | HEREUNDER. 19 | 20 | Statement of Purpose 21 | 22 | The laws of most jurisdictions throughout the world automatically confer 23 | exclusive Copyright and Related Rights (defined below) upon the creator 24 | and subsequent owner(s) (each and all, an "owner") of an original work of 25 | authorship and/or a database (each, a "Work"). 26 | 27 | Certain owners wish to permanently relinquish those rights to a Work for 28 | the purpose of contributing to a commons of creative, cultural and 29 | scientific works ("Commons") that the public can reliably and without fear 30 | of later claims of infringement build upon, modify, incorporate in other 31 | works, reuse and redistribute as freely as possible in any form whatsoever 32 | and for any purposes, including without limitation commercial purposes. 33 | These owners may contribute to the Commons to promote the ideal of a free 34 | culture and the further production of creative, cultural and scientific 35 | works, or to gain reputation or greater distribution for their Work in 36 | part through the use and efforts of others. 37 | 38 | For these and/or other purposes and motivations, and without any 39 | expectation of additional consideration or compensation, the person 40 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 41 | is an owner of Copyright and Related Rights in the Work, voluntarily 42 | elects to apply CC0 to the Work and publicly distribute the Work under its 43 | terms, with knowledge of his or her Copyright and Related Rights in the 44 | Work and the meaning and intended legal effect of CC0 on those rights. 45 | 46 | 1. Copyright and Related Rights. A Work made available under CC0 may be 47 | protected by copyright and related or neighboring rights ("Copyright and 48 | Related Rights"). Copyright and Related Rights include, but are not 49 | limited to, the following: 50 | 51 | i. the right to reproduce, adapt, distribute, perform, display, 52 | communicate, and translate a Work; 53 | ii. moral rights retained by the original author(s) and/or performer(s); 54 | iii. publicity and privacy rights pertaining to a person's image or 55 | likeness depicted in a Work; 56 | iv. rights protecting against unfair competition in regards to a Work, 57 | subject to the limitations in paragraph 4(a), below; 58 | v. rights protecting the extraction, dissemination, use and reuse of data 59 | in a Work; 60 | vi. database rights (such as those arising under Directive 96/9/EC of the 61 | European Parliament and of the Council of 11 March 1996 on the legal 62 | protection of databases, and under any national implementation 63 | thereof, including any amended or successor version of such 64 | directive); and 65 | vii. other similar, equivalent or corresponding rights throughout the 66 | world based on applicable law or treaty, and any national 67 | implementations thereof. 68 | 69 | 2. Waiver. To the greatest extent permitted by, but not in contravention 70 | of, applicable law, Affirmer hereby overtly, fully, permanently, 71 | irrevocably and unconditionally waives, abandons, and surrenders all of 72 | Affirmer's Copyright and Related Rights and associated claims and causes 73 | of action, whether now known or unknown (including existing as well as 74 | future claims and causes of action), in the Work (i) in all territories 75 | worldwide, (ii) for the maximum duration provided by applicable law or 76 | treaty (including future time extensions), (iii) in any current or future 77 | medium and for any number of copies, and (iv) for any purpose whatsoever, 78 | including without limitation commercial, advertising or promotional 79 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 80 | member of the public at large and to the detriment of Affirmer's heirs and 81 | successors, fully intending that such Waiver shall not be subject to 82 | revocation, rescission, cancellation, termination, or any other legal or 83 | equitable action to disrupt the quiet enjoyment of the Work by the public 84 | as contemplated by Affirmer's express Statement of Purpose. 85 | 86 | 3. Public License Fallback. Should any part of the Waiver for any reason 87 | be judged legally invalid or ineffective under applicable law, then the 88 | Waiver shall be preserved to the maximum extent permitted taking into 89 | account Affirmer's express Statement of Purpose. In addition, to the 90 | extent the Waiver is so judged Affirmer hereby grants to each affected 91 | person a royalty-free, non transferable, non sublicensable, non exclusive, 92 | irrevocable and unconditional license to exercise Affirmer's Copyright and 93 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 94 | maximum duration provided by applicable law or treaty (including future 95 | time extensions), (iii) in any current or future medium and for any number 96 | of copies, and (iv) for any purpose whatsoever, including without 97 | limitation commercial, advertising or promotional purposes (the 98 | "License"). The License shall be deemed effective as of the date CC0 was 99 | applied by Affirmer to the Work. Should any part of the License for any 100 | reason be judged legally invalid or ineffective under applicable law, such 101 | partial invalidity or ineffectiveness shall not invalidate the remainder 102 | of the License, and in such case Affirmer hereby affirms that he or she 103 | will not (i) exercise any of his or her remaining Copyright and Related 104 | Rights in the Work or (ii) assert any associated claims and causes of 105 | action with respect to the Work, in either case contrary to Affirmer's 106 | express Statement of Purpose. 107 | 108 | 4. Limitations and Disclaimers. 109 | 110 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 111 | surrendered, licensed or otherwise affected by this document. 112 | b. Affirmer offers the Work as-is and makes no representations or 113 | warranties of any kind concerning the Work, express, implied, 114 | statutory or otherwise, including without limitation warranties of 115 | title, merchantability, fitness for a particular purpose, non 116 | infringement, or the absence of latent or other defects, accuracy, or 117 | the present or absence of errors, whether or not discoverable, all to 118 | the greatest extent permissible under applicable law. 119 | c. Affirmer disclaims responsibility for clearing rights of other persons 120 | that may apply to the Work or any use thereof, including without 121 | limitation any person's Copyright and Related Rights in the Work. 122 | Further, Affirmer disclaims responsibility for obtaining any necessary 123 | consents, permissions or other rights required for any use of the 124 | Work. 125 | d. Affirmer understands and acknowledges that Creative Commons is not a 126 | party to this document and has no duty or obligation with respect to 127 | this CC0 or use of the Work. 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dwl - dwm for Wayland 2 | 3 | > ### ⚠️ Migrated to Codeberg 4 | > 5 | > This project has [migrated to Codeberg](https://codeberg.org/dwl/dwl). 6 | 7 | 8 | Join us on our IRC channel: [#dwl on Libera Chat] 9 | Or on our [Discord server]. 10 | 11 | dwl is a compact, hackable compositor for [Wayland] based on [wlroots]. It is 12 | intended to fill the same space in the Wayland world that dwm does in X11, 13 | primarily in terms of philosophy, and secondarily in terms of functionality. 14 | Like dwm, dwl is: 15 | 16 | - Easy to understand, hack on, and extend with patches 17 | - One C source file (or a very small number) configurable via `config.h` 18 | - Limited to 2200 SLOC to promote hackability 19 | - Tied to as few external dependencies as possible 20 | 21 | dwl is not meant to provide every feature under the sun. Instead, like dwm, it 22 | sticks to features which are necessary, simple, and straightforward to implement 23 | given the base on which it is built. Implemented default features are: 24 | 25 | - Any features provided by dwm/Xlib: simple window borders, tags, keybindings, 26 | client rules, mouse move/resize. Providing a built-in status bar is an 27 | exception to this goal, to avoid dependencies on font rendering and/or 28 | drawing libraries when an external bar could work well. 29 | - Configurable multi-monitor layout support, including position and rotation 30 | - Configurable HiDPI/multi-DPI support 31 | - Idle-inhibit protocol which lets applications such as mpv disable idle 32 | monitoring 33 | - Provide information to external status bars via stdout/stdin 34 | - Urgency hints via xdg-activate protocol 35 | - Support screen lockers via ext-session-lock-v1 protocol 36 | - Various Wayland protocols 37 | - XWayland support as provided by wlroots (can be enabled in `config.mk`) 38 | - Zero flickering - Wayland users naturally expect that "every frame is perfect" 39 | - Layer shell popups (used by Waybar) 40 | - Damage tracking provided by scenegraph API 41 | 42 | Features under consideration (possibly as patches) are: 43 | 44 | - Protocols made trivial by wlroots 45 | - Implement the text-input and input-method protocols to support IME once ibus 46 | implements input-method v2 (see https://github.com/ibus/ibus/pull/2256 and 47 | https://github.com/djpohly/dwl/pull/235) 48 | 49 | Feature *non-goals* for the main codebase include: 50 | 51 | - Client-side decoration (any more than is necessary to tell the clients not to) 52 | - Client-initiated window management, such as move, resize, and close, which can 53 | be done through the compositor 54 | - Animations and visual effects 55 | 56 | ## Building dwl 57 | 58 | dwl has the following dependencies: 59 | ``` 60 | libinput 61 | wayland 62 | wlroots (compiled with the libinput backend) 63 | xkbcommon 64 | wayland-protocols (compile-time only) 65 | pkg-config (compile-time only) 66 | ``` 67 | If you enable X11 support: 68 | ``` 69 | libxcb 70 | libxcb-wm 71 | wlroots (compiled with X11 support) 72 | Xwayland (runtime only) 73 | ``` 74 | 75 | Simply install these (and their `-devel` versions if your distro has separate 76 | development packages) and run `make`. If you wish to build against a Git 77 | version of wlroots, check out the [wlroots-next branch]. 78 | 79 | To enable XWayland, you should uncomment its flags in `config.mk`. 80 | 81 | ## Configuration 82 | 83 | All configuration is done by editing `config.h` and recompiling, in the same 84 | manner as dwm. There is no way to separately restart the window manager in 85 | Wayland without restarting the entire display server, so any changes will take 86 | effect the next time dwl is executed. 87 | 88 | As in the dwm community, we encourage users to share patches they have created. 89 | Check out the [patches page on our wiki]! 90 | 91 | ## Running dwl 92 | 93 | dwl can be run on any of the backends supported by wlroots. This means you can 94 | run it as a separate window inside either an X11 or Wayland session, as well 95 | as directly from a VT console. Depending on your distro's setup, you may need 96 | to add your user to the `video` and `input` groups before you can run dwl on 97 | a VT. If you are using `elogind` or `systemd-logind` you need to install 98 | polkit; otherwise you need to add yourself in the `seat` group and 99 | enable/start the seatd daemon. 100 | 101 | When dwl is run with no arguments, it will launch the server and begin handling 102 | any shortcuts configured in `config.h`. There is no status bar or other 103 | decoration initially; these are instead clients that can be run within 104 | the Wayland session. 105 | Do note that the background color is black. 106 | 107 | If you would like to run a script or command automatically at startup, you can 108 | specify the command using the `-s` option. This command will be executed as a 109 | shell command using `/bin/sh -c`. It serves a similar function to `.xinitrc`, 110 | but differs in that the display server will not shut down when this process 111 | terminates. Instead, dwl will send this process a SIGTERM at shutdown and wait 112 | for it to terminate (if it hasn't already). This makes it ideal for execing into 113 | a user service manager like [s6], [anopa], [runit], or [`systemd --user`]. 114 | 115 | Note: The `-s` command is run as a *child process* of dwl, which means that it 116 | does not have the ability to affect the environment of dwl or of any processes 117 | that it spawns. If you need to set environment variables that affect the entire 118 | dwl session, these must be set prior to running dwl. For example, Wayland 119 | requires a valid `XDG_RUNTIME_DIR`, which is usually set up by a session manager 120 | such as `elogind` or `systemd-logind`. If your system doesn't do this 121 | automatically, you will need to configure it prior to launching `dwl`, e.g.: 122 | 123 | export XDG_RUNTIME_DIR=/tmp/xdg-runtime-$(id -u) 124 | mkdir -p $XDG_RUNTIME_DIR 125 | dwl 126 | 127 | ### Status information 128 | 129 | Information about selected layouts, current window title, app-id, and 130 | selected/occupied/urgent tags is written to the stdin of the `-s` command (see 131 | the `printstatus()` function for details). This information can be used to 132 | populate an external status bar with a script that parses the information. 133 | Failing to read this information will cause dwl to block, so if you do want to 134 | run a startup command that does not consume the status information, you can 135 | close standard input with the `<&-` shell redirection, for example: 136 | 137 | dwl -s 'foot --server <&-' 138 | 139 | If your startup command is a shell script, you can achieve the same inside the 140 | script with the line 141 | 142 | exec <&- 143 | 144 | To get a list of status bars that work with dwl consult our [wiki]. 145 | 146 | ## Replacements for X applications 147 | 148 | You can find a [list of useful resources on our wiki]. 149 | 150 | ## Acknowledgements 151 | 152 | dwl began by extending the TinyWL example provided (CC0) by the sway/wlroots 153 | developers. This was made possible in many cases by looking at how sway 154 | accomplished something, then trying to do the same in as suckless a way as 155 | possible. 156 | 157 | Many thanks to suckless.org and the dwm developers and community for the 158 | inspiration, and to the various contributors to the project, including: 159 | 160 | - Alexander Courtis for the XWayland implementation 161 | - Guido Cella for the layer-shell protocol implementation, patch maintenance, 162 | and for helping to keep the project running 163 | - Stivvo for output management and fullscreen support, and patch maintenance 164 | 165 | 166 | [Discord server]: https://discord.gg/jJxZnrGPWN 167 | [#dwl on Libera Chat]: https://web.libera.chat/?channels=#dwl 168 | [Wayland]: https://wayland.freedesktop.org/ 169 | [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots/ 170 | [wlroots-next branch]: https://github.com/djpohly/dwl/tree/wlroots-next 171 | [patches page on our wiki]: https://github.com/djpohly/dwl/wiki/Patches 172 | [s6]: https://skarnet.org/software/s6/ 173 | [anopa]: https://jjacky.com/anopa/ 174 | [runit]: http://smarden.org/runit/faq.html#userservices 175 | [`systemd --user`]: https://wiki.archlinux.org/title/Systemd/User 176 | [wiki]: https://github.com/djpohly/dwl/wiki#compatible-status-bars 177 | [list of useful resources on our wiki]: 178 | https://github.com/djpohly/dwl/wiki#migrating-from-x 179 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | /* Taken from https://github.com/djpohly/dwl/issues/466 */ 2 | #define COLOR(hex) { ((hex >> 24) & 0xFF) / 255.0f, \ 3 | ((hex >> 16) & 0xFF) / 255.0f, \ 4 | ((hex >> 8) & 0xFF) / 255.0f, \ 5 | (hex & 0xFF) / 255.0f } 6 | /* appearance */ 7 | static const int sloppyfocus = 1; /* focus follows mouse */ 8 | static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ 9 | static const unsigned int borderpx = 1; /* border pixel of windows */ 10 | static const float bordercolor[] = COLOR(0x444444ff); 11 | static const float focuscolor[] = COLOR(0x005577ff); 12 | static const float urgentcolor[] = COLOR(0xff0000ff); 13 | /* To conform the xdg-protocol, set the alpha to zero to restore the old behavior */ 14 | static const float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; /* You can also use glsl colors */ 15 | 16 | /* tagging - TAGCOUNT must be no greater than 31 */ 17 | #define TAGCOUNT (9) 18 | 19 | /* logging */ 20 | static int log_level = WLR_ERROR; 21 | 22 | static const Rule rules[] = { 23 | /* app_id title tags mask isfloating monitor */ 24 | /* examples: 25 | { "Gimp", NULL, 0, 1, -1 }, 26 | */ 27 | { "firefox", NULL, 1 << 8, 0, -1 }, 28 | }; 29 | 30 | /* layout(s) */ 31 | static const Layout layouts[] = { 32 | /* symbol arrange function */ 33 | { "[]=", tile }, 34 | { "><>", NULL }, /* no layout function means floating behavior */ 35 | { "[M]", monocle }, 36 | }; 37 | 38 | /* monitors */ 39 | static const MonitorRule monrules[] = { 40 | /* name mfact nmaster scale layout rotate/reflect x y */ 41 | /* example of a HiDPI laptop monitor: 42 | { "eDP-1", 0.5, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, 43 | */ 44 | /* defaults */ 45 | { NULL, 0.55, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, 46 | }; 47 | 48 | /* keyboard */ 49 | static const struct xkb_rule_names xkb_rules = { 50 | /* can specify fields: rules, model, layout, variant, options */ 51 | /* example: 52 | .options = "ctrl:nocaps", 53 | */ 54 | .options = NULL, 55 | }; 56 | 57 | static const int repeat_rate = 25; 58 | static const int repeat_delay = 600; 59 | 60 | /* Trackpad */ 61 | static const int tap_to_click = 1; 62 | static const int tap_and_drag = 1; 63 | static const int drag_lock = 1; 64 | static const int natural_scrolling = 0; 65 | static const int disable_while_typing = 1; 66 | static const int left_handed = 0; 67 | static const int middle_button_emulation = 0; 68 | /* You can choose between: 69 | LIBINPUT_CONFIG_SCROLL_NO_SCROLL 70 | LIBINPUT_CONFIG_SCROLL_2FG 71 | LIBINPUT_CONFIG_SCROLL_EDGE 72 | LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN 73 | */ 74 | static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; 75 | 76 | /* You can choose between: 77 | LIBINPUT_CONFIG_CLICK_METHOD_NONE 78 | LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS 79 | LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER 80 | */ 81 | static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; 82 | 83 | /* You can choose between: 84 | LIBINPUT_CONFIG_SEND_EVENTS_ENABLED 85 | LIBINPUT_CONFIG_SEND_EVENTS_DISABLED 86 | LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE 87 | */ 88 | static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; 89 | 90 | /* You can choose between: 91 | LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT 92 | LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE 93 | */ 94 | static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; 95 | static const double accel_speed = 0.0; 96 | /* You can choose between: 97 | LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle 98 | LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right 99 | */ 100 | static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; 101 | 102 | /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ 103 | #define MODKEY WLR_MODIFIER_ALT 104 | 105 | #define TAGKEYS(KEY,SKEY,TAG) \ 106 | { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ 107 | { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ 108 | { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ 109 | { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } 110 | 111 | /* helper for spawning shell commands in the pre dwm-5.0 fashion */ 112 | #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } 113 | 114 | /* commands */ 115 | static const char *termcmd[] = { "foot", NULL }; 116 | static const char *menucmd[] = { "bemenu-run", NULL }; 117 | 118 | static const Key keys[] = { 119 | /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ 120 | /* modifier key function argument */ 121 | { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, 122 | { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, 123 | { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, 124 | { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, 125 | { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, 126 | { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, 127 | { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05} }, 128 | { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05} }, 129 | { MODKEY, XKB_KEY_Return, zoom, {0} }, 130 | { MODKEY, XKB_KEY_Tab, view, {0} }, 131 | { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, 132 | { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, 133 | { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, 134 | { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, 135 | { MODKEY, XKB_KEY_space, setlayout, {0} }, 136 | { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, 137 | { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, 138 | { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, 139 | { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, 140 | { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, 141 | { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, 142 | { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, 143 | { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, 144 | TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), 145 | TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), 146 | TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), 147 | TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), 148 | TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), 149 | TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), 150 | TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), 151 | TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), 152 | TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), 153 | { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, 154 | 155 | /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ 156 | { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, 157 | /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is 158 | * do not remove them. 159 | */ 160 | #define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } 161 | CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), 162 | CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), 163 | }; 164 | 165 | static const Button buttons[] = { 166 | { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, 167 | { MODKEY, BTN_MIDDLE, togglefloating, {0} }, 168 | { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, 169 | }; 170 | -------------------------------------------------------------------------------- /client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Attempt to consolidate unavoidable suck into one file, away from dwl.c. This 3 | * file is not meant to be pretty. We use a .h file with static inline 4 | * functions instead of a separate .c module, or function pointers like sway, so 5 | * that they will simply compile out if the chosen #defines leave them unused. 6 | */ 7 | 8 | /* Leave these functions first; they're used in the others */ 9 | static inline int 10 | client_is_x11(Client *c) 11 | { 12 | #ifdef XWAYLAND 13 | return c->type == X11Managed || c->type == X11Unmanaged; 14 | #endif 15 | return 0; 16 | } 17 | 18 | static inline struct wlr_surface * 19 | client_surface(Client *c) 20 | { 21 | #ifdef XWAYLAND 22 | if (client_is_x11(c)) 23 | return c->surface.xwayland->surface; 24 | #endif 25 | return c->surface.xdg->surface; 26 | } 27 | 28 | static inline int 29 | toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) 30 | { 31 | struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; 32 | struct wlr_surface *root_surface; 33 | struct wlr_layer_surface_v1 *layer_surface; 34 | Client *c = NULL; 35 | LayerSurface *l = NULL; 36 | int type = -1; 37 | #ifdef XWAYLAND 38 | struct wlr_xwayland_surface *xsurface; 39 | #endif 40 | 41 | if (!s) 42 | return -1; 43 | root_surface = wlr_surface_get_root_surface(s); 44 | 45 | #ifdef XWAYLAND 46 | if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { 47 | c = xsurface->data; 48 | type = c->type; 49 | goto end; 50 | } 51 | #endif 52 | 53 | if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) { 54 | l = layer_surface->data; 55 | type = LayerShell; 56 | goto end; 57 | } 58 | 59 | xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface); 60 | while (xdg_surface) { 61 | tmp_xdg_surface = NULL; 62 | switch (xdg_surface->role) { 63 | case WLR_XDG_SURFACE_ROLE_POPUP: 64 | if (!xdg_surface->popup || !xdg_surface->popup->parent) 65 | return -1; 66 | 67 | tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); 68 | 69 | if (!tmp_xdg_surface) 70 | return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); 71 | 72 | xdg_surface = tmp_xdg_surface; 73 | break; 74 | case WLR_XDG_SURFACE_ROLE_TOPLEVEL: 75 | c = xdg_surface->data; 76 | type = c->type; 77 | goto end; 78 | case WLR_XDG_SURFACE_ROLE_NONE: 79 | return -1; 80 | } 81 | } 82 | 83 | end: 84 | if (pl) 85 | *pl = l; 86 | if (pc) 87 | *pc = c; 88 | return type; 89 | } 90 | 91 | /* The others */ 92 | static inline void 93 | client_activate_surface(struct wlr_surface *s, int activated) 94 | { 95 | struct wlr_xdg_toplevel *toplevel; 96 | #ifdef XWAYLAND 97 | struct wlr_xwayland_surface *xsurface; 98 | if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { 99 | wlr_xwayland_surface_activate(xsurface, activated); 100 | return; 101 | } 102 | #endif 103 | if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s))) 104 | wlr_xdg_toplevel_set_activated(toplevel, activated); 105 | } 106 | 107 | static inline uint32_t 108 | client_set_bounds(Client *c, int32_t width, int32_t height) 109 | { 110 | #ifdef XWAYLAND 111 | if (client_is_x11(c)) 112 | return 0; 113 | #endif 114 | if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >= 115 | XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION && width >= 0 && height >= 0 116 | && (c->bounds.width != width || c->bounds.height != height)) { 117 | c->bounds.width = width; 118 | c->bounds.height = height; 119 | return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height); 120 | } 121 | return 0; 122 | } 123 | 124 | static inline const char * 125 | client_get_appid(Client *c) 126 | { 127 | #ifdef XWAYLAND 128 | if (client_is_x11(c)) 129 | return c->surface.xwayland->class; 130 | #endif 131 | return c->surface.xdg->toplevel->app_id; 132 | } 133 | 134 | static inline void 135 | client_get_clip(Client *c, struct wlr_box *clip) 136 | { 137 | struct wlr_box xdg_geom = {0}; 138 | *clip = (struct wlr_box){ 139 | .x = 0, 140 | .y = 0, 141 | .width = c->geom.width - c->bw, 142 | .height = c->geom.height - c->bw, 143 | }; 144 | 145 | #ifdef XWAYLAND 146 | if (client_is_x11(c)) 147 | return; 148 | #endif 149 | 150 | wlr_xdg_surface_get_geometry(c->surface.xdg, &xdg_geom); 151 | clip->x = xdg_geom.x; 152 | clip->y = xdg_geom.y; 153 | } 154 | 155 | static inline void 156 | client_get_geometry(Client *c, struct wlr_box *geom) 157 | { 158 | #ifdef XWAYLAND 159 | if (client_is_x11(c)) { 160 | geom->x = c->surface.xwayland->x; 161 | geom->y = c->surface.xwayland->y; 162 | geom->width = c->surface.xwayland->width; 163 | geom->height = c->surface.xwayland->height; 164 | return; 165 | } 166 | #endif 167 | wlr_xdg_surface_get_geometry(c->surface.xdg, geom); 168 | } 169 | 170 | static inline Client * 171 | client_get_parent(Client *c) 172 | { 173 | Client *p = NULL; 174 | #ifdef XWAYLAND 175 | if (client_is_x11(c) && c->surface.xwayland->parent) 176 | toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); 177 | #endif 178 | if (c->surface.xdg->toplevel->parent) 179 | toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL); 180 | return p; 181 | } 182 | 183 | static inline const char * 184 | client_get_title(Client *c) 185 | { 186 | #ifdef XWAYLAND 187 | if (client_is_x11(c)) 188 | return c->surface.xwayland->title; 189 | #endif 190 | return c->surface.xdg->toplevel->title; 191 | } 192 | 193 | static inline int 194 | client_is_float_type(Client *c) 195 | { 196 | struct wlr_xdg_toplevel *toplevel; 197 | struct wlr_xdg_toplevel_state state; 198 | 199 | #ifdef XWAYLAND 200 | if (client_is_x11(c)) { 201 | struct wlr_xwayland_surface *surface = c->surface.xwayland; 202 | xcb_size_hints_t *size_hints = surface->size_hints; 203 | size_t i; 204 | if (surface->modal) 205 | return 1; 206 | 207 | for (i = 0; i < surface->window_type_len; i++) 208 | if (surface->window_type[i] == netatom[NetWMWindowTypeDialog] 209 | || surface->window_type[i] == netatom[NetWMWindowTypeSplash] 210 | || surface->window_type[i] == netatom[NetWMWindowTypeToolbar] 211 | || surface->window_type[i] == netatom[NetWMWindowTypeUtility]) 212 | return 1; 213 | 214 | return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0 215 | && (size_hints->max_width == size_hints->min_width 216 | || size_hints->max_height == size_hints->min_height); 217 | } 218 | #endif 219 | 220 | toplevel = c->surface.xdg->toplevel; 221 | state = toplevel->current; 222 | return toplevel->parent || (state.min_width != 0 && state.min_height != 0 223 | && (state.min_width == state.max_width 224 | || state.min_height == state.max_height)); 225 | } 226 | 227 | static inline int 228 | client_is_rendered_on_mon(Client *c, Monitor *m) 229 | { 230 | /* This is needed for when you don't want to check formal assignment, 231 | * but rather actual displaying of the pixels. 232 | * Usually VISIBLEON suffices and is also faster. */ 233 | struct wlr_surface_output *s; 234 | int unused_lx, unused_ly; 235 | if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly)) 236 | return 0; 237 | wl_list_for_each(s, &client_surface(c)->current_outputs, link) 238 | if (s->output == m->wlr_output) 239 | return 1; 240 | return 0; 241 | } 242 | 243 | static inline int 244 | client_is_stopped(Client *c) 245 | { 246 | int pid; 247 | siginfo_t in = {0}; 248 | #ifdef XWAYLAND 249 | if (client_is_x11(c)) 250 | return 0; 251 | #endif 252 | 253 | wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); 254 | if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) { 255 | /* This process is not our child process, while is very unluckely that 256 | * it is stopped, in order to do not skip frames assume that it is. */ 257 | if (errno == ECHILD) 258 | return 1; 259 | } else if (in.si_pid) { 260 | if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) 261 | return 1; 262 | if (in.si_code == CLD_CONTINUED) 263 | return 0; 264 | } 265 | 266 | return 0; 267 | } 268 | 269 | static inline int 270 | client_is_unmanaged(Client *c) 271 | { 272 | #ifdef XWAYLAND 273 | return c->type == X11Unmanaged; 274 | #endif 275 | return 0; 276 | } 277 | 278 | static inline void 279 | client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) 280 | { 281 | if (kb) 282 | wlr_seat_keyboard_notify_enter(seat, s, kb->keycodes, 283 | kb->num_keycodes, &kb->modifiers); 284 | else 285 | wlr_seat_keyboard_notify_enter(seat, s, NULL, 0, NULL); 286 | } 287 | 288 | static inline void 289 | client_restack_surface(Client *c) 290 | { 291 | #ifdef XWAYLAND 292 | if (client_is_x11(c)) 293 | wlr_xwayland_surface_restack(c->surface.xwayland, NULL, 294 | XCB_STACK_MODE_ABOVE); 295 | #endif 296 | return; 297 | } 298 | 299 | static inline void 300 | client_send_close(Client *c) 301 | { 302 | #ifdef XWAYLAND 303 | if (client_is_x11(c)) { 304 | wlr_xwayland_surface_close(c->surface.xwayland); 305 | return; 306 | } 307 | #endif 308 | wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); 309 | } 310 | 311 | static inline void 312 | client_set_border_color(Client *c, const float color[static 4]) 313 | { 314 | int i; 315 | for (i = 0; i < 4; i++) 316 | wlr_scene_rect_set_color(c->border[i], color); 317 | } 318 | 319 | static inline void 320 | client_set_fullscreen(Client *c, int fullscreen) 321 | { 322 | #ifdef XWAYLAND 323 | if (client_is_x11(c)) { 324 | wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen); 325 | return; 326 | } 327 | #endif 328 | wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); 329 | } 330 | 331 | static inline uint32_t 332 | client_set_size(Client *c, uint32_t width, uint32_t height) 333 | { 334 | #ifdef XWAYLAND 335 | if (client_is_x11(c)) { 336 | wlr_xwayland_surface_configure(c->surface.xwayland, 337 | c->geom.x, c->geom.y, width, height); 338 | return 0; 339 | } 340 | #endif 341 | if (width == c->surface.xdg->toplevel->current.width 342 | && height ==c->surface.xdg->toplevel->current.height) 343 | return 0; 344 | return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, width, height); 345 | } 346 | 347 | static inline void 348 | client_set_tiled(Client *c, uint32_t edges) 349 | { 350 | #ifdef XWAYLAND 351 | if (client_is_x11(c)) 352 | return; 353 | #endif 354 | if (wl_resource_get_version(c->surface.xdg->resource) 355 | >= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) { 356 | wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); 357 | } else { 358 | wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE); 359 | } 360 | } 361 | 362 | static inline void 363 | client_set_suspended(Client *c, int suspended) 364 | { 365 | #ifdef XWAYLAND 366 | if (client_is_x11(c)) { 367 | wlr_xwayland_surface_set_withdrawn(c->surface.xwayland, suspended); 368 | return; 369 | } 370 | #endif 371 | 372 | wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); 373 | } 374 | 375 | static inline int 376 | client_wants_focus(Client *c) 377 | { 378 | #ifdef XWAYLAND 379 | return client_is_unmanaged(c) 380 | && wlr_xwayland_or_surface_wants_focus(c->surface.xwayland) 381 | && wlr_xwayland_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; 382 | #endif 383 | return 0; 384 | } 385 | 386 | static inline int 387 | client_wants_fullscreen(Client *c) 388 | { 389 | #ifdef XWAYLAND 390 | if (client_is_x11(c)) 391 | return c->surface.xwayland->fullscreen; 392 | #endif 393 | return c->surface.xdg->toplevel->requested.fullscreen; 394 | } 395 | -------------------------------------------------------------------------------- /protocols/wlr-layer-shell-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2017 Drew DeVault 5 | 6 | Permission to use, copy, modify, distribute, and sell this 7 | software and its documentation for any purpose is hereby granted 8 | without fee, provided that the above copyright notice appear in 9 | all copies and that both that copyright notice and this permission 10 | notice appear in supporting documentation, and that the name of 11 | the copyright holders not be used in advertising or publicity 12 | pertaining to distribution of the software without specific, 13 | written prior permission. The copyright holders make no 14 | representations about the suitability of this software for any 15 | purpose. It is provided "as is" without express or implied 16 | warranty. 17 | 18 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 19 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 22 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 23 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 24 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 25 | THIS SOFTWARE. 26 | 27 | 28 | 29 | 30 | Clients can use this interface to assign the surface_layer role to 31 | wl_surfaces. Such surfaces are assigned to a "layer" of the output and 32 | rendered with a defined z-depth respective to each other. They may also be 33 | anchored to the edges and corners of a screen and specify input handling 34 | semantics. This interface should be suitable for the implementation of 35 | many desktop shell components, and a broad number of other applications 36 | that interact with the desktop. 37 | 38 | 39 | 40 | 41 | Create a layer surface for an existing surface. This assigns the role of 42 | layer_surface, or raises a protocol error if another role is already 43 | assigned. 44 | 45 | Creating a layer surface from a wl_surface which has a buffer attached 46 | or committed is a client error, and any attempts by a client to attach 47 | or manipulate a buffer prior to the first layer_surface.configure call 48 | must also be treated as errors. 49 | 50 | After creating a layer_surface object and setting it up, the client 51 | must perform an initial commit without any buffer attached. 52 | The compositor will reply with a layer_surface.configure event. 53 | The client must acknowledge it and is then allowed to attach a buffer 54 | to map the surface. 55 | 56 | You may pass NULL for output to allow the compositor to decide which 57 | output to use. Generally this will be the one that the user most 58 | recently interacted with. 59 | 60 | Clients can specify a namespace that defines the purpose of the layer 61 | surface. 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | These values indicate which layers a surface can be rendered in. They 79 | are ordered by z depth, bottom-most first. Traditional shell surfaces 80 | will typically be rendered between the bottom and top layers. 81 | Fullscreen shell surfaces are typically rendered at the top layer. 82 | Multiple surfaces can share a single layer, and ordering within a 83 | single layer is undefined. 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | This request indicates that the client will not use the layer_shell 97 | object any more. Objects that have been created through this instance 98 | are not affected. 99 | 100 | 101 | 102 | 103 | 104 | 105 | An interface that may be implemented by a wl_surface, for surfaces that 106 | are designed to be rendered as a layer of a stacked desktop-like 107 | environment. 108 | 109 | Layer surface state (layer, size, anchor, exclusive zone, 110 | margin, interactivity) is double-buffered, and will be applied at the 111 | time wl_surface.commit of the corresponding wl_surface is called. 112 | 113 | Attaching a null buffer to a layer surface unmaps it. 114 | 115 | Unmapping a layer_surface means that the surface cannot be shown by the 116 | compositor until it is explicitly mapped again. The layer_surface 117 | returns to the state it had right after layer_shell.get_layer_surface. 118 | The client can re-map the surface by performing a commit without any 119 | buffer attached, waiting for a configure event and handling it as usual. 120 | 121 | 122 | 123 | 124 | Sets the size of the surface in surface-local coordinates. The 125 | compositor will display the surface centered with respect to its 126 | anchors. 127 | 128 | If you pass 0 for either value, the compositor will assign it and 129 | inform you of the assignment in the configure event. You must set your 130 | anchor to opposite edges in the dimensions you omit; not doing so is a 131 | protocol error. Both values are 0 by default. 132 | 133 | Size is double-buffered, see wl_surface.commit. 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Requests that the compositor anchor the surface to the specified edges 142 | and corners. If two orthogonal edges are specified (e.g. 'top' and 143 | 'left'), then the anchor point will be the intersection of the edges 144 | (e.g. the top left corner of the output); otherwise the anchor point 145 | will be centered on that edge, or in the center if none is specified. 146 | 147 | Anchor is double-buffered, see wl_surface.commit. 148 | 149 | 150 | 151 | 152 | 153 | 154 | Requests that the compositor avoids occluding an area with other 155 | surfaces. The compositor's use of this information is 156 | implementation-dependent - do not assume that this region will not 157 | actually be occluded. 158 | 159 | A positive value is only meaningful if the surface is anchored to one 160 | edge or an edge and both perpendicular edges. If the surface is not 161 | anchored, anchored to only two perpendicular edges (a corner), anchored 162 | to only two parallel edges or anchored to all edges, a positive value 163 | will be treated the same as zero. 164 | 165 | A positive zone is the distance from the edge in surface-local 166 | coordinates to consider exclusive. 167 | 168 | Surfaces that do not wish to have an exclusive zone may instead specify 169 | how they should interact with surfaces that do. If set to zero, the 170 | surface indicates that it would like to be moved to avoid occluding 171 | surfaces with a positive exclusive zone. If set to -1, the surface 172 | indicates that it would not like to be moved to accommodate for other 173 | surfaces, and the compositor should extend it all the way to the edges 174 | it is anchored to. 175 | 176 | For example, a panel might set its exclusive zone to 10, so that 177 | maximized shell surfaces are not shown on top of it. A notification 178 | might set its exclusive zone to 0, so that it is moved to avoid 179 | occluding the panel, but shell surfaces are shown underneath it. A 180 | wallpaper or lock screen might set their exclusive zone to -1, so that 181 | they stretch below or over the panel. 182 | 183 | The default value is 0. 184 | 185 | Exclusive zone is double-buffered, see wl_surface.commit. 186 | 187 | 188 | 189 | 190 | 191 | 192 | Requests that the surface be placed some distance away from the anchor 193 | point on the output, in surface-local coordinates. Setting this value 194 | for edges you are not anchored to has no effect. 195 | 196 | The exclusive zone includes the margin. 197 | 198 | Margin is double-buffered, see wl_surface.commit. 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | Types of keyboard interaction possible for layer shell surfaces. The 209 | rationale for this is twofold: (1) some applications are not interested 210 | in keyboard events and not allowing them to be focused can improve the 211 | desktop experience; (2) some applications will want to take exclusive 212 | keyboard focus. 213 | 214 | 215 | 216 | 217 | This value indicates that this surface is not interested in keyboard 218 | events and the compositor should never assign it the keyboard focus. 219 | 220 | This is the default value, set for newly created layer shell surfaces. 221 | 222 | This is useful for e.g. desktop widgets that display information or 223 | only have interaction with non-keyboard input devices. 224 | 225 | 226 | 227 | 228 | Request exclusive keyboard focus if this surface is above the shell surface layer. 229 | 230 | For the top and overlay layers, the seat will always give 231 | exclusive keyboard focus to the top-most layer which has keyboard 232 | interactivity set to exclusive. If this layer contains multiple 233 | surfaces with keyboard interactivity set to exclusive, the compositor 234 | determines the one receiving keyboard events in an implementation- 235 | defined manner. In this case, no guarantee is made when this surface 236 | will receive keyboard focus (if ever). 237 | 238 | For the bottom and background layers, the compositor is allowed to use 239 | normal focus semantics. 240 | 241 | This setting is mainly intended for applications that need to ensure 242 | they receive all keyboard events, such as a lock screen or a password 243 | prompt. 244 | 245 | 246 | 247 | 248 | This requests the compositor to allow this surface to be focused and 249 | unfocused by the user in an implementation-defined manner. The user 250 | should be able to unfocus this surface even regardless of the layer 251 | it is on. 252 | 253 | Typically, the compositor will want to use its normal mechanism to 254 | manage keyboard focus between layer shell surfaces with this setting 255 | and regular toplevels on the desktop layer (e.g. click to focus). 256 | Nevertheless, it is possible for a compositor to require a special 257 | interaction to focus or unfocus layer shell surfaces (e.g. requiring 258 | a click even if focus follows the mouse normally, or providing a 259 | keybinding to switch focus between layers). 260 | 261 | This setting is mainly intended for desktop shell components (e.g. 262 | panels) that allow keyboard interaction. Using this option can allow 263 | implementing a desktop shell that can be fully usable without the 264 | mouse. 265 | 266 | 267 | 268 | 269 | 270 | 271 | Set how keyboard events are delivered to this surface. By default, 272 | layer shell surfaces do not receive keyboard events; this request can 273 | be used to change this. 274 | 275 | This setting is inherited by child surfaces set by the get_popup 276 | request. 277 | 278 | Layer surfaces receive pointer, touch, and tablet events normally. If 279 | you do not want to receive them, set the input region on your surface 280 | to an empty region. 281 | 282 | Keyboard interactivity is double-buffered, see wl_surface.commit. 283 | 284 | 285 | 286 | 287 | 288 | 289 | This assigns an xdg_popup's parent to this layer_surface. This popup 290 | should have been created via xdg_surface::get_popup with the parent set 291 | to NULL, and this request must be invoked before committing the popup's 292 | initial state. 293 | 294 | See the documentation of xdg_popup for more details about what an 295 | xdg_popup is and how it is used. 296 | 297 | 298 | 299 | 300 | 301 | 302 | When a configure event is received, if a client commits the 303 | surface in response to the configure event, then the client 304 | must make an ack_configure request sometime before the commit 305 | request, passing along the serial of the configure event. 306 | 307 | If the client receives multiple configure events before it 308 | can respond to one, it only has to ack the last configure event. 309 | 310 | A client is not required to commit immediately after sending 311 | an ack_configure request - it may even ack_configure several times 312 | before its next surface commit. 313 | 314 | A client may send multiple ack_configure requests before committing, but 315 | only the last request sent before a commit indicates which configure 316 | event the client really is responding to. 317 | 318 | 319 | 320 | 321 | 322 | 323 | This request destroys the layer surface. 324 | 325 | 326 | 327 | 328 | 329 | The configure event asks the client to resize its surface. 330 | 331 | Clients should arrange their surface for the new states, and then send 332 | an ack_configure request with the serial sent in this configure event at 333 | some point before committing the new surface. 334 | 335 | The client is free to dismiss all but the last configure event it 336 | received. 337 | 338 | The width and height arguments specify the size of the window in 339 | surface-local coordinates. 340 | 341 | The size is a hint, in the sense that the client is free to ignore it if 342 | it doesn't resize, pick a smaller size (to satisfy aspect ratio or 343 | resize in steps of NxM pixels). If the client picks a smaller size and 344 | is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the 345 | surface will be centered on this axis. 346 | 347 | If the width or height arguments are zero, it means the client should 348 | decide its own window dimension. 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | The closed event is sent by the compositor when the surface will no 358 | longer be shown. The output may have been destroyed or the user may 359 | have asked for it to be removed. Further changes to the surface will be 360 | ignored. The client should destroy the resource after receiving this 361 | event, and create a new surface if they so choose. 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | Change the layer that the surface is rendered on. 384 | 385 | Layer is double-buffered, see wl_surface.commit. 386 | 387 | 388 | 389 | 390 | 391 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | dwl - dwm for Wayland 2 | 3 | Copyright © 2020 dwl team 4 | 5 | See also the files LICENSE.tinywl, LICENSE.dwm and LICENSE.sway. 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | ---- 18 | 19 | GNU GENERAL PUBLIC LICENSE 20 | Version 3, 29 June 2007 21 | 22 | Copyright (C) 2007 Free Software Foundation, Inc. 23 | Everyone is permitted to copy and distribute verbatim copies 24 | of this license document, but changing it is not allowed. 25 | 26 | Preamble 27 | 28 | The GNU General Public License is a free, copyleft license for 29 | software and other kinds of works. 30 | 31 | The licenses for most software and other practical works are designed 32 | to take away your freedom to share and change the works. By contrast, 33 | the GNU General Public License is intended to guarantee your freedom to 34 | share and change all versions of a program--to make sure it remains free 35 | software for all its users. We, the Free Software Foundation, use the 36 | GNU General Public License for most of our software; it applies also to 37 | any other work released this way by its authors. You can apply it to 38 | your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Our General Public Licenses are designed to make sure that you 42 | have the freedom to distribute copies of free software (and charge for 43 | them if you wish), that you receive source code or can get it if you 44 | want it, that you can change the software or use pieces of it in new 45 | free programs, and that you know you can do these things. 46 | 47 | To protect your rights, we need to prevent others from denying you 48 | these rights or asking you to surrender the rights. Therefore, you have 49 | certain responsibilities if you distribute copies of the software, or if 50 | you modify it: responsibilities to respect the freedom of others. 51 | 52 | For example, if you distribute copies of such a program, whether 53 | gratis or for a fee, you must pass on to the recipients the same 54 | freedoms that you received. You must make sure that they, too, receive 55 | or can get the source code. And you must show them these terms so they 56 | know their rights. 57 | 58 | Developers that use the GNU GPL protect your rights with two steps: 59 | (1) assert copyright on the software, and (2) offer you this License 60 | giving you legal permission to copy, distribute and/or modify it. 61 | 62 | For the developers' and authors' protection, the GPL clearly explains 63 | that there is no warranty for this free software. For both users' and 64 | authors' sake, the GPL requires that modified versions be marked as 65 | changed, so that their problems will not be attributed erroneously to 66 | authors of previous versions. 67 | 68 | Some devices are designed to deny users access to install or run 69 | modified versions of the software inside them, although the manufacturer 70 | can do so. This is fundamentally incompatible with the aim of 71 | protecting users' freedom to change the software. The systematic 72 | pattern of such abuse occurs in the area of products for individuals to 73 | use, which is precisely where it is most unacceptable. Therefore, we 74 | have designed this version of the GPL to prohibit the practice for those 75 | products. If such problems arise substantially in other domains, we 76 | stand ready to extend this provision to those domains in future versions 77 | of the GPL, as needed to protect the freedom of users. 78 | 79 | Finally, every program is threatened constantly by software patents. 80 | States should not allow patents to restrict development and use of 81 | software on general-purpose computers, but in those that do, we wish to 82 | avoid the special danger that patents applied to a free program could 83 | make it effectively proprietary. To prevent this, the GPL assures that 84 | patents cannot be used to render the program non-free. 85 | 86 | The precise terms and conditions for copying, distribution and 87 | modification follow. 88 | 89 | TERMS AND CONDITIONS 90 | 91 | 0. Definitions. 92 | 93 | "This License" refers to version 3 of the GNU General Public License. 94 | 95 | "Copyright" also means copyright-like laws that apply to other kinds of 96 | works, such as semiconductor masks. 97 | 98 | "The Program" refers to any copyrightable work licensed under this 99 | License. Each licensee is addressed as "you". "Licensees" and 100 | "recipients" may be individuals or organizations. 101 | 102 | To "modify" a work means to copy from or adapt all or part of the work 103 | in a fashion requiring copyright permission, other than the making of an 104 | exact copy. The resulting work is called a "modified version" of the 105 | earlier work or a work "based on" the earlier work. 106 | 107 | A "covered work" means either the unmodified Program or a work based 108 | on the Program. 109 | 110 | To "propagate" a work means to do anything with it that, without 111 | permission, would make you directly or secondarily liable for 112 | infringement under applicable copyright law, except executing it on a 113 | computer or modifying a private copy. Propagation includes copying, 114 | distribution (with or without modification), making available to the 115 | public, and in some countries other activities as well. 116 | 117 | To "convey" a work means any kind of propagation that enables other 118 | parties to make or receive copies. Mere interaction with a user through 119 | a computer network, with no transfer of a copy, is not conveying. 120 | 121 | An interactive user interface displays "Appropriate Legal Notices" 122 | to the extent that it includes a convenient and prominently visible 123 | feature that (1) displays an appropriate copyright notice, and (2) 124 | tells the user that there is no warranty for the work (except to the 125 | extent that warranties are provided), that licensees may convey the 126 | work under this License, and how to view a copy of this License. If 127 | the interface presents a list of user commands or options, such as a 128 | menu, a prominent item in the list meets this criterion. 129 | 130 | 1. Source Code. 131 | 132 | The "source code" for a work means the preferred form of the work 133 | for making modifications to it. "Object code" means any non-source 134 | form of a work. 135 | 136 | A "Standard Interface" means an interface that either is an official 137 | standard defined by a recognized standards body, or, in the case of 138 | interfaces specified for a particular programming language, one that 139 | is widely used among developers working in that language. 140 | 141 | The "System Libraries" of an executable work include anything, other 142 | than the work as a whole, that (a) is included in the normal form of 143 | packaging a Major Component, but which is not part of that Major 144 | Component, and (b) serves only to enable use of the work with that 145 | Major Component, or to implement a Standard Interface for which an 146 | implementation is available to the public in source code form. A 147 | "Major Component", in this context, means a major essential component 148 | (kernel, window system, and so on) of the specific operating system 149 | (if any) on which the executable work runs, or a compiler used to 150 | produce the work, or an object code interpreter used to run it. 151 | 152 | The "Corresponding Source" for a work in object code form means all 153 | the source code needed to generate, install, and (for an executable 154 | work) run the object code and to modify the work, including scripts to 155 | control those activities. However, it does not include the work's 156 | System Libraries, or general-purpose tools or generally available free 157 | programs which are used unmodified in performing those activities but 158 | which are not part of the work. For example, Corresponding Source 159 | includes interface definition files associated with source files for 160 | the work, and the source code for shared libraries and dynamically 161 | linked subprograms that the work is specifically designed to require, 162 | such as by intimate data communication or control flow between those 163 | subprograms and other parts of the work. 164 | 165 | The Corresponding Source need not include anything that users 166 | can regenerate automatically from other parts of the Corresponding 167 | Source. 168 | 169 | The Corresponding Source for a work in source code form is that 170 | same work. 171 | 172 | 2. Basic Permissions. 173 | 174 | All rights granted under this License are granted for the term of 175 | copyright on the Program, and are irrevocable provided the stated 176 | conditions are met. This License explicitly affirms your unlimited 177 | permission to run the unmodified Program. The output from running a 178 | covered work is covered by this License only if the output, given its 179 | content, constitutes a covered work. This License acknowledges your 180 | rights of fair use or other equivalent, as provided by copyright law. 181 | 182 | You may make, run and propagate covered works that you do not 183 | convey, without conditions so long as your license otherwise remains 184 | in force. You may convey covered works to others for the sole purpose 185 | of having them make modifications exclusively for you, or provide you 186 | with facilities for running those works, provided that you comply with 187 | the terms of this License in conveying all material for which you do 188 | not control copyright. Those thus making or running the covered works 189 | for you must do so exclusively on your behalf, under your direction 190 | and control, on terms that prohibit them from making any copies of 191 | your copyrighted material outside their relationship with you. 192 | 193 | Conveying under any other circumstances is permitted solely under 194 | the conditions stated below. Sublicensing is not allowed; section 10 195 | makes it unnecessary. 196 | 197 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 198 | 199 | No covered work shall be deemed part of an effective technological 200 | measure under any applicable law fulfilling obligations under article 201 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 202 | similar laws prohibiting or restricting circumvention of such 203 | measures. 204 | 205 | When you convey a covered work, you waive any legal power to forbid 206 | circumvention of technological measures to the extent such circumvention 207 | is effected by exercising rights under this License with respect to 208 | the covered work, and you disclaim any intention to limit operation or 209 | modification of the work as a means of enforcing, against the work's 210 | users, your or third parties' legal rights to forbid circumvention of 211 | technological measures. 212 | 213 | 4. Conveying Verbatim Copies. 214 | 215 | You may convey verbatim copies of the Program's source code as you 216 | receive it, in any medium, provided that you conspicuously and 217 | appropriately publish on each copy an appropriate copyright notice; 218 | keep intact all notices stating that this License and any 219 | non-permissive terms added in accord with section 7 apply to the code; 220 | keep intact all notices of the absence of any warranty; and give all 221 | recipients a copy of this License along with the Program. 222 | 223 | You may charge any price or no price for each copy that you convey, 224 | and you may offer support or warranty protection for a fee. 225 | 226 | 5. Conveying Modified Source Versions. 227 | 228 | You may convey a work based on the Program, or the modifications to 229 | produce it from the Program, in the form of source code under the 230 | terms of section 4, provided that you also meet all of these conditions: 231 | 232 | a) The work must carry prominent notices stating that you modified 233 | it, and giving a relevant date. 234 | 235 | b) The work must carry prominent notices stating that it is 236 | released under this License and any conditions added under section 237 | 7. This requirement modifies the requirement in section 4 to 238 | "keep intact all notices". 239 | 240 | c) You must license the entire work, as a whole, under this 241 | License to anyone who comes into possession of a copy. This 242 | License will therefore apply, along with any applicable section 7 243 | additional terms, to the whole of the work, and all its parts, 244 | regardless of how they are packaged. This License gives no 245 | permission to license the work in any other way, but it does not 246 | invalidate such permission if you have separately received it. 247 | 248 | d) If the work has interactive user interfaces, each must display 249 | Appropriate Legal Notices; however, if the Program has interactive 250 | interfaces that do not display Appropriate Legal Notices, your 251 | work need not make them do so. 252 | 253 | A compilation of a covered work with other separate and independent 254 | works, which are not by their nature extensions of the covered work, 255 | and which are not combined with it such as to form a larger program, 256 | in or on a volume of a storage or distribution medium, is called an 257 | "aggregate" if the compilation and its resulting copyright are not 258 | used to limit the access or legal rights of the compilation's users 259 | beyond what the individual works permit. Inclusion of a covered work 260 | in an aggregate does not cause this License to apply to the other 261 | parts of the aggregate. 262 | 263 | 6. Conveying Non-Source Forms. 264 | 265 | You may convey a covered work in object code form under the terms 266 | of sections 4 and 5, provided that you also convey the 267 | machine-readable Corresponding Source under the terms of this License, 268 | in one of these ways: 269 | 270 | a) Convey the object code in, or embodied in, a physical product 271 | (including a physical distribution medium), accompanied by the 272 | Corresponding Source fixed on a durable physical medium 273 | customarily used for software interchange. 274 | 275 | b) Convey the object code in, or embodied in, a physical product 276 | (including a physical distribution medium), accompanied by a 277 | written offer, valid for at least three years and valid for as 278 | long as you offer spare parts or customer support for that product 279 | model, to give anyone who possesses the object code either (1) a 280 | copy of the Corresponding Source for all the software in the 281 | product that is covered by this License, on a durable physical 282 | medium customarily used for software interchange, for a price no 283 | more than your reasonable cost of physically performing this 284 | conveying of source, or (2) access to copy the 285 | Corresponding Source from a network server at no charge. 286 | 287 | c) Convey individual copies of the object code with a copy of the 288 | written offer to provide the Corresponding Source. This 289 | alternative is allowed only occasionally and noncommercially, and 290 | only if you received the object code with such an offer, in accord 291 | with subsection 6b. 292 | 293 | d) Convey the object code by offering access from a designated 294 | place (gratis or for a charge), and offer equivalent access to the 295 | Corresponding Source in the same way through the same place at no 296 | further charge. You need not require recipients to copy the 297 | Corresponding Source along with the object code. If the place to 298 | copy the object code is a network server, the Corresponding Source 299 | may be on a different server (operated by you or a third party) 300 | that supports equivalent copying facilities, provided you maintain 301 | clear directions next to the object code saying where to find the 302 | Corresponding Source. Regardless of what server hosts the 303 | Corresponding Source, you remain obligated to ensure that it is 304 | available for as long as needed to satisfy these requirements. 305 | 306 | e) Convey the object code using peer-to-peer transmission, provided 307 | you inform other peers where the object code and Corresponding 308 | Source of the work are being offered to the general public at no 309 | charge under subsection 6d. 310 | 311 | A separable portion of the object code, whose source code is excluded 312 | from the Corresponding Source as a System Library, need not be 313 | included in conveying the object code work. 314 | 315 | A "User Product" is either (1) a "consumer product", which means any 316 | tangible personal property which is normally used for personal, family, 317 | or household purposes, or (2) anything designed or sold for incorporation 318 | into a dwelling. In determining whether a product is a consumer product, 319 | doubtful cases shall be resolved in favor of coverage. For a particular 320 | product received by a particular user, "normally used" refers to a 321 | typical or common use of that class of product, regardless of the status 322 | of the particular user or of the way in which the particular user 323 | actually uses, or expects or is expected to use, the product. A product 324 | is a consumer product regardless of whether the product has substantial 325 | commercial, industrial or non-consumer uses, unless such uses represent 326 | the only significant mode of use of the product. 327 | 328 | "Installation Information" for a User Product means any methods, 329 | procedures, authorization keys, or other information required to install 330 | and execute modified versions of a covered work in that User Product from 331 | a modified version of its Corresponding Source. The information must 332 | suffice to ensure that the continued functioning of the modified object 333 | code is in no case prevented or interfered with solely because 334 | modification has been made. 335 | 336 | If you convey an object code work under this section in, or with, or 337 | specifically for use in, a User Product, and the conveying occurs as 338 | part of a transaction in which the right of possession and use of the 339 | User Product is transferred to the recipient in perpetuity or for a 340 | fixed term (regardless of how the transaction is characterized), the 341 | Corresponding Source conveyed under this section must be accompanied 342 | by the Installation Information. But this requirement does not apply 343 | if neither you nor any third party retains the ability to install 344 | modified object code on the User Product (for example, the work has 345 | been installed in ROM). 346 | 347 | The requirement to provide Installation Information does not include a 348 | requirement to continue to provide support service, warranty, or updates 349 | for a work that has been modified or installed by the recipient, or for 350 | the User Product in which it has been modified or installed. Access to a 351 | network may be denied when the modification itself materially and 352 | adversely affects the operation of the network or violates the rules and 353 | protocols for communication across the network. 354 | 355 | Corresponding Source conveyed, and Installation Information provided, 356 | in accord with this section must be in a format that is publicly 357 | documented (and with an implementation available to the public in 358 | source code form), and must require no special password or key for 359 | unpacking, reading or copying. 360 | 361 | 7. Additional Terms. 362 | 363 | "Additional permissions" are terms that supplement the terms of this 364 | License by making exceptions from one or more of its conditions. 365 | Additional permissions that are applicable to the entire Program shall 366 | be treated as though they were included in this License, to the extent 367 | that they are valid under applicable law. If additional permissions 368 | apply only to part of the Program, that part may be used separately 369 | under those permissions, but the entire Program remains governed by 370 | this License without regard to the additional permissions. 371 | 372 | When you convey a copy of a covered work, you may at your option 373 | remove any additional permissions from that copy, or from any part of 374 | it. (Additional permissions may be written to require their own 375 | removal in certain cases when you modify the work.) You may place 376 | additional permissions on material, added by you to a covered work, 377 | for which you have or can give appropriate copyright permission. 378 | 379 | Notwithstanding any other provision of this License, for material you 380 | add to a covered work, you may (if authorized by the copyright holders of 381 | that material) supplement the terms of this License with terms: 382 | 383 | a) Disclaiming warranty or limiting liability differently from the 384 | terms of sections 15 and 16 of this License; or 385 | 386 | b) Requiring preservation of specified reasonable legal notices or 387 | author attributions in that material or in the Appropriate Legal 388 | Notices displayed by works containing it; or 389 | 390 | c) Prohibiting misrepresentation of the origin of that material, or 391 | requiring that modified versions of such material be marked in 392 | reasonable ways as different from the original version; or 393 | 394 | d) Limiting the use for publicity purposes of names of licensors or 395 | authors of the material; or 396 | 397 | e) Declining to grant rights under trademark law for use of some 398 | trade names, trademarks, or service marks; or 399 | 400 | f) Requiring indemnification of licensors and authors of that 401 | material by anyone who conveys the material (or modified versions of 402 | it) with contractual assumptions of liability to the recipient, for 403 | any liability that these contractual assumptions directly impose on 404 | those licensors and authors. 405 | 406 | All other non-permissive additional terms are considered "further 407 | restrictions" within the meaning of section 10. If the Program as you 408 | received it, or any part of it, contains a notice stating that it is 409 | governed by this License along with a term that is a further 410 | restriction, you may remove that term. If a license document contains 411 | a further restriction but permits relicensing or conveying under this 412 | License, you may add to a covered work material governed by the terms 413 | of that license document, provided that the further restriction does 414 | not survive such relicensing or conveying. 415 | 416 | If you add terms to a covered work in accord with this section, you 417 | must place, in the relevant source files, a statement of the 418 | additional terms that apply to those files, or a notice indicating 419 | where to find the applicable terms. 420 | 421 | Additional terms, permissive or non-permissive, may be stated in the 422 | form of a separately written license, or stated as exceptions; 423 | the above requirements apply either way. 424 | 425 | 8. Termination. 426 | 427 | You may not propagate or modify a covered work except as expressly 428 | provided under this License. Any attempt otherwise to propagate or 429 | modify it is void, and will automatically terminate your rights under 430 | this License (including any patent licenses granted under the third 431 | paragraph of section 11). 432 | 433 | However, if you cease all violation of this License, then your 434 | license from a particular copyright holder is reinstated (a) 435 | provisionally, unless and until the copyright holder explicitly and 436 | finally terminates your license, and (b) permanently, if the copyright 437 | holder fails to notify you of the violation by some reasonable means 438 | prior to 60 days after the cessation. 439 | 440 | Moreover, your license from a particular copyright holder is 441 | reinstated permanently if the copyright holder notifies you of the 442 | violation by some reasonable means, this is the first time you have 443 | received notice of violation of this License (for any work) from that 444 | copyright holder, and you cure the violation prior to 30 days after 445 | your receipt of the notice. 446 | 447 | Termination of your rights under this section does not terminate the 448 | licenses of parties who have received copies or rights from you under 449 | this License. If your rights have been terminated and not permanently 450 | reinstated, you do not qualify to receive new licenses for the same 451 | material under section 10. 452 | 453 | 9. Acceptance Not Required for Having Copies. 454 | 455 | You are not required to accept this License in order to receive or 456 | run a copy of the Program. Ancillary propagation of a covered work 457 | occurring solely as a consequence of using peer-to-peer transmission 458 | to receive a copy likewise does not require acceptance. However, 459 | nothing other than this License grants you permission to propagate or 460 | modify any covered work. These actions infringe copyright if you do 461 | not accept this License. Therefore, by modifying or propagating a 462 | covered work, you indicate your acceptance of this License to do so. 463 | 464 | 10. Automatic Licensing of Downstream Recipients. 465 | 466 | Each time you convey a covered work, the recipient automatically 467 | receives a license from the original licensors, to run, modify and 468 | propagate that work, subject to this License. You are not responsible 469 | for enforcing compliance by third parties with this License. 470 | 471 | An "entity transaction" is a transaction transferring control of an 472 | organization, or substantially all assets of one, or subdividing an 473 | organization, or merging organizations. If propagation of a covered 474 | work results from an entity transaction, each party to that 475 | transaction who receives a copy of the work also receives whatever 476 | licenses to the work the party's predecessor in interest had or could 477 | give under the previous paragraph, plus a right to possession of the 478 | Corresponding Source of the work from the predecessor in interest, if 479 | the predecessor has it or can get it with reasonable efforts. 480 | 481 | You may not impose any further restrictions on the exercise of the 482 | rights granted or affirmed under this License. For example, you may 483 | not impose a license fee, royalty, or other charge for exercise of 484 | rights granted under this License, and you may not initiate litigation 485 | (including a cross-claim or counterclaim in a lawsuit) alleging that 486 | any patent claim is infringed by making, using, selling, offering for 487 | sale, or importing the Program or any portion of it. 488 | 489 | 11. Patents. 490 | 491 | A "contributor" is a copyright holder who authorizes use under this 492 | License of the Program or a work on which the Program is based. The 493 | work thus licensed is called the contributor's "contributor version". 494 | 495 | A contributor's "essential patent claims" are all patent claims 496 | owned or controlled by the contributor, whether already acquired or 497 | hereafter acquired, that would be infringed by some manner, permitted 498 | by this License, of making, using, or selling its contributor version, 499 | but do not include claims that would be infringed only as a 500 | consequence of further modification of the contributor version. For 501 | purposes of this definition, "control" includes the right to grant 502 | patent sublicenses in a manner consistent with the requirements of 503 | this License. 504 | 505 | Each contributor grants you a non-exclusive, worldwide, royalty-free 506 | patent license under the contributor's essential patent claims, to 507 | make, use, sell, offer for sale, import and otherwise run, modify and 508 | propagate the contents of its contributor version. 509 | 510 | In the following three paragraphs, a "patent license" is any express 511 | agreement or commitment, however denominated, not to enforce a patent 512 | (such as an express permission to practice a patent or covenant not to 513 | sue for patent infringement). To "grant" such a patent license to a 514 | party means to make such an agreement or commitment not to enforce a 515 | patent against the party. 516 | 517 | If you convey a covered work, knowingly relying on a patent license, 518 | and the Corresponding Source of the work is not available for anyone 519 | to copy, free of charge and under the terms of this License, through a 520 | publicly available network server or other readily accessible means, 521 | then you must either (1) cause the Corresponding Source to be so 522 | available, or (2) arrange to deprive yourself of the benefit of the 523 | patent license for this particular work, or (3) arrange, in a manner 524 | consistent with the requirements of this License, to extend the patent 525 | license to downstream recipients. "Knowingly relying" means you have 526 | actual knowledge that, but for the patent license, your conveying the 527 | covered work in a country, or your recipient's use of the covered work 528 | in a country, would infringe one or more identifiable patents in that 529 | country that you have reason to believe are valid. 530 | 531 | If, pursuant to or in connection with a single transaction or 532 | arrangement, you convey, or propagate by procuring conveyance of, a 533 | covered work, and grant a patent license to some of the parties 534 | receiving the covered work authorizing them to use, propagate, modify 535 | or convey a specific copy of the covered work, then the patent license 536 | you grant is automatically extended to all recipients of the covered 537 | work and works based on it. 538 | 539 | A patent license is "discriminatory" if it does not include within 540 | the scope of its coverage, prohibits the exercise of, or is 541 | conditioned on the non-exercise of one or more of the rights that are 542 | specifically granted under this License. You may not convey a covered 543 | work if you are a party to an arrangement with a third party that is 544 | in the business of distributing software, under which you make payment 545 | to the third party based on the extent of your activity of conveying 546 | the work, and under which the third party grants, to any of the 547 | parties who would receive the covered work from you, a discriminatory 548 | patent license (a) in connection with copies of the covered work 549 | conveyed by you (or copies made from those copies), or (b) primarily 550 | for and in connection with specific products or compilations that 551 | contain the covered work, unless you entered into that arrangement, 552 | or that patent license was granted, prior to 28 March 2007. 553 | 554 | Nothing in this License shall be construed as excluding or limiting 555 | any implied license or other defenses to infringement that may 556 | otherwise be available to you under applicable patent law. 557 | 558 | 12. No Surrender of Others' Freedom. 559 | 560 | If conditions are imposed on you (whether by court order, agreement or 561 | otherwise) that contradict the conditions of this License, they do not 562 | excuse you from the conditions of this License. If you cannot convey a 563 | covered work so as to satisfy simultaneously your obligations under this 564 | License and any other pertinent obligations, then as a consequence you may 565 | not convey it at all. For example, if you agree to terms that obligate you 566 | to collect a royalty for further conveying from those to whom you convey 567 | the Program, the only way you could satisfy both those terms and this 568 | License would be to refrain entirely from conveying the Program. 569 | 570 | 13. Use with the GNU Affero General Public License. 571 | 572 | Notwithstanding any other provision of this License, you have 573 | permission to link or combine any covered work with a work licensed 574 | under version 3 of the GNU Affero General Public License into a single 575 | combined work, and to convey the resulting work. The terms of this 576 | License will continue to apply to the part which is the covered work, 577 | but the special requirements of the GNU Affero General Public License, 578 | section 13, concerning interaction through a network will apply to the 579 | combination as such. 580 | 581 | 14. Revised Versions of this License. 582 | 583 | The Free Software Foundation may publish revised and/or new versions of 584 | the GNU General Public License from time to time. Such new versions will 585 | be similar in spirit to the present version, but may differ in detail to 586 | address new problems or concerns. 587 | 588 | Each version is given a distinguishing version number. If the 589 | Program specifies that a certain numbered version of the GNU General 590 | Public License "or any later version" applies to it, you have the 591 | option of following the terms and conditions either of that numbered 592 | version or of any later version published by the Free Software 593 | Foundation. If the Program does not specify a version number of the 594 | GNU General Public License, you may choose any version ever published 595 | by the Free Software Foundation. 596 | 597 | If the Program specifies that a proxy can decide which future 598 | versions of the GNU General Public License can be used, that proxy's 599 | public statement of acceptance of a version permanently authorizes you 600 | to choose that version for the Program. 601 | 602 | Later license versions may give you additional or different 603 | permissions. However, no additional obligations are imposed on any 604 | author or copyright holder as a result of your choosing to follow a 605 | later version. 606 | 607 | 15. Disclaimer of Warranty. 608 | 609 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 610 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 611 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 612 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 613 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 614 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 615 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 616 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 617 | 618 | 16. Limitation of Liability. 619 | 620 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 621 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 622 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 623 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 624 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 625 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 626 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 627 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 628 | SUCH DAMAGES. 629 | 630 | 17. Interpretation of Sections 15 and 16. 631 | 632 | If the disclaimer of warranty and limitation of liability provided 633 | above cannot be given local legal effect according to their terms, 634 | reviewing courts shall apply local law that most closely approximates 635 | an absolute waiver of all civil liability in connection with the 636 | Program, unless a warranty or assumption of liability accompanies a 637 | copy of the Program in return for a fee. 638 | 639 | END OF TERMS AND CONDITIONS 640 | 641 | How to Apply These Terms to Your New Programs 642 | 643 | If you develop a new program, and you want it to be of the greatest 644 | possible use to the public, the best way to achieve this is to make it 645 | free software which everyone can redistribute and change under these terms. 646 | 647 | To do so, attach the following notices to the program. It is safest 648 | to attach them to the start of each source file to most effectively 649 | state the exclusion of warranty; and each file should have at least 650 | the "copyright" line and a pointer to where the full notice is found. 651 | 652 | 653 | Copyright (C) 654 | 655 | This program is free software: you can redistribute it and/or modify 656 | it under the terms of the GNU General Public License as published by 657 | the Free Software Foundation, either version 3 of the License, or 658 | (at your option) any later version. 659 | 660 | This program is distributed in the hope that it will be useful, 661 | but WITHOUT ANY WARRANTY; without even the implied warranty of 662 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 663 | GNU General Public License for more details. 664 | 665 | You should have received a copy of the GNU General Public License 666 | along with this program. If not, see . 667 | 668 | Also add information on how to contact you by electronic and paper mail. 669 | 670 | If the program does terminal interaction, make it output a short 671 | notice like this when it starts in an interactive mode: 672 | 673 | Copyright (C) 674 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 675 | This is free software, and you are welcome to redistribute it 676 | under certain conditions; type `show c' for details. 677 | 678 | The hypothetical commands `show w' and `show c' should show the appropriate 679 | parts of the General Public License. Of course, your program's commands 680 | might be different; for a GUI interface, you would use an "about box". 681 | 682 | You should also get your employer (if you work as a programmer) or school, 683 | if any, to sign a "copyright disclaimer" for the program, if necessary. 684 | For more information on this, and how to apply and follow the GNU GPL, see 685 | . 686 | 687 | The GNU General Public License does not permit incorporating your program 688 | into proprietary programs. If your program is a subroutine library, you 689 | may consider it more useful to permit linking proprietary applications with 690 | the library. If this is what you want to do, use the GNU Lesser General 691 | Public License instead of this License. But first, please read 692 | . 693 | -------------------------------------------------------------------------------- /dwl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * See LICENSE file for copyright and license details. 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #ifdef XWAYLAND 57 | #include 58 | #include 59 | #include 60 | #endif 61 | 62 | #include "util.h" 63 | 64 | /* macros */ 65 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 66 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 67 | #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) 68 | #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) 69 | #define LENGTH(X) (sizeof X / sizeof X[0]) 70 | #define END(A) ((A) + LENGTH(A)) 71 | #define TAGMASK ((1u << TAGCOUNT) - 1) 72 | #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) 73 | #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) 74 | 75 | /* enums */ 76 | enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ 77 | enum { XDGShell, LayerShell, X11Managed, X11Unmanaged }; /* client types */ 78 | enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrFS, LyrTop, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ 79 | #ifdef XWAYLAND 80 | enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, 81 | NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ 82 | #endif 83 | 84 | typedef union { 85 | int i; 86 | uint32_t ui; 87 | float f; 88 | const void *v; 89 | } Arg; 90 | 91 | typedef struct { 92 | unsigned int mod; 93 | unsigned int button; 94 | void (*func)(const Arg *); 95 | const Arg arg; 96 | } Button; 97 | 98 | typedef struct Monitor Monitor; 99 | typedef struct { 100 | /* Must keep these three elements in this order */ 101 | unsigned int type; /* XDGShell or X11* */ 102 | struct wlr_box geom; /* layout-relative, includes border */ 103 | Monitor *mon; 104 | struct wlr_scene_tree *scene; 105 | struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ 106 | struct wlr_scene_tree *scene_surface; 107 | struct wl_list link; 108 | struct wl_list flink; 109 | union { 110 | struct wlr_xdg_surface *xdg; 111 | struct wlr_xwayland_surface *xwayland; 112 | } surface; 113 | struct wl_listener commit; 114 | struct wl_listener map; 115 | struct wl_listener maximize; 116 | struct wl_listener unmap; 117 | struct wl_listener destroy; 118 | struct wl_listener set_title; 119 | struct wl_listener fullscreen; 120 | struct wlr_box prev; /* layout-relative, includes border */ 121 | struct wlr_box bounds; 122 | #ifdef XWAYLAND 123 | struct wl_listener activate; 124 | struct wl_listener associate; 125 | struct wl_listener dissociate; 126 | struct wl_listener configure; 127 | struct wl_listener set_hints; 128 | #endif 129 | unsigned int bw; 130 | uint32_t tags; 131 | int isfloating, isurgent, isfullscreen; 132 | uint32_t resize; /* configure serial of a pending resize */ 133 | } Client; 134 | 135 | typedef struct { 136 | uint32_t mod; 137 | xkb_keysym_t keysym; 138 | void (*func)(const Arg *); 139 | const Arg arg; 140 | } Key; 141 | 142 | typedef struct { 143 | struct wl_list link; 144 | struct wlr_keyboard *wlr_keyboard; 145 | 146 | int nsyms; 147 | const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ 148 | uint32_t mods; /* invalid if nsyms == 0 */ 149 | struct wl_event_source *key_repeat_source; 150 | 151 | struct wl_listener modifiers; 152 | struct wl_listener key; 153 | struct wl_listener destroy; 154 | } Keyboard; 155 | 156 | typedef struct { 157 | /* Must keep these three elements in this order */ 158 | unsigned int type; /* LayerShell */ 159 | struct wlr_box geom; 160 | Monitor *mon; 161 | struct wlr_scene_tree *scene; 162 | struct wlr_scene_tree *popups; 163 | struct wlr_scene_layer_surface_v1 *scene_layer; 164 | struct wl_list link; 165 | int mapped; 166 | struct wlr_layer_surface_v1 *layer_surface; 167 | 168 | struct wl_listener destroy; 169 | struct wl_listener map; 170 | struct wl_listener unmap; 171 | struct wl_listener surface_commit; 172 | } LayerSurface; 173 | 174 | typedef struct { 175 | const char *symbol; 176 | void (*arrange)(Monitor *); 177 | } Layout; 178 | 179 | struct Monitor { 180 | struct wl_list link; 181 | struct wlr_output *wlr_output; 182 | struct wlr_scene_output *scene_output; 183 | struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ 184 | struct wl_listener frame; 185 | struct wl_listener destroy; 186 | struct wl_listener request_state; 187 | struct wl_listener destroy_lock_surface; 188 | struct wlr_session_lock_surface_v1 *lock_surface; 189 | struct wlr_box m; /* monitor area, layout-relative */ 190 | struct wlr_box w; /* window area, layout-relative */ 191 | struct wl_list layers[4]; /* LayerSurface::link */ 192 | const Layout *lt[2]; 193 | unsigned int seltags; 194 | unsigned int sellt; 195 | uint32_t tagset[2]; 196 | double mfact; 197 | int gamma_lut_changed; 198 | int nmaster; 199 | char ltsymbol[16]; 200 | }; 201 | 202 | typedef struct { 203 | const char *name; 204 | float mfact; 205 | int nmaster; 206 | float scale; 207 | const Layout *lt; 208 | enum wl_output_transform rr; 209 | int x, y; 210 | } MonitorRule; 211 | 212 | typedef struct { 213 | const char *id; 214 | const char *title; 215 | uint32_t tags; 216 | int isfloating; 217 | int monitor; 218 | } Rule; 219 | 220 | typedef struct { 221 | struct wlr_scene_tree *scene; 222 | 223 | struct wlr_session_lock_v1 *lock; 224 | struct wl_listener new_surface; 225 | struct wl_listener unlock; 226 | struct wl_listener destroy; 227 | } SessionLock; 228 | 229 | /* function declarations */ 230 | static void applybounds(Client *c, struct wlr_box *bbox); 231 | static void applyrules(Client *c); 232 | static void arrange(Monitor *m); 233 | static void arrangelayer(Monitor *m, struct wl_list *list, 234 | struct wlr_box *usable_area, int exclusive); 235 | static void arrangelayers(Monitor *m); 236 | static void axisnotify(struct wl_listener *listener, void *data); 237 | static void buttonpress(struct wl_listener *listener, void *data); 238 | static void chvt(const Arg *arg); 239 | static void checkidleinhibitor(struct wlr_surface *exclude); 240 | static void cleanup(void); 241 | static void cleanupkeyboard(struct wl_listener *listener, void *data); 242 | static void cleanupmon(struct wl_listener *listener, void *data); 243 | static void closemon(Monitor *m); 244 | static void commitlayersurfacenotify(struct wl_listener *listener, void *data); 245 | static void commitnotify(struct wl_listener *listener, void *data); 246 | static void createdecoration(struct wl_listener *listener, void *data); 247 | static void createidleinhibitor(struct wl_listener *listener, void *data); 248 | static void createkeyboard(struct wlr_keyboard *keyboard); 249 | static void createlayersurface(struct wl_listener *listener, void *data); 250 | static void createlocksurface(struct wl_listener *listener, void *data); 251 | static void createmon(struct wl_listener *listener, void *data); 252 | static void createnotify(struct wl_listener *listener, void *data); 253 | static void createpointer(struct wlr_pointer *pointer); 254 | static void cursorframe(struct wl_listener *listener, void *data); 255 | static void destroydragicon(struct wl_listener *listener, void *data); 256 | static void destroyidleinhibitor(struct wl_listener *listener, void *data); 257 | static void destroylayersurfacenotify(struct wl_listener *listener, void *data); 258 | static void destroylock(SessionLock *lock, int unlocked); 259 | static void destroylocksurface(struct wl_listener *listener, void *data); 260 | static void destroynotify(struct wl_listener *listener, void *data); 261 | static void destroysessionlock(struct wl_listener *listener, void *data); 262 | static void destroysessionmgr(struct wl_listener *listener, void *data); 263 | static Monitor *dirtomon(enum wlr_direction dir); 264 | static void focusclient(Client *c, int lift); 265 | static void focusmon(const Arg *arg); 266 | static void focusstack(const Arg *arg); 267 | static Client *focustop(Monitor *m); 268 | static void fullscreennotify(struct wl_listener *listener, void *data); 269 | static void handlesig(int signo); 270 | static void incnmaster(const Arg *arg); 271 | static void inputdevice(struct wl_listener *listener, void *data); 272 | static int keybinding(uint32_t mods, xkb_keysym_t sym); 273 | static void keypress(struct wl_listener *listener, void *data); 274 | static void keypressmod(struct wl_listener *listener, void *data); 275 | static int keyrepeat(void *data); 276 | static void killclient(const Arg *arg); 277 | static void locksession(struct wl_listener *listener, void *data); 278 | static void maplayersurfacenotify(struct wl_listener *listener, void *data); 279 | static void mapnotify(struct wl_listener *listener, void *data); 280 | static void maximizenotify(struct wl_listener *listener, void *data); 281 | static void monocle(Monitor *m); 282 | static void motionabsolute(struct wl_listener *listener, void *data); 283 | static void motionnotify(uint32_t time); 284 | static void motionrelative(struct wl_listener *listener, void *data); 285 | static void moveresize(const Arg *arg); 286 | static void outputmgrapply(struct wl_listener *listener, void *data); 287 | static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); 288 | static void outputmgrtest(struct wl_listener *listener, void *data); 289 | static void pointerfocus(Client *c, struct wlr_surface *surface, 290 | double sx, double sy, uint32_t time); 291 | static void printstatus(void); 292 | static void quit(const Arg *arg); 293 | static void rendermon(struct wl_listener *listener, void *data); 294 | static void requeststartdrag(struct wl_listener *listener, void *data); 295 | static void requestmonstate(struct wl_listener *listener, void *data); 296 | static void resize(Client *c, struct wlr_box geo, int interact); 297 | static void run(char *startup_cmd); 298 | static void setcursor(struct wl_listener *listener, void *data); 299 | static void setcursorshape(struct wl_listener *listener, void *data); 300 | static void setfloating(Client *c, int floating); 301 | static void setfullscreen(Client *c, int fullscreen); 302 | static void setgamma(struct wl_listener *listener, void *data); 303 | static void setlayout(const Arg *arg); 304 | static void setmfact(const Arg *arg); 305 | static void setmon(Client *c, Monitor *m, uint32_t newtags); 306 | static void setpsel(struct wl_listener *listener, void *data); 307 | static void setsel(struct wl_listener *listener, void *data); 308 | static void setup(void); 309 | static void spawn(const Arg *arg); 310 | static void startdrag(struct wl_listener *listener, void *data); 311 | static void tag(const Arg *arg); 312 | static void tagmon(const Arg *arg); 313 | static void tile(Monitor *m); 314 | static void togglefloating(const Arg *arg); 315 | static void togglefullscreen(const Arg *arg); 316 | static void toggletag(const Arg *arg); 317 | static void toggleview(const Arg *arg); 318 | static void unlocksession(struct wl_listener *listener, void *data); 319 | static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); 320 | static void unmapnotify(struct wl_listener *listener, void *data); 321 | static void updatemons(struct wl_listener *listener, void *data); 322 | static void updatetitle(struct wl_listener *listener, void *data); 323 | static void urgent(struct wl_listener *listener, void *data); 324 | static void view(const Arg *arg); 325 | static void virtualkeyboard(struct wl_listener *listener, void *data); 326 | static Monitor *xytomon(double x, double y); 327 | static void xytonode(double x, double y, struct wlr_surface **psurface, 328 | Client **pc, LayerSurface **pl, double *nx, double *ny); 329 | static void zoom(const Arg *arg); 330 | 331 | /* variables */ 332 | static const char broken[] = "broken"; 333 | static pid_t child_pid = -1; 334 | static int locked; 335 | static void *exclusive_focus; 336 | static struct wl_display *dpy; 337 | static struct wlr_backend *backend; 338 | static struct wlr_scene *scene; 339 | static struct wlr_scene_tree *layers[NUM_LAYERS]; 340 | static struct wlr_scene_tree *drag_icon; 341 | /* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ 342 | static const int layermap[] = { LyrBg, LyrBottom, LyrTop, LyrOverlay }; 343 | static struct wlr_renderer *drw; 344 | static struct wlr_allocator *alloc; 345 | static struct wlr_compositor *compositor; 346 | static struct wlr_session *session; 347 | 348 | static struct wlr_xdg_shell *xdg_shell; 349 | static struct wlr_xdg_activation_v1 *activation; 350 | static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; 351 | static struct wl_list clients; /* tiling order */ 352 | static struct wl_list fstack; /* focus order */ 353 | static struct wlr_idle_notifier_v1 *idle_notifier; 354 | static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; 355 | static struct wlr_layer_shell_v1 *layer_shell; 356 | static struct wlr_output_manager_v1 *output_mgr; 357 | static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; 358 | static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; 359 | static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; 360 | 361 | static struct wlr_cursor *cursor; 362 | static struct wlr_xcursor_manager *cursor_mgr; 363 | 364 | static struct wlr_session_lock_manager_v1 *session_lock_mgr; 365 | static struct wlr_scene_rect *locked_bg; 366 | static struct wlr_session_lock_v1 *cur_lock; 367 | static struct wl_listener lock_listener = {.notify = locksession}; 368 | 369 | static struct wlr_seat *seat; 370 | static struct wl_list keyboards; 371 | static unsigned int cursor_mode; 372 | static Client *grabc; 373 | static int grabcx, grabcy; /* client-relative */ 374 | 375 | static struct wlr_output_layout *output_layout; 376 | static struct wlr_box sgeom; 377 | static struct wl_list mons; 378 | static Monitor *selmon; 379 | 380 | #ifdef XWAYLAND 381 | static void activatex11(struct wl_listener *listener, void *data); 382 | static void associatex11(struct wl_listener *listener, void *data); 383 | static void configurex11(struct wl_listener *listener, void *data); 384 | static void createnotifyx11(struct wl_listener *listener, void *data); 385 | static void dissociatex11(struct wl_listener *listener, void *data); 386 | static xcb_atom_t getatom(xcb_connection_t *xc, const char *name); 387 | static void sethints(struct wl_listener *listener, void *data); 388 | static void xwaylandready(struct wl_listener *listener, void *data); 389 | static struct wlr_xwayland *xwayland; 390 | static xcb_atom_t netatom[NetLast]; 391 | #endif 392 | 393 | /* configuration, allows nested code to access above variables */ 394 | #include "config.h" 395 | 396 | /* attempt to encapsulate suck into one file */ 397 | #include "client.h" 398 | 399 | /* function implementations */ 400 | void 401 | applybounds(Client *c, struct wlr_box *bbox) 402 | { 403 | /* set minimum possible */ 404 | c->geom.width = MAX(1, c->geom.width); 405 | c->geom.height = MAX(1, c->geom.height); 406 | 407 | if (c->geom.x >= bbox->x + bbox->width) 408 | c->geom.x = bbox->x + bbox->width - c->geom.width; 409 | if (c->geom.y >= bbox->y + bbox->height) 410 | c->geom.y = bbox->y + bbox->height - c->geom.height; 411 | if (c->geom.x + c->geom.width + 2 * c->bw <= bbox->x) 412 | c->geom.x = bbox->x; 413 | if (c->geom.y + c->geom.height + 2 * c->bw <= bbox->y) 414 | c->geom.y = bbox->y; 415 | } 416 | 417 | void 418 | applyrules(Client *c) 419 | { 420 | /* rule matching */ 421 | const char *appid, *title; 422 | uint32_t i, newtags = 0; 423 | const Rule *r; 424 | Monitor *mon = selmon, *m; 425 | 426 | c->isfloating = client_is_float_type(c); 427 | if (!(appid = client_get_appid(c))) 428 | appid = broken; 429 | if (!(title = client_get_title(c))) 430 | title = broken; 431 | 432 | for (r = rules; r < END(rules); r++) { 433 | if ((!r->title || strstr(title, r->title)) 434 | && (!r->id || strstr(appid, r->id))) { 435 | c->isfloating = r->isfloating; 436 | newtags |= r->tags; 437 | i = 0; 438 | wl_list_for_each(m, &mons, link) 439 | if (r->monitor == i++) 440 | mon = m; 441 | } 442 | } 443 | wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrFloat : LyrTile]); 444 | setmon(c, mon, newtags); 445 | } 446 | 447 | void 448 | arrange(Monitor *m) 449 | { 450 | Client *c; 451 | wl_list_for_each(c, &clients, link) { 452 | if (c->mon == m) { 453 | wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); 454 | client_set_suspended(c, !VISIBLEON(c, m)); 455 | } 456 | } 457 | 458 | wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 459 | (c = focustop(m)) && c->isfullscreen); 460 | 461 | strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); 462 | 463 | if (m->lt[m->sellt]->arrange) 464 | m->lt[m->sellt]->arrange(m); 465 | motionnotify(0); 466 | checkidleinhibitor(NULL); 467 | } 468 | 469 | void 470 | arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) 471 | { 472 | LayerSurface *layersurface; 473 | struct wlr_box full_area = m->m; 474 | 475 | wl_list_for_each(layersurface, list, link) { 476 | struct wlr_layer_surface_v1 *wlr_layer_surface = layersurface->layer_surface; 477 | struct wlr_layer_surface_v1_state *state = &wlr_layer_surface->current; 478 | 479 | if (exclusive != (state->exclusive_zone > 0)) 480 | continue; 481 | 482 | wlr_scene_layer_surface_v1_configure(layersurface->scene_layer, &full_area, usable_area); 483 | wlr_scene_node_set_position(&layersurface->popups->node, 484 | layersurface->scene->node.x, layersurface->scene->node.y); 485 | layersurface->geom.x = layersurface->scene->node.x; 486 | layersurface->geom.y = layersurface->scene->node.y; 487 | } 488 | } 489 | 490 | void 491 | arrangelayers(Monitor *m) 492 | { 493 | int i; 494 | struct wlr_box usable_area = m->m; 495 | uint32_t layers_above_shell[] = { 496 | ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, 497 | ZWLR_LAYER_SHELL_V1_LAYER_TOP, 498 | }; 499 | LayerSurface *layersurface; 500 | if (!m->wlr_output->enabled) 501 | return; 502 | 503 | /* Arrange exclusive surfaces from top->bottom */ 504 | for (i = 3; i >= 0; i--) 505 | arrangelayer(m, &m->layers[i], &usable_area, 1); 506 | 507 | if (memcmp(&usable_area, &m->w, sizeof(struct wlr_box))) { 508 | m->w = usable_area; 509 | arrange(m); 510 | } 511 | 512 | /* Arrange non-exlusive surfaces from top->bottom */ 513 | for (i = 3; i >= 0; i--) 514 | arrangelayer(m, &m->layers[i], &usable_area, 0); 515 | 516 | /* Find topmost keyboard interactive layer, if such a layer exists */ 517 | for (i = 0; i < LENGTH(layers_above_shell); i++) { 518 | wl_list_for_each_reverse(layersurface, 519 | &m->layers[layers_above_shell[i]], link) { 520 | if (!locked && layersurface->layer_surface->current.keyboard_interactive 521 | && layersurface->mapped) { 522 | /* Deactivate the focused client. */ 523 | focusclient(NULL, 0); 524 | exclusive_focus = layersurface; 525 | client_notify_enter(layersurface->layer_surface->surface, wlr_seat_get_keyboard(seat)); 526 | return; 527 | } 528 | } 529 | } 530 | } 531 | 532 | void 533 | axisnotify(struct wl_listener *listener, void *data) 534 | { 535 | /* This event is forwarded by the cursor when a pointer emits an axis event, 536 | * for example when you move the scroll wheel. */ 537 | struct wlr_pointer_axis_event *event = data; 538 | wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); 539 | /* TODO: allow usage of scroll whell for mousebindings, it can be implemented 540 | * checking the event's orientation and the delta of the event */ 541 | /* Notify the client with pointer focus of the axis event. */ 542 | wlr_seat_pointer_notify_axis(seat, 543 | event->time_msec, event->orientation, event->delta, 544 | event->delta_discrete, event->source); 545 | } 546 | 547 | void 548 | buttonpress(struct wl_listener *listener, void *data) 549 | { 550 | struct wlr_pointer_button_event *event = data; 551 | struct wlr_keyboard *keyboard; 552 | uint32_t mods; 553 | Client *c; 554 | const Button *b; 555 | 556 | wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); 557 | 558 | switch (event->state) { 559 | case WLR_BUTTON_PRESSED: 560 | cursor_mode = CurPressed; 561 | if (locked) 562 | break; 563 | 564 | /* Change focus if the button was _pressed_ over a client */ 565 | xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); 566 | if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) 567 | focusclient(c, 1); 568 | 569 | keyboard = wlr_seat_get_keyboard(seat); 570 | mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; 571 | for (b = buttons; b < END(buttons); b++) { 572 | if (CLEANMASK(mods) == CLEANMASK(b->mod) && 573 | event->button == b->button && b->func) { 574 | b->func(&b->arg); 575 | return; 576 | } 577 | } 578 | break; 579 | case WLR_BUTTON_RELEASED: 580 | /* If you released any buttons, we exit interactive move/resize mode. */ 581 | /* TODO should reset to the pointer focus's current setcursor */ 582 | if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { 583 | wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); 584 | cursor_mode = CurNormal; 585 | /* Drop the window off on its new monitor */ 586 | selmon = xytomon(cursor->x, cursor->y); 587 | setmon(grabc, selmon, 0); 588 | return; 589 | } else { 590 | cursor_mode = CurNormal; 591 | } 592 | break; 593 | } 594 | /* If the event wasn't handled by the compositor, notify the client with 595 | * pointer focus that a button press has occurred */ 596 | wlr_seat_pointer_notify_button(seat, 597 | event->time_msec, event->button, event->state); 598 | } 599 | 600 | void 601 | chvt(const Arg *arg) 602 | { 603 | wlr_session_change_vt(session, arg->ui); 604 | } 605 | 606 | void 607 | checkidleinhibitor(struct wlr_surface *exclude) 608 | { 609 | int inhibited = 0, unused_lx, unused_ly; 610 | struct wlr_idle_inhibitor_v1 *inhibitor; 611 | wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { 612 | struct wlr_surface *surface = wlr_surface_get_root_surface(inhibitor->surface); 613 | struct wlr_scene_tree *tree = surface->data; 614 | if (exclude != surface && (bypass_surface_visibility || (!tree 615 | || wlr_scene_node_coords(&tree->node, &unused_lx, &unused_ly)))) { 616 | inhibited = 1; 617 | break; 618 | } 619 | } 620 | 621 | wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); 622 | } 623 | 624 | void 625 | cleanup(void) 626 | { 627 | #ifdef XWAYLAND 628 | wlr_xwayland_destroy(xwayland); 629 | xwayland = NULL; 630 | #endif 631 | wl_display_destroy_clients(dpy); 632 | if (child_pid > 0) { 633 | kill(child_pid, SIGTERM); 634 | waitpid(child_pid, NULL, 0); 635 | } 636 | wlr_xcursor_manager_destroy(cursor_mgr); 637 | wlr_output_layout_destroy(output_layout); 638 | wl_display_destroy(dpy); 639 | /* Destroy after the wayland display (when the monitors are already destroyed) 640 | to avoid destroying them with an invalid scene output. */ 641 | wlr_scene_node_destroy(&scene->tree.node); 642 | } 643 | 644 | void 645 | cleanupkeyboard(struct wl_listener *listener, void *data) 646 | { 647 | Keyboard *kb = wl_container_of(listener, kb, destroy); 648 | 649 | wl_event_source_remove(kb->key_repeat_source); 650 | wl_list_remove(&kb->link); 651 | wl_list_remove(&kb->modifiers.link); 652 | wl_list_remove(&kb->key.link); 653 | wl_list_remove(&kb->destroy.link); 654 | free(kb); 655 | } 656 | 657 | void 658 | cleanupmon(struct wl_listener *listener, void *data) 659 | { 660 | Monitor *m = wl_container_of(listener, m, destroy); 661 | LayerSurface *l, *tmp; 662 | int i; 663 | 664 | for (i = 0; i <= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; i++) 665 | wl_list_for_each_safe(l, tmp, &m->layers[i], link) 666 | wlr_layer_surface_v1_destroy(l->layer_surface); 667 | 668 | wl_list_remove(&m->destroy.link); 669 | wl_list_remove(&m->frame.link); 670 | wl_list_remove(&m->link); 671 | m->wlr_output->data = NULL; 672 | wlr_output_layout_remove(output_layout, m->wlr_output); 673 | wlr_scene_output_destroy(m->scene_output); 674 | wlr_scene_node_destroy(&m->fullscreen_bg->node); 675 | 676 | closemon(m); 677 | free(m); 678 | } 679 | 680 | void 681 | closemon(Monitor *m) 682 | { 683 | /* update selmon if needed and 684 | * move closed monitor's clients to the focused one */ 685 | Client *c; 686 | if (wl_list_empty(&mons)) { 687 | selmon = NULL; 688 | } else if (m == selmon) { 689 | int nmons = wl_list_length(&mons), i = 0; 690 | do /* don't switch to disabled mons */ 691 | selmon = wl_container_of(mons.next, selmon, link); 692 | while (!selmon->wlr_output->enabled && i++ < nmons); 693 | } 694 | 695 | wl_list_for_each(c, &clients, link) { 696 | if (c->isfloating && c->geom.x > m->m.width) 697 | resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, 698 | .width = c->geom.width, .height = c->geom.height}, 0); 699 | if (c->mon == m) 700 | setmon(c, selmon, c->tags); 701 | } 702 | focusclient(focustop(selmon), 1); 703 | printstatus(); 704 | } 705 | 706 | void 707 | commitlayersurfacenotify(struct wl_listener *listener, void *data) 708 | { 709 | LayerSurface *layersurface = wl_container_of(listener, layersurface, surface_commit); 710 | struct wlr_layer_surface_v1 *wlr_layer_surface = layersurface->layer_surface; 711 | struct wlr_output *wlr_output = wlr_layer_surface->output; 712 | struct wlr_scene_tree *layer = layers[layermap[wlr_layer_surface->current.layer]]; 713 | 714 | /* For some reason this layersurface have no monitor, this can be because 715 | * its monitor has just been destroyed */ 716 | if (!wlr_output || !(layersurface->mon = wlr_output->data)) 717 | return; 718 | 719 | if (layer != layersurface->scene->node.parent) { 720 | wlr_scene_node_reparent(&layersurface->scene->node, layer); 721 | wlr_scene_node_reparent(&layersurface->popups->node, layer); 722 | wl_list_remove(&layersurface->link); 723 | wl_list_insert(&layersurface->mon->layers[wlr_layer_surface->current.layer], 724 | &layersurface->link); 725 | } 726 | if (wlr_layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) 727 | wlr_scene_node_reparent(&layersurface->popups->node, layers[LyrTop]); 728 | 729 | if (wlr_layer_surface->current.committed == 0 730 | && layersurface->mapped == wlr_layer_surface->surface->mapped) 731 | return; 732 | layersurface->mapped = wlr_layer_surface->surface->mapped; 733 | 734 | arrangelayers(layersurface->mon); 735 | } 736 | 737 | void 738 | commitnotify(struct wl_listener *listener, void *data) 739 | { 740 | Client *c = wl_container_of(listener, c, commit); 741 | 742 | if (client_surface(c)->mapped) 743 | resize(c, c->geom, (c->isfloating && !c->isfullscreen)); 744 | 745 | /* mark a pending resize as completed */ 746 | if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) 747 | c->resize = 0; 748 | } 749 | 750 | void 751 | createdecoration(struct wl_listener *listener, void *data) 752 | { 753 | struct wlr_xdg_toplevel_decoration_v1 *dec = data; 754 | wlr_xdg_toplevel_decoration_v1_set_mode(dec, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); 755 | } 756 | 757 | void 758 | createidleinhibitor(struct wl_listener *listener, void *data) 759 | { 760 | struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; 761 | LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); 762 | 763 | checkidleinhibitor(NULL); 764 | } 765 | 766 | void 767 | createkeyboard(struct wlr_keyboard *keyboard) 768 | { 769 | struct xkb_context *context; 770 | struct xkb_keymap *keymap; 771 | Keyboard *kb = keyboard->data = ecalloc(1, sizeof(*kb)); 772 | kb->wlr_keyboard = keyboard; 773 | 774 | /* Prepare an XKB keymap and assign it to the keyboard. */ 775 | context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 776 | keymap = xkb_keymap_new_from_names(context, &xkb_rules, 777 | XKB_KEYMAP_COMPILE_NO_FLAGS); 778 | if (!keymap) 779 | die("createkeyboard: failed to compile keymap"); 780 | 781 | wlr_keyboard_set_keymap(keyboard, keymap); 782 | xkb_keymap_unref(keymap); 783 | xkb_context_unref(context); 784 | wlr_keyboard_set_repeat_info(keyboard, repeat_rate, repeat_delay); 785 | 786 | /* Here we set up listeners for keyboard events. */ 787 | LISTEN(&keyboard->events.modifiers, &kb->modifiers, keypressmod); 788 | LISTEN(&keyboard->events.key, &kb->key, keypress); 789 | LISTEN(&keyboard->base.events.destroy, &kb->destroy, cleanupkeyboard); 790 | 791 | wlr_seat_set_keyboard(seat, keyboard); 792 | 793 | kb->key_repeat_source = wl_event_loop_add_timer( 794 | wl_display_get_event_loop(dpy), keyrepeat, kb); 795 | 796 | /* And add the keyboard to our list of keyboards */ 797 | wl_list_insert(&keyboards, &kb->link); 798 | } 799 | 800 | void 801 | createlayersurface(struct wl_listener *listener, void *data) 802 | { 803 | struct wlr_layer_surface_v1 *wlr_layer_surface = data; 804 | LayerSurface *layersurface; 805 | struct wlr_layer_surface_v1_state old_state; 806 | struct wlr_scene_tree *l = layers[layermap[wlr_layer_surface->pending.layer]]; 807 | 808 | if (!wlr_layer_surface->output) 809 | wlr_layer_surface->output = selmon ? selmon->wlr_output : NULL; 810 | 811 | if (!wlr_layer_surface->output) { 812 | wlr_layer_surface_v1_destroy(wlr_layer_surface); 813 | return; 814 | } 815 | 816 | layersurface = wlr_layer_surface->data = ecalloc(1, sizeof(LayerSurface)); 817 | layersurface->type = LayerShell; 818 | LISTEN(&wlr_layer_surface->surface->events.commit, 819 | &layersurface->surface_commit, commitlayersurfacenotify); 820 | LISTEN(&wlr_layer_surface->events.destroy, &layersurface->destroy, 821 | destroylayersurfacenotify); 822 | LISTEN(&wlr_layer_surface->surface->events.map, &layersurface->map, 823 | maplayersurfacenotify); 824 | LISTEN(&wlr_layer_surface->surface->events.unmap, &layersurface->unmap, 825 | unmaplayersurfacenotify); 826 | 827 | layersurface->layer_surface = wlr_layer_surface; 828 | layersurface->mon = wlr_layer_surface->output->data; 829 | layersurface->scene_layer = wlr_scene_layer_surface_v1_create(l, wlr_layer_surface); 830 | layersurface->scene = layersurface->scene_layer->tree; 831 | layersurface->popups = wlr_layer_surface->surface->data = wlr_scene_tree_create(l); 832 | 833 | layersurface->scene->node.data = layersurface; 834 | 835 | wl_list_insert(&layersurface->mon->layers[wlr_layer_surface->pending.layer], 836 | &layersurface->link); 837 | 838 | /* Temporarily set the layer's current state to pending 839 | * so that we can easily arrange it 840 | */ 841 | old_state = wlr_layer_surface->current; 842 | wlr_layer_surface->current = wlr_layer_surface->pending; 843 | layersurface->mapped = 1; 844 | arrangelayers(layersurface->mon); 845 | wlr_layer_surface->current = old_state; 846 | } 847 | 848 | void 849 | createlocksurface(struct wl_listener *listener, void *data) 850 | { 851 | SessionLock *lock = wl_container_of(listener, lock, new_surface); 852 | struct wlr_session_lock_surface_v1 *lock_surface = data; 853 | Monitor *m = lock_surface->output->data; 854 | struct wlr_scene_tree *scene_tree = lock_surface->surface->data = 855 | wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); 856 | m->lock_surface = lock_surface; 857 | 858 | wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); 859 | wlr_session_lock_surface_v1_configure(lock_surface, m->m.width, m->m.height); 860 | 861 | LISTEN(&lock_surface->events.destroy, &m->destroy_lock_surface, destroylocksurface); 862 | 863 | if (m == selmon) 864 | client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); 865 | } 866 | 867 | void 868 | createmon(struct wl_listener *listener, void *data) 869 | { 870 | /* This event is raised by the backend when a new output (aka a display or 871 | * monitor) becomes available. */ 872 | struct wlr_output *wlr_output = data; 873 | const MonitorRule *r; 874 | size_t i; 875 | Monitor *m = wlr_output->data = ecalloc(1, sizeof(*m)); 876 | m->wlr_output = wlr_output; 877 | 878 | wlr_output_init_render(wlr_output, alloc, drw); 879 | 880 | /* Initialize monitor state using configured rules */ 881 | for (i = 0; i < LENGTH(m->layers); i++) 882 | wl_list_init(&m->layers[i]); 883 | m->tagset[0] = m->tagset[1] = 1; 884 | for (r = monrules; r < END(monrules); r++) { 885 | if (!r->name || strstr(wlr_output->name, r->name)) { 886 | m->mfact = r->mfact; 887 | m->nmaster = r->nmaster; 888 | wlr_output_set_scale(wlr_output, r->scale); 889 | m->lt[0] = m->lt[1] = r->lt; 890 | wlr_output_set_transform(wlr_output, r->rr); 891 | m->m.x = r->x; 892 | m->m.y = r->y; 893 | break; 894 | } 895 | } 896 | 897 | /* The mode is a tuple of (width, height, refresh rate), and each 898 | * monitor supports only a specific set of modes. We just pick the 899 | * monitor's preferred mode; a more sophisticated compositor would let 900 | * the user configure it. */ 901 | wlr_output_set_mode(wlr_output, wlr_output_preferred_mode(wlr_output)); 902 | 903 | /* Set up event listeners */ 904 | LISTEN(&wlr_output->events.frame, &m->frame, rendermon); 905 | LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); 906 | LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); 907 | 908 | wlr_output_enable(wlr_output, 1); 909 | if (!wlr_output_commit(wlr_output)) 910 | return; 911 | 912 | wl_list_insert(&mons, &m->link); 913 | printstatus(); 914 | 915 | /* The xdg-protocol specifies: 916 | * 917 | * If the fullscreened surface is not opaque, the compositor must make 918 | * sure that other screen content not part of the same surface tree (made 919 | * up of subsurfaces, popups or similarly coupled surfaces) are not 920 | * visible below the fullscreened surface. 921 | * 922 | */ 923 | /* updatemons() will resize and set correct position */ 924 | m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); 925 | wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); 926 | 927 | /* Adds this to the output layout in the order it was configured in. 928 | * 929 | * The output layout utility automatically adds a wl_output global to the 930 | * display, which Wayland clients can see to find out information about the 931 | * output (such as DPI, scale factor, manufacturer, etc). 932 | */ 933 | m->scene_output = wlr_scene_output_create(scene, wlr_output); 934 | if (m->m.x < 0 || m->m.y < 0) 935 | wlr_output_layout_add_auto(output_layout, wlr_output); 936 | else 937 | wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); 938 | strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); 939 | } 940 | 941 | void 942 | createnotify(struct wl_listener *listener, void *data) 943 | { 944 | /* This event is raised when wlr_xdg_shell receives a new xdg surface from a 945 | * client, either a toplevel (application window) or popup, 946 | * or when wlr_layer_shell receives a new popup from a layer. 947 | * If you want to do something tricky with popups you should check if 948 | * its parent is wlr_xdg_shell or wlr_layer_shell */ 949 | struct wlr_xdg_surface *xdg_surface = data; 950 | Client *c = NULL; 951 | LayerSurface *l = NULL; 952 | 953 | if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { 954 | struct wlr_box box; 955 | int type = toplevel_from_wlr_surface(xdg_surface->surface, &c, &l); 956 | if (!xdg_surface->popup->parent || type < 0) 957 | return; 958 | xdg_surface->surface->data = wlr_scene_xdg_surface_create( 959 | xdg_surface->popup->parent->data, xdg_surface); 960 | if ((l && !l->mon) || (c && !c->mon)) 961 | return; 962 | box = type == LayerShell ? l->mon->m : c->mon->w; 963 | box.x -= (type == LayerShell ? l->geom.x : c->geom.x); 964 | box.y -= (type == LayerShell ? l->geom.y : c->geom.y); 965 | wlr_xdg_popup_unconstrain_from_box(xdg_surface->popup, &box); 966 | return; 967 | } else if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_NONE) 968 | return; 969 | 970 | /* Allocate a Client for this surface */ 971 | c = xdg_surface->data = ecalloc(1, sizeof(*c)); 972 | c->surface.xdg = xdg_surface; 973 | c->bw = borderpx; 974 | 975 | wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, 976 | WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); 977 | 978 | LISTEN(&xdg_surface->surface->events.commit, &c->commit, commitnotify); 979 | LISTEN(&xdg_surface->surface->events.map, &c->map, mapnotify); 980 | LISTEN(&xdg_surface->surface->events.unmap, &c->unmap, unmapnotify); 981 | LISTEN(&xdg_surface->events.destroy, &c->destroy, destroynotify); 982 | LISTEN(&xdg_surface->toplevel->events.set_title, &c->set_title, updatetitle); 983 | LISTEN(&xdg_surface->toplevel->events.request_fullscreen, &c->fullscreen, 984 | fullscreennotify); 985 | LISTEN(&xdg_surface->toplevel->events.request_maximize, &c->maximize, 986 | maximizenotify); 987 | } 988 | 989 | void 990 | createpointer(struct wlr_pointer *pointer) 991 | { 992 | if (wlr_input_device_is_libinput(&pointer->base)) { 993 | struct libinput_device *libinput_device = (struct libinput_device*) 994 | wlr_libinput_get_device_handle(&pointer->base); 995 | 996 | if (libinput_device_config_tap_get_finger_count(libinput_device)) { 997 | libinput_device_config_tap_set_enabled(libinput_device, tap_to_click); 998 | libinput_device_config_tap_set_drag_enabled(libinput_device, tap_and_drag); 999 | libinput_device_config_tap_set_drag_lock_enabled(libinput_device, drag_lock); 1000 | libinput_device_config_tap_set_button_map(libinput_device, button_map); 1001 | } 1002 | 1003 | if (libinput_device_config_scroll_has_natural_scroll(libinput_device)) 1004 | libinput_device_config_scroll_set_natural_scroll_enabled(libinput_device, natural_scrolling); 1005 | 1006 | if (libinput_device_config_dwt_is_available(libinput_device)) 1007 | libinput_device_config_dwt_set_enabled(libinput_device, disable_while_typing); 1008 | 1009 | if (libinput_device_config_left_handed_is_available(libinput_device)) 1010 | libinput_device_config_left_handed_set(libinput_device, left_handed); 1011 | 1012 | if (libinput_device_config_middle_emulation_is_available(libinput_device)) 1013 | libinput_device_config_middle_emulation_set_enabled(libinput_device, middle_button_emulation); 1014 | 1015 | if (libinput_device_config_scroll_get_methods(libinput_device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) 1016 | libinput_device_config_scroll_set_method (libinput_device, scroll_method); 1017 | 1018 | if (libinput_device_config_click_get_methods(libinput_device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) 1019 | libinput_device_config_click_set_method (libinput_device, click_method); 1020 | 1021 | if (libinput_device_config_send_events_get_modes(libinput_device)) 1022 | libinput_device_config_send_events_set_mode(libinput_device, send_events_mode); 1023 | 1024 | if (libinput_device_config_accel_is_available(libinput_device)) { 1025 | libinput_device_config_accel_set_profile(libinput_device, accel_profile); 1026 | libinput_device_config_accel_set_speed(libinput_device, accel_speed); 1027 | } 1028 | } 1029 | 1030 | wlr_cursor_attach_input_device(cursor, &pointer->base); 1031 | } 1032 | 1033 | void 1034 | cursorframe(struct wl_listener *listener, void *data) 1035 | { 1036 | /* This event is forwarded by the cursor when a pointer emits an frame 1037 | * event. Frame events are sent after regular pointer events to group 1038 | * multiple events together. For instance, two axis events may happen at the 1039 | * same time, in which case a frame event won't be sent in between. */ 1040 | /* Notify the client with pointer focus of the frame event. */ 1041 | wlr_seat_pointer_notify_frame(seat); 1042 | } 1043 | 1044 | void 1045 | destroydragicon(struct wl_listener *listener, void *data) 1046 | { 1047 | /* Focus enter isn't sent during drag, so refocus the focused node. */ 1048 | focusclient(focustop(selmon), 1); 1049 | motionnotify(0); 1050 | } 1051 | 1052 | void 1053 | destroyidleinhibitor(struct wl_listener *listener, void *data) 1054 | { 1055 | /* `data` is the wlr_surface of the idle inhibitor being destroyed, 1056 | * at this point the idle inhibitor is still in the list of the manager */ 1057 | checkidleinhibitor(wlr_surface_get_root_surface(data)); 1058 | } 1059 | 1060 | void 1061 | destroylayersurfacenotify(struct wl_listener *listener, void *data) 1062 | { 1063 | LayerSurface *layersurface = wl_container_of(listener, layersurface, destroy); 1064 | 1065 | wl_list_remove(&layersurface->link); 1066 | wl_list_remove(&layersurface->destroy.link); 1067 | wl_list_remove(&layersurface->map.link); 1068 | wl_list_remove(&layersurface->unmap.link); 1069 | wl_list_remove(&layersurface->surface_commit.link); 1070 | wlr_scene_node_destroy(&layersurface->scene->node); 1071 | free(layersurface); 1072 | } 1073 | 1074 | void 1075 | destroylock(SessionLock *lock, int unlock) 1076 | { 1077 | wlr_seat_keyboard_notify_clear_focus(seat); 1078 | if ((locked = !unlock)) 1079 | goto destroy; 1080 | 1081 | wlr_scene_node_set_enabled(&locked_bg->node, 0); 1082 | 1083 | focusclient(focustop(selmon), 0); 1084 | motionnotify(0); 1085 | 1086 | destroy: 1087 | wl_list_remove(&lock->new_surface.link); 1088 | wl_list_remove(&lock->unlock.link); 1089 | wl_list_remove(&lock->destroy.link); 1090 | 1091 | wlr_scene_node_destroy(&lock->scene->node); 1092 | cur_lock = NULL; 1093 | free(lock); 1094 | } 1095 | 1096 | void 1097 | destroylocksurface(struct wl_listener *listener, void *data) 1098 | { 1099 | Monitor *m = wl_container_of(listener, m, destroy_lock_surface); 1100 | struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; 1101 | 1102 | m->lock_surface = NULL; 1103 | wl_list_remove(&m->destroy_lock_surface.link); 1104 | 1105 | if (lock_surface->surface != seat->keyboard_state.focused_surface) 1106 | return; 1107 | 1108 | if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { 1109 | surface = wl_container_of(cur_lock->surfaces.next, surface, link); 1110 | client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); 1111 | } else if (!locked) { 1112 | focusclient(focustop(selmon), 1); 1113 | } else { 1114 | wlr_seat_keyboard_clear_focus(seat); 1115 | } 1116 | } 1117 | 1118 | void 1119 | destroynotify(struct wl_listener *listener, void *data) 1120 | { 1121 | /* Called when the xdg_toplevel is destroyed. */ 1122 | Client *c = wl_container_of(listener, c, destroy); 1123 | wl_list_remove(&c->destroy.link); 1124 | wl_list_remove(&c->set_title.link); 1125 | wl_list_remove(&c->fullscreen.link); 1126 | #ifdef XWAYLAND 1127 | if (c->type != XDGShell) { 1128 | wl_list_remove(&c->activate.link); 1129 | wl_list_remove(&c->associate.link); 1130 | wl_list_remove(&c->configure.link); 1131 | wl_list_remove(&c->dissociate.link); 1132 | wl_list_remove(&c->set_hints.link); 1133 | } else 1134 | #endif 1135 | { 1136 | wl_list_remove(&c->commit.link); 1137 | wl_list_remove(&c->map.link); 1138 | wl_list_remove(&c->unmap.link); 1139 | } 1140 | free(c); 1141 | } 1142 | 1143 | void 1144 | destroysessionlock(struct wl_listener *listener, void *data) 1145 | { 1146 | SessionLock *lock = wl_container_of(listener, lock, destroy); 1147 | destroylock(lock, 0); 1148 | } 1149 | 1150 | void 1151 | destroysessionmgr(struct wl_listener *listener, void *data) 1152 | { 1153 | wl_list_remove(&lock_listener.link); 1154 | wl_list_remove(&listener->link); 1155 | } 1156 | 1157 | Monitor * 1158 | dirtomon(enum wlr_direction dir) 1159 | { 1160 | struct wlr_output *next; 1161 | if (!wlr_output_layout_get(output_layout, selmon->wlr_output)) 1162 | return selmon; 1163 | if ((next = wlr_output_layout_adjacent_output(output_layout, 1164 | dir, selmon->wlr_output, selmon->m.x, selmon->m.y))) 1165 | return next->data; 1166 | if ((next = wlr_output_layout_farthest_output(output_layout, 1167 | dir ^ (WLR_DIRECTION_LEFT|WLR_DIRECTION_RIGHT), 1168 | selmon->wlr_output, selmon->m.x, selmon->m.y))) 1169 | return next->data; 1170 | return selmon; 1171 | } 1172 | 1173 | void 1174 | focusclient(Client *c, int lift) 1175 | { 1176 | struct wlr_surface *old = seat->keyboard_state.focused_surface; 1177 | int unused_lx, unused_ly, old_client_type; 1178 | Client *old_c = NULL; 1179 | LayerSurface *old_l = NULL; 1180 | 1181 | if (locked) 1182 | return; 1183 | 1184 | /* Raise client in stacking order if requested */ 1185 | if (c && lift) 1186 | wlr_scene_node_raise_to_top(&c->scene->node); 1187 | 1188 | if (c && client_surface(c) == old) 1189 | return; 1190 | 1191 | if ((old_client_type = toplevel_from_wlr_surface(old, &old_c, &old_l)) == XDGShell) { 1192 | struct wlr_xdg_popup *popup, *tmp; 1193 | wl_list_for_each_safe(popup, tmp, &old_c->surface.xdg->popups, link) 1194 | wlr_xdg_popup_destroy(popup); 1195 | } 1196 | 1197 | /* Put the new client atop the focus stack and select its monitor */ 1198 | if (c && !client_is_unmanaged(c)) { 1199 | wl_list_remove(&c->flink); 1200 | wl_list_insert(&fstack, &c->flink); 1201 | selmon = c->mon; 1202 | c->isurgent = 0; 1203 | client_restack_surface(c); 1204 | 1205 | /* Don't change border color if there is an exclusive focus or we are 1206 | * handling a drag operation */ 1207 | if (!exclusive_focus && !seat->drag) 1208 | client_set_border_color(c, focuscolor); 1209 | } 1210 | 1211 | /* Deactivate old client if focus is changing */ 1212 | if (old && (!c || client_surface(c) != old)) { 1213 | /* If an overlay is focused, don't focus or activate the client, 1214 | * but only update its position in fstack to render its border with focuscolor 1215 | * and focus it after the overlay is closed. */ 1216 | if (old_client_type == LayerShell && wlr_scene_node_coords( 1217 | &old_l->scene->node, &unused_lx, &unused_ly) 1218 | && old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { 1219 | return; 1220 | } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { 1221 | return; 1222 | /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg 1223 | * and probably other clients */ 1224 | } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { 1225 | client_set_border_color(old_c, bordercolor); 1226 | 1227 | client_activate_surface(old, 0); 1228 | } 1229 | } 1230 | printstatus(); 1231 | 1232 | if (!c) { 1233 | /* With no client, all we have left is to clear focus */ 1234 | wlr_seat_keyboard_notify_clear_focus(seat); 1235 | return; 1236 | } 1237 | 1238 | /* Change cursor surface */ 1239 | motionnotify(0); 1240 | 1241 | /* Have a client, so focus its top-level wlr_surface */ 1242 | client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); 1243 | 1244 | /* Activate the new client */ 1245 | client_activate_surface(client_surface(c), 1); 1246 | } 1247 | 1248 | void 1249 | focusmon(const Arg *arg) 1250 | { 1251 | int i = 0, nmons = wl_list_length(&mons); 1252 | if (nmons) 1253 | do /* don't switch to disabled mons */ 1254 | selmon = dirtomon(arg->i); 1255 | while (!selmon->wlr_output->enabled && i++ < nmons); 1256 | focusclient(focustop(selmon), 1); 1257 | } 1258 | 1259 | void 1260 | focusstack(const Arg *arg) 1261 | { 1262 | /* Focus the next or previous client (in tiling order) on selmon */ 1263 | Client *c, *sel = focustop(selmon); 1264 | if (!sel || sel->isfullscreen) 1265 | return; 1266 | if (arg->i > 0) { 1267 | wl_list_for_each(c, &sel->link, link) { 1268 | if (&c->link == &clients) 1269 | continue; /* wrap past the sentinel node */ 1270 | if (VISIBLEON(c, selmon)) 1271 | break; /* found it */ 1272 | } 1273 | } else { 1274 | wl_list_for_each_reverse(c, &sel->link, link) { 1275 | if (&c->link == &clients) 1276 | continue; /* wrap past the sentinel node */ 1277 | if (VISIBLEON(c, selmon)) 1278 | break; /* found it */ 1279 | } 1280 | } 1281 | /* If only one client is visible on selmon, then c == sel */ 1282 | focusclient(c, 1); 1283 | } 1284 | 1285 | /* We probably should change the name of this, it sounds like 1286 | * will focus the topmost client of this mon, when actually will 1287 | * only return that client */ 1288 | Client * 1289 | focustop(Monitor *m) 1290 | { 1291 | Client *c; 1292 | wl_list_for_each(c, &fstack, flink) 1293 | if (VISIBLEON(c, m)) 1294 | return c; 1295 | return NULL; 1296 | } 1297 | 1298 | void 1299 | fullscreennotify(struct wl_listener *listener, void *data) 1300 | { 1301 | Client *c = wl_container_of(listener, c, fullscreen); 1302 | setfullscreen(c, client_wants_fullscreen(c)); 1303 | } 1304 | 1305 | void 1306 | handlesig(int signo) 1307 | { 1308 | if (signo == SIGCHLD) { 1309 | #ifdef XWAYLAND 1310 | siginfo_t in; 1311 | /* wlroots expects to reap the XWayland process itself, so we 1312 | * use WNOWAIT to keep the child waitable until we know it's not 1313 | * XWayland. 1314 | */ 1315 | while (!waitid(P_ALL, 0, &in, WEXITED|WNOHANG|WNOWAIT) && in.si_pid 1316 | && (!xwayland || in.si_pid != xwayland->server->pid)) 1317 | waitpid(in.si_pid, NULL, 0); 1318 | #else 1319 | while (waitpid(-1, NULL, WNOHANG) > 0); 1320 | #endif 1321 | } else if (signo == SIGINT || signo == SIGTERM) { 1322 | quit(NULL); 1323 | } 1324 | } 1325 | 1326 | void 1327 | incnmaster(const Arg *arg) 1328 | { 1329 | if (!arg || !selmon) 1330 | return; 1331 | selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); 1332 | arrange(selmon); 1333 | } 1334 | 1335 | void 1336 | inputdevice(struct wl_listener *listener, void *data) 1337 | { 1338 | /* This event is raised by the backend when a new input device becomes 1339 | * available. */ 1340 | struct wlr_input_device *device = data; 1341 | uint32_t caps; 1342 | 1343 | switch (device->type) { 1344 | case WLR_INPUT_DEVICE_KEYBOARD: 1345 | createkeyboard(wlr_keyboard_from_input_device(device)); 1346 | break; 1347 | case WLR_INPUT_DEVICE_POINTER: 1348 | createpointer(wlr_pointer_from_input_device(device)); 1349 | break; 1350 | default: 1351 | /* TODO handle other input device types */ 1352 | break; 1353 | } 1354 | 1355 | /* We need to let the wlr_seat know what our capabilities are, which is 1356 | * communiciated to the client. In dwl we always have a cursor, even if 1357 | * there are no pointer devices, so we always include that capability. */ 1358 | /* TODO do we actually require a cursor? */ 1359 | caps = WL_SEAT_CAPABILITY_POINTER; 1360 | if (!wl_list_empty(&keyboards)) 1361 | caps |= WL_SEAT_CAPABILITY_KEYBOARD; 1362 | wlr_seat_set_capabilities(seat, caps); 1363 | } 1364 | 1365 | int 1366 | keybinding(uint32_t mods, xkb_keysym_t sym) 1367 | { 1368 | /* 1369 | * Here we handle compositor keybindings. This is when the compositor is 1370 | * processing keys, rather than passing them on to the client for its own 1371 | * processing. 1372 | */ 1373 | int handled = 0; 1374 | const Key *k; 1375 | for (k = keys; k < END(keys); k++) { 1376 | if (CLEANMASK(mods) == CLEANMASK(k->mod) && 1377 | sym == k->keysym && k->func) { 1378 | k->func(&k->arg); 1379 | handled = 1; 1380 | } 1381 | } 1382 | return handled; 1383 | } 1384 | 1385 | void 1386 | keypress(struct wl_listener *listener, void *data) 1387 | { 1388 | int i; 1389 | /* This event is raised when a key is pressed or released. */ 1390 | Keyboard *kb = wl_container_of(listener, kb, key); 1391 | struct wlr_keyboard_key_event *event = data; 1392 | 1393 | /* Translate libinput keycode -> xkbcommon */ 1394 | uint32_t keycode = event->keycode + 8; 1395 | /* Get a list of keysyms based on the keymap for this keyboard */ 1396 | const xkb_keysym_t *syms; 1397 | int nsyms = xkb_state_key_get_syms( 1398 | kb->wlr_keyboard->xkb_state, keycode, &syms); 1399 | 1400 | int handled = 0; 1401 | uint32_t mods = wlr_keyboard_get_modifiers(kb->wlr_keyboard); 1402 | 1403 | wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); 1404 | 1405 | /* On _press_ if there is no active screen locker, 1406 | * attempt to process a compositor keybinding. */ 1407 | if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) 1408 | for (i = 0; i < nsyms; i++) 1409 | handled = keybinding(mods, syms[i]) || handled; 1410 | 1411 | if (handled && kb->wlr_keyboard->repeat_info.delay > 0) { 1412 | kb->mods = mods; 1413 | kb->keysyms = syms; 1414 | kb->nsyms = nsyms; 1415 | wl_event_source_timer_update(kb->key_repeat_source, 1416 | kb->wlr_keyboard->repeat_info.delay); 1417 | } else { 1418 | kb->nsyms = 0; 1419 | wl_event_source_timer_update(kb->key_repeat_source, 0); 1420 | } 1421 | 1422 | if (handled) 1423 | return; 1424 | 1425 | /* Pass unhandled keycodes along to the client. */ 1426 | wlr_seat_set_keyboard(seat, kb->wlr_keyboard); 1427 | wlr_seat_keyboard_notify_key(seat, event->time_msec, 1428 | event->keycode, event->state); 1429 | } 1430 | 1431 | void 1432 | keypressmod(struct wl_listener *listener, void *data) 1433 | { 1434 | /* This event is raised when a modifier key, such as shift or alt, is 1435 | * pressed. We simply communicate this to the client. */ 1436 | Keyboard *kb = wl_container_of(listener, kb, modifiers); 1437 | /* 1438 | * A seat can only have one keyboard, but this is a limitation of the 1439 | * Wayland protocol - not wlroots. We assign all connected keyboards to the 1440 | * same seat. You can swap out the underlying wlr_keyboard like this and 1441 | * wlr_seat handles this transparently. 1442 | */ 1443 | wlr_seat_set_keyboard(seat, kb->wlr_keyboard); 1444 | /* Send modifiers to the client. */ 1445 | wlr_seat_keyboard_notify_modifiers(seat, 1446 | &kb->wlr_keyboard->modifiers); 1447 | } 1448 | 1449 | int 1450 | keyrepeat(void *data) 1451 | { 1452 | Keyboard *kb = data; 1453 | int i; 1454 | if (!kb->nsyms || kb->wlr_keyboard->repeat_info.rate <= 0) 1455 | return 0; 1456 | 1457 | wl_event_source_timer_update(kb->key_repeat_source, 1458 | 1000 / kb->wlr_keyboard->repeat_info.rate); 1459 | 1460 | for (i = 0; i < kb->nsyms; i++) 1461 | keybinding(kb->mods, kb->keysyms[i]); 1462 | 1463 | return 0; 1464 | } 1465 | 1466 | void 1467 | killclient(const Arg *arg) 1468 | { 1469 | Client *sel = focustop(selmon); 1470 | if (sel) 1471 | client_send_close(sel); 1472 | } 1473 | 1474 | void 1475 | locksession(struct wl_listener *listener, void *data) 1476 | { 1477 | struct wlr_session_lock_v1 *session_lock = data; 1478 | SessionLock *lock; 1479 | wlr_scene_node_set_enabled(&locked_bg->node, 1); 1480 | if (cur_lock) { 1481 | wlr_session_lock_v1_destroy(session_lock); 1482 | return; 1483 | } 1484 | lock = session_lock->data = ecalloc(1, sizeof(*lock)); 1485 | focusclient(NULL, 0); 1486 | 1487 | lock->scene = wlr_scene_tree_create(layers[LyrBlock]); 1488 | cur_lock = lock->lock = session_lock; 1489 | locked = 1; 1490 | 1491 | LISTEN(&session_lock->events.new_surface, &lock->new_surface, createlocksurface); 1492 | LISTEN(&session_lock->events.destroy, &lock->destroy, destroysessionlock); 1493 | LISTEN(&session_lock->events.unlock, &lock->unlock, unlocksession); 1494 | 1495 | wlr_session_lock_v1_send_locked(session_lock); 1496 | } 1497 | 1498 | void 1499 | maplayersurfacenotify(struct wl_listener *listener, void *data) 1500 | { 1501 | LayerSurface *l = wl_container_of(listener, l, map); 1502 | motionnotify(0); 1503 | } 1504 | 1505 | void 1506 | mapnotify(struct wl_listener *listener, void *data) 1507 | { 1508 | /* Called when the surface is mapped, or ready to display on-screen. */ 1509 | Client *p, *w, *c = wl_container_of(listener, c, map); 1510 | Monitor *m; 1511 | int i; 1512 | 1513 | /* Create scene tree for this client and its border */ 1514 | c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); 1515 | wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); 1516 | c->scene_surface = c->type == XDGShell 1517 | ? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg) 1518 | : wlr_scene_subsurface_tree_create(c->scene, client_surface(c)); 1519 | c->scene->node.data = c->scene_surface->node.data = c; 1520 | 1521 | /* Handle unmanaged clients first so we can return prior create borders */ 1522 | if (client_is_unmanaged(c)) { 1523 | client_get_geometry(c, &c->geom); 1524 | /* Unmanaged clients always are floating */ 1525 | wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); 1526 | wlr_scene_node_set_position(&c->scene->node, c->geom.x + borderpx, 1527 | c->geom.y + borderpx); 1528 | if (client_wants_focus(c)) { 1529 | focusclient(c, 1); 1530 | exclusive_focus = c; 1531 | } 1532 | goto unset_fullscreen; 1533 | } 1534 | 1535 | for (i = 0; i < 4; i++) { 1536 | c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, bordercolor); 1537 | c->border[i]->node.data = c; 1538 | } 1539 | 1540 | /* Initialize client geometry with room for border */ 1541 | client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); 1542 | client_get_geometry(c, &c->geom); 1543 | c->geom.width += 2 * c->bw; 1544 | c->geom.height += 2 * c->bw; 1545 | 1546 | /* Insert this client into client lists. */ 1547 | wl_list_insert(&clients, &c->link); 1548 | wl_list_insert(&fstack, &c->flink); 1549 | 1550 | /* Set initial monitor, tags, floating status, and focus: 1551 | * we always consider floating, clients that have parent and thus 1552 | * we set the same tags and monitor than its parent, if not 1553 | * try to apply rules for them */ 1554 | /* TODO: https://github.com/djpohly/dwl/pull/334#issuecomment-1330166324 */ 1555 | if (c->type == XDGShell && (p = client_get_parent(c))) { 1556 | c->isfloating = 1; 1557 | wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); 1558 | setmon(c, p->mon, p->tags); 1559 | } else { 1560 | applyrules(c); 1561 | } 1562 | printstatus(); 1563 | 1564 | unset_fullscreen: 1565 | m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); 1566 | wl_list_for_each(w, &clients, link) 1567 | if (w != c && w->isfullscreen && m == w->mon && (w->tags & c->tags)) 1568 | setfullscreen(w, 0); 1569 | } 1570 | 1571 | void 1572 | maximizenotify(struct wl_listener *listener, void *data) 1573 | { 1574 | /* This event is raised when a client would like to maximize itself, 1575 | * typically because the user clicked on the maximize button on 1576 | * client-side decorations. dwl doesn't support maximization, but 1577 | * to conform to xdg-shell protocol we still must send a configure. 1578 | * Since xdg-shell protocol v5 we should ignore request of unsupported 1579 | * capabilities, just schedule a empty configure when the client uses <5 1580 | * protocol version 1581 | * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ 1582 | Client *c = wl_container_of(listener, c, maximize); 1583 | if (wl_resource_get_version(c->surface.xdg->resource) 1584 | < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) 1585 | wlr_xdg_surface_schedule_configure(c->surface.xdg); 1586 | } 1587 | 1588 | void 1589 | monocle(Monitor *m) 1590 | { 1591 | Client *c; 1592 | int n = 0; 1593 | 1594 | wl_list_for_each(c, &clients, link) { 1595 | if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) 1596 | continue; 1597 | resize(c, m->w, 0); 1598 | n++; 1599 | } 1600 | if (n) 1601 | snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); 1602 | if ((c = focustop(m))) 1603 | wlr_scene_node_raise_to_top(&c->scene->node); 1604 | } 1605 | 1606 | void 1607 | motionabsolute(struct wl_listener *listener, void *data) 1608 | { 1609 | /* This event is forwarded by the cursor when a pointer emits an _absolute_ 1610 | * motion event, from 0..1 on each axis. This happens, for example, when 1611 | * wlroots is running under a Wayland window rather than KMS+DRM, and you 1612 | * move the mouse over the window. You could enter the window from any edge, 1613 | * so we have to warp the mouse there. There is also some hardware which 1614 | * emits these events. */ 1615 | struct wlr_pointer_motion_absolute_event *event = data; 1616 | wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); 1617 | motionnotify(event->time_msec); 1618 | } 1619 | 1620 | void 1621 | motionnotify(uint32_t time) 1622 | { 1623 | double sx = 0, sy = 0; 1624 | Client *c = NULL, *w = NULL; 1625 | LayerSurface *l = NULL; 1626 | int type; 1627 | struct wlr_surface *surface = NULL; 1628 | 1629 | /* time is 0 in internal calls meant to restore pointer focus. */ 1630 | if (time) { 1631 | wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); 1632 | 1633 | /* Update selmon (even while dragging a window) */ 1634 | if (sloppyfocus) 1635 | selmon = xytomon(cursor->x, cursor->y); 1636 | } 1637 | 1638 | /* Update drag icon's position */ 1639 | wlr_scene_node_set_position(&drag_icon->node, cursor->x, cursor->y); 1640 | 1641 | /* If we are currently grabbing the mouse, handle and return */ 1642 | if (cursor_mode == CurMove) { 1643 | /* Move the grabbed client to the new position. */ 1644 | resize(grabc, (struct wlr_box){.x = cursor->x - grabcx, .y = cursor->y - grabcy, 1645 | .width = grabc->geom.width, .height = grabc->geom.height}, 1); 1646 | return; 1647 | } else if (cursor_mode == CurResize) { 1648 | resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, 1649 | .width = cursor->x - grabc->geom.x, .height = cursor->y - grabc->geom.y}, 1); 1650 | return; 1651 | } 1652 | 1653 | /* Find the client under the pointer and send the event along. */ 1654 | xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); 1655 | 1656 | if (cursor_mode == CurPressed && !seat->drag) { 1657 | if ((type = toplevel_from_wlr_surface( 1658 | seat->pointer_state.focused_surface, &w, &l)) >= 0) { 1659 | c = w; 1660 | surface = seat->pointer_state.focused_surface; 1661 | sx = cursor->x - (type == LayerShell ? l->geom.x : w->geom.x); 1662 | sy = cursor->y - (type == LayerShell ? l->geom.y : w->geom.y); 1663 | } 1664 | } 1665 | 1666 | /* If there's no client surface under the cursor, set the cursor image to a 1667 | * default. This is what makes the cursor image appear when you move it 1668 | * off of a client or over its border. */ 1669 | if (!surface && !seat->drag) 1670 | wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); 1671 | 1672 | pointerfocus(c, surface, sx, sy, time); 1673 | } 1674 | 1675 | void 1676 | motionrelative(struct wl_listener *listener, void *data) 1677 | { 1678 | /* This event is forwarded by the cursor when a pointer emits a _relative_ 1679 | * pointer motion event (i.e. a delta) */ 1680 | struct wlr_pointer_motion_event *event = data; 1681 | /* The cursor doesn't move unless we tell it to. The cursor automatically 1682 | * handles constraining the motion to the output layout, as well as any 1683 | * special configuration applied for the specific input device which 1684 | * generated the event. You can pass NULL for the device if you want to move 1685 | * the cursor around without any input. */ 1686 | wlr_cursor_move(cursor, &event->pointer->base, event->delta_x, event->delta_y); 1687 | motionnotify(event->time_msec); 1688 | } 1689 | 1690 | void 1691 | moveresize(const Arg *arg) 1692 | { 1693 | if (cursor_mode != CurNormal && cursor_mode != CurPressed) 1694 | return; 1695 | xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); 1696 | if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) 1697 | return; 1698 | 1699 | /* Float the window and tell motionnotify to grab it */ 1700 | setfloating(grabc, 1); 1701 | switch (cursor_mode = arg->ui) { 1702 | case CurMove: 1703 | grabcx = cursor->x - grabc->geom.x; 1704 | grabcy = cursor->y - grabc->geom.y; 1705 | wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); 1706 | break; 1707 | case CurResize: 1708 | /* Doesn't work for X11 output - the next absolute motion event 1709 | * returns the cursor to where it started */ 1710 | wlr_cursor_warp_closest(cursor, NULL, 1711 | grabc->geom.x + grabc->geom.width, 1712 | grabc->geom.y + grabc->geom.height); 1713 | wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); 1714 | break; 1715 | } 1716 | } 1717 | 1718 | void 1719 | outputmgrapply(struct wl_listener *listener, void *data) 1720 | { 1721 | struct wlr_output_configuration_v1 *config = data; 1722 | outputmgrapplyortest(config, 0); 1723 | } 1724 | 1725 | void 1726 | outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) 1727 | { 1728 | /* 1729 | * Called when a client such as wlr-randr requests a change in output 1730 | * configuration. This is only one way that the layout can be changed, 1731 | * so any Monitor information should be updated by updatemons() after an 1732 | * output_layout.change event, not here. 1733 | */ 1734 | struct wlr_output_configuration_head_v1 *config_head; 1735 | int ok = 1; 1736 | 1737 | wl_list_for_each(config_head, &config->heads, link) { 1738 | struct wlr_output *wlr_output = config_head->state.output; 1739 | Monitor *m = wlr_output->data; 1740 | 1741 | wlr_output_enable(wlr_output, config_head->state.enabled); 1742 | if (!config_head->state.enabled) 1743 | goto apply_or_test; 1744 | if (config_head->state.mode) 1745 | wlr_output_set_mode(wlr_output, config_head->state.mode); 1746 | else 1747 | wlr_output_set_custom_mode(wlr_output, 1748 | config_head->state.custom_mode.width, 1749 | config_head->state.custom_mode.height, 1750 | config_head->state.custom_mode.refresh); 1751 | 1752 | /* Don't move monitors if position wouldn't change, this to avoid 1753 | * wlroots marking the output as manually configured */ 1754 | if (m->m.x != config_head->state.x || m->m.y != config_head->state.y) 1755 | wlr_output_layout_add(output_layout, wlr_output, 1756 | config_head->state.x, config_head->state.y); 1757 | wlr_output_set_transform(wlr_output, config_head->state.transform); 1758 | wlr_output_set_scale(wlr_output, config_head->state.scale); 1759 | wlr_output_enable_adaptive_sync(wlr_output, 1760 | config_head->state.adaptive_sync_enabled); 1761 | 1762 | apply_or_test: 1763 | if (test) { 1764 | ok &= wlr_output_test(wlr_output); 1765 | wlr_output_rollback(wlr_output); 1766 | } else { 1767 | ok &= wlr_output_commit(wlr_output); 1768 | } 1769 | } 1770 | 1771 | if (ok) 1772 | wlr_output_configuration_v1_send_succeeded(config); 1773 | else 1774 | wlr_output_configuration_v1_send_failed(config); 1775 | wlr_output_configuration_v1_destroy(config); 1776 | 1777 | /* TODO: use a wrapper function? */ 1778 | updatemons(NULL, NULL); 1779 | } 1780 | 1781 | void 1782 | outputmgrtest(struct wl_listener *listener, void *data) 1783 | { 1784 | struct wlr_output_configuration_v1 *config = data; 1785 | outputmgrapplyortest(config, 1); 1786 | } 1787 | 1788 | void 1789 | pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, 1790 | uint32_t time) 1791 | { 1792 | struct timespec now; 1793 | int internal_call = !time; 1794 | 1795 | if (sloppyfocus && !internal_call && c && !client_is_unmanaged(c)) 1796 | focusclient(c, 0); 1797 | 1798 | /* If surface is NULL, clear pointer focus */ 1799 | if (!surface) { 1800 | wlr_seat_pointer_notify_clear_focus(seat); 1801 | return; 1802 | } 1803 | 1804 | if (internal_call) { 1805 | clock_gettime(CLOCK_MONOTONIC, &now); 1806 | time = now.tv_sec * 1000 + now.tv_nsec / 1000000; 1807 | } 1808 | 1809 | /* Let the client know that the mouse cursor has entered one 1810 | * of its surfaces, and make keyboard focus follow if desired. 1811 | * wlroots makes this a no-op if surface is already focused */ 1812 | wlr_seat_pointer_notify_enter(seat, surface, sx, sy); 1813 | wlr_seat_pointer_notify_motion(seat, time, sx, sy); 1814 | 1815 | } 1816 | 1817 | void 1818 | printstatus(void) 1819 | { 1820 | Monitor *m = NULL; 1821 | Client *c; 1822 | uint32_t occ, urg, sel; 1823 | const char *appid, *title; 1824 | 1825 | wl_list_for_each(m, &mons, link) { 1826 | occ = urg = 0; 1827 | wl_list_for_each(c, &clients, link) { 1828 | if (c->mon != m) 1829 | continue; 1830 | occ |= c->tags; 1831 | if (c->isurgent) 1832 | urg |= c->tags; 1833 | } 1834 | if ((c = focustop(m))) { 1835 | title = client_get_title(c); 1836 | appid = client_get_appid(c); 1837 | printf("%s title %s\n", m->wlr_output->name, title ? title : broken); 1838 | printf("%s appid %s\n", m->wlr_output->name, appid ? appid : broken); 1839 | printf("%s fullscreen %u\n", m->wlr_output->name, c->isfullscreen); 1840 | printf("%s floating %u\n", m->wlr_output->name, c->isfloating); 1841 | sel = c->tags; 1842 | } else { 1843 | printf("%s title \n", m->wlr_output->name); 1844 | printf("%s appid \n", m->wlr_output->name); 1845 | printf("%s fullscreen \n", m->wlr_output->name); 1846 | printf("%s floating \n", m->wlr_output->name); 1847 | sel = 0; 1848 | } 1849 | 1850 | printf("%s selmon %u\n", m->wlr_output->name, m == selmon); 1851 | printf("%s tags %u %u %u %u\n", m->wlr_output->name, occ, m->tagset[m->seltags], 1852 | sel, urg); 1853 | printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); 1854 | } 1855 | fflush(stdout); 1856 | } 1857 | 1858 | void 1859 | quit(const Arg *arg) 1860 | { 1861 | wl_display_terminate(dpy); 1862 | } 1863 | 1864 | void 1865 | rendermon(struct wl_listener *listener, void *data) 1866 | { 1867 | /* This function is called every time an output is ready to display a frame, 1868 | * generally at the output's refresh rate (e.g. 60Hz). */ 1869 | Monitor *m = wl_container_of(listener, m, frame); 1870 | Client *c; 1871 | struct wlr_output_state pending = {0}; 1872 | struct wlr_gamma_control_v1 *gamma_control; 1873 | struct timespec now; 1874 | 1875 | /* Render if no XDG clients have an outstanding resize and are visible on 1876 | * this monitor. */ 1877 | wl_list_for_each(c, &clients, link) 1878 | if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) 1879 | goto skip; 1880 | 1881 | /* 1882 | * HACK: The "correct" way to set the gamma is to commit it together with 1883 | * the rest of the state in one go, but to do that we would need to rewrite 1884 | * wlr_scene_output_commit() in order to add the gamma to the pending 1885 | * state before committing, instead try to commit the gamma in one frame, 1886 | * and commit the rest of the state in the next one (or in the same frame if 1887 | * the gamma can not be committed). 1888 | */ 1889 | if (m->gamma_lut_changed) { 1890 | gamma_control = wlr_gamma_control_manager_v1_get_control(gamma_control_mgr, m->wlr_output); 1891 | m->gamma_lut_changed = 0; 1892 | 1893 | if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) 1894 | goto commit; 1895 | 1896 | if (!wlr_output_test_state(m->wlr_output, &pending)) { 1897 | wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); 1898 | goto commit; 1899 | } 1900 | wlr_output_commit_state(m->wlr_output, &pending); 1901 | wlr_output_schedule_frame(m->wlr_output); 1902 | } else { 1903 | commit: 1904 | wlr_scene_output_commit(m->scene_output, NULL); 1905 | } 1906 | 1907 | skip: 1908 | /* Let clients know a frame has been rendered */ 1909 | clock_gettime(CLOCK_MONOTONIC, &now); 1910 | wlr_scene_output_send_frame_done(m->scene_output, &now); 1911 | wlr_output_state_finish(&pending); 1912 | } 1913 | 1914 | void 1915 | requeststartdrag(struct wl_listener *listener, void *data) 1916 | { 1917 | struct wlr_seat_request_start_drag_event *event = data; 1918 | 1919 | if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, 1920 | event->serial)) 1921 | wlr_seat_start_pointer_drag(seat, event->drag, event->serial); 1922 | else 1923 | wlr_data_source_destroy(event->drag->source); 1924 | } 1925 | 1926 | void 1927 | requestmonstate(struct wl_listener *listener, void *data) 1928 | { 1929 | struct wlr_output_event_request_state *event = data; 1930 | wlr_output_commit_state(event->output, event->state); 1931 | updatemons(NULL, NULL); 1932 | } 1933 | 1934 | void 1935 | resize(Client *c, struct wlr_box geo, int interact) 1936 | { 1937 | struct wlr_box *bbox = interact ? &sgeom : &c->mon->w; 1938 | struct wlr_box clip; 1939 | client_set_bounds(c, geo.width, geo.height); 1940 | c->geom = geo; 1941 | applybounds(c, bbox); 1942 | 1943 | /* Update scene-graph, including borders */ 1944 | wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); 1945 | wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); 1946 | wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); 1947 | wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); 1948 | wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); 1949 | wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); 1950 | wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); 1951 | wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); 1952 | wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); 1953 | 1954 | /* this is a no-op if size hasn't changed */ 1955 | c->resize = client_set_size(c, c->geom.width - 2 * c->bw, 1956 | c->geom.height - 2 * c->bw); 1957 | client_get_clip(c, &clip); 1958 | wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); 1959 | } 1960 | 1961 | void 1962 | run(char *startup_cmd) 1963 | { 1964 | /* Add a Unix socket to the Wayland display. */ 1965 | const char *socket = wl_display_add_socket_auto(dpy); 1966 | if (!socket) 1967 | die("startup: display_add_socket_auto"); 1968 | setenv("WAYLAND_DISPLAY", socket, 1); 1969 | 1970 | /* Start the backend. This will enumerate outputs and inputs, become the DRM 1971 | * master, etc */ 1972 | if (!wlr_backend_start(backend)) 1973 | die("startup: backend_start"); 1974 | 1975 | /* Now that the socket exists and the backend is started, run the startup command */ 1976 | if (startup_cmd) { 1977 | int piperw[2]; 1978 | if (pipe(piperw) < 0) 1979 | die("startup: pipe:"); 1980 | if ((child_pid = fork()) < 0) 1981 | die("startup: fork:"); 1982 | if (child_pid == 0) { 1983 | dup2(piperw[0], STDIN_FILENO); 1984 | close(piperw[0]); 1985 | close(piperw[1]); 1986 | execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); 1987 | die("startup: execl:"); 1988 | } 1989 | dup2(piperw[1], STDOUT_FILENO); 1990 | close(piperw[1]); 1991 | close(piperw[0]); 1992 | } 1993 | printstatus(); 1994 | 1995 | /* At this point the outputs are initialized, choose initial selmon based on 1996 | * cursor position, and set default cursor image */ 1997 | selmon = xytomon(cursor->x, cursor->y); 1998 | 1999 | /* TODO hack to get cursor to display in its initial location (100, 100) 2000 | * instead of (0, 0) and then jumping. still may not be fully 2001 | * initialized, as the image/coordinates are not transformed for the 2002 | * monitor when displayed here */ 2003 | wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); 2004 | wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); 2005 | 2006 | /* Run the Wayland event loop. This does not return until you exit the 2007 | * compositor. Starting the backend rigged up all of the necessary event 2008 | * loop configuration to listen to libinput events, DRM events, generate 2009 | * frame events at the refresh rate, and so on. */ 2010 | wl_display_run(dpy); 2011 | } 2012 | 2013 | void 2014 | setcursor(struct wl_listener *listener, void *data) 2015 | { 2016 | /* This event is raised by the seat when a client provides a cursor image */ 2017 | struct wlr_seat_pointer_request_set_cursor_event *event = data; 2018 | /* If we're "grabbing" the cursor, don't use the client's image, we will 2019 | * restore it after "grabbing" sending a leave event, followed by a enter 2020 | * event, which will result in the client requesting set the cursor surface */ 2021 | if (cursor_mode != CurNormal && cursor_mode != CurPressed) 2022 | return; 2023 | /* This can be sent by any client, so we check to make sure this one is 2024 | * actually has pointer focus first. If so, we can tell the cursor to 2025 | * use the provided surface as the cursor image. It will set the 2026 | * hardware cursor on the output that it's currently on and continue to 2027 | * do so as the cursor moves between outputs. */ 2028 | if (event->seat_client == seat->pointer_state.focused_client) 2029 | wlr_cursor_set_surface(cursor, event->surface, 2030 | event->hotspot_x, event->hotspot_y); 2031 | } 2032 | 2033 | void 2034 | setcursorshape(struct wl_listener *listener, void *data) 2035 | { 2036 | struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; 2037 | if (cursor_mode != CurNormal && cursor_mode != CurPressed) 2038 | return; 2039 | /* This can be sent by any client, so we check to make sure this one is 2040 | * actually has pointer focus first. If so, we can tell the cursor to 2041 | * use the provided cursor shape. */ 2042 | if (event->seat_client == seat->pointer_state.focused_client) 2043 | wlr_cursor_set_xcursor(cursor, cursor_mgr, 2044 | wlr_cursor_shape_v1_name(event->shape)); 2045 | } 2046 | 2047 | void 2048 | setfloating(Client *c, int floating) 2049 | { 2050 | c->isfloating = floating; 2051 | if (!c->mon) 2052 | return; 2053 | wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen 2054 | ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); 2055 | arrange(c->mon); 2056 | printstatus(); 2057 | } 2058 | 2059 | void 2060 | setfullscreen(Client *c, int fullscreen) 2061 | { 2062 | c->isfullscreen = fullscreen; 2063 | if (!c->mon) 2064 | return; 2065 | c->bw = fullscreen ? 0 : borderpx; 2066 | client_set_fullscreen(c, fullscreen); 2067 | wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen 2068 | ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); 2069 | 2070 | if (fullscreen) { 2071 | c->prev = c->geom; 2072 | resize(c, c->mon->m, 0); 2073 | } else { 2074 | /* restore previous size instead of arrange for floating windows since 2075 | * client positions are set by the user and cannot be recalculated */ 2076 | resize(c, c->prev, 0); 2077 | } 2078 | arrange(c->mon); 2079 | printstatus(); 2080 | } 2081 | 2082 | void 2083 | setgamma(struct wl_listener *listener, void *data) 2084 | { 2085 | struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; 2086 | Monitor *m = event->output->data; 2087 | m->gamma_lut_changed = 1; 2088 | wlr_output_schedule_frame(m->wlr_output); 2089 | } 2090 | 2091 | void 2092 | setlayout(const Arg *arg) 2093 | { 2094 | if (!selmon) 2095 | return; 2096 | if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) 2097 | selmon->sellt ^= 1; 2098 | if (arg && arg->v) 2099 | selmon->lt[selmon->sellt] = (Layout *)arg->v; 2100 | strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); 2101 | arrange(selmon); 2102 | printstatus(); 2103 | } 2104 | 2105 | /* arg > 1.0 will set mfact absolutely */ 2106 | void 2107 | setmfact(const Arg *arg) 2108 | { 2109 | float f; 2110 | 2111 | if (!arg || !selmon || !selmon->lt[selmon->sellt]->arrange) 2112 | return; 2113 | f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; 2114 | if (f < 0.1 || f > 0.9) 2115 | return; 2116 | selmon->mfact = f; 2117 | arrange(selmon); 2118 | } 2119 | 2120 | void 2121 | setmon(Client *c, Monitor *m, uint32_t newtags) 2122 | { 2123 | Monitor *oldmon = c->mon; 2124 | 2125 | if (oldmon == m) 2126 | return; 2127 | c->mon = m; 2128 | c->prev = c->geom; 2129 | 2130 | /* Scene graph sends surface leave/enter events on move and resize */ 2131 | if (oldmon) 2132 | arrange(oldmon); 2133 | if (m) { 2134 | /* Make sure window actually overlaps with the monitor */ 2135 | resize(c, c->geom, 0); 2136 | c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ 2137 | setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ 2138 | setfloating(c, c->isfloating); 2139 | } 2140 | focusclient(focustop(selmon), 1); 2141 | } 2142 | 2143 | void 2144 | setpsel(struct wl_listener *listener, void *data) 2145 | { 2146 | /* This event is raised by the seat when a client wants to set the selection, 2147 | * usually when the user copies something. wlroots allows compositors to 2148 | * ignore such requests if they so choose, but in dwl we always honor 2149 | */ 2150 | struct wlr_seat_request_set_primary_selection_event *event = data; 2151 | wlr_seat_set_primary_selection(seat, event->source, event->serial); 2152 | } 2153 | 2154 | void 2155 | setsel(struct wl_listener *listener, void *data) 2156 | { 2157 | /* This event is raised by the seat when a client wants to set the selection, 2158 | * usually when the user copies something. wlroots allows compositors to 2159 | * ignore such requests if they so choose, but in dwl we always honor 2160 | */ 2161 | struct wlr_seat_request_set_selection_event *event = data; 2162 | wlr_seat_set_selection(seat, event->source, event->serial); 2163 | } 2164 | 2165 | void 2166 | setup(void) 2167 | { 2168 | int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; 2169 | struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; 2170 | sigemptyset(&sa.sa_mask); 2171 | 2172 | for (i = 0; i < LENGTH(sig); i++) 2173 | sigaction(sig[i], &sa, NULL); 2174 | 2175 | wlr_log_init(log_level, NULL); 2176 | 2177 | /* The Wayland display is managed by libwayland. It handles accepting 2178 | * clients from the Unix socket, manging Wayland globals, and so on. */ 2179 | dpy = wl_display_create(); 2180 | 2181 | /* The backend is a wlroots feature which abstracts the underlying input and 2182 | * output hardware. The autocreate option will choose the most suitable 2183 | * backend based on the current environment, such as opening an X11 window 2184 | * if an X11 server is running. */ 2185 | if (!(backend = wlr_backend_autocreate(dpy, &session))) 2186 | die("couldn't create backend"); 2187 | 2188 | /* Initialize the scene graph used to lay out windows */ 2189 | scene = wlr_scene_create(); 2190 | for (i = 0; i < NUM_LAYERS; i++) 2191 | layers[i] = wlr_scene_tree_create(&scene->tree); 2192 | drag_icon = wlr_scene_tree_create(&scene->tree); 2193 | wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); 2194 | 2195 | /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user 2196 | * can also specify a renderer using the WLR_RENDERER env var. 2197 | * The renderer is responsible for defining the various pixel formats it 2198 | * supports for shared memory, this configures that for clients. */ 2199 | if (!(drw = wlr_renderer_autocreate(backend))) 2200 | die("couldn't create renderer"); 2201 | 2202 | /* Create shm, drm and linux_dmabuf interfaces by ourselves. 2203 | * The simplest way is call: 2204 | * wlr_renderer_init_wl_display(drw); 2205 | * but we need to create manually the linux_dmabuf interface to integrate it 2206 | * with wlr_scene. */ 2207 | wlr_renderer_init_wl_shm(drw, dpy); 2208 | 2209 | if (wlr_renderer_get_dmabuf_texture_formats(drw)) { 2210 | wlr_drm_create(dpy, drw); 2211 | wlr_scene_set_linux_dmabuf_v1(scene, 2212 | wlr_linux_dmabuf_v1_create_with_renderer(dpy, 4, drw)); 2213 | } 2214 | 2215 | /* Autocreates an allocator for us. 2216 | * The allocator is the bridge between the renderer and the backend. It 2217 | * handles the buffer creation, allowing wlroots to render onto the 2218 | * screen */ 2219 | if (!(alloc = wlr_allocator_autocreate(backend, drw))) 2220 | die("couldn't create allocator"); 2221 | 2222 | /* This creates some hands-off wlroots interfaces. The compositor is 2223 | * necessary for clients to allocate surfaces and the data device manager 2224 | * handles the clipboard. Each of these wlroots interfaces has room for you 2225 | * to dig your fingers in and play with their behavior if you want. Note that 2226 | * the clients cannot set the selection directly without compositor approval, 2227 | * see the setsel() function. */ 2228 | compositor = wlr_compositor_create(dpy, 6, drw); 2229 | wlr_subcompositor_create(dpy); 2230 | wlr_data_device_manager_create(dpy); 2231 | wlr_export_dmabuf_manager_v1_create(dpy); 2232 | wlr_screencopy_manager_v1_create(dpy); 2233 | wlr_data_control_manager_v1_create(dpy); 2234 | wlr_primary_selection_v1_device_manager_create(dpy); 2235 | wlr_viewporter_create(dpy); 2236 | wlr_single_pixel_buffer_manager_v1_create(dpy); 2237 | wlr_fractional_scale_manager_v1_create(dpy, 1); 2238 | 2239 | /* Initializes the interface used to implement urgency hints */ 2240 | activation = wlr_xdg_activation_v1_create(dpy); 2241 | LISTEN_STATIC(&activation->events.request_activate, urgent); 2242 | 2243 | gamma_control_mgr = wlr_gamma_control_manager_v1_create(dpy); 2244 | LISTEN_STATIC(&gamma_control_mgr->events.set_gamma, setgamma); 2245 | 2246 | /* Creates an output layout, which a wlroots utility for working with an 2247 | * arrangement of screens in a physical layout. */ 2248 | output_layout = wlr_output_layout_create(); 2249 | LISTEN_STATIC(&output_layout->events.change, updatemons); 2250 | wlr_xdg_output_manager_v1_create(dpy, output_layout); 2251 | 2252 | /* Configure a listener to be notified when new outputs are available on the 2253 | * backend. */ 2254 | wl_list_init(&mons); 2255 | LISTEN_STATIC(&backend->events.new_output, createmon); 2256 | 2257 | /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a 2258 | * Wayland protocol which is used for application windows. For more 2259 | * detail on shells, refer to the article: 2260 | * 2261 | * https://drewdevault.com/2018/07/29/Wayland-shells.html 2262 | */ 2263 | wl_list_init(&clients); 2264 | wl_list_init(&fstack); 2265 | 2266 | xdg_shell = wlr_xdg_shell_create(dpy, 6); 2267 | LISTEN_STATIC(&xdg_shell->events.new_surface, createnotify); 2268 | 2269 | layer_shell = wlr_layer_shell_v1_create(dpy, 3); 2270 | LISTEN_STATIC(&layer_shell->events.new_surface, createlayersurface); 2271 | 2272 | idle_notifier = wlr_idle_notifier_v1_create(dpy); 2273 | 2274 | idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); 2275 | LISTEN_STATIC(&idle_inhibit_mgr->events.new_inhibitor, createidleinhibitor); 2276 | 2277 | session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); 2278 | wl_signal_add(&session_lock_mgr->events.new_lock, &lock_listener); 2279 | LISTEN_STATIC(&session_lock_mgr->events.destroy, destroysessionmgr); 2280 | locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, 2281 | (float [4]){0.1, 0.1, 0.1, 1.0}); 2282 | wlr_scene_node_set_enabled(&locked_bg->node, 0); 2283 | 2284 | /* Use decoration protocols to negotiate server-side decorations */ 2285 | wlr_server_decoration_manager_set_default_mode( 2286 | wlr_server_decoration_manager_create(dpy), 2287 | WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); 2288 | xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); 2289 | LISTEN_STATIC(&xdg_decoration_mgr->events.new_toplevel_decoration, createdecoration); 2290 | 2291 | /* 2292 | * Creates a cursor, which is a wlroots utility for tracking the cursor 2293 | * image shown on screen. 2294 | */ 2295 | cursor = wlr_cursor_create(); 2296 | wlr_cursor_attach_output_layout(cursor, output_layout); 2297 | 2298 | /* Creates an xcursor manager, another wlroots utility which loads up 2299 | * Xcursor themes to source cursor images from and makes sure that cursor 2300 | * images are available at all scale factors on the screen (necessary for 2301 | * HiDPI support). Scaled cursors will be loaded with each output. */ 2302 | cursor_mgr = wlr_xcursor_manager_create(NULL, 24); 2303 | setenv("XCURSOR_SIZE", "24", 1); 2304 | 2305 | /* 2306 | * wlr_cursor *only* displays an image on screen. It does not move around 2307 | * when the pointer moves. However, we can attach input devices to it, and 2308 | * it will generate aggregate events for all of them. In these events, we 2309 | * can choose how we want to process them, forwarding them to clients and 2310 | * moving the cursor around. More detail on this process is described in 2311 | * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html 2312 | * 2313 | * And more comments are sprinkled throughout the notify functions above. 2314 | */ 2315 | LISTEN_STATIC(&cursor->events.motion, motionrelative); 2316 | LISTEN_STATIC(&cursor->events.motion_absolute, motionabsolute); 2317 | LISTEN_STATIC(&cursor->events.button, buttonpress); 2318 | LISTEN_STATIC(&cursor->events.axis, axisnotify); 2319 | LISTEN_STATIC(&cursor->events.frame, cursorframe); 2320 | 2321 | cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); 2322 | LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); 2323 | 2324 | /* 2325 | * Configures a seat, which is a single "seat" at which a user sits and 2326 | * operates the computer. This conceptually includes up to one keyboard, 2327 | * pointer, touch, and drawing tablet device. We also rig up a listener to 2328 | * let us know when new input devices are available on the backend. 2329 | */ 2330 | wl_list_init(&keyboards); 2331 | LISTEN_STATIC(&backend->events.new_input, inputdevice); 2332 | virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); 2333 | LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); 2334 | seat = wlr_seat_create(dpy, "seat0"); 2335 | LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); 2336 | LISTEN_STATIC(&seat->events.request_set_selection, setsel); 2337 | LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); 2338 | LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); 2339 | LISTEN_STATIC(&seat->events.start_drag, startdrag); 2340 | 2341 | output_mgr = wlr_output_manager_v1_create(dpy); 2342 | LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); 2343 | LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); 2344 | 2345 | wlr_scene_set_presentation(scene, wlr_presentation_create(dpy, backend)); 2346 | 2347 | #ifdef XWAYLAND 2348 | /* 2349 | * Initialise the XWayland X server. 2350 | * It will be started when the first X client is started. 2351 | */ 2352 | xwayland = wlr_xwayland_create(dpy, compositor, 1); 2353 | if (xwayland) { 2354 | LISTEN_STATIC(&xwayland->events.ready, xwaylandready); 2355 | LISTEN_STATIC(&xwayland->events.new_surface, createnotifyx11); 2356 | 2357 | setenv("DISPLAY", xwayland->display_name, 1); 2358 | } else { 2359 | fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); 2360 | } 2361 | #endif 2362 | } 2363 | 2364 | void 2365 | spawn(const Arg *arg) 2366 | { 2367 | if (fork() == 0) { 2368 | dup2(STDERR_FILENO, STDOUT_FILENO); 2369 | setsid(); 2370 | execvp(((char **)arg->v)[0], (char **)arg->v); 2371 | die("dwl: execvp %s failed:", ((char **)arg->v)[0]); 2372 | } 2373 | } 2374 | 2375 | void 2376 | startdrag(struct wl_listener *listener, void *data) 2377 | { 2378 | struct wlr_drag *drag = data; 2379 | if (!drag->icon) 2380 | return; 2381 | 2382 | drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; 2383 | LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); 2384 | } 2385 | 2386 | void 2387 | tag(const Arg *arg) 2388 | { 2389 | Client *sel = focustop(selmon); 2390 | if (!sel || (arg->ui & TAGMASK) == 0) 2391 | return; 2392 | 2393 | sel->tags = arg->ui & TAGMASK; 2394 | focusclient(focustop(selmon), 1); 2395 | arrange(selmon); 2396 | printstatus(); 2397 | } 2398 | 2399 | void 2400 | tagmon(const Arg *arg) 2401 | { 2402 | Client *sel = focustop(selmon); 2403 | if (sel) 2404 | setmon(sel, dirtomon(arg->i), 0); 2405 | } 2406 | 2407 | void 2408 | tile(Monitor *m) 2409 | { 2410 | unsigned int i, n = 0, mw, my, ty; 2411 | Client *c; 2412 | 2413 | wl_list_for_each(c, &clients, link) 2414 | if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) 2415 | n++; 2416 | if (n == 0) 2417 | return; 2418 | 2419 | if (n > m->nmaster) 2420 | mw = m->nmaster ? m->w.width * m->mfact : 0; 2421 | else 2422 | mw = m->w.width; 2423 | i = my = ty = 0; 2424 | wl_list_for_each(c, &clients, link) { 2425 | if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) 2426 | continue; 2427 | if (i < m->nmaster) { 2428 | resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, 2429 | .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); 2430 | my += c->geom.height; 2431 | } else { 2432 | resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, 2433 | .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); 2434 | ty += c->geom.height; 2435 | } 2436 | i++; 2437 | } 2438 | } 2439 | 2440 | void 2441 | togglefloating(const Arg *arg) 2442 | { 2443 | Client *sel = focustop(selmon); 2444 | /* return if fullscreen */ 2445 | if (sel && !sel->isfullscreen) 2446 | setfloating(sel, !sel->isfloating); 2447 | } 2448 | 2449 | void 2450 | togglefullscreen(const Arg *arg) 2451 | { 2452 | Client *sel = focustop(selmon); 2453 | if (sel) 2454 | setfullscreen(sel, !sel->isfullscreen); 2455 | } 2456 | 2457 | void 2458 | toggletag(const Arg *arg) 2459 | { 2460 | uint32_t newtags; 2461 | Client *sel = focustop(selmon); 2462 | if (!sel) 2463 | return; 2464 | newtags = sel->tags ^ (arg->ui & TAGMASK); 2465 | if (!newtags) 2466 | return; 2467 | 2468 | sel->tags = newtags; 2469 | focusclient(focustop(selmon), 1); 2470 | arrange(selmon); 2471 | printstatus(); 2472 | } 2473 | 2474 | void 2475 | toggleview(const Arg *arg) 2476 | { 2477 | uint32_t newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0; 2478 | 2479 | if (!newtagset) 2480 | return; 2481 | 2482 | selmon->tagset[selmon->seltags] = newtagset; 2483 | focusclient(focustop(selmon), 1); 2484 | arrange(selmon); 2485 | printstatus(); 2486 | } 2487 | 2488 | void 2489 | unlocksession(struct wl_listener *listener, void *data) 2490 | { 2491 | SessionLock *lock = wl_container_of(listener, lock, unlock); 2492 | destroylock(lock, 1); 2493 | } 2494 | 2495 | void 2496 | unmaplayersurfacenotify(struct wl_listener *listener, void *data) 2497 | { 2498 | LayerSurface *layersurface = wl_container_of(listener, layersurface, unmap); 2499 | 2500 | layersurface->mapped = 0; 2501 | wlr_scene_node_set_enabled(&layersurface->scene->node, 0); 2502 | if (layersurface == exclusive_focus) 2503 | exclusive_focus = NULL; 2504 | if (layersurface->layer_surface->output 2505 | && (layersurface->mon = layersurface->layer_surface->output->data)) 2506 | arrangelayers(layersurface->mon); 2507 | if (layersurface->layer_surface->surface == 2508 | seat->keyboard_state.focused_surface) 2509 | focusclient(focustop(selmon), 1); 2510 | motionnotify(0); 2511 | } 2512 | 2513 | void 2514 | unmapnotify(struct wl_listener *listener, void *data) 2515 | { 2516 | /* Called when the surface is unmapped, and should no longer be shown. */ 2517 | Client *c = wl_container_of(listener, c, unmap); 2518 | if (c == grabc) { 2519 | cursor_mode = CurNormal; 2520 | grabc = NULL; 2521 | } 2522 | 2523 | if (client_is_unmanaged(c)) { 2524 | if (c == exclusive_focus) 2525 | exclusive_focus = NULL; 2526 | if (client_surface(c) == seat->keyboard_state.focused_surface) 2527 | focusclient(focustop(selmon), 1); 2528 | } else { 2529 | wl_list_remove(&c->link); 2530 | setmon(c, NULL, 0); 2531 | wl_list_remove(&c->flink); 2532 | } 2533 | 2534 | wlr_scene_node_destroy(&c->scene->node); 2535 | printstatus(); 2536 | motionnotify(0); 2537 | } 2538 | 2539 | void 2540 | updatemons(struct wl_listener *listener, void *data) 2541 | { 2542 | /* 2543 | * Called whenever the output layout changes: adding or removing a 2544 | * monitor, changing an output's mode or position, etc. This is where 2545 | * the change officially happens and we update geometry, window 2546 | * positions, focus, and the stored configuration in wlroots' 2547 | * output-manager implementation. 2548 | */ 2549 | struct wlr_output_configuration_v1 *config = 2550 | wlr_output_configuration_v1_create(); 2551 | Client *c; 2552 | struct wlr_output_configuration_head_v1 *config_head; 2553 | Monitor *m; 2554 | 2555 | /* First remove from the layout the disabled monitors */ 2556 | wl_list_for_each(m, &mons, link) { 2557 | if (m->wlr_output->enabled) 2558 | continue; 2559 | config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); 2560 | config_head->state.enabled = 0; 2561 | /* Remove this output from the layout to avoid cursor enter inside it */ 2562 | wlr_output_layout_remove(output_layout, m->wlr_output); 2563 | closemon(m); 2564 | memset(&m->m, 0, sizeof(m->m)); 2565 | memset(&m->w, 0, sizeof(m->w)); 2566 | } 2567 | /* Insert outputs that need to */ 2568 | wl_list_for_each(m, &mons, link) 2569 | if (m->wlr_output->enabled 2570 | && !wlr_output_layout_get(output_layout, m->wlr_output)) 2571 | wlr_output_layout_add_auto(output_layout, m->wlr_output); 2572 | /* Now that we update the output layout we can get its box */ 2573 | wlr_output_layout_get_box(output_layout, NULL, &sgeom); 2574 | 2575 | /* Make sure the clients are hidden when dwl is locked */ 2576 | wlr_scene_node_set_position(&locked_bg->node, sgeom.x, sgeom.y); 2577 | wlr_scene_rect_set_size(locked_bg, sgeom.width, sgeom.height); 2578 | 2579 | wl_list_for_each(m, &mons, link) { 2580 | if (!m->wlr_output->enabled) 2581 | continue; 2582 | config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); 2583 | 2584 | /* Get the effective monitor geometry to use for surfaces */ 2585 | wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); 2586 | m->w = m->m; 2587 | wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); 2588 | 2589 | wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); 2590 | wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); 2591 | 2592 | if (m->lock_surface) { 2593 | struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; 2594 | wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); 2595 | wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, 2596 | m->m.height); 2597 | } 2598 | 2599 | /* Calculate the effective monitor geometry to use for clients */ 2600 | arrangelayers(m); 2601 | /* Don't move clients to the left output when plugging monitors */ 2602 | arrange(m); 2603 | /* make sure fullscreen clients have the right size */ 2604 | if ((c = focustop(m)) && c->isfullscreen) 2605 | resize(c, m->m, 0); 2606 | 2607 | m->gamma_lut_changed = 1; 2608 | config_head->state.enabled = 1; 2609 | config_head->state.mode = m->wlr_output->current_mode; 2610 | config_head->state.x = m->m.x; 2611 | config_head->state.y = m->m.y; 2612 | } 2613 | 2614 | if (selmon && selmon->wlr_output->enabled) { 2615 | wl_list_for_each(c, &clients, link) 2616 | if (!c->mon && client_surface(c)->mapped) 2617 | setmon(c, selmon, c->tags); 2618 | focusclient(focustop(selmon), 1); 2619 | if (selmon->lock_surface) { 2620 | client_notify_enter(selmon->lock_surface->surface, 2621 | wlr_seat_get_keyboard(seat)); 2622 | client_activate_surface(selmon->lock_surface->surface, 1); 2623 | } 2624 | } 2625 | 2626 | /* FIXME: figure out why the cursor image is at 0,0 after turning all 2627 | * the monitors on. 2628 | * Move the cursor image where it used to be. It does not generate a 2629 | * wl_pointer.motion event for the clients, it's only the image what it's 2630 | * at the wrong position after all. */ 2631 | wlr_cursor_move(cursor, NULL, 0, 0); 2632 | 2633 | wlr_output_manager_v1_set_configuration(output_mgr, config); 2634 | } 2635 | 2636 | void 2637 | updatetitle(struct wl_listener *listener, void *data) 2638 | { 2639 | Client *c = wl_container_of(listener, c, set_title); 2640 | if (c == focustop(c->mon)) 2641 | printstatus(); 2642 | } 2643 | 2644 | void 2645 | urgent(struct wl_listener *listener, void *data) 2646 | { 2647 | struct wlr_xdg_activation_v1_request_activate_event *event = data; 2648 | Client *c = NULL; 2649 | toplevel_from_wlr_surface(event->surface, &c, NULL); 2650 | if (!c || c == focustop(selmon)) 2651 | return; 2652 | 2653 | if (client_surface(c)->mapped) 2654 | client_set_border_color(c, urgentcolor); 2655 | c->isurgent = 1; 2656 | printstatus(); 2657 | } 2658 | 2659 | void 2660 | view(const Arg *arg) 2661 | { 2662 | if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) 2663 | return; 2664 | selmon->seltags ^= 1; /* toggle sel tagset */ 2665 | if (arg->ui & TAGMASK) 2666 | selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; 2667 | focusclient(focustop(selmon), 1); 2668 | arrange(selmon); 2669 | printstatus(); 2670 | } 2671 | 2672 | void 2673 | virtualkeyboard(struct wl_listener *listener, void *data) 2674 | { 2675 | struct wlr_virtual_keyboard_v1 *keyboard = data; 2676 | createkeyboard(&keyboard->keyboard); 2677 | } 2678 | 2679 | Monitor * 2680 | xytomon(double x, double y) 2681 | { 2682 | struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); 2683 | return o ? o->data : NULL; 2684 | } 2685 | 2686 | void 2687 | xytonode(double x, double y, struct wlr_surface **psurface, 2688 | Client **pc, LayerSurface **pl, double *nx, double *ny) 2689 | { 2690 | struct wlr_scene_node *node, *pnode; 2691 | struct wlr_surface *surface = NULL; 2692 | Client *c = NULL; 2693 | LayerSurface *l = NULL; 2694 | int layer; 2695 | 2696 | for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { 2697 | if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) 2698 | continue; 2699 | 2700 | if (node->type == WLR_SCENE_NODE_BUFFER) 2701 | surface = wlr_scene_surface_try_from_buffer( 2702 | wlr_scene_buffer_from_node(node))->surface; 2703 | /* Walk the tree to find a node that knows the client */ 2704 | for (pnode = node; pnode && !c; pnode = &pnode->parent->node) 2705 | c = pnode->data; 2706 | if (c && c->type == LayerShell) { 2707 | c = NULL; 2708 | l = pnode->data; 2709 | } 2710 | } 2711 | 2712 | if (psurface) *psurface = surface; 2713 | if (pc) *pc = c; 2714 | if (pl) *pl = l; 2715 | } 2716 | 2717 | void 2718 | zoom(const Arg *arg) 2719 | { 2720 | Client *c, *sel = focustop(selmon); 2721 | 2722 | if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) 2723 | return; 2724 | 2725 | /* Search for the first tiled window that is not sel, marking sel as 2726 | * NULL if we pass it along the way */ 2727 | wl_list_for_each(c, &clients, link) 2728 | if (VISIBLEON(c, selmon) && !c->isfloating) { 2729 | if (c != sel) 2730 | break; 2731 | sel = NULL; 2732 | } 2733 | 2734 | /* Return if no other tiled window was found */ 2735 | if (&c->link == &clients) 2736 | return; 2737 | 2738 | /* If we passed sel, move c to the front; otherwise, move sel to the 2739 | * front */ 2740 | if (!sel) 2741 | sel = c; 2742 | wl_list_remove(&sel->link); 2743 | wl_list_insert(&clients, &sel->link); 2744 | 2745 | focusclient(sel, 1); 2746 | arrange(selmon); 2747 | } 2748 | 2749 | #ifdef XWAYLAND 2750 | void 2751 | activatex11(struct wl_listener *listener, void *data) 2752 | { 2753 | Client *c = wl_container_of(listener, c, activate); 2754 | 2755 | /* Only "managed" windows can be activated */ 2756 | if (c->type == X11Managed) 2757 | wlr_xwayland_surface_activate(c->surface.xwayland, 1); 2758 | } 2759 | 2760 | void 2761 | associatex11(struct wl_listener *listener, void *data) 2762 | { 2763 | Client *c = wl_container_of(listener, c, associate); 2764 | 2765 | LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); 2766 | LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); 2767 | } 2768 | 2769 | void 2770 | configurex11(struct wl_listener *listener, void *data) 2771 | { 2772 | Client *c = wl_container_of(listener, c, configure); 2773 | struct wlr_xwayland_surface_configure_event *event = data; 2774 | if (!c->mon) 2775 | return; 2776 | if (c->isfloating || c->type == X11Unmanaged) 2777 | resize(c, (struct wlr_box){.x = event->x, .y = event->y, 2778 | .width = event->width, .height = event->height}, 0); 2779 | else 2780 | arrange(c->mon); 2781 | } 2782 | 2783 | void 2784 | createnotifyx11(struct wl_listener *listener, void *data) 2785 | { 2786 | struct wlr_xwayland_surface *xsurface = data; 2787 | Client *c; 2788 | 2789 | /* Allocate a Client for this surface */ 2790 | c = xsurface->data = ecalloc(1, sizeof(*c)); 2791 | c->surface.xwayland = xsurface; 2792 | c->type = xsurface->override_redirect ? X11Unmanaged : X11Managed; 2793 | c->bw = borderpx; 2794 | 2795 | /* Listen to the various events it can emit */ 2796 | LISTEN(&xsurface->events.associate, &c->associate, associatex11); 2797 | LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); 2798 | LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); 2799 | LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); 2800 | LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); 2801 | LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); 2802 | LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); 2803 | LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); 2804 | } 2805 | 2806 | void 2807 | dissociatex11(struct wl_listener *listener, void *data) 2808 | { 2809 | Client *c = wl_container_of(listener, c, dissociate); 2810 | wl_list_remove(&c->map.link); 2811 | wl_list_remove(&c->unmap.link); 2812 | } 2813 | 2814 | xcb_atom_t 2815 | getatom(xcb_connection_t *xc, const char *name) 2816 | { 2817 | xcb_atom_t atom = 0; 2818 | xcb_intern_atom_reply_t *reply; 2819 | xcb_intern_atom_cookie_t cookie = xcb_intern_atom(xc, 0, strlen(name), name); 2820 | if ((reply = xcb_intern_atom_reply(xc, cookie, NULL))) 2821 | atom = reply->atom; 2822 | free(reply); 2823 | 2824 | return atom; 2825 | } 2826 | 2827 | void 2828 | sethints(struct wl_listener *listener, void *data) 2829 | { 2830 | Client *c = wl_container_of(listener, c, set_hints); 2831 | struct wlr_surface *surface = client_surface(c); 2832 | if (c == focustop(selmon)) 2833 | return; 2834 | 2835 | c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); 2836 | 2837 | if (c->isurgent && surface && surface->mapped) 2838 | client_set_border_color(c, urgentcolor); 2839 | 2840 | printstatus(); 2841 | } 2842 | 2843 | void 2844 | xwaylandready(struct wl_listener *listener, void *data) 2845 | { 2846 | struct wlr_xcursor *xcursor; 2847 | xcb_connection_t *xc = xcb_connect(xwayland->display_name, NULL); 2848 | int err = xcb_connection_has_error(xc); 2849 | if (err) { 2850 | fprintf(stderr, "xcb_connect to X server failed with code %d\n. Continuing with degraded functionality.\n", err); 2851 | return; 2852 | } 2853 | 2854 | /* Collect atoms we are interested in. If getatom returns 0, we will 2855 | * not detect that window type. */ 2856 | netatom[NetWMWindowTypeDialog] = getatom(xc, "_NET_WM_WINDOW_TYPE_DIALOG"); 2857 | netatom[NetWMWindowTypeSplash] = getatom(xc, "_NET_WM_WINDOW_TYPE_SPLASH"); 2858 | netatom[NetWMWindowTypeToolbar] = getatom(xc, "_NET_WM_WINDOW_TYPE_TOOLBAR"); 2859 | netatom[NetWMWindowTypeUtility] = getatom(xc, "_NET_WM_WINDOW_TYPE_UTILITY"); 2860 | 2861 | /* assign the one and only seat */ 2862 | wlr_xwayland_set_seat(xwayland, seat); 2863 | 2864 | /* Set the default XWayland cursor to match the rest of dwl. */ 2865 | if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "default", 1))) 2866 | wlr_xwayland_set_cursor(xwayland, 2867 | xcursor->images[0]->buffer, xcursor->images[0]->width * 4, 2868 | xcursor->images[0]->width, xcursor->images[0]->height, 2869 | xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); 2870 | 2871 | xcb_disconnect(xc); 2872 | } 2873 | #endif 2874 | 2875 | int 2876 | main(int argc, char *argv[]) 2877 | { 2878 | char *startup_cmd = NULL; 2879 | int c; 2880 | 2881 | while ((c = getopt(argc, argv, "s:hdv")) != -1) { 2882 | if (c == 's') 2883 | startup_cmd = optarg; 2884 | else if (c == 'd') 2885 | log_level = WLR_DEBUG; 2886 | else if (c == 'v') 2887 | die("dwl " VERSION); 2888 | else 2889 | goto usage; 2890 | } 2891 | if (optind < argc) 2892 | goto usage; 2893 | 2894 | /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ 2895 | if (!getenv("XDG_RUNTIME_DIR")) 2896 | die("XDG_RUNTIME_DIR must be set"); 2897 | setup(); 2898 | run(startup_cmd); 2899 | cleanup(); 2900 | return EXIT_SUCCESS; 2901 | 2902 | usage: 2903 | die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); 2904 | } 2905 | --------------------------------------------------------------------------------