├── docs └── .gitignore ├── .envrc ├── subprojects ├── .gitignore ├── wlroots.wrap ├── expected.wrap └── cereal.wrap ├── cardboard ├── wlr_cpp_fixes │ ├── meson.build │ └── include │ │ └── wlr_cpp_fixes │ │ ├── xwayland.h │ │ └── types │ │ └── wlr_layer_shell_v1.h ├── Helpers.h ├── Command.h ├── Spawn.h ├── Config.h ├── Spawn.cpp ├── main.cpp ├── StrongAlias.h ├── meson.build ├── ViewOperations.h ├── Output.h ├── SurfaceManager.h ├── Cursor.h ├── Layers.h ├── ViewAnimation.h ├── XDGView.h ├── NotNull.h ├── View.cpp ├── ViewAnimation.cpp ├── OutputManager.h ├── Xwayland.h ├── OptionalRef.h ├── Keyboard.h ├── Listener.h ├── Keyboard.cpp ├── Cursor.cpp ├── IPC.h ├── Server.h ├── SurfaceManager.cpp ├── ViewOperations.cpp ├── OutputManager.cpp ├── commands │ └── dispatch_command.cpp ├── Workspace.h ├── View.h ├── Server.cpp ├── Seat.h └── IPC.cpp ├── cardboard.desktop ├── .editorconfig ├── .gitignore ├── meson_options.txt ├── .gitlab-ci.yml ├── cutter ├── meson.build ├── main.cpp └── parse_arguments.h ├── shell.nix ├── libcardboard ├── meson.build ├── src │ ├── ipc.cpp │ ├── client.cpp │ └── command_protocol.cpp └── include │ └── cardboard │ ├── ipc.h │ ├── client.h │ └── command_protocol.h ├── nix ├── sources.json └── sources.nix ├── protocols └── meson.build ├── meson.build ├── man ├── cardboard.1.md └── cutter.1.md ├── .clang-format └── README.md /docs/.gitignore: -------------------------------------------------------------------------------- 1 | gen 2 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | eval "$(lorri direnv)" -------------------------------------------------------------------------------- /subprojects/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.wrap -------------------------------------------------------------------------------- /subprojects/wlroots.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/swaywm/wlroots 3 | revision = 0.12.0 4 | -------------------------------------------------------------------------------- /subprojects/expected.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://gitlab.com/cardboardwm/expected 3 | revision = master 4 | -------------------------------------------------------------------------------- /cardboard/wlr_cpp_fixes/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | wlr_cpp_fixes_inc = include_directories('include') 3 | -------------------------------------------------------------------------------- /cardboard.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Cardboard 3 | Comment=A scrollable tiling Wayland compositor 4 | Exec=cardboard 5 | Type=Application 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | 7 | [*.{cpp,h,hpp}] 8 | indent_style = space 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.log 4 | tmp/ 5 | .ccls-cache/ 6 | *.plist 7 | 8 | build 9 | subprojects/wlroots 10 | subprojects/expected 11 | compile_commands.json 12 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') 2 | option('man', type: 'boolean', value: 'false', description: 'Build man pages. (Requires pandoc)') -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: registry.gitlab.com/cardboardwm/cardboard-ci-image/ci-image 2 | stages: 3 | - build 4 | 5 | gnu: 6 | stage: build 7 | script: 8 | - CC=gcc CXX=g++ meson build-gnu 9 | - ninja -C build-gnu 10 | 11 | clang: 12 | stage: build 13 | script: 14 | - CC=clang CXX=clang++ meson build-clang 15 | - ninja -C build-clang 16 | -------------------------------------------------------------------------------- /cutter/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | expected_proj = subproject('expected', required: true) 3 | expected = expected_proj.get_variable('expected_dep') 4 | 5 | executable( 6 | 'cutter', 7 | files('main.cpp'), 8 | include_directories: [libcardboard_inc], 9 | link_with: libcardboard, 10 | dependencies: [expected], 11 | install: true 12 | ) 13 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | let 3 | sources = import ./nix/sources.nix; 4 | pkgs = import sources.nixpkgs {}; 5 | in 6 | pkgs.mkShell { 7 | nativeBuildInputs = with pkgs; [ 8 | meson 9 | ninja 10 | pkg-config 11 | gcc10 12 | ] ++ pkgs.wlroots.nativeBuildInputs; 13 | 14 | buildInputs = with pkgs; [ 15 | wayland 16 | libffi 17 | ] ++ pkgs.wlroots.buildInputs; 18 | } 19 | -------------------------------------------------------------------------------- /subprojects/cereal.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = cereal-1.3.0 3 | 4 | source_url = https://github.com/USCiLab/cereal/archive/v1.3.0.tar.gz 5 | source_filename = cereal-1.3.0.tar.gz 6 | source_hash = 329ea3e3130b026c03a4acc50e168e7daff4e6e661bc6a7dfec0d77b570851d5 7 | 8 | patch_url = https://wrapdb.mesonbuild.com/v1/projects/cereal/1.3.0/1/get_zip 9 | patch_filename = cereal-1.3.0-1-wrap.zip 10 | patch_hash = 418724e544fb7cf2eab4b0dbd623925f81e79e7b38f098f6dc07bf2c27cb48a6 11 | -------------------------------------------------------------------------------- /libcardboard/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | libcardboard_inc = include_directories('include') 3 | 4 | expected_proj = subproject('expected', required: true) 5 | expected = expected_proj.get_variable('expected_dep') 6 | 7 | cereal_proj = subproject('cereal', required: true) 8 | cereal = cereal_proj.get_variable('cereal_dep') 9 | 10 | sources = files( 11 | 'src/command_protocol.cpp', 12 | 'src/ipc.cpp', 13 | 'src/client.cpp', 14 | ) 15 | 16 | install_subdir('include/cardboard', 17 | install_dir: get_option('includedir') 18 | ) 19 | 20 | libcardboard = library( 21 | 'cardboard', 22 | sources, 23 | include_directories: libcardboard_inc, 24 | install: true, 25 | dependencies: [cereal, expected], 26 | cpp_args: '-Wno-deprecated' 27 | ) 28 | -------------------------------------------------------------------------------- /cardboard/wlr_cpp_fixes/include/wlr_cpp_fixes/xwayland.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_WLR_CPP_FIXES_XWAYLAND_H_INCLUDED 12 | #define CARDBOARD_WLR_CPP_FIXES_XWAYLAND_H_INCLUDED 13 | 14 | extern "C" { 15 | 16 | #define class class_ 17 | #define static 18 | #include 19 | #undef class 20 | #undef static 21 | } 22 | 23 | #endif // CARDBOARD_WLR_CPP_FIXES_XWAYLAND_H_INCLUDED 24 | -------------------------------------------------------------------------------- /cardboard/wlr_cpp_fixes/include/wlr_cpp_fixes/types/wlr_layer_shell_v1.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_WLR_CPP_FIXES_WLR_LAYER_SHELL_V1_H_INCLUDED 12 | #define CARDBOARD_WLR_CPP_FIXES_WLR_LAYER_SHELL_V1_H_INCLUDED 13 | 14 | extern "C" { 15 | 16 | #define namespace namespace_ 17 | #include 18 | #undef namespace 19 | } 20 | 21 | #endif // CARDBOARD_WLR_CPP_FIXES_WLR_LAYER_SHELL_V1_H_INCLUDED 22 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "niv": { 3 | "branch": "master", 4 | "description": "Easy dependency management for Nix projects", 5 | "homepage": "https://github.com/nmattia/niv", 6 | "owner": "nmattia", 7 | "repo": "niv", 8 | "rev": "9d35b9e4837ab88517210b1701127612c260eccf", 9 | "sha256": "0q50xhnm8g2yfyakrh0nly4swyygxpi0a8cb9gp65wcakcgvzvdh", 10 | "type": "tarball", 11 | "url": "https://github.com/nmattia/niv/archive/9d35b9e4837ab88517210b1701127612c260eccf.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "nixpkgs": { 15 | "branch": "nixos-20.09", 16 | "description": "Nix Packages collection", 17 | "homepage": "", 18 | "owner": "NixOS", 19 | "repo": "nixpkgs", 20 | "rev": "05334ad78526ead39af85f846515606d9f052a11", 21 | "sha256": "1jrn6prplav0knaj859qqwg0l6mcyiv5yjd567hpgm4bi9hy1nxg", 22 | "type": "tarball", 23 | "url": "https://github.com/NixOS/nixpkgs/archive/05334ad78526ead39af85f846515606d9f052a11.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cardboard/Helpers.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_HELPERS_H_INCLUDED 12 | #define CARDBOARD_HELPERS_H_INCLUDED 13 | 14 | #include 15 | 16 | #include "Listener.h" 17 | #include "Server.h" 18 | 19 | constexpr void register_handlers(Server& server, ListenerData subject, std::initializer_list pairs) 20 | { 21 | for (const auto& pair : pairs) { 22 | server.listeners.add_listener(pair.signal, 23 | Listener { pair.notify, &server, subject }); 24 | } 25 | } 26 | 27 | #endif // CARDBOARD_HELPERS_H_INCLUDED 28 | -------------------------------------------------------------------------------- /protocols/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | wayland_protos = dependency('wayland-protocols') 3 | wayland_server = dependency('wayland-server') 4 | 5 | wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') 6 | 7 | wayland_scanner = find_program('wayland-scanner') 8 | 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 | ] 31 | 32 | server_protos_src = [] 33 | server_protos_headers = [] 34 | 35 | foreach p : server_protocols 36 | xml = join_paths(p) 37 | server_protos_src += wayland_scanner_code.process(xml) 38 | server_protos_headers += wayland_scanner_server.process(xml) 39 | endforeach 40 | 41 | lib_server_protos = static_library( 42 | 'server_protos', 43 | server_protos_src + server_protos_headers 44 | ) 45 | 46 | server_protos = declare_dependency( 47 | link_with: lib_server_protos, 48 | sources: server_protos_headers, 49 | ) 50 | -------------------------------------------------------------------------------- /cardboard/Command.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_COMMAND_H_INCLUDED 12 | #define CARDBOARD_COMMAND_H_INCLUDED 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | struct Server; 20 | 21 | /** 22 | * \brief This struct holds the result of an IPC command, which can be a string message 23 | * for the moment. 24 | */ 25 | struct CommandResult { 26 | std::string message; 27 | }; 28 | 29 | /** 30 | * \brief Callable that stores a bound command (internal function pointer and all arguments) 31 | */ 32 | using Command = std::function; 33 | 34 | /** 35 | * \brief Dispatches a the command's arguments (CommandData) and binds the command into the Command type. 36 | */ 37 | Command dispatch_command(const CommandData&); 38 | 39 | #endif //CARDBOARD_COMMAND_H_INCLUDED 40 | -------------------------------------------------------------------------------- /cardboard/Spawn.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_SPAWN_H_INCLUDED 12 | #define CARDBOARD_SPAWN_H_INCLUDED 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | /** 21 | * \file 22 | * \brief This header file holds a simple utility function for spawning 23 | * a system utility in another process. 24 | */ 25 | 26 | /** 27 | * \brief Executes the function \a fn in a new process, in background. 28 | * \a fn should call a function from the \c exec(3) family. 29 | * 30 | * If any library call from \a fn encounters an error (especially the exec function), 31 | * it is caught by the parent and returned by this function. 32 | * 33 | * \returns The errno value if something failed, or 0 if successful. 34 | * */ 35 | std::error_code spawn(std::function fn); 36 | 37 | #endif // CARDBOARD_SPAWN_H_INCLUDED 38 | -------------------------------------------------------------------------------- /cardboard/Config.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_CONFIG_H_INCLUDED 12 | #define CARDBOARD_CONFIG_H_INCLUDED 13 | 14 | #include 15 | 16 | /// Various configurations for the compositor. 17 | struct Config { 18 | /** 19 | * \brief One or more keys that when pressed allow the user to move and/or resize 20 | * the window interactively. 21 | * 22 | * The config key in cutter is named 'mouse_mod' for simplicity, yet it allows more 23 | * if you want. 24 | * */ 25 | uint32_t mouse_mods; 26 | 27 | /** 28 | * \brief The gap between the tiled windows (measured in pixels); default is 10 pixels 29 | */ 30 | int gap = 10; 31 | 32 | /** 33 | * \brief The color behind the focused column 34 | */ 35 | struct { 36 | float r, g, b, a; 37 | } focus_color { 0.f, 0.f, 0.7f, 0.5f }; 38 | }; 39 | 40 | #endif // CARDBOARD_CONFIG_H_INCLUDED 41 | -------------------------------------------------------------------------------- /cardboard/Spawn.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include "Spawn.h" 12 | 13 | template 14 | static void ignore(Args&&...) 15 | { 16 | } 17 | 18 | std::error_code spawn(std::function fn) 19 | { 20 | // credits: http://www.lubutu.com/code/spawning-in-unix 21 | int fd[2]; 22 | if (pipe(fd) == -1) { 23 | int err = errno; 24 | return std::error_code(err, std::generic_category()); 25 | } 26 | if (fork() == 0) { 27 | close(fd[0]); 28 | fcntl(fd[1], F_SETFD, FD_CLOEXEC); 29 | 30 | setsid(); 31 | int status = fn(); 32 | 33 | ignore(write(fd[1], &errno, sizeof(errno))); 34 | exit(status); 35 | } 36 | 37 | close(fd[1]); 38 | 39 | int code = 0; 40 | if (!read(fd[0], &code, sizeof(code))) { 41 | code = 0; 42 | } 43 | close(fd[0]); 44 | return std::error_code(code, std::generic_category()); 45 | } 46 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | project( 3 | 'cardboardwm', 4 | 'c', 'cpp', 5 | version: '0.0.1', 6 | license: 'XXX', 7 | default_options: [ 8 | 'c_std=c17', 9 | 'cpp_std=c++2a', 10 | 'warning_level=2', 11 | 'werror=true', 12 | 'default_library=static' 13 | ], 14 | ) 15 | 16 | add_project_arguments( 17 | [ 18 | '-DWLR_USE_UNSTABLE', 19 | '-Wno-missing-field-initializers', 20 | ], 21 | language: 'cpp' 22 | ) 23 | 24 | # workaround for wlroots 0.10.1 on gcc 10 25 | add_global_arguments( 26 | ['-fcommon'], language: 'c' 27 | ) 28 | 29 | datadir = get_option('datadir') 30 | 31 | install_data('cardboard.desktop', install_dir: join_paths(datadir, 'wayland-sessions')) 32 | 33 | subdir('protocols') 34 | subdir('libcardboard') 35 | subdir('cardboard') 36 | subdir('cutter') 37 | 38 | if get_option('man') 39 | pandoc = find_program('pandoc') 40 | mandir1 = join_paths(get_option('mandir'), 'man1') 41 | 42 | cardboard_man = custom_target( 43 | 'cardboard_man', 44 | output: 'cardboard.1', 45 | input: 'man/cardboard.1.md', 46 | command: [pandoc, '@INPUT@', '-o', '@OUTPUT@', '-s', '-t', 'man', '-f', 'markdown-smart'], 47 | install: true, 48 | install_dir: mandir1 49 | ) 50 | 51 | cutter_man = custom_target( 52 | 'cutter_man', 53 | output: 'cutter.1', 54 | input: 'man/cutter.1.md', 55 | command: [pandoc, '@INPUT@', '-o', '@OUTPUT@', '-s', '-t', 'man', '-f', 'markdown-smart'], 56 | install: true, 57 | install_dir: mandir1 58 | ) 59 | endif -------------------------------------------------------------------------------- /cardboard/main.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | extern "C" { 12 | #include 13 | } 14 | 15 | #include 16 | #include 17 | 18 | #include "BuildConfig.h" 19 | #include "Server.h" 20 | 21 | Server server; 22 | 23 | void sig_handler(int signo) 24 | { 25 | if (signo == SIGCHLD) { 26 | signal(signo, sig_handler); 27 | while (waitpid(-1, 0, WNOHANG) > 0) 28 | ; 29 | } else if (signo == SIGINT || signo == SIGHUP || signo == SIGTERM) { 30 | server.teardown(0); 31 | } 32 | } 33 | 34 | int main() 35 | { 36 | wlr_log_init(WLR_DEBUG, nullptr); 37 | 38 | signal(SIGINT, sig_handler); 39 | signal(SIGHUP, sig_handler); 40 | signal(SIGTERM, sig_handler); 41 | signal(SIGCHLD, sig_handler); 42 | signal(SIGPIPE, SIG_IGN); 43 | 44 | if (!server.init()) { 45 | return EXIT_FAILURE; 46 | } 47 | 48 | server.run(); 49 | server.stop(); 50 | return server.exit_code; 51 | } 52 | -------------------------------------------------------------------------------- /cardboard/StrongAlias.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_STRONG_ALIAS_H_INCLUDED 12 | #define CARDBOARD_STRONG_ALIAS_H_INCLUDED 13 | 14 | #include 15 | 16 | template 17 | class StrongAlias { 18 | public: 19 | explicit StrongAlias(const T& value) 20 | : m_value { value } {}; 21 | explicit StrongAlias(T&& value) 22 | : m_value { std::move(value) } {}; 23 | explicit StrongAlias() 24 | : m_value {} 25 | { 26 | } 27 | 28 | T& get() { return m_value; } 29 | const T& get() const { return m_value; } 30 | 31 | explicit operator T() { return m_value; } 32 | explicit operator T() const { return m_value; } 33 | 34 | auto& operator=(const T& t) 35 | { 36 | m_value = t; 37 | 38 | return *this; 39 | } 40 | 41 | auto& operator=(T&& t) 42 | { 43 | m_value = std::move(t); 44 | 45 | return *this; 46 | } 47 | 48 | private: 49 | T m_value; 50 | }; 51 | 52 | #endif //CARDBOARD_STRONG_ALIAS_H_INCLUDED 53 | -------------------------------------------------------------------------------- /man/cardboard.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: cardboard 3 | section: 1 4 | header: Cardboard Manual 5 | footer: Version 0.0.1 6 | --- 7 | 8 | # NAME 9 | cardboard - A scrollable tiling Wayland compositors designed with 10 | laptops in mind. 11 | 12 | # SYNOPSIS 13 | *cardboard* 14 | 15 | # DESCRIPTION 16 | Cardboard is a unique, scrollable tiling Wayland compositor designed with laptops 17 | in mind, based on *wlroots*. 18 | 19 | Scrollable tiling is an idea that comes from the Gnome extension PaperWM. 20 | In this tiling mode, the windows occupy the height of the screen and are displaced 21 | in a list, such as that the user can scroll through them, as opposed to tiling 22 | by filling the screen with windows disposed in a tree like structure. The main idea 23 | is based on the fact that the user will most likely not look at more than three windows 24 | at the same time. 25 | 26 | # ENVIRONMENT 27 | *CARDBOARD_SOCKET* 28 | The IPC unix domain socket address accepting commands 29 | 30 | # SEE ALSO 31 | *cutter(1)* 32 | 33 | # BUGS 34 | See GitLab issues: https://gitlab.com/cardboardwm/cardboard/-/issues 35 | 36 | # LICENSE 37 | Copyright (C) 2020-2021 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 38 | 39 | This program is free software: you can redistribute it and/or modify it under the terms of the 40 | GNU General Public License as published by the Free Software Foundation, version 3. 41 | 42 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 43 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 44 | See the GNU General Public License for more details. 45 | 46 | You should have received a copy of the GNU General Public License along with this program. 47 | If not, see . 48 | -------------------------------------------------------------------------------- /libcardboard/src/ipc.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include 12 | 13 | #include 14 | 15 | namespace libcardboard::ipc { 16 | 17 | Header interpret_header(const AlignedHeaderBuffer& buffer) 18 | { 19 | if constexpr (std::endian::native == std::endian::little) { 20 | return { *reinterpret_cast(buffer.data()) }; 21 | } else { 22 | uint32_t r = *reinterpret_cast(buffer.data()); 23 | r = (r & 0x000000ffu) << 24u | (r & 0x0000ff00u) << 8u | (r & 0x00ff0000u) >> 8u | (r & 0xff000000u) >> 24u; 24 | 25 | return { static_cast(r) }; 26 | } 27 | } 28 | 29 | AlignedHeaderBuffer create_header_buffer(const Header& header) 30 | { 31 | AlignedHeaderBuffer buffer; 32 | 33 | *reinterpret_cast(buffer.data()) = header.incoming_bytes; 34 | 35 | if constexpr (std::endian::native == std::endian::big) { 36 | uint32_t r = *reinterpret_cast(buffer.data()); 37 | 38 | r = ((r >> 24u) & 0x000000ffu) | ((r << 8u) & 0x00ff0000u) | ((r >> 8u) & 0x0000ff00u) | ((r << 24u) & 0xff000000u); 39 | 40 | *reinterpret_cast(buffer.data()) = r; 41 | } 42 | 43 | return buffer; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /libcardboard/include/cardboard/ipc.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef LIBCARDBOARD_IPC_H_INCLUDED 12 | #define LIBCARDBOARD_IPC_H_INCLUDED 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | /// Utility code for connecting to cardboard's socket and reading and writing the IPC header. 19 | namespace libcardboard::ipc { 20 | 21 | /** 22 | * \brief The IPC header that describes the payload 23 | */ 24 | struct Header { 25 | int incoming_bytes; 26 | }; 27 | 28 | /** 29 | * \brief The size of the IPC header in bytes 30 | */ 31 | constexpr std::size_t HEADER_SIZE = 4; 32 | 33 | /** 34 | * \brief Buffer type where the IPC header can be stored for fast serialization and deserialization 35 | */ 36 | struct alignas(alignof(Header)) AlignedHeaderBuffer : std::array { 37 | }; 38 | 39 | /** 40 | * \brief Deserializes the data in the buffer into a Header value 41 | */ 42 | Header interpret_header(const AlignedHeaderBuffer&); 43 | 44 | /** 45 | * \brief Serializes the Header value into the returned buffer 46 | */ 47 | AlignedHeaderBuffer create_header_buffer(const Header&); 48 | 49 | /** 50 | * \brief The environment variable name where CARDBOARD_SOCKET path will be stored 51 | */ 52 | [[maybe_unused]] static const char* SOCKET_ENV_VAR = "CARDBOARD_SOCKET"; 53 | 54 | }; 55 | 56 | #endif //LIBCARDBOARD_IPC_H_INCLUDED 57 | -------------------------------------------------------------------------------- /cardboard/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | wayland_server = dependency('wayland-server') 3 | xkbcommon = dependency('xkbcommon') 4 | xcb = dependency('xcb', required: get_option('xwayland')) 5 | 6 | wlroots_version = '>=0.10.0' 7 | wlroots_proj = subproject( 8 | 'wlroots', 9 | default_options: ['examples=false'], 10 | required: true, 11 | version: wlroots_version, 12 | ) 13 | 14 | wlroots = wlroots_proj.get_variable('wlroots') 15 | wlroots_conf = wlroots_proj.get_variable('conf_data') 16 | wlroots_has_xwayland = wlroots_conf.get('WLR_HAS_XWAYLAND') == 1 17 | 18 | if get_option('xwayland').enabled() and not wlroots_has_xwayland 19 | error('Cannot enable Xwayland support in cardboard: wlroots has been built without Xwayland support') 20 | endif 21 | have_xwayland = xcb.found() and wlroots_has_xwayland 22 | 23 | expected_proj = subproject('expected', required: true) 24 | expected = expected_proj.get_variable('expected_dep') 25 | 26 | conf_data = configuration_data() 27 | conf_data.set10('HAVE_XWAYLAND', have_xwayland) 28 | 29 | configure_file(output: 'BuildConfig.h', configuration: conf_data) 30 | 31 | cardboard_deps = [ 32 | expected, 33 | wayland_server, 34 | wlroots, 35 | xkbcommon, 36 | server_protos, 37 | ] 38 | 39 | cardboard_sources = files( 40 | 'Cursor.cpp', 41 | 'IPC.cpp', 42 | 'Keyboard.cpp', 43 | 'Layers.cpp', 44 | 'Output.cpp', 45 | 'OutputManager.cpp', 46 | 'Seat.cpp', 47 | 'Server.cpp', 48 | 'Spawn.cpp', 49 | 'View.cpp', 50 | 'Workspace.cpp', 51 | 'XDGView.cpp', 52 | 'ViewOperations.cpp', 53 | 'ViewAnimation.cpp', 54 | 'SurfaceManager.cpp', 55 | 'main.cpp', 56 | 'commands/dispatch_command.cpp' 57 | ) 58 | 59 | if have_xwayland 60 | cardboard_deps += xcb 61 | cardboard_sources += 'Xwayland.cpp' 62 | endif 63 | 64 | subdir('wlr_cpp_fixes') 65 | 66 | executable( 67 | 'cardboard', 68 | cardboard_sources, 69 | include_directories: [wlr_cpp_fixes_inc, libcardboard_inc], 70 | dependencies: cardboard_deps, 71 | link_with: libcardboard, 72 | install: true 73 | ) 74 | -------------------------------------------------------------------------------- /libcardboard/include/cardboard/client.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef LIBCARDBOARD_CLIENT_H_INCLUDED 12 | #define LIBCARDBOARD_CLIENT_H_INCLUDED 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "command_protocol.h" 20 | 21 | #include 22 | 23 | /// Code for sending commands to Cardboard and receiving responses. 24 | namespace libcutter { 25 | 26 | /** 27 | * \brief Manages a connection to the Cardboard IPC server 28 | */ 29 | class Client { 30 | public: 31 | Client(const Client&) = delete; 32 | Client(Client&&) noexcept; 33 | ~Client(); 34 | 35 | /** 36 | * \brief Serializes and sends a CommandData packet to the server 37 | */ 38 | tl::expected send_command(const CommandData&); 39 | 40 | /** 41 | * \brief Waits for a string response from the server 42 | */ 43 | tl::expected wait_response(); 44 | 45 | private: 46 | Client(int, std::unique_ptr); 47 | 48 | int socket_fd; 49 | std::unique_ptr socket_address; 50 | 51 | friend tl::expected open_client(); 52 | }; 53 | 54 | /** 55 | * \brief Creates a client connection on the socket path reported by the system 56 | */ 57 | tl::expected open_client(); 58 | 59 | } 60 | 61 | #endif //LIBCARDBOARD_CLIENT_H_INCLUDED 62 | -------------------------------------------------------------------------------- /cardboard/ViewOperations.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_VIEW_OPERATIONS_H_INCLUDED 12 | #define CARDBOARD_VIEW_OPERATIONS_H_INCLUDED 13 | 14 | #include "Server.h" 15 | #include "View.h" 16 | 17 | #include "NotNull.h" 18 | #include "StrongAlias.h" 19 | 20 | /** 21 | * \file 22 | * \brief ViewOperations hosts high-level View management functions, handling various low-level details. 23 | * 24 | * For example, if you want to move a View to a Workspace, you should use change_view_workspace instead of 25 | * assigning the workspace_id field manually. 26 | * */ 27 | 28 | struct Workspace; 29 | 30 | using AbsoluteScroll = StrongAlias; 31 | using RelativeScroll = StrongAlias; 32 | 33 | /// Sends a view to another workspace 34 | void change_view_workspace(Server& server, View& view, Workspace& new_workspace); 35 | 36 | /// Moves window while maintaining windows manager integrity 37 | void reconfigure_view_position(Server& server, View& view, int x, int y, bool animate = true); 38 | 39 | /// Resizes window while maintaining windows manager integrity 40 | void reconfigure_view_size(Server& server, View& view, int width, int height); 41 | 42 | /// Sets the workspace absolute scroll position to `scroll` 43 | void scroll_workspace(OutputManager&, Workspace&, AbsoluteScroll scroll, bool animate = true); 44 | 45 | /// Scrolls workspace with as `scroll` as offset. 46 | void scroll_workspace(OutputManager&, Workspace&, RelativeScroll scroll, bool animate = true); 47 | 48 | #endif //CARDBOARD_VIEW_OPERATIONS_H_INCLUDED 49 | -------------------------------------------------------------------------------- /cardboard/Output.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_OUTPUT_H_INCLUDED 12 | #define CARDBOARD_OUTPUT_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | #include 18 | } 19 | 20 | #include 21 | 22 | #include "Layers.h" 23 | #include "Server.h" 24 | 25 | /** 26 | * \file 27 | * \brief This file contains listeners and helper functions for simple output operations. 28 | * 29 | * Outputs are displays. 30 | */ 31 | 32 | struct Output { 33 | struct wlr_output* wlr_output; 34 | struct wlr_output_damage* wlr_output_damage; 35 | struct wlr_box usable_area; 36 | 37 | /// Time of last presentation. Use it to calculate the delta time. 38 | struct timespec last_present; 39 | 40 | /// Executed for each frame render per output. 41 | static void frame_handler(struct wl_listener* listener, void* data); 42 | /// Executed as soon as the first pixel is put on the screen; 43 | static void present_handler(struct wl_listener* listener, void* data); 44 | /// Executed when the output is detached. 45 | static void destroy_handler(struct wl_listener* listener, void* data); 46 | /// Executed when the output changes fields (transform, scale etc). 47 | static void commit_handler(struct wl_listener* listener, void* data); 48 | /// Executed when the mode changes (resolution, framerate etc) 49 | static void mode_handler(struct wl_listener* listener, void* data); 50 | }; 51 | 52 | /// Registers event listeners and does bookkeeping for a newly added output. 53 | void register_output(Server& server, Output&& output); 54 | 55 | #endif // CARDBOARD_OUTPUT_H_INCLUDED 56 | -------------------------------------------------------------------------------- /cardboard/SurfaceManager.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_VIEW_MANAGER_H_INCLUDED 12 | #define CARDBOARD_VIEW_MANAGER_H_INCLUDED 13 | 14 | #include "BuildConfig.h" 15 | 16 | #include 17 | #include 18 | 19 | #include "Layers.h" 20 | #include "OptionalRef.h" 21 | #include "OutputManager.h" 22 | #include "View.h" 23 | #include "ViewAnimation.h" 24 | #include "Workspace.h" 25 | #include "Xwayland.h" 26 | 27 | struct SurfaceManager { 28 | std::list> views; 29 | #if HAVE_XWAYLAND 30 | std::list> xwayland_or_surfaces; 31 | #endif 32 | LayerArray layers; 33 | 34 | /// Common mapping procedure for views regardless of their underlying shell. 35 | void map_view(Server&, View&); 36 | /// Common unmapping procedure for views regardless of their underlying shell. 37 | void unmap_view(Server&, View&); 38 | /// Puts the \a view on top. 39 | void move_view_to_front(View&); 40 | void remove_view(ViewAnimation&, View&); 41 | 42 | /** 43 | * \brief Returns the xdg / xwayland / layer_shell surface leaf of the first 44 | * view / layer / xwayland override redirect surface under the cursor. 45 | * 46 | * \a lx and \a ly are root (output layout) coordinates. That is, coordinates relative to the imaginary plane of all surfaces. 47 | * 48 | * \param[out] surface The \c wlr_surface found under the cursor. 49 | * \param[out] sx The x coordinate of the found surface in root coordinates. 50 | * \param[out] sy The y coordinate of the found surface in root coordinates. 51 | */ 52 | OptionalRef get_surface_under_cursor(OutputManager&, double lx, double ly, struct wlr_surface*& surface, double& sx, double& sy); 53 | }; 54 | 55 | #endif // CARDBOARD_VIEW_MANAGER_H_INCLUDED 56 | -------------------------------------------------------------------------------- /cutter/main.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "parse_arguments.h" 19 | 20 | void print_usage(char* argv0) 21 | { 22 | std::cerr << "Usage: " << argv0 << " [args...]" << std::endl; 23 | } 24 | 25 | int main(int argc, char* argv[]) 26 | { 27 | if (argc < 2) { 28 | print_usage(argv[0]); 29 | return EXIT_FAILURE; 30 | } 31 | 32 | CommandData command_data = parse_arguments(argc, argv) 33 | .map_error([](const std::string& error) { 34 | std::cerr << "cutter: " << error << std::endl; 35 | exit(EXIT_FAILURE); 36 | }) 37 | .value(); 38 | 39 | libcutter::Client client = libcutter::open_client() 40 | .map_error([](const std::string& error) { 41 | std::cerr << "cutter: " << error << std::endl; 42 | exit(EXIT_FAILURE); 43 | }) 44 | .value(); 45 | 46 | client.send_command(command_data) 47 | .map_error([](const std::string& error) { 48 | std::cerr << "cutter: " << error << std::endl; 49 | exit(EXIT_FAILURE); 50 | }); 51 | 52 | std::string response = client.wait_response() 53 | .map_error([](int error_code) { 54 | std::cerr << "cutter: error code " << error_code << std::endl; 55 | exit(EXIT_FAILURE); 56 | }) 57 | .value(); 58 | 59 | std::cout << response; 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /cardboard/Cursor.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_CURSOR_H_INCLUDED 12 | #define CARDBOARD_CURSOR_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | #include 18 | } 19 | 20 | #include 21 | 22 | #include "OptionalRef.h" 23 | #include "OutputManager.h" 24 | 25 | struct Server; 26 | struct Seat; 27 | 28 | // TODO: rename to Cursor after namespacing all structs 29 | struct SeatCursor { 30 | struct wlr_cursor* wlr_cursor; 31 | struct wlr_xcursor_manager* wlr_xcursor_manager; 32 | OptionalRef image_surface_destroy_listener; 33 | 34 | private: 35 | /// Called when the surface of the mouse pointer is destroyed by the client. 36 | static void image_surface_destroy_handler(struct wl_listener* listener, void* data); 37 | }; 38 | 39 | void init_cursor(OutputManager&, SeatCursor& cursor); 40 | 41 | /** 42 | * \brief Sets the cursor image to an xcursor named \a image. 43 | * 44 | * \param image - xcursor image name 45 | */ 46 | void cursor_set_image(Server& server, Seat& seat, SeatCursor& cursor, const char* image); 47 | /** 48 | * \brief Sets the cursor image to a given \a surface with a given hotspot. 49 | * 50 | * The hotspot is the point of the cursor that interacts with the elements on the screen. 51 | * For a normal pointer, the hotspot is usually the tip of the cursor image. 52 | * For a cross pointer, the hotspot is usually in the center. 53 | */ 54 | void cursor_set_image_surface(Server& server, Seat& seat, SeatCursor& cursor, struct wlr_surface* surface, int32_t hotspot_x, int32_t hotspot_y); 55 | /** 56 | * \brief Sets the cursor image according to the surface underneath, 57 | * and sends the appropriate cursor events to the surface underneath, if any. 58 | */ 59 | void cursor_rebase(Server& server, Seat& seat, SeatCursor& cursor, uint32_t time = 0); 60 | 61 | /// Warps the cursor to the given output layout coordinates. 62 | void cursor_warp(Server& server, Seat& seat, SeatCursor& cursor, int lx, int ly); 63 | 64 | #endif // CARDBOARD_CURSOR_H_INCLUDED 65 | -------------------------------------------------------------------------------- /cardboard/Layers.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_LAYERS_H_INCLUDED 12 | #define CARDBOARD_LAYERS_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | } 21 | 22 | #include 23 | #include 24 | 25 | #include "NotNull.h" 26 | #include "OptionalRef.h" 27 | 28 | struct Server; 29 | struct Output; 30 | struct OutputManager; 31 | 32 | /** 33 | * \brief Represents a layer_surface from the layer shell in the compositor. 34 | */ 35 | struct LayerSurface { 36 | struct wlr_layer_surface_v1* surface; 37 | struct wlr_box geometry; 38 | enum zwlr_layer_shell_v1_layer layer; 39 | OptionalRef output; 40 | 41 | bool get_surface_under_coords(double lx, double ly, struct wlr_surface*& surface, double& sx, double& sy) const; 42 | /// Returns true if \a output is the output of this layer surface. 43 | bool is_on_output(Output& output) const; 44 | 45 | static void commit_handler(struct wl_listener* listener, void* data); 46 | static void destroy_handler(struct wl_listener* listener, void* data); 47 | static void map_handler(struct wl_listener* listener, void* data); 48 | static void unmap_handler(struct wl_listener* listener, void* data); 49 | static void new_popup_handler(struct wl_listener* listener, void* data); 50 | static void output_destroy_handler(struct wl_listener* listener, void* data); 51 | }; 52 | 53 | struct LayerSurfacePopup { 54 | struct wlr_xdg_popup* wlr_popup; 55 | NotNullPointer parent; 56 | 57 | void unconstrain(OutputManager& output_manager); 58 | 59 | static void destroy_handler(struct wl_listener* listener, void* data); 60 | static void new_popup_handler(struct wl_listener* listener, void* data); 61 | static void map_handler(struct wl_listener* listener, void* data); 62 | }; 63 | 64 | using LayerArray = std::array, 4>; 65 | 66 | /// Registers a LayerSurface. 67 | void create_layer(Server& server, LayerSurface&& layer_surface); 68 | 69 | /// Arranges all the layers of an \a output. 70 | void arrange_layers(Server& server, Output& output); 71 | 72 | #endif // CARDBOARD_LAYERS_H_INCLUDED 73 | -------------------------------------------------------------------------------- /cardboard/ViewAnimation.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef BUILD_VIEWANIMATION_H 12 | #define BUILD_VIEWANIMATION_H 13 | 14 | extern "C" { 15 | #include 16 | }; 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "View.h" 23 | 24 | struct Server; 25 | 26 | class ViewAnimation; 27 | using ViewAnimationInstance = std::unique_ptr; 28 | 29 | struct AnimationTask { 30 | View* view; 31 | int target_x; 32 | int target_y; 33 | std::function animation_finished_callback = nullptr; 34 | }; 35 | 36 | struct AnimationSettings { 37 | int ms_per_frame; 38 | int animation_duration; //ms 39 | }; 40 | 41 | class ViewAnimation { 42 | ViewAnimation(AnimationSettings, OutputManager&); 43 | 44 | public: 45 | void enqueue_task(const AnimationTask&); 46 | /// Cancel animation tasks for the given view and warp it to its target coords. 47 | void cancel_tasks(View&); 48 | 49 | private: 50 | struct Task { 51 | View* view; 52 | int startx, starty; 53 | int targetx, targety; 54 | std::chrono::time_point begin; 55 | std::function animation_finished_callback; 56 | bool cancelled; 57 | 58 | // aggregate initialization not working wtf 59 | Task(View* view, int startx, int starty, int targetx, int targety, std::chrono::time_point begin, std::function animation_finished_callback) 60 | : view(view) 61 | , startx(startx) 62 | , starty(starty) 63 | , targetx(targetx) 64 | , targety(targety) 65 | , begin(begin) 66 | , animation_finished_callback(animation_finished_callback) 67 | , cancelled(false) 68 | { 69 | } 70 | }; 71 | 72 | std::deque tasks; 73 | 74 | wl_event_source* event_source; 75 | AnimationSettings settings; 76 | NotNullPointer output_manager; 77 | static int timer_callback(void* data); 78 | friend ViewAnimationInstance create_view_animation(Server* server, AnimationSettings); 79 | }; 80 | 81 | ViewAnimationInstance create_view_animation(Server* server, AnimationSettings); 82 | 83 | #endif //BUILD_VIEWANIMATION_H 84 | -------------------------------------------------------------------------------- /cardboard/XDGView.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_XDGVIEW_H_INCLUDED 12 | #define CARDBOARD_XDGVIEW_H_INCLUDED 13 | 14 | #include 15 | 16 | #include "View.h" 17 | 18 | class XDGView final : public View { 19 | public: 20 | struct wlr_xdg_surface* xdg_surface; 21 | /// Stores listeners that are active only when the view is mapped. They are removed when unmapping. 22 | std::array map_unmap_listeners; 23 | 24 | XDGView(struct wlr_xdg_surface* xdg_surface); 25 | ~XDGView() = default; 26 | 27 | struct wlr_surface* get_surface() final; 28 | bool get_surface_under_coords(double lx, double ly, struct wlr_surface*& surface, double& sx, double& sy) final; 29 | void resize(int width, int height) final; 30 | void prepare(Server& server) final; 31 | void set_activated(bool activated) final; 32 | void set_fullscreen(bool fullscreen) final; 33 | void for_each_surface(wlr_surface_iterator_func_t iterator, void* data) final; 34 | bool is_transient_for(View& ancestor) final; 35 | void close_popups() final; 36 | void close() final; 37 | 38 | public: 39 | static void surface_map_handler(struct wl_listener* listener, void* data); 40 | static void surface_unmap_handler(struct wl_listener* listener, void* data); 41 | static void surface_destroy_handler(struct wl_listener* listener, void* data); 42 | static void surface_new_popup_handler(struct wl_listener* listener, void* data); 43 | 44 | private: 45 | static void surface_commit_handler(struct wl_listener* listener, void* data); 46 | static void toplevel_request_move_handler(struct wl_listener* listener, void* data); 47 | static void toplevel_request_resize_handler(struct wl_listener* listener, void* data); 48 | static void toplevel_request_fullscreen_handler(struct wl_listener* listener, void* data); 49 | }; 50 | 51 | struct XDGPopup { 52 | struct wlr_xdg_popup* wlr_popup; 53 | NotNullPointer parent; 54 | 55 | XDGPopup(struct wlr_xdg_popup*, NotNullPointer); 56 | 57 | void unconstrain(Server& server); 58 | 59 | public: 60 | static void destroy_handler(struct wl_listener* listener, void* data); 61 | static void new_popup_handler(struct wl_listener* listener, void* data); 62 | static void map_handler(struct wl_listener* listener, void* data); 63 | }; 64 | 65 | #endif // CARDBOARD_XDGVIEW_H_INCLUDED 66 | -------------------------------------------------------------------------------- /man/cutter.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: cutter 3 | section: 1 4 | header: Cardboard Manual 5 | footer: Version 0.0.1 6 | --- 7 | 8 | # NAME 9 | cutter - the Cardboard client that sends commands to the compositor 10 | 11 | # SYNOPSIS 12 | *cutter* COMMAND [ARGUMENTS] 13 | 14 | # DESCRIPTION 15 | **cutter** is the companion program of the Cardboard compositor, which allows 16 | the user to send actions to, get information from and configure the compositor. 17 | **cutter** in nature is similar to **i3msg** on i3, **swaymsg** on Sway and 18 | **bspc** on bspwm. 19 | 20 | The program itself is based on *libcardboard* which offers an interface to communicate 21 | with the compositor, an interface that can be used in any other program. 22 | 23 | # COMMANDS 24 | cutter *bind* KEYBIND COMMAND 25 | : Registers KEYBIND to execute COMMAND when the key binding is activated 26 | 27 | cutter *exec* COMMAND 28 | : Executes COMMAND in Cardboard 29 | 30 | cutter *config* (mouse_mod|gap|focus_color) VALUES 31 | : Configures a feature in Cardboard. *mouse_mod* takes a keyboard key as VALUE, 32 | gap takes a number of pixels as VALUE, and focus_color takes three numbers representing 33 | a colour as VALUES 34 | 35 | cutter *quit* 36 | : Terminates Cardboard Compositor execution 37 | 38 | cutter *focus* (left|right|up|down|cycle) 39 | : Changes the currently focused window in the direction indicated by the 40 | argument. In the case of cycle mode, the focus is changed in regard to 41 | the focus stack 42 | 43 | cutter *close* 44 | : Terminates the window of the currently active application 45 | 46 | cutter *workspace* (move|switch) N 47 | : move option moves the currently active window to workspace number N. 48 | switch option switches the active workspace to workspace number N. 49 | 50 | cutter *toggle_floating* 51 | : Toggles current window between tiled and floating states. 52 | 53 | cutter *move* DX [DY] 54 | : If the active window is floating, it is moved left and right into the chain of 55 | tiles depending on the sign of DX, and optionally up and down in the column 56 | depending on the sign of DY. If the current window is floating, it is moved 57 | by DX and DY (in pixels) from its original position 58 | 59 | cutter *resize* width height 60 | : Resizes currently active window 61 | 62 | cutter *pop_from_column* 63 | : Pops the active window from the column it is in, into a new one. 64 | 65 | 66 | # ENVIRONMENT 67 | *CARDBOARD_SOCKET* 68 | The IPC unix domain socket address accepting commands 69 | 70 | # SEE ALSO 71 | *cardboard(1)* 72 | 73 | # BUGS 74 | See GitLab issues: https://gitlab.com/cardboardwm/cardboard/-/issues 75 | 76 | # LICENSE 77 | Copyright (C) 2020-2021 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 78 | 79 | This program is free software: you can redistribute it and/or modify it under the terms of the 80 | GNU General Public License as published by the Free Software Foundation, version 3. 81 | 82 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 83 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 84 | See the GNU General Public License for more details. 85 | 86 | You should have received a copy of the GNU General Public License along with this program. 87 | If not, see . 88 | 89 | -------------------------------------------------------------------------------- /cardboard/NotNull.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_NOT_NULL_H_INCLUDED 12 | #define CARDBOARD_NOT_NULL_H_INCLUDED 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // Adapted from GSL::not_null from Microsoft's GSL library, MIT licensed 20 | // https://github.com/microsoft/GSL/blob/0843ea444fcdf07868214da171c4fa9d244e7472/include/gsl/pointers 21 | // https://github.com/microsoft/GSL/blob/0843ea444fcdf07868214da171c4fa9d244e7472/LICENSE 22 | template 23 | class NotNullPointer { 24 | using pointer_type = Type*; 25 | 26 | public: 27 | static_assert(std::is_assignable::value, "Type cannot be assigned nullptr."); 28 | 29 | template ::value>> 30 | constexpr NotNullPointer(U&& u) 31 | : ptr_(std::forward(u)) 32 | { 33 | assert(ptr_ != nullptr); 34 | } 35 | 36 | template ::value>> 37 | constexpr NotNullPointer(pointer_type u) 38 | : ptr_(u) 39 | { 40 | assert(ptr_ != nullptr); 41 | } 42 | 43 | template ::value>> 44 | constexpr NotNullPointer(const NotNullPointer& other) 45 | : NotNullPointer(other.get()) 46 | { 47 | } 48 | 49 | NotNullPointer(const NotNullPointer& other) = default; 50 | NotNullPointer& operator=(const NotNullPointer& other) = default; 51 | 52 | constexpr pointer_type get() const 53 | { 54 | assert(ptr_ != nullptr); 55 | return ptr_; 56 | } 57 | 58 | constexpr operator pointer_type() const { return get(); } 59 | constexpr pointer_type operator->() const { return get(); } 60 | constexpr decltype(auto) operator*() const { return *get(); } 61 | 62 | // prevents compilation when someone attempts to assign a null pointer constant 63 | NotNullPointer(std::nullptr_t) = delete; 64 | NotNullPointer& operator=(std::nullptr_t) = delete; 65 | 66 | // unwanted operators...pointers only point to single objects! 67 | NotNullPointer& operator++() = delete; 68 | NotNullPointer& operator--() = delete; 69 | NotNullPointer operator++(int) = delete; 70 | NotNullPointer operator--(int) = delete; 71 | NotNullPointer& operator+=(std::ptrdiff_t) = delete; 72 | NotNullPointer& operator-=(std::ptrdiff_t) = delete; 73 | void operator[](std::ptrdiff_t) const = delete; 74 | 75 | private: 76 | pointer_type ptr_; 77 | }; 78 | 79 | namespace std { 80 | template 81 | struct hash> { 82 | std::size_t operator()(NotNullPointer const& ptr) const noexcept 83 | { 84 | return std::hash {}(ptr.get()); 85 | } 86 | }; 87 | } 88 | 89 | #endif //CARDBOARD_NOT_NULL_H_INCLUDED 90 | -------------------------------------------------------------------------------- /cardboard/View.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | extern "C" { 12 | #include 13 | #include 14 | #include 15 | } 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "Server.h" 24 | #include "View.h" 25 | 26 | OptionalRef View::get_views_output(Server& server) 27 | { 28 | if (workspace_id < 0) { 29 | return NullRef; 30 | } 31 | return server.output_manager->get_view_workspace(*this).output; 32 | } 33 | 34 | void View::change_output(OptionalRef old_output, OptionalRef new_output) 35 | { 36 | if (old_output && old_output != new_output) { 37 | wlr_surface_send_leave(get_surface(), old_output.unwrap().wlr_output); 38 | } 39 | 40 | if (new_output) { 41 | wlr_surface_send_enter(get_surface(), new_output.unwrap().wlr_output); 42 | } 43 | } 44 | 45 | void View::save_state(ExpansionSavedState to_save) 46 | { 47 | assert(!saved_state.has_value() && expansion_state == ExpansionState::NORMAL); 48 | 49 | saved_state = to_save; 50 | wlr_log(WLR_DEBUG, "saved size (%4d, %4d)", saved_state->width, saved_state->height); 51 | } 52 | 53 | void View::recover() 54 | { 55 | if (expansion_state == ExpansionState::RECOVERING) { 56 | expansion_state = ExpansionState::NORMAL; 57 | } 58 | } 59 | 60 | void View::cycle_width(int screen_width) 61 | { 62 | if (screen_width == 0) { 63 | // shouldn't happen, but better safe than sorry 64 | return; 65 | } 66 | static constexpr double golden_ratio = 0x1.3c6ef372fe94fp-1; // 1 / phi 67 | constexpr std::array ratios = { 1 - golden_ratio, 0.5, golden_ratio }; 68 | double current_ratio = static_cast(geometry.width) / static_cast(screen_width); 69 | 70 | decltype(ratios)::size_type i = 0; 71 | while (i < ratios.size() && ratios[i] <= current_ratio) { 72 | i++; 73 | } 74 | 75 | if (fabs(current_ratio - ratios[i]) < 0.01) { 76 | i = (i + 1) % ratios.size(); 77 | } 78 | 79 | if (i == ratios.size()) { 80 | i = 0; 81 | } 82 | resize(static_cast(screen_width * ratios[i]), geometry.height); 83 | } 84 | 85 | void View::move(OutputManager& output_manager, int x_, int y_) 86 | { 87 | x = x_; 88 | y = y_; 89 | output_manager.set_dirty(); 90 | } 91 | 92 | bool View::is_mapped_and_normal() 93 | { 94 | return mapped && expansion_state == View::ExpansionState::NORMAL; 95 | } 96 | 97 | void View::resize(int width, int height) 98 | { 99 | target_width = width; 100 | target_height = height; 101 | } 102 | 103 | void create_view(Server& server, NotNullPointer view_) 104 | { 105 | server.surface_manager.views.emplace_back(view_); 106 | auto* view = server.surface_manager.views.back().get(); 107 | 108 | view->prepare(server); 109 | } 110 | -------------------------------------------------------------------------------- /cardboard/ViewAnimation.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include "ViewAnimation.h" 12 | 13 | #include "Server.h" 14 | 15 | ViewAnimation::ViewAnimation(AnimationSettings settings, OutputManager& output_manager) 16 | : settings { settings } 17 | , output_manager { &output_manager } 18 | { 19 | } 20 | 21 | void ViewAnimation::enqueue_task(const AnimationTask& task) 22 | { 23 | tasks.emplace_back( 24 | task.view, 25 | task.view->x, 26 | task.view->y, 27 | task.target_x, 28 | task.target_y, 29 | std::chrono::high_resolution_clock::now(), 30 | task.animation_finished_callback); 31 | } 32 | 33 | void ViewAnimation::cancel_tasks(View& view) 34 | { 35 | for (auto& task : tasks) { 36 | if (task.view == &view) { 37 | task.cancelled = true; 38 | task.view->x = task.view->target_x; 39 | task.view->y = task.view->target_y; 40 | } 41 | } 42 | } 43 | 44 | static float beziere_blend(float t) 45 | { 46 | t = std::min(1.0f, t); 47 | return t * t * (3.0f - 2.0f * t); 48 | } 49 | 50 | int ViewAnimation::timer_callback(void* data) 51 | { 52 | auto* view_animation = static_cast(data); 53 | 54 | auto current_time = std::chrono::high_resolution_clock::now(); 55 | auto tasks_number = view_animation->tasks.size(); 56 | for (size_t i = 0; i < tasks_number; i++) { 57 | auto task = view_animation->tasks.front(); 58 | view_animation->tasks.pop_front(); 59 | 60 | if (task.cancelled) { 61 | continue; 62 | } 63 | 64 | auto interval = std::chrono::duration_cast( 65 | current_time - task.begin); 66 | float completeness = static_cast(interval.count()) / view_animation->settings.animation_duration; 67 | 68 | float multiplier = beziere_blend(completeness); 69 | task.view->move( 70 | *view_animation->output_manager, 71 | task.startx - multiplier * (task.startx - task.targetx), 72 | task.starty - multiplier * (task.starty - task.targety)); 73 | 74 | if (completeness < 0.999) { // animation incomplete; 75 | view_animation->tasks.push_back(task); 76 | } else { 77 | if (task.animation_finished_callback) { 78 | task.animation_finished_callback(); 79 | } 80 | } 81 | } 82 | 83 | wl_event_source_timer_update(view_animation->event_source, view_animation->settings.ms_per_frame); 84 | return 0; 85 | } 86 | 87 | ViewAnimationInstance create_view_animation(Server* server, AnimationSettings settings) 88 | { 89 | auto view_animation = std::make_unique(ViewAnimation { settings, *server->output_manager }); 90 | view_animation->event_source = wl_event_loop_add_timer(server->event_loop, ViewAnimation::timer_callback, view_animation.get()); 91 | 92 | wl_event_source_timer_update(view_animation->event_source, settings.ms_per_frame); 93 | 94 | return view_animation; 95 | } 96 | -------------------------------------------------------------------------------- /cardboard/OutputManager.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_OUTPUT_MANAGER_H_INCLUDED 12 | #define CARDBOARD_OUTPUT_MANAGER_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | } 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "NotNull.h" 24 | #include "OptionalRef.h" 25 | #include "Workspace.h" 26 | 27 | struct Output; 28 | struct Server; 29 | 30 | struct OutputManager { 31 | wlr_output_manager_v1* output_manager_v1; 32 | wlr_output_layout* output_layout; 33 | std::list outputs; 34 | std::vector workspaces; 35 | 36 | void register_handlers(Server& server, struct wl_signal* new_output); 37 | 38 | /// Returns the box of an output in the output layout. 39 | NotNullPointer get_output_box(const Output& output) const; 40 | 41 | /// Returns the usable area of the \a output as a rectangle with its coordinates placed in the global (output layout) space. 42 | struct wlr_box get_output_real_usable_area(const Output& output) const; 43 | 44 | /// Returns the output under the given point, if any. 45 | OptionalRef get_output_at(double lx, double ly) const; 46 | 47 | /// Returns true if the \a reference output contains the given point. 48 | bool output_contains_point(const Output& reference, int lx, int ly) const; 49 | 50 | /// Removes \a output from the output list. Doesn't do anything else. 51 | void remove_output_from_list(Output& output); 52 | 53 | /// Creates a new workspace, without any assigned output. 54 | Workspace& create_workspace(Server* server); 55 | Workspace& get_view_workspace(View&); 56 | 57 | void set_dirty(); 58 | 59 | static void output_manager_apply_handler(wl_listener* listener, void* data); 60 | 61 | static void output_manager_test_handler(wl_listener* listener, void* data); 62 | 63 | private: 64 | 65 | /** 66 | * \brief Executed when a new output (monitor) is attached. 67 | * 68 | * It adds the output to \a output_layout. The output 69 | * will be processed further after \a output_layout signals 70 | * that the output has been added and configured. 71 | * 72 | * \sa output_layout_add_handler Called after \a OutputManager::output_layout adds and configures the output. 73 | */ 74 | static void new_output_handler(struct wl_listener* listener, void* data); 75 | 76 | /** 77 | * \brief Processes the output after it has been configured by \a output_layout. 78 | * 79 | * After that, the compositor stores it and registers some event handlers. 80 | * The compositor then assigns a workspace to this output, creating one if none is available. 81 | */ 82 | static void output_layout_add_handler(struct wl_listener* listener, void* data); 83 | }; 84 | 85 | using OutputManagerInstance = std::unique_ptr; 86 | 87 | OutputManagerInstance create_output_manager(Server* server); 88 | 89 | #endif // CARDBOARD_OUTPUT_MANAGER_H_INCLUDED 90 | -------------------------------------------------------------------------------- /cardboard/Xwayland.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_XWAYLAND_H_INCLUDED 12 | #define CARDBOARD_XWAYLAND_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | } 17 | 18 | #include "View.h" 19 | 20 | #include 21 | 22 | class XwaylandView final : public View { 23 | public: 24 | // This is pretty ugly, but remember that Xwayland by itself is ugly. 25 | // Views are supposed to not share state with the server, but X being X needs some info. 26 | Server* server; 27 | 28 | struct wlr_xwayland_surface* xwayland_surface; 29 | /// Stores listeners that are active only when the view is mapped. They are removed when unmapping. 30 | std::array map_unmap_listeners; 31 | 32 | XwaylandView(Server* server, struct wlr_xwayland_surface* xwayland_surface); 33 | ~XwaylandView() = default; 34 | 35 | struct wlr_surface* get_surface() final; 36 | bool get_surface_under_coords(double lx, double ly, struct wlr_surface*& surface, double& sx, double& sy) final; 37 | void resize(int width, int height) final; 38 | void move(OutputManager&, int x, int y) final; 39 | void prepare(Server& server) final; 40 | void set_activated(bool activated) final; 41 | void set_fullscreen(bool fullscreen) final; 42 | void for_each_surface(wlr_surface_iterator_func_t iterator, void* data) final; 43 | bool is_transient_for(View& ancestor) final; 44 | void close_popups() final; 45 | void close() final; 46 | 47 | void destroy(); 48 | void unmap(); 49 | 50 | public: 51 | static void surface_map_handler(struct wl_listener* listener, void* data); 52 | static void surface_unmap_handler(struct wl_listener* listener, void* data); 53 | static void surface_destroy_handler(struct wl_listener* listener, void* data); 54 | static void surface_request_configure_handler(struct wl_listener* listener, void* data); 55 | 56 | private: 57 | static void surface_commit_handler(struct wl_listener* listener, void* data); 58 | static void surface_request_fullscreen_handler(struct wl_listener* listener, void* data); 59 | }; 60 | 61 | /// An "unmanaged" Xwayland surface. The "OR" stands for Override Redirect. Stuff like menus and tooltips. 62 | struct XwaylandORSurface { 63 | Server* server; 64 | struct wlr_xwayland_surface* xwayland_surface; 65 | struct wl_listener* commit_listener; 66 | int lx, ly; 67 | bool mapped; 68 | 69 | bool get_surface_under_coords(double lx, double ly, struct wlr_surface*& surface, double& sx, double& sy); 70 | void map(Server& server); 71 | 72 | public: 73 | static void surface_map_handler(struct wl_listener* listener, void* data); 74 | static void surface_unmap_handler(struct wl_listener* listener, void* data); 75 | static void surface_destroy_handler(struct wl_listener* listener, void* data); 76 | static void surface_request_configure_handler(struct wl_listener* listener, void* data); 77 | 78 | private: 79 | static void surface_commit_handler(struct wl_listener* listener, void* data); 80 | }; 81 | 82 | /// Registers an XwaylandORSurface. 83 | XwaylandORSurface* create_xwayland_or_surface(Server& server, struct wlr_xwayland_surface* xwayland_surface); 84 | 85 | #endif // CARDBOARD_XWAYLAND_H_INCLUDED 86 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: false 11 | AlignTrailingComments: false 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: false 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: true 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: All 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: true 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: false 26 | BinPackParameters: false 27 | BraceWrapping: 28 | AfterCaseLabel: true 29 | AfterClass: false 30 | AfterControlStatement: true 31 | AfterEnum: true 32 | AfterFunction: true 33 | AfterNamespace: false 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: false 37 | AfterExternBlock: false 38 | BeforeCatch: true 39 | BeforeElse: true 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: All 45 | BreakBeforeBraces: WebKit 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeComma 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 0 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: false 60 | DerivePointerAlignment: false 61 | DisableFormat: false 62 | ExperimentalAutoDetectBinPacking: false 63 | FixNamespaceComments: false 64 | ForEachMacros: 65 | - foreach 66 | - Q_FOREACH 67 | - BOOST_FOREACH 68 | IncludeBlocks: Preserve 69 | IncludeCategories: 70 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 71 | Priority: 2 72 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 73 | Priority: 3 74 | - Regex: '.*' 75 | Priority: 1 76 | IncludeIsMainRegex: '(Test)?$' 77 | IndentCaseLabels: false 78 | IndentPPDirectives: None 79 | IndentWidth: 4 80 | IndentWrappedFunctionNames: false 81 | JavaScriptQuotes: Leave 82 | JavaScriptWrapImports: true 83 | KeepEmptyLinesAtTheStartOfBlocks: true 84 | MacroBlockBegin: '' 85 | MacroBlockEnd: '' 86 | MaxEmptyLinesToKeep: 1 87 | NamespaceIndentation: Inner 88 | ObjCBinPackProtocolList: Auto 89 | ObjCBlockIndentWidth: 4 90 | ObjCSpaceAfterProperty: true 91 | ObjCSpaceBeforeProtocolList: true 92 | PenaltyBreakAssignment: 2 93 | PenaltyBreakBeforeFirstCallParameter: 19 94 | PenaltyBreakComment: 300 95 | PenaltyBreakFirstLessLess: 120 96 | PenaltyBreakString: 1000 97 | PenaltyBreakTemplateDeclaration: 10 98 | PenaltyExcessCharacter: 1000000 99 | PenaltyReturnTypeOnItsOwnLine: 60 100 | PointerAlignment: Left 101 | ReflowComments: true 102 | SortIncludes: true 103 | SortUsingDeclarations: true 104 | SpaceAfterCStyleCast: false 105 | SpaceAfterLogicalNot: false 106 | SpaceAfterTemplateKeyword: true 107 | SpaceBeforeAssignmentOperators: true 108 | SpaceBeforeCpp11BracedList: true 109 | SpaceBeforeCtorInitializerColon: true 110 | SpaceBeforeInheritanceColon: true 111 | SpaceBeforeParens: ControlStatements 112 | SpaceBeforeRangeBasedForLoopColon: true 113 | SpaceInEmptyParentheses: false 114 | SpacesBeforeTrailingComments: 1 115 | SpacesInAngles: false 116 | SpacesInContainerLiterals: true 117 | SpacesInCStyleCastParentheses: false 118 | SpacesInParentheses: false 119 | SpacesInSquareBrackets: false 120 | Standard: Cpp11 121 | StatementMacros: 122 | - Q_UNUSED 123 | - QT_REQUIRE_VERSION 124 | TabWidth: 8 125 | UseTab: Never 126 | ... 127 | 128 | -------------------------------------------------------------------------------- /libcardboard/include/cardboard/command_protocol.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef LIBCARDBOARD_COMMAND_PROTOCOL_H_INCLUDED 12 | #define LIBCARDBOARD_COMMAND_PROTOCOL_H_INCLUDED 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | /// Structs representing arguments for IPC commands. 23 | namespace command_arguments { 24 | struct quit { 25 | int code; 26 | }; 27 | 28 | struct focus { 29 | enum class Direction { 30 | Left, 31 | Right, 32 | Up, 33 | Down, 34 | } direction; 35 | 36 | bool cycle; 37 | }; 38 | 39 | struct bind; 40 | 41 | struct exec { 42 | std::vector argv; 43 | }; 44 | 45 | struct close { 46 | }; 47 | 48 | struct workspace { 49 | struct switch_ { 50 | int n; 51 | }; 52 | 53 | struct move { 54 | int n; 55 | }; 56 | 57 | std::variant workspace; 58 | }; 59 | 60 | struct toggle_floating { 61 | }; 62 | 63 | struct move { 64 | int dx, dy; 65 | }; 66 | 67 | struct resize { 68 | int width, height; 69 | }; 70 | 71 | struct insert_into_column { 72 | }; 73 | 74 | struct pop_from_column { 75 | }; 76 | 77 | struct config { 78 | struct mouse_mod { 79 | std::vector modifiers; 80 | }; 81 | 82 | struct gap { 83 | int gap; 84 | }; 85 | 86 | struct focus_color { 87 | float r, g, b, a; 88 | }; 89 | 90 | std::variant config; 91 | }; 92 | 93 | struct cycle_width { 94 | }; 95 | } 96 | 97 | /** 98 | * \brief Variant that contains the arguments of each accepted command, as a different type. 99 | */ 100 | using CommandData = std::variant< 101 | command_arguments::quit, 102 | command_arguments::focus, 103 | command_arguments::exec, 104 | command_arguments::bind, 105 | command_arguments::close, 106 | command_arguments::workspace, 107 | command_arguments::toggle_floating, 108 | command_arguments::move, 109 | command_arguments::resize, 110 | command_arguments::insert_into_column, 111 | command_arguments::pop_from_column, 112 | command_arguments::config, 113 | command_arguments::cycle_width>; 114 | 115 | namespace command_arguments { 116 | struct bind { 117 | std::vector modifiers; 118 | std::string key; 119 | std::unique_ptr command; 120 | 121 | bind() = default; 122 | 123 | bind(std::vector modifiers, std::string key, CommandData command) 124 | : modifiers { std::move(modifiers) } 125 | , key { std::move(key) } 126 | , command { std::make_unique(std::move(command)) } 127 | { 128 | } 129 | 130 | bind(const bind& other) 131 | : modifiers { other.modifiers } 132 | , key { other.key } 133 | , command { std::make_unique(*other.command) } 134 | { 135 | } 136 | 137 | bind(bind&&) = default; 138 | bind& operator=(bind&&) = default; 139 | 140 | bind& operator=(const bind& other) 141 | { 142 | if (&other == this) 143 | return *this; 144 | 145 | modifiers = other.modifiers; 146 | key = other.key; 147 | command = std::make_unique(*other.command); 148 | 149 | return *this; 150 | } 151 | }; 152 | } 153 | 154 | /** 155 | * \brief Deserializes data from a region of memory 156 | */ 157 | tl::expected read_command_data(void* data, size_t); 158 | 159 | /** 160 | * \brief Serializes CommandData type data into a std::string buffer 161 | */ 162 | tl::expected write_command_data(const CommandData&); 163 | 164 | #endif //LIBCARDBOARD_COMMAND_PROTOCOL_H_INCLUDED 165 | -------------------------------------------------------------------------------- /cardboard/OptionalRef.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_OPTIONAL_REF_H_INCLUDED 12 | #define CARDBOARD_OPTIONAL_REF_H_INCLUDED 13 | 14 | #include 15 | #include 16 | 17 | #include "NotNull.h" 18 | 19 | /** 20 | * \brief Represents a reference that may or may not be null. 21 | * 22 | * You must check for the reference to not be null (with OptionalRef::has_value) 23 | * before calling OptionalRef::unwrap to get the reference. 24 | * 25 | * You can also run a function with the reference if it is not null, see OptionalRef::and_then 26 | * and OptionalRef::or_else. 27 | */ 28 | template 29 | class OptionalRef { 30 | public: 31 | OptionalRef() 32 | : ptr(nullptr) 33 | { 34 | } 35 | OptionalRef(T& ref) 36 | : ptr(&ref) 37 | { 38 | } 39 | explicit OptionalRef(T* ptr) 40 | : ptr(ptr) 41 | { 42 | } 43 | 44 | explicit OptionalRef(std::nullptr_t) 45 | : ptr(nullptr) 46 | { 47 | } 48 | 49 | T& unwrap() 50 | { 51 | assert(ptr != nullptr); 52 | 53 | return *ptr; 54 | } 55 | 56 | /// Returns the reference. An assertion error will be generated if the reference is null. 57 | const T& unwrap() const 58 | { 59 | assert(ptr != nullptr); 60 | 61 | return *ptr; 62 | } 63 | 64 | /// Returns true if the reference is not null. 65 | bool has_value() const noexcept 66 | { 67 | return ptr != nullptr; 68 | } 69 | 70 | /// Returns true if the reference is not null. 71 | explicit operator bool() const noexcept 72 | { 73 | return has_value(); 74 | } 75 | 76 | /// Checks if both OptionalRefs point to the same value, or if they are both null. 77 | bool operator==(const OptionalRef& other) const 78 | { 79 | return ptr == other.ptr; 80 | } 81 | 82 | /// Checks if the two OptionalRefs point to different values, or if they are not both null. 83 | bool operator!=(const OptionalRef& other) const 84 | { 85 | return ptr != other.ptr; 86 | } 87 | 88 | /** 89 | * \brief Calls and returns the return value of \a f if the reference is not null. 90 | * 91 | * \a f is a callable with the type OptionalRef(T&). 92 | */ 93 | template 94 | OptionalRef and_then(F&& f) 95 | { 96 | if (has_value()) { 97 | return f(unwrap()); 98 | } 99 | 100 | return OptionalRef(nullptr); 101 | } 102 | 103 | /** 104 | * \brief Calls \a f if the reference is not null. 105 | * 106 | * \a f is a callable with the type void(T&). 107 | */ 108 | template 109 | void and_then(F&& f) 110 | { 111 | if (has_value()) { 112 | f(unwrap()); 113 | } 114 | } 115 | 116 | /** 117 | * \brief Calls \a f if the reference is null and returns its return value. 118 | * 119 | * \a f is a callable with the type OptionalRef(T&). 120 | * */ 121 | template 122 | OptionalRef or_else(F&& f) 123 | { 124 | if (!has_value()) { 125 | return f(); 126 | } 127 | 128 | return *this; 129 | } 130 | 131 | /** 132 | * \brief Gets the raw_pointer, could be useful. Doesn't check for nullptr. 133 | */ 134 | T* raw_pointer() 135 | { 136 | return ptr; 137 | } 138 | 139 | const T* raw_pointer() const 140 | { 141 | return ptr; 142 | } 143 | 144 | private: 145 | T* ptr; 146 | }; 147 | 148 | /// The null OptionalRef. 149 | template 150 | const auto NullRef = OptionalRef(nullptr); 151 | 152 | template 153 | OptionalRef(NotNullPointer nnptr)->OptionalRef; 154 | 155 | #endif // CARDBOARD_OPTIONAL_REF_H_INCLUDED 156 | -------------------------------------------------------------------------------- /cardboard/Keyboard.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_KEYBOARD_H_INCLUDED 12 | #define CARDBOARD_KEYBOARD_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | } 18 | 19 | #include 20 | 21 | #include "Command.h" 22 | #include "NotNull.h" 23 | 24 | /** 25 | * \file 26 | * \brief This file has event handlers for keyboard-related events and an implementation 27 | * of key bindings. 28 | */ 29 | 30 | struct Server; 31 | struct Seat; 32 | 33 | /// A keyboard device, managed by a seat. 34 | struct Keyboard { 35 | struct wlr_input_device* device; 36 | 37 | private: 38 | // i can't make a friend only the required method (Seat::add_keyboard) because 39 | // that would be a cyclic dependency 40 | friend struct Seat; 41 | }; 42 | 43 | /** 44 | * \brief This structure holds the configured key bindings. 45 | * 46 | * Key bindings are an association between a sequence of pressed modifiers and one normal key on the keyboard, 47 | * and an IPC command to call when the keys are pressed together. 48 | * 49 | * With \c cutter, binding a terminal to alt+return (Alt key and Enter): 50 | * 51 | * \code{.sh} 52 | * cutter bind alt+return exec terminal 53 | * \endcode 54 | * 55 | * Is equivalent to calling the following command when Alt and Return are pressed together: 56 | * 57 | * \code{.sh} 58 | * cutter exec terminal 59 | * \endcode 60 | */ 61 | struct KeybindingsConfig { 62 | static_assert(WLR_MODIFIER_COUNT <= 12, "too many modifiers"); 63 | 64 | /** 65 | * \brief An array of key binding maps, for each modifier combination. 66 | * 67 | * The array index is the mod mask of the key binding. 68 | * 69 | * They key of each map is the lowercase variant of the keysym, and the value is 70 | * a pair of an IPC command handler and its arguments. 71 | * 72 | * \attention The \c keysym \b must be the lowercase variant! Key bindings containing uppercase characters 73 | * will have the shift mod mask set. 74 | * 75 | * For example, to retrieve the IPC command for the super + shift + x binding: 76 | * 77 | * \code{.cpp} 78 | * map[WLR_MODIFIER_LOGO | WLR_MODIFIER_SHIFT][XKB_KEY_x] // notice the lowercase `x` 79 | * \endcode 80 | */ 81 | std::unordered_map map[(1 << WLR_MODIFIER_COUNT) - 1]; 82 | }; 83 | 84 | /** 85 | * \brief Object that is passed to keyboard-related handlers for context. 86 | */ 87 | struct KeyboardHandleData { 88 | NotNullPointer seat; ///< The seat of the device 89 | NotNullPointer keyboard; ///< The device from which the key handling event arised 90 | NotNullPointer config; ///< Pointer to the global key binding configuration 91 | 92 | bool operator==(KeyboardHandleData other) const 93 | { 94 | return keyboard == other.keyboard && config == other.config; 95 | } 96 | 97 | private: 98 | /** 99 | * \brief Fired when a non-modifier key is pressed. 100 | * 101 | * Executes key binding commands if the key together with the currently active 102 | * modifiers match. Else, sends the key to the surface currently holding keyboard focus. 103 | */ 104 | static void key_handler(struct wl_listener* listener, void* data); 105 | 106 | /// Signals that a keyboard has been disconnected. 107 | static void destroy_handler(struct wl_listener* listener, void* data); 108 | 109 | /// Notifies the currently focused surface about the pressed state of the modifier keys. 110 | static void modifiers_handler(struct wl_listener* listener, void* data); 111 | 112 | friend void register_keyboard_handlers(Server& server, Seat& seat, Keyboard& keyboard); 113 | }; 114 | 115 | void register_keyboard_handlers(Server& server, Seat& seat, Keyboard& keyboard); 116 | 117 | #endif // CARDBOARD_KEYBOARD_H_INCLUDED 118 | -------------------------------------------------------------------------------- /libcardboard/src/client.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace libcutter { 19 | 20 | libcutter::Client::~Client() 21 | { 22 | close(socket_fd); 23 | } 24 | 25 | tl::expected libcutter::Client::send_command(const CommandData& command_data) 26 | { 27 | using namespace std::string_literals; 28 | 29 | auto buffer = write_command_data(command_data); 30 | 31 | if (!buffer) { 32 | return tl::unexpected(buffer.error()); 33 | } 34 | 35 | libcardboard::ipc::AlignedHeaderBuffer header_buffer = libcardboard::ipc::create_header_buffer({ static_cast(buffer->size() + 1) }); 36 | 37 | if (write(socket_fd, header_buffer.data(), libcardboard::ipc::HEADER_SIZE) == -1) { 38 | int err = errno; 39 | return tl::unexpected("unable to write payload: "s + strerror(err)); 40 | } 41 | 42 | if (write(socket_fd, buffer->c_str(), buffer->size() + 1) == -1) { 43 | int err = errno; 44 | return tl::unexpected("unable to write payload: "s + strerror(err)); 45 | } 46 | 47 | return {}; 48 | } 49 | 50 | tl::expected libcutter::Client::wait_response() 51 | { 52 | libcardboard::ipc::AlignedHeaderBuffer buffer; 53 | 54 | if (recv(socket_fd, buffer.data(), libcardboard::ipc::HEADER_SIZE, 0) == libcardboard::ipc::HEADER_SIZE) { 55 | libcardboard::ipc::Header header = libcardboard::ipc::interpret_header(buffer); 56 | 57 | if (header.incoming_bytes == 0) { 58 | return std::string {}; 59 | } 60 | 61 | auto* response_buffer = new std::byte[header.incoming_bytes]; 62 | if (recv(socket_fd, response_buffer, sizeof(header.incoming_bytes), 0) == header.incoming_bytes) { 63 | return std::string(reinterpret_cast(response_buffer)); 64 | } else { 65 | int err = errno; 66 | return tl::unexpected(err); 67 | } 68 | } else { 69 | int err = errno; 70 | 71 | if (err == EBADF) { 72 | return std::string {}; 73 | } 74 | 75 | return tl::unexpected(err); 76 | } 77 | } 78 | 79 | Client::Client(int socket_fd, std::unique_ptr socket_address) 80 | : socket_fd { socket_fd } 81 | , socket_address { std::move(socket_address) } 82 | { 83 | } 84 | 85 | Client::Client(Client&& other) noexcept 86 | : socket_fd { other.socket_fd } 87 | , socket_address { std::move(other.socket_address) } 88 | { 89 | other.socket_fd = -1; 90 | } 91 | 92 | tl::expected open_client() 93 | { 94 | using namespace std::string_literals; 95 | 96 | int socket_fd = -1; 97 | if (socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); socket_fd == -1) { 98 | return tl::unexpected("Failed to create socket"s); 99 | } 100 | 101 | std::string socket_path; 102 | if (char* socket_env = getenv(libcardboard::ipc::SOCKET_ENV_VAR); socket_env != nullptr) { 103 | socket_path = socket_env; 104 | } else { 105 | if (char* wayland_env = getenv("WAYLAND_DISPLAY"); wayland_env != nullptr) { 106 | socket_path = "/tmp/cardboard-"s + wayland_env; 107 | } else { 108 | return tl::unexpected("WAYLAND_DISPLAY not set"s); 109 | } 110 | } 111 | 112 | auto socket_address = std::make_unique(); 113 | socket_address->sun_family = AF_UNIX; 114 | strncpy(socket_address->sun_path, socket_path.c_str(), socket_path.size()); 115 | 116 | if ( 117 | connect( 118 | socket_fd, 119 | reinterpret_cast(socket_address.get()), 120 | sizeof(sockaddr_un)) 121 | == -1) { 122 | close(socket_fd); 123 | return tl::unexpected("Unable to connect"s); 124 | } 125 | 126 | return Client { 127 | socket_fd, 128 | std::move(socket_address) 129 | }; 130 | } 131 | } -------------------------------------------------------------------------------- /libcardboard/src/command_protocol.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | /// \cond IGNORE 26 | namespace cereal { 27 | 28 | template 29 | void serialize(Archive& ar, command_arguments::quit& quit) 30 | { 31 | ar(quit.code); 32 | } 33 | 34 | template 35 | void serialize(Archive& ar, command_arguments::focus& focus) 36 | { 37 | ar(focus.direction, focus.cycle); 38 | } 39 | 40 | template 41 | void serialize(Archive& ar, command_arguments::exec& exec) 42 | { 43 | ar(exec.argv); 44 | } 45 | 46 | template 47 | void serialize(Archive& ar, command_arguments::bind& bind) 48 | { 49 | ar(bind.modifiers, bind.key, bind.command); 50 | } 51 | 52 | template 53 | void serialize(Archive&, command_arguments::close&) 54 | { 55 | } 56 | 57 | template 58 | void serialize(Archive& ar, command_arguments::workspace::switch_& switch_) 59 | { 60 | ar(switch_.n); 61 | } 62 | 63 | template 64 | void serialize(Archive& ar, command_arguments::workspace::move& move) 65 | { 66 | ar(move.n); 67 | } 68 | 69 | template 70 | void serialize(Archive& ar, command_arguments::workspace& workspace) 71 | { 72 | ar(workspace.workspace); 73 | } 74 | 75 | template 76 | void serialize(Archive&, command_arguments::toggle_floating&) 77 | { 78 | } 79 | 80 | template 81 | void serialize(Archive& ar, command_arguments::move& move) 82 | { 83 | ar(move.dx, move.dy); 84 | } 85 | 86 | template 87 | void serialize(Archive& ar, command_arguments::resize& resize) 88 | { 89 | ar(resize.width, resize.height); 90 | } 91 | 92 | template 93 | void serialize(Archive&, command_arguments::insert_into_column&) 94 | { 95 | } 96 | 97 | template 98 | void serialize(Archive&, command_arguments::pop_from_column&) 99 | { 100 | } 101 | 102 | template 103 | void serialize(Archive& ar, command_arguments::config::mouse_mod& mouse_mod) 104 | { 105 | ar(mouse_mod.modifiers); 106 | } 107 | 108 | template 109 | void serialize(Archive& ar, command_arguments::config::gap& gap) 110 | { 111 | ar(gap.gap); 112 | } 113 | 114 | template 115 | void serialize(Archive& ar, command_arguments::config::focus_color& focus_color) 116 | { 117 | ar(focus_color.r, focus_color.g, focus_color.b, focus_color.a); 118 | } 119 | 120 | template 121 | void serialize(Archive& ar, command_arguments::config& config) 122 | { 123 | ar(config.config); 124 | } 125 | 126 | template 127 | void serialize(Archive&, command_arguments::cycle_width&) 128 | { 129 | } 130 | } 131 | /// \endcond 132 | 133 | tl::expected read_command_data(void* data, size_t size) 134 | { 135 | try { 136 | std::string buffer(static_cast(data), size); 137 | std::istringstream buffer_stream { buffer }; 138 | cereal::PortableBinaryInputArchive archive { buffer_stream }; 139 | 140 | CommandData command_data; 141 | archive(command_data); 142 | 143 | return command_data; 144 | } catch (const cereal::Exception& e) { 145 | return tl::unexpected(std::string { e.what() }); 146 | } 147 | } 148 | 149 | tl::expected write_command_data(const CommandData& command_data) 150 | { 151 | using namespace std::string_literals; 152 | 153 | std::stringstream buffer_stream; 154 | 155 | { 156 | cereal::PortableBinaryOutputArchive archive { buffer_stream }; 157 | archive(command_data); 158 | } 159 | 160 | return buffer_stream.str(); 161 | } 162 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: spec: 10 | if spec.builtin or true then 11 | builtins_fetchurl { inherit (spec) url sha256; } 12 | else 13 | pkgs.fetchurl { inherit (spec) url sha256; }; 14 | 15 | fetch_tarball = pkgs: spec: 16 | if spec.builtin or true then 17 | builtins_fetchTarball { inherit (spec) url sha256; } 18 | else 19 | pkgs.fetchzip { inherit (spec) url sha256; }; 20 | 21 | fetch_git = spec: 22 | builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; 23 | 24 | fetch_builtin-tarball = spec: 25 | builtins.trace 26 | '' 27 | WARNING: 28 | The niv type "builtin-tarball" will soon be deprecated. You should 29 | instead use `builtin = true`. 30 | 31 | $ niv modify -a type=tarball -a builtin=true 32 | '' 33 | builtins_fetchTarball { inherit (spec) url sha256; }; 34 | 35 | fetch_builtin-url = spec: 36 | builtins.trace 37 | '' 38 | WARNING: 39 | The niv type "builtin-url" will soon be deprecated. You should 40 | instead use `builtin = true`. 41 | 42 | $ niv modify -a type=file -a builtin=true 43 | '' 44 | (builtins_fetchurl { inherit (spec) url sha256; }); 45 | 46 | # 47 | # Various helpers 48 | # 49 | 50 | # The set of packages used when specs are fetched using non-builtins. 51 | mkPkgs = sources: 52 | let 53 | sourcesNixpkgs = 54 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; 55 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 56 | hasThisAsNixpkgsPath = == ./.; 57 | in 58 | if builtins.hasAttr "nixpkgs" sources 59 | then sourcesNixpkgs 60 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 61 | import {} 62 | else 63 | abort 64 | '' 65 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 66 | add a package called "nixpkgs" to your sources.json. 67 | ''; 68 | 69 | # The actual fetching function. 70 | fetch = pkgs: name: spec: 71 | 72 | if ! builtins.hasAttr "type" spec then 73 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 74 | else if spec.type == "file" then fetch_file pkgs spec 75 | else if spec.type == "tarball" then fetch_tarball pkgs spec 76 | else if spec.type == "git" then fetch_git spec 77 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec 78 | else if spec.type == "builtin-url" then fetch_builtin-url spec 79 | else 80 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 81 | 82 | # Ports of functions for older nix versions 83 | 84 | # a Nix version of mapAttrs if the built-in doesn't exist 85 | mapAttrs = builtins.mapAttrs or ( 86 | f: set: with builtins; 87 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 88 | ); 89 | 90 | # fetchTarball version that is compatible between all the versions of Nix 91 | builtins_fetchTarball = { url, sha256 }@attrs: 92 | let 93 | inherit (builtins) lessThan nixVersion fetchTarball; 94 | in 95 | if lessThan nixVersion "1.12" then 96 | fetchTarball { inherit url; } 97 | else 98 | fetchTarball attrs; 99 | 100 | # fetchurl version that is compatible between all the versions of Nix 101 | builtins_fetchurl = { url, sha256 }@attrs: 102 | let 103 | inherit (builtins) lessThan nixVersion fetchurl; 104 | in 105 | if lessThan nixVersion "1.12" then 106 | fetchurl { inherit url; } 107 | else 108 | fetchurl attrs; 109 | 110 | # Create the final "sources" from the config 111 | mkSources = config: 112 | mapAttrs ( 113 | name: spec: 114 | if builtins.hasAttr "outPath" spec 115 | then abort 116 | "The values in sources.json should not have an 'outPath' attribute" 117 | else 118 | spec // { outPath = fetch config.pkgs name spec; } 119 | ) config.sources; 120 | 121 | # The "config" used by the fetchers 122 | mkConfig = 123 | { sourcesFile ? ./sources.json 124 | , sources ? builtins.fromJSON (builtins.readFile sourcesFile) 125 | , pkgs ? mkPkgs sources 126 | }: rec { 127 | # The sources, i.e. the attribute set of spec name to spec 128 | inherit sources; 129 | 130 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 131 | inherit pkgs; 132 | }; 133 | in 134 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 135 | -------------------------------------------------------------------------------- /cardboard/Listener.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_LISTENER_H 12 | #define CARDBOARD_LISTENER_H 13 | 14 | #include "BuildConfig.h" 15 | 16 | extern "C" { 17 | #include 18 | #include 19 | #include 20 | #include 21 | } 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "Cursor.h" 29 | #include "Keyboard.h" 30 | #include "Layers.h" 31 | #include "XDGView.h" 32 | #if HAVE_XWAYLAND 33 | #include "Xwayland.h" 34 | #endif 35 | 36 | struct NoneT { 37 | }; 38 | struct Server; 39 | 40 | /// Constant data for many event listeners. 41 | using ListenerData = std::variant< 42 | NoneT, 43 | KeyboardHandleData, 44 | LayerSurface*, 45 | LayerSurfacePopup*, 46 | Output*, 47 | OutputManager*, 48 | SeatCursor*, 49 | Seat*, 50 | #if HAVE_XWAYLAND 51 | XwaylandView*, 52 | XwaylandORSurface*, 53 | #endif 54 | XDGView*, 55 | XDGPopup*>; 56 | 57 | /** 58 | * \brief The Listener is a wrapper around Wayland's \c wl_listener concept. 59 | * 60 | * It holds context information related to an event handler call. 61 | */ 62 | struct Listener { 63 | wl_listener listener; 64 | Server* server; 65 | ListenerData listener_data; 66 | 67 | Listener(wl_notify_func_t notify, Server* server, ListenerData listener_data) 68 | : listener { {}, notify } 69 | , server { server } 70 | , listener_data { std::move(listener_data) } 71 | { 72 | } 73 | }; 74 | 75 | /** 76 | * \brief Returns a pointer to the Server struct from within a \a listener 77 | * given to a Wayland event handler function. 78 | */ 79 | inline Server* get_server(wl_listener* listener) 80 | { 81 | Listener* l = wl_container_of(listener, l, listener); 82 | return l->server; 83 | } 84 | 85 | /** 86 | * \brief Returns the enclosed data from a \a listener. 87 | */ 88 | template 89 | T& get_listener_data(wl_listener* listener) 90 | { 91 | Listener* l = wl_container_of(listener, l, listener); 92 | return std::get(l->listener_data); 93 | } 94 | 95 | /** 96 | * \brief Holds the event listeners and Listener objects for all the event handlers 97 | * registered during the lifetime of the compositor. 98 | */ 99 | class ListenerList { 100 | public: 101 | ~ListenerList() 102 | { 103 | for (auto& listener : listeners) 104 | wl_list_remove(&listener.listener.link); 105 | } 106 | 107 | /// Registers a \a listener for a given \a signal. 108 | wl_listener* add_listener(wl_signal* signal, Listener&& listener) 109 | { 110 | listeners.push_back(std::move(listener)); 111 | wl_signal_add(signal, &listeners.back().listener); 112 | 113 | return &listeners.back().listener; 114 | } 115 | 116 | /// Unregisters a Listener object associated with a ray Wayland \a raw_listener. 117 | void remove_listener(wl_listener* raw_listener) 118 | { 119 | wl_list_remove(&raw_listener->link); 120 | 121 | listeners.remove_if([raw_listener](auto& listener) { return raw_listener == &listener.listener; }); 122 | } 123 | 124 | /// Unregisters all event listeners associated with \a owner (i.e. \a owner is the listener data). 125 | template 126 | void clear_listeners(T owner) 127 | { 128 | bool it_works = false; 129 | auto next = listeners.end(); 130 | auto it = listeners.begin(); 131 | while (it != listeners.end()) { 132 | next = std::next(it); 133 | 134 | if (auto pval = std::get_if(&it->listener_data)) { 135 | if (*pval == owner) { 136 | remove_listener(&it->listener); 137 | it_works = true; 138 | } 139 | } 140 | 141 | it = next; 142 | } 143 | 144 | assert(it_works && "this object doesn't have listeners"); 145 | } 146 | 147 | private: 148 | std::list listeners; 149 | }; 150 | 151 | struct ListenerPair { 152 | struct wl_signal* signal; 153 | wl_notify_func_t notify; 154 | }; 155 | 156 | #endif //CARDBOARD_LISTENER_H 157 | -------------------------------------------------------------------------------- /cardboard/Keyboard.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | extern "C" { 12 | #include 13 | #include 14 | #include 15 | #include 16 | } 17 | 18 | #include "Helpers.h" 19 | #include "Keyboard.h" 20 | #include "Server.h" 21 | 22 | void KeyboardHandleData::destroy_handler(struct wl_listener* listener, void*) 23 | { 24 | auto* server = get_server(listener); 25 | auto handle_data = get_listener_data(listener); 26 | 27 | server->listeners.clear_listeners(handle_data); 28 | 29 | handle_data.seat->keyboards.erase(std::remove_if(handle_data.seat->keyboards.begin(), handle_data.seat->keyboards.end(), [&handle_data](const auto& other) { 30 | return handle_data.keyboard == &other; 31 | }), 32 | handle_data.seat->keyboards.end()); 33 | } 34 | 35 | void KeyboardHandleData::modifiers_handler(struct wl_listener* listener, void*) 36 | { 37 | auto handle_data = get_listener_data(listener); 38 | 39 | wlr_seat_set_keyboard(handle_data.seat->wlr_seat, handle_data.keyboard->device); 40 | // send modifiers to the client 41 | wlr_seat_keyboard_notify_modifiers(handle_data.seat->wlr_seat, &handle_data.keyboard->device->keyboard->modifiers); 42 | } 43 | 44 | void KeyboardHandleData::key_handler(struct wl_listener* listener, void* data) 45 | { 46 | Server* server = get_server(listener); 47 | auto handle_data = get_listener_data(listener); 48 | 49 | auto* event = static_cast(data); 50 | 51 | bool handled = false; 52 | uint32_t modifiers = wlr_keyboard_get_modifiers(handle_data.keyboard->device->keyboard); 53 | if (event->state == WLR_KEY_PRESSED) { 54 | const xkb_keysym_t* syms; 55 | int syms_number = xkb_state_key_get_syms( 56 | handle_data.keyboard->device->keyboard->xkb_state, 57 | event->keycode + 8, 58 | &syms); 59 | 60 | // TODO: keybinds that work when there is an exclusive client 61 | if (!handle_data.seat->exclusive_client) { 62 | for (int i = 0; i < syms_number; i++) { 63 | auto& map = handle_data.config->map[modifiers]; 64 | // as you can see below, keysyms are always stored lowercase 65 | if (auto it = map.find(xkb_keysym_to_lower(syms[i])); it != map.end()) { 66 | (it->second)(server); 67 | handled = true; 68 | } 69 | } 70 | } 71 | 72 | // VT changing works even when there is an exclusive client, 73 | // to avoid getting locked out 74 | if (!handled) { 75 | for (int i = 0; i < syms_number; i++) { 76 | if (syms[i] >= XKB_KEY_XF86Switch_VT_1 && syms[i] <= XKB_KEY_XF86Switch_VT_12) { 77 | if (wlr_backend_is_multi(server->backend)) { 78 | if (auto* session = wlr_backend_get_session(server->backend); session) { 79 | auto vt = syms[i] - XKB_KEY_XF86Switch_VT_1 + 1; 80 | wlr_session_change_vt(session, vt); 81 | } 82 | } 83 | handled = true; 84 | } 85 | } 86 | } 87 | } 88 | 89 | if (!handled) { 90 | wlr_seat_set_keyboard(handle_data.seat->wlr_seat, handle_data.keyboard->device); 91 | wlr_seat_keyboard_notify_key(handle_data.seat->wlr_seat, event->time_msec, event->keycode, event->state); 92 | } 93 | } 94 | 95 | void register_keyboard_handlers(Server& server, Seat& seat, Keyboard& keyboard) 96 | { 97 | register_handlers(server, 98 | KeyboardHandleData { &seat, &keyboard, &server.keybindings_config }, 99 | { 100 | { &keyboard.device->keyboard->events.key, KeyboardHandleData::key_handler }, 101 | { &keyboard.device->keyboard->events.modifiers, KeyboardHandleData::modifiers_handler }, 102 | { &keyboard.device->keyboard->events.destroy, KeyboardHandleData::destroy_handler }, 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /cardboard/Cursor.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | extern "C" { 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | } 18 | 19 | #include "Cursor.h" 20 | #include "Server.h" 21 | 22 | static void image_surface_destroy_handler(struct wl_listener* listener, void*) 23 | { 24 | auto* server = get_server(listener); 25 | auto* cursor = get_listener_data(listener); 26 | 27 | cursor_set_image(*server, server->seat, *cursor, nullptr); 28 | cursor_rebase(*server, server->seat, *cursor); 29 | } 30 | 31 | /** 32 | * \brief Registers a destroy handler (SeatCursor::image_surface_destroy_handler) on \a surface 33 | * to correctly unset the surface as the cursor after it gets destroyed. 34 | */ 35 | static void register_image_surface(Server& server, SeatCursor& cursor, struct wlr_surface* surface) 36 | { 37 | cursor.image_surface_destroy_listener = cursor.image_surface_destroy_listener.and_then([&server](auto& listener) { 38 | server.listeners.remove_listener(&listener); 39 | return NullRef; 40 | }); 41 | if (surface) { 42 | cursor.image_surface_destroy_listener = OptionalRef( 43 | server.listeners.add_listener(&surface->events.destroy, 44 | Listener { image_surface_destroy_handler, &server, &cursor })); 45 | } 46 | } 47 | 48 | void init_cursor(OutputManager& output_manager, SeatCursor& cursor) 49 | { 50 | cursor.wlr_cursor = wlr_cursor_create(); 51 | cursor.wlr_cursor->data = &cursor; 52 | wlr_cursor_attach_output_layout(cursor.wlr_cursor, output_manager.output_layout); 53 | 54 | cursor.wlr_xcursor_manager = wlr_xcursor_manager_create(nullptr, 24); 55 | wlr_xcursor_manager_load(cursor.wlr_xcursor_manager, 1); 56 | } 57 | 58 | void cursor_set_image(Server& server, Seat& seat, SeatCursor& cursor, const char* image) 59 | { 60 | if (!(seat.wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) { 61 | return; 62 | } 63 | 64 | register_image_surface(server, cursor, nullptr); 65 | if (!image) { 66 | wlr_cursor_set_image(cursor.wlr_cursor, nullptr, 0, 0, 0, 0, 0, 0); 67 | } else { 68 | wlr_xcursor_manager_set_cursor_image(cursor.wlr_xcursor_manager, image, cursor.wlr_cursor); 69 | } 70 | } 71 | 72 | void cursor_set_image_surface(Server& server, Seat& seat, SeatCursor& cursor, struct wlr_surface* surface, int32_t hotspot_x, int32_t hotspot_y) 73 | { 74 | if (!(seat.wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) { 75 | return; 76 | } 77 | 78 | register_image_surface(server, cursor, surface); 79 | wlr_cursor_set_surface(cursor.wlr_cursor, surface, hotspot_x, hotspot_y); 80 | } 81 | 82 | void cursor_rebase(Server& server, Seat& seat, SeatCursor& cursor, uint32_t time) 83 | { 84 | // time is an optional argument defaulting to 0 85 | if (time == 0) { 86 | struct timespec now; 87 | clock_gettime(CLOCK_MONOTONIC, &now); 88 | time = now.tv_nsec / 1000; 89 | } 90 | 91 | double sx, sy; 92 | struct wlr_surface* surface = nullptr; 93 | server.surface_manager.get_surface_under_cursor(*(server.output_manager), cursor.wlr_cursor->x, cursor.wlr_cursor->y, surface, sx, sy); 94 | if (!surface) { 95 | // set the cursor to default 96 | cursor_set_image(server, seat, cursor, "left_ptr"); 97 | } 98 | if (surface && seat.is_input_allowed(surface)) { 99 | bool focus_changed = seat.wlr_seat->pointer_state.focused_surface != surface; 100 | // Gives pointer focus when the cursor enters the surface 101 | wlr_seat_pointer_notify_enter(seat.wlr_seat, surface, sx, sy); 102 | if (!focus_changed) { 103 | // the enter event contains coordinates, so we notify on motion only 104 | // if the focus did not change 105 | wlr_seat_pointer_notify_motion(seat.wlr_seat, time, sx, sy); 106 | } 107 | } else { 108 | // Clear focus so pointer events are not sent to the last entered surface 109 | wlr_seat_pointer_clear_focus(seat.wlr_seat); 110 | } 111 | } 112 | void cursor_warp(Server& server, Seat& seat, SeatCursor& cursor, int lx, int ly) 113 | { 114 | wlr_cursor_warp(cursor.wlr_cursor, nullptr, lx, ly); 115 | seat.process_cursor_motion(server); 116 | } 117 | -------------------------------------------------------------------------------- /cardboard/IPC.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_IPC_H_INCLUDED 12 | #define CARDBOARD_IPC_H_INCLUDED 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include "Listener.h" 22 | #include "NotNull.h" 23 | 24 | /** 25 | * \file 26 | * \brief This file contains functions and structures related to the IPC mechanism of Cardboard. 27 | * 28 | * Cardboard uses socket-based IPC to communicate with client programs that can send commands and modify 29 | * the configuration of the running compositor. Settings are loaded by running a script (usually a plain shell script) 30 | * that invokes such commands. 31 | * 32 | * The bundled, CLI client is named \c cutter. 33 | */ 34 | 35 | struct Server; 36 | class IPC; 37 | using IPCInstance = std::unique_ptr; 38 | 39 | /** 40 | * \brief Manages all incoming client connections, communicating with them using the Cardboard IPC protocol 41 | */ 42 | class IPC { 43 | /** 44 | * \brief describes the stage of the client-server protocol communication of a client 45 | */ 46 | enum class ClientState { 47 | READING_HEADER, 48 | READING_PAYLOAD, 49 | WRITING 50 | }; 51 | 52 | /** 53 | * \brief the state of a single client 54 | */ 55 | struct Client { 56 | IPC* ipc; 57 | int client_fd; 58 | ClientState state; 59 | wl_event_source* readable_event_source = nullptr; 60 | wl_event_source* writable_event_source = nullptr; 61 | int payload_size = 0; 62 | std::string message {}; 63 | 64 | Client(IPC* ipc, int client_fd, ClientState state) 65 | : ipc(ipc) 66 | , client_fd(client_fd) 67 | , state(state) 68 | { 69 | } 70 | Client(const Client&) = delete; 71 | 72 | Client(Client&&) noexcept; 73 | 74 | /** 75 | * \brief disconnects the client and removes the event sources from wayland's book keeping 76 | */ 77 | ~Client(); 78 | }; 79 | 80 | private: 81 | explicit IPC( 82 | Server* server, 83 | int socket_fd, 84 | std::unique_ptr&& socket_address, 85 | std::function&& command_callback) 86 | : server { server } 87 | , socket_fd { socket_fd } 88 | , socket_address { std::move(socket_address) } 89 | , command_callback { std::move(command_callback) } 90 | { 91 | } 92 | 93 | public: 94 | IPC() = delete; 95 | IPC(const IPC&) = delete; 96 | IPC(IPC&&) = default; 97 | 98 | private: 99 | /** 100 | * \brief the callback wayland calls when a client is trying to connect 101 | */ 102 | static int handle_client_connection(int fd, uint32_t mask, void* data); 103 | 104 | /** 105 | * \brief the callback wayland calls when data is available on a client connection file descriptor 106 | */ 107 | static int handle_client_readable(int fd, uint32_t mask, void* data); 108 | 109 | /** 110 | * \brief the callback wayland calls a client connection file descriptor can be written to 111 | */ 112 | static int handle_client_writeable(int fd, uint32_t mask, void* data); 113 | 114 | private: 115 | /** 116 | * \brief removes a client from the clients list - thus disconnecting it as well 117 | */ 118 | void remove_client(Client*); 119 | 120 | private: 121 | ListenerList ipc_listeners; 122 | NotNullPointer server; 123 | int socket_fd; 124 | std::unique_ptr socket_address; 125 | std::function command_callback; 126 | 127 | std::list clients; 128 | 129 | friend std::optional create_ipc(Server& server, const std::string& socket_path, std::function command_callback); 130 | }; 131 | 132 | /** 133 | * \brief Creates an IPC instance 134 | * \param server the server that handles the ipc instance 135 | * \param socket_path the path of the system where the socket path will be found 136 | * \param command_callback callable that will be called when a command will be received 137 | * \return returns the newly created IPC instance 138 | */ 139 | std::optional create_ipc(Server& server, const std::string& socket_path, std::function command_callback); 140 | 141 | #endif // CARDBOARD_IPC_H_INCLUDED 142 | -------------------------------------------------------------------------------- /cardboard/Server.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_SERVER_H_INCLUDED 12 | #define CARDBOARD_SERVER_H_INCLUDED 13 | 14 | #include "BuildConfig.h" 15 | 16 | extern "C" { 17 | #include 18 | #define static 19 | #include 20 | #undef static 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | } 29 | 30 | #include 31 | 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "Config.h" 43 | #include "IPC.h" 44 | #include "Keyboard.h" 45 | #include "Layers.h" 46 | #include "Listener.h" 47 | #include "NotNull.h" 48 | #include "OptionalRef.h" 49 | #include "Output.h" 50 | #include "OutputManager.h" 51 | #include "Seat.h" 52 | #include "SurfaceManager.h" 53 | #include "View.h" 54 | #include "ViewAnimation.h" 55 | #include "Workspace.h" 56 | 57 | #if HAVE_XWAYLAND 58 | #include 59 | #endif 60 | 61 | const std::string_view CARDBOARD_NAME = "cardboard"; ///< The name of the compositor. 62 | const std::string_view CONFIG_NAME = "cardboardrc"; ///< The name of the config script. 63 | /// The environment variable for the standard config directory. 64 | const std::string_view CONFIG_HOME_ENV = "XDG_CONFIG_HOME"; 65 | 66 | const int WORKSPACE_NR = 4; ///< Default number of pre-initialized workspaces. 67 | 68 | /** 69 | * \brief Holds all the information of the currently running compositor. 70 | * 71 | * Its methods also implement most of the basic functionality not related to other structures. 72 | */ 73 | struct Server { 74 | struct wl_display* wl_display; 75 | struct wlr_compositor* compositor; 76 | struct wlr_backend* backend; 77 | struct wlr_renderer* renderer; 78 | 79 | IPCInstance ipc; 80 | /// Event loop object for integrating IPC events with the rest of Wayland's event system. 81 | wl_event_loop* event_loop; 82 | /// IPC event source for Wayland' event loop mechanism. 83 | wl_event_source* ipc_event_source; 84 | 85 | std::string config_path; 86 | 87 | struct wlr_xdg_shell* xdg_shell; 88 | struct wlr_layer_shell_v1* layer_shell; 89 | struct wlr_xwayland* xwayland; 90 | 91 | OutputManagerInstance output_manager; 92 | SurfaceManager surface_manager; 93 | ViewAnimationInstance view_animation; 94 | 95 | ListenerList listeners; 96 | KeybindingsConfig keybindings_config; 97 | Config config; 98 | 99 | Seat seat; 100 | 101 | int exit_code = EXIT_SUCCESS; 102 | 103 | Server() = default; 104 | Server(const Server&) = delete; 105 | 106 | /// Initializes the Wayland compositor. 107 | bool init(); 108 | /// Creates socket file 109 | bool init_ipc(); 110 | #if HAVE_XWAYLAND 111 | void init_xwayland(); 112 | #endif 113 | /// Runs the config script in background. Executed before Server::init_ipc2. 114 | bool load_settings(); 115 | 116 | /// Runs the event loop. 117 | bool run(); 118 | /// Stops the engines. 119 | void stop(); 120 | /// Stops the event loop. Runs before Server::stop. 121 | void teardown(int code); 122 | 123 | private: 124 | /** 125 | * \brief Called when a new \c xdg_surface is created by a client. 126 | * 127 | * An \c xdg-surface is a type of surface exposed by the \c xdg-shell. 128 | * This handler creates a View based on it. 129 | */ 130 | static void new_xdg_surface_handler(struct wl_listener* listener, void* data); 131 | 132 | /** 133 | * \brief Called when a new \c layer_surface is created by a client. 134 | * 135 | * A \c layer_surface is a type of surface exposed by the \c layer-shell. 136 | * This handler does NOT create Views based on it. 137 | */ 138 | static void new_layer_surface_handler(struct wl_listener* listener, void* data); 139 | 140 | /** 141 | * \brief Called when a new \c xwayland_surface is spawned by an X client. 142 | * 143 | * An \c xwayland_surface is a type of surface exposed by the xwayland wlroots system. 144 | */ 145 | static void new_xwayland_surface_handler(struct wl_listener* listener, void* data); 146 | }; 147 | 148 | #endif // CARDBOARD_SERVER_H_INCLUDED 149 | -------------------------------------------------------------------------------- /cardboard/SurfaceManager.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include "SurfaceManager.h" 12 | #include "Server.h" 13 | 14 | void SurfaceManager::map_view(Server& server, View& view) 15 | { 16 | view.mapped = true; 17 | 18 | // can be nullptr, this is fine 19 | auto* prev_focused = server.seat.get_focused_view().raw_pointer(); 20 | 21 | server.seat.get_focused_workspace(server).and_then([&server, &view, prev_focused](auto& ws) { 22 | ws.add_view(*(server.output_manager), view, prev_focused); 23 | }); 24 | server.seat.focus_view(server, view); 25 | } 26 | 27 | void SurfaceManager::unmap_view(Server& server, View& view) 28 | { 29 | if (view.mapped) { 30 | view.mapped = false; 31 | server.output_manager->get_view_workspace(view).remove_view(*(server.output_manager), view); 32 | } 33 | 34 | server.seat.hide_view(server, view); 35 | server.seat.remove_from_focus_stack(view); 36 | } 37 | 38 | void SurfaceManager::move_view_to_front(View& view) 39 | { 40 | views.splice(views.begin(), views, std::find_if(views.begin(), views.end(), [&view](const std::unique_ptr& x) { return &view == x.get(); })); 41 | } 42 | 43 | void SurfaceManager::remove_view(ViewAnimation& view_animation, View& view) 44 | { 45 | view_animation.cancel_tasks(view); 46 | views.remove_if([&view](const auto& other) { return &view == other.get(); }); 47 | } 48 | 49 | OptionalRef SurfaceManager::get_surface_under_cursor(OutputManager& output_manager, double lx, double ly, struct wlr_surface*& surface, double& sx, double& sy) 50 | { 51 | OptionalRef output = output_manager.get_output_at(lx, ly); 52 | const auto ws_it = std::find_if(output_manager.workspaces.begin(), output_manager.workspaces.end(), [output](const auto& other) { 53 | return other.output == output; 54 | }); 55 | if (ws_it == output_manager.workspaces.end() || !ws_it->output) { 56 | return NullRef; 57 | } 58 | 59 | // it is guaranteed that the workspace is activated on an output 60 | 61 | // we are trying surfaces from top to bottom 62 | 63 | // first, overlays and top layers 64 | for (const auto layer : { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP }) { 65 | // fullscreen views render on top of the TOP layer 66 | if (ws_it->fullscreen_view && layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP) { 67 | continue; 68 | } 69 | for (const auto& layer_surface : layers[layer]) { 70 | if (!layer_surface.surface->mapped || !layer_surface.is_on_output(ws_it->output.unwrap())) { 71 | continue; 72 | } 73 | 74 | if (layer_surface.get_surface_under_coords(lx, ly, surface, sx, sy)) { 75 | return NullRef; 76 | } 77 | } 78 | } 79 | 80 | // second, unmanaged xwayland surfaces 81 | #if HAVE_XWAYLAND 82 | for (const auto& xwayland_or_surface : xwayland_or_surfaces) { 83 | if (!xwayland_or_surface->mapped) { 84 | continue; 85 | } 86 | if (xwayland_or_surface->get_surface_under_coords(lx, ly, surface, sx, sy)) { 87 | return NullRef; 88 | } 89 | } 90 | #endif 91 | 92 | if (ws_it->fullscreen_view && ws_it->fullscreen_view.unwrap().get_surface_under_coords(lx, ly, surface, sx, sy)) { 93 | return ws_it->fullscreen_view; 94 | } 95 | 96 | // third, floating views 97 | for (NotNullPointer floating_view : ws_it->floating_views) { 98 | if (!floating_view->mapped) { 99 | continue; 100 | } 101 | 102 | if (floating_view->get_surface_under_coords(lx, ly, surface, sx, sy)) { 103 | return OptionalRef(floating_view); 104 | } 105 | } 106 | 107 | // fourth, regular, tiled views 108 | for (auto& column : ws_it->columns) { 109 | for (auto& tile : column.tiles) { 110 | NotNullPointer view = tile.view; 111 | if (!view->mapped) { 112 | continue; 113 | } 114 | 115 | if (view->get_surface_under_coords(lx, ly, surface, sx, sy)) { 116 | return OptionalRef(view); 117 | } 118 | } 119 | } 120 | 121 | // and the very last, bottom layers and backgrounds 122 | for (const auto layer : { ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND }) { 123 | for (const auto& layer_surface : layers[layer]) { 124 | if (!layer_surface.surface->mapped) { 125 | continue; 126 | } 127 | 128 | if (layer_surface.get_surface_under_coords(lx, ly, surface, sx, sy)) { 129 | return NullRef; 130 | } 131 | } 132 | } 133 | 134 | return NullRef; 135 | } 136 | -------------------------------------------------------------------------------- /cardboard/ViewOperations.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include "ViewOperations.h" 12 | 13 | /// Moves the view from a workspace to another. Handles special cases like fullscreen views, floating views etc. 14 | void change_view_workspace(Server& server, View& view, Workspace& new_workspace) 15 | { 16 | Workspace& workspace = server.output_manager->get_view_workspace(view); 17 | 18 | if (&workspace == &new_workspace) { 19 | return; 20 | } 21 | 22 | if (view.expansion_state == View::ExpansionState::FULLSCREEN) { 23 | if (new_workspace.fullscreen_view) { 24 | new_workspace.set_fullscreen_view(*(server.output_manager), NullRef); 25 | } 26 | new_workspace.fullscreen_view = OptionalRef(view); 27 | new_workspace.arrange_workspace(*(server.output_manager)); 28 | } 29 | 30 | bool floating = workspace.is_view_floating(view); 31 | if (floating && new_workspace.output.has_value() && view.expansion_state != View::ExpansionState::FULLSCREEN) { 32 | auto output_area = server.output_manager->get_output_real_usable_area(new_workspace.output.unwrap()); 33 | 34 | if (view.x < output_area.x || view.x >= output_area.x + output_area.width || view.y < output_area.y || view.y >= output_area.y + output_area.height) { 35 | view.move( 36 | *server.output_manager, 37 | output_area.x + output_area.width / 2, 38 | output_area.y + output_area.height / 2); 39 | } 40 | } 41 | 42 | workspace.remove_view(*(server.output_manager), view); 43 | new_workspace.add_view(*(server.output_manager), view, nullptr, floating); 44 | 45 | if (auto last_focused_view = std::find_if(server.seat.focus_stack.begin(), server.seat.focus_stack.end(), [&workspace, &view](View* v) { 46 | return v->workspace_id == workspace.index && v != &view; 47 | }); 48 | last_focused_view != server.seat.focus_stack.end()) { 49 | server.seat.focus_view(server, OptionalRef(*last_focused_view)); 50 | } else { 51 | server.seat.focus_view(server, NullRef); 52 | }; 53 | cursor_rebase(server, server.seat, server.seat.cursor); 54 | } 55 | 56 | /// If a floating view changed the output it appears on (for example by dragging), move it to that output's workspace. 57 | static void update_view_workspace(Server& server, View& view) 58 | { 59 | if (server.output_manager->workspaces[view.workspace_id].find_floating(&view) != server.output_manager->workspaces[view.workspace_id].floating_views.end()) { 60 | OptionalRef current_output = server.output_manager->get_output_at(view.x, view.y); 61 | 62 | if (current_output && current_output != server.output_manager->workspaces[view.workspace_id].output) { 63 | auto workspace = std::find_if(server.output_manager->workspaces.begin(), server.output_manager->workspaces.end(), [current_output](auto& w) { 64 | return w.output == current_output; 65 | }); 66 | 67 | change_view_workspace(server, view, *workspace); 68 | } 69 | } 70 | } 71 | 72 | /// Does the appropriate movement for tiled and floating views. When moved, tiled views scroll the workspace, and floating views need to be updated when changing outputs. 73 | void reconfigure_view_position(Server& server, View& view, int x, int y, bool animate) 74 | { 75 | if (auto& workspace = server.output_manager->workspaces[view.workspace_id]; workspace.find_column(&view) != workspace.columns.end()) { 76 | int dx = view.x - x; 77 | 78 | scroll_workspace(*(server.output_manager), workspace, RelativeScroll { dx }, animate); 79 | } else { 80 | view.move(*server.output_manager, x, y); 81 | update_view_workspace(server, view); 82 | } 83 | } 84 | 85 | /// Resizes \a view to the given size. Tiled views have their heights determined by the tiling algorithm, therefore they don't change height. 86 | void reconfigure_view_size(Server& server, View& view, int width, int height) 87 | { 88 | auto& workspace = server.output_manager->get_view_workspace(view); 89 | 90 | if (auto column_it = workspace.find_column(&view); column_it != workspace.columns.end()) { 91 | height = view.geometry.height; 92 | for (auto& tile : column_it->mapped_and_normal_tiles()) { 93 | tile.view->resize(width, height); 94 | } 95 | } else { 96 | view.resize(width, height); 97 | } 98 | } 99 | 100 | /// Sets the workspace scroll to an absolute value. 101 | void scroll_workspace(OutputManager& output_manager, Workspace& workspace, AbsoluteScroll scroll, bool animate) 102 | { 103 | workspace.scroll_x = scroll.get(); 104 | workspace.arrange_workspace(output_manager, animate); 105 | } 106 | 107 | /// Scrolls the workspace by a delta value. 108 | void scroll_workspace(OutputManager& output_manager, Workspace& workspace, RelativeScroll scroll, bool animate) 109 | { 110 | workspace.scroll_x += scroll.get(); 111 | workspace.arrange_workspace(output_manager, animate); 112 | } 113 | -------------------------------------------------------------------------------- /cardboard/OutputManager.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | extern "C" { 12 | #include 13 | #include 14 | #include 15 | } 16 | 17 | #include "Helpers.h" 18 | #include "Layers.h" 19 | #include "Listener.h" 20 | #include "Output.h" 21 | #include "OutputManager.h" 22 | 23 | void OutputManager::register_handlers(Server& server, struct wl_signal* new_output) 24 | { 25 | ::register_handlers(server, this, { 26 | { new_output, OutputManager::new_output_handler }, 27 | { &output_layout->events.add, OutputManager::output_layout_add_handler }, 28 | }); 29 | } 30 | 31 | NotNullPointer OutputManager::get_output_box(const Output& output) const 32 | { 33 | return wlr_output_layout_get_box(output_layout, output.wlr_output); 34 | } 35 | 36 | struct wlr_box OutputManager::get_output_real_usable_area(const Output& output) const 37 | { 38 | const auto* output_box = wlr_output_layout_get_box(output_layout, output.wlr_output); 39 | auto real_usable_area = output.usable_area; 40 | real_usable_area.x += output_box->x; 41 | real_usable_area.y += output_box->y; 42 | 43 | return real_usable_area; 44 | } 45 | 46 | OptionalRef OutputManager::get_output_at(double lx, double ly) const 47 | { 48 | auto* raw_output = wlr_output_layout_output_at(output_layout, lx, ly); 49 | if (raw_output == nullptr) { 50 | return NullRef; 51 | } 52 | 53 | return OptionalRef(static_cast(raw_output->data)); 54 | } 55 | 56 | bool OutputManager::output_contains_point(const Output& reference, int lx, int ly) const 57 | { 58 | return wlr_output_layout_contains_point(output_layout, reference.wlr_output, lx, ly); 59 | } 60 | 61 | void OutputManager::remove_output_from_list(Output& output) 62 | { 63 | outputs.remove_if([&output](auto& other) { return &other == &output; }); 64 | } 65 | 66 | void OutputManager::new_output_handler(struct wl_listener* listener, void* data) 67 | { 68 | Server* server = get_server(listener); 69 | auto* output = static_cast(data); 70 | 71 | // pick the monitor's preferred mode 72 | if (!wl_list_empty(&output->modes)) { 73 | struct wlr_output_mode* mode = wlr_output_preferred_mode(output); 74 | wlr_output_set_mode(output, mode); 75 | wlr_output_enable(output, true); 76 | if (!wlr_output_commit(output)) { 77 | return; 78 | } 79 | } 80 | 81 | // add output to the layout. add_auto arranges outputs left-to-right 82 | // in the order they appear. 83 | wlr_output_layout_add_auto(server->output_manager->output_layout, output); 84 | } 85 | 86 | void OutputManager::output_layout_add_handler(struct wl_listener* listener, void* data) 87 | { 88 | Server* server = get_server(listener); 89 | auto* l_output = static_cast(data); 90 | 91 | auto output_ = Output { .wlr_output = l_output->output }; 92 | // FIXME: should this go in the constructor? 93 | wlr_output_effective_resolution(output_.wlr_output, &output_.usable_area.width, &output_.usable_area.height); 94 | register_output(*server, std::move(output_)); 95 | 96 | auto& output = server->output_manager->outputs.back(); 97 | 98 | Workspace* ws_to_assign = nullptr; 99 | for (auto& ws : server->output_manager->workspaces) { 100 | if (!ws.output) { 101 | ws_to_assign = &ws; 102 | break; 103 | } 104 | } 105 | 106 | if (!ws_to_assign) { 107 | ws_to_assign = &server->output_manager->create_workspace(server); 108 | } 109 | 110 | ws_to_assign->activate(output); 111 | arrange_layers(*server, output); 112 | 113 | // the output doesn't need to be exposed as a wayland global 114 | // because wlr_output_layout does it for us already 115 | } 116 | 117 | Workspace& OutputManager::create_workspace(Server* server) 118 | { 119 | workspaces.push_back({ .server = server, .index = static_cast(workspaces.size()) }); 120 | return workspaces.back(); 121 | } 122 | 123 | Workspace& OutputManager::get_view_workspace(View& view) 124 | { 125 | return workspaces[view.workspace_id]; 126 | } 127 | 128 | void OutputManager::set_dirty() 129 | { 130 | for (auto& output : outputs) { 131 | wlr_output_damage_add_whole(output.wlr_output_damage); 132 | } 133 | } 134 | 135 | void OutputManager::output_manager_apply_handler([[maybe_unused]] wl_listener* listener, [[maybe_unused]] void* data) 136 | { 137 | } 138 | 139 | void OutputManager::output_manager_test_handler([[maybe_unused]] wl_listener* listener, [[maybe_unused]] void* data) 140 | { 141 | } 142 | 143 | OutputManagerInstance create_output_manager(Server* server) 144 | { 145 | auto output_manager = std::make_unique(); 146 | 147 | output_manager->output_layout = wlr_output_layout_create(); 148 | output_manager->output_manager_v1 = wlr_output_manager_v1_create(server->wl_display); 149 | 150 | output_manager->register_handlers(*server, &server->backend->events.new_output); 151 | 152 | register_handlers( 153 | *server, 154 | output_manager.get(), 155 | { { &output_manager->output_manager_v1->events.apply, OutputManager::output_manager_apply_handler }, 156 | { &output_manager->output_manager_v1->events.test, OutputManager::output_manager_test_handler } }); 157 | 158 | return output_manager; 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UNMAINTAINED. Read the explanation bellow 2 | 3 | If you are curious to see what this project was about, you can check out [this cool blog post written by Daniel Aleksandersen][blog-post]. 4 | 5 | If the scrollable tiling concept intrigues you, you can either: 6 | * Use [PaperWM on GNOME](https://github.com/paperwm/PaperWM), or 7 | * Check out [Karousel](https://github.com/peterfajdiga/karousel), a KWin tiling script. 8 | 9 | We (Alex and Tudor) had great fun while developing Cardboard, allowing us to learn more about Wayland and C++ development. 10 | Cardboard was made as a project for the [National Digital Innovation and Creativity Olympiad - InfoEducație][infoedu], 11 | a government-sponsored software development contest, camp, and hackathon* for Romanian high school students. 12 | We figured it would be great to make a project that is based on new Linux technologies while also helping the 13 | Wayland community with a compositor implementing a novel tiling algorithm. We have since started university and working 14 | on the side, which doesn't allow us to maintain the messy codebase we left behind. 15 | Wlroots has changed in the meantime too, requiring us to keep up with its changes to keep our compositor functional. 16 | None of us use this software day to day, so naturally this task didn't receive much priority, if at all. 17 | 18 | Still, we would like to keep this project public, for other to learn from it. 19 | For a more up to date version, you can try [Julien Langlois][fork]' fork. We are thankful for his effort to touch up our codebase. 20 | 21 | If you want to build your own compositor, we have [a couple pointers][pointers]: 22 | 23 | * Make use of the new [Wlroots Scene Graph API][wlr_scene] as much as possible. Such a luxury wasn't yet upstreamed 24 | when we were working on Cardboard. 25 | * As always, a very minimal implementation of many Wlroots goodies (including the scene graph API) is 26 | [cage][cage]. Read and study the code. 27 | * Read [this blog post written by Tudor][the-wayland-experience]. It was written after coding Cardboard. As such, 28 | it doesn't take into consideration the now-released scene graph API. 29 | * Have fun! 30 | 31 | *: For our hackathon submission, see [The X Files ISA][x-files-isa]. 32 | 33 | [infoedu]: https://infoeducatie.ro/home 34 | [x-files-isa]: https://gitlab.com/cardboardwm/x-files-isa 35 | [fork]: https://gitlab.com/langlois-jf/cardboard 36 | [pointers]: https://xkcd.com/138/ 37 | [wlr_scene]: https://wayland.emersion.fr/wlroots/wlr/types/wlr_scene.h.html 38 | [cage]: https://github.com/cage-kiosk/cage 39 | [the-wayland-experience]: https://tudorr.ro/blog/technical/2021/01/26/the-wayland-experience/ 40 | [blog-post]: https://www.ctrl.blog/entry/cardboardwm.html 41 | 42 | # Cardboard 43 | 44 | Cardboard is a unique, scrollable tiling Wayland compositor designed with 45 | laptops in mind. Based on [wlroots](https://github.com/swaywm/wlroots). 46 | 47 | Cardboard is experimental software, and no warranty should be derived from it. 48 | 49 | ## Scrollable tiling? 50 | 51 | The idea of scrollable tiling comes from 52 | [PaperWM](https://github.com/paperwm/PaperWM). Instead of tiling by filling the 53 | screen with windows placed in a tree-like container structure, in scrollable 54 | tiling, each workspace is a series of vertically-maximized windows placed one 55 | after another, overflowing the screen. The monitor becomes a sliding window over 56 | this sequence of windows, allowing for easier window placement. The main idea 57 | is that you are not going to look at more than about three windows at the same 58 | time. While in other tiling compositors you can put the lesser-used windows in 59 | different workspaces, you will have to remember their position in your head and 60 | deal with juggling through the workspaces. With scrollable tiling, unused 61 | windows are just placed off-screen, and you can always scroll back to see them. 62 | Cardboard also provides workspaces, each with its own distinct scrollable plane. 63 | Each output has its own active workspace. 64 | 65 | ## Cutter 66 | 67 | Cardboard has a companion program named Cutter. Cutter is a tool for 68 | communicating actions and getting information to and from cardboard, similar to 69 | `i3msg` on i3, `swaymsg` on Sway and `bspc` on bspwm. It is also the program for 70 | configuring Cardboard. Config files are basic executable files that should call 71 | Cutter to set config values. Cutter itself is based on `libcardboard`, a library 72 | to easily communicate with Cardboard. 73 | 74 | ## Building the code 75 | 76 | Cardboard requires Meson and Ninja to build the code. 77 | 78 | Libraries: 79 | 80 | * wayland-server 81 | * xkbcommon 82 | * wlroots 83 | 84 | The build script won't use the system wlroots. Instead, it will try to compile 85 | its own, so make sure you have its dependencies installed. Cardboard supports 86 | Xwayland, so if you enable it (it's enabled by default, disable with `meson 87 | -Dxwayland=disabled `), you 88 | also need what wlroots needs to get Xwayland working. 89 | 90 | You should also have a C++ compiler that compiles in `-std=c++2a` mode. Currently, 91 | compilation works with `-std=c++17` too, but this might break soon. You need to 92 | edit the root `meson.build` file to compile in C++ 17 mode. 93 | 94 | Get the code, build and run: 95 | 96 | ``` sh 97 | $ git clone https://gitlab.com/cardboardwm/cardboard 98 | $ cd cardboard 99 | $ meson build 100 | $ ninja -C build 101 | $ ./build/cardboard/cardboard # to run the thing 102 | ``` 103 | 104 | ### Building man pages 105 | In order to build the man pages, along the binary, the meson `man` variable 106 | needs to be set to `true`: 107 | 108 | ```sh 109 | $ meson build -Dman=true 110 | ``` 111 | 112 | ## Configuration 113 | 114 | Cardboard tries to run `~/.config/cardboard/cardboardrc` on startup. You can use 115 | to run commands and set keybindings: 116 | 117 | ``` sh 118 | #!/bin/sh 119 | 120 | alias cutter=$HOME/src/cardboard/build/cutter/cutter 121 | 122 | mod=alt 123 | 124 | cutter config mouse_mod $mod 125 | 126 | cutter bind $mod+x quit 127 | cutter bind $mod+return exec sakura 128 | cutter bind $mod+left focus left 129 | cutter bind $mod+right focus right 130 | cutter bind $mod+shift+left move -10 0 131 | cutter bind $mod+shift+right move 10 0 132 | cutter bind $mod+shift+up move 0 -10 133 | cutter bind $mod+shift+down move 0 10 134 | cutter bind $mod+w close 135 | for i in $(seq 1 6); do 136 | cutter bind $mod+$i workspace switch $(( i - 1 )) 137 | cutter bind $mod+ctrl+$i workspace move $(( i - 1 )) 138 | done 139 | 140 | cutter bind $mod+shift+space toggle_floating 141 | 142 | cutter exec toolbox run swaybg -i ~/wallpapers/autobahn.png 143 | cutter exec toolbox run waybar 144 | ``` 145 | 146 | As you can see, to run a program, you need to preffix it with `cutter exec`. 147 | 148 | Have fun! 149 | -------------------------------------------------------------------------------- /cardboard/commands/dispatch_command.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #include "../Command.h" 12 | #include "commands.h" 13 | 14 | #include 15 | #include 16 | 17 | template 18 | struct overloaded : Ts... { 19 | using Ts::operator()...; 20 | }; 21 | template 22 | overloaded(Ts...)->overloaded; 23 | 24 | uint32_t modifier_array_to_mask(const std::vector& modifiers) 25 | { 26 | static const std::unordered_map mod_table = { 27 | { "shift", WLR_MODIFIER_SHIFT }, 28 | { "ctrl", WLR_MODIFIER_CTRL }, 29 | { "alt", WLR_MODIFIER_ALT }, 30 | { "super", WLR_MODIFIER_LOGO }, 31 | { "caps", WLR_MODIFIER_CAPS }, 32 | { "mod2", WLR_MODIFIER_MOD2 }, 33 | { "mod3", WLR_MODIFIER_MOD3 }, 34 | { "mod5", WLR_MODIFIER_MOD5 } 35 | }; 36 | 37 | uint32_t mask = 0; 38 | for (auto mod : modifiers) { 39 | mask |= mod_table.at(mod); 40 | } 41 | return mask; 42 | } 43 | 44 | static Command dispatch_workspace(const command_arguments::workspace& workspace) 45 | { 46 | return std::visit(overloaded { 47 | [](command_arguments::workspace::switch_ switch_) -> Command { 48 | return [switch_](Server* server) { 49 | return commands::workspace_switch(server, switch_.n); 50 | }; 51 | }, 52 | [](command_arguments::workspace::move move) -> Command { 53 | return [move](Server* server) { 54 | return commands::workspace_move(server, move.n); 55 | }; 56 | }, 57 | }, 58 | workspace.workspace); 59 | } 60 | 61 | static Command dispatch_config(const command_arguments::config& config) 62 | { 63 | return std::visit(overloaded { 64 | [](command_arguments::config::mouse_mod mouse_mod) -> Command { 65 | uint32_t modifiers = modifier_array_to_mask(mouse_mod.modifiers); 66 | return [modifiers](Server* server) { 67 | return commands::config_mouse_mod(server, modifiers); 68 | }; 69 | }, 70 | [](command_arguments::config::focus_color focus_color) -> Command { 71 | return [focus_color](Server* server) { 72 | return commands::config_focus_color( 73 | server, 74 | focus_color.r, 75 | focus_color.g, 76 | focus_color.b, 77 | focus_color.a); 78 | }; 79 | }, 80 | [](command_arguments::config::gap gap) -> Command { 81 | return [gap](Server* server) { 82 | return commands::config_gap(server, gap.gap); 83 | }; 84 | } }, 85 | config.config); 86 | } 87 | 88 | Command dispatch_command(const CommandData& command_data) 89 | { 90 | return std::visit(overloaded { 91 | [](const command_arguments::focus focus_data) -> Command { 92 | if (!focus_data.cycle) { 93 | return [focus_data](Server* server) { 94 | return commands::focus(server, focus_data.direction); 95 | }; 96 | } else { 97 | return commands::focus_cycle; 98 | } 99 | }, 100 | [](const command_arguments::quit quit_data) -> Command { 101 | return [quit_data](Server* server) { return commands::quit(server, quit_data.code); }; 102 | }, 103 | [](const command_arguments::bind& bind_data) -> Command { 104 | uint32_t modifiers = modifier_array_to_mask(bind_data.modifiers); 105 | 106 | xkb_keysym_t sym = xkb_keysym_from_name(bind_data.key.c_str(), XKB_KEYSYM_CASE_INSENSITIVE); 107 | 108 | if (sym == XKB_KEY_NoSymbol) 109 | return { [bind_data](Server*) -> CommandResult { return { std::string("Invalid keysym: ") + bind_data.key }; } }; 110 | 111 | Command command = dispatch_command(*(bind_data.command)); 112 | return { [modifiers, sym, command](Server* server) { 113 | return commands::bind(server, modifiers, sym, command); 114 | } }; 115 | }, 116 | [](const command_arguments::exec exec_data) -> Command { 117 | return [exec_data](Server* server) { 118 | return commands::exec(server, exec_data.argv); 119 | }; 120 | }, 121 | [](const command_arguments::close) -> Command { 122 | return commands::close; 123 | }, 124 | [](const command_arguments::workspace& workspace) -> Command { 125 | return dispatch_workspace(workspace); 126 | }, 127 | [](const command_arguments::toggle_floating&) -> Command { 128 | return commands::toggle_floating; 129 | }, 130 | [](const command_arguments::move& move) -> Command { 131 | return [move](Server* server) { 132 | return commands::move(server, move.dx, move.dy); 133 | }; 134 | }, 135 | [](const command_arguments::resize& resize) -> Command { 136 | return [resize](Server* server) { 137 | return commands::resize(server, resize.width, resize.height); 138 | }; 139 | }, 140 | [](const command_arguments::insert_into_column&) -> Command { 141 | return commands::insert_into_column; 142 | }, 143 | [](const command_arguments::pop_from_column&) -> Command { 144 | return commands::pop_from_column; 145 | }, 146 | [](const command_arguments::config& config) -> Command { 147 | return dispatch_config(config); 148 | }, 149 | [](const command_arguments::cycle_width&) -> Command { 150 | return commands::cycle_width; 151 | }, 152 | }, 153 | command_data); 154 | } 155 | -------------------------------------------------------------------------------- /cardboard/Workspace.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_TILING_H_INCLUDED 12 | #define CARDBOARD_TILING_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | } 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "NotNull.h" 26 | #include "OptionalRef.h" 27 | 28 | class View; 29 | struct Server; 30 | struct Output; 31 | struct OutputManager; 32 | struct Seat; 33 | 34 | /** 35 | * \brief A Workspace is a group of tiled windows. 36 | * 37 | * Workspaces are assigned to at most one output. 38 | * 39 | * The workspace can be imagined as a plane with vertically maxed 40 | * views placed side-by-side. The plane has its origin in the origin of its first view. 41 | * Parts of this plane are visible on the output, regarded as the viewport. 42 | * 43 | * This viewport can be moved on the horizontal axis of the plane, like a sliding window. 44 | * As a result, only a segment of these horizontally aligned views is shown on the screen. The 45 | * scrolling of the viewport is controlled by the #scroll_x variable. 46 | */ 47 | struct Workspace { 48 | using IndexType = ssize_t; 49 | struct Column { 50 | struct Tile { 51 | NotNullPointer view; 52 | NotNullPointer column; 53 | /// This is initially 1. It represents the number of "parts" (as in "two parts water, one part sugar") when calculating the height of the tile relative to the column. 54 | float vertical_scale = 1.0f; 55 | }; 56 | 57 | /** 58 | * \brief This abomination is meant to be a singleton in Column to let us write 59 | * loops that iterate over tiles whose views are mapped (on screen) and in normal state 60 | * (i.e. not fullscreened or recovering from fullscreen). Example: 61 | * 62 | * \code{.cpp} 63 | * for (auto& tile : columns.mapped_and_normal_tiles()) { 64 | * // do stuff 65 | * } 66 | * \endcode 67 | */ 68 | class MappedAndNormal { 69 | private: 70 | NotNullPointer> tile_list; 71 | 72 | MappedAndNormal(NotNullPointer> tile_list) 73 | : tile_list(tile_list) 74 | { 75 | } 76 | 77 | public: 78 | /// This quasi-iterator iterates over mapped and normal tiles. 79 | struct IteratorWrapper { 80 | NotNullPointer> tile_list; 81 | std::list::iterator it; 82 | 83 | IteratorWrapper& operator++(); 84 | Tile& operator*(); 85 | bool operator!=(const IteratorWrapper& other); 86 | }; 87 | 88 | IteratorWrapper begin(); 89 | IteratorWrapper end(); 90 | 91 | friend struct Column; 92 | }; 93 | 94 | std::list tiles; 95 | 96 | MappedAndNormal mapped_and_normal_tiles(); 97 | std::unordered_set> get_mapped_and_normal_set(); 98 | }; 99 | 100 | std::list columns; 101 | std::list> floating_views; 102 | 103 | /** 104 | * \brief The output assigned to this workspace (or the output to which this workspace is assigned). 105 | * 106 | * It's equal to \c std::nullopt if this workspace isn't assigned. 107 | */ 108 | OptionalRef output; 109 | /// The currently full screened view, if any. 110 | OptionalRef fullscreen_view; 111 | std::optional> fullscreen_original_size; 112 | Server* server; 113 | 114 | IndexType index; 115 | 116 | /** 117 | * \brief The offset of the viewport. 118 | */ 119 | int scroll_x = 0; 120 | 121 | /// If set to true, arrange_workspace will not use animations. 122 | bool suspend_animations = false; 123 | 124 | /** 125 | * \brief Returns an iterator to the column containing \a view. 126 | * 127 | * \param view - can be null! 128 | */ 129 | std::list::iterator find_column(View* view); 130 | 131 | /** 132 | * \brief Returns an iterator to the a floating view. 133 | * 134 | * \param view - can be null! 135 | */ 136 | std::list>::iterator find_floating(View* view); 137 | 138 | /** 139 | * \brief Adds the \a view to the right of the \a next_to view and tiles it accordingly. 140 | * 141 | * Even if \a next_to is not the only tile in a column, \a view is still going to be 142 | * added to its very own column on the right. 143 | * 144 | * \param transfering - set to true if we toggle the floating state 145 | * \param next_to - can be null! 146 | */ 147 | void add_view(OutputManager& output_manager, View& view, View* next_to, bool floating = false, bool transferring = false); 148 | 149 | /** 150 | * \brief Removes \a view from the workspace and tiles the others accordingly. 151 | * 152 | * If the \a view is the sole view of a column, the column is destroyed too. 153 | */ 154 | void remove_view(OutputManager& output_manager, View& view, bool transferring = false); 155 | 156 | /** 157 | * \brief Takes \a view from its column and attaches it at the end of \a column. 158 | * 159 | * \param view - must be tiled in this workspace 160 | * \param column - must be from this workspace 161 | */ 162 | void insert_into_column(OutputManager& output_manager, View& view, Column& column); 163 | 164 | /** 165 | * \brief Takes the least (bottom-most) view from the \a column and puts it in its own column on the right. 166 | * 167 | * \param column - must be from this workspace 168 | */ 169 | void pop_from_column(OutputManager& output_manager, Column& column); 170 | 171 | /** 172 | * \brief Puts windows in tiled position and takes care of fullscreen views. 173 | */ 174 | void arrange_workspace(OutputManager& output_manager, bool animate = true); 175 | 176 | /** 177 | * \brief Scrolls the viewport of the workspace just enough to make the 178 | * entirety of \a view visible, i.e. there are no off-screen parts of it. 179 | * 180 | * \param condense - if true, if \a view is the first or last in the sequence, align it to the border 181 | */ 182 | void fit_view_on_screen(OutputManager& output_manager, View& view, bool condense = false); 183 | 184 | /** 185 | * \brief From the currently visible view (those that are inside the viewport), return the one that has 186 | * most coverage as a ratio of its width. There may be more views having the most coverage. 187 | * If \a focused_view is one of them, return it directly. Can return nullptr. 188 | */ 189 | OptionalRef find_dominant_view(OutputManager& output_manager, Seat& seat, OptionalRef focused_view); 190 | 191 | /** 192 | * \brief Returns the x coordinate of \a view in workspace coordinates. 193 | * The origin of the workspace plane is the top-left corner of the first window, 194 | * be it off-screen or not. 195 | */ 196 | int get_view_wx(View&); 197 | 198 | /// Sets \a view as the currently fullscreen view. If null, the fullscreen view will be cleared, if any. 199 | void set_fullscreen_view(OutputManager& output_manager, OptionalRef view); 200 | 201 | /// Returns true if the view is floating in this Workspace. 202 | bool is_view_floating(View& view); 203 | 204 | /** 205 | * \brief Assigns the workspace to an \a output. 206 | */ 207 | void activate(Output& output); 208 | 209 | /** 210 | * \brief Marks the workspace as inactive: it is not assigned to any output. 211 | */ 212 | void deactivate(); 213 | }; 214 | 215 | #endif // CARDBOARD_TILING_H_INCLUDED 216 | -------------------------------------------------------------------------------- /cardboard/View.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_VIEW_H_INCLUDED 12 | #define CARDBOARD_VIEW_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | #include 18 | #include 19 | } 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "Workspace.h" 26 | 27 | struct Server; 28 | 29 | /** 30 | * \brief Represents a normal window on the screen. 31 | * 32 | * Each View has its base in a shell (xdg-shell, layer-shell, XWayland). Currently, only 33 | * xdg-shell is supported. The View represents an xdg-shell toplevel surface. 34 | * 35 | * These windows may be associated with a Workspace, if its tiled. You can read about the tiled behaviour 36 | * in the documentation of the Workspace class. 37 | * 38 | * Views can also be mapped or unmapped, shown or hidden. Mapped and unmapped are states defined by the shell. 39 | * Usually, a mapped View is a window shown on the screen ready for user action, while an unmapped View is 40 | * deactivated and hidden (think minimized). On the other hand, shown and hidden are specific to the compositor. 41 | * A shown View is currently displayed on an output (screen), in an active workspace 42 | * (a workspace must be shown on an output to be active). It is visible. A hidden View is a View that is mapped, 43 | * but not visible on the screen. This happens when the View is mapped in a deactivated Workspace. 44 | * 45 | * So a shown View is always mapped, but a mapped View is not always shown. An unmapped View is always hidden, 46 | * but a hidden View is not always unmapped. 47 | * 48 | * There is only a flag for the mapped state (View::mapped), which is set by the unmap event handler of the 49 | * corresponding shell. 50 | * 51 | * \sa Writing a Wayland compositor with wlroots: shells 52 | * by Drew DeVault 53 | */ 54 | class View { 55 | public: 56 | struct ExpansionSavedState { 57 | int x, y; 58 | int width, height; 59 | }; 60 | enum class ExpansionState { 61 | NORMAL, 62 | /// For Views that have been un-fullscreened but didn't get the commit event yet. 63 | RECOVERING, 64 | FULLSCREEN, 65 | }; 66 | virtual ~View() = default; 67 | /** 68 | * \brief The size and offset of the usable working area. 69 | * 70 | * Unlike X11, Wayland only has the concept of a \c wl_surface, which is a rectangular region 71 | * that displays pixels and receives input. Layers give a meaning to these surfaces. 72 | * 73 | * These surfaces also don't know anything about the screen they are drawn on, not even their position 74 | * on the screen. As these surfaces are just rectangles with pixels, they are also used to contain 75 | * shadows and decorations that the user can't interact with, near the border of the surface. This is why 76 | * inside this rectangle there is an inner rectangle, positioned at an (x, y) offset from the 77 | * top-left corner of the surface with a \c width and a \c height that is the usable region of the surface. 78 | * This is what the user perceives as a window. 79 | * 80 | * This inner rectangle is called a \a geometry. 81 | * 82 | * \attention This property should not be modified directy, but by the resize() function. 83 | * Geometry modifications must be communicated to the client and the client must acknowledge 84 | * how much it will change. The property will change according to the desire of the client. 85 | * The change happens in xdg_surface_commit_handler(). 86 | */ 87 | struct wlr_box geometry; 88 | /// Saved size before expansion (fullscreen); 89 | std::optional saved_state; 90 | ExpansionState expansion_state; 91 | /// Holds the size from when the view was tiled if it's currently floating, or from when the view was floating if currently tiled. 92 | std::pair previous_size; 93 | 94 | /// The id of the workspace this View is assigned to. Set to -1 if none. 95 | Workspace::IndexType workspace_id; 96 | 97 | int x, y; ///< Coordinates of the surface, relative to the output layout (root coordinates). 98 | /** 99 | * \brief Computed coordinates of the surface after applying the tiling algorithm. 100 | * 101 | * The x and y coordinates represent the current coords on the screen and may be altered by ongoing animations. 102 | * target_x and target_y reflect the most recent computation of the tiling algorithm, without being disturbed by the 103 | * animation system. 104 | * */ 105 | int target_x, target_y; 106 | 107 | /** 108 | * \brief The width and height the compositor expects the view to have 109 | */ 110 | int target_width, target_height; 111 | 112 | bool mapped; 113 | bool new_view; ///< True if the view didn't have its first map. 114 | 115 | /// Get the top level surface of this view. 116 | virtual struct wlr_surface* get_surface() = 0; 117 | 118 | /** 119 | * \brief Gets the child sub-surface of this view's toplevel xdg surface sitting under a point, if exists. 120 | * 121 | * \a lx and \a ly are the given point, in output layout relative coordinates. 122 | * 123 | * \param[out] surface the surface under the cursor, if found. 124 | * \param[out] sx the x coordinate of the given point, relative to the surface. 125 | * \param[out] sy the y coordinate of the given point, relative to the surface 126 | * 127 | * \returns \c true if there is a surface underneath the point, \c false if there isn't. 128 | */ 129 | virtual bool get_surface_under_coords(double lx, double ly, struct wlr_surface*& surface, double& sx, double& sy) = 0; 130 | 131 | /// Requests the resize to the client. Do not assume that the client is resized afterwards. 132 | virtual void resize(int width, int height); 133 | 134 | /// Requests the move to the client. Do not assume that the client is resized afterwards. 135 | virtual void move(OutputManager& server, int x, int y); 136 | 137 | /// Prepares the view before registering to the server by attaching some handlers and doing shell-specific stuff. 138 | virtual void prepare(Server& server) = 0; 139 | 140 | /// Set activated (focused) status to \a activated. 141 | virtual void set_activated(bool activated) = 0; 142 | 143 | /// Set fullscreen status to \a fullscreen. 144 | virtual void set_fullscreen(bool fullscreen) = 0; 145 | 146 | virtual void for_each_surface(wlr_surface_iterator_func_t iterator, void* data) = 0; 147 | 148 | /// Returns true if this view is a descendant of \a ancestor. 149 | virtual bool is_transient_for(View& ancestor) = 0; 150 | 151 | /// Closes currently active popups. 152 | virtual void close_popups() = 0; 153 | 154 | /// Closes view 155 | virtual void close() = 0; 156 | 157 | /// Returns the output where this view is drawn on. 158 | OptionalRef get_views_output(Server& server); 159 | 160 | /// Marks the change of output where this view is drawn. 161 | void change_output(OptionalRef old_output, OptionalRef new_output); 162 | 163 | /// Saves the position and size of the view before becoming fullscreen. 164 | void save_state(ExpansionSavedState to_save); 165 | 166 | /// Mark view as normal after its size is confirmed recovered after an expansion. 167 | void recover(); 168 | 169 | /// Cycles through predefined widths expressed as ratios of the screen's width. 170 | void cycle_width(int screen_width); 171 | 172 | /// Returns true if the view is mapped and in not fullscreen state. 173 | bool is_mapped_and_normal(); 174 | 175 | protected: 176 | View() 177 | : geometry { 0, 0, 0, 0 } 178 | , expansion_state(ExpansionState::NORMAL) 179 | , workspace_id(-1) 180 | , x(0) 181 | , y(0) 182 | , target_x(0) 183 | , target_y(0) 184 | , mapped(false) 185 | , new_view(true) 186 | { 187 | } 188 | }; 189 | 190 | /// Registers a view to the server and attaches the event handlers. 191 | void create_view(Server& server, NotNullPointer view); 192 | 193 | #endif // CARDBOARD_VIEW_H_INCLUDED 194 | -------------------------------------------------------------------------------- /cardboard/Server.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | extern "C" { 12 | #include 13 | #include 14 | #define static 15 | #include 16 | #undef static 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | } 31 | 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #include 38 | 39 | #include "Helpers.h" 40 | #include "IPC.h" 41 | #include "Seat.h" 42 | #include "Server.h" 43 | #include "Spawn.h" 44 | 45 | bool Server::init() 46 | { 47 | wl_display = wl_display_create(); 48 | // let wlroots select the required hardware abstractions 49 | backend = wlr_backend_autocreate(wl_display, nullptr); 50 | 51 | event_loop = wl_display_get_event_loop(wl_display); 52 | 53 | renderer = wlr_backend_get_renderer(backend); 54 | wlr_renderer_init_wl_display(renderer, wl_display); 55 | 56 | compositor = wlr_compositor_create(wl_display, renderer); 57 | wlr_data_device_manager_create(wl_display); // for clipboard managers 58 | 59 | output_manager = create_output_manager(this); 60 | 61 | // https://drewdevault.com/2018/07/29/Wayland-shells.html 62 | xdg_shell = wlr_xdg_shell_create(wl_display); 63 | layer_shell = wlr_layer_shell_v1_create(wl_display); 64 | 65 | // low effort protocol implementations 66 | wlr_xdg_output_manager_v1_create(wl_display, output_manager->output_layout); 67 | wlr_gamma_control_manager_v1_create(wl_display); 68 | wlr_export_dmabuf_manager_v1_create(wl_display); 69 | wlr_screencopy_manager_v1_create(wl_display); 70 | wlr_data_control_manager_v1_create(wl_display); 71 | wlr_gtk_primary_selection_device_manager_create(wl_display); 72 | wlr_primary_selection_v1_device_manager_create(wl_display); 73 | 74 | init_seat(*this, seat, DEFAULT_SEAT); 75 | 76 | config = Config { 77 | .mouse_mods = WLR_MODIFIER_LOGO, 78 | }; 79 | 80 | for (int i = 0; i < WORKSPACE_NR; i++) { 81 | output_manager->create_workspace(this); 82 | } 83 | 84 | register_handlers(*this, NoneT {}, { 85 | { &xdg_shell->events.new_surface, Server::new_xdg_surface_handler }, 86 | { &layer_shell->events.new_surface, Server::new_layer_surface_handler }, 87 | }); 88 | 89 | seat.register_handlers(*this, &backend->events.new_input); 90 | 91 | return true; 92 | } 93 | 94 | bool Server::init_ipc() 95 | { 96 | std::string socket_path; 97 | 98 | char* env_path = getenv(libcardboard::ipc::SOCKET_ENV_VAR); 99 | if (env_path != nullptr) { 100 | socket_path = env_path; 101 | } else { 102 | std::string display = "wayland-0"; 103 | if (char* env_display = getenv("WAYLAND_DISPLAY")) { 104 | display = env_display; 105 | } 106 | socket_path = "/tmp/cardboard-" + display; 107 | } 108 | 109 | ipc = create_ipc(*this, socket_path, [this](const CommandData& command_data) -> std::string { 110 | return dispatch_command(command_data)(this).message; 111 | }).value(); 112 | 113 | return true; 114 | } 115 | 116 | #if HAVE_XWAYLAND 117 | void Server::init_xwayland() 118 | { 119 | wlr_log(WLR_DEBUG, "Initializing Xwayland"); 120 | xwayland = wlr_xwayland_create(wl_display, compositor, true); 121 | 122 | listeners.add_listener(&xwayland->events.new_surface, 123 | Listener { new_xwayland_surface_handler, this, NoneT {} }); 124 | 125 | setenv("DISPLAY", xwayland->display_name, true); 126 | } 127 | #endif 128 | 129 | bool Server::load_settings() 130 | { 131 | if (const char* config_home = getenv(CONFIG_HOME_ENV.data()); config_home != nullptr) { 132 | // please std::format end my suffering 133 | config_path += config_home; 134 | config_path += '/'; 135 | config_path += CARDBOARD_NAME; 136 | config_path += '/'; 137 | config_path += CONFIG_NAME; 138 | } else { 139 | const char* home = getenv("HOME"); 140 | if (home == nullptr) { 141 | wlr_log(WLR_ERROR, "Couldn't get home directory"); 142 | return false; 143 | } 144 | 145 | config_path += home; 146 | config_path += "/.config/"; 147 | config_path += CARDBOARD_NAME; 148 | config_path += '/'; 149 | config_path += CONFIG_NAME; 150 | } 151 | 152 | wlr_log(WLR_DEBUG, "Running config file %s", config_path.c_str()); 153 | 154 | auto error_code = spawn([&]() { 155 | execle(config_path.c_str(), config_path.c_str(), nullptr, environ); 156 | 157 | return EXIT_FAILURE; 158 | }); 159 | if (error_code.value() != 0) { 160 | wlr_log(WLR_ERROR, "Couldn't execute the config file: %s", error_code.message().c_str()); 161 | return false; 162 | } 163 | 164 | return true; 165 | } 166 | 167 | bool Server::run() 168 | { 169 | #if HAVE_XWAYLAND 170 | init_xwayland(); 171 | #endif 172 | // add UNIX socket to the Wayland display 173 | const char* socket = wl_display_add_socket_auto(wl_display); 174 | if (!socket) { 175 | wlr_backend_destroy(backend); 176 | return false; 177 | } 178 | 179 | if (!wlr_backend_start(backend)) { 180 | wlr_backend_destroy(backend); 181 | wl_display_destroy(wl_display); 182 | return false; 183 | } 184 | 185 | setenv("WAYLAND_DISPLAY", socket, true); 186 | 187 | if (!init_ipc() || !load_settings()) { 188 | return false; 189 | } 190 | 191 | view_animation = create_view_animation(this, { 17, 100 }); 192 | 193 | wlr_log(WLR_INFO, "Running Cardboard on WAYLAND_DISPLAY=%s", socket); 194 | wl_display_run(wl_display); 195 | 196 | return true; 197 | } 198 | 199 | void Server::stop() 200 | { 201 | ipc = nullptr; // release ipc system 202 | wlr_log(WLR_INFO, "Shutting down Cardboard"); 203 | #if HAVE_XWAYLAND 204 | wlr_xwayland_destroy(xwayland); 205 | #endif 206 | wl_display_destroy_clients(wl_display); 207 | wl_display_destroy(wl_display); 208 | } 209 | 210 | void Server::teardown(int code) 211 | { 212 | wl_display_terminate(wl_display); 213 | exit_code = code; 214 | } 215 | 216 | void Server::new_xdg_surface_handler(struct wl_listener* listener, void* data) 217 | { 218 | Server* server = get_server(listener); 219 | auto* xdg_surface = static_cast(data); 220 | 221 | if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { 222 | return; 223 | } 224 | 225 | create_view(*server, new XDGView(xdg_surface)); 226 | } 227 | 228 | void Server::new_layer_surface_handler(struct wl_listener* listener, void* data) 229 | { 230 | Server* server = get_server(listener); 231 | auto* layer_surface = static_cast(data); 232 | wlr_log(WLR_DEBUG, 233 | "new layer surface: namespace %s layer %d anchor %d " 234 | "size %dx%d margin %d,%d,%d,%d", 235 | layer_surface->namespace_, 236 | layer_surface->client_pending.layer, 237 | layer_surface->client_pending.anchor, 238 | layer_surface->client_pending.desired_width, 239 | layer_surface->client_pending.desired_height, 240 | layer_surface->client_pending.margin.top, 241 | layer_surface->client_pending.margin.right, 242 | layer_surface->client_pending.margin.bottom, 243 | layer_surface->client_pending.margin.left); 244 | 245 | OptionalRef output_to_assign; 246 | if (layer_surface->output) { 247 | output_to_assign = OptionalRef(static_cast(layer_surface->output->data)); 248 | } else { 249 | // Assigns output of the focused workspace 250 | output_to_assign = server->seat.get_focused_workspace(*server) 251 | .and_then([](auto& ws) { return ws.output; }) 252 | .or_else([server]() { 253 | if (server->output_manager->outputs.empty()) { 254 | return NullRef; 255 | } 256 | return OptionalRef(server->output_manager->outputs.front()); 257 | }) 258 | .or_else([layer_surface]() { wlr_layer_surface_v1_close(layer_surface); return NullRef; }); 259 | } 260 | 261 | output_to_assign.and_then([layer_surface, server](auto& output) { 262 | LayerSurface ls { .surface = layer_surface, .output = output }; 263 | ls.surface->output = output.wlr_output; 264 | create_layer(*server, std::move(ls)); 265 | }); 266 | } 267 | 268 | #if HAVE_XWAYLAND 269 | void Server::new_xwayland_surface_handler(struct wl_listener* listener, void* data) 270 | { 271 | auto* server = get_server(listener); 272 | auto* xsurface = static_cast(data); 273 | 274 | if (xsurface->override_redirect) { 275 | server->surface_manager.xwayland_or_surfaces.emplace_back(create_xwayland_or_surface(*server, xsurface)); 276 | return; 277 | } 278 | 279 | wlr_log(WLR_DEBUG, "new xwayland surface title='%s' class='%s'", xsurface->title, xsurface->class_); 280 | create_view(*server, new XwaylandView(server, xsurface)); 281 | } 282 | #endif 283 | -------------------------------------------------------------------------------- /cardboard/Seat.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CARDBOARD_SEAT_H_INCLUDED 12 | #define CARDBOARD_SEAT_H_INCLUDED 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | #include 18 | } 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "Cursor.h" 29 | #include "Keyboard.h" 30 | #include "NotNull.h" 31 | #include "OptionalRef.h" 32 | #include "View.h" 33 | 34 | struct Server; 35 | struct OutputManager; 36 | 37 | constexpr const char* DEFAULT_SEAT = "seat0"; 38 | const int WORKSPACE_SCROLL_FINGERS = 3; 39 | const double WORKSPACE_SCROLL_SENSITIVITY = 2.0; ///< sensitivity multiplier 40 | const double WORKSPACE_SCROLL_FRICTION = 0.9; ///< friction multiplier 41 | const int WORKSPACE_SWITCH_FINGERS = 4; 42 | 43 | struct Seat { 44 | struct GrabState { 45 | struct Move { 46 | NotNullPointer view; 47 | double lx, ly; 48 | int view_x, view_y; 49 | }; 50 | struct Resize { 51 | NotNullPointer view; 52 | double lx, ly; 53 | struct wlr_box geometry; 54 | uint32_t resize_edges; 55 | NotNullPointer workspace; 56 | int scroll_x; 57 | int view_x, view_y; 58 | bool old_suspend_animations; 59 | }; 60 | struct WorkspaceScroll { 61 | NotNullPointer workspace; 62 | OptionalRef dominant_view; 63 | double speed; ///< scrolling speed 64 | double delta_since_update; ///< how much the fingers moved since the last update 65 | double scroll_x; ///< the scroll of the workspace stored as a double 66 | bool ready; ///< set to true after the fingers move for the first time 67 | bool wants_to_stop; ///< set to true after lifting the fingers off the touchpad 68 | }; 69 | struct WorkspaceSwitch { 70 | NotNullPointer workspace; 71 | double direction; ///negative for "upper" workspaces, positive for below workspaces 72 | }; 73 | std::variant grab_data; 74 | }; 75 | 76 | SeatCursor cursor; 77 | std::optional grab_state; 78 | 79 | struct wlr_seat* wlr_seat; 80 | struct wlr_input_inhibit_manager* inhibit_manager; 81 | 82 | std::list keyboards; 83 | std::list> focus_stack; ///< Views ordered by the time they were focused, from most recent. 84 | 85 | std::optional focused_layer; 86 | std::optional exclusive_client; 87 | 88 | void register_handlers(Server& server, struct wl_signal* new_input); 89 | 90 | /// Returns the currently focused View. It is defined as the View currently holding keyboard focus. 91 | OptionalRef get_focused_view(); 92 | /// Hides the \a view from the screen without unmapping. Happens when a Workspace is deactivated. 93 | void hide_view(Server& server, View& view); 94 | /// Gives keyboard focus to a plain surface (OR xwayland usually) 95 | void focus_surface(struct wlr_surface* surface); 96 | /** 97 | * \brief Sets the focus state on \a view. Auto-scrolls the Workspace if it's tiled. 98 | * 99 | * If \a view is null, the previously focused view will be unfocused and no other view will be focused. 100 | */ 101 | void focus_view(Server& server, OptionalRef view, bool condense_workspace = false); 102 | /// Marks the layer as receiving keyboard focus from this seat. 103 | void focus_layer(Server& server, struct wlr_layer_surface_v1* layer); 104 | /** 105 | * \brief Focus the most recently focused view on \a column. 106 | * 107 | * \param column - must be from this workspace 108 | */ 109 | void focus_column(Server& server, Workspace::Column& column); 110 | /// Removes the \a view from the focus stack. 111 | void remove_from_focus_stack(View& view); 112 | 113 | void begin_move(Server& server, View& view); 114 | void begin_resize(Server& server, View& view, uint32_t edges); 115 | void begin_workspace_scroll(Server& server, Workspace& workspace); 116 | void process_cursor_motion(Server& server, uint32_t time = 0); 117 | void process_cursor_move(Server&, GrabState::Move move_data); 118 | void process_cursor_resize(Server&, GrabState::Resize resize_data); 119 | void process_swipe_begin(Server& server, uint32_t fingers); 120 | void process_swipe_update(Server& server, uint32_t fingers, double dx, double dy); 121 | void process_swipe_end(Server& server); 122 | void end_interactive(Server& server); 123 | void end_touchpad_swipe(Server& server); 124 | 125 | /// Updates the scroll of the workspace during three-finger swipe, taking in account speed and friction. 126 | void update_swipe(Server& server); 127 | 128 | /// Returns true if the \a view is currently in a grab operation. 129 | bool is_grabbing(View& view); 130 | 131 | /// Returns the workspace under the cursor. 132 | OptionalRef get_focused_workspace(Server& server); 133 | 134 | /// Moves the focus to a different workspace, if the workspace is already on a monitor, it focuses that monitor 135 | void focus(Server& server, Workspace& workspace); // TODO: yikes, passing Server* 136 | 137 | /// Considers a \a client as exclusive. Only the surfaces of the \a client will get input events. 138 | void set_exclusive_client(Server& server, struct wl_client* client); 139 | /** 140 | * \brief Returns true if input events can be delivered to \a surface. 141 | * 142 | * Input events can be delivered freely when there is no exclusive client. If there is 143 | * an exclusive client set, input may be allowed only if the surface belongs to that client. 144 | */ 145 | bool is_input_allowed(struct wlr_surface* surface); 146 | 147 | /// Returns true if the mod keys is pressed on any of this seat's keyboards. 148 | bool is_mod_pressed(uint32_t mods); 149 | 150 | /// Sets keyboard focus on a \a surface. 151 | void keyboard_notify_enter(struct wlr_surface* surface); 152 | 153 | public: 154 | /** 155 | * \brief Called when an input device (keyboard, mouse, touchscreen, tablet) is attached. 156 | * 157 | * The device is registered within the compositor accordingly. 158 | */ 159 | static void new_input_handler(struct wl_listener* listener, void* data); 160 | 161 | /** 162 | * \brief Activates the inhibition of input events for the requester. 163 | * 164 | * Per the wlr-input-inhibitor protocol. 165 | */ 166 | static void activate_inhibit_handler(struct wl_listener* listener, void* data); 167 | 168 | /** 169 | * \brief Deactivates the inhibiton of input events for the requester. 170 | * 171 | * Per the wlr-input-inhibitor protocol. 172 | */ 173 | static void deactivate_inhibit_handler(struct wl_listener* listener, void* data); 174 | 175 | /** 176 | * \brief Called when the cursor (mouse) is moved. 177 | * 178 | * In some cases, the mouse might "jump" to a position instead of moving by a certain delta. 179 | * This happens with the X11 backend for example. This handler only handles moves by a delta. 180 | * 181 | * \sa cursor_motion_absolute_handler for when the cursor "jumps". 182 | */ 183 | static void cursor_motion_handler(struct wl_listener* listener, void* data); 184 | 185 | /** 186 | * \brief Called when the cursor "jumps" or "warps" to an absolute position on the screen. 187 | * 188 | * \sa cursor_motion_handler for cursor moves by a delta. 189 | */ 190 | static void cursor_motion_absolute_handler(struct wl_listener* listener, void* data); 191 | 192 | /** 193 | * \brief Handles mouse button clicks. 194 | * 195 | * Besides transmitting clicks to the clients (by the means of Server::seat), 196 | * it also focuses the window under the cursor, with its side effects, such as auto-scrolling 197 | * the viewport of the Workspace the View under the cursor is in. 198 | */ 199 | static void cursor_button_handler(struct wl_listener* listener, void* data); 200 | 201 | /** 202 | * \brief Handles scrolling. 203 | */ 204 | static void cursor_axis_handler(struct wl_listener* listener, void* data); 205 | 206 | /** 207 | * \brief Handles pointer frame events. 208 | * 209 | * A frame event is sent after regular pointer events to group multiple events together. 210 | * For instance, two axis events may happend at the same time, in which case a frame event 211 | * won't be sent in between. 212 | */ 213 | static void cursor_frame_handler(struct wl_listener* listener, void* data); 214 | 215 | /** 216 | * \brief Called when the user starts a swipe on the touchpad (more than one finger). 217 | */ 218 | static void cursor_swipe_begin_handler(struct wl_listener* listener, void* data); 219 | 220 | /** 221 | * \brief Called when the user moved their fingers during a swipe on the touchpad. 222 | */ 223 | static void cursor_swipe_update_handler(struct wl_listener* listener, void* data); 224 | 225 | /** 226 | * \brief Called after the user lifted all their fingers, eding the swipe. 227 | */ 228 | static void cursor_swipe_end_handler(struct wl_listener* listener, void* data); 229 | 230 | /** 231 | * \brief Called when a client wants to set its own mouse cursor image. 232 | */ 233 | static void request_cursor_handler(struct wl_listener* listener, void* data); 234 | 235 | /** 236 | * \brief Called when a client puts something in the clipboard (Ctrl-C) 237 | */ 238 | static void request_selection_handler(struct wl_listener* listener, void* data); 239 | 240 | /** 241 | * \brief Called when a client puts something in the primary selection (selects some text). 242 | */ 243 | static void request_primary_selection_handler(struct wl_listener* listener, void* data); 244 | }; 245 | 246 | void init_seat(Server& server, Seat& seat, const char* name); 247 | 248 | #endif // CARDBOARD_SEAT_H_INCLUDED 249 | -------------------------------------------------------------------------------- /cardboard/IPC.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | extern "C" { 12 | #include 13 | } 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "IPC.h" 25 | #include "Server.h" 26 | 27 | #include 28 | #include 29 | 30 | std::optional create_ipc( 31 | Server& server, 32 | const std::string& socket_path, 33 | std::function command_callback) 34 | { 35 | int ipc_socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); 36 | 37 | if (ipc_socket_fd == -1) { 38 | wlr_log(WLR_ERROR, "Couldn't create ipc socket"); 39 | return std::nullopt; 40 | } 41 | 42 | if (fcntl(ipc_socket_fd, F_SETFD, FD_CLOEXEC) == -1) { 43 | wlr_log(WLR_ERROR, "Unable to set CLOEXEC on IPC client socket: %s", strerror(errno)); 44 | close(ipc_socket_fd); 45 | return std::nullopt; 46 | } 47 | 48 | if (fcntl(ipc_socket_fd, F_SETFL, O_NONBLOCK) == -1) { 49 | wlr_log(WLR_ERROR, "Unable to set O_NONBLOCK on IPC client socket: %s", strerror(errno)); 50 | close(ipc_socket_fd); 51 | return std::nullopt; 52 | } 53 | 54 | std::unique_ptr socket_address = std::make_unique(); 55 | 56 | socket_address->sun_family = AF_UNIX; 57 | memcpy(socket_address->sun_path, socket_path.c_str(), socket_path.size() + 1); 58 | 59 | unlink(socket_path.c_str()); 60 | if (bind(ipc_socket_fd, reinterpret_cast(socket_address.get()), sizeof(*socket_address)) == -1) { 61 | int err = errno; 62 | wlr_log(WLR_ERROR, "Couldn't bind a name ('%s') to the IPC socket. (%d)", socket_address->sun_path, err); 63 | return std::nullopt; 64 | } 65 | 66 | if (listen(ipc_socket_fd, SOMAXCONN) == -1) { 67 | wlr_log(WLR_ERROR, "Couldn't listen to the IPC socket '%s'.", socket_address->sun_path); 68 | return std::nullopt; 69 | } 70 | 71 | // add destroy display handling 72 | IPCInstance ipc = std::make_unique(IPC { 73 | &server, ipc_socket_fd, std::move(socket_address), std::move(command_callback) }); 74 | wl_event_loop_add_fd( 75 | server.event_loop, 76 | ipc_socket_fd, 77 | WL_EVENT_READABLE, 78 | IPC::handle_client_connection, 79 | ipc.get()); 80 | 81 | return ipc; 82 | } 83 | 84 | // This function implements wl_event_loop_fd_func_t 85 | int IPC::handle_client_connection(int /*fd*/, uint32_t mask, void* data) 86 | { 87 | assert(mask == WL_EVENT_READABLE); 88 | 89 | auto ipc = static_cast(data); 90 | const int client_fd = accept(ipc->socket_fd, nullptr, nullptr); 91 | if (client_fd == -1) { 92 | wlr_log(WLR_ERROR, "Failed to accept on IPC socket %s: %s", ipc->socket_address->sun_path, strerror(errno)); 93 | return 0; 94 | } 95 | 96 | int flags; 97 | if ((flags = fcntl(client_fd, F_GETFD)) == -1 98 | || fcntl(client_fd, F_SETFD, flags | FD_CLOEXEC) == -1) { 99 | wlr_log(WLR_ERROR, "Unable to set CLOEXEC on IPC client socket: %s", strerror(errno)); 100 | close(client_fd); 101 | return 0; 102 | } 103 | 104 | if ((flags = fcntl(client_fd, F_GETFL)) == -1 105 | || fcntl(client_fd, F_GETFL, flags | O_NONBLOCK) == -1) { 106 | wlr_log(WLR_ERROR, "Unable to set O_NONBLOCK on IPC client socket: %s", strerror(errno)); 107 | close(client_fd); 108 | return 0; 109 | } 110 | 111 | ipc->clients.emplace_back( 112 | ipc, 113 | client_fd, 114 | IPC::ClientState::READING_HEADER); 115 | 116 | ipc->clients.back().readable_event_source = wl_event_loop_add_fd( 117 | ipc->server->event_loop, 118 | client_fd, 119 | WL_EVENT_READABLE, 120 | IPC::handle_client_readable, 121 | &ipc->clients.back()); 122 | 123 | return 0; 124 | } 125 | 126 | int IPC::handle_client_readable(int /*fd*/, uint32_t mask, void* data) 127 | { 128 | auto client = static_cast(data); 129 | 130 | if (mask & WL_EVENT_ERROR) { 131 | wlr_log(WLR_ERROR, "IPC Client socket error, disconnecting"); 132 | client->ipc->remove_client(client); 133 | return 0; 134 | } 135 | 136 | if (mask & WL_EVENT_HANGUP) { 137 | wlr_log(WLR_DEBUG, "IPC Client on fd %d hung up, disconnecting", client->client_fd); 138 | client->ipc->remove_client(client); 139 | return 0; 140 | } 141 | 142 | int available_bytes; 143 | if (ioctl(client->client_fd, FIONREAD, &available_bytes) == -1) { 144 | wlr_log(WLR_INFO, "Unable to read pending available data"); 145 | client->ipc->remove_client(client); 146 | return 0; 147 | } 148 | 149 | switch (client->state) { 150 | case IPC::ClientState::READING_HEADER: { 151 | if (available_bytes < static_cast(libcardboard::ipc::HEADER_SIZE)) { 152 | break; 153 | } 154 | 155 | libcardboard::ipc::AlignedHeaderBuffer buffer; 156 | if (ssize_t received = recv( 157 | client->client_fd, 158 | buffer.data(), 159 | libcardboard::ipc::HEADER_SIZE, 160 | 0); 161 | received != -1) { 162 | libcardboard::ipc::Header header = libcardboard::ipc::interpret_header(buffer); 163 | client->payload_size = header.incoming_bytes; 164 | client->state = IPC::ClientState::READING_PAYLOAD; 165 | 166 | available_bytes -= received; 167 | 168 | if (client->payload_size <= available_bytes) { 169 | // do nothing jumps to the IPC::ClientState::READING_PAYLOAD case 170 | } else { 171 | break; 172 | } 173 | } else { 174 | wlr_log(WLR_INFO, "recv failed on header"); 175 | client->ipc->remove_client(client); 176 | return 0; 177 | } 178 | [[fallthrough]]; 179 | } 180 | case IPC::ClientState::READING_PAYLOAD: { 181 | if (client->payload_size > available_bytes) 182 | break; 183 | 184 | auto* buffer = new std::byte[client->payload_size]; 185 | if (ssize_t received = recv( 186 | client->client_fd, 187 | buffer, 188 | client->payload_size, 189 | 0); 190 | received == -1) { 191 | wlr_log(WLR_INFO, "couldn't read payload"); 192 | client->ipc->remove_client(client); 193 | return 0; 194 | } 195 | read_command_data(buffer, client->payload_size) 196 | .map([client](const CommandData& command_data) { 197 | client->message = client->ipc->command_callback(command_data); 198 | }) 199 | .map_error([client](const std::string& error) { 200 | using namespace std::string_literals; 201 | wlr_log(WLR_INFO, "unable to parse command: %s", error.c_str()); 202 | client->message = "Unable to parse command: " + error; 203 | }); 204 | delete[] buffer; 205 | 206 | client->writable_event_source = wl_event_loop_add_fd( 207 | client->ipc->server->event_loop, 208 | client->client_fd, 209 | WL_EVENT_WRITABLE, 210 | IPC::handle_client_writeable, 211 | client); 212 | 213 | client->state = IPC::ClientState::WRITING; 214 | break; 215 | } 216 | case IPC::ClientState::WRITING: 217 | break; // do nothing 218 | } 219 | 220 | return 0; 221 | } 222 | 223 | int IPC::handle_client_writeable(int /*fd*/, uint32_t mask, void* data) 224 | { 225 | auto client = static_cast(data); 226 | 227 | if (mask & WL_EVENT_ERROR) { 228 | wlr_log(WLR_ERROR, "IPC Client socket error, disconnecting"); 229 | client->ipc->remove_client(client); 230 | return 0; 231 | } 232 | 233 | if (mask & WL_EVENT_HANGUP) { 234 | wlr_log(WLR_DEBUG, "IPC Client on fd %d hung up, disconnecting", client->client_fd); 235 | client->ipc->remove_client(client); 236 | return 0; 237 | } 238 | 239 | if (!client->message.empty()) { 240 | libcardboard::ipc::AlignedHeaderBuffer header = libcardboard::ipc::create_header_buffer({ static_cast(client->message.size()) }); 241 | 242 | auto* buffer = new std::byte[client->message.empty() + header.size()]; 243 | std::copy( 244 | header.begin(), 245 | header.end(), 246 | buffer); 247 | 248 | std::copy( 249 | client->message.begin(), 250 | client->message.end(), 251 | reinterpret_cast(buffer)); 252 | 253 | ssize_t written = write(client->client_fd, buffer, client->message.empty() + header.size()); 254 | if (written == -1 && errno == EAGAIN) { 255 | return 0; 256 | } 257 | 258 | if (written == -1) { 259 | wlr_log(WLR_INFO, "Unable to send data to IPC client"); 260 | } 261 | } 262 | 263 | client->ipc->remove_client(client); 264 | return 0; 265 | } 266 | 267 | void IPC::remove_client(IPC::Client* client) 268 | { 269 | clients.remove_if( 270 | [client](auto& x) { return client == &x; }); 271 | } 272 | 273 | IPC::Client::~Client() 274 | { 275 | // shutdown routine for ipc client 276 | shutdown(client_fd, SHUT_RDWR); 277 | 278 | if (readable_event_source) { 279 | wl_event_source_remove(readable_event_source); 280 | } 281 | 282 | if (writable_event_source) { 283 | wl_event_source_remove(writable_event_source); 284 | } 285 | } 286 | 287 | IPC::Client::Client(IPC::Client&& other) noexcept 288 | : ipc { other.ipc } 289 | , client_fd { other.client_fd } 290 | , state { other.state } 291 | , readable_event_source { other.readable_event_source } 292 | , writable_event_source { other.writable_event_source } 293 | , payload_size { other.payload_size } 294 | , message { std::move(other.message) } 295 | { 296 | other.ipc = nullptr; 297 | other.client_fd = 0; 298 | other.readable_event_source = nullptr; 299 | other.writable_event_source = nullptr; 300 | other.payload_size = 0; 301 | } 302 | -------------------------------------------------------------------------------- /cutter/parse_arguments.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /* 3 | Copyright (C) 2020 Alexandru-Iulian Magan, Tudor-Ioan Roman, and contributors. 4 | 5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. 6 | 7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with this program. If not, see . 10 | */ 11 | #ifndef CUTTER_PARSE_ARGUMENTS_H_INCLUDED 12 | #define CUTTER_PARSE_ARGUMENTS_H_INCLUDED 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | namespace detail { 26 | using namespace std::string_literals; 27 | 28 | static auto find_mod_key(const std::string& key) 29 | { 30 | static const std::unordered_set mod_keys = { "shift", "ctrl", "alt", "super", "caps", "mod2", "mod3", "mod5" }; 31 | return mod_keys.find(key) != mod_keys.end(); 32 | }; 33 | 34 | tl::expected parse_config_mouse_mod(const std::vector& args) 35 | { 36 | if (args.size() != 1) { 37 | return tl::unexpected("malformed config value"s); 38 | } 39 | 40 | std::vector modifiers; 41 | auto locale = std::locale(""); 42 | 43 | size_t pos = 0; 44 | while (pos < args[0].size()) { 45 | auto plus_index = args[0].find('+', pos); 46 | auto token = args[0].substr(pos, plus_index - pos); 47 | 48 | if (!find_mod_key(token)) { 49 | return tl::unexpected("token '"s + token + "' is not a modifier"); 50 | } 51 | modifiers.push_back(token); 52 | if (plus_index == args[0].npos) { 53 | pos = args[0].size(); 54 | } else { 55 | pos = plus_index + 1; 56 | } 57 | } 58 | 59 | return command_arguments::config { command_arguments::config::mouse_mod { std::move(modifiers) } }; 60 | } 61 | 62 | tl::expected parse_config_gap(const std::vector& args) 63 | { 64 | if (args.size() != 1) { 65 | return tl::unexpected("malformed config value"s); 66 | } 67 | 68 | return command_arguments::config { command_arguments::config::gap { std::stoi(args[0]) } }; 69 | } 70 | 71 | tl::expected parse_config_focus_color(const std::vector& args) 72 | { 73 | if (args.size() != 4) { 74 | return tl::unexpected("malformed config values"s); 75 | } 76 | 77 | return command_arguments::config { command_arguments::config::focus_color { 78 | static_cast(std::stoi(args[0])) / 255.f, 79 | static_cast(std::stoi(args[1])) / 255.f, 80 | static_cast(std::stoi(args[2])) / 255.f, 81 | static_cast(std::stoi(args[3])) / 255.f } }; 82 | } 83 | 84 | tl::expected parse_arguments(std::vector arguments); 85 | 86 | tl::expected parse_quit(const std::vector& args) 87 | { 88 | int code = 0; 89 | if (!args.empty()) { 90 | std::stringstream oss(args[0]); 91 | if (!(oss >> code)) { 92 | return tl::unexpected("malformed exit code '"s + args[0] + "'"); 93 | } 94 | } 95 | return command_arguments::quit { code }; 96 | } 97 | 98 | tl::expected parse_focus(const std::vector& args) 99 | { 100 | if (args.empty()) 101 | return tl::unexpected("not enough arguments"s); 102 | 103 | if (args[0] == "left") { 104 | return command_arguments::focus { command_arguments::focus::Direction::Left, false }; 105 | } else if (args[0] == "right") { 106 | return command_arguments::focus { command_arguments::focus::Direction::Right, false }; 107 | } else if (args[0] == "up") { 108 | return command_arguments::focus { command_arguments::focus::Direction::Up, false }; 109 | } else if (args[0] == "down") { 110 | return command_arguments::focus { command_arguments::focus::Direction::Down, false }; 111 | } else if (args[0] == "cycle") { 112 | return command_arguments::focus { {}, true }; 113 | } 114 | 115 | return tl::unexpected("invalid direction '"s + args[0] + "'"); 116 | } 117 | 118 | tl::expected parse_exec(const std::vector& args) 119 | { 120 | return command_arguments::exec { args }; 121 | } 122 | 123 | tl::expected parse_bind(const std::vector& args) 124 | { 125 | std::vector modifiers; 126 | std::string key; 127 | 128 | auto locale = std::locale(""); 129 | 130 | if (args.size() < 2) { 131 | return tl::unexpected("not enough arguments"s); 132 | } 133 | 134 | size_t pos = 0; 135 | while (pos < args[0].size()) { 136 | auto plus_index = args[0].find('+', pos); 137 | auto token = args[0].substr(pos, plus_index - pos); 138 | 139 | if (find_mod_key(token)) { 140 | modifiers.push_back(token); 141 | } else { 142 | for (char& c : token) 143 | c = std::tolower(c, locale); 144 | key = token; 145 | } 146 | 147 | if (plus_index == args[0].npos) { 148 | pos = args[0].size(); 149 | } else { 150 | pos = plus_index + 1; 151 | } 152 | } 153 | 154 | auto sub_command_args = std::vector(args.begin() + 1, args.end()); 155 | auto command_data = parse_arguments(sub_command_args); 156 | 157 | if (!command_data.has_value()) 158 | return tl::unexpected("could not parse sub command: \n"s + command_data.error()); 159 | 160 | return command_arguments::bind { 161 | std::move(modifiers), 162 | std::move(key), 163 | std::move(*command_data) 164 | }; 165 | } 166 | 167 | tl::expected parse_close(const std::vector&) 168 | { 169 | return command_arguments::close {}; 170 | } 171 | 172 | tl::expected parse_workspace(const std::vector& args) 173 | { 174 | using namespace command_arguments; 175 | 176 | if (args.empty()) { 177 | return tl::unexpected("not enough arguments"s); 178 | } 179 | 180 | if (args[0] == "switch") { 181 | if (args.size() < 2) { 182 | return tl::unexpected("not enough arguments"s); 183 | } 184 | 185 | return workspace { workspace::switch_ { std::stoi(args[1]) } }; 186 | } else if (args[0] == "move") { 187 | if (args.size() < 2) { 188 | return tl::unexpected("not enough arguments"s); 189 | } 190 | 191 | return workspace { workspace::move { std::stoi(args[1]) } }; 192 | } else { 193 | return tl::unexpected("unknown workspace sub-command"s); 194 | } 195 | } 196 | 197 | tl::expected parse_toggle_floating(const std::vector&) 198 | { 199 | return command_arguments::toggle_floating {}; 200 | } 201 | 202 | tl::expected parse_move(const std::vector& args) 203 | { 204 | if (args.empty()) { 205 | return tl::unexpected("not enough arguments"s); 206 | } 207 | 208 | if (args.size() == 1) { 209 | return command_arguments::move { std::stoi(args[0]), 0 }; 210 | } else { 211 | return command_arguments::move { std::stoi(args[0]), std::stoi(args[1]) }; 212 | } 213 | } 214 | 215 | tl::expected parse_resize(const std::vector& args) 216 | { 217 | if (args.size() < 2) { 218 | return tl::unexpected("not enough arguments"s); 219 | } 220 | 221 | return command_arguments::resize { std::stoi(args[0]), std::stoi(args[1]) }; 222 | } 223 | 224 | tl::expected parse_insert_into_column(const std::vector&) 225 | { 226 | return command_arguments::insert_into_column {}; 227 | } 228 | 229 | tl::expected parse_pop_from_column(const std::vector&) 230 | { 231 | return command_arguments::pop_from_column {}; 232 | } 233 | 234 | tl::expected parse_config(const std::vector& args) 235 | { 236 | if (args.empty()) { 237 | return tl::unexpected("not enough arguments"s); 238 | } 239 | 240 | std::string key = args[0]; 241 | auto new_args = std::vector(std::next(args.begin()), args.end()); 242 | if (key == "mouse_mod") { 243 | return parse_config_mouse_mod(new_args); 244 | } else if (key == "focus_color") { 245 | return parse_config_focus_color(new_args); 246 | } else if (key == "gap") { 247 | return parse_config_gap(new_args); 248 | } 249 | 250 | return tl::unexpected("invalid config key '"s + key + "''"); 251 | } 252 | 253 | tl::expected parse_cycle_width(const std::vector&) 254 | { 255 | return command_arguments::cycle_width {}; 256 | } 257 | 258 | using parse_f = tl::expected (*)(const std::vector&); 259 | static std::unordered_map parse_table = { 260 | { "quit", parse_quit }, 261 | { "focus", parse_focus }, 262 | { "exec", parse_exec }, 263 | { "bind", parse_bind }, 264 | { "close", parse_close }, 265 | { "workspace", parse_workspace }, 266 | { "toggle_floating", parse_toggle_floating }, 267 | { "move", parse_move }, 268 | { "resize", parse_resize }, 269 | { "insert_into_column", parse_insert_into_column }, 270 | { "pop_from_column", parse_pop_from_column }, 271 | { "config", parse_config }, 272 | { "cycle_width", parse_cycle_width }, 273 | }; 274 | 275 | tl::expected parse_arguments(std::vector arguments) 276 | { 277 | if (arguments.empty()) { 278 | return tl::unexpected("not enough arguments"s); 279 | } 280 | 281 | if (auto it = detail::parse_table.find(arguments[0]); it != detail::parse_table.end()) { 282 | arguments.erase(arguments.begin()); 283 | return (*it->second)(arguments); 284 | } 285 | 286 | return tl::unexpected("unknown command '"s + arguments[0] + "'"); 287 | } 288 | } 289 | 290 | tl::expected parse_arguments(int argc, char* argv[]) 291 | { 292 | std::vector arguments; 293 | 294 | arguments.reserve(argc - 1); 295 | for (int i = 1; i < argc; i++) { 296 | arguments.emplace_back(std::string { argv[i] }); 297 | } 298 | 299 | return detail::parse_arguments(std::move(arguments)); 300 | } 301 | 302 | #endif //CUTTER_PARSE_ARGUMENTS_H_INCLUDED 303 | --------------------------------------------------------------------------------