├── .build.yml ├── .clang-format ├── .gitignore ├── Contributing.md ├── LICENSE ├── README.md ├── changelog ├── v0.3.2.md ├── v0.4.0.md ├── v0.4.1.md ├── v0.4.2.md ├── v0.5.0.md ├── v0.5.1.md ├── v0.5.2.md ├── v0.6.0.md ├── v0.6.1.md ├── v0.6.2.md ├── v0.7.0.md └── v0.8.0.md ├── makedocs.sh ├── manpages ├── awesome.1.md ├── es │ └── awesome.1.md ├── fr │ ├── awesome.1.md │ └── way-cooler.1.md └── way-cooler.1.md ├── meson.build ├── protocols ├── meson.build ├── way-cooler-keybindings-unstable-v1.xml ├── way-cooler-mousegrabber-unstable-v1.xml └── wlr-layer-shell-unstable-v1.xml ├── run-clang-format.py └── way-cooler ├── cursor.c ├── cursor.h ├── input.c ├── input.h ├── keybindings.c ├── keybindings.h ├── keyboard.c ├── keyboard.h ├── layer_shell.c ├── layer_shell.h ├── main.c ├── meson.build ├── mousegrabber.c ├── mousegrabber.h ├── output.c ├── output.h ├── pointer.c ├── pointer.h ├── seat.c ├── seat.h ├── server.c ├── server.h ├── view.c ├── view.h ├── xdg.c ├── xdg.h ├── xkb_hash_set.c ├── xkb_hash_set.h ├── xwayland.c └── xwayland.h /.build.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - lua 4 | - meson 5 | - ninja 6 | - wayland 7 | - wayland-protocols 8 | - mesa 9 | - xorg-server-xwayland 10 | - cairo 11 | - xcb-util-image 12 | - libinput 13 | - pixman 14 | - libxkbcommon 15 | - gdk-pixbuf2 16 | - xcb-util-image 17 | - libcap 18 | - clang 19 | - libxcb 20 | - xcb-util-image 21 | - xcb-util-cursor 22 | - xcb-util-wm 23 | - python 24 | sources: 25 | - https://github.com/way-cooler/way-cooler 26 | - https://github.com/swaywm/wlroots 27 | tasks: 28 | - check-c-style: | 29 | cd way-cooler 30 | python ./run-clang-format.py -r ./way-cooler/ 31 | - wlroots: | 32 | cd wlroots 33 | # TODO Update to 0.8.2 or 0.9.0 whichever comes first 34 | git checkout cde544de 35 | meson --prefix=/usr build -Drootston=false -Dexamples=false 36 | ninja -C build 37 | sudo ninja -C build install 38 | - way-cooler-common: | 39 | cd way-cooler 40 | CC=gcc meson build-gcc 41 | CC=clang meson build-clang 42 | - way-cooler-gcc: | 43 | cd way-cooler/build-gcc 44 | ninja 45 | - way-cooler-clang: | 46 | cd way-cooler/build-clang 47 | ninja 48 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Chromium 4 | 5 | AccessModifierOffset: -1 6 | 7 | AlignAfterOpenBracket: DontAlign 8 | AlignConsecutiveAssignments: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlines: Left 11 | AlignOperands: false 12 | AlignTrailingComments: false 13 | 14 | AllowAllArgumentsOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: false 16 | AllowShortBlocksOnASingleLine: false 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: None 19 | AllowShortIfStatementsOnASingleLine: false 20 | AllowShortLoopsOnASingleLine: false 21 | 22 | AlwaysBreakAfterDefinitionReturnType: None 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakBeforeMultilineStrings: true 25 | 26 | BinPackArguments: true 27 | BinPackParameters: true 28 | 29 | # NOTE: Only takes effect if BreakBeforeBraces is "Custom" 30 | BraceWrapping: 31 | AfterClass: false 32 | AfterControlStatement: false 33 | AfterEnum: false 34 | AfterFunction: false 35 | AfterNamespace: false 36 | AfterObjCDeclaration: false 37 | AfterStruct: false 38 | AfterUnion: false 39 | AfterExternBlock: false 40 | BeforeCatch: false 41 | BeforeElse: false 42 | IndentBraces: false 43 | SplitEmptyFunction: true 44 | SplitEmptyRecord: true 45 | SplitEmptyNamespace: true 46 | 47 | BreakBeforeBinaryOperators: None 48 | BreakBeforeBraces: Attach 49 | BreakBeforeTernaryOperators: false 50 | BreakStringLiterals: true 51 | 52 | ColumnLimit: 80 53 | 54 | ContinuationIndentWidth: 8 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | 59 | IncludeBlocks: Preserve 60 | IncludeCategories: 61 | - Regex: '^' 62 | Priority: 2 63 | - Regex: '^<.*\.h>' 64 | Priority: 1 65 | - Regex: '^<.*' 66 | Priority: 2 67 | - Regex: '.*' 68 | Priority: 3 69 | IncludeIsMainRegex: '([-_](test|unittest))?$' 70 | 71 | IndentCaseLabels: false 72 | IndentPPDirectives: None 73 | IndentWidth: 4 74 | IndentWrappedFunctionNames: false 75 | 76 | KeepEmptyLinesAtTheStartOfBlocks: false 77 | MaxEmptyLinesToKeep: 1 78 | 79 | # TODO What? 80 | PenaltyBreakAssignment: 2 81 | PenaltyBreakBeforeFirstCallParameter: 1 82 | PenaltyBreakComment: 300 83 | PenaltyBreakFirstLessLess: 120 84 | PenaltyBreakString: 1000 85 | PenaltyBreakTemplateDeclaration: 10 86 | PenaltyExcessCharacter: 1000000 87 | PenaltyReturnTypeOnItsOwnLine: 200 88 | 89 | PointerAlignment: Right 90 | 91 | SortIncludes: true 92 | 93 | SpaceAfterCStyleCast: false 94 | SpaceBeforeAssignmentOperators: true 95 | SpaceBeforeCtorInitializerColon: true 96 | SpaceBeforeParens: ControlStatements 97 | SpaceBeforeRangeBasedForLoopColon: true 98 | SpaceInEmptyParentheses: false 99 | SpacesInAngles: false 100 | SpacesInContainerLiterals: true 101 | SpacesInCStyleCastParentheses: false 102 | SpacesInParentheses: false 103 | SpacesInSquareBrackets: false 104 | 105 | Standard: Auto 106 | TabWidth: 4 107 | UseTab: ForContinuationAndIndentation 108 | ... 109 | 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Generated by meson 8 | build 9 | 10 | # For building wlroots as a subproject 11 | subprojects 12 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Style Guide 2 | 3 | ## General Guidelines 4 | 5 | ## Git 6 | 7 | [Use good git commit messages](https://chris.beams.io/posts/git-commit/). Please 8 | squash/rebase liberally. 9 | 10 | ## C formatting 11 | 12 | Way Cooler formats its C code using clang-format. This format is mostly the same 13 | format as wlroots, though there are some differences. Whatever clang-format 14 | corrects to is the correct style. 15 | 16 | Imports are grouped as described by the Google import style guide. 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Immington Industries 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Way Cooler 2 | [![Downloads](https://img.shields.io/crates/d/way-cooler.svg)](https://crates.io/crates/way-cooler) 3 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/way-cooler/way-cooler/) 4 | 5 | Way Cooler is the compositor component of [AwesomeWM][] for [Wayland][]. 6 | 7 | ## Building 8 | 9 | To build Way Cooler, ensure you have meson installed 10 | (as well as [wlroots][], or use the `subprojects/` directory 11 | and build it locally). 12 | 13 | Then, execute: 14 | 15 | ```bash 16 | meson build 17 | ninja -C build 18 | ``` 19 | 20 | To run the compositor simply execute `build/way-cooler/way-cooler` in a TTY or 21 | any existing window manager. 22 | 23 | It can run with [this patched version of the Awesome 24 | client](https://github.com/way-cooler/awesome). The simplest way to execute both 25 | is to run `way-cooler -c `. 26 | 27 | Though technically they can run standalone, the compositor is not usable by 28 | itself and the client will fail out in other compositor due to the custom 29 | protocols not being present. 30 | 31 | ## Development 32 | 33 | Way Cooler is under active development. If you would like to contribute you can 34 | contact me best on [IRC][] (I also hang out on freenode). 35 | 36 | **Master is not usable for production**. There are old versions of Way Cooler 37 | that do work, however: 38 | 39 | * Is written in Rust and must be built with `cargo`. 40 | * They use an old framework, [wlc][], and thus are very limited and buggy. 41 | * Was not designed to emulate Awesome, but instead has [i3][] tiling and its own 42 | (very incomplete) Lua libraries. 43 | 44 | [Wayland]: https://wayland.freedesktop.org/ 45 | [wlc]: https://github.com/Cloudef/wlc 46 | [AwesomeWM]: https://awesomewm.org/ 47 | [wlroots]: https://github.com/swaywm/wlroots 48 | [IRC]: https://webchat.oftc.net/?channels=awesome&uio=d4 49 | [i3]: https://i3wm.org 50 | -------------------------------------------------------------------------------- /changelog/v0.3.2.md: -------------------------------------------------------------------------------- 1 | # Keyboard fix, layout improvements 2 | 3 | This branch is mostly a couple of bugfixes and a couple of layout additions. 4 | 5 | ## New Features 6 | - `--version` flag 7 | - windows can be moved between containers 8 | - **New keybindings in int file** - `Alt+Shift+` to move window between containers 9 | 10 | ## Bugfixes 11 | - Issues with key bindings sometimes not working 12 | - Tiling losing focus when dmenu was closed 13 | - WIndows with close prompts not being closable 14 | - Switching to current workspace turning wndows invisible 15 | -------------------------------------------------------------------------------- /changelog/v0.4.0.md: -------------------------------------------------------------------------------- 1 | # D-Bus IPC, Background separation. 2 | 3 | ## Major Highlights 4 | - Switched IPC to D-Bus 5 | - To run in a TTY session, need to start Way Cooler with `dbus-launch --exit-with-session way-cooler` 6 | - Removed background program, it is now a [separate project](https://github.com/Immington-Industries/way-cooler-bg) 7 | - Added support for images, including png, jpg, and gif formats. 8 | - Fixed issue where the cursor could not be loaded on some distros (such as Ubuntu). We now package a backup cursor icon. 9 | 10 | ## Tiling/Layout 11 | - Can now resize windows 12 | - When resizing tiled windows, it is ensured that you can not make them size 0. 13 | - Resize with `ctrl+` and drag. 14 | - Can now make windows floating with `Alt+Shift+Space` 15 | - Switch between floating and non-floating with `Alt+Space` 16 | - Way Cooler now remembers what window you last had focused, using a clever "path" system to keep track of active paths. 17 | - Fixed problem where the active path was not being updated properly when switching workspaces 18 | - Reduced the number of debug print statements. 19 | 20 | ## Init file 21 | - Added special function `way_cooler_init`. This function will be triggered after Way Cooler has completed initialization. It is where all start up programs should go, and ultimately will be a nice place to hand out tokens when we implement security levels to the IPC. 22 | -------------------------------------------------------------------------------- /changelog/v0.4.1.md: -------------------------------------------------------------------------------- 1 | # Basic binary distribution, in-place restart, tiling fixes 2 | 3 | ## Configuration 4 | - Can now restart Way Cooler in place. Will restart the background program (if any), and reload all keybindings and other options. 5 | 6 | ## Tiling 7 | - Can now open a new window even if there is no currently active container 8 | - Closing a window now uses the "recently used" path to focus on the next window 9 | - Popup windows will now stay on their workspaces and not otherwise do weird things (mostly) 10 | - Floating windows are now always rendered above tiled windows 11 | - Floating an empty workspace no longer causes Way Cooler to hang 12 | 13 | ## Logistics 14 | - Way Cooler can now be compiled statically with wlc 15 | - Basic binary distribution. Currently only x86_64, will later have i686 and armv6 here as well. To install, unpack the tar file and run `install.sh` 16 | - Added a login manager entry for Way Cooler (confirmed to work on light-dm and gdm) 17 | 18 | ## Background 19 | - `way-cooler-bg` no longer needs to have a cursor file specified. 20 | -------------------------------------------------------------------------------- /changelog/v0.4.2.md: -------------------------------------------------------------------------------- 1 | # Fullscreen command, tiling fixes, better diagnostics 2 | 3 | ## Commands / Keybindings 4 | - Added fullscreen command 5 | - Default keybinding is `mod+f` 6 | - Command is also exposed through the D-Bus command `FullScreen` 7 | - Added commands to control the location of the pointer. 8 | - Absolute positioning (`SetPointerPos`) 9 | - Placing the mouse at the corner of a container (`GrabAtCorner`) 10 | - Modifier for mouse controls (eg resize and move window) is now configurable 11 | - The default modifier is now `Alt`, instead of the previous `Ctrl` 12 | 13 | ## Tiling 14 | - Popup windows now automatically are floating. 15 | - Wayland apps (eg `termite`, `weston-terminal`, etc) should no longer have blurry text. 16 | - Resizing a window while running Way Cooler from a TTY now works correctly. 17 | 18 | ## Logging 19 | - Way Cooler now log all environment variables in the prelude of the log. 20 | - When a key is bound to a command that is now logged. 21 | 22 | ## Background 23 | - Resizing the output / changing the resolution will now properly update the background. 24 | 25 | ## Misc 26 | - Already existing debug checks have been turned on for release builds. 27 | - Restart should properly call the user-defined restart callback now. 28 | - Updated to use the `StableGraph` of Petgraph, which should improve performance and reliability. 29 | -------------------------------------------------------------------------------- /changelog/v0.5.0.md: -------------------------------------------------------------------------------- 1 | # Mini-graphical update 2 | 3 | This release expands the graphical capabilities of Way Cooler, adding core features such as borders and bar support while also paving the way for more advanced features such as notifications. 4 | 5 | This is also the first release with a binary attached. It is an x86_64 ready-to-run version of Way Cooler statically linked to the latest wlc release (0.0.8). 6 | 7 | ## New Configuration format 8 | - The configuration format has changed significantly. **This is a backward incompatible change**. Please see the example configuration for more information. 9 | - In short, values must now be part of a category (e.g, no longer `border_size` it's `way_cooler.windows = { borders = { size = 20 } }`, for example). 10 | - `config` object has been merged into `way_cooler`. 11 | 12 | ## X11 Bar support 13 | - Added support for X11 bars, such as `polybar` and `lemonbar` 14 | - To enable, set the `x11_bar` variable under the `programs` category to the name of the window that the bar spawns (e.g, `bar` when running lemonbar as `lemonbar -n "bar"` 15 | - Support for more integrated bars will come in a later version. 16 | 17 | ## Borders 18 | - Added compositor/server side borders. 19 | - Size and active/inactive color configurable through configuration option. 20 | - Color specified as a hexadecimal number 21 | - Title bars are also independently configurable, e.g they have their own size and colors. 22 | 23 | ## Gaps 24 | - Added gap support between windows. You can think of these as transparent borders. 25 | 26 | ## D-Bus 27 | - Commands now block when locking the layout tree. This means that commands should fail much less often especially when in a tight loop. 28 | - Added `ActiveWorkspace` command to get the name of the current workspace. 29 | 30 | ## Bug Fixes 31 | - Context menus should stop disappearing/crashing the program. 32 | - Note that they may not be positioned correctly, this will be fixed in a later patch. 33 | - Floating windows will now properly become fullscreen. 34 | - Defaulting to the pre-compiled configuration file is now much more obvious in the log. 35 | - Floating windows should now no longer snap back to their position. 36 | - Lua thread listener should now no longer die when restarting and having a bad config. 37 | - Floating a window in a sub container should no longer cause a crash. 38 | - The background should now resize when the output changes resolution. 39 | -------------------------------------------------------------------------------- /changelog/v0.5.1.md: -------------------------------------------------------------------------------- 1 | # Bug fixes, more configuration changes 2 | 3 | ## Bug Fixes 4 | - Fixed race condition that caused tree instability (e.g a crash) 5 | - This was normally triggered by closing e.g `mpv` with the close window command. 6 | 7 | ## Configuration 8 | 9 | **Backwards incompatible changes** 10 | - Cleaned up util functions 11 | - Removed `spawn_dmenu` and `spawn_terminal` command, please use `util.program.spawn_once` instead. 12 | -------------------------------------------------------------------------------- /changelog/v0.5.2.md: -------------------------------------------------------------------------------- 1 | # Border improvements, configurable pointer snap, bug fixes 2 | 3 | ## Resizing 4 | * There is now an option to select the behaviour of the mouse when resizing (e.g, snap to the window like in Awesome, or don't snap anywhere like in i3). See the updated default configuration for more information. #269 5 | + This added a new configuration category, `mouse`. This will be expanded later to provide more options for the mouse, including libinput configuration once #189 is addressed. 6 | 7 | ## Borders 8 | * Title bars will now update their text to reflect the title of the window #270 9 | * Gaps and borders can now be used at the same time #263 10 | * Active border color should now work better (e.g less times when two are highlighted active, or when none of them are even though the user is focused on a window) #263 11 | 12 | ## Windows 13 | * Popup windows now attempt to focus in the center of the screen, and are never too small than the minimum floating window limit. #264 14 | + Note that due to limitations in Xwayland, they may not always have their geometry properly set, so they may still sometimes appear in the top left corner. 15 | * Way Cooler now remembers which view you focused on within in a container. In other words, the algorithm to select the last active window is now one-to-one with i3. #204 16 | 17 | ## Bug Fixes 18 | Sending containers across workspaces will no longer cause a duplicate active number (and a crash) #267 19 | -------------------------------------------------------------------------------- /changelog/v0.6.0.md: -------------------------------------------------------------------------------- 1 | # Full i3 tiling, scrot-support, lock-screen support, redshift-support, multi-head output support, and of course bug fixes 2 | 3 | This has been, by far, the biggest release for Way Cooler. I'd lie if I said this was a result of feature creep. There were actually features I removed in favour of a later release (namely #273 and #138) just so I could get this one out of the door. 4 | 5 | Way Cooler has come a long way. If you want to learn more about what has changed please read this release, the linked issues, the [up-to-date README](https://github.com/way-cooler/way-cooler/blob/master/README.md), look at [our fancy new website](http://way-cooler.org), and finally look at the fancy pictures I have attached that I took while developing these features. 6 | 7 | If you'd like to download Way Cooler, the best way is through the download page on the site: http://way-cooler.org/download 8 | 9 | Going forward, more attention is going to be paid to the Lua and D-Bus API. There's going to be some huge changes, but there will be stabilization happening not soon after that in order to encourage the development of client programs for Way Cooler. Once that is done, I will finally get started on writing the last really needed client program for Way Cooler: the top bar. 10 | 11 | 12 | ## Redshift support 13 | #201 #285 14 | Contrary to popular belief, you *can* use Wayland with redshift :wink:. Way Cooler now works with a [patched version of redshift](https://github.com/giucam/redshift). To specify for it to work, simply specify `redshift -m wayland` and it will work just as it did before. 15 | 16 | This is the same way Sway does it, and if you want this to become mainstream I suggest talking to the maintainers of redshift in order to get this merged into upstream. 17 | 18 | ## Multi-head output 19 | #297 #64 20 | Finally, finally Way Cooler now supports multiple monitors. It doesn't yet support hotplugging, due to a bug in wlc, however if you start Way Cooler with multiple monitors plugged in they will properly be assigned and used. 21 | 22 | ## Scrot-like program 23 | #171 24 | There's a new program in the growing repository for Way Cooler, [wc-grab](https://github.com/way-cooler/way-cooler-grab). This is a fairly simple program that emulates scrot somewhat. It takes a screenshot of the current active output and dumps it to a file. By default, it dumps it to `screenshot.png`, but that can be controlled with the `-o` flag. 25 | 26 | See `wc-grab --help` for more information. 27 | 28 | ## Lockscreen support 29 | #279 30 | Way Cooler now supports lock screens as of #279. In order to lock the screen, in the Lua file you simply specify which program should be used. When the appropriate keybinding is pressed, Way Cooler locks down and puts the client program front and center. Once the client program closes (either gracefully or not), control is returned to the user. 31 | 32 | I have made [wc-lock](https://github.com/way-cooler/way-cooler-lock) as a simple implementation of a lock screen for Way Cooler. It uses PAM to authenticate, so once the program is spawned the user simply has to type in their password and hit enter in order to return to the normal operation. 33 | 34 | ## Basic mode support 35 | #299 36 | Related to lock screen support, basic modes have been added to Way Cooler. There are three so far: 37 | 1) Default - just like before 38 | 2) Locked - when a lockscreen is active 39 | 3) Custom - Everything runs as in default, but for every wlc callback a custom Lua function can be invoked 40 | 41 | For more information, see the attached issue #299 . 42 | 43 | ## Tabbed/Stacked tiling support 44 | #301 #163 45 | Way Cooler now has tabbed/stacked tiling support, just like i3/sway does. This is the last i3-related feature that is plan to be added. If more would like to be added, I suggest either sending a patch or letting your voice heard why a certain feature should be ported as well. 46 | 47 | ## Container borders 48 | Related to tabbed/stacked tiling, nested containers now have borders drawn around them, making it much easier to see where nested containers are without guessing from the way the windows are laid out. 49 | 50 | ## Bug fixes 51 | * Can no longer remove root container in workspaces #280 52 | * Fixed duplicate active number crash #276 53 | * Background now uses output geometry #291 54 | * Fixed issues when gaps were enabled #289 55 | -------------------------------------------------------------------------------- /changelog/v0.6.1.md: -------------------------------------------------------------------------------- 1 | # Documentation and Bug fixes 2 | 3 | ## New features 4 | This version introduces two new features: The ability to unbind keybindings and to allow passthrough for keybindings. See #345 for more details. 5 | 6 | ## Changes to existing features 7 | * Root level containers have always have borders now. #315 8 | * If `XDG_RUNTIME_DIR` doesn't exist, an error explaining that is logged. #310 9 | 10 | ## Bugfixes 11 | * Screen scraping doesn't scrape active output (needs v0.2 of `wc-grab`) #349 12 | * Nested sub containers do not render borders properly when using new container command when a new container can't be spawned (e.g when there's one child) #344 13 | * Popup windows are no longer sized incorrectly #337 14 | * Popup windows should now be always centered on the screen correctly #218 15 | * Switching containers to a new workspace will no longer cause it to be invisible on a workspace that is not active. If it is moved to an inactive workspace, that one is made visible instead #333 16 | * Floating windows now update their focused colour correctly #320 17 | * Floating sub windows (e.g right click menus) no longer spawn with borders #319 18 | * Floating windows (such as dmenu) are no longer hidden when spawned in a tabbed/stacked container #317 19 | 20 | 21 | 22 | * Nested Tabbed/Stacked borders are now rendered correctly #313 23 | -------------------------------------------------------------------------------- /changelog/v0.6.2.md: -------------------------------------------------------------------------------- 1 | # Fixed regressions, quality of life enhancements 2 | 3 | ## Lua 4 | Added the no-op command, this allows the user to register a keybinding but not yet make it do anything. #363 5 | 6 | ## Tiling bugfixes 7 | * dmenu will no longer render as too tall #355 8 | * Scrollbars and other small windows no longer have weird visual artifacts #364 9 | * All popup windows should now be properly focused on the screen #218 10 | 11 | ## Distribution 12 | * The install page on way-cooler.org should function correctly now 13 | * Binaries are now statically compiled with wlc by default 14 | -------------------------------------------------------------------------------- /changelog/v0.7.0.md: -------------------------------------------------------------------------------- 1 | # EOL WLC, extension program improvements, better tiling borders 2 | 3 | This is the final release of Way Cooler that will utilize wlc. From this point forward, consider [rust-wlc](https://github.com/way-cooler/rust-wlc) abandoned. 4 | 5 | Way Cooler is switching to [wlroots](https://github.com/swaywm/wlroots). You can follow the porting process [on our wlroots-rs bindings repo](https://github.com/swaywm/wlroots-rs). Once those bindings are complete, Way Cooler will switch to wlroots for v0.8.0. Once the move is complete, work will begin again on making Way Cooler function as a drop-in replacement for AwesomeWM. 6 | 7 | # Distribution 8 | * NixOS users can now enjoy a much more complete Way Cooler experince thanks to the work done by @gnidorah. (https://github.com/way-cooler/way-cooler/issues/446) 9 | * Install script from http://way-cooler.org/download now properly sets the uid bit for non-systemd systems. (https://github.com/way-cooler/way-cooler/issues/398) 10 | * Raspberry Pi's are now officially supported. (https://github.com/way-cooler/way-cooler/issues/369) 11 | * It is now possible to compile Way Cooler for ArmV7 devices. (https://github.com/way-cooler/way-cooler/issues/445) 12 | 13 | # Extension Programs 14 | Note that all of the extension programs (`wc-bg`, `wc-lock`, and `wc-grab`) have **backward incompatible** changes in this release. Previous versions will not work as expected! 15 | 16 | ## Background 17 | The following changes take effect in [this release](https://github.com/way-cooler/way-cooler-bg/releases/tag/v0.3.0): 18 | * The background program binary has been renamed to `wc-bg` (originally `way-cooler-bg`). 19 | * The background program now properly assigns backgrounds to all outputs. 20 | * A default background is now used when the background program is provided no arguments. Thanks to @platipo for the background contribution! (https://github.com/way-cooler/way-cooler/issues/141) 21 | * The background program no longer uses a hack in order to render but uses the standard desktop-shell Wayland protocol. 22 | + As a consequence, you can now use `sway-bg` on Way Cooler and `wc-bg` on Sway. 23 | 24 | ## Lockscreen 25 | * The lockscreen program no longer uses a hack in order to render but uses a modified version of the desktop-shell protocol. 26 | + It is modified so that we can lock multiple screens using an effect. This may change in the future, but that means we are non-standard compared to e.g Sway. 27 | * Added a fancy new blur effect that will blur the screen when it locks. (https://github.com/way-cooler/way-cooler-lock/releases/tag/v0.2.0) 28 | 29 | ## Screenshot taker 30 | * Updated to use the latest D-Bus protocol. (https://github.com/way-cooler/way-cooler-grab/releases/tag/v0.3.0) 31 | 32 | # Way Cooler 33 | ## Configuration 34 | * Can now choose whether root containers have borders or not. (https://github.com/way-cooler/way-cooler/pull/451) 35 | 36 | ## Dependencies 37 | * Moved from hlua to rlua in preparation for AwesomeWM compatibility. (https://github.com/way-cooler/way-cooler/issues/378) 38 | * Updated wayland-rs to v0.12.0. (https://github.com/way-cooler/way-cooler/pull/452) 39 | + Fixes build error on some Ubuntu systems. (https://github.com/way-cooler/way-cooler/pull/452) 40 | * Updated petgraph to v0.4.7 (https://github.com/way-cooler/way-cooler/pull/400) 41 | * Updated rust-wlc. 42 | 43 | ## WLC updates 44 | * Updated to use the latest pointer motion callback. This fixes the rounding bug that would cause the mouse to not perform as expected. (https://github.com/way-cooler/way-cooler/pull/453) 45 | * Can now copy text from/to pure Wayland and XWayland clients. (https://github.com/way-cooler/way-cooler/issues/328) 46 | 47 | ## AwesomeWM Compatibility 48 | * Started preleminary work on AwesomeWM compatibility. (https://github.com/way-cooler/way-cooler/pull/383, https://github.com/way-cooler/way-cooler/pull/396, https://github.com/way-cooler/way-cooler/pull/429, https://github.com/way-cooler/way-cooler/pull/423) 49 | + Most of this is setting up the OO and signal systems used by the Lua libraries. This is mostly complete and all that's left is implementing the interfaces. 50 | 51 | ## Lua 52 | * Config directory now included in Lua's `package.path` (https://github.com/way-cooler/way-cooler/pull/352) 53 | * Defaulting to the pre-compiled configuration fallback now properly cleans up state from the previous Lua instance. (https://github.com/way-cooler/way-cooler/issues/382) 54 | 55 | ## Tiling 56 | * Tabbed/Stacked tiling made much more like i3. Special thanks to @Arnaz87 for putting the work in to fix the rendering! (https://github.com/way-cooler/way-cooler/pull/439, https://github.com/way-cooler/way-cooler/pull/450) 57 | * Floating containers no longer show in Tabbed/Stacked list. (https://github.com/way-cooler/way-cooler/issues/440) 58 | * Title bar now displayed correctly even when its value is different from the other border sizes. (https://github.com/way-cooler/way-cooler/issues/410) 59 | * Floating containers are now properly pulled forward again when focused. (https://github.com/way-cooler/way-cooler/pull/432, https://github.com/way-cooler/way-cooler/issues/412) 60 | * When a floating view is the focused view it is now properly displayed on top of other floating views (https://github.com/way-cooler/way-cooler/issues/415, https://github.com/way-cooler/way-cooler/pull/413) 61 | 62 | ## Soundness fixes 63 | * Spawning programs no longer sometimes triggers a segfault. (https://github.com/way-cooler/way-cooler/issues/430) 64 | -------------------------------------------------------------------------------- /changelog/v0.8.0.md: -------------------------------------------------------------------------------- 1 | # Alpha Channel on borders 2 | # Configuration 3 | * Changed the format for colours in the `init.lua` file. No longer accepts a number and instead should be a string. **This is backwards incompatible**. 4 | 5 | Thanks to @timo-schmid for providing the patch for this release. 6 | -------------------------------------------------------------------------------- /makedocs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | man() { 4 | file=$1 5 | dir=$2 6 | 7 | lang="$(dirname $file)" 8 | basename=$(basename $file .md) 9 | section=${basename##*.} 10 | 11 | mkdir -p "$dir/$lang/man$section" 12 | echo "$file > $dir/$lang/man$section/$basename.gz" 13 | asciidoctor -d manpage -b manpage -o - "$file" | gzip > "$dir/$lang/man$section/$basename.gz" 14 | } 15 | 16 | html() { 17 | file=$1 18 | dir=$2 19 | basename=$(basename $file .md) 20 | 21 | lang="$(dirname $file)" 22 | out="$dir/$lang/$basename.html" 23 | 24 | mkdir -p "$dir/$lang" 25 | echo "$file > $out" 26 | asciidoctor -o - "$file" > "$out" 27 | } 28 | 29 | # defaults, do all 30 | do_man=true 31 | do_html=true 32 | 33 | for i in "$@"; do 34 | case $i in 35 | -m|--manualonly) 36 | do_html=false 37 | ;; 38 | -h|--htmlonly) 39 | do_man=false 40 | ;; 41 | *) 42 | if [ -z "$input" ]; then 43 | input=$i 44 | else 45 | output="$PWD/$i" 46 | fi 47 | ;; 48 | esac 49 | done 50 | 51 | which "asciidoctor" > /dev/null 52 | if [ "$?" -eq 1 ]; then 53 | echo "The asciidoctor executable is required to build man files" 54 | exit 1 55 | fi 56 | 57 | if [ -z "$output" -o -z "$input" ]; then 58 | echo "Way-cooler's documentation maker\nUsage: ./makedocs.sh [-m|--manualonly] [-h|--htmlonly] input output" 59 | exit 1 60 | fi 61 | 62 | cd $input 63 | for file in */*.md *.md; do 64 | [ "$do_html" = true ] && html $file $output 65 | [ "$do_man" = true ] && man $file $output 66 | done 67 | cd - > /dev/null -------------------------------------------------------------------------------- /manpages/awesome.1.md: -------------------------------------------------------------------------------- 1 | awesome(1) 2 | ========== 3 | 4 | NAME 5 | ---- 6 | 7 | awesome - a clone of AwesomeWM as a Wayland client. 8 | 9 | SYNOPSIS 10 | -------- 11 | 12 | *awesome* [*--version*] 13 | 14 | DESCRIPTION 15 | ----------- 16 | 17 | *way-cooler* is a tiling window manager for Wayland based on AwesomeWM. 18 | 19 | *awesome* controls *way-cooler*'s behavior by exposing Lua and DBUS APIs. At startup, it reads an RC file and configure *way-cooler* accordingly. 20 | 21 | The Lua API is (will be) the same as AwesomeWM, so any documentation should be interchangeable. The configuration options are discussed more in length in the *awesomerc* man page. 22 | 23 | OPTIONS 24 | ------- 25 | *--version*: 26 | Print version information to standard output, then exit. 27 | 28 | CUSTOMIZATION 29 | ------------- 30 | Create a rc file in '$HOME/.config/way-cooler/rc.lua'. It will be read on launch and will perform all the specified customization. 31 | 32 | SEE ALSO 33 | -------- 34 | *way-cooler*(1) *awesomerc*(5) 35 | 36 | BUGS 37 | ---- 38 | Please feel free to report them to https://github.com/way-cooler/way-cooler 39 | 40 | AUTHORS 41 | ------- 42 | Preston Carpenter (a.k.a. Timidger) and others. 43 | 44 | WWW 45 | --- 46 | https://way-cooler.org 47 | -------------------------------------------------------------------------------- /manpages/es/awesome.1.md: -------------------------------------------------------------------------------- 1 | awesome(1) 2 | ========== 3 | NOMBRE 4 | ---- 5 | awesome - un clon de AwesomeWM como cliente Wayland 6 | 7 | SINOPSIS 8 | -------- 9 | *awesome* [*--version*] 10 | 11 | DESCRIPCION 12 | ----------- 13 | 14 | *way-cooler* es un gestor de ventanas tipo mosaico para Wayland basado en AwesomeWM. 15 | 16 | *awesome* controla el comportamiento de *way-cooler* exponiendo APIs de Lua y DBUS. Al iniciar, lee un archivo RC y configura *way-cooler* consecuentemente. 17 | 18 | La API de Lua es (y sera) igual a la de AwesomeWM, asi que cualquier documentacion deberia ser intercambiable. Las opciones de configuracion son explicadas con mayor detalles en la pagina de manual de *awesomerc*. 19 | 20 | OPCIONES 21 | ------- 22 | *--version*: 23 | Imprime informacion de version a la salida estandar, y termina el programa. 24 | 25 | PERSONALIZACION 26 | ------------- 27 | Haga un archivo rc en '$HOME/.config/way-cooler/rc.lua'. Sera leido al iniciar y realizara todas las personalizaciones especificadas. 28 | 29 | VEASE TAMBIEN 30 | -------- 31 | *way-cooler*(1) *awesomerc*(5) 32 | 33 | BUGS 34 | ---- 35 | 36 | Por favor, sientase libre de reportarlos en https://github.com/way-cooler/way-cooler 37 | 38 | AUTORES 39 | ------- 40 | Preston Carpenter (a.k.a. Timidger) y otros. 41 | 42 | WWW 43 | --- 44 | https://way-cooler.org 45 | -------------------------------------------------------------------------------- /manpages/fr/awesome.1.md: -------------------------------------------------------------------------------- 1 | awesome(1) 2 | ========== 3 | 4 | NOM 5 | ---- 6 | 7 | awesome - client Wayland clone de AwesomeWM. 8 | 9 | SYNOPSIS 10 | -------- 11 | 12 | *awesome* [*--version*] 13 | 14 | DESCRIPTION 15 | ----------- 16 | 17 | *way-cooler* est un gestionnaire de fenêtres par tuile pour Wayland basé sur AwesomeWM. 18 | 19 | *awesome* contrôle *way-cooler* en exposant des APIs Lua et DBUS. Lors du lancement, il exécute son fichier de configuration et configure du même coup *way-cooler*. 20 | 21 | L'API Lua est (presque) celui d'AwesomeWM. Ainsi, la documentation devrait être interchangeable. Pour une discussion plus complète sur l'API Lua, veuillez consulter la documentation *awesomerc*. 22 | 23 | OPTIONS 24 | ------- 25 | *--version*: 26 | Affiche la version dans la sortie standard, puis termine l'exécution. 27 | 28 | PERSONNALISATION 29 | ---------------- 30 | Créer le fichier de configuration '$HOME/.config/way-cooler/rc.lua'. Lors du lancement, il s'exécutera et aura accès à l'API Lua pour configurer adéquatement way-cooler. 31 | 32 | À VOIR 33 | ------ 34 | *way-cooler*(1) *awesomerc*(5) 35 | 36 | BUGS 37 | ---- 38 | Tous les rapports d'erreur sont les bienvenues. Voir https://github.com/way-cooler/way-cooler 39 | 40 | AUTEURS 41 | ------- 42 | Preston Carpenter (a.k.a. Timidger) et autres. 43 | 44 | WWW 45 | --- 46 | https://way-cooler.org -------------------------------------------------------------------------------- /manpages/fr/way-cooler.1.md: -------------------------------------------------------------------------------- 1 | way-cooler(1) 2 | ============= 3 | 4 | NOM 5 | ---- 6 | 7 | way-cooler - Compositeur Wayland hautement configurable basé sur AwesomeWM 8 | 9 | SYNOPSIS 10 | -------- 11 | 12 | *way-cooler* [*--version*] 13 | 14 | DESCRIPTION 15 | ----------- 16 | 17 | *way-cooler* est un gestionnaire de fenêtres par tuile pour WayLand basé sur AwesomeWM. Les fenêtres sont disposées selon des dispositions dynamiques par l'entremise de l'exécutable *awesome*. 18 | 19 | L'utilisateur a accès à plusieurs vues de bureau grâce à des **tags**, auxquels sont assignées des fenêtres et qui dictent (en général) leur disposition. 20 | 21 | OPTIONS 22 | ------- 23 | *--version*: 24 | Affiche la version dans la sortie standard, puis termine l'exécution. 25 | 26 | PERSONNALISATION 27 | ---------------- 28 | *way-cooler* peut (et devrait) être personnalisé en lançant l'exécutable *awesome* (attention, pas l'exécutable de AwesomeWM) avec une configuration située à '.config/way-cooler/rc.lua'. 29 | 30 | À VOIR 31 | ------ 32 | *awesome*(1) *awesomerc*(5) 33 | 34 | BUGS 35 | ---- 36 | Tous les rapports d'erreur sont les bienvenues. Voir https://github.com/way-cooler/way-cooler 37 | 38 | AUTEURS 39 | ------- 40 | Preston Carpenter (a.k.a. Timidger) et autres. 41 | 42 | WWW 43 | --- 44 | https://way-cooler.org -------------------------------------------------------------------------------- /manpages/way-cooler.1.md: -------------------------------------------------------------------------------- 1 | way-cooler(1) 2 | ============= 3 | 4 | NAME 5 | ---- 6 | 7 | way-cooler - AwesomeWM-based customizable Wayland compositor (window manager) 8 | 9 | SYNOPSIS 10 | -------- 11 | 12 | *way-cooler* [*--version*] 13 | 14 | DESCRIPTION 15 | ----------- 16 | 17 | *way-cooler* is a tiling window manager for WayLand based on AwesomeWM. It handles windows based on dynamic layouts with the help of the *awesome* utilitary. 18 | 19 | It provides the user with multiple desktop views by the means of tags. Each window is assigned one or more tags, which dictates (in most cases) the layout it will use. 20 | 21 | OPTIONS 22 | ------- 23 | *--version*: 24 | Print version information to standard output, then exit. 25 | 26 | CUSTOMIZATION 27 | ------------- 28 | *way-cooler* can (and should) be customized by launching the *awesome* command (not the original AwesomeWM one) with a custom '.config/way-cooler/rc.lua' file. 29 | 30 | SEE ALSO 31 | -------- 32 | *awesome*(1) *awesomerc*(5) 33 | 34 | BUGS 35 | ---- 36 | Please feel free to report them to https://github.com/way-cooler/way-cooler 37 | 38 | AUTHORS 39 | ------- 40 | Preston Carpenter (a.k.a. Timidger) and others. 41 | 42 | WWW 43 | --- 44 | https://way-cooler.org -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'way-cooler', 3 | 'c', 4 | version: '0.8.0', 5 | license: 'MIT', 6 | meson_version: '>=0.48.0', 7 | default_options: [ 8 | 'c_std=c11', 9 | 'warning_level=2', 10 | 'werror=true', 11 | ] 12 | ) 13 | 14 | add_project_arguments( 15 | [ 16 | '-DWLR_USE_UNSTABLE', 17 | 18 | '-Wno-unused-parameter', 19 | '-Wno-unused-result', 20 | '-Wundef', 21 | '-Wvla', 22 | ], 23 | language: 'c' 24 | ) 25 | 26 | git = find_program('git', native: true, required: false) 27 | 28 | if git.found() 29 | git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']) 30 | git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']) 31 | if git_commit_hash.returncode() == 0 and git_branch.returncode() == 0 32 | version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash.stdout().strip(), git_branch.stdout().strip()) 33 | endif 34 | endif 35 | add_project_arguments('-DWAY_COOLER_VERSION=@0@'.format(version), language: 'c') 36 | 37 | subdir('protocols') 38 | subdir('way-cooler') 39 | -------------------------------------------------------------------------------- /protocols/meson.build: -------------------------------------------------------------------------------- 1 | wayland_protos = dependency('wayland-protocols', version: '>=1.14') 2 | wayland_server = dependency('wayland-server') 3 | 4 | wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') 5 | 6 | wayland_scanner = find_program('wayland-scanner') 7 | 8 | # should check wayland_scanner's version, but it is hard to get 9 | if wayland_server.version().version_compare('>=1.14.91') 10 | code_type = 'private-code' 11 | else 12 | code_type = 'code' 13 | endif 14 | 15 | wayland_scanner_code = generator( 16 | wayland_scanner, 17 | output: '@BASENAME@-protocol.c', 18 | arguments: [code_type, '@INPUT@', '@OUTPUT@'], 19 | ) 20 | 21 | wayland_scanner_server = generator( 22 | wayland_scanner, 23 | output: '@BASENAME@-protocol.h', 24 | arguments: ['server-header', '@INPUT@', '@OUTPUT@'], 25 | ) 26 | 27 | server_protocols = [ 28 | [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], 29 | ['wlr-layer-shell-unstable-v1.xml'], 30 | ['way-cooler-mousegrabber-unstable-v1.xml'], 31 | ['way-cooler-keybindings-unstable-v1.xml'], 32 | ] 33 | 34 | server_protos_src = [] 35 | server_protos_headers = [] 36 | 37 | foreach p : server_protocols 38 | xml = join_paths(p) 39 | server_protos_src += wayland_scanner_code.process(xml) 40 | server_protos_headers += wayland_scanner_server.process(xml) 41 | endforeach 42 | 43 | lib_server_protos = static_library( 44 | 'server_protos', 45 | server_protos_src + server_protos_headers 46 | ) # for the include directory 47 | 48 | server_protos = declare_dependency( 49 | link_with: lib_server_protos, 50 | sources: server_protos_headers, 51 | ) 52 | -------------------------------------------------------------------------------- /protocols/way-cooler-keybindings-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2019 Preston Carpenter 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 | This interface allows clients to register a list of keys that will never 30 | be sent to other clients. 31 | 32 | When these keys are part of keyboard events the relevant event will 33 | instead be sent to the client that bounds to this interface. 34 | 35 | This is intended for general keybindings, not for lock screens or other 36 | clients that already have surfaces to accept input for. 37 | 38 | 39 | 40 | 41 | Describes the physical state of a key that produced the key event. 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | A key was pressed or released. 60 | The time argument is a timestamp with millisecond 61 | granularity, with an undefined base. 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /protocols/way-cooler-mousegrabber-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2019 Preston Carpenter 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 | This interface allows clients to manipulate the compositor's cursor 30 | position on the screen. 31 | 32 | The intended use case of this is to re-implement AwesomeWM's original 33 | "keygrabber" Lua interface. 34 | 35 | 36 | 37 | 38 | Attempts to grab the mouse from the compositor. 39 | 40 | Only one client can grab the mouse at a time. Attempts to grab while 41 | another client is already grabbing is a protocol error. 42 | 43 | 44 | 45 | 46 | 47 | 48 | Relinquishes control of the mouse back to the compositor. 49 | 50 | It is a protocol error to call this when the client has not grabbed the mouse. 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /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 | You may pass NULL for output to allow the compositor to decide which 51 | output to use. Generally this will be the one that the user most 52 | recently interacted with. 53 | 54 | Clients can specify a namespace that defines the purpose of the layer 55 | surface. 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | These values indicate which layers a surface can be rendered in. They 73 | are ordered by z depth, bottom-most first. Traditional shell surfaces 74 | will typically be rendered between the bottom and top layers. 75 | Fullscreen shell surfaces are typically rendered at the top layer. 76 | Multiple surfaces can share a single layer, and ordering within a 77 | single layer is undefined. 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | An interface that may be implemented by a wl_surface, for surfaces that 90 | are designed to be rendered as a layer of a stacked desktop-like 91 | environment. 92 | 93 | Layer surface state (layer, size, anchor, exclusive zone, 94 | margin, interactivity) is double-buffered, and will be applied at the 95 | time wl_surface.commit of the corresponding wl_surface is called. 96 | 97 | 98 | 99 | 100 | Sets the size of the surface in surface-local coordinates. The 101 | compositor will display the surface centered with respect to its 102 | anchors. 103 | 104 | If you pass 0 for either value, the compositor will assign it and 105 | inform you of the assignment in the configure event. You must set your 106 | anchor to opposite edges in the dimensions you omit; not doing so is a 107 | protocol error. Both values are 0 by default. 108 | 109 | Size is double-buffered, see wl_surface.commit. 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | Requests that the compositor anchor the surface to the specified edges 118 | and corners. If two orthogonal edges are specified (e.g. 'top' and 119 | 'left'), then the anchor point will be the intersection of the edges 120 | (e.g. the top left corner of the output); otherwise the anchor point 121 | will be centered on that edge, or in the center if none is specified. 122 | 123 | Anchor is double-buffered, see wl_surface.commit. 124 | 125 | 126 | 127 | 128 | 129 | 130 | Requests that the compositor avoids occluding an area with other 131 | surfaces. The compositor's use of this information is 132 | implementation-dependent - do not assume that this region will not 133 | actually be occluded. 134 | 135 | A positive value is only meaningful if the surface is anchored to one 136 | edge or an edge and both perpendicular edges. If the surface is not 137 | anchored, anchored to only two perpendicular edges (a corner), anchored 138 | to only two parallel edges or anchored to all edges, a positive value 139 | will be treated the same as zero. 140 | 141 | A positive zone is the distance from the edge in surface-local 142 | coordinates to consider exclusive. 143 | 144 | Surfaces that do not wish to have an exclusive zone may instead specify 145 | how they should interact with surfaces that do. If set to zero, the 146 | surface indicates that it would like to be moved to avoid occluding 147 | surfaces with a positive exclusive zone. If set to -1, the surface 148 | indicates that it would not like to be moved to accommodate for other 149 | surfaces, and the compositor should extend it all the way to the edges 150 | it is anchored to. 151 | 152 | For example, a panel might set its exclusive zone to 10, so that 153 | maximized shell surfaces are not shown on top of it. A notification 154 | might set its exclusive zone to 0, so that it is moved to avoid 155 | occluding the panel, but shell surfaces are shown underneath it. A 156 | wallpaper or lock screen might set their exclusive zone to -1, so that 157 | they stretch below or over the panel. 158 | 159 | The default value is 0. 160 | 161 | Exclusive zone is double-buffered, see wl_surface.commit. 162 | 163 | 164 | 165 | 166 | 167 | 168 | Requests that the surface be placed some distance away from the anchor 169 | point on the output, in surface-local coordinates. Setting this value 170 | for edges you are not anchored to has no effect. 171 | 172 | The exclusive zone includes the margin. 173 | 174 | Margin is double-buffered, see wl_surface.commit. 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | Set to 1 to request that the seat send keyboard events to this layer 185 | surface. For layers below the shell surface layer, the seat will use 186 | normal focus semantics. For layers above the shell surface layers, the 187 | seat will always give exclusive keyboard focus to the top-most layer 188 | which has keyboard interactivity set to true. 189 | 190 | Layer surfaces receive pointer, touch, and tablet events normally. If 191 | you do not want to receive them, set the input region on your surface 192 | to an empty region. 193 | 194 | Events is double-buffered, see wl_surface.commit. 195 | 196 | 197 | 198 | 199 | 200 | 201 | This assigns an xdg_popup's parent to this layer_surface. This popup 202 | should have been created via xdg_surface::get_popup with the parent set 203 | to NULL, and this request must be invoked before committing the popup's 204 | initial state. 205 | 206 | See the documentation of xdg_popup for more details about what an 207 | xdg_popup is and how it is used. 208 | 209 | 210 | 211 | 212 | 213 | 214 | When a configure event is received, if a client commits the 215 | surface in response to the configure event, then the client 216 | must make an ack_configure request sometime before the commit 217 | request, passing along the serial of the configure event. 218 | 219 | If the client receives multiple configure events before it 220 | can respond to one, it only has to ack the last configure event. 221 | 222 | A client is not required to commit immediately after sending 223 | an ack_configure request - it may even ack_configure several times 224 | before its next surface commit. 225 | 226 | A client may send multiple ack_configure requests before committing, but 227 | only the last request sent before a commit indicates which configure 228 | event the client really is responding to. 229 | 230 | 231 | 232 | 233 | 234 | 235 | This request destroys the layer surface. 236 | 237 | 238 | 239 | 240 | 241 | The configure event asks the client to resize its surface. 242 | 243 | Clients should arrange their surface for the new states, and then send 244 | an ack_configure request with the serial sent in this configure event at 245 | some point before committing the new surface. 246 | 247 | The client is free to dismiss all but the last configure event it 248 | received. 249 | 250 | The width and height arguments specify the size of the window in 251 | surface-local coordinates. 252 | 253 | The size is a hint, in the sense that the client is free to ignore it if 254 | it doesn't resize, pick a smaller size (to satisfy aspect ratio or 255 | resize in steps of NxM pixels). If the client picks a smaller size and 256 | is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the 257 | surface will be centered on this axis. 258 | 259 | If the width or height arguments are zero, it means the client should 260 | decide its own window dimension. 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | The closed event is sent by the compositor when the surface will no 270 | longer be shown. The output may have been destroyed or the user may 271 | have asked for it to be removed. Further changes to the surface will be 272 | ignored. The client should destroy the resource after receiving this 273 | event, and create a new surface if they so choose. 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | Change the layer that the surface is rendered on. 295 | 296 | Layer is double-buffered, see wl_surface.commit. 297 | 298 | 299 | 300 | 301 | 302 | -------------------------------------------------------------------------------- /run-clang-format.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A wrapper script around clang-format, suitable for linting multiple files 3 | and to use for continuous integration. 4 | 5 | Taken from https://github.com/Sarcasm/run-clang-format 6 | 7 | This is an alternative API for the clang-format command line. 8 | It runs over multiple files and directories in parallel. 9 | A diff output is produced and a sensible exit code is returned. 10 | 11 | """ 12 | 13 | from __future__ import print_function, unicode_literals 14 | 15 | import argparse 16 | import codecs 17 | import difflib 18 | import fnmatch 19 | import io 20 | import multiprocessing 21 | import os 22 | import signal 23 | import subprocess 24 | import sys 25 | import traceback 26 | 27 | from functools import partial 28 | 29 | DEFAULT_EXTENSIONS = 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx' 30 | 31 | 32 | class ExitStatus: 33 | SUCCESS = 0 34 | DIFF = 1 35 | TROUBLE = 2 36 | 37 | 38 | def list_files(files, recursive=False, extensions=None, exclude=None): 39 | if extensions is None: 40 | extensions = [] 41 | if exclude is None: 42 | exclude = [] 43 | 44 | out = [] 45 | for file in files: 46 | if recursive and os.path.isdir(file): 47 | for dirpath, dnames, fnames in os.walk(file): 48 | fpaths = [os.path.join(dirpath, fname) for fname in fnames] 49 | for pattern in exclude: 50 | # os.walk() supports trimming down the dnames list 51 | # by modifying it in-place, 52 | # to avoid unnecessary directory listings. 53 | dnames[:] = [ 54 | x for x in dnames 55 | if 56 | not fnmatch.fnmatch(os.path.join(dirpath, x), pattern) 57 | ] 58 | fpaths = [ 59 | x for x in fpaths if not fnmatch.fnmatch(x, pattern) 60 | ] 61 | for f in fpaths: 62 | ext = os.path.splitext(f)[1][1:] 63 | if ext in extensions: 64 | out.append(f) 65 | else: 66 | out.append(file) 67 | return out 68 | 69 | 70 | def make_diff(file, original, reformatted): 71 | return list( 72 | difflib.unified_diff( 73 | original, 74 | reformatted, 75 | fromfile='{}\t(original)'.format(file), 76 | tofile='{}\t(reformatted)'.format(file), 77 | n=3)) 78 | 79 | 80 | class DiffError(Exception): 81 | def __init__(self, message, errs=None): 82 | super(DiffError, self).__init__(message) 83 | self.errs = errs or [] 84 | 85 | 86 | class UnexpectedError(Exception): 87 | def __init__(self, message, exc=None): 88 | super(UnexpectedError, self).__init__(message) 89 | self.formatted_traceback = traceback.format_exc() 90 | self.exc = exc 91 | 92 | 93 | def run_clang_format_diff_wrapper(args, file): 94 | try: 95 | ret = run_clang_format_diff(args, file) 96 | return ret 97 | except DiffError: 98 | raise 99 | except Exception as e: 100 | raise UnexpectedError('{}: {}: {}'.format(file, e.__class__.__name__, 101 | e), e) 102 | 103 | 104 | def run_clang_format_diff(args, file): 105 | try: 106 | with io.open(file, 'r', encoding='utf-8') as f: 107 | original = f.readlines() 108 | except IOError as exc: 109 | raise DiffError(str(exc)) 110 | invocation = [args.clang_format_executable, file] 111 | 112 | # Use of utf-8 to decode the process output. 113 | # 114 | # Hopefully, this is the correct thing to do. 115 | # 116 | # It's done due to the following assumptions (which may be incorrect): 117 | # - clang-format will returns the bytes read from the files as-is, 118 | # without conversion, and it is already assumed that the files use utf-8. 119 | # - if the diagnostics were internationalized, they would use utf-8: 120 | # > Adding Translations to Clang 121 | # > 122 | # > Not possible yet! 123 | # > Diagnostic strings should be written in UTF-8, 124 | # > the client can translate to the relevant code page if needed. 125 | # > Each translation completely replaces the format string 126 | # > for the diagnostic. 127 | # > -- http://clang.llvm.org/docs/InternalsManual.html#internals-diag-translation 128 | # 129 | # It's not pretty, due to Python 2 & 3 compatibility. 130 | encoding_py3 = {} 131 | if sys.version_info[0] >= 3: 132 | encoding_py3['encoding'] = 'utf-8' 133 | 134 | try: 135 | proc = subprocess.Popen( 136 | invocation, 137 | stdout=subprocess.PIPE, 138 | stderr=subprocess.PIPE, 139 | universal_newlines=True, 140 | **encoding_py3) 141 | except OSError as exc: 142 | raise DiffError(str(exc)) 143 | proc_stdout = proc.stdout 144 | proc_stderr = proc.stderr 145 | if sys.version_info[0] < 3: 146 | # make the pipes compatible with Python 3, 147 | # reading lines should output unicode 148 | encoding = 'utf-8' 149 | proc_stdout = codecs.getreader(encoding)(proc_stdout) 150 | proc_stderr = codecs.getreader(encoding)(proc_stderr) 151 | # hopefully the stderr pipe won't get full and block the process 152 | outs = list(proc_stdout.readlines()) 153 | errs = list(proc_stderr.readlines()) 154 | proc.wait() 155 | if proc.returncode: 156 | raise DiffError("clang-format exited with status {}: '{}'".format( 157 | proc.returncode, file), errs) 158 | return make_diff(file, original, outs), errs 159 | 160 | 161 | def bold_red(s): 162 | return '\x1b[1m\x1b[31m' + s + '\x1b[0m' 163 | 164 | 165 | def colorize(diff_lines): 166 | def bold(s): 167 | return '\x1b[1m' + s + '\x1b[0m' 168 | 169 | def cyan(s): 170 | return '\x1b[36m' + s + '\x1b[0m' 171 | 172 | def green(s): 173 | return '\x1b[32m' + s + '\x1b[0m' 174 | 175 | def red(s): 176 | return '\x1b[31m' + s + '\x1b[0m' 177 | 178 | for line in diff_lines: 179 | if line[:4] in ['--- ', '+++ ']: 180 | yield bold(line) 181 | elif line.startswith('@@ '): 182 | yield cyan(line) 183 | elif line.startswith('+'): 184 | yield green(line) 185 | elif line.startswith('-'): 186 | yield red(line) 187 | else: 188 | yield line 189 | 190 | 191 | def print_diff(diff_lines, use_color): 192 | if use_color: 193 | diff_lines = colorize(diff_lines) 194 | if sys.version_info[0] < 3: 195 | sys.stdout.writelines((l.encode('utf-8') for l in diff_lines)) 196 | else: 197 | sys.stdout.writelines(diff_lines) 198 | 199 | 200 | def print_trouble(prog, message, use_colors): 201 | error_text = 'error:' 202 | if use_colors: 203 | error_text = bold_red(error_text) 204 | print("{}: {} {}".format(prog, error_text, message), file=sys.stderr) 205 | 206 | 207 | def main(): 208 | parser = argparse.ArgumentParser(description=__doc__) 209 | parser.add_argument( 210 | '--clang-format-executable', 211 | metavar='EXECUTABLE', 212 | help='path to the clang-format executable', 213 | default='clang-format') 214 | parser.add_argument( 215 | '--extensions', 216 | help='comma separated list of file extensions (default: {})'.format( 217 | DEFAULT_EXTENSIONS), 218 | default=DEFAULT_EXTENSIONS) 219 | parser.add_argument( 220 | '-r', 221 | '--recursive', 222 | action='store_true', 223 | help='run recursively over directories') 224 | parser.add_argument('files', metavar='file', nargs='+') 225 | parser.add_argument( 226 | '-q', 227 | '--quiet', 228 | action='store_true') 229 | parser.add_argument( 230 | '-j', 231 | metavar='N', 232 | type=int, 233 | default=0, 234 | help='run N clang-format jobs in parallel' 235 | ' (default number of cpus + 1)') 236 | parser.add_argument( 237 | '--color', 238 | default='auto', 239 | choices=['auto', 'always', 'never'], 240 | help='show colored diff (default: auto)') 241 | parser.add_argument( 242 | '-e', 243 | '--exclude', 244 | metavar='PATTERN', 245 | action='append', 246 | default=[], 247 | help='exclude paths matching the given glob-like pattern(s)' 248 | ' from recursive search') 249 | 250 | args = parser.parse_args() 251 | 252 | # use default signal handling, like diff return SIGINT value on ^C 253 | # https://bugs.python.org/issue14229#msg156446 254 | signal.signal(signal.SIGINT, signal.SIG_DFL) 255 | try: 256 | signal.SIGPIPE 257 | except AttributeError: 258 | # compatibility, SIGPIPE does not exist on Windows 259 | pass 260 | else: 261 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) 262 | 263 | colored_stdout = False 264 | colored_stderr = False 265 | if args.color == 'always': 266 | colored_stdout = True 267 | colored_stderr = True 268 | elif args.color == 'auto': 269 | colored_stdout = sys.stdout.isatty() 270 | colored_stderr = sys.stderr.isatty() 271 | 272 | retcode = ExitStatus.SUCCESS 273 | files = list_files( 274 | args.files, 275 | recursive=args.recursive, 276 | exclude=args.exclude, 277 | extensions=args.extensions.split(',')) 278 | 279 | if not files: 280 | return 281 | 282 | njobs = args.j 283 | if njobs == 0: 284 | njobs = multiprocessing.cpu_count() + 1 285 | njobs = min(len(files), njobs) 286 | 287 | if njobs == 1: 288 | # execute directly instead of in a pool, 289 | # less overhead, simpler stacktraces 290 | it = (run_clang_format_diff_wrapper(args, file) for file in files) 291 | pool = None 292 | else: 293 | pool = multiprocessing.Pool(njobs) 294 | it = pool.imap_unordered( 295 | partial(run_clang_format_diff_wrapper, args), files) 296 | while True: 297 | try: 298 | outs, errs = next(it) 299 | except StopIteration: 300 | break 301 | except DiffError as e: 302 | print_trouble(parser.prog, str(e), use_colors=colored_stderr) 303 | retcode = ExitStatus.TROUBLE 304 | sys.stderr.writelines(e.errs) 305 | except UnexpectedError as e: 306 | print_trouble(parser.prog, str(e), use_colors=colored_stderr) 307 | sys.stderr.write(e.formatted_traceback) 308 | retcode = ExitStatus.TROUBLE 309 | # stop at the first unexpected error, 310 | # something could be very wrong, 311 | # don't process all files unnecessarily 312 | if pool: 313 | pool.terminate() 314 | break 315 | else: 316 | sys.stderr.writelines(errs) 317 | if outs == []: 318 | continue 319 | if not args.quiet: 320 | print_diff(outs, use_color=colored_stdout) 321 | if retcode == ExitStatus.SUCCESS: 322 | retcode = ExitStatus.DIFF 323 | return retcode 324 | 325 | 326 | if __name__ == '__main__': 327 | sys.exit(main()) 328 | -------------------------------------------------------------------------------- /way-cooler/cursor.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | #include "cursor.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "mousegrabber.h" 11 | #include "output.h" 12 | #include "seat.h" 13 | #include "server.h" 14 | #include "view.h" 15 | 16 | #define BUTTON_LEFT 272 17 | #define BUTTON_RIGHT 273 18 | #define BUTTON_MIDDLE 274 19 | #define SCROLL_UP -1 20 | #define SCROLL_DOWN 1 21 | 22 | static void wc_process_motion(struct wc_server *server, uint32_t time) { 23 | struct wc_seat *seat = server->seat; 24 | struct wc_cursor *cursor = server->cursor; 25 | struct wlr_cursor *wlr_cursor = server->cursor->wlr_cursor; 26 | struct wc_view *view = cursor->grabbed.view; 27 | 28 | switch (cursor->cursor_mode) { 29 | case WC_CURSOR_MOVE: { 30 | wc_view_damage_whole(view); 31 | 32 | view->geo.x = wlr_cursor->x - cursor->grabbed.original_x; 33 | view->geo.y = wlr_cursor->y - cursor->grabbed.original_y; 34 | 35 | wc_view_damage_whole(view); 36 | break; 37 | } 38 | case WC_CURSOR_RESIZE: { 39 | int dx = wlr_cursor->x - cursor->grabbed.original_x; 40 | int dy = wlr_cursor->y - cursor->grabbed.original_y; 41 | struct wlr_box new_geo = { 42 | .x = view->geo.x, 43 | .y = view->geo.y, 44 | .width = cursor->grabbed.original_view_geo.width, 45 | .height = cursor->grabbed.original_view_geo.height, 46 | }; 47 | 48 | if (cursor->grabbed.resize_edges & WLR_EDGE_TOP) { 49 | new_geo.y = cursor->grabbed.original_view_geo.y + dy; 50 | new_geo.height -= dy; 51 | if (new_geo.height < 1) { 52 | new_geo.y += new_geo.height; 53 | } 54 | } else if (cursor->grabbed.resize_edges & WLR_EDGE_BOTTOM) { 55 | new_geo.height += dy; 56 | } 57 | 58 | if (cursor->grabbed.resize_edges & WLR_EDGE_LEFT) { 59 | new_geo.x = cursor->grabbed.original_view_geo.x + dx; 60 | new_geo.width -= dx; 61 | if (new_geo.width < 1) { 62 | new_geo.x += new_geo.width; 63 | } 64 | } else if (cursor->grabbed.resize_edges & WLR_EDGE_RIGHT) { 65 | new_geo.width += dx; 66 | } 67 | 68 | wc_view_update_geometry(view, new_geo); 69 | 70 | break; 71 | } 72 | case WC_CURSOR_PASSTHROUGH: { 73 | double sx, sy; 74 | struct wlr_surface *surface = NULL; 75 | struct wc_view *view = wc_view_at( 76 | server, wlr_cursor->x, wlr_cursor->y, &sx, &sy, &surface); 77 | if (!view && cursor->use_client_image) { 78 | wc_cursor_set_client_cursor(cursor, NULL); 79 | } 80 | 81 | wc_seat_update_surface_focus(seat, surface, sx, sy, time); 82 | break; 83 | } 84 | } 85 | 86 | struct wlr_output *active_output = wlr_output_layout_output_at( 87 | server->output_layout, wlr_cursor->x, wlr_cursor->y); 88 | if (active_output != server->active_output->wlr_output) { 89 | struct wc_output *output_; 90 | wl_list_for_each(output_, &server->outputs, link) { 91 | if (output_->wlr_output == active_output) { 92 | server->active_output = output_; 93 | break; 94 | } 95 | } 96 | } 97 | 98 | wc_mousegrabber_notify_mouse_moved( 99 | server->mousegrabber, wlr_cursor->x, wlr_cursor->y); 100 | } 101 | 102 | static void wc_cursor_motion(struct wl_listener *listener, void *data) { 103 | struct wc_cursor *cursor = wl_container_of(listener, cursor, motion); 104 | struct wlr_event_pointer_motion *event = data; 105 | 106 | wlr_cursor_move( 107 | cursor->wlr_cursor, event->device, event->delta_x, event->delta_y); 108 | wc_process_motion(cursor->server, event->time_msec); 109 | } 110 | 111 | static void wc_cursor_motion_absolute( 112 | struct wl_listener *listener, void *data) { 113 | struct wc_cursor *cursor = 114 | wl_container_of(listener, cursor, motion_absolute); 115 | struct wlr_event_pointer_motion_absolute *event = data; 116 | 117 | wlr_cursor_warp_absolute( 118 | cursor->wlr_cursor, event->device, event->x, event->y); 119 | wc_process_motion(cursor->server, event->time_msec); 120 | } 121 | 122 | static void wc_cursor_button(struct wl_listener *listener, void *data) { 123 | struct wc_cursor *cursor = wl_container_of(listener, cursor, button); 124 | struct wc_server *server = cursor->server; 125 | struct wlr_event_pointer_button *event = data; 126 | 127 | switch (event->button) { 128 | case BUTTON_LEFT: 129 | if (event->state) { 130 | server->mousegrabber->button |= 1 << 0; 131 | } else { 132 | server->mousegrabber->button &= ~(1 << 0); 133 | } 134 | break; 135 | case BUTTON_MIDDLE: 136 | if (event->state) { 137 | server->mousegrabber->button |= 1 << 1; 138 | } else { 139 | server->mousegrabber->button &= ~(1 << 1); 140 | } 141 | break; 142 | case BUTTON_RIGHT: 143 | if (event->state) { 144 | server->mousegrabber->button |= 1 << 2; 145 | } else { 146 | server->mousegrabber->button &= ~(1 << 2); 147 | } 148 | break; 149 | } 150 | 151 | wc_mousegrabber_notify_mouse_button( 152 | server->mousegrabber, cursor->wlr_cursor->x, cursor->wlr_cursor->y); 153 | 154 | if (server->mouse_grab) { 155 | return; 156 | } 157 | 158 | wlr_seat_pointer_notify_button( 159 | server->seat->seat, event->time_msec, event->button, event->state); 160 | 161 | double sx, sy; 162 | struct wlr_surface *surface = NULL; 163 | struct wc_view *view = wc_view_at(server, cursor->wlr_cursor->x, 164 | cursor->wlr_cursor->y, &sx, &sy, &surface); 165 | if (event->state == WLR_BUTTON_RELEASED) { 166 | cursor->cursor_mode = WC_CURSOR_PASSTHROUGH; 167 | } else if (view != NULL) { 168 | wc_focus_view(view); 169 | } 170 | } 171 | 172 | static void wc_cursor_axis(struct wl_listener *listener, void *data) { 173 | struct wc_cursor *cursor = wl_container_of(listener, cursor, axis); 174 | struct wc_server *server = cursor->server; 175 | struct wlr_event_pointer_axis *event = data; 176 | 177 | if (event->delta_discrete == SCROLL_UP) { 178 | server->mousegrabber->button &= ~(1 << 4); 179 | server->mousegrabber->button |= 1 << 3; 180 | } else if (event->delta_discrete == SCROLL_DOWN) { 181 | server->mousegrabber->button |= 1 << 4; 182 | server->mousegrabber->button &= ~(1 << 3); 183 | } 184 | 185 | wc_mousegrabber_notify_mouse_button( 186 | server->mousegrabber, cursor->wlr_cursor->x, cursor->wlr_cursor->y); 187 | 188 | if (server->mouse_grab) { 189 | return; 190 | } 191 | 192 | wlr_seat_pointer_notify_axis(server->seat->seat, event->time_msec, 193 | event->orientation, event->delta, event->delta_discrete, 194 | event->source); 195 | } 196 | 197 | static void wc_cursor_frame(struct wl_listener *listener, void *data) { 198 | struct wc_cursor *cursor = wl_container_of(listener, cursor, frame); 199 | struct wc_server *server = cursor->server; 200 | 201 | wlr_seat_pointer_notify_frame(server->seat->seat); 202 | } 203 | 204 | void wc_cursor_set_client_cursor(struct wc_cursor *cursor, 205 | struct wlr_seat_pointer_request_set_cursor_event *event) { 206 | struct wc_server *server = cursor->server; 207 | bool use_client_image = event != NULL; 208 | 209 | if (cursor->compositor_image == NULL) { 210 | if (use_client_image) { 211 | wlr_cursor_set_surface(cursor->wlr_cursor, event->surface, 212 | event->hotspot_x, event->hotspot_y); 213 | } else if (use_client_image != cursor->use_client_image) { 214 | const char *image = cursor->compositor_image ? 215 | cursor->compositor_image : 216 | cursor->default_image; 217 | wlr_xcursor_manager_set_cursor_image( 218 | server->xcursor_mgr, image, cursor->wlr_cursor); 219 | } 220 | } 221 | cursor->use_client_image = use_client_image; 222 | } 223 | 224 | void wc_cursor_set_compositor_cursor( 225 | struct wc_cursor *cursor, const char *cursor_name) { 226 | struct wc_server *server = cursor->server; 227 | 228 | char *copy = NULL; 229 | bool skip_lock = false; 230 | bool lock_software_cursors = false; 231 | if (cursor_name != NULL) { 232 | lock_software_cursors = true; 233 | // Only lock here if we haven't previously locked. 234 | skip_lock = cursor->compositor_image != NULL; 235 | copy = strdup(cursor_name); 236 | } else { 237 | // Always unlock when clearing the compositor cursor image. 238 | lock_software_cursors = false; 239 | free(cursor->compositor_image); 240 | } 241 | cursor->compositor_image = copy; 242 | 243 | if (!skip_lock) { 244 | struct wc_output *output; 245 | wl_list_for_each(output, &server->outputs, link) { 246 | wlr_output_lock_software_cursors( 247 | output->wlr_output, lock_software_cursors); 248 | } 249 | } 250 | 251 | const char *image = cursor->compositor_image ? cursor->compositor_image : 252 | cursor->default_image; 253 | 254 | wlr_xcursor_manager_set_cursor_image( 255 | server->xcursor_mgr, image, cursor->wlr_cursor); 256 | } 257 | 258 | void wc_cursor_init(struct wc_server *server) { 259 | struct wc_cursor *cursor = calloc(1, sizeof(struct wc_cursor)); 260 | server->cursor = cursor; 261 | cursor->wlr_cursor = wlr_cursor_create(); 262 | cursor->server = server; 263 | 264 | cursor->default_image = "left_ptr"; 265 | 266 | wlr_cursor_attach_output_layout(cursor->wlr_cursor, server->output_layout); 267 | 268 | cursor->motion.notify = wc_cursor_motion; 269 | cursor->motion_absolute.notify = wc_cursor_motion_absolute; 270 | cursor->button.notify = wc_cursor_button; 271 | cursor->axis.notify = wc_cursor_axis; 272 | cursor->frame.notify = wc_cursor_frame; 273 | 274 | wl_signal_add(&cursor->wlr_cursor->events.motion, &cursor->motion); 275 | wl_signal_add(&cursor->wlr_cursor->events.motion_absolute, 276 | &cursor->motion_absolute); 277 | wl_signal_add(&cursor->wlr_cursor->events.button, &cursor->button); 278 | wl_signal_add(&cursor->wlr_cursor->events.axis, &cursor->axis); 279 | wl_signal_add(&cursor->wlr_cursor->events.frame, &cursor->frame); 280 | 281 | server->xcursor_mgr = wlr_xcursor_manager_create(NULL, 24); 282 | wlr_xcursor_manager_load(server->xcursor_mgr, 1); 283 | 284 | // Hack to get the image set an initialization time 285 | cursor->use_client_image = true; 286 | } 287 | 288 | void wc_cursor_fini(struct wc_server *server) { 289 | struct wc_cursor *cursor = server->cursor; 290 | 291 | // NOTE wlroots takes care of this, 292 | // otherwise this will be a double free. 293 | // wlr_xcursor_manager_destroy(server->xcursor_mgr); 294 | 295 | wl_list_remove(&cursor->motion.link); 296 | wl_list_remove(&cursor->motion_absolute.link); 297 | wl_list_remove(&cursor->button.link); 298 | wl_list_remove(&cursor->axis.link); 299 | wl_list_remove(&cursor->frame.link); 300 | 301 | wlr_cursor_destroy(cursor->wlr_cursor); 302 | cursor->wlr_cursor = NULL; 303 | 304 | free(server->cursor); 305 | server->cursor = NULL; 306 | } 307 | -------------------------------------------------------------------------------- /way-cooler/cursor.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_CURSOR_H 2 | #define WC_CURSOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | enum wc_cursor_mode { 10 | WC_CURSOR_PASSTHROUGH = 0, 11 | WC_CURSOR_MOVE, 12 | WC_CURSOR_RESIZE, 13 | }; 14 | 15 | struct wc_cursor { 16 | struct wc_server *server; 17 | struct wlr_cursor *wlr_cursor; 18 | 19 | // When non-NULL, this takes precedence over all other cursor images. 20 | char *compositor_image; 21 | // Flag to determine if we are using the client provided image. 22 | bool use_client_image; 23 | /* The image to use if there is no compositor_image or use_client_image is 24 | * not set. 25 | */ 26 | const char *default_image; 27 | 28 | enum wc_cursor_mode cursor_mode; 29 | /* 30 | * Original location data of a view when it is grabbed. This is 31 | * used in calculations when resizing and moving it from 32 | * the original location. 33 | * 34 | * depending on mode, these may or may not be valid 35 | */ 36 | struct { 37 | struct wc_view *view; 38 | // Original coordinates of where the cursor was. 39 | int original_x, original_y; 40 | struct wlr_box original_view_geo; 41 | uint32_t resize_edges; 42 | } grabbed; 43 | 44 | struct wl_listener motion; 45 | struct wl_listener motion_absolute; 46 | struct wl_listener button; 47 | struct wl_listener axis; 48 | struct wl_listener frame; 49 | }; 50 | 51 | void wc_cursor_init(struct wc_server *server); 52 | 53 | void wc_cursor_fini(struct wc_server *server); 54 | 55 | /* Sets the cursor image, given from the client. If cursor_name is NULL then 56 | * it will defer to the default compositor cursor. 57 | * 58 | * If the compositor cursor is set to a non-NULL value then this value will be 59 | * ignored until that is null. Once the compositor cursor becomes non-NULL 60 | * there is no need to recall this, it will be done automatically. 61 | * 62 | * To switch to using the compositor cursor again, use wc_set_compositor_cursor. 63 | */ 64 | void wc_cursor_set_client_cursor(struct wc_cursor *cursor, 65 | struct wlr_seat_pointer_request_set_cursor_event *event); 66 | 67 | /* Sets the current image used by the compositor. If cursor_name is NULL then 68 | * it will set the cursor to the client provided cursor, or to the default 69 | * cursor if there is no current client provided one. 70 | * 71 | * This is primarily used by mousegrabber, and is only intended to allow the 72 | * special Awesome client to change the cursor. This is basically a huge 73 | * subversion of how Wayland is supposed to work. 74 | */ 75 | void wc_cursor_set_compositor_cursor( 76 | struct wc_cursor *cursor, const char *cursor_name); 77 | 78 | #endif // WC_CURSOR_H 79 | -------------------------------------------------------------------------------- /way-cooler/input.c: -------------------------------------------------------------------------------- 1 | #include "input.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "keyboard.h" 10 | #include "pointer.h" 11 | #include "seat.h" 12 | #include "server.h" 13 | 14 | static void wc_new_input(struct wl_listener *listener, void *data) { 15 | struct wc_server *server = wl_container_of(listener, server, new_input); 16 | struct wlr_input_device *device = data; 17 | switch (device->type) { 18 | case WLR_INPUT_DEVICE_KEYBOARD: 19 | wc_new_keyboard(server, device); 20 | break; 21 | case WLR_INPUT_DEVICE_POINTER: 22 | wc_new_pointer(server, device); 23 | break; 24 | default: 25 | wlr_log(WLR_ERROR, "Device type not supported: %d", device->type); 26 | return; 27 | } 28 | uint32_t caps = 0; 29 | if (!wl_list_empty(&server->keyboards)) { 30 | caps |= WL_SEAT_CAPABILITY_KEYBOARD; 31 | } 32 | if (!wl_list_empty(&server->pointers)) { 33 | caps |= WL_SEAT_CAPABILITY_POINTER; 34 | } 35 | wlr_seat_set_capabilities(server->seat->seat, caps); 36 | } 37 | 38 | void wc_inputs_init(struct wc_server *server) { 39 | server->new_input.notify = wc_new_input; 40 | wl_signal_add(&server->backend->events.new_input, &server->new_input); 41 | 42 | wc_keyboards_init(server); 43 | wc_pointers_init(server); 44 | } 45 | 46 | void wc_inputs_fini(struct wc_server *server) { 47 | wl_list_remove(&server->new_input.link); 48 | 49 | wc_keyboards_fini(server); 50 | wc_pointers_fini(server); 51 | } 52 | -------------------------------------------------------------------------------- /way-cooler/input.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_INPUT_H 2 | #define WC_INPUT_H 3 | 4 | #include "server.h" 5 | 6 | void wc_inputs_init(struct wc_server *server); 7 | 8 | void wc_inputs_fini(struct wc_server *server); 9 | 10 | #endif // WC_INPUT_H 11 | -------------------------------------------------------------------------------- /way-cooler/keybindings.c: -------------------------------------------------------------------------------- 1 | #include "keybindings.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "seat.h" 9 | #include "server.h" 10 | #include "way-cooler-keybindings-unstable-v1-protocol.h" 11 | #include "xkb_hash_set.h" 12 | 13 | static void register_key(struct wl_client *client, struct wl_resource *resource, 14 | uint32_t key, uint32_t mods) { 15 | struct wc_keybindings *keybindings = wl_resource_get_user_data(resource); 16 | struct xkb_hash_set *registered_keys = keybindings->registered_keys; 17 | 18 | xkb_hash_set_add_entry(registered_keys, key, mods); 19 | } 20 | 21 | static void clear_keys(struct wl_client *client, struct wl_resource *resource) { 22 | struct wc_keybindings *keybindings = wl_resource_get_user_data(resource); 23 | wc_keybindings_clear_keys(keybindings); 24 | } 25 | 26 | static const struct zway_cooler_keybindings_interface keybindings_impl = { 27 | .register_key = register_key, 28 | .clear_keys = clear_keys, 29 | }; 30 | 31 | static void keybindings_handle_resource_destroy(struct wl_resource *resource) { 32 | struct wc_keybindings *keybindings = wl_resource_get_user_data(resource); 33 | 34 | if (keybindings->resource == resource) { 35 | keybindings->resource = NULL; 36 | keybindings->client = NULL; 37 | } 38 | } 39 | 40 | static void keybindings_bind( 41 | struct wl_client *client, void *data, uint32_t version, uint32_t id) { 42 | struct wc_keybindings *keybindings = data; 43 | struct wl_resource *resource = wl_resource_create( 44 | client, &zway_cooler_keybindings_interface, version, id); 45 | wl_resource_set_user_data(resource, keybindings); 46 | 47 | if (resource == NULL) { 48 | wl_client_post_no_memory(client); 49 | return; 50 | } 51 | 52 | keybindings->resource = resource; 53 | 54 | wl_resource_set_implementation(resource, &keybindings_impl, keybindings, 55 | keybindings_handle_resource_destroy); 56 | } 57 | 58 | void wc_keybindings_init(struct wc_server *server) { 59 | struct wc_keybindings *keybindings = 60 | calloc(1, sizeof(struct wc_keybindings)); 61 | keybindings->server = server; 62 | keybindings->global = wl_global_create(server->wl_display, 63 | &zway_cooler_keybindings_interface, KEYBINDINGS_VERSION, 64 | keybindings, keybindings_bind); 65 | 66 | keybindings->registered_keys = calloc(1, sizeof(struct xkb_hash_set)); 67 | 68 | server->keybindings = keybindings; 69 | } 70 | 71 | void wc_keybindings_fini(struct wc_server *server) { 72 | wl_global_destroy(server->keybindings->global); 73 | 74 | wc_keybindings_clear_keys(server->keybindings); 75 | 76 | if (server->keybindings->registered_keys) { 77 | free(server->keybindings->registered_keys); 78 | } 79 | 80 | free(server->keybindings); 81 | 82 | server->keybindings = NULL; 83 | } 84 | 85 | void wc_keybindings_clear_keys(struct wc_keybindings *keybindings) { 86 | struct xkb_hash_set *registered_keys = keybindings->registered_keys; 87 | xkb_hash_set_clear(registered_keys); 88 | } 89 | 90 | bool wc_keybindings_notify_key_if_registered(struct wc_keybindings *keybindings, 91 | uint32_t key_code, xkb_mod_mask_t key_mask, bool pressed, 92 | uint32_t time) { 93 | struct wc_server *server = keybindings->server; 94 | 95 | if (keybindings->resource == NULL) { 96 | return false; 97 | } 98 | 99 | struct xkb_hash_set *registered_keys = keybindings->registered_keys; 100 | 101 | bool present = xkb_hash_set_get_entry(registered_keys, key_code, key_mask); 102 | 103 | enum zway_cooler_keybindings_key_state press_state = pressed ? 104 | ZWAY_COOLER_KEYBINDINGS_KEY_STATE_PRESSED : 105 | ZWAY_COOLER_KEYBINDINGS_KEY_STATE_RELEASED; 106 | zway_cooler_keybindings_send_key( 107 | keybindings->resource, time, key_code, press_state, key_mask); 108 | 109 | struct wlr_seat_client *focused_client = 110 | server->seat->seat->keyboard_state.focused_client; 111 | if (!present && focused_client) 112 | present = present || focused_client->client == keybindings->client; 113 | 114 | return present; 115 | } 116 | -------------------------------------------------------------------------------- /way-cooler/keybindings.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_KEYBINDINGS_H 2 | #define WC_KEYBINDINGS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "server.h" 8 | #include "xkb_hash_set.h" 9 | 10 | #define KEYBINDINGS_VERSION 1 11 | 12 | struct wc_keybindings { 13 | struct wc_server *server; 14 | 15 | struct xkb_hash_set *registered_keys; 16 | 17 | struct wl_global *global; 18 | struct wl_resource *resource; 19 | struct wl_client *client; 20 | }; 21 | 22 | void wc_keybindings_init(struct wc_server *server); 23 | 24 | void wc_keybindings_fini(struct wc_server *server); 25 | 26 | /* 27 | * Checks if the key is registered as a keybinding and, if so, sends it to the 28 | * registered keybindings client. 29 | * 30 | * If the key is registered true is returned. 31 | * 32 | * Mods is expected to be all mods that are either depressed, latched, or 33 | * locked. 34 | */ 35 | bool wc_keybindings_notify_key_if_registered(struct wc_keybindings *keybindings, 36 | uint32_t key_code, xkb_mod_mask_t key_mask, bool pressed, 37 | uint32_t time); 38 | 39 | /* 40 | * Clears the stored keybindings, meaning those keys will no longer be filtered 41 | * from other clients. 42 | */ 43 | 44 | void wc_keybindings_clear_keys(struct wc_keybindings *keybindings); 45 | 46 | #endif // WC_KEYBINDINGS_H 47 | -------------------------------------------------------------------------------- /way-cooler/keyboard.c: -------------------------------------------------------------------------------- 1 | #include "keyboard.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "keybindings.h" 14 | #include "seat.h" 15 | 16 | static bool wc_keyboard_mod_is_active( 17 | struct wc_keyboard *keyboard, const char *mod_name) { 18 | struct xkb_state *state = keyboard->device->keyboard->xkb_state; 19 | return xkb_state_mod_name_is_active( 20 | state, mod_name, XKB_STATE_MODS_DEPRESSED); 21 | } 22 | 23 | static void wc_keyboard_on_key(struct wl_listener *listener, void *data) { 24 | struct wc_keyboard *keyboard = wl_container_of(listener, keyboard, key); 25 | struct wc_server *server = keyboard->server; 26 | struct wlr_seat *seat = server->seat->seat; 27 | struct wlr_event_keyboard_key *event = data; 28 | 29 | uint32_t keycode = event->keycode + 8; 30 | const xkb_keysym_t *syms; 31 | int nsyms = xkb_state_key_get_syms( 32 | keyboard->device->keyboard->xkb_state, keycode, &syms); 33 | 34 | bool handled = false; 35 | for (int i = 0; i < nsyms; i++) { 36 | xkb_keysym_t keysym = syms[i]; 37 | if (keysym >= XKB_KEY_XF86Switch_VT_1 && 38 | keysym <= XKB_KEY_XF86Switch_VT_12) { 39 | handled = true; 40 | if (wlr_backend_is_multi(server->backend)) { 41 | struct wlr_session *session = 42 | wlr_backend_get_session(server->backend); 43 | if (session) { 44 | xkb_keysym_t vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; 45 | wlr_session_change_vt(session, vt); 46 | } 47 | } 48 | } 49 | 50 | switch (keysym) { 51 | case XKB_KEY_Escape: 52 | if (wc_keyboard_mod_is_active(keyboard, "Shift") && 53 | wc_keyboard_mod_is_active(keyboard, "Control")) { 54 | wl_display_terminate(server->wl_display); 55 | handled = true; 56 | break; 57 | } 58 | } 59 | } 60 | if (!handled) { 61 | struct wlr_keyboard_modifiers *keyboard_modifiers = 62 | &keyboard->device->keyboard->modifiers; 63 | xkb_mod_mask_t modifiers = keyboard_modifiers->depressed | 64 | keyboard_modifiers->latched | keyboard_modifiers->locked; 65 | 66 | bool pressed = event->state == WLR_KEY_PRESSED; 67 | handled = wc_keybindings_notify_key_if_registered(server->keybindings, 68 | keycode, modifiers, pressed, event->time_msec); 69 | } 70 | 71 | if (!handled) { 72 | wlr_seat_set_keyboard(seat, keyboard->device); 73 | wlr_seat_keyboard_notify_key( 74 | seat, event->time_msec, event->keycode, event->state); 75 | } 76 | } 77 | 78 | static void wc_keyboard_on_modifiers(struct wl_listener *listener, void *data) { 79 | struct wc_keyboard *keyboard = 80 | wl_container_of(listener, keyboard, modifiers); 81 | struct wlr_seat *seat = keyboard->server->seat->seat; 82 | 83 | wlr_seat_set_keyboard(seat, keyboard->device); 84 | wlr_seat_keyboard_notify_modifiers( 85 | seat, &keyboard->device->keyboard->modifiers); 86 | } 87 | 88 | static void wc_keyboard_removed(struct wl_listener *listener, void *data) { 89 | struct wc_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); 90 | wlr_log(WLR_INFO, "Keyboard removed: %p", keyboard->device); 91 | wl_list_remove(&keyboard->link); 92 | 93 | wl_list_remove(&keyboard->key.link); 94 | wl_list_remove(&keyboard->modifiers.link); 95 | wl_list_remove(&keyboard->destroy.link); 96 | free(keyboard); 97 | } 98 | 99 | void wc_new_keyboard( 100 | struct wc_server *server, struct wlr_input_device *device) { 101 | wlr_log(WLR_INFO, "New keyboard detected: %p", device); 102 | 103 | wlr_seat_set_keyboard(server->seat->seat, device); 104 | 105 | struct wc_keyboard *keyboard = calloc(1, sizeof(struct wc_keyboard)); 106 | keyboard->server = server; 107 | keyboard->device = device; 108 | 109 | /* We need to prepare an XKB keymap and assign it to the keyboard. This 110 | * assumes the defaults (i.e. uses the XKB_DEFAULT_* environment variables). 111 | */ 112 | struct xkb_rule_names rules = {0}; 113 | struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 114 | struct xkb_keymap *keymap = xkb_map_new_from_names( 115 | context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); 116 | 117 | wlr_keyboard_set_keymap(device->keyboard, keymap); 118 | xkb_keymap_unref(keymap); 119 | xkb_context_unref(context); 120 | 121 | wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); 122 | 123 | keyboard->key.notify = wc_keyboard_on_key; 124 | keyboard->modifiers.notify = wc_keyboard_on_modifiers; 125 | keyboard->destroy.notify = wc_keyboard_removed; 126 | 127 | wl_signal_add(&device->keyboard->events.key, &keyboard->key); 128 | wl_signal_add(&device->keyboard->events.modifiers, &keyboard->modifiers); 129 | wl_signal_add(&device->events.destroy, &keyboard->destroy); 130 | 131 | wl_list_insert(&server->keyboards, &keyboard->link); 132 | } 133 | 134 | void wc_keyboards_init(struct wc_server *server) { 135 | wl_list_init(&server->keyboards); 136 | } 137 | 138 | void wc_keyboards_fini(struct wc_server *server) { 139 | struct wc_keyboard *keyboard; 140 | struct wc_keyboard *temp; 141 | wl_list_for_each_safe(keyboard, temp, &server->keyboards, link) { 142 | wc_keyboard_removed(&keyboard->destroy, NULL); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /way-cooler/keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_KEYBOARD_H 2 | #define WC_KEYBOARD_H 3 | 4 | #include "wlr/types/wlr_input_device.h" 5 | 6 | #include "server.h" 7 | 8 | struct wc_keyboard { 9 | struct wl_list link; 10 | struct wc_server *server; 11 | 12 | struct wlr_input_device *device; 13 | 14 | struct wl_listener key; 15 | struct wl_listener modifiers; 16 | struct wl_listener destroy; 17 | }; 18 | 19 | void wc_keyboards_init(struct wc_server *server); 20 | 21 | void wc_keyboards_fini(struct wc_server *server); 22 | 23 | void wc_new_keyboard(struct wc_server *server, struct wlr_input_device *device); 24 | 25 | #endif // WC_KEYBOARD_H 26 | -------------------------------------------------------------------------------- /way-cooler/layer_shell.c: -------------------------------------------------------------------------------- 1 | #include "layer_shell.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "output.h" 13 | #include "seat.h" 14 | #include "server.h" 15 | #include "view.h" 16 | 17 | static const uint32_t LAYER_BOTH_HORIZ = 18 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; 19 | static const uint32_t LAYER_BOTH_VERT = 20 | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; 21 | 22 | static void wc_layer_shell_commit(struct wl_listener *listener, void *data) { 23 | struct wc_layer *layer = wl_container_of(listener, layer, commit); 24 | struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface; 25 | struct wlr_output *wlr_output = layer_surface->output; 26 | if (wlr_output == NULL) { 27 | return; 28 | } 29 | struct wc_output *wc_output = wlr_output->data; 30 | struct wlr_box old_geo = layer->geo; 31 | wc_layer_shell_arrange_layers(wlr_output->data); 32 | 33 | bool geo_changed = 34 | memcmp(&old_geo, &layer->geo, sizeof(struct wlr_box)) != 0; 35 | bool layer_changed = layer->layer != layer_surface->current.layer; 36 | if (layer_changed) { 37 | wl_list_remove(&layer->link); 38 | wl_list_insert( 39 | &wc_output->layers[layer_surface->current.layer], &layer->link); 40 | layer->layer = layer_surface->current.layer; 41 | } 42 | if (geo_changed || layer_changed) { 43 | wc_output_damage_surface( 44 | wlr_output->data, layer_surface->surface, NULL, old_geo); 45 | } 46 | 47 | pixman_region32_t damage; 48 | pixman_region32_init(&damage); 49 | wlr_surface_get_effective_damage(layer_surface->surface, &damage); 50 | pixman_region32_translate(&damage, layer->geo.x, layer->geo.y); 51 | wc_output_damage_surface( 52 | wlr_output->data, layer_surface->surface, &damage, layer->geo); 53 | 54 | pixman_region32_fini(&damage); 55 | } 56 | 57 | static void wc_layer_shell_map(struct wl_listener *listener, void *data) { 58 | struct wc_layer *layer = wl_container_of(listener, layer, map); 59 | struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface; 60 | layer->mapped = true; 61 | 62 | pixman_region32_t damage; 63 | pixman_region32_init(&damage); 64 | wlr_surface_get_effective_damage(layer_surface->surface, &damage); 65 | pixman_region32_translate(&damage, layer->geo.x, layer->geo.y); 66 | 67 | wc_output_damage_surface(layer_surface->output->data, 68 | layer_surface->surface, &damage, layer->geo); 69 | 70 | pixman_region32_fini(&damage); 71 | } 72 | 73 | static void wc_layer_shell_unmap(struct wl_listener *listener, void *data) { 74 | struct wc_layer *layer = wl_container_of(listener, layer, unmap); 75 | struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface; 76 | layer->mapped = false; 77 | 78 | wc_output_damage_surface(layer_surface->output->data, 79 | layer_surface->surface, NULL, layer->geo); 80 | } 81 | 82 | void wc_layer_shell_destroy(struct wl_listener *listener, void *data) { 83 | struct wc_layer *layer = wl_container_of(listener, layer, destroy); 84 | wl_list_remove(&layer->link); 85 | 86 | wl_list_remove(&layer->commit.link); 87 | wl_list_remove(&layer->map.link); 88 | wl_list_remove(&layer->unmap.link); 89 | wl_list_remove(&layer->destroy.link); 90 | 91 | wlr_layer_surface_v1_close(layer->layer_surface); 92 | free(layer); 93 | } 94 | 95 | static void wc_arrange_layer(struct wc_output *output, struct wc_seat *seat, 96 | struct wl_list *layers, struct wlr_box *usable_area, bool exclusive) { 97 | struct wlr_box full_area = {0}; 98 | wlr_output_effective_resolution( 99 | output->wlr_output, &full_area.width, &full_area.height); 100 | struct wc_layer *wc_layer; 101 | wl_list_for_each_reverse(wc_layer, layers, link) { 102 | struct wlr_layer_surface_v1 *layer = wc_layer->layer_surface; 103 | struct wlr_layer_surface_v1_state *state = &layer->current; 104 | if (exclusive != (state->exclusive_zone > 0)) { 105 | continue; 106 | } 107 | struct wlr_box bounds = *usable_area; 108 | if (state->exclusive_zone == -1) { 109 | bounds = full_area; 110 | } 111 | struct wlr_box arranged_area = { 112 | .width = state->desired_width, 113 | .height = state->desired_height, 114 | }; 115 | 116 | // horizontal axis 117 | if ((state->anchor & LAYER_BOTH_HORIZ) && arranged_area.width == 0) { 118 | arranged_area.x = bounds.x; 119 | arranged_area.width = bounds.width; 120 | } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) { 121 | arranged_area.x = bounds.x; 122 | } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { 123 | arranged_area.x = bounds.x + (bounds.width - arranged_area.width); 124 | } else { 125 | arranged_area.x = 126 | bounds.x + ((bounds.width / 2) - (arranged_area.width / 2)); 127 | } 128 | 129 | // vertical axis 130 | if ((state->anchor & LAYER_BOTH_VERT) && arranged_area.height == 0) { 131 | arranged_area.y = bounds.y; 132 | arranged_area.height = bounds.height; 133 | } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { 134 | arranged_area.y = bounds.y; 135 | } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { 136 | arranged_area.y = bounds.y + (bounds.height - arranged_area.height); 137 | } else { 138 | arranged_area.y = bounds.y + 139 | ((bounds.height / 2) - (arranged_area.height / 2)); 140 | } 141 | 142 | // left and right margin 143 | if ((state->anchor & LAYER_BOTH_HORIZ) == LAYER_BOTH_HORIZ) { 144 | arranged_area.x += state->margin.left; 145 | arranged_area.width -= state->margin.left + state->margin.right; 146 | } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) { 147 | arranged_area.x += state->margin.left; 148 | } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { 149 | arranged_area.x -= state->margin.right; 150 | } 151 | 152 | // top and bottom margin 153 | if ((state->anchor & LAYER_BOTH_VERT) == LAYER_BOTH_VERT) { 154 | arranged_area.y += state->margin.top; 155 | arranged_area.height -= state->margin.top + state->margin.bottom; 156 | } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { 157 | arranged_area.y += state->margin.top; 158 | } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { 159 | arranged_area.y -= state->margin.bottom; 160 | } 161 | 162 | if (arranged_area.width < 0 || arranged_area.height < 0) { 163 | wlr_log(WLR_ERROR, "Bad width/height: %d, %d", arranged_area.width, 164 | arranged_area.height); 165 | wlr_layer_surface_v1_close(layer); 166 | continue; 167 | } 168 | 169 | wc_layer->geo = arranged_area; 170 | // TODO Apply exclusive zones 171 | wlr_layer_surface_v1_configure( 172 | layer, arranged_area.width, arranged_area.height); 173 | 174 | // TODO send cursor enter events if it's now hovering 175 | } 176 | } 177 | 178 | void wc_layer_shell_arrange_layers(struct wc_output *output) { 179 | struct wlr_box usable_area = {0}; 180 | struct wc_server *server = output->server; 181 | struct wc_seat *seat = server->seat; 182 | wlr_output_effective_resolution( 183 | output->wlr_output, &usable_area.width, &usable_area.height); 184 | wc_arrange_layer(output, seat, 185 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &usable_area, 186 | true); 187 | wc_arrange_layer(output, seat, 188 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &usable_area, true); 189 | wc_arrange_layer(output, seat, 190 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &usable_area, 191 | true); 192 | wc_arrange_layer(output, seat, 193 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &usable_area, 194 | true); 195 | 196 | // TODO Arrange maximized views once we have those 197 | 198 | wc_arrange_layer(output, seat, 199 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &usable_area, 200 | false); 201 | wc_arrange_layer(output, seat, 202 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &usable_area, 203 | false); 204 | wc_arrange_layer(output, seat, 205 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &usable_area, 206 | false); 207 | wc_arrange_layer(output, seat, 208 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &usable_area, 209 | false); 210 | 211 | uint32_t layers_above_shell[] = { 212 | ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, 213 | ZWLR_LAYER_SHELL_V1_LAYER_TOP, 214 | }; 215 | size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]); 216 | struct wc_layer *layer = NULL; 217 | struct wc_layer *topmost = NULL; 218 | for (size_t i = 0; i < nlayers; i++) { 219 | wl_list_for_each_reverse( 220 | layer, &output->layers[layers_above_shell[i]], link) { 221 | if (layer->layer_surface->current.keyboard_interactive) { 222 | topmost = layer; 223 | break; 224 | } 225 | } 226 | if (topmost != NULL) { 227 | break; 228 | } 229 | } 230 | 231 | wc_seat_set_focus_layer(seat, topmost ? topmost->layer_surface : NULL); 232 | } 233 | 234 | static void wc_layer_shell_new_surface( 235 | struct wl_listener *listener, void *data) { 236 | struct wc_server *server = 237 | wl_container_of(listener, server, new_layer_surface); 238 | struct wlr_layer_surface_v1 *layer_surface = data; 239 | struct wc_output *active_output = wc_get_active_output(server); 240 | if (active_output == NULL) { 241 | wlr_layer_surface_v1_close(layer_surface); 242 | return; 243 | } 244 | 245 | if (!layer_surface->output) { 246 | // If client did not request an output, give them the focused one. 247 | layer_surface->output = active_output->wlr_output; 248 | } 249 | struct wc_output *output = layer_surface->output->data; 250 | 251 | struct wc_layer *layer = calloc(1, sizeof(struct wc_layer)); 252 | layer->server = server; 253 | layer->layer_surface = layer_surface; 254 | 255 | layer->commit.notify = wc_layer_shell_commit; 256 | wl_signal_add(&layer_surface->surface->events.commit, &layer->commit); 257 | layer->map.notify = wc_layer_shell_map; 258 | wl_signal_add(&layer_surface->events.map, &layer->map); 259 | layer->unmap.notify = wc_layer_shell_unmap; 260 | wl_signal_add(&layer_surface->events.unmap, &layer->unmap); 261 | layer->destroy.notify = wc_layer_shell_destroy; 262 | wl_signal_add(&layer_surface->events.destroy, &layer->destroy); 263 | 264 | size_t len = sizeof(output->layers) / sizeof(output->layers[0]); 265 | if (layer_surface->current.layer >= len) { 266 | wlr_log(WLR_ERROR, "Bad surface layer %d", 267 | layer_surface->current.layer); 268 | wlr_layer_surface_v1_close(layer_surface); 269 | return; 270 | } 271 | wl_list_insert(&output->layers[layer_surface->current.layer], &layer->link); 272 | 273 | struct wlr_layer_surface_v1_state old_state = layer_surface->current; 274 | layer_surface->current = layer_surface->client_pending; 275 | wc_layer_shell_arrange_layers(output); 276 | layer_surface->current = old_state; 277 | } 278 | 279 | void wc_layers_init(struct wc_server *server) { 280 | server->layer_shell = wlr_layer_shell_v1_create(server->wl_display); 281 | 282 | server->new_layer_surface.notify = wc_layer_shell_new_surface; 283 | wl_signal_add(&server->layer_shell->events.new_surface, 284 | &server->new_layer_surface); 285 | } 286 | 287 | void wc_layers_fini(struct wc_server *server) { 288 | server->layer_shell = NULL; 289 | 290 | wl_list_remove(&server->new_layer_surface.link); 291 | } 292 | -------------------------------------------------------------------------------- /way-cooler/layer_shell.h: -------------------------------------------------------------------------------- 1 | #ifndef LAYER_SHELL_H 2 | #define LAYER_SHELL_H 3 | 4 | #include 5 | 6 | #include "server.h" 7 | 8 | struct wc_layer { 9 | struct wl_list link; 10 | struct wc_server *server; 11 | 12 | struct wlr_layer_surface_v1 *layer_surface; 13 | struct wlr_box geo; 14 | bool mapped; 15 | enum zwlr_layer_shell_v1_layer layer; 16 | 17 | struct wl_listener commit; 18 | struct wl_listener map; 19 | struct wl_listener unmap; 20 | struct wl_listener destroy; 21 | }; 22 | 23 | void wc_layers_init(struct wc_server *server); 24 | 25 | void wc_layers_fini(struct wc_server *server); 26 | 27 | void wc_layer_shell_destroy(struct wl_listener *listener, void *data); 28 | 29 | // Arrange the layer shells on this output. 30 | void wc_layer_shell_arrange_layers(struct wc_output *output); 31 | 32 | #endif // LAYER_SHELL_H 33 | -------------------------------------------------------------------------------- /way-cooler/main.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "server.h" 14 | 15 | const char *WC_HELP_MESSAGE = 16 | "Usage: %s [OPTION] startup_command\n" 17 | "\n" 18 | " -c Execute the command after startup.\n" 19 | " -h Show help message and quit.\n" 20 | " -d Turn on debugging" 21 | "\n"; 22 | 23 | const char *WC_GETOPT_OPTIONS = 24 | #ifdef __GNUC__ 25 | "+" 26 | #endif 27 | "hc:d"; 28 | 29 | const char *WC_BINARY_PATH = NULL; 30 | 31 | void print_usage(void) { 32 | printf(WC_HELP_MESSAGE, WC_BINARY_PATH); 33 | } 34 | 35 | int main(int argc, char *argv[]) { 36 | WC_BINARY_PATH = argv[0]; 37 | wlr_log_init(WLR_DEBUG, NULL); 38 | char *startup_cmd = NULL; 39 | 40 | int c; 41 | while ((c = getopt(argc, argv, WC_GETOPT_OPTIONS)) != -1) { 42 | switch (c) { 43 | case 'd': 44 | WC_DEBUG = 1; 45 | break; 46 | case 'c': 47 | if (startup_cmd != NULL) { 48 | free(startup_cmd); 49 | } 50 | startup_cmd = strdup(optarg); 51 | break; 52 | case 'h': 53 | default: 54 | print_usage(); 55 | goto fail; 56 | } 57 | } 58 | if (optind < argc) { 59 | print_usage(); 60 | goto fail; 61 | } 62 | 63 | struct wc_server server = {0}; 64 | if (!init_server(&server)) { 65 | wlr_log(WLR_ERROR, "Could not initialize Wayland resources"); 66 | goto fail; 67 | } 68 | wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", 69 | server.wayland_socket); 70 | if (!wlr_backend_start(server.backend)) { 71 | wlr_backend_destroy(server.backend); 72 | wl_display_destroy(server.wl_display); 73 | goto fail; 74 | } 75 | setenv("WAYLAND_DISPLAY", server.wayland_socket, true); 76 | 77 | server.startup_cmd = startup_cmd; 78 | 79 | wl_display_run(server.wl_display); 80 | fini_server(&server); 81 | 82 | return 0; 83 | 84 | fail: 85 | free(startup_cmd); 86 | exit(1); 87 | } 88 | -------------------------------------------------------------------------------- /way-cooler/meson.build: -------------------------------------------------------------------------------- 1 | cc = meson.get_compiler('c') 2 | 3 | wayland_server = dependency('wayland-server') 4 | wayland_client = dependency('wayland-client') 5 | wayland_cursor = dependency('wayland-cursor') 6 | wayland_egl = dependency('wayland-egl') 7 | wayland_protos = dependency('wayland-protocols', version: '>=1.14') 8 | xkbcommon = dependency('xkbcommon') 9 | xcb = dependency('xcb') 10 | pixman = dependency('pixman-1') 11 | libinput = dependency('libinput', version: '>=1.6.0') 12 | 13 | # Try first to find wlroots as a subproject, then as a system dependency 14 | wlroots_version = '>=0.5.0' 15 | wlroots_proj = subproject( 16 | 'wlroots', 17 | default_options: ['rootston=false', 'examples=false'], 18 | required: false, 19 | version: wlroots_version, 20 | ) 21 | if wlroots_proj.found() 22 | wlroots = wlroots_proj.get_variable('wlroots') 23 | wlroots_conf = wlroots_proj.get_variable('conf_data') 24 | wlroots_has_xwayland = wlroots_conf.get('WLR_HAS_XWAYLAND') == 1 25 | else 26 | wlroots = dependency('wlroots', version: wlroots_version) 27 | wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include ', dependencies: wlroots) == '1' 28 | endif 29 | 30 | if not wlroots_has_xwayland 31 | error('Cannot build compositor: wlroots has been built without Xwayland support') 32 | endif 33 | 34 | way_cooler_deps = [ 35 | wayland_server, 36 | wlroots, 37 | xkbcommon, 38 | server_protos, 39 | pixman, 40 | libinput, 41 | ] 42 | 43 | way_cooler_sources = files( 44 | 'cursor.c', 45 | 'input.c', 46 | 'keybindings.c', 47 | 'keyboard.c', 48 | 'layer_shell.c', 49 | 'main.c', 50 | 'mousegrabber.c', 51 | 'output.c', 52 | 'pointer.c', 53 | 'seat.c', 54 | 'server.c', 55 | 'view.c', 56 | 'xdg.c', 57 | 'xkb_hash_set.c', 58 | 'xwayland.c', 59 | ) 60 | 61 | executable( 62 | 'way-cooler', 63 | way_cooler_sources, 64 | install : true, 65 | include_directories: [include_directories('.')], 66 | dependencies: way_cooler_deps, 67 | ) 68 | -------------------------------------------------------------------------------- /way-cooler/mousegrabber.c: -------------------------------------------------------------------------------- 1 | #include "mousegrabber.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "cursor.h" 11 | #include "output.h" 12 | #include "server.h" 13 | #include "way-cooler-mousegrabber-unstable-v1-protocol.h" 14 | 15 | static void grab_mouse(struct wl_client *client, struct wl_resource *resource, 16 | const char *new_cursor_name) { 17 | struct wc_mousegrabber *mousegrabber = wl_resource_get_user_data(resource); 18 | struct wc_server *server = mousegrabber->server; 19 | struct wc_cursor *cursor = server->cursor; 20 | 21 | if (mousegrabber->resource != NULL || server->mouse_grab) { 22 | wl_resource_post_error(resource, 23 | ZWAY_COOLER_MOUSEGRABBER_ERROR_ALREADY_GRABBED, 24 | "mouse has already been grabbed"); 25 | return; 26 | } 27 | 28 | mousegrabber->resource = resource; 29 | mousegrabber->client = client; 30 | 31 | server->mouse_grab = true; 32 | cursor->cursor_mode = WC_CURSOR_PASSTHROUGH; 33 | wc_cursor_set_compositor_cursor(cursor, new_cursor_name); 34 | 35 | wlr_log(WLR_DEBUG, "mousegrabber: mouse grabbed"); 36 | } 37 | 38 | static void unset_mouse(struct wc_server *server) { 39 | struct wc_cursor *cursor = server->cursor; 40 | if (cursor == NULL || !server->mouse_grab) { 41 | return; 42 | } 43 | 44 | server->mouse_grab = false; 45 | wc_cursor_set_compositor_cursor(cursor, NULL); 46 | 47 | wlr_log(WLR_DEBUG, "mousegrabber: mouse released"); 48 | } 49 | 50 | static void release_mouse( 51 | struct wl_client *client, struct wl_resource *resource) { 52 | struct wc_mousegrabber *mousegrabber = wl_resource_get_user_data(resource); 53 | 54 | if (mousegrabber->resource != NULL) { 55 | assert(mousegrabber->client); 56 | } 57 | 58 | if (mousegrabber->resource == NULL || mousegrabber->client != client) { 59 | wl_resource_post_error(resource, 60 | ZWAY_COOLER_MOUSEGRABBER_ERROR_NOT_GRABBED, 61 | "mouse has not been grabbed by this client"); 62 | return; 63 | } 64 | 65 | unset_mouse(mousegrabber->server); 66 | 67 | // NOTE: Calls our destroy event, which clears client resource pointers. 68 | wl_resource_destroy(mousegrabber->resource); 69 | } 70 | 71 | static const struct zway_cooler_mousegrabber_interface mousegrabber_impl = { 72 | .grab_mouse = grab_mouse, 73 | .release_mouse = release_mouse, 74 | }; 75 | 76 | static void mousegrabber_handle_resource_destroy(struct wl_resource *resource) { 77 | struct wc_mousegrabber *mousegrabber = wl_resource_get_user_data(resource); 78 | 79 | if (mousegrabber->resource == resource) { 80 | unset_mouse(mousegrabber->server); 81 | mousegrabber->resource = NULL; 82 | mousegrabber->client = NULL; 83 | } 84 | } 85 | 86 | static void mousegrabber_bind(struct wl_client *wl_client, void *data, 87 | uint32_t version, uint32_t id) { 88 | struct wc_mousegrabber *mousegrabber = data; 89 | struct wl_resource *resource = wl_resource_create( 90 | wl_client, &zway_cooler_mousegrabber_interface, version, id); 91 | wl_resource_set_user_data(resource, mousegrabber); 92 | 93 | if (resource == NULL) { 94 | wl_client_post_no_memory(wl_client); 95 | return; 96 | } 97 | 98 | wl_resource_set_implementation(resource, &mousegrabber_impl, mousegrabber, 99 | mousegrabber_handle_resource_destroy); 100 | } 101 | 102 | void wc_mousegrabber_init(struct wc_server *server) { 103 | struct wc_mousegrabber *mousegrabber = 104 | calloc(1, sizeof(struct wc_mousegrabber)); 105 | mousegrabber->server = server; 106 | mousegrabber->global = wl_global_create(server->wl_display, 107 | &zway_cooler_mousegrabber_interface, MOUSEGRABBER_VERSION, 108 | mousegrabber, mousegrabber_bind); 109 | 110 | server->mousegrabber = mousegrabber; 111 | } 112 | 113 | void wc_mousegrabber_fini(struct wc_server *server) { 114 | if (server->mousegrabber->resource) { 115 | wl_list_remove(wl_resource_get_link(server->mousegrabber->resource)); 116 | } 117 | wl_global_destroy(server->mousegrabber->global); 118 | 119 | free(server->mousegrabber); 120 | 121 | server->mousegrabber = NULL; 122 | } 123 | 124 | void wc_mousegrabber_notify_mouse_moved( 125 | struct wc_mousegrabber *mousegrabber, int x, int y) { 126 | if (mousegrabber == NULL || mousegrabber->resource == NULL) { 127 | return; 128 | } 129 | 130 | zway_cooler_mousegrabber_send_mouse_moved( 131 | mousegrabber->resource, x, y, mousegrabber->button); 132 | } 133 | 134 | void wc_mousegrabber_notify_mouse_button( 135 | struct wc_mousegrabber *mousegrabber, int x, int y) { 136 | if (mousegrabber == NULL || mousegrabber->resource == NULL) { 137 | return; 138 | } 139 | 140 | zway_cooler_mousegrabber_send_mouse_button( 141 | mousegrabber->resource, x, y, mousegrabber->button); 142 | } 143 | -------------------------------------------------------------------------------- /way-cooler/mousegrabber.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_MOUSEGRABBER_H 2 | #define WC_MOUSEGRABBER_H 3 | 4 | #include 5 | 6 | #include "server.h" 7 | 8 | #define MOUSEGRABBER_VERSION 1 9 | 10 | struct wc_mousegrabber { 11 | struct wc_server *server; 12 | 13 | // down up right middle left 14 | unsigned int button : 5; 15 | 16 | struct wl_global *global; 17 | struct wl_resource *resource; 18 | struct wl_client *client; 19 | }; 20 | 21 | void wc_mousegrabber_init(struct wc_server *server); 22 | 23 | void wc_mousegrabber_fini(struct wc_server *server); 24 | 25 | void wc_mousegrabber_notify_mouse_moved( 26 | struct wc_mousegrabber *mousegrabber, int x, int y); 27 | 28 | void wc_mousegrabber_notify_mouse_button( 29 | struct wc_mousegrabber *mousegrabber, int x, int y); 30 | 31 | #endif // WC_MOUSEGRABBER_H 32 | -------------------------------------------------------------------------------- /way-cooler/output.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | 3 | #include "output.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "layer_shell.h" 19 | #include "server.h" 20 | #include "view.h" 21 | 22 | struct wc_render_data { 23 | struct wlr_renderer *renderer; 24 | pixman_region32_t *damage; 25 | struct timespec *when; 26 | }; 27 | 28 | /* Used to move all of the data necessary to render a surface from the top-level 29 | * frame handler to the per-surface render function. */ 30 | struct wc_view_render_data { 31 | struct wlr_output *output; 32 | struct wc_view *view; 33 | struct wc_render_data render_data; 34 | }; 35 | 36 | /* Used to move all of the data necessary to render a surface from the layers */ 37 | struct wc_layer_render_data { 38 | struct wc_layer *layer; 39 | struct wc_render_data render_data; 40 | }; 41 | 42 | /* Used when calculating the damage of a surface */ 43 | struct wc_surface_damage_data { 44 | struct wc_output *output; 45 | pixman_region32_t *surface_damage; 46 | // The full size of the surface, used if the surface_damage is NULL 47 | struct wlr_box surface_output_geo; 48 | }; 49 | 50 | static void damage_surface_iterator( 51 | struct wlr_surface *surface, int sx, int sy, void *data_) { 52 | struct wc_surface_damage_data *damage_data = data_; 53 | 54 | struct wlr_box surface_area = damage_data->surface_output_geo; 55 | surface_area.x += sx; 56 | surface_area.y += sy; 57 | 58 | struct wc_output *output = damage_data->output; 59 | if (damage_data->surface_damage == NULL) { 60 | wlr_output_damage_add_box(output->damage, &surface_area); 61 | } else { 62 | wlr_output_damage_add(output->damage, damage_data->surface_damage); 63 | } 64 | wlr_output_schedule_frame(output->wlr_output); 65 | } 66 | 67 | static void scissor_output( 68 | struct wlr_output *wlr_output, pixman_box32_t *rect) { 69 | struct wlr_renderer *renderer = 70 | wlr_backend_get_renderer(wlr_output->backend); 71 | assert(renderer); 72 | 73 | struct wlr_box box = { 74 | .x = rect->x1, 75 | .y = rect->y1, 76 | .width = rect->x2 - rect->x1, 77 | .height = rect->y2 - rect->y1, 78 | }; 79 | 80 | int ow, oh; 81 | wlr_output_transformed_resolution(wlr_output, &ow, &oh); 82 | 83 | enum wl_output_transform transform = 84 | wlr_output_transform_invert(wlr_output->transform); 85 | wlr_box_transform(&box, &box, transform, ow, oh); 86 | 87 | wlr_renderer_scissor(renderer, &box); 88 | } 89 | 90 | static void wc_render_surface(struct wlr_surface *surface, 91 | pixman_region32_t *damage, struct wlr_output *output, 92 | struct wlr_renderer *renderer, struct timespec *when, int sx, int sy, 93 | int ox, int oy) { 94 | struct wlr_texture *texture = wlr_surface_get_texture(surface); 95 | if (texture == NULL) { 96 | return; 97 | } 98 | 99 | struct wlr_box box = { 100 | .x = sx + ox, 101 | .y = sy + oy, 102 | .width = surface->current.width * output->scale, 103 | .height = surface->current.height * output->scale, 104 | }; 105 | float matrix[9]; 106 | enum wl_output_transform transform = 107 | wlr_output_transform_invert(surface->current.transform); 108 | wlr_matrix_project_box( 109 | matrix, &box, transform, 0, output->transform_matrix); 110 | 111 | int nrects; 112 | pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); 113 | for (int i = 0; i < nrects; i++) { 114 | scissor_output(output, &rects[i]); 115 | wlr_render_texture_with_matrix(renderer, texture, matrix, 1); 116 | } 117 | 118 | wlr_surface_send_frame_done(surface, when); 119 | } 120 | 121 | static void wc_render_view( 122 | struct wlr_surface *surface, int sx, int sy, void *data) { 123 | struct wc_view_render_data *rdata = data; 124 | pixman_region32_t *damage = rdata->render_data.damage; 125 | struct wc_view *view = rdata->view; 126 | struct wlr_output *output = rdata->output; 127 | 128 | double ox = 0, oy = 0; 129 | wlr_output_layout_output_coords( 130 | view->server->output_layout, output, &ox, &oy); 131 | ox += view->geo.x + sx; 132 | oy += view->geo.y + sy; 133 | 134 | wc_render_surface(surface, damage, output, rdata->render_data.renderer, 135 | rdata->render_data.when, sx, sy, ox, oy); 136 | } 137 | 138 | static void wc_render_layer( 139 | struct wlr_surface *surface, int sx, int sy, void *data) { 140 | struct wc_layer_render_data *rdata = data; 141 | pixman_region32_t *damage = rdata->render_data.damage; 142 | struct wc_layer *layer = rdata->layer; 143 | struct wc_server *server = layer->server; 144 | struct wlr_output *output = layer->layer_surface->output; 145 | 146 | double ox = 0, oy = 0; 147 | wlr_output_layout_output_coords(server->output_layout, output, &ox, &oy); 148 | ox += layer->geo.x + sx, oy += layer->geo.y + sy; 149 | 150 | wc_render_surface(surface, damage, output, rdata->render_data.renderer, 151 | rdata->render_data.when, sx, sy, layer->geo.x, layer->geo.y); 152 | } 153 | 154 | static void wc_render_layers(struct timespec *now, pixman_region32_t *damage, 155 | struct wlr_renderer *renderer, struct wc_output *output, 156 | struct wl_list *layers) { 157 | struct wc_layer *layer; 158 | wl_list_for_each_reverse(layer, layers, link) { 159 | if (!layer->mapped) { 160 | continue; 161 | } 162 | struct wc_render_data render_data = { 163 | .renderer = renderer, 164 | .damage = damage, 165 | .when = now, 166 | }; 167 | struct wc_layer_render_data rdata = { 168 | .layer = layer, 169 | .render_data = render_data, 170 | }; 171 | 172 | wlr_layer_surface_v1_for_each_surface( 173 | layer->layer_surface, wc_render_layer, &rdata); 174 | } 175 | } 176 | 177 | static void wc_output_frame(struct wl_listener *listener, void *data) { 178 | struct wc_output *output = wl_container_of(listener, output, frame); 179 | struct wc_server *server = output->server; 180 | struct wlr_output *wlr_output = output->wlr_output; 181 | struct wlr_renderer *renderer = 182 | wlr_backend_get_renderer(wlr_output->backend); 183 | assert(renderer); 184 | 185 | struct timespec now; 186 | clock_gettime(CLOCK_MONOTONIC, &now); 187 | 188 | bool needs_swap = false; 189 | pixman_region32_t damage; 190 | pixman_region32_init(&damage); 191 | if (!wlr_output_damage_attach_render( 192 | output->damage, &needs_swap, &damage)) { 193 | return; 194 | } 195 | 196 | if (!needs_swap) { 197 | goto damage_finish; 198 | } 199 | 200 | wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); 201 | 202 | if (!pixman_region32_not_empty(&damage)) { 203 | goto renderer_end; 204 | } 205 | 206 | if (WC_DEBUG) { 207 | wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1}); 208 | } 209 | 210 | float background_color[4] = {0.0f, 0.0f, 0.0f, 1}; 211 | int nrects; 212 | pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); 213 | for (int i = 0; i < nrects; i++) { 214 | scissor_output(output->wlr_output, &rects[i]); 215 | wlr_renderer_clear(renderer, background_color); 216 | } 217 | 218 | struct wl_list *backgrounds = 219 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]; 220 | struct wl_list *bottom = &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]; 221 | struct wl_list *top = &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]; 222 | struct wl_list *overlay = 223 | &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]; 224 | 225 | wc_render_layers(&now, &damage, renderer, output, backgrounds); 226 | wc_render_layers(&now, &damage, renderer, output, bottom); 227 | 228 | // Render traditional shell surfaces between bottom and top layers. 229 | struct wc_view *view; 230 | wl_list_for_each_reverse(view, &server->views, link) { 231 | if (!view->mapped) { 232 | continue; 233 | } 234 | struct wc_render_data render_data = { 235 | .renderer = renderer, 236 | .damage = &damage, 237 | .when = &now, 238 | }; 239 | struct wc_view_render_data rdata = { 240 | .output = output->wlr_output, 241 | .view = view, 242 | .render_data = render_data, 243 | }; 244 | 245 | wc_view_for_each_surface(view, wc_render_view, &rdata); 246 | } 247 | 248 | wc_render_layers(&now, &damage, renderer, output, top); 249 | wc_render_layers(&now, &damage, renderer, output, overlay); 250 | 251 | renderer_end: 252 | wlr_output_render_software_cursors(wlr_output, &damage); 253 | wlr_renderer_scissor(renderer, NULL); 254 | wlr_renderer_end(renderer); 255 | 256 | int width, height; 257 | wlr_output_transformed_resolution(wlr_output, &width, &height); 258 | 259 | if (WC_DEBUG) { 260 | pixman_region32_union_rect(&damage, &damage, 0, 0, width, height); 261 | } 262 | 263 | enum wl_output_transform transform = 264 | wlr_output_transform_invert(wlr_output->transform); 265 | wlr_region_transform(&damage, &damage, transform, width, height); 266 | wlr_output_set_damage(wlr_output, &damage); 267 | wlr_output_commit(wlr_output); 268 | 269 | damage_finish: 270 | pixman_region32_fini(&damage); 271 | } 272 | 273 | static void wc_output_destroy(struct wl_listener *listener, void *data) { 274 | struct wc_output *output = wl_container_of(listener, output, destroy); 275 | struct wc_server *server = output->server; 276 | wl_list_remove(&output->link); 277 | 278 | wl_list_remove(&output->frame.link); 279 | wl_list_remove(&output->destroy.link); 280 | 281 | if (server->active_output == output) { 282 | server->active_output = NULL; 283 | if (!wl_list_empty(&server->outputs)) { 284 | server->active_output = wl_container_of( 285 | server->outputs.prev, server->active_output, link); 286 | } 287 | } 288 | 289 | size_t len = sizeof(output->layers) / sizeof(output->layers[0]); 290 | for (size_t i = 0; i < len; i++) { 291 | struct wc_layer *layer; 292 | struct wc_layer *temp; 293 | wl_list_for_each_safe(layer, temp, &output->layers[i], link) { 294 | wc_layer_shell_destroy(&layer->destroy, NULL); 295 | } 296 | } 297 | 298 | if (server->output_layout != NULL) { 299 | wlr_output_layout_remove(server->output_layout, output->wlr_output); 300 | } 301 | wlr_output_destroy_global(output->wlr_output); 302 | 303 | free(output); 304 | } 305 | 306 | static void wc_new_output(struct wl_listener *listener, void *data) { 307 | struct wc_server *server = wl_container_of(listener, server, new_output); 308 | struct wlr_output *wlr_output = data; 309 | 310 | if (!wl_list_empty(&wlr_output->modes)) { 311 | struct wlr_output_mode *mode = 312 | wl_container_of(wlr_output->modes.prev, mode, link); 313 | wlr_output_set_mode(wlr_output, mode); 314 | } 315 | 316 | struct wc_output *output = calloc(1, sizeof(struct wc_output)); 317 | output->wlr_output = wlr_output; 318 | output->server = server; 319 | wlr_output->data = output; 320 | output->damage = wlr_output_damage_create(wlr_output); 321 | 322 | size_t len = sizeof(output->layers) / sizeof(output->layers[0]); 323 | for (size_t i = 0; i < len; i++) { 324 | wl_list_init(&output->layers[i]); 325 | } 326 | 327 | output->frame.notify = wc_output_frame; 328 | output->destroy.notify = wc_output_destroy; 329 | 330 | wl_signal_add(&output->damage->events.frame, &output->frame); 331 | wl_signal_add(&wlr_output->events.destroy, &output->destroy); 332 | 333 | wl_list_insert(&server->outputs, &output->link); 334 | 335 | if (server->active_output == NULL) { 336 | server->active_output = output; 337 | } 338 | 339 | wlr_output_layout_add_auto(server->output_layout, wlr_output); 340 | wlr_output_create_global(wlr_output); 341 | 342 | wc_layer_shell_arrange_layers(output); 343 | struct wc_output *temp; 344 | wl_list_for_each_safe(output, temp, &server->outputs, link) { 345 | wlr_output_damage_add_whole(output->damage); 346 | } 347 | } 348 | 349 | struct wc_output *wc_get_active_output(struct wc_server *server) { 350 | if (wl_list_empty(&server->outputs)) { 351 | return NULL; 352 | } 353 | struct wc_output *output = server->active_output; 354 | if (output == NULL) { 355 | output = wl_container_of(server->outputs.prev, output, link); 356 | } 357 | return output; 358 | } 359 | 360 | void wc_output_damage_surface(struct wc_output *output, 361 | struct wlr_surface *surface, pixman_region32_t *surface_damage, 362 | struct wlr_box surface_output_geo) { 363 | struct wc_surface_damage_data damage_data = { 364 | .output = output, 365 | .surface_output_geo = surface_output_geo, 366 | .surface_damage = surface_damage, 367 | }; 368 | wlr_surface_for_each_surface( 369 | surface, damage_surface_iterator, &damage_data); 370 | } 371 | 372 | void wc_output_init(struct wc_server *server) { 373 | server->output_layout = wlr_output_layout_create(); 374 | 375 | wl_list_init(&server->outputs); 376 | server->new_output.notify = wc_new_output; 377 | 378 | wl_signal_add(&server->backend->events.new_output, &server->new_output); 379 | } 380 | 381 | void wc_output_fini(struct wc_server *server) { 382 | struct wc_output *output; 383 | struct wc_output *temp; 384 | wl_list_for_each_safe(output, temp, &server->outputs, link) { 385 | wc_output_destroy(&output->destroy, NULL); 386 | } 387 | 388 | wlr_output_layout_destroy(server->output_layout); 389 | server->output_layout = NULL; 390 | 391 | wl_list_remove(&server->new_output.link); 392 | } 393 | -------------------------------------------------------------------------------- /way-cooler/output.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_OUTPUT_H 2 | #define WC_OUTPUT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "server.h" 10 | 11 | struct wc_output { 12 | struct wl_list link; 13 | struct wc_server *server; 14 | 15 | struct wlr_output *wlr_output; 16 | struct wlr_output_damage *damage; 17 | 18 | struct wl_list layers[4]; 19 | 20 | struct wl_listener destroy; 21 | struct wl_listener frame; 22 | }; 23 | 24 | void wc_output_init(struct wc_server *server); 25 | 26 | void wc_output_fini(struct wc_server *server); 27 | 28 | // Gets the output that was last active (e.g. last had user activity). 29 | // 30 | // If there are no outputs, NULL is returned. If there has been no activity, 31 | // the first output in the list is returned. 32 | struct wc_output *wc_get_active_output(struct wc_server *server); 33 | 34 | /// Damages the surface which is at the given output coordinates. 35 | /// 36 | /// If surface_damage is NULL the entire surface is damaged using the 37 | /// geometry provided in surface_output_geo. 38 | void wc_output_damage_surface(struct wc_output *output, 39 | struct wlr_surface *surface, pixman_region32_t *surface_damage, 40 | struct wlr_box surface_output_geo); 41 | 42 | #endif // WC_OUTPUT_H 43 | -------------------------------------------------------------------------------- /way-cooler/pointer.c: -------------------------------------------------------------------------------- 1 | #include "pointer.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "cursor.h" 11 | 12 | static void wc_pointer_removed(struct wl_listener *listener, void *data) { 13 | struct wc_pointer *pointer = wl_container_of(listener, pointer, destroy); 14 | wl_list_remove(&pointer->link); 15 | 16 | wl_list_remove(&pointer->destroy.link); 17 | 18 | free(pointer); 19 | } 20 | 21 | void wc_new_pointer(struct wc_server *server, struct wlr_input_device *device) { 22 | struct wc_pointer *pointer = calloc(1, sizeof(struct wc_pointer)); 23 | 24 | pointer->server = server; 25 | pointer->device = device; 26 | 27 | pointer->destroy.notify = wc_pointer_removed; 28 | 29 | wl_signal_add(&device->events.destroy, &pointer->destroy); 30 | 31 | wl_list_insert(&server->pointers, &pointer->link); 32 | 33 | wlr_cursor_attach_input_device(server->cursor->wlr_cursor, device); 34 | 35 | /* 36 | * TODO We should make this a configuration option, 37 | * I'm just putting this here to keep me sane (fuck mouse acceleration). 38 | */ 39 | struct libinput_device *libinput_device = 40 | wlr_libinput_get_device_handle(device); 41 | if (libinput_device != NULL && wlr_input_device_is_libinput(device)) { 42 | libinput_device_config_accel_set_profile( 43 | libinput_device, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); 44 | libinput_device_config_accel_set_speed(libinput_device, 0.0); 45 | } 46 | } 47 | 48 | void wc_pointers_init(struct wc_server *server) { 49 | wl_list_init(&server->pointers); 50 | } 51 | 52 | void wc_pointers_fini(struct wc_server *server) { 53 | struct wc_pointer *pointer; 54 | struct wc_pointer *temp; 55 | wl_list_for_each_safe(pointer, temp, &server->pointers, link) { 56 | wc_pointer_removed(&pointer->destroy, NULL); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /way-cooler/pointer.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_POINTER_H 2 | #define WC_POINTER_H 3 | 4 | #include "wlr/types/wlr_input_device.h" 5 | 6 | #include "server.h" 7 | 8 | struct wc_pointer { 9 | struct wl_list link; 10 | struct wc_server *server; 11 | 12 | struct wlr_input_device *device; 13 | 14 | struct wl_listener destroy; 15 | }; 16 | 17 | void wc_pointers_init(struct wc_server *server); 18 | 19 | void wc_pointers_fini(struct wc_server *server); 20 | 21 | void wc_new_pointer(struct wc_server *server, struct wlr_input_device *device); 22 | 23 | #endif // WC_POINTER_H 24 | -------------------------------------------------------------------------------- /way-cooler/seat.c: -------------------------------------------------------------------------------- 1 | #include "seat.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "cursor.h" 10 | #include "server.h" 11 | 12 | static void wc_seat_request_cursor(struct wl_listener *listener, void *data) { 13 | struct wc_seat *seat = wl_container_of(listener, seat, request_set_cursor); 14 | struct wc_server *server = seat->server; 15 | struct wc_cursor *cursor = server->cursor; 16 | struct wlr_seat_pointer_request_set_cursor_event *event = data; 17 | struct wlr_seat_client *focused_client = 18 | server->seat->seat->pointer_state.focused_client; 19 | 20 | if (focused_client == event->seat_client) { 21 | wc_cursor_set_client_cursor(cursor, event); 22 | } 23 | } 24 | 25 | void wc_seat_update_surface_focus(struct wc_seat *seat, 26 | struct wlr_surface *surface, double sx, double sy, uint32_t time) { 27 | struct wlr_seat *wlr_seat = seat->seat; 28 | struct wc_server *server = seat->server; 29 | 30 | if (surface == NULL || server->mouse_grab) { 31 | wlr_seat_pointer_clear_focus(wlr_seat); 32 | return; 33 | } 34 | 35 | bool focused_changed = wlr_seat->pointer_state.focused_surface != surface; 36 | wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); 37 | if (!focused_changed) { 38 | wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); 39 | } 40 | } 41 | 42 | void wc_seat_set_focus_layer( 43 | struct wc_seat *seat, struct wlr_layer_surface_v1 *layer) { 44 | // TODO 45 | } 46 | 47 | void wc_seat_init(struct wc_server *server) { 48 | struct wc_seat *seat = calloc(1, sizeof(struct wc_seat)); 49 | seat->server = server; 50 | seat->seat = wlr_seat_create(server->wl_display, "seat0"); 51 | 52 | seat->request_set_cursor.notify = wc_seat_request_cursor; 53 | 54 | wl_signal_add( 55 | &seat->seat->events.request_set_cursor, &seat->request_set_cursor); 56 | 57 | wlr_xwayland_set_seat(server->xwayland, seat->seat); 58 | 59 | server->seat = seat; 60 | } 61 | 62 | void wc_seat_fini(struct wc_server *server) { 63 | struct wc_seat *seat = server->seat; 64 | 65 | wlr_xwayland_set_seat(server->xwayland, NULL); 66 | 67 | free(seat); 68 | server->seat = NULL; 69 | } 70 | -------------------------------------------------------------------------------- /way-cooler/seat.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_SEAT_H 2 | #define WC_SEAT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "server.h" 9 | 10 | struct wc_seat { 11 | struct wc_server *server; 12 | struct wlr_seat *seat; 13 | 14 | struct wl_listener request_set_cursor; 15 | }; 16 | 17 | void wc_seat_init(struct wc_server *server); 18 | 19 | void wc_seat_fini(struct wc_server *server); 20 | 21 | // Updates the seat's focus based on the surface. If surface is NULL the focus 22 | // is cleared. 23 | void wc_seat_update_surface_focus(struct wc_seat *seat, 24 | struct wlr_surface *surface, double sx, double sy, uint32_t time); 25 | 26 | void wc_seat_set_focus_layer( 27 | struct wc_seat *seat, struct wlr_layer_surface_v1 *layer); 28 | 29 | #endif // WC_SEAT_H 30 | -------------------------------------------------------------------------------- /way-cooler/server.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | 3 | #include "server.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "cursor.h" 24 | #include "input.h" 25 | #include "keybindings.h" 26 | #include "layer_shell.h" 27 | #include "mousegrabber.h" 28 | #include "output.h" 29 | #include "seat.h" 30 | #include "view.h" 31 | #include "xwayland.h" 32 | 33 | static void startup_command_killed(struct wl_listener *listener, void *data) { 34 | struct wc_server *server = 35 | wl_container_of(listener, server, startup_client_destroyed); 36 | wlr_log(WLR_INFO, "Startup command killed"); 37 | // TODO Something sophisticated - restart the client, shutdown, etc. 38 | } 39 | 40 | bool init_server(struct wc_server *server) { 41 | if (server == NULL) { 42 | return false; 43 | } 44 | 45 | server->wl_display = wl_display_create(); 46 | server->wayland_socket = wl_display_add_socket_auto(server->wl_display); 47 | if (!server->wayland_socket) { 48 | wlr_backend_destroy(server->backend); 49 | return false; 50 | } 51 | 52 | server->backend = wlr_backend_autocreate(server->wl_display, NULL); 53 | server->renderer = wlr_backend_get_renderer(server->backend); 54 | wlr_renderer_init_wl_display(server->renderer, server->wl_display); 55 | server->compositor = 56 | wlr_compositor_create(server->wl_display, server->renderer); 57 | if (server->compositor == NULL) { 58 | return false; 59 | } 60 | 61 | server->screencopy_manager = 62 | wlr_screencopy_manager_v1_create(server->wl_display); 63 | server->data_device_manager = 64 | wlr_data_device_manager_create(server->wl_display); 65 | 66 | wc_xwayland_init(server); 67 | wc_seat_init(server); 68 | wc_output_init(server); 69 | wc_inputs_init(server); 70 | wc_views_init(server); 71 | wc_layers_init(server); 72 | wc_cursor_init(server); 73 | 74 | // XXX This must be initialized after the output layout 75 | server->xdg_output_manager = wlr_xdg_output_manager_v1_create( 76 | server->wl_display, server->output_layout); 77 | 78 | wc_mousegrabber_init(server); 79 | wc_keybindings_init(server); 80 | 81 | return true; 82 | } 83 | 84 | void fini_server(struct wc_server *server) { 85 | // TODO Why is this segfaulting compositor closing? 86 | /* 87 | wc_seat_fini(server); 88 | wc_output_fini(server); 89 | wc_inputs_fini(server); 90 | wc_views_fini(server); 91 | wc_layers_fini(server); 92 | wc_cursor_fini(server); 93 | 94 | wlr_screencopy_manager_v1_destroy(server->screencopy_manager); 95 | wlr_data_device_manager_destroy(server->data_device_manager); 96 | wlr_xdg_output_manager_v1_destroy(server->xdg_output_manager); 97 | 98 | wc_mousegrabber_fini(server); 99 | wc_keybindings_fini(server); 100 | */ 101 | 102 | wc_xwayland_fini(server); 103 | wl_display_destroy_clients(server->wl_display); 104 | wl_display_destroy(server->wl_display); 105 | } 106 | 107 | static bool set_cloexec(int fd, bool cloexec) { 108 | int flags = fcntl(fd, F_GETFD); 109 | if (flags == -1) { 110 | goto failed; 111 | } 112 | if (cloexec) { 113 | flags = flags | FD_CLOEXEC; 114 | } else { 115 | flags = flags & ~FD_CLOEXEC; 116 | } 117 | if (fcntl(fd, F_SETFD, flags) == -1) { 118 | goto failed; 119 | } 120 | return true; 121 | failed: 122 | wlr_log(WLR_ERROR, "fcntl failed"); 123 | return false; 124 | } 125 | 126 | void wc_server_execute_startup_command(struct wc_server *server) { 127 | int sockets[2]; 128 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) { 129 | wlr_log(WLR_ERROR, "Failed to create client wayland socket pair"); 130 | abort(); 131 | } 132 | if (!set_cloexec(sockets[0], true) || !set_cloexec(sockets[1], true)) { 133 | wlr_log(WLR_ERROR, "Failed to set exec flag for socket"); 134 | abort(); 135 | } 136 | server->startup_client = wl_client_create(server->wl_display, sockets[0]); 137 | if (server->startup_client == NULL) { 138 | wlr_log(WLR_ERROR, "Could not create startup wl_client"); 139 | abort(); 140 | } 141 | server->startup_client_destroyed.notify = startup_command_killed; 142 | wl_client_add_destroy_listener( 143 | server->startup_client, &server->startup_client_destroyed); 144 | 145 | wlr_log(WLR_INFO, "Executing \"%s\"", server->startup_cmd); 146 | pid_t pid = fork(); 147 | if (pid < 0) { 148 | wlr_log(WLR_ERROR, "Failed to fork for startup command"); 149 | abort(); 150 | } else if (pid == 0) { 151 | /* Child process. Will be used to prevent zombie processes by 152 | killing its parent and having init be its new parent. 153 | */ 154 | pid = fork(); 155 | if (pid < 0) { 156 | wlr_log(WLR_ERROR, "Failed to fork for second time"); 157 | abort(); 158 | } else if (pid == 0) { 159 | if (!set_cloexec(sockets[1], false)) { 160 | wlr_log(WLR_ERROR, 161 | "Could not unset close exec flag for forked child"); 162 | abort(); 163 | } 164 | char wayland_socket_str[16]; 165 | snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", 166 | sockets[1]); 167 | setenv("WAYLAND_SOCKET", wayland_socket_str, true); 168 | execl("/bin/sh", "/bin/sh", "-c", server->startup_cmd, NULL); 169 | wlr_log(WLR_ERROR, "exec failed"); 170 | exit(1); 171 | } 172 | exit(0); 173 | } 174 | close(sockets[1]); 175 | } 176 | -------------------------------------------------------------------------------- /way-cooler/server.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_SERVER_H 2 | #define WC_SERVER_H 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 | 18 | int WC_DEBUG; 19 | 20 | struct wc_server { 21 | const char *wayland_socket; 22 | struct wl_display *wl_display; 23 | struct wlr_backend *backend; 24 | struct wlr_renderer *renderer; 25 | struct wlr_compositor *compositor; 26 | 27 | const char *startup_cmd; 28 | struct wl_client *startup_client; 29 | struct wl_listener startup_client_destroyed; 30 | 31 | struct wlr_xcursor_manager *xcursor_mgr; 32 | struct wc_cursor *cursor; 33 | 34 | struct wc_seat *seat; 35 | 36 | struct wl_list keyboards; 37 | struct wl_list pointers; 38 | struct wl_listener new_input; 39 | 40 | struct wlr_output_layout *output_layout; 41 | struct wc_output *active_output; 42 | struct wl_list outputs; 43 | struct wl_listener new_output; 44 | 45 | struct wl_list views; 46 | 47 | struct wlr_xwayland *xwayland; 48 | struct wl_listener new_xwayland_surface; 49 | struct wl_listener xwayland_ready; 50 | 51 | struct wlr_xdg_shell *xdg_shell; 52 | struct wl_listener new_xdg_surface; 53 | 54 | struct wlr_layer_shell_v1 *layer_shell; 55 | struct wl_listener new_layer_surface; 56 | 57 | struct wlr_screencopy_manager_v1 *screencopy_manager; 58 | struct wlr_data_device_manager *data_device_manager; 59 | struct wlr_xdg_output_manager_v1 *xdg_output_manager; 60 | 61 | struct wc_mousegrabber *mousegrabber; 62 | struct wc_keybindings *keybindings; 63 | bool mouse_grab; 64 | }; 65 | 66 | bool init_server(struct wc_server *server); 67 | void fini_server(struct wc_server *server); 68 | void wc_server_execute_startup_command(struct wc_server *server); 69 | 70 | #endif // WC_SERVER_H 71 | -------------------------------------------------------------------------------- /way-cooler/view.c: -------------------------------------------------------------------------------- 1 | #include "view.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cursor.h" 12 | #include "layer_shell.h" 13 | #include "output.h" 14 | #include "seat.h" 15 | #include "server.h" 16 | #include "xdg.h" 17 | #include "xwayland.h" 18 | 19 | static bool wc_is_view_at(struct wc_view *view, double lx, double ly, 20 | double *out_sx, double *out_sy, struct wlr_surface **out_surface) { 21 | int view_sx = lx - view->geo.x; 22 | int view_sy = ly - view->geo.y; 23 | 24 | *out_surface = NULL; 25 | switch (view->surface_type) { 26 | case WC_XDG: 27 | *out_surface = wlr_xdg_surface_surface_at( 28 | view->xdg_surface, view_sx, view_sy, out_sx, out_sy); 29 | break; 30 | case WC_XWAYLAND: 31 | if (view->xwayland_surface->surface != NULL) { 32 | *out_surface = 33 | wlr_surface_surface_at(view->xwayland_surface->surface, 34 | view_sx, view_sy, out_sx, out_sy); 35 | } 36 | break; 37 | } 38 | return *out_surface != NULL; 39 | } 40 | 41 | void wc_view_get_outputs(struct wlr_output_layout *layout, struct wc_view *view, 42 | struct wlr_output **out_outputs) { 43 | struct wlr_box geo = {0}; 44 | memcpy(&geo, &view->geo, sizeof(struct wlr_box)); 45 | 46 | size_t next_index = 0; 47 | // top left 48 | out_outputs[next_index++] = 49 | wlr_output_layout_output_at(layout, geo.x, geo.y); 50 | // top right 51 | out_outputs[next_index++] = 52 | wlr_output_layout_output_at(layout, geo.x + geo.width, geo.y); 53 | // bottom left 54 | out_outputs[next_index++] = 55 | wlr_output_layout_output_at(layout, geo.x, geo.y + geo.height); 56 | // bottom right 57 | out_outputs[next_index++] = wlr_output_layout_output_at( 58 | layout, geo.x + geo.width, geo.y + geo.height); 59 | 60 | for (size_t i = 1; i < 4; i++) { 61 | for (size_t j = 0; j < i; j++) { 62 | if (out_outputs[i] == out_outputs[j]) { 63 | out_outputs[i] = NULL; 64 | } 65 | } 66 | } 67 | } 68 | 69 | struct wlr_surface *wc_view_surface(struct wc_view *view) { 70 | switch (view->surface_type) { 71 | case WC_XDG: 72 | return view->xdg_surface->surface; 73 | case WC_XWAYLAND: 74 | return view->xwayland_surface->surface; 75 | } 76 | return NULL; 77 | } 78 | 79 | void wc_view_move(struct wc_view *view, struct wlr_box geo) { 80 | struct wc_server *server = view->server; 81 | struct wlr_surface *focused_surface = 82 | server->seat->seat->pointer_state.focused_surface; 83 | struct wlr_surface *surface = wc_view_surface(view); 84 | 85 | if (surface != focused_surface) { 86 | return; 87 | } 88 | 89 | struct wc_cursor *cursor = server->cursor; 90 | struct wlr_cursor *wlr_cursor = cursor->wlr_cursor; 91 | 92 | cursor->cursor_mode = WC_CURSOR_MOVE; 93 | cursor->grabbed.view = view; 94 | cursor->grabbed.original_x = wlr_cursor->x - view->geo.x; 95 | cursor->grabbed.original_y = wlr_cursor->y - view->geo.y; 96 | 97 | cursor->grabbed.original_view_geo.x = view->geo.x; 98 | cursor->grabbed.original_view_geo.y = view->geo.y; 99 | cursor->grabbed.original_view_geo.width = geo.width; 100 | cursor->grabbed.original_view_geo.height = geo.height; 101 | } 102 | 103 | void wc_view_resize(struct wc_view *view, struct wlr_box geo, uint32_t edges) { 104 | struct wc_server *server = view->server; 105 | struct wc_cursor *cursor = server->cursor; 106 | struct wlr_cursor *wlr_cursor = cursor->wlr_cursor; 107 | struct wlr_surface *focused_surface = 108 | server->seat->seat->pointer_state.focused_surface; 109 | struct wlr_surface *surface = wc_view_surface(view); 110 | 111 | if (surface != focused_surface) { 112 | return; 113 | } 114 | 115 | cursor->cursor_mode = WC_CURSOR_RESIZE; 116 | cursor->grabbed.view = view; 117 | cursor->grabbed.original_x = wlr_cursor->x; 118 | cursor->grabbed.original_y = wlr_cursor->y; 119 | 120 | cursor->grabbed.original_view_geo.x = view->geo.x; 121 | cursor->grabbed.original_view_geo.y = view->geo.y; 122 | cursor->grabbed.original_view_geo.width = geo.width; 123 | cursor->grabbed.original_view_geo.height = geo.height; 124 | 125 | cursor->grabbed.resize_edges = edges; 126 | } 127 | 128 | void wc_view_update_geometry(struct wc_view *view, struct wlr_box new_geo) { 129 | switch (view->surface_type) { 130 | case WC_XDG: 131 | view->pending_serial = wlr_xdg_toplevel_set_size( 132 | view->xdg_surface, new_geo.width, new_geo.height); 133 | view->is_pending_serial = true; 134 | break; 135 | case WC_XWAYLAND: 136 | view->pending_serial = 1; 137 | wlr_xwayland_surface_configure(view->xwayland_surface, new_geo.x, 138 | new_geo.y, new_geo.width, new_geo.height); 139 | break; 140 | } 141 | 142 | memcpy(&view->pending_geometry, &new_geo, sizeof(struct wlr_box)); 143 | } 144 | 145 | void wc_view_damage(struct wc_view *view, pixman_region32_t *damage) { 146 | struct wlr_output *outputs[4] = {0}; 147 | wc_view_get_outputs(view->server->output_layout, view, outputs); 148 | 149 | // Keep a copy of the damage because otherwise it gets screwed up 150 | // in the presence of multiple outputs. 151 | pixman_region32_t damage_copy; 152 | pixman_region32_init(&damage_copy); 153 | if (damage != NULL) { 154 | pixman_region32_copy(&damage_copy, damage); 155 | } 156 | struct wlr_surface *surface = NULL; 157 | switch (view->surface_type) { 158 | case WC_XDG: 159 | surface = view->xdg_surface->surface; 160 | break; 161 | case WC_XWAYLAND: 162 | surface = view->xwayland_surface->surface; 163 | break; 164 | } 165 | if (surface == NULL) { 166 | return; 167 | } 168 | 169 | for (size_t i = 0; i < 4; i++) { 170 | struct wlr_output *output = outputs[i]; 171 | struct wlr_box *output_box = 172 | wlr_output_layout_get_box(view->server->output_layout, output); 173 | 174 | if (output) { 175 | struct wlr_box view_output_geo = { 176 | .x = view->geo.x - output_box->x, 177 | .y = view->geo.y - output_box->y, 178 | .width = view->geo.width, 179 | .height = view->geo.height, 180 | }; 181 | if (damage != NULL) { 182 | pixman_region32_translate( 183 | damage, view_output_geo.x, view_output_geo.y); 184 | wc_output_damage_surface( 185 | output->data, surface, damage, view_output_geo); 186 | pixman_region32_copy(damage, &damage_copy); 187 | } else { 188 | wc_output_damage_surface( 189 | output->data, surface, damage, view_output_geo); 190 | } 191 | } 192 | } 193 | pixman_region32_fini(&damage_copy); 194 | } 195 | 196 | void wc_view_damage_whole(struct wc_view *view) { 197 | wc_view_damage(view, NULL); 198 | } 199 | 200 | void wc_view_commit(struct wc_view *view, struct wlr_box geo) { 201 | if (!view->mapped) { 202 | return; 203 | } 204 | 205 | struct wlr_surface *surface = NULL; 206 | switch (view->surface_type) { 207 | case WC_XDG: 208 | surface = view->xdg_surface->surface; 209 | break; 210 | case WC_XWAYLAND: 211 | surface = view->xwayland_surface->surface; 212 | break; 213 | } 214 | if (surface == NULL) { 215 | return; 216 | } 217 | 218 | pixman_region32_t damage; 219 | pixman_region32_init(&damage); 220 | wlr_surface_get_effective_damage(surface, &damage); 221 | wc_view_damage(view, &damage); 222 | 223 | bool size_changed = view->geo.width != surface->current.width || 224 | view->geo.height != surface->current.height; 225 | 226 | if (size_changed) { 227 | wc_view_damage_whole(view); 228 | view->geo.width = surface->current.width; 229 | view->geo.height = surface->current.height; 230 | wc_view_damage_whole(view); 231 | } 232 | 233 | uint32_t pending_serial = view->pending_serial; 234 | switch (view->surface_type) { 235 | case WC_XDG: 236 | if (pending_serial > 0 && 237 | pending_serial >= view->xdg_surface->configure_serial) { 238 | wc_view_damage_whole(view); 239 | 240 | if (view->pending_geometry.x != view->geo.x) { 241 | view->geo.x = view->pending_geometry.x + 242 | view->pending_geometry.width - geo.width; 243 | } 244 | if (view->pending_geometry.y != view->geo.y) { 245 | view->geo.y = view->pending_geometry.y + 246 | view->pending_geometry.height - geo.height; 247 | } 248 | 249 | wc_view_damage_whole(view); 250 | 251 | if (pending_serial == view->xdg_surface->configure_serial) { 252 | view->pending_serial = 0; 253 | view->is_pending_serial = false; 254 | } 255 | } 256 | break; 257 | case WC_XWAYLAND: 258 | if (pending_serial > 0) { 259 | wc_view_damage_whole(view); 260 | 261 | if (view->pending_geometry.x != view->geo.x) { 262 | view->geo.x = view->pending_geometry.x + 263 | view->pending_geometry.width - geo.width; 264 | } 265 | if (view->pending_geometry.y != view->geo.y) { 266 | view->geo.y = view->pending_geometry.y + 267 | view->pending_geometry.height - geo.height; 268 | } 269 | 270 | wc_view_damage_whole(view); 271 | view->pending_serial = 0; 272 | } 273 | break; 274 | } 275 | pixman_region32_fini(&damage); 276 | } 277 | 278 | struct wc_view *wc_view_at(struct wc_server *server, double lx, double ly, 279 | double *out_sx, double *out_sy, struct wlr_surface **out_surface) { 280 | assert(out_surface != NULL); 281 | 282 | struct wc_view *view; 283 | wl_list_for_each(view, &server->views, link) { 284 | if (wc_is_view_at(view, lx, ly, out_sx, out_sy, out_surface)) { 285 | return view; 286 | } 287 | } 288 | return NULL; 289 | } 290 | 291 | void wc_focus_view(struct wc_view *view) { 292 | assert(view != NULL); 293 | 294 | struct wc_server *server = view->server; 295 | struct wlr_surface *surface = wc_view_surface(view); 296 | struct wlr_seat *seat = server->seat->seat; 297 | struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; 298 | if (prev_surface == surface) { 299 | return; 300 | } 301 | 302 | if (prev_surface && wlr_surface_is_xdg_surface(prev_surface)) { 303 | struct wlr_xdg_surface *previous = 304 | wlr_xdg_surface_from_wlr_surface(prev_surface); 305 | wlr_xdg_toplevel_set_activated(previous, false); 306 | } 307 | 308 | /* Move the view to the front */ 309 | wl_list_remove(&view->link); 310 | wl_list_insert(&server->views, &view->link); 311 | wc_view_damage_whole(view); 312 | 313 | switch (view->surface_type) { 314 | case WC_XDG: 315 | wlr_xdg_toplevel_set_activated(view->xdg_surface, true); 316 | break; 317 | case WC_XWAYLAND: 318 | wlr_xwayland_surface_activate(view->xwayland_surface, true); 319 | break; 320 | } 321 | 322 | struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); 323 | if (keyboard != NULL) { 324 | wlr_seat_keyboard_notify_enter(seat, surface, keyboard->keycodes, 325 | keyboard->num_keycodes, &keyboard->modifiers); 326 | } 327 | } 328 | 329 | void wc_views_init(struct wc_server *server) { 330 | wl_list_init(&server->views); 331 | wc_xdg_init(server); 332 | } 333 | 334 | void wc_views_fini(struct wc_server *server) { 335 | struct wc_view *view; 336 | struct wc_view *temp; 337 | wl_list_for_each_safe(view, temp, &server->views, link) { 338 | switch (view->surface_type) { 339 | case WC_XDG: 340 | wc_xdg_surface_destroy(&view->destroy, NULL); 341 | break; 342 | case WC_XWAYLAND: 343 | wc_xwayland_surface_destroy(&view->destroy, NULL); 344 | break; 345 | } 346 | } 347 | 348 | wc_xdg_fini(server); 349 | } 350 | 351 | void wc_view_for_each_surface(struct wc_view *view, 352 | wlr_surface_iterator_func_t iterator, void *data) { 353 | switch (view->surface_type) { 354 | case WC_XDG: 355 | wlr_xdg_surface_for_each_surface(view->xdg_surface, iterator, data); 356 | break; 357 | case WC_XWAYLAND: { 358 | struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface; 359 | iterator(xwayland_surface->surface, 0, 0, data); 360 | break; 361 | } 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /way-cooler/view.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_VIEW_H 2 | #define WC_VIEW_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "server.h" 13 | 14 | enum wc_surface_type { 15 | WC_XDG, 16 | WC_XWAYLAND, 17 | }; 18 | 19 | struct wc_view { 20 | struct wl_list link; 21 | struct wc_server *server; 22 | 23 | enum wc_surface_type surface_type; 24 | union { 25 | struct wlr_xdg_surface *xdg_surface; 26 | struct wlr_xwayland_surface *xwayland_surface; 27 | }; 28 | 29 | bool mapped; 30 | 31 | /* Current coordinates of the view. 32 | * 33 | * NOTE The width and height may not reflect what the client currently 34 | * thinks, but this is only temporary - when you change these you _must_ 35 | * notify the client of its new size. 36 | */ 37 | struct wlr_box geo; 38 | 39 | // Serial for a pending move / resize. 40 | uint32_t pending_serial; 41 | bool is_pending_serial; 42 | /* NOTE Do not use the width and height for damage calculation except when 43 | * calculating x and y. 44 | * 45 | * Use the surface's wlr_surface.current field for the damage's width and 46 | * height. 47 | */ 48 | struct wlr_box pending_geometry; 49 | 50 | struct wl_listener map; 51 | struct wl_listener unmap; 52 | struct wl_listener commit; 53 | struct wl_listener destroy; 54 | struct wl_listener request_move; 55 | struct wl_listener request_resize; 56 | struct wl_listener configure; 57 | }; 58 | 59 | void wc_views_init(struct wc_server *server); 60 | 61 | void wc_views_fini(struct wc_server *server); 62 | 63 | // Add the calculated damage to all the surfaces that make up this view. 64 | void wc_view_damage(struct wc_view *view, pixman_region32_t *damage); 65 | 66 | // Damage the whole view, based on its current geometry. 67 | void wc_view_damage_whole(struct wc_view *view); 68 | 69 | /* Commits damage from the client. 70 | * 71 | * If the size has changed the entire view is automatically damaged. 72 | */ 73 | void wc_view_commit(struct wc_view *view, struct wlr_box geo); 74 | 75 | // Get the main surface associated with the view. 76 | struct wlr_surface *wc_view_surface(struct wc_view *view); 77 | 78 | /* Moves the view to the specified coordinates. 79 | * 80 | * Note that this is done by setting geometry in the cursor and so all 81 | * handling is done in cursor.c 82 | */ 83 | void wc_view_move(struct wc_view *view, struct wlr_box geo); 84 | 85 | /* Resize the view to the specified location. 86 | * Note this is done by setting geometry in the cursor and so all 87 | * handling is done in cursor.c 88 | */ 89 | void wc_view_resize(struct wc_view *view, struct wlr_box geo, uint32_t edges); 90 | 91 | // Set the new geometry of the view to be applied when the client commits to it. 92 | void wc_view_update_geometry(struct wc_view *view, struct wlr_box new_geo); 93 | 94 | /* Finds the topmost (assuming server->views is top-to-bottom) view at the 95 | * specified output layout coordinates. If one cannot be found NULL is returned. 96 | * 97 | * The out_surface parameter is the surface at that point. Note that this might 98 | * be a subsurface of the view and thus that is why it is returned. 99 | * 100 | *If a view is found the surface coordinates are stored in out_sx and out_sy. 101 | */ 102 | struct wc_view *wc_view_at(struct wc_server *server, double lx, double ly, 103 | double *out_sx, double *out_sy, struct wlr_surface **out_surface); 104 | 105 | // Focuses on a view. Automatically un-focuses the previous view. 106 | void wc_focus_view(struct wc_view *view); 107 | 108 | /* Get the outputs that the view is on. 109 | * 110 | * There can be up to four (one for each corner), so the out_outputs should be 111 | * an array of at least 4 (it will zero out the first four). 112 | * 113 | * The order is as follows (with holes being null): top left, top right, bottom 114 | * left, bottom right 115 | * 116 | * Each output is guaranteed to be unique in the array. 117 | */ 118 | void wc_view_get_outputs(struct wlr_output_layout *layout, struct wc_view *view, 119 | struct wlr_output *out_outputs[4]); 120 | 121 | // Apply an iterator function to each surface of the view 122 | void wc_view_for_each_surface( 123 | struct wc_view *view, wlr_surface_iterator_func_t iterator, void *data); 124 | 125 | #endif // WC_VIEW_H 126 | -------------------------------------------------------------------------------- /way-cooler/xdg.c: -------------------------------------------------------------------------------- 1 | #include "xdg.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "cursor.h" 11 | #include "output.h" 12 | #include "seat.h" 13 | #include "server.h" 14 | #include "view.h" 15 | 16 | static void wc_xdg_surface_map(struct wl_listener *listener, void *data) { 17 | struct wc_view *view = wl_container_of(listener, view, map); 18 | view->mapped = true; 19 | wc_focus_view(view); 20 | 21 | struct wlr_xdg_surface *surface = view->xdg_surface; 22 | struct wlr_box box = {0}; 23 | wlr_xdg_surface_get_geometry(surface, &box); 24 | memcpy(&view->geo, &box, sizeof(struct wlr_box)); 25 | 26 | wc_view_damage_whole(view); 27 | } 28 | 29 | static void wc_xdg_surface_unmap(struct wl_listener *listener, void *data) { 30 | struct wc_view *view = wl_container_of(listener, view, unmap); 31 | view->mapped = false; 32 | 33 | wc_view_damage_whole(view); 34 | } 35 | 36 | static void wc_xdg_surface_commit(struct wl_listener *listener, void *data) { 37 | struct wc_view *view = wl_container_of(listener, view, commit); 38 | struct wlr_xdg_surface *xdg_surface = view->xdg_surface; 39 | 40 | struct wlr_box geo = {0}; 41 | wlr_xdg_surface_get_geometry(xdg_surface, &geo); 42 | 43 | wc_view_commit(view, geo); 44 | } 45 | 46 | void wc_xdg_surface_destroy(struct wl_listener *listener, void *data) { 47 | struct wc_view *view = wl_container_of(listener, view, destroy); 48 | wl_list_remove(&view->link); 49 | 50 | wl_list_remove(&view->map.link); 51 | wl_list_remove(&view->unmap.link); 52 | wl_list_remove(&view->commit.link); 53 | wl_list_remove(&view->request_move.link); 54 | wl_list_remove(&view->request_resize.link); 55 | wl_list_remove(&view->destroy.link); 56 | 57 | free(view); 58 | } 59 | 60 | static void wc_xdg_toplevel_request_move( 61 | struct wl_listener *listener, void *data) { 62 | struct wc_view *view = wl_container_of(listener, view, request_move); 63 | 64 | struct wlr_box geo; 65 | wlr_xdg_surface_get_geometry(view->xdg_surface, &geo); 66 | 67 | wc_view_move(view, geo); 68 | } 69 | 70 | static void wc_xdg_toplevel_request_resize( 71 | struct wl_listener *listener, void *data) { 72 | struct wc_view *view = wl_container_of(listener, view, request_resize); 73 | struct wlr_xdg_toplevel_resize_event *event = data; 74 | 75 | struct wlr_box geo; 76 | wlr_xdg_surface_get_geometry(view->xdg_surface, &geo); 77 | 78 | wc_view_resize(view, geo, event->edges); 79 | } 80 | 81 | static void wc_xdg_new_surface(struct wl_listener *listener, void *data) { 82 | struct wc_server *server = 83 | wl_container_of(listener, server, new_xdg_surface); 84 | struct wlr_xdg_surface *xdg_surface = data; 85 | if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { 86 | return; 87 | } 88 | 89 | struct wc_view *view = calloc(1, sizeof(struct wc_view)); 90 | view->server = server; 91 | view->xdg_surface = xdg_surface; 92 | view->surface_type = WC_XDG; 93 | 94 | view->map.notify = wc_xdg_surface_map; 95 | view->unmap.notify = wc_xdg_surface_unmap; 96 | view->commit.notify = wc_xdg_surface_commit; 97 | view->destroy.notify = wc_xdg_surface_destroy; 98 | 99 | wl_signal_add(&xdg_surface->events.map, &view->map); 100 | wl_signal_add(&xdg_surface->events.unmap, &view->unmap); 101 | wl_signal_add(&xdg_surface->surface->events.commit, &view->commit); 102 | wl_signal_add(&xdg_surface->events.destroy, &view->destroy); 103 | 104 | struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel; 105 | view->request_move.notify = wc_xdg_toplevel_request_move; 106 | wl_signal_add(&toplevel->events.request_move, &view->request_move); 107 | view->request_resize.notify = wc_xdg_toplevel_request_resize; 108 | wl_signal_add(&toplevel->events.request_resize, &view->request_resize); 109 | 110 | wl_list_insert(&server->views, &view->link); 111 | } 112 | 113 | void wc_xdg_init(struct wc_server *server) { 114 | server->xdg_shell = wlr_xdg_shell_create(server->wl_display); 115 | server->new_xdg_surface.notify = wc_xdg_new_surface; 116 | wl_signal_add( 117 | &server->xdg_shell->events.new_surface, &server->new_xdg_surface); 118 | } 119 | 120 | void wc_xdg_fini(struct wc_server *server) { 121 | server->xdg_shell = NULL; 122 | 123 | wl_list_remove(&server->new_xdg_surface.link); 124 | } 125 | -------------------------------------------------------------------------------- /way-cooler/xdg.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_XDG_H 2 | #define WC_XDG_H 3 | 4 | #include "server.h" 5 | 6 | void wc_xdg_init(struct wc_server *server); 7 | 8 | void wc_xdg_fini(struct wc_server *server); 9 | 10 | void wc_xdg_surface_destroy(struct wl_listener *listener, void *data); 11 | 12 | #endif // WC_XDG_H 13 | -------------------------------------------------------------------------------- /way-cooler/xkb_hash_set.c: -------------------------------------------------------------------------------- 1 | #include "xkb_hash_set.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | void xkb_hash_set_clear(struct xkb_hash_set *hash_set) { 9 | const size_t hash_set_size = 10 | sizeof(hash_set->set) / sizeof(hash_set->set[0]); 11 | for (size_t i = 0; i < hash_set_size; i++) { 12 | struct hash_entry *entry = hash_set->set[i].next; 13 | struct hash_entry *next; 14 | while (entry != NULL) { 15 | next = entry->next; 16 | free(entry); 17 | entry = next; 18 | } 19 | } 20 | memset(&hash_set->set, 0, sizeof(hash_set->set)); 21 | } 22 | 23 | void xkb_hash_set_add_entry( 24 | struct xkb_hash_set *hash_set, uint32_t key, xkb_mod_mask_t mask) { 25 | assert(key < (sizeof(hash_set->set) / sizeof(hash_set->set[0]))); 26 | // Strip out caps lock, mod 2, and any since those should be ignored. 27 | mask &= ~(XCB_MOD_MASK_LOCK | XCB_MOD_MASK_2 | XCB_MOD_MASK_ANY); 28 | 29 | struct hash_entry *entry = &hash_set->set[key]; 30 | if (!entry->present) { 31 | entry->present = true; 32 | entry->mod_mask = mask; 33 | } else { 34 | while (entry->next != NULL) { 35 | entry = entry->next; 36 | } 37 | entry->next = calloc(1, sizeof(struct hash_entry)); 38 | entry->next->present = true; 39 | entry->next->mod_mask = mask; 40 | } 41 | } 42 | 43 | bool xkb_hash_set_get_entry( 44 | struct xkb_hash_set *hash_set, uint32_t key, xkb_mod_mask_t mask) { 45 | assert(key < sizeof(hash_set->set) / sizeof(hash_set->set[0])); 46 | // Strip out caps lock, mod 2, and any since those should be ignored. 47 | mask &= ~(XCB_MOD_MASK_LOCK | XCB_MOD_MASK_2 | XCB_MOD_MASK_ANY); 48 | 49 | struct hash_entry *entry = &hash_set->set[key]; 50 | if (entry->present) { 51 | while (entry != NULL) { 52 | if (entry->mod_mask == mask) { 53 | return true; 54 | } 55 | entry = entry->next; 56 | } 57 | } 58 | return false; 59 | } 60 | -------------------------------------------------------------------------------- /way-cooler/xkb_hash_set.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #ifndef WC_XKB_HASH_SET_H 7 | #define WC_XKB_HASH_SET_H 8 | 9 | struct hash_entry { 10 | struct hash_entry *next; 11 | xkb_mod_mask_t mod_mask; 12 | bool present; 13 | }; 14 | 15 | struct xkb_hash_set { 16 | struct hash_entry set[XKB_KEY_VoidSymbol]; 17 | }; 18 | 19 | void xkb_hash_set_clear(struct xkb_hash_set *hash_set); 20 | 21 | void xkb_hash_set_add_entry( 22 | struct xkb_hash_set *hash_set, uint32_t key, xkb_mod_mask_t mask); 23 | 24 | bool xkb_hash_set_get_entry( 25 | struct xkb_hash_set *hash_set, uint32_t key, xkb_mod_mask_t mask); 26 | 27 | #endif // WC_XKB_HASH_SET_H 28 | -------------------------------------------------------------------------------- /way-cooler/xwayland.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200112L 2 | #include "xwayland.h" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "cursor.h" 10 | #include "seat.h" 11 | #include "server.h" 12 | #include "view.h" 13 | 14 | void wc_xwayland_surface_destroy(struct wl_listener *listener, void *data) { 15 | struct wc_view *view = wl_container_of(listener, view, destroy); 16 | 17 | wl_list_remove(&view->link); 18 | 19 | wl_list_remove(&view->map.link); 20 | wl_list_remove(&view->unmap.link); 21 | wl_list_remove(&view->configure.link); 22 | wl_list_remove(&view->request_move.link); 23 | wl_list_remove(&view->request_resize.link); 24 | wl_list_remove(&view->destroy.link); 25 | 26 | free(view); 27 | } 28 | 29 | static void wc_xwayland_request_configure( 30 | struct wl_listener *listener, void *data) { 31 | struct wc_view *view = wl_container_of(listener, view, configure); 32 | 33 | struct wlr_xwayland_surface_configure_event *event = data; 34 | 35 | view->geo.x = event->x; 36 | view->geo.y = event->y; 37 | view->geo.width = event->width; 38 | view->geo.height = event->height; 39 | 40 | wlr_xwayland_surface_configure(view->xwayland_surface, event->x, event->y, 41 | event->width, event->height); 42 | } 43 | 44 | static void wc_xwayland_commit(struct wl_listener *listener, void *data) { 45 | struct wc_view *view = wl_container_of(listener, view, commit); 46 | struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface; 47 | 48 | struct wlr_box size = { 49 | .x = view->geo.x, 50 | .y = view->geo.y, 51 | .width = xwayland_surface->width, 52 | .height = xwayland_surface->height, 53 | }; 54 | 55 | wc_view_commit(view, size); 56 | } 57 | 58 | static void wc_xwayland_surface_map(struct wl_listener *listener, void *data) { 59 | struct wc_view *view = wl_container_of(listener, view, map); 60 | struct wlr_xwayland_surface *surface = data; 61 | 62 | view->mapped = true; 63 | wc_focus_view(view); 64 | 65 | view->geo.width = surface->width; 66 | view->geo.height = surface->height; 67 | 68 | view->commit.notify = wc_xwayland_commit; 69 | wl_signal_add( 70 | &view->xwayland_surface->surface->events.commit, &view->commit); 71 | 72 | wc_view_damage_whole(view); 73 | } 74 | 75 | static void wc_xwayland_surface_unmap( 76 | struct wl_listener *listener, void *data) { 77 | struct wc_view *view = wl_container_of(listener, view, unmap); 78 | view->mapped = false; 79 | 80 | wl_list_remove(&view->commit.link); 81 | 82 | wc_view_damage_whole(view); 83 | } 84 | 85 | static void wc_xwayland_request_move(struct wl_listener *listener, void *data) { 86 | struct wc_view *view = wl_container_of(listener, view, request_move); 87 | 88 | struct wlr_box geo = { 89 | .x = view->geo.x, 90 | .y = view->geo.y, 91 | .width = view->xwayland_surface->width, 92 | .height = view->xwayland_surface->height, 93 | }; 94 | 95 | wc_view_move(view, geo); 96 | } 97 | 98 | static void wc_xwayland_request_resize( 99 | struct wl_listener *listener, void *data) { 100 | struct wc_view *view = wl_container_of(listener, view, request_resize); 101 | struct wlr_xwayland_resize_event *event = data; 102 | 103 | struct wlr_box geo = { 104 | .x = view->geo.x, 105 | .y = view->geo.y, 106 | .width = view->xwayland_surface->width, 107 | .height = view->xwayland_surface->height, 108 | }; 109 | 110 | wc_view_resize(view, geo, event->edges); 111 | } 112 | 113 | static void wc_xwayland_new_surface(struct wl_listener *listener, void *data) { 114 | struct wc_server *server = 115 | wl_container_of(listener, server, new_xwayland_surface); 116 | struct wlr_xwayland_surface *xwayland_surface = data; 117 | 118 | struct wc_view *view = calloc(1, sizeof(struct wc_view)); 119 | view->server = server; 120 | view->xwayland_surface = xwayland_surface; 121 | view->surface_type = WC_XWAYLAND; 122 | 123 | view->map.notify = wc_xwayland_surface_map; 124 | view->unmap.notify = wc_xwayland_surface_unmap; 125 | view->configure.notify = wc_xwayland_request_configure; 126 | view->request_move.notify = wc_xwayland_request_move; 127 | view->request_resize.notify = wc_xwayland_request_resize; 128 | view->destroy.notify = wc_xwayland_surface_destroy; 129 | 130 | wl_signal_add(&xwayland_surface->events.map, &view->map); 131 | wl_signal_add(&xwayland_surface->events.unmap, &view->unmap); 132 | wl_signal_add( 133 | &xwayland_surface->events.request_configure, &view->configure); 134 | wl_signal_add(&xwayland_surface->events.request_move, &view->request_move); 135 | wl_signal_add( 136 | &xwayland_surface->events.request_resize, &view->request_resize); 137 | wl_signal_add(&xwayland_surface->events.destroy, &view->destroy); 138 | 139 | wl_list_insert(&server->views, &view->link); 140 | } 141 | 142 | static void wc_xwayland_ready(struct wl_listener *listener, void *data) { 143 | struct wc_server *server = 144 | wl_container_of(listener, server, xwayland_ready); 145 | if (server->startup_cmd != NULL) { 146 | // NOTE Executed here so that DISPLAY is correct for the client 147 | wc_server_execute_startup_command(server); 148 | } 149 | } 150 | 151 | void wc_xwayland_init(struct wc_server *server) { 152 | server->xwayland = 153 | wlr_xwayland_create(server->wl_display, server->compositor, false); 154 | 155 | wl_signal_add(&server->xwayland->events.new_surface, 156 | &server->new_xwayland_surface); 157 | wl_signal_add(&server->xwayland->events.ready, &server->xwayland_ready); 158 | 159 | server->new_xwayland_surface.notify = wc_xwayland_new_surface; 160 | server->xwayland_ready.notify = wc_xwayland_ready; 161 | 162 | setenv("DISPLAY", server->xwayland->display_name, true); 163 | 164 | if (server->xwayland == NULL) { 165 | abort(); 166 | } 167 | } 168 | 169 | void wc_xwayland_fini(struct wc_server *server) { 170 | wlr_xwayland_destroy(server->xwayland); 171 | server->xwayland = NULL; 172 | 173 | wl_list_remove(&server->new_xwayland_surface.link); 174 | } 175 | -------------------------------------------------------------------------------- /way-cooler/xwayland.h: -------------------------------------------------------------------------------- 1 | #ifndef WC_XWAYLAND_H 2 | #define WC_XWAYLAND_H 3 | 4 | #include "server.h" 5 | 6 | void wc_xwayland_init(struct wc_server *server); 7 | 8 | void wc_xwayland_fini(struct wc_server *server); 9 | 10 | void wc_xwayland_surface_destroy(struct wl_listener *listener, void *data); 11 | 12 | #endif // WC_XWAYLAND_H 13 | --------------------------------------------------------------------------------