From de6c07ca2a6a307bf66e9068fb54ad4e2fa97d10 Mon Sep 17 00:00:00 2001 From: Cagebreak Signing Key 1 Date: Mon, 17 Feb 2020 17:28:35 +0000 Subject: [PATCH] Release 1.0.0 - Add basic tiling compositor functionality --- .clang-format | 11 + .gitignore | 1 + Bugs.md | 12 + LICENSE | 21 + README.md | 155 ++++ cagebreak.c | 537 ++++++++++++++ config.h.in | 8 + examples/config | 72 ++ fuzz/execl_override.c | 11 + fuzz/fuzz-parse.c | 86 +++ fuzz/meson.build | 32 + idle_inhibit_v1.c | 65 ++ idle_inhibit_v1.h | 9 + keybinding.c | 818 ++++++++++++++++++++++ keybinding.h | 86 +++ keys/cagebreak_signing_key_1.asc | 65 ++ keys/cagebreak_signing_key_2.asc | 64 ++ man/cagebreak-config.5.md | 266 +++++++ man/cagebreak.1.md | 90 +++ meson.build | 231 ++++++ meson_options.txt | 2 + message.c | 179 +++++ message.h | 33 + output.c | 569 +++++++++++++++ output.h | 57 ++ pango.c | 94 +++ pango.h | 13 + parse.c | 416 +++++++++++ parse.h | 10 + render.c | 254 +++++++ render.h | 9 + seat.c | 1129 ++++++++++++++++++++++++++++++ seat.h | 115 +++ server.c | 49 ++ server.h | 56 ++ util.c | 35 + util.h | 13 + view.c | 320 +++++++++ view.h | 115 +++ workspace.c | 70 ++ workspace.h | 38 + xdg_shell.c | 355 ++++++++++ xdg_shell.h | 41 ++ xwayland.c | 226 ++++++ xwayland.h | 26 + 45 files changed, 6864 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Bugs.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cagebreak.c create mode 100644 config.h.in create mode 100644 examples/config create mode 100644 fuzz/execl_override.c create mode 100644 fuzz/fuzz-parse.c create mode 100644 fuzz/meson.build create mode 100644 idle_inhibit_v1.c create mode 100644 idle_inhibit_v1.h create mode 100644 keybinding.c create mode 100644 keybinding.h create mode 100644 keys/cagebreak_signing_key_1.asc create mode 100644 keys/cagebreak_signing_key_2.asc create mode 100644 man/cagebreak-config.5.md create mode 100644 man/cagebreak.1.md create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 message.c create mode 100644 message.h create mode 100644 output.c create mode 100644 output.h create mode 100644 pango.c create mode 100644 pango.h create mode 100644 parse.c create mode 100644 parse.h create mode 100644 render.c create mode 100644 render.h create mode 100644 seat.c create mode 100644 seat.h create mode 100644 server.c create mode 100644 server.h create mode 100644 util.c create mode 100644 util.h create mode 100644 view.c create mode 100644 view.h create mode 100644 workspace.c create mode 100644 workspace.h create mode 100644 xdg_shell.c create mode 100644 xdg_shell.h create mode 100644 xwayland.c create mode 100644 xwayland.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4374fdd --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +--- +BasedOnStyle: LLVM +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +IndentWidth: 4 +ObjCBlockIndentWidth: 4 +TabWidth: 4 +UseTab: ForIndentation +AlwaysBreakAfterReturnType: TopLevel +SpaceBeforeParens: Never +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/Bugs.md b/Bugs.md new file mode 100644 index 0000000..e22f7dd --- /dev/null +++ b/Bugs.md @@ -0,0 +1,12 @@ +# Bugs + +This file is slightly unusual to exist given that the project is currently +developed on github which offers an issue tracker. However, documenting +bugs inside of the repository makes the project more robust, should it ever +be moved to another hosting provider with a different issue tracking approach +or in case of network outages, where the github issue tracker may be unavailable +for developers. It also makes possible the description of bugs which were not +submitted on the issue tracker, should any other forms of contact become +available. + +## Issues diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..75d5a15 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2020 The Cagebreak authors +Copyright (c) 2018-2020 Jente Hidskes +Copyright (c) 2019 The Sway authors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8117d1f --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ +# Cagebreak: A Wayland Tiling Compositor Inspired by Ratpoison + +This is Cagebreak, a Wayland tiling compositor. The goal of this project is to +provide a successor to ratpoison for wayland users. However, this is +no reimplementation of ratpoison. Should you like to know if a feature +will be implemented, open an issue or get in touch with the development team. + +This README is only relevant for development resources and instructions. For a +description of Cagebreak and installation instructions for end-users, please see +the man page supplied in the `man/` directory and the [Wiki](https://github.com/project-repo/cagebreak/wiki/). + +Cagebreak is based on [Cage](https://github.com/Hjdskes/cage), a Wayland kiosk +compositor. + +## Experimenting with Cagebreak + +Cagebreak is currently being developed under arch linux and uses the libraries +as they are obtained through pacman. However, cagebreak should also work on +other distributions given the proper library versions. + +You can build Cagebreak with the [meson](https://mesonbuild.com/) build system. It +requires wayland, wlroots and xkbcommon to be installed. Note that Cagebreak is +developed against the latest tag of wlroots, in order not to constantly chase +breaking changes as soon as they occur. + +Simply execute the following steps to build Cagebreak: + +``` +$ meson build +$ ninja -C build +``` + +### Release Build + +By default, this builds a debug build. To build a release build, use `meson +build --buildtype=release`. + +### Xwayland Support + +Cagebreak comes with compile-time support for XWayland. To enable this, +first make sure that your version of wlroots is compiled with this +option. Then, add `-Dxwayland=true` to the `meson` command above. Note +that you'll need to have the XWayland binary installed on your system +for this to work. + +### Running Cagebreak + +You can start Cagebreak by running `./build/cagebreak`. If you run it from +within an existing X11 or Wayland session, it will open in a virtual output as +a window in your existing session. If you run it in a TTY, it'll run with the +KMS+DRM backend. For more configuration options, see the man pages. + +## Contributing to Cagebreak + +Cagebreak is currently developed to fit the needs of its creators. Should you desire +to implement a feature, please let us know in advance by opening an issue. However, +the feature set is intentionally limited (i.e. we removed support for a desktop +background) and will continue to be so in the future. + +Nonetheless, don't be intimidated by the (slightly lengthy) release checklist or any other +part of this file. Do what you can, open an issue and we will collaborate +toward a solution. + +### Branching strategy + +All features are to be developed on feature branches, named after the feature. + +There exists a branch `development` to which all reasonable feature branches +are merged for final testing. + +Once `development` is ready for a release, meaning that the release checklist is fulfilled, +it is merged into `master`, creating a new release, which is tagged and signed. + +Merging into master is always done by + +``` +git merge --no-ff development +git tag -u keyid version +git push origin master +``` + +and a log message roughly describing the features is added in the commit. + +### Releases + +Release checklist + + * [ ] Cursory testing + * [ ] libfuzzer testing + * [ ] Version Number + * [ ] -v flag + * [ ] git tag + * [ ] Relevant Documentation + * [ ] New features documented + * [ ] man page + * [ ] wiki + * [ ] New configuration documented + * [ ] man page + * [ ] wiki + * [ ] Changelog in README + * [ ] Document fixed bugs in Bugs.md + * [ ] Signature + * [ ] Branching Strategy + +### Signing Key + +All releases are signed by at least one of the following collection of +keys. + + * E79F6D9E113529F4B1FFE4D5C4F974D70CEC2C5B + * 4739D329C9187A1C2795C20A02ABFDEC3A40545F + +Should we at any point retire a key, we will only replace it with keys signed +by at least one of the above collection. + +Should we at any point have official mail addresses, their keys will be signed by +a valid key noted above. + +The full public keys can be found in `keys/` along with any revocation certificates. + +### Reproducible Builds + +Currently our project seems to build the same way on any given system, when compiled +multiple times. However, at the moment we are unable to supply instructions +for building our software reproducibly. Reproducible builds are planned for the +near future. + +### Fuzzing + +Along with the project source code, a fuzzing framework based on `libfuzzer` is +supplied. This allows for the testing of the parsing code responsible for reading +the `cagebreak` configuration file. When the `libfuzzer` is available (please +use the `clang` compiler to enable it), building the fuzz-testing software can +be enabled by passing `-Dfuzz=true` to meson. This generates a `build/fuzz/fuzz-parse` +binary according to the `libfuzzer` specifications. Further documentation on +how to run this binary can be found [here](https://llvm.org/docs/LibFuzzer.html). + +## Bugs + +For any bug, please [create an +issue](https://github.com/project-repo/cagebreak/issues/new) on +[GitHub](https://github.com/project-rep/cagebreak). + +Fixed bugs are to be assigned a number and summarized inside Bugs.md for future reference +independent of github, in case this service is unavailable. + +## Changelog + +### Release 1.0.0 + +Adds basic tiling window manager functionality to cage. + +## License + +Please see [LICENSE](https://github.com/project-repo/cagebreak/blob/master/LICENSE) diff --git a/cagebreak.c b/cagebreak.c new file mode 100644 index 0000000..4a71480 --- /dev/null +++ b/cagebreak.c @@ -0,0 +1,537 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2018-2020 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200812L + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if CG_HAS_XWAYLAND +#include +#endif +#include +#include +#include +#include +#if CG_HAS_XWAYLAND +#include +#endif + +#include "idle_inhibit_v1.h" +#include "keybinding.h" +#include "message.h" +#include "output.h" +#include "parse.h" +#include "seat.h" +#include "server.h" +#include "view.h" +#include "xdg_shell.h" +#if CG_HAS_XWAYLAND +#include "xwayland.h" +#endif + +#ifndef WAIT_ANY +#define WAIT_ANY -1 +#endif + +void +set_sig_handler(int sig, void (*action)(int)) { + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = action; + sigemptyset(&act.sa_mask); + if(sigaction(sig, &act, NULL)) { + wlr_log(WLR_ERROR, "Error setting signal handler: %s\n", + strerror(errno)); + } +} + +void +sig_chld_handler(int signal) { + int pid = 1; + while(pid > 0) { + pid = waitpid(WAIT_ANY, NULL, WNOHANG); + } +} + +static bool +drop_permissions(void) { + if(getuid() != geteuid() || getgid() != getegid()) { + if(setuid(getuid()) != 0 || setgid(getgid()) != 0) { + wlr_log(WLR_ERROR, "Unable to drop root, refusing to start"); + return false; + } + } + + if(setuid(0) != -1) { + wlr_log(WLR_ERROR, "Unable to drop root (we shouldn't be able to " + "restore it after setuid), refusing to start"); + return false; + } + + return true; +} + +static int +handle_signal(int signal, void *const data) { + struct cg_server *server = data; + + switch(signal) { + case SIGINT: + /* Fallthrough */ + case SIGTERM: + display_terminate(server); + return 0; + case SIGALRM: { + struct cg_output *output; + wl_list_for_each(output, &server->outputs, link) { + message_clear(output); + } + return 0; + } + default: + return 0; + } +} + +static void +usage(FILE *file, const char *const cage) { + fprintf(file, + "Usage: %s [OPTIONS]\n" + "\n" + " -r\t Rotate the output 90 degrees clockwise, specify up to three " + "times\n" +#ifdef DEBUG + " -D\t Turn on damage tracking debugging\n" +#endif + " -h\t Display this help message\n" + " -v\t Show the version number and exit\n", + cage); +} + +static bool +parse_args(struct cg_server *server, int argc, char *argv[]) { + int c; +#ifdef DEBUG + while((c = getopt(argc, argv, "rDhv")) != -1) { +#else + while((c = getopt(argc, argv, "rhv")) != -1) { +#endif + switch(c) { + case 'r': + server->output_transform++; + if(server->output_transform > WL_OUTPUT_TRANSFORM_270) { + server->output_transform = WL_OUTPUT_TRANSFORM_NORMAL; + } + break; +#ifdef DEBUG + case 'D': + server->debug_damage_tracking = true; + break; +#endif + case 'h': + usage(stdout, argv[0]); + return false; + case 'v': + fprintf(stdout, "Cagebreak version " CG_VERSION "\n"); + exit(0); + default: + usage(stderr, argv[0]); + return false; + } + } + + if(optind > argc) { + usage(stderr, argv[0]); + return false; + } + + return true; +} + +/* Parse config file. Lines longer than "max_line_size" are ignored */ +int +set_configuration(struct cg_server *server, + const char *const config_file_path) { + FILE *config_file = fopen(config_file_path, "r"); + if(config_file == NULL) { + wlr_log(WLR_ERROR, "Could not open config file \"%s\"", + config_file_path); + return -1; + } + size_t max_line_size = 256; + char line[max_line_size * sizeof(char)]; + for(unsigned int line_num = 1; + fgets(line, max_line_size, config_file) != NULL; ++line_num) { + line[strcspn(line, "\n")] = '\0'; + if(*line != '\0' && *line != '#') { + if(parse_rc_line(server, line) != 0) { + wlr_log(WLR_ERROR, "Error in config file \"%s\", line %d\n", + config_file_path, line_num); + return -1; + } + } + } + return 0; +} + +char * +get_config_file() { + const char *config_home_path = getenv("XDG_CONFIG_HOME"); + char *addition = "/cagebreak/config"; + if(config_home_path == NULL || config_home_path[0] == '\0') { + config_home_path = getenv("HOME"); + if(config_home_path == NULL || config_home_path[0] == '\0') { + return NULL; + } + addition = "/.config/cagebreak/config"; + } + char *config_path = malloc( + (strlen(config_home_path) + strlen(addition) + 1) * sizeof(char)); + sprintf(config_path, "%s%s", config_home_path, addition); + return config_path; +} + +int +main(int argc, char *argv[]) { + struct cg_server server = {0}; + struct wl_event_loop *event_loop = NULL; + struct wl_event_source *sigint_source = NULL; + struct wl_event_source *sigterm_source = NULL; + struct wl_event_source *sigalrm_source = NULL; + struct wlr_backend *backend = NULL; + struct wlr_renderer *renderer = NULL; + struct wlr_compositor *compositor = NULL; + struct wlr_data_device_manager *data_device_manager = NULL; + struct wlr_server_decoration_manager *server_decoration_manager = NULL; + struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager = NULL; + struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager = NULL; + struct wlr_screencopy_manager_v1 *screencopy_manager = NULL; + struct wlr_xdg_output_manager_v1 *output_manager = NULL; + struct wlr_gamma_control_manager_v1 *gamma_control_manager = NULL; + struct wlr_xdg_shell *xdg_shell = NULL; +#if CG_HAS_XWAYLAND + struct wlr_xwayland *xwayland = NULL; + struct wlr_xcursor_manager *xcursor_manager = NULL; +#endif + int ret = 0; + + if(!parse_args(&server, argc, argv)) { + return 1; + } + +#ifdef DEBUG + wlr_log_init(WLR_DEBUG, NULL); +#else + wlr_log_init(WLR_ERROR, NULL); +#endif + + /* Wayland requires XDG_RUNTIME_DIR to be set. */ + if(!getenv("XDG_RUNTIME_DIR")) { + wlr_log(WLR_ERROR, "XDG_RUNTIME_DIR is not set in the environment"); + return 1; + } + + server.wl_display = wl_display_create(); + if(!server.wl_display) { + wlr_log(WLR_ERROR, "Cannot allocate a Wayland display"); + return 1; + } + + server.running = true; + + server.modes = malloc(4 * sizeof(char *)); + server.modes[0] = strdup("top"); + server.modes[1] = strdup("root"); + server.modes[2] = strdup("resize"); + server.modes[3] = NULL; + + server.nws = 1; + server.message_timeout = 2; + + event_loop = wl_display_get_event_loop(server.wl_display); + sigint_source = + wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server); + sigterm_source = + wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server); + sigalrm_source = + wl_event_loop_add_signal(event_loop, SIGALRM, handle_signal, &server); + server.event_loop = event_loop; + + set_sig_handler(SIGCHLD, sig_chld_handler); + + backend = wlr_backend_autocreate(server.wl_display, NULL); + if(!backend) { + wlr_log(WLR_ERROR, "Unable to create the wlroots backend"); + ret = 1; + goto end; + } + server.backend = backend; + + if(!drop_permissions()) { + ret = 1; + goto end; + } + + server.keybindings = keybinding_list_init(); + if(server.keybindings == NULL || server.keybindings->keybindings == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate keybindings"); + ret = 1; + goto end; + } + + renderer = wlr_backend_get_renderer(backend); + wlr_renderer_init_wl_display(renderer, server.wl_display); + + server.bg_color = (float[4]){0, 0, 0, 1}; + wl_list_init(&server.outputs); + + server.output_layout = wlr_output_layout_create(); + if(!server.output_layout) { + wlr_log(WLR_ERROR, "Unable to create output layout"); + ret = 1; + goto end; + } + + compositor = wlr_compositor_create(server.wl_display, renderer); + if(!compositor) { + wlr_log(WLR_ERROR, "Unable to create the wlroots compositor"); + ret = 1; + goto end; + } + + data_device_manager = wlr_data_device_manager_create(server.wl_display); + if(!data_device_manager) { + wlr_log(WLR_ERROR, "Unable to create the data device manager"); + ret = 1; + goto end; + } + + /* Configure a listener to be notified when new outputs are + * available on the backend. We use this only to detect the + * first output and ignore subsequent outputs. */ + server.new_output.notify = handle_new_output; + wl_signal_add(&backend->events.new_output, &server.new_output); + + server.seat = seat_create(&server, backend); + if(!server.seat) { + wlr_log(WLR_ERROR, "Unable to create the seat"); + ret = 1; + goto end; + } + + server.idle = wlr_idle_create(server.wl_display); + if(!server.idle) { + wlr_log(WLR_ERROR, "Unable to create the idle tracker"); + ret = 1; + goto end; + } + + server.idle_inhibit_v1 = wlr_idle_inhibit_v1_create(server.wl_display); + if(!server.idle_inhibit_v1) { + wlr_log(WLR_ERROR, "Cannot create the idle inhibitor"); + ret = 1; + goto end; + } + server.new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1_new; + wl_signal_add(&server.idle_inhibit_v1->events.new_inhibitor, + &server.new_idle_inhibitor_v1); + wl_list_init(&server.inhibitors); + + xdg_shell = wlr_xdg_shell_create(server.wl_display); + if(!xdg_shell) { + wlr_log(WLR_ERROR, "Unable to create the XDG shell interface"); + ret = 1; + goto end; + } + server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new; + wl_signal_add(&xdg_shell->events.new_surface, + &server.new_xdg_shell_surface); + + xdg_decoration_manager = + wlr_xdg_decoration_manager_v1_create(server.wl_display); + if(!xdg_decoration_manager) { + wlr_log(WLR_ERROR, "Unable to create the XDG decoration manager"); + ret = 1; + goto end; + } + wl_signal_add(&xdg_decoration_manager->events.new_toplevel_decoration, + &server.xdg_toplevel_decoration); + server.xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration; + + server_decoration_manager = + wlr_server_decoration_manager_create(server.wl_display); + if(!server_decoration_manager) { + wlr_log(WLR_ERROR, "Unable to create the server decoration manager"); + ret = 1; + goto end; + } + wlr_server_decoration_manager_set_default_mode( + server_decoration_manager, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + + export_dmabuf_manager = + wlr_export_dmabuf_manager_v1_create(server.wl_display); + if(!export_dmabuf_manager) { + wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager"); + ret = 1; + goto end; + } + + screencopy_manager = wlr_screencopy_manager_v1_create(server.wl_display); + if(!screencopy_manager) { + wlr_log(WLR_ERROR, "Unable to create the screencopy manager"); + ret = 1; + goto end; + } + + output_manager = wlr_xdg_output_manager_v1_create(server.wl_display, + server.output_layout); + if(!output_manager) { + wlr_log(WLR_ERROR, "Unable to create the output manager"); + ret = 1; + goto end; + } + + gamma_control_manager = + wlr_gamma_control_manager_v1_create(server.wl_display); + if(!gamma_control_manager) { + wlr_log(WLR_ERROR, "Unable to create the gamma control manager"); + ret = 1; + goto end; + } + +#if CG_HAS_XWAYLAND + xwayland = wlr_xwayland_create(server.wl_display, compositor, true); + if(!xwayland) { + wlr_log(WLR_ERROR, "Cannot create XWayland server"); + ret = 1; + goto end; + } + server.new_xwayland_surface.notify = handle_xwayland_surface_new; + wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface); + + xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE); + if(!xcursor_manager) { + wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager"); + ret = 1; + goto end; + } + + if(setenv("DISPLAY", xwayland->display_name, true) < 0) { + wlr_log_errno(WLR_ERROR, "Unable to set DISPLAY for XWayland.", + "Clients may not be able to connect"); + } else { + wlr_log(WLR_DEBUG, "XWayland is running on display %s", + xwayland->display_name); + } + + if(wlr_xcursor_manager_load(xcursor_manager, 1)) { + wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme"); + } + struct wlr_xcursor *xcursor = + wlr_xcursor_manager_get_xcursor(xcursor_manager, DEFAULT_XCURSOR, 1); + if(xcursor) { + struct wlr_xcursor_image *image = xcursor->images[0]; + wlr_xwayland_set_cursor(xwayland, image->buffer, image->width * 4, + image->width, image->height, image->hotspot_x, + image->hotspot_y); + } +#endif + + const char *socket = wl_display_add_socket_auto(server.wl_display); + if(!socket) { + wlr_log_errno(WLR_ERROR, "Unable to open Wayland socket"); + ret = 1; + goto end; + } + + if(!wlr_backend_start(backend)) { + wlr_log(WLR_ERROR, "Unable to start the wlroots backend"); + ret = 1; + goto end; + } + + if(setenv("WAYLAND_DISPLAY", socket, true) < 0) { + wlr_log_errno(WLR_ERROR, "Unable to set WAYLAND_DISPLAY.", + "Clients may not be able to connect"); + } else { + wlr_log(WLR_DEBUG, + "Cagebreak " CG_VERSION " is running on Wayland display %s", + socket); + } + +#if CG_HAS_XWAYLAND + wlr_xwayland_set_seat(xwayland, server.seat->seat); +#endif + + { // config_file should only be visible as long as it is valid + char *config_file = get_config_file(); + if(config_file == NULL) { + wlr_log(WLR_ERROR, "Unable to get path to config file"); + ret = 1; + goto end; + } + if(set_configuration(&server, config_file) != 0) { + free(config_file); + ret = 1; + goto end; + } + free(config_file); + } + + /* Place the cursor to the topl left of the output layout. */ + wlr_cursor_warp(server.seat->cursor, NULL, 0, 0); + + wl_display_run(server.wl_display); + +#if CG_HAS_XWAYLAND + wlr_xwayland_destroy(xwayland); + wlr_xcursor_manager_destroy(xcursor_manager); +#endif + wl_display_destroy_clients(server.wl_display); + +end: + for(unsigned int i = 0; server.modes[i] != NULL; ++i) { + free(server.modes[i]); + } + free(server.modes); + + keybinding_list_free(server.keybindings); + wl_event_source_remove(sigint_source); + wl_event_source_remove(sigterm_source); + wl_event_source_remove(sigalrm_source); + + seat_destroy(server.seat); + /* This function is not null-safe, but we only ever get here + with a proper wl_display. */ + wl_display_destroy(server.wl_display); + wlr_output_layout_destroy(server.output_layout); + return ret; +} diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..cf6bcbc --- /dev/null +++ b/config.h.in @@ -0,0 +1,8 @@ +#ifndef CG_CONFIG_H +#define CG_CONFIG_H + +#mesondefine CG_HAS_XWAYLAND + +#mesondefine CG_VERSION + +#endif diff --git a/examples/config b/examples/config new file mode 100644 index 0000000..cd7ee3c --- /dev/null +++ b/examples/config @@ -0,0 +1,72 @@ +###################### +# General settings an key bindings +###################### + +exec xterm + +workspaces 6 +background 0.25 0.21 0.2 + +escape C-t + +bind s hsplit +bind S vsplit +bind Q only +bind D quit +bind Tab focus +bind A-Tab focusprev +bind n next +bind p prev +bind w exec firefox +bind R setmode resize +bind N nextscreen +bind P prevscreen +bind a time +bind C-n movetonextscreen +bind H exchangeleft +bind J exchangedown +bind K exchangeup +bind L exchangeright +bind h focusleft +bind j focusdown +bind k focusup +bind l focusright +bind t exec xterm +definekey resize h resizeleft +definekey resize l resizeright +definekey resize j resizedown +definekey resize k resizeup +definekey resize Escape setmode top + +###################### +#Workspaces +###################### +definekey top A-1 workspace 1 +definekey top A-2 workspace 2 +definekey top A-3 workspace 3 +definekey top A-4 workspace 4 +definekey top A-5 workspace 5 +definekey top A-6 workspace 6 + +definekey top C-1 movetoworkspace 1 +definekey top C-2 movetoworkspace 2 +definekey top C-3 movetoworkspace 3 +definekey top C-4 movetoworkspace 4 +definekey top C-5 movetoworkspace 5 +definekey top C-6 movetoworkspace 6 + +definekey top XF86_Switch_VT_1 switchvt 1 +definekey top XF86_Switch_VT_2 switchvt 2 +definekey top XF86_Switch_VT_3 switchvt 3 +definekey top XF86_Switch_VT_4 switchvt 4 +definekey top XF86_Switch_VT_5 switchvt 5 +definekey top XF86_Switch_VT_6 switchvt 6 + +###################### +#Bind Function keys +###################### +definekey top XF86AudioMute exec pactl set-sink-mute 0 toggle +definekey top XF86AudioLowerVolume exec pactl set-sink-mute 0 off&&amixer set Master 1%- +definekey top XF86AudioRaiseVolume exec pactl set-sink-mute 0 off&&amixer set Master 1%+ +definekey top XF86MonBrightnessDown exec xbacklight -dec 1 +definekey top XF86MonBrightnessUp exec xbacklight -inc 1 diff --git a/fuzz/execl_override.c b/fuzz/execl_override.c new file mode 100644 index 0000000..281282f --- /dev/null +++ b/fuzz/execl_override.c @@ -0,0 +1,11 @@ +/* This file is used by the fuzzer in order to prevent executing shell commands. + */ +#define _GNU_SOURCE +#include + +struct tm *(*orig_localtime)(const time_t *timep); + +int +execl(const char *pathname, const char *arg, ...) { + return 0; +} diff --git a/fuzz/fuzz-parse.c b/fuzz/fuzz-parse.c new file mode 100644 index 0000000..257f289 --- /dev/null +++ b/fuzz/fuzz-parse.c @@ -0,0 +1,86 @@ +#define _POSIX_C_SOURCE 200812L +#define FUZZING + +#include "../keybinding.h" +#include "../parse.h" +#include "../seat.h" +#include "../server.h" +#include +#include +#include +#include +#include +#include +#include + +#ifndef WAIT_ANY +#define WAIT_ANY -1 +#endif + +void +set_sig_handler(int sig) { + struct sigaction sa; + sa.sa_handler = SIG_IGN; // handle signal by ignoring + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if(sigaction(SIGCHLD, &sa, 0) == -1) { + perror(0); + exit(1); + } +} + +struct cg_server server; + +int +LLVMFuzzerInitialize(int *argc, char ***argv) { + set_sig_handler(SIGCHLD); + + server.wl_display = NULL; + server.event_loop = NULL; + + server.seat = malloc(sizeof(struct cg_seat)); + server.seat->mode = 0; + server.seat->default_mode = 0; + + server.idle = NULL; + server.idle_inhibit_v1 = NULL; + /* new_idle_inhibitor_v1 */ + wl_list_init(&server.inhibitors); + + server.output_layout = NULL; + wl_list_init(&server.outputs); + server.curr_output = NULL; + + /* new_output */ + + /* xdg_toplevel_decoration */ + /* new_xdg_shell_surface */ + /* new_xwayland_surface */ + server.keybindings = keybinding_list_init(); + server.output_transform = 0; + + server.running = true; + server.modes = malloc(sizeof(char *)); + server.modes[0] = NULL; + server.nws = 1; + server.message_timeout = 0; + server.bg_color = malloc(4 * sizeof(float)); + server.bg_color[0] = 0; + server.bg_color[1] = 0; + server.bg_color[2] = 0; + server.bg_color[3] = 1; + + return 0; +} + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + char *str = malloc(size * sizeof(char) + 1); + strncpy(str, (char *)data, size); + str[size] = 0; + parse_rc_line(&server, str); + free(str); + keybinding_list_free(server.keybindings); + server.keybindings = keybinding_list_init(); + return 0; +} diff --git a/fuzz/meson.build b/fuzz/meson.build new file mode 100644 index 0000000..0266717 --- /dev/null +++ b/fuzz/meson.build @@ -0,0 +1,32 @@ +fuzz_sources = [ + 'fuzz-parse.c', + ] + +fuzz_headers = [ + '../parse.h', + ] + +inc = include_directories(['..','../build/']) + +fuzz_dependencies = cagebreak_dependencies +if fuzzing_engine.found() + fuzz_dependencies += fuzzing_engine +else + link_args = [ '-fsanitize=fuzzer', ] +endif + +override_lib = shared_library('execl_override', + [ 'execl_override.c' ], + install: false + ) + +executable( + 'fuzz-parse', + fuzz_sources + fuzz_headers + cagebreak_headers + cagebreak_sources, + dependencies: fuzz_dependencies, + install: false, + include_directories: inc, + link_args: link_args + fuzz_link_args, + c_args: fuzz_compile_args, + link_with: override_lib, + ) diff --git a/idle_inhibit_v1.c b/idle_inhibit_v1.c new file mode 100644 index 0000000..ee7ca82 --- /dev/null +++ b/idle_inhibit_v1.c @@ -0,0 +1,65 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2019 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include + +#include "idle_inhibit_v1.h" +#include "server.h" + +struct cg_idle_inhibitor_v1 { + struct cg_server *server; + + struct wl_list link; // server::inhibitors + struct wl_listener destroy; +}; + +static void +idle_inhibit_v1_check_active(struct cg_server *server) { + /* As of right now, this does not check whether the inhibitor + * is visible or not.*/ + bool inhibited = !wl_list_empty(&server->inhibitors); + wlr_idle_set_enabled(server->idle, NULL, !inhibited); +} + +static void +handle_destroy(struct wl_listener *listener, void *data) { + struct cg_idle_inhibitor_v1 *inhibitor = + wl_container_of(listener, inhibitor, destroy); + struct cg_server *server = inhibitor->server; + + wl_list_remove(&inhibitor->link); + wl_list_remove(&inhibitor->destroy.link); + free(inhibitor); + + idle_inhibit_v1_check_active(server); +} + +void +handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data) { + struct cg_server *server = + wl_container_of(listener, server, new_idle_inhibitor_v1); + struct wlr_idle_inhibitor_v1 *wlr_inhibitor = data; + + struct cg_idle_inhibitor_v1 *inhibitor = + calloc(1, sizeof(struct cg_idle_inhibitor_v1)); + if(!inhibitor) { + return; + } + + inhibitor->server = server; + wl_list_insert(&server->inhibitors, &inhibitor->link); + + inhibitor->destroy.notify = handle_destroy; + wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy); + + idle_inhibit_v1_check_active(server); +} diff --git a/idle_inhibit_v1.h b/idle_inhibit_v1.h new file mode 100644 index 0000000..04c585f --- /dev/null +++ b/idle_inhibit_v1.h @@ -0,0 +1,9 @@ +#ifndef CG_IDLE_INHIBIT_H +#define CG_IDLE_INHIBIT_H + +#include + +void +handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data); + +#endif diff --git a/keybinding.c b/keybinding.c new file mode 100644 index 0000000..e410538 --- /dev/null +++ b/keybinding.c @@ -0,0 +1,818 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keybinding.h" +#include "message.h" +#include "output.h" +#include "seat.h" +#include "server.h" +#include "view.h" +#include "workspace.h" +#include "xdg_shell.h" + +int +keybinding_resize(struct keybinding_list *list) { + if(list->length == list->capacity) { + list->capacity *= 2; + struct keybinding **new_list = + realloc(list->keybindings, sizeof(void *) * list->capacity); + if(new_list == NULL) { + return -1; + } else { + list->keybindings = new_list; + return 0; + } + } + return 0; +} + +struct keybinding ** +find_keybinding(const struct keybinding_list *list, + const struct keybinding *keybinding) { + struct keybinding **it = list->keybindings; + for(size_t i = 0; i < list->length; ++i, ++it) { + if(!(keybinding->modifiers ^ (*it)->modifiers) && + keybinding->mode == (*it)->mode && keybinding->key == (*it)->key) { + return it; + } + } + return NULL; +} + +void +keybinding_free(struct keybinding *keybinding) { + switch(keybinding->action) { + case KEYBINDING_RUN_COMMAND: + if(keybinding->data.c != NULL) { + free(keybinding->data.c); + } + default: + break; + } + free(keybinding); +} + +int +keybinding_list_push(struct keybinding_list *list, + struct keybinding *keybinding) { + + /* Error resizing list */ + if(keybinding_resize(list) != 0) { + return -1; + } + + /*Maintain that only a single keybinding for a key, modifier and mode may + * exist*/ + struct keybinding **found_keybinding = find_keybinding(list, keybinding); + if(found_keybinding != NULL) { + keybinding_free(*found_keybinding); + *found_keybinding = keybinding; + wlr_log(WLR_DEBUG, "A keybinding was found twice in the config file."); + } else { + list->keybindings[list->length] = keybinding; + ++list->length; + } + return 0; +} + +struct keybinding_list * +keybinding_list_init() { + struct keybinding_list *list = malloc(sizeof(struct keybinding_list)); + list->keybindings = malloc(sizeof(struct keybinding *)); + list->capacity = 1; + list->length = 0; + return list; +} + +void +keybinding_list_free(struct keybinding_list *list) { + for(unsigned int i = 0; i < list->length; ++i) { + keybinding_free(list->keybindings[i]); + } + free(list->keybindings); + free(list); +} + +struct cg_tile * +find_right_tile(const struct cg_tile *tile) { + struct cg_tile *it = tile->next; + while(it != tile && it->tile.x != tile->tile.x + tile->tile.width) { + it = it->next; + } + if(it == tile) { + return NULL; + } else { + return it; + } +} + +struct cg_tile * +find_left_tile(const struct cg_tile *tile) { + struct cg_tile *it = tile->prev; + while(it != tile && it->tile.x + it->tile.width != tile->tile.x) { + it = it->prev; + } + if(it == tile) { + return NULL; + } else { + return it; + } +} + +struct cg_tile * +find_top_tile(const struct cg_tile *tile) { + struct cg_tile *it = tile->prev; + while(it != tile && it->tile.y + it->tile.height != tile->tile.y) { + it = it->prev; + } + if(it == tile) { + return NULL; + } else { + return it; + } +} + +struct cg_tile * +find_bottom_tile(const struct cg_tile *tile) { + struct cg_tile *it = tile->next; + while(it != tile && it->tile.y != tile->tile.y + tile->tile.height) { + it = it->next; + } + if(it == tile) { + return NULL; + } else { + return it; + } +} + +void +swap_tile(struct cg_tile *tile, + struct cg_tile *(*find_tile)(const struct cg_tile *)) { + struct cg_tile *swap_tile = find_tile(tile); + struct cg_server *server = tile->workspace->server; + if(swap_tile == NULL || swap_tile == tile) { + return; + } + struct cg_view *tmp_view = tile->view; + tile->view = swap_tile->view; + swap_tile->view = tmp_view; + if(tile->view != NULL) { + view_maximize(tile->view, &tile->tile); + view_damage_whole(tile->view); + } else { + wlr_output_damage_add_box(tile->workspace->output->damage, &tile->tile); + } + workspace_focus_tile( + server->curr_output->workspaces[server->curr_output->curr_workspace], + swap_tile); + if(swap_tile->view != NULL) { + view_maximize(swap_tile->view, &swap_tile->tile); + view_damage_whole(swap_tile->view); + } else { + wlr_output_damage_add_box(tile->workspace->output->damage, &tile->tile); + } + + seat_set_focus(server->seat, swap_tile->view); +} + +void +swap_tile_left(struct cg_tile *tile) { + swap_tile(tile, find_left_tile); +} + +void +swap_tile_right(struct cg_tile *tile) { + swap_tile(tile, find_right_tile); +} + +void +swap_tile_top(struct cg_tile *tile) { + swap_tile(tile, find_top_tile); +} + +void +swap_tile_bottom(struct cg_tile *tile) { + swap_tile(tile, find_bottom_tile); +} + +void +focus_tile(struct cg_tile *tile, + struct cg_tile *(*find_tile)(const struct cg_tile *tile)) { + struct cg_tile *new_tile = find_tile(tile); + if(new_tile == NULL) { + return; + } + struct cg_server *server = new_tile->workspace->server; + workspace_focus_tile(tile->workspace, new_tile); + seat_set_focus(server->seat, new_tile->view); +} + +void +focus_tile_left(struct cg_tile *tile) { + focus_tile(tile, find_left_tile); +} + +void +focus_tile_right(struct cg_tile *tile) { + focus_tile(tile, find_right_tile); +} + +void +focus_tile_top(struct cg_tile *tile) { + focus_tile(tile, find_top_tile); +} + +void +focus_tile_bottom(struct cg_tile *tile) { + focus_tile(tile, find_bottom_tile); +} + +// Returns whether x is between a and b (a exclusive, b exclusive) where +// anext; it != tile && it != NULL; + it = it->next) { + if(it == parent) { + continue; + } + if(it->tile.x == tile->tile.x + tile->tile.width) { + if(!resize_allowed(it, tile, dim_offset, -dim_offset, get_coord, + get_dim)) { + return false; + } + } else if(it->tile.x + it->tile.width == tile->tile.x) { + if(!resize_allowed(it, tile, 0, coord_offset, get_coord, get_dim)) { + return false; + } + } + } + return true; +} + +void +resize(struct cg_tile *tile, const struct cg_tile *parent, int coord_offset, + int dim_offset, int *(*get_coord)(struct cg_tile *tile), + int *(*get_dim)(struct cg_tile *tile)) { + if(coord_offset == 0 && dim_offset == 0) { + return; + } + + for(struct cg_tile *it = tile->next; it != tile && it != NULL; + it = it->next) { + if(it == parent) { + continue; + } + if(*get_coord(it) == *get_coord(tile) + *get_dim(tile)) { + resize(it, tile, dim_offset, -dim_offset, get_coord, get_dim); + } else if(*get_coord(it) + *get_dim(it) == *get_coord(tile)) { + resize(it, tile, 0, coord_offset, get_coord, get_dim); + } + } + + int old_x = tile->tile.x, old_y = tile->tile.y; + *get_coord(tile) += coord_offset; + *get_dim(tile) += dim_offset; + struct wlr_box damage_box = { + .x = old_x, + .y = old_y, + .width = tile->tile.x - old_x == 0 ? tile->tile.width : coord_offset, + .height = tile->tile.y == 0 ? tile->tile.height : coord_offset}; + wlr_output_damage_add_box(tile->workspace->output->damage, &damage_box); + if(tile->view != NULL) { + view_maximize(tile->view, &tile->tile); + } +} + +int * +get_width(struct cg_tile *tile) { + return &tile->tile.width; +} + +int * +get_height(struct cg_tile *tile) { + return &tile->tile.height; +} + +int * +get_x(struct cg_tile *tile) { + return &tile->tile.x; +} + +int * +get_y(struct cg_tile *tile) { + return &tile->tile.y; +} + +bool +resize_allowed_horizontal(struct cg_tile *tile, struct cg_tile *parent, + int x_offset, int width_offset) { + return resize_allowed(tile, parent, x_offset, width_offset, get_x, + get_width); +} + +bool +resize_allowed_vertical(struct cg_tile *tile, struct cg_tile *parent, + int y_offset, int height_offset) { + return resize_allowed(tile, parent, y_offset, height_offset, get_y, + get_height); +} + +void +resize_horizontal(struct cg_tile *tile, struct cg_tile *parent, int x_offset, + int width_offset) { + resize(tile, parent, x_offset, width_offset, get_x, get_width); +} + +void +resize_vertical(struct cg_tile *tile, struct cg_tile *parent, int y_offset, + int height_offset) { + resize(tile, parent, y_offset, height_offset, get_y, get_height); +} + +/* hpixs: positiv -> right, negative -> left; vpixs: positiv -> down, negative + * -> up */ +void +resize_tile(struct cg_server *server, int hpixs, int vpixs) { + struct cg_output *output = server->curr_output; + struct wlr_box *output_box = wlr_output_layout_get_box( + output->server->output_layout, output->wlr_output); + + struct cg_tile *focused = + output->workspaces[output->curr_workspace]->focused_tile; + /* First do the horizontal adjustment */ + if(hpixs != 0 && focused->tile.width < output_box->width && + is_between_strict(0, output_box->width, focused->tile.width + hpixs)) { + int x_offset = 0; + /* In case we are on the total right, move the left edge of the tile */ + if(focused->tile.x + focused->tile.width == output_box->width) { + x_offset = -hpixs; + } + bool resize_allowed = + resize_allowed_horizontal(focused, NULL, x_offset, hpixs); + if(resize_allowed) { + resize_horizontal(focused, NULL, x_offset, hpixs); + } + } + /* Repeat for vertical */ + if(vpixs != 0 && focused->tile.height < output_box->height && + is_between_strict(0, output_box->height, focused->tile.height + vpixs)) { + int y_offset = 0; + if(focused->tile.y + focused->tile.height == output_box->height) { + y_offset = -vpixs; + } + bool resize_allowed = + resize_allowed_vertical(focused, NULL, y_offset, vpixs); + if(resize_allowed) { + resize_vertical(focused, NULL, y_offset, vpixs); + } + } +} + +void +keybinding_workspace_fullscreen(struct cg_server *server) { + struct cg_view *current_view = seat_get_focus(server->seat); + struct cg_output *output = server->curr_output; + + /* We are focused on the background */ + if(current_view == NULL) { + struct cg_view *it = NULL; + wl_list_for_each(it, &output->workspaces[output->curr_workspace]->views, + link) { + if(view_is_visible(it)) { + current_view = it; + break; + } + } + } + + workspace_free_tiles(output->workspaces[output->curr_workspace]); + full_screen_workspace_tiles(output, + output->workspaces[output->curr_workspace]); + + seat_set_focus(server->seat, current_view); +} + +// Switch to a differerent virtual terminal +static int +keybinding_switch_vt(struct wlr_backend* backend, unsigned int vt) { + if(wlr_backend_is_multi(backend)) { + struct wlr_session* session = wlr_backend_get_session(backend); + if(session) { + wlr_session_change_vt(session, vt); + } + return 0; + } + return -1; +} + +/* Split screen (vertical or horizontal) + * Important: Do not attempt to perform mathematical simplifications in this + * function without taking rounding errors into account. */ +static void +keybinding_split_output(struct cg_output *output, bool vertical) { + struct cg_view *original_view = seat_get_focus(output->server->seat); + struct cg_workspace *curr_workspace = + output->workspaces[output->curr_workspace]; + int32_t width = curr_workspace->focused_tile->tile.width; + int32_t height = curr_workspace->focused_tile->tile.height; + int32_t x = curr_workspace->focused_tile->tile.x; + int32_t y = curr_workspace->focused_tile->tile.y; + int32_t new_width, new_height; + + if(vertical) { + new_width = width / 2; + new_height = height; + } else { + new_width = width; + new_height = height / 2; + } + if(new_width < 1 || new_height < 1) { + return; + } + + struct cg_view *next_view = NULL; + struct cg_view *it; + wl_list_for_each(it, &output->workspaces[output->curr_workspace]->views, + link) { + if(!view_is_visible(it) && original_view != it) { + next_view = it; + break; + } + } + + int32_t new_x, new_y; + if(vertical) { + new_x = x + new_width; + new_y = y; + } else { + new_x = x; + new_y = y + new_height; + } + + struct cg_tile *new_tile = calloc(1, sizeof(struct cg_tile)); + new_tile->tile.x = new_x; + new_tile->tile.y = new_y; + new_tile->tile.width = x + width - new_x; + new_tile->tile.height = y + height - new_y; + new_tile->prev = curr_workspace->focused_tile; + new_tile->next = curr_workspace->focused_tile->next; + new_tile->view = next_view; + new_tile->workspace = curr_workspace; + curr_workspace->focused_tile->next->prev = new_tile; + curr_workspace->focused_tile->next = new_tile; + + curr_workspace->focused_tile->tile.width = new_width; + curr_workspace->focused_tile->tile.height = new_height; + workspace_focus_tile(curr_workspace, curr_workspace->focused_tile); + + if(next_view != NULL) { + view_maximize(next_view, &new_tile->tile); + } + + if(original_view != NULL) { + view_maximize(original_view, &curr_workspace->focused_tile->tile); + } +} + +static void +keybinding_split_vertical(struct cg_server *server) { + keybinding_split_output(server->curr_output, true); +} + +static void +keybinding_split_horizontal(struct cg_server *server) { + keybinding_split_output(server->curr_output, false); +} + +static void +into_process(const char *command) { + execl("/bin/sh", "sh", "-c", command, (char *)NULL); + _exit(1); +} + +void +keybinding_cycle_outputs(struct cg_server *server, bool reverse) { + if(reverse) { + server->curr_output = wl_container_of(server->curr_output->link.prev, + server->curr_output, link); + } else { + server->curr_output = wl_container_of(server->curr_output->link.next, + server->curr_output, link); + } + if(&server->curr_output->link == &server->outputs) { + if(reverse) { + server->curr_output = wl_container_of( + server->curr_output->link.prev, server->curr_output, link); + } else { + server->curr_output = wl_container_of( + server->curr_output->link.next, server->curr_output, link); + } + } + seat_set_focus( + server->seat, + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view); + message_printf(server->curr_output, "Current Output"); +} + +/* Cycle through views, whereby the workspace does not change */ +void +keybinding_cycle_views(struct cg_server *server, bool reverse) { + struct cg_workspace *curr_workspace = + server->curr_output->workspaces[server->curr_output->curr_workspace]; + struct cg_view *current_view = curr_workspace->focused_tile->view; + + struct cg_view *new_view; + // We are focused on the desktop + if(current_view == NULL) { + current_view = wl_container_of(&curr_workspace->views, new_view, link); + if(reverse) { + new_view = + wl_container_of(curr_workspace->views.prev, new_view, link); + } else { + new_view = + wl_container_of(curr_workspace->views.next, new_view, link); + } + } else { + if(reverse) { + new_view = wl_container_of(current_view->link.prev, new_view, link); + } else { + new_view = wl_container_of(current_view->link.next, new_view, link); + } + } + while(&new_view->link != ¤t_view->link && + (&new_view->link == &curr_workspace->views || + view_is_visible(new_view))) { + if(reverse) { + new_view = wl_container_of(new_view->link.prev, new_view, link); + } else { + new_view = wl_container_of(new_view->link.next, new_view, link); + } + } + if(&new_view->link == &curr_workspace->views) { + return; + } + + wlr_output_damage_add_box(curr_workspace->output->damage, &curr_workspace->focused_tile->tile); + seat_set_focus(server->seat, new_view); + /* Move the previous view to the end of the list unless we are focused on + * the desktop*/ + if(!reverse && ¤t_view->link != &curr_workspace->views) { + wl_list_remove(¤t_view->link); + wl_list_insert(curr_workspace->views.prev, ¤t_view->link); + } +} + +void +keybinding_cycle_tiles(struct cg_server *server, bool reverse) { + struct cg_output *output = server->curr_output; + struct cg_workspace *workspace = output->workspaces[output->curr_workspace]; + if(reverse) { + workspace_focus_tile(workspace, workspace->focused_tile->prev); + } else { + workspace_focus_tile(workspace, workspace->focused_tile->next); + } + struct cg_view *next_view = workspace->focused_tile->view; + + seat_set_focus(server->seat, next_view); +} + +int +keybinding_switch_ws(struct cg_server *server, uint32_t ws) { + if(ws > server->nws) { + wlr_log(WLR_ERROR, + "Requested workspace %u, but only have %u workspaces.", ws, + server->nws); + return -1; + } + struct cg_output *output = server->curr_output; + output->curr_workspace = ws; + seat_set_focus(server->seat, + server->curr_output->workspaces[ws]->focused_tile->view); + wlr_output_damage_add_whole(output->damage); + message_printf(server->curr_output, "Workspace %d", ws + 1); + return 0; +} + +void +keybinding_show_time(struct cg_server *server) { + char *msg, *tmp; + time_t timep; + + timep = time(NULL); + tmp = ctime(&timep); + msg = strdup(tmp); + msg[strcspn(msg, "\n")] = '\0'; /* Remove the newline */ + + message_printf(server->curr_output, "%s", msg); +} + +void +keybinding_move_view_to_next_output(struct cg_server *server) { + if(wl_list_length(&server->outputs) <= 1) { + return; + } + struct cg_view *view = + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view; + if(view != NULL) { + wl_list_remove(&view->link); + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view = NULL; + keybinding_cycle_views(server, false); + view_damage_whole(view); + if(server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view == NULL) { + seat_set_focus(server->seat, NULL); + } + } + keybinding_cycle_outputs(server, false); + if(view != NULL) { + wl_list_insert(&server->curr_output + ->workspaces[server->curr_output->curr_workspace] + ->views, + &view->link); + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view = view; + view->workspace = server->curr_output + ->workspaces[server->curr_output->curr_workspace]; + view_position(view); + seat_set_focus(server->seat, view); + } +} + +void +keybinding_move_view_to_workspace(struct cg_server *server, uint32_t ws) { + struct cg_view *view = + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view; + if(view != NULL) { + wl_list_remove(&view->link); + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view = NULL; + keybinding_cycle_views(server, false); + if(server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view == NULL) { + seat_set_focus(server->seat, NULL); + } + } + keybinding_switch_ws(server, ws); + if(view != NULL) { + struct cg_workspace *ws = + server->curr_output + ->workspaces[server->curr_output->curr_workspace]; + view->workspace = ws; + wl_list_insert(&ws->views, &view->link); + ws->focused_tile->view = view; + view_maximize(view, &ws->focused_tile->tile); + seat_set_focus(server->seat, view); + } +} + +/* Hint: see keybinding.h for details on "data" */ +int +run_action(enum keybinding_action action, struct cg_server *server, + union keybinding_params data) { + switch(action) { + case KEYBINDING_QUIT: + display_terminate(server); + break; + case KEYBINDING_CHANGE_TTY: + return keybinding_switch_vt(server->backend, data.u); + case KEYBINDING_LAYOUT_FULLSCREEN: + keybinding_workspace_fullscreen(server); + break; + case KEYBINDING_SPLIT_HORIZONTAL: + keybinding_split_horizontal(server); + break; + case KEYBINDING_SPLIT_VERTICAL: + keybinding_split_vertical(server); + break; + case KEYBINDING_RUN_COMMAND: + if(fork() == 0) { + into_process(data.c); + } + break; + case KEYBINDING_CYCLE_VIEWS: + keybinding_cycle_views(server, data.b); + break; + case KEYBINDING_CYCLE_TILES: + keybinding_cycle_tiles(server, data.b); + break; + case KEYBINDING_CYCLE_OUTPUT: + keybinding_cycle_outputs(server, data.b); + break; + case KEYBINDING_SWITCH_WORKSPACE: + keybinding_switch_ws(server, data.u); + break; + case KEYBINDING_SWITCH_MODE: + server->seat->mode = data.u; + break; + case KEYBINDING_SWITCH_DEFAULT_MODE: + server->seat->mode = data.u; + server->seat->default_mode = data.u; + break; + case KEYBINDING_NOOP: + break; + case KEYBINDING_SHOW_TIME: + keybinding_show_time(server); + break; + case KEYBINDING_RESIZE_TILE_HORIZONTAL: + resize_tile(server, data.i, 0); + break; + case KEYBINDING_RESIZE_TILE_VERTICAL: + resize_tile(server, 0, data.i); + break; + case KEYBINDING_MOVE_VIEW_TO_WORKSPACE: { + keybinding_move_view_to_workspace(server, data.u); + break; + } + case KEYBINDING_SWAP_LEFT: { + swap_tile_left( + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile); + break; + } + case KEYBINDING_SWAP_RIGHT: { + swap_tile_right( + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile); + break; + } + case KEYBINDING_SWAP_TOP: { + swap_tile_top( + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile); + break; + } + case KEYBINDING_SWAP_BOTTOM: { + swap_tile_bottom( + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile); + break; + } + case KEYBINDING_FOCUS_LEFT: { + focus_tile_left( + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile); + break; + } + case KEYBINDING_FOCUS_RIGHT: { + focus_tile_right( + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile); + break; + } + case KEYBINDING_FOCUS_TOP: { + focus_tile_top( + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile); + break; + } + case KEYBINDING_FOCUS_BOTTOM: { + focus_tile_bottom( + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile); + break; + } + case KEYBINDING_MOVE_VIEW_TO_NEXT_OUTPUT: { + keybinding_move_view_to_next_output(server); + break; + } + default: { + wlr_log(WLR_ERROR, + "run_action was called with a value not present in \"enum " + "keybinding_action\". This should not happen."); + return -1; + } + } + return 0; +} diff --git a/keybinding.h b/keybinding.h new file mode 100644 index 0000000..d06bbbb --- /dev/null +++ b/keybinding.h @@ -0,0 +1,86 @@ +#ifndef KEYBINDING_H + +#define KEYBINDING_H + +#include "config.h" + +#include +#include + +struct cg_server; + +/* Important: if you add a keybinding which uses data.c or requires "free" + * to be called, don't forget to add it to the function "keybinding_list_free" + * in keybinding.c */ +enum keybinding_action { + KEYBINDING_RUN_COMMAND, // data.c is the string to execute + KEYBINDING_SPLIT_VERTICAL, + KEYBINDING_SPLIT_HORIZONTAL, + KEYBINDING_CHANGE_TTY, // data.u is the desired tty + KEYBINDING_LAYOUT_FULLSCREEN, + KEYBINDING_CYCLE_VIEWS, // data.b is 0 if forward, 1 if reverse + KEYBINDING_CYCLE_TILES, // data.b is 0 if forward, 1 if reverse + KEYBINDING_CYCLE_OUTPUT, // data.b is 0 if forward, 1 if reverse + KEYBINDING_QUIT, + KEYBINDING_NOOP, + KEYBINDING_SWITCH_WORKSPACE, // data.u is the desired workspace + KEYBINDING_SWITCH_MODE, // data.u is the desired mode + KEYBINDING_SWITCH_DEFAULT_MODE, // data.u is the desired mode + KEYBINDING_RESIZE_TILE_HORIZONTAL, // data.i is the number of pixels to add + // to the current width + KEYBINDING_RESIZE_TILE_VERTICAL, // data.i is the number of pixels to add to + // the current height + KEYBINDING_MOVE_VIEW_TO_WORKSPACE, // data.u is the desired workspace + KEYBINDING_MOVE_VIEW_TO_NEXT_OUTPUT, + KEYBINDING_SHOW_TIME, + + KEYBINDING_SWAP_LEFT, + KEYBINDING_SWAP_RIGHT, + KEYBINDING_SWAP_TOP, + KEYBINDING_SWAP_BOTTOM, + + KEYBINDING_FOCUS_LEFT, + KEYBINDING_FOCUS_RIGHT, + KEYBINDING_FOCUS_TOP, + KEYBINDING_FOCUS_BOTTOM, +}; + +union keybinding_params { + char *c; + uint32_t u; + int32_t i; + bool b; +}; + +struct keybinding { + uint16_t mode; + xkb_mod_mask_t modifiers; + xkb_keysym_t key; + enum keybinding_action action; + union keybinding_params data; // See enum keybinding_action for details +}; + +struct keybinding_list { + uint32_t length; + uint32_t capacity; + struct keybinding **keybindings; +}; + +int +keybinding_list_push(struct keybinding_list *list, + struct keybinding *keybinding); +void +keybinding_list_free(struct keybinding_list *list); +void +keybinding_cycle_outputs(struct cg_server *server, bool reverse); +struct keybinding ** +find_keybinding(const struct keybinding_list *list, + const struct keybinding *keybinding); +struct keybinding_list * +keybinding_list_init(); + +int +run_action(enum keybinding_action action, struct cg_server *server, + union keybinding_params data); + +#endif /* end of include guard KEYBINDINGS_H */ diff --git a/keys/cagebreak_signing_key_1.asc b/keys/cagebreak_signing_key_1.asc new file mode 100644 index 0000000..835d485 --- /dev/null +++ b/keys/cagebreak_signing_key_1.asc @@ -0,0 +1,65 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBF5Fb5sBEADZAsr5mxQgX3lbBVmk/5Svml7uTH49PLYW46KkQJ7wp4f6FOGJ +vGdmTdOj7fiB6/Wt1ivklq5eQlhhCecoKq/1yOk51E4EO6SnsUKy6WLJPxGz5MTl +Sqs+ryJ2KYjSmkazIK3a+/Kr2LaL2n4NaVSL8iIjbUmlIywO0AsNGgy1bcXtHynQ +2lI6KkifEpgMSPrQvGWyYzzIjh6XBsNbyrzqodZdQXcwHUFPtzcnKLdeeeeV6Pj3 +Z7Ys64kl3gWLOeHPICubavOVuqUV68XsuymtiFw5sMJzTerTNcflwJ8//f9tpEg1 +5p1o33+OlSc3ILinunsIXjyDYe4Bt4ufyfooCHhRtXHhiSPfCSG7pAAZzG/W6C6y +UBHl4GBG0s9YrMXSufPqwLD0Pmd+RbGstekTPV6DJ/RozCQztApkxAx0PWCTG3fW +hxstb/yh0jmrHKQeQ4B3FGR/9iNjZ9kc/qQZAG+GVKtuitvDQ/Ud8My45LXMyWYV +Y/U/Sed40zZ2X+H87+RIEeWIcZmestftVRCb1viJG5vFlPHFZtYwboPMhLrFwFiO +F6uYXIs63qF0f12zW/OXgETky2uKJssfWswt+doKHJcIetxK38GDpb4ONpvlNBZH +ma9J+atziUd9xz2bS+0zd8H2N6H+gtk2hPGVu+3tdsyG2kNLi5Gt2gHi8QARAQAB +tFNDYWdlYnJlYWsgU2lnbmluZyBLZXkgMSAoY2FnZWJyZWFrIHNpZ25pbmcga2V5 +KSA8Y2FnZWJyZWFrX3NpZ25pbmdfa2V5XzFAY2FnZWJyZWFrPokCVAQTAQgAPhYh +BOefbZ4RNSn0sf/k1cT5dNcM7CxbBQJeRW+bAhsDBQkB4TOABQsJCAcCBhUKCQgL +AgQWAgMBAh4BAheAAAoJEMT5dNcM7CxbUegQAI27BRuSVS8W6blSPAFdIt6s/XqQ +wTph/6OqiUl3kxxaJv1E4Gz3JY+VXnOUkgTTsEAa/4T4bpVWuOof91jFchXGgMAL +qJx9cJrTbEGmdxUS59EdPMMGLKXiZ0BWH3aGVTIkVIKbZAYH6TY3gzHMOzSXrzDl +vGnVFa9+0YKSa/jY5dVyMKIA5MoaHftrM8m1iQgR7olsmxFNcZhk0pykK/zpDmOG +Wv5Sh9jtyGsuEPydGDUsAPRQiNzUBlkTuEOlVut2GYhI2p/lTQG2c9phoY5BZqMz +4EOrWYUuHrw48cZdXq4B5zHzwXs/lxnV317e7K+jJmnezJH4e34rs2l9lz6uObeZ +RN4CW4Rf6O250BPwYcH2t2JT6AkP681h/MQxLEzCVesBPk3Ki53e0yZFUU8lmO2G +/U0/rxZD2MksUfqtNOAbytFL46DvSKydD9eyLRrD/Kq29m3grMXZpVwe0M2Cym1g +A/HVrBgCNZnAWGYn7ZkUoKc/vzOquyePsvsMnFuTp4U2a4BLf6G0GYJJH5Xq6HKl +f1+QjRIozTthFQF0HlBG2Z2kfFRPVdr5uCe882jHHzXBUbp6bAvcZ3lW4lOx76T6 +dnIUrMNZFIxSnDEGz5Uwo4k2a1KUa1tiKV2KZIfFhzrCc636gA/Cg7UI3xlfloIU +xTStVYA3MB1bHtS3iQIzBBABCAAdFiEERznTKckYehwnlcIKAqv97DpAVF8FAl5F +zccACgkQAqv97DpAVF9vZBAAtVbax+9YS85abbDFHBEwIrZBXwyHqimgbEK/MVDE +0epwEjViVwDtP3aT61ZnBHwn5MOgRPZz8o/cTQZh7Qzd/rBh5UAzm2Fy8KwG/BQ4 +btAKDHR/KgBPALhprrGU3bDvBjJQt2k7HYZZTJOPtlZ1nTiB6VVntFQMOFBVT+k9 +Iuf9VDj9z3P+XCVJgFjc7TDdcWS4hLYAYi4s8H9MVJ4DI0AFbii0Fo5DwEBAfbFS +cYB8OsbZ2FBcs55fui/JVPwhWmXV++LW/PlgQbWg0iv0qgMPYCnlW5Zr5Pfgf/IY +DVWqdlSIUw7ZOFUg0gW0bqrwE6PmW4Ox6N55d4xPY7rpxHGpJovrdhcH0ztzAeZi +H2v34lBpXxmYRFkaGe6LYW4TdUYhInNhZ5gg9rDUdkdSXU0yiQVRHf9DdW6HYVF/ +peqEFqs36konBu8ZwX+m1EP41vTomQEZrYnKSTw7Nh2Hvn/hlAuxOng2w1CbH8kr +yEpSykFjB0jAwlpVS3joLrprwFLRQWAnnzUBa9S//JFVgWNDSqt1CqxhIqyo/5cQ +PCPyv7Vm8ZgJaIz4hMJXCGA2rM1NzD0/PpMTBJrTW/lnwBde9OlCY9yqMsWv9ap7 +02yGpl7NJ1cvxqGIpBdeRfUAKewG4gkltd92yixxr90DvCJcY/WUJdtSuIkEFXBV +X8G5Ag0EXkVvmwEQAL+p2EeYrwlVFbbg9dL1+jfo4jhrsNUHvwd13ZF0mLDJ6jYS +wTjCBXDrp4Qx6ho0dy7ruUeAw7ObkwaqpuWQbuDpVnsWE7/yZ13dqY/duWx3THi9 +RBmIR+bTzhEIzxhaiDaFFXxBBfHSOtovsqjQndRy361pOMahhgDSqKcl5mB8JapC +kYlto30XILcb7RP4l/EQ77SpgjZwxNsDPXEjiUGQQ7cIdqnUw/jY5xlRMjYMvEaJ +xtU8sZCHoO2Ns3yG/qR5s3u141wJYmaRGk7vujScCVSBTRNpLJIVnfuTZqUlyqRz +gai4pFtXdea8WxXlHibGw6iRcqVQgDQSZamzrFtMpJI6Ha38ap6svcISYQWQo348 +sdC+STCNj7xerKkiuz+7FN174aXSi8gHMEILC2Ne+wdpMWXCM2njnEFs8xHoVXXC +lHXmx3AxKxTz3kcMoL8ahQlySXG+hFcj45La9/daGvIbbEKpfC8Gb30dBneOVZrA +zWPvG1G01QF3SCpr9EyB/cCGe7CXklor/VB1WdYQ6zjj7QXaPBr2s8Euykaj067H +jCeVV05sQgJmOYtG5F/y04s5ACNF0eyFQuipTGo0TdmA6JO8mGf1RLho5qdyFOCB +cK6GOgzk6rm0GtAMSbNoKtzmtRFYP2Stp3dQ/Qq+kp1+PWPZeo47MMuyRcI/ABEB +AAGJAjwEGAEIACYWIQTnn22eETUp9LH/5NXE+XTXDOwsWwUCXkVvmwIbDAUJAeEz +gAAKCRDE+XTXDOwsW38WEADSi0I9skDW+a41IP6BG8I/fU6/8Lrjm5eGQWt7ObkY +hTmCHxeyim3FtrnjPKcswR6R35SC/YQzZXqH9rFvvT/CxpTlB8yo40fWNT09f7yo +QMuJIpYOp/du21I3dQLvySrTewSXr8kSn5HuHBtgTsFh0phVnD3G/4VfGa6fBrFR +m22+czu/rz7uzxOcFfOQXqt33Vs46EwTVBRBjGE0kKh21YS5+yJm1s5vAhq0NOWd +wjk3U0KGzqrqf6VGlLuyCaz6UEyppNYZP9gGlKIADRvQiqTud8HZyjcr+HY8vif+ +KJHV9kFX1uTYdM0JEv9SzFxYzooh3LwlH3e+CT5KfHtD9NYKFetsSXjj87HUlPf2 +RtWbb/xrvYjvYHV8AdwyjjBC5l0MOostDwgDp5iQJCQWRszzz6IgaLfD07sx0z8F +JSVOrBRknqvcBxFvM7WyWkps/12/TnTVuY/q+8dOBKgMIkLUzVBu9VPOQAdQNMVa +h9kxE8bxIp6ceWnOOycSq+CSqsko3HA2I/wsO4Xeu0BaRz7YbhVeTTa4oHzvvYGa +Nqwy8TL7UI/27dRXP5bp66aA5QIpJnvXsLLQFMV7DRDcmRynr13I4trw3YS69D6X +fd9Oo4FI4BL7UUFTFSj+xA75oPTMD9KI2xrwuVqTf4K0ThT3KB0oKEApFEq2HUzo +Rw== +=IZJF +-----END PGP PUBLIC KEY BLOCK----- diff --git a/keys/cagebreak_signing_key_2.asc b/keys/cagebreak_signing_key_2.asc new file mode 100644 index 0000000..878a12d --- /dev/null +++ b/keys/cagebreak_signing_key_2.asc @@ -0,0 +1,64 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBF5FrbQBEAC4sSciEBH78/6DqVWBa9QaX+jJ1bnvWBDyWxSLu+tXR0lYn9zF +KC7cp9IYkQmRC+n3bvDF9YL3Ps7Ew5NKfxuamL+e3gyfd3co3xit/T9U5fe1OKSe +qZkYmb5UQab+k82JdyVXeDu/c8W0/cD0boT1JK06oXM1CoaaKCQfuZmcnrP/d7+y +E/6rdoehOavF2cOZE2xJeQaDK9i1ZyRjOKwb5el99jTddrS9Ge1P41uLqyAmbuXy +IYmWCi1Afy0KenD/w9sBqsk9V8oALcxTORit6EtNusUMNW6SY8VSvTNyyLL1O9L2 +OavsWy9YCrUsWle9sD++7swWoxgD2/gWZC54GXvIBWrmP9vwe8VnkegF7IYXwZgB +wxuMYsICPgKxHIdEigVr3DGGa3qXckr9JFbK7x2a3C7CmcTUebuNHc6U8UNcP4vF +fZ8BfW0VDm36QKpgx4H87FBsxBgsjWU0k8ndoXqnEMFhszMR8mZTWPxc4bD9pmDx +h0YLQ+sI0xuDZW6GPYzaUPNzVI7ZY/5TpuL8YUBRmL8OTW8KxELbrOYf23elFYaM +zJqldx6MNWTSfNtFm/ZGDAA+qjS0SVnEYOKzKatS/wRQsW8unGOjvhkWcm60900a +w71haE73R62HTSX1R/2hx41PCIooi/KZ5QG4JZG8YPRWTBchgzrekrWeIQARAQAB +tDtDYWdlYnJlYWsgU2lnbmluZyBLZXkgMiA8Y2FnZWJyZWFrX3NpZ25pbmdfa2V5 +XzJAY2FnZWJyZWFrPokCVAQTAQgAPhYhBEc50ynJGHocJ5XCCgKr/ew6QFRfBQJe +Ra20AhsDBQkB4TOABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEAKr/ew6QFRf +xkMP/1v8do0h2HMf2mVYQBenkbTZsM1dqdtO6aF12P9BeEfNtjzlc0tAeDipw/qc +LgjMKUCBlvGq1dOKFod4+fFUvcjo8jjZNIf5LsgqnDrp6+LOALS2IJS7WTe1AKqK +4xX1VL9JiLbjoz4alp84aIhmuYO+lrt90+5S3eShjkt16DMPXgf0DaRdXmacjbQp +x235ZBHGVGlLqKsvSb4XCfIt4Ue8tlpjjs1UNFPQo4xhK7syoxIhe4j+dMZzPKEy +gwEbqo4tZgdjUljpaogvWZmzORqIHB1tBL5hedBfq5TCqiznwbr1XR29KlIL0uvt +9D8Decd3t/tCoDVhD2NOWx+beCNmj4/rvCGABCbJb+0Nr177sdv2ig/T7x74Nxir +SsHea00cbZXTH5xuhsDxPUKYgYj5mITXZUD8PDz/8GS6Cq3x+v59PKB4oDIjSfPY +tMm1w77bjS/TGLOMDRsb0GR5ZOmmbrJQcAWM7DKCz5Nc9gsb8JVNY1QyZ6lhdJYA +SUu2H3wgrvC713NfFlntqDl62zZmRF8P/9Yu4siUu3jtexoE1mnyiTz1QhfSG6bJ +Z6gc4sXpNnvX2jz92TK/vU4p6+rPy8vkUl3+A96Sxxcccxr8WUl4iRyMIZ9YkRQe +c7W9q2fbxU60iWJCBHjG6HIQyBKm1esmO+d5hg84gFPFovoNiQIzBBABCAAdFiEE +559tnhE1KfSx/+TVxPl01wzsLFsFAl5GRloACgkQxPl01wzsLFsYpRAAssi0tZRx +1cpFNuwE1mmh/kJ4X5vuM66x2LtU8GuHrYSGyKSiw8hQoWCybn3oGbtCL86v9xDU +XGkMmdSZgp7+XZ6OJP9IHN6i3XXV9YCyLjPIMqbIL+9NARLomt71uYXZDhYdnaeU +O0l2MQhARxMXt6F5eI3F3W8dAVIeZwpjipNT+8vz+yQ9i6zaJeFySu8WyxpSVxxb +V+jTnAjADo1GpzaOttqPrIFXf8ax5h+M0FSyfPos8havArE17+rPxmZSPeRWYPCv +XGbPvl62CiXVZx0lEjRoSNSXY7V1H24qKpkxNAEWNHjnSn9XPdtkM/G2OqZQjyVW +AqFvM1Q22ehBkJItjq1lPrdR2Ffm6TP6i+xkyEdy9I4KrzZM8d5k34DFQilSTmju +KbGSIb5+0WJErWDT32g1SYP/2XQaOEQJe/ksxkAPQySSVTX/jTG9SqpY9uR9Cj0j +11VsB8dJAHaSHMJXMSyW+7FE3kP68ai5k7KKnuvsceyOLup0yPBYUpuzETphDL7L +aF5070MteLJe2lrsEavliQ9l2tgDdWUwjFJSvqyq2u7jnfs/5ecyKl6neHjkhJ2f +cgXoiw0w74RSqnyQR8bHpd/DwYBLcyFpF1Z29Yi8g7pxiutlcG4TO49CR4dVnqwD +0zqW8zkAeir5S1jmE+VfLBe8Qn3WcZ5IIYW5Ag0EXkWttAEQAKrHVGru5PRfnR9R +XwSSkyOcw3MQuiUX19+Yhz4WmvZpZNojnZIX/UDwigJ5808k0C4BrzhnK5EwzEqU +Ec20CZE9KAOIu9/tKktyu/xNmuu9SZidGmgq1XgbGRaz6IcgenoFZFtR247P3NYI +tzdHZTX9/tVApOohtIP3NaR1BzjrgIGi2OOXJZ3Q75+DaG4A+AVaGjuL3cP/5N0P +UvMpqfh/F3Dy34D6qMK9VTdiFmqkXAC2uJuJkyZJ/2sZ4guQ8nrqIwBehBh1UDc5 +iIWpK8lEOuSoHYi/pm8MB/uoB2xJ/it4WMNtTd2G72UE6c58Q/TThxKeAn6ln1sy +k2f84lpwmoIHyBqxPfFy5yJwVqZ4C3oC8BbA2NxVvoKC0/LlguFVr4GO0I+etj0X +KOSfaCXu8spptZHSQLRFOSYXyiQ6fRQVv0kzwjW6p/KzmJ3SnxwByMV+FtmOFp4j +kiRqDiJnCSSlqNAZEC+XL8w1CYK9/A7+ZrFs6gVmOpzWIM8ItEFEheqNDxeixwWR +gegcJ5KvIsdZHYsIkcJydSUB3rQ8Lfx+eFJ+Bio4t+T8MC9eqM4pvdxVemOmQSUX +XQYL747PUs7zCiWqN9P8u1BSv4M7ErU1/lqQ3rhmFvd9moiFpVJ7CSASlWgBOPxa +3To3zHneEGOsJiOgSx1X+e2NtgmzABEBAAGJAjwEGAEIACYWIQRHOdMpyRh6HCeV +wgoCq/3sOkBUXwUCXkWttAIbDAUJAeEzgAAKCRACq/3sOkBUX1eiD/0YuoUY2Au0 +RDkRbcUzK8BTSBF3uAYTsJuPV7whnJFosguj1M11WPafxvDbEmaGctakwirC0Wo0 +mJ+O/fHiVH0XbYePB70UnIyL/JhyADESglfMcBUjptP2J7cmVuONU3m1M9Y0rNHR +ZJXbz8PLZ1c2QBs3EtUbYFnPgUeGlpnzj+mgwKaz1O4hbLA2jSg5nIKvOrI5pNzO +vEMHOlWHfniQ3CrL6Ylt800oJTLkL1W7br8cjwv3VkPr7JuNPplerSJOg8f9VOkG +9wp95Fi2A9rY8E+lkZsL/zuaAk1lQXpdlC7a6rEDPcdMZ5orA1/f1thzq6cuX3jv +2oIculNxzhdxogEE8HpvGdeWIB7m1gblVlDIT+I8ShbCX2cfRKTdMH3keP171jNi +rbTq6Fi9yxeSfz6B+WqBi2fK7To+hSVUcv7uetrmb+zp1z5EtzTpzA/lccsVgBMn ++LZwMPjJ6KEMIpYDmL+cZADrE8meXwgTfSqzhRUJ+rrkJqsiiAlbCub3KkBqbGxW +XSpiWTS9vMIqdnKUShXstOXJy3DGFv9c9PByjMNLvlfHxmVLs0P6k8jbcF5v1OWj +c2FO8TZK+AqNi37kd3CiNNjdKnaTDt95n5bqaYu+w6hpBjUnZmozI6VVSW8BgqHN +MI0SwN7gPGlOudIlzBJwygPPhSnZXtDYJA== +=Dz6g +-----END PGP PUBLIC KEY BLOCK----- diff --git a/man/cagebreak-config.5.md b/man/cagebreak-config.5.md new file mode 100644 index 0000000..bbf3096 --- /dev/null +++ b/man/cagebreak-config.5.md @@ -0,0 +1,266 @@ +% CAGEBREAK-CONFIG(1) Version 1.0 | Cagebreak Manual + +# NAME + +**cagebreak-config** — Cagebreak config file + +# SYNOPSIS + +**\$XDG_CONFIG_PATH/cagebreak/config** + +# DESCRIPTION + +The `cagebreak` configuration file is a simple plain text file which configures +the way `cagebreak` behaves. +Each line constitutes a separate command which is parsed +independently from the rest of the file. Comments can be added +by prepending a line with the # symbol. + +## COMMANDS + +**background - Set background color** + +> Set the background color. This command expects three floating point numbers +> between 0 and 1, specifying the r,g and b values respectively. +> (e.g. "background 1.0 0.0 0.0" sets to background color to red) +> There is no support for specifying a background image. + +**bind - Bind key to action in command mode** + +> This command requires a key (see **KEY DEFINITIONS**) and an action (see **ACTIONS**) as an argument. +> Subsequently, pressing this key while in command mode executes the +> supplied action. `bind ` is equivalent to +> `definekey root ` + +**definekey - Bind key to action in arbitrary mode** + +> This command behaves similarly to the **bind** command with the +> difference that the mode in which the keybinding is activated is +> specified by the user. A call to this function is to be structured as follows: +> +>> `definekey ` + +**definemode** + +> This command requires a single argument; the name of the mode to be defined. +> Subsequent to a call to this function, the defined mode may be used along with +> the definekey command to create a custom keymapping. Synopsis: +> +>> `definemode ` + +**escape** + +> Defines the key with which the current mode can be changed to "root". +> `escape ` is equivalent to `definekey top switch_mode root` + +**exec** + +> Executes the supplied shell command using *`sh -c ""`*. Synopsis: +> +>> `exec ` + +**workspaces** + +> Requires a single integer larger than 1 and less than 30 as an argument. Sets the number of +> workspaces to the supplied number + +# ACTIONS + +**vsplit** + +> Split current tile vertically + +**hsplit** + +> Split current tile horizontally + +**quit** + +> Exit cagebreak + +**focus** + +> Focus next tile + +**focusprev** + +> Focus previous tile + +**next** + +> Focus next window in current tile + +**prev** + +> Focus previous window in current tile + +**only** + +> Remove all splits and make the current window fill the entire screen + +**abort** + +> Return to the default mode without running any action + +**time** + +> Display time + +**nextscreen** + +> Focus the next screen + +**prevscreen** + +> Focus the previous screen + +**resizeleft** + +> Resize the current tile towards the left + +**resizeright** + +> Resize the current tile towards the right + +**resizedown** + +> Resize the current tile towards the bottom + +**resizeup** + +> Resize the current tile towards the top + +**workspace ** + +> Change to the n-th workspace + +**movetoworkspace ** + +> Move the currently focused window to the n-th workspace + +**exchangeleft** + +> Exchange the current window with the window in the tile to the left + +**exchangeright** + +> Exchange the current window with the window in the tile to the right + +**exchangeup** + +> Exchange the current window with the window in the tile to the top + +**exchangedown** + +> Exchange the current window with the window in the tile to the bottom + +**focusleft** + +> Focus the tile to the left + +**focusright** + +> Focus the tile to the right + +**focusup** + +> Focus the tile to the top + +**focusdown** + +> Focus the tile to the bottom + +**movetonextscreen** + +> Move the current window to the next screen + +**switchvt ** + +> Switch to tty n + +**mode ** + +> Enter mode "``". After a keybinding is processed, return to default mode + +**setmode ** + +> Set the default mode to `` + +# MODES + +By default, three modes are defined: + +**top** + +> The default mode. Keybindings defined in this mode can be accessed +> directly, without the use of an escape key. + +**root** + +> The command mode. Keybindings defined in this mode can be accessed +> after pressing the key defined by the `escape` command. + +**resize** + +> Resize mode. This mode is used for resizing tiles. + +# KEY DEFINITIONS + +Keys are specified by their names as displayed for example by *`xev`*. +In addition, modifiers can be specified using the following syntax: + +> `-` + +The supported modifiers are: + +**A - Alt** + +**C - Control** + +**L - Logo** + +**S - Shift** + +**2 - Mod2** + +**3 - Mod 3** + +**5 - Mod 5** + +For example to specify the keybinding Control+t, the expression: + +>`C-t` + +is used. + +# SEE ALSO + +**cagebreak(1)** + +# BUGS + +See GitHub Issues: + +# LICENSE + +Copyright (c) 2020 The Cagebreak authors +Copyright (c) 2018-2020 Jente Hidskes +Copyright (c) 2019 The Sway authors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/man/cagebreak.1.md b/man/cagebreak.1.md new file mode 100644 index 0000000..6d5b051 --- /dev/null +++ b/man/cagebreak.1.md @@ -0,0 +1,90 @@ +% CAGEBREAK(1) Version 1.0 | Cagebreak Manual + +# NAME + +**cagebreak** — A Wayland tiling compositor to the likes of ratpoison + +# SYNOPSIS + +| **cagebreak** [OPTIONS] + +# DESCRIPTION + +`cagebreak` is a slim, keyboard-controlled, tiling compositor for +wayland conceptually based on the X11 window manager `ratpoison`. + +It allows for the screen to be split into non-overlapping tiles and, +in contrast to the original `ratpoison`, has native support for +multi-workspace operation. + +All interactions between the user and `cagebreak` are done via +the keyboard. Configuration of this behaviour is specified +in the **\$XDG_CONFIG_PATH/cagebreak/config** file (See **cagebreak-config(5)**). + +## OPTIONS + +-h + +: Display help message and exit + +-r + +: Rotate the output 90 degrees clockwise, can be specified up to three times + +-v + +: Show version number and exit + +# ENVIRONMENT + +`XKB_DEFAULT_LAYOUT` + +: The keyboard layout to be used (See **xkeyboard-config(7)**) + +`XKB_DEFAULT_MODEL` + +: The keyboard model to be used (See **xkeyboard-config(7)**) + +`XKB_DEFAULT_VARIANT` + +: The keyboard variant to be used (See **xkeyboard-config(7)**) + +`XKB_DEFAULT_RULES` + +: The xkb rules to be used + +`XKB_DEFAULT_OPTIONS` + +: The xkb options to be used + +# SEE ALSO + +**cagebreak-config(5)** + +# BUGS + +See GitHub Issues: + +# LICENSE + +Copyright (c) 2020 The Cagebreak authors +Copyright (c) 2018-2020 Jente Hidskes +Copyright (c) 2019 The Sway authors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..9e46f9d --- /dev/null +++ b/meson.build @@ -0,0 +1,231 @@ +project('cagebreak', 'c', + version: '1.0.0', + license: 'MIT', + default_options: [ + 'c_std=c11', + 'warning_level=3', + ], +) + +add_project_arguments( + [ + '-DWLR_USE_UNSTABLE', + '-Wall', + '-Wundef', + '-Wno-unused-parameter', + ], + language: 'c', +) + +if get_option('buildtype').startswith('debug') + add_project_arguments( + [ + '-DDEBUG', + '-g', + ], + language : 'c', +) +endif + +cc = meson.get_compiler('c') + +fuzz_compile_args = [] +fuzz_link_args = [] +if get_option('fuzz') + fuzzing_engine = meson.get_compiler('c').find_library('Fuzzer', required : false) + if fuzzing_engine.found() + fuzz_compile_args += '-fsanitize-coverage=trace-pc-guard,trace-cmp' + elif cc.has_argument('-fsanitize=fuzzer-no-link') + fuzz_compile_args += '-fsanitize=fuzzer-no-link' + fuzz_link_args += '-fsanitize=fuzzer-no-link' + else + error('Looks like neither libFuzzer nor -fsanitize=fuzzer-no-link is supported. Disable fuzzing using -Dfuzz=false to build without fuzzers.') + endif +endif + +is_freebsd = host_machine.system().startswith('freebsd') +if is_freebsd + add_project_arguments( + [ + '-Wno-format-extra-args', + '-Wno-gnu-zero-variadic-macro-arguments', + ], + language: 'c' + ) +endif + +wlroots = dependency('wlroots', version: '>= 0.9.1') +wayland_protos = dependency('wayland-protocols', version: '>=1.14') +wayland_server = dependency('wayland-server') +wayland_cursor = dependency('wayland-cursor') +wayland_client = dependency('wayland-client') +pixman = dependency('pixman-1') +xkbcommon = dependency('xkbcommon') +cairo = dependency('cairo') +pango = dependency('pango') +pangocairo = dependency('pangocairo') +math = cc.find_library('m') + +wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') +wayland_scanner = find_program('wayland-scanner') +wayland_scanner_server = generator( + wayland_scanner, + output: '@BASENAME@-protocol.h', + arguments: ['server-header', '@INPUT@', '@OUTPUT@'], +) + +server_protocols = [ + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], +] + +server_protos_headers = [] + +foreach p : server_protocols + xml = join_paths(p) + server_protos_headers += wayland_scanner_server.process(xml) +endforeach + +server_protos = declare_dependency( + sources: server_protos_headers, +) + +if get_option('xwayland') + wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include ', dependencies: wlroots) == '1' + if not wlroots_has_xwayland + error('Cannot build Cagebreak with XWayland support: wlroots has been built without it') + else + have_xwayland = true + endif +else + have_xwayland = false +endif + +git = find_program('git', native: true, required: false) +if git.found() + git_commit = run_command([git, 'rev-parse', '--short', 'HEAD']) + git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD']) + if git_commit.returncode() == 0 and git_branch.returncode() == 0 + version = '@0@-@1@ (branch \'@2@\')'.format( + meson.project_version(), + git_commit.stdout().strip(), + git_branch.stdout().strip(), + ) + endif +else + version = '@0@'.format(meson.project_version()) +endif + +conf_data = configuration_data() +conf_data.set10('CG_HAS_XWAYLAND', have_xwayland) +conf_data.set_quoted('CG_VERSION', version) + + +cagebreak_main_file = [ 'cagebreak.c', ] +cagebreak_source_strings = [ + 'idle_inhibit_v1.c', + 'keybinding.c', + 'workspace.c', + 'output.c', + 'parse.c', + 'render.c', + 'seat.c', + 'util.c', + 'view.c', + 'xdg_shell.c', + 'server.c', + 'message.c', + 'pango.c', +] + +cagebreak_header_strings = [ + 'idle_inhibit_v1.h', + 'keybinding.h', + 'workspace.h', + 'output.h', + 'parse.h', + 'render.h', + 'seat.h', + 'server.h', + 'util.h', + 'view.h', + 'xdg_shell.h', + 'pango.h', + 'message.h', +] + +if conf_data.get('CG_HAS_XWAYLAND', 0) == 1 + cagebreak_source_strings += 'xwayland.c' + cagebreak_header_strings += 'xwayland.h' +endif + +cagebreak_sources = [ + configure_file(input: 'config.h.in', + output: 'config.h', + configuration: conf_data), + ] + +cagebreak_headers = [] + +foreach source : cagebreak_source_strings + cagebreak_sources += files(source) +endforeach + +foreach header : cagebreak_header_strings + cagebreak_headers += files(header) +endforeach + +cagebreak_dependencies = [ + server_protos, + wayland_server, + wayland_client, + wayland_cursor, + wlroots, + xkbcommon, + pixman, + math, + pango, + cairo, + pangocairo, +] + +executable( + meson.project_name(), + cagebreak_main_file + cagebreak_sources + cagebreak_headers, + dependencies: cagebreak_dependencies, + install: true, + link_args: fuzz_link_args, + c_args: fuzz_compile_args, +) + +pandoc = find_program('pandoc') +mandir1 = join_paths(get_option('mandir'), 'man1') +mandir5 = join_paths(get_option('mandir'), 'man5') + +cagebreak_man = custom_target('cagebreak_man', + output : 'cagebreak.1', + input : 'man/cagebreak.1.md', + command : [pandoc, '-i', '@INPUT@', '-o', '@OUTPUT@', '-f', 'markdown-smart', '-t', 'man', '-s'], + install: true, + install_dir: mandir1 + ) + +cagebreak_man = custom_target('cagebreak_config_man', + output : 'cagebreak-config.5', + input : 'man/cagebreak-config.5.md', + command : [pandoc, '-i', '@INPUT@', '-o', '@OUTPUT@', '-f', 'markdown-smart', '-t', 'man', '-s'], + install: true, + install_dir: mandir5 + ) + +if get_option('fuzz') + subdir('fuzz') +endif + +summary = [ + '', + 'Cagebreak @0@'.format(version), + '', + ' xwayland: @0@'.format(have_xwayland), + '' +] +message('\n'.join(summary)) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..5982c3d --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,2 @@ +option('xwayland', type: 'boolean', value: 'false', description: 'Enable support for X11 applications') +option('fuzz', type: 'boolean', value: 'false', description: 'Enable building fuzzer targets') diff --git a/message.c b/message.c new file mode 100644 index 0000000..66e09dd --- /dev/null +++ b/message.c @@ -0,0 +1,179 @@ +#include "pango.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cairo.h" +#include "message.h" +#include "output.h" +#include "server.h" + +cairo_subpixel_order_t +to_cairo_subpixel_order(const enum wl_output_subpixel subpixel) { + switch(subpixel) { + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: + return CAIRO_SUBPIXEL_ORDER_RGB; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: + return CAIRO_SUBPIXEL_ORDER_BGR; + case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: + return CAIRO_SUBPIXEL_ORDER_VRGB; + case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: + return CAIRO_SUBPIXEL_ORDER_VBGR; + default: + return CAIRO_SUBPIXEL_ORDER_DEFAULT; + } + return CAIRO_SUBPIXEL_ORDER_DEFAULT; +} + +struct wlr_texture * +create_message_texture(const char *string, const struct cg_output *output) { + const int PADDING = 2; + const int MESSAGE_HEIGHT = 17 + 2 * PADDING; + const char *font = "pango:Monospace 10"; + + struct wlr_texture *texture; + double scale = output->wlr_output->scale; + int width = 0; + int height = MESSAGE_HEIGHT * scale; + + // We must use a non-nil cairo_t for cairo_set_font_options to work. + // Therefore, we cannot use cairo_create(NULL). + cairo_surface_t *dummy_surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); + cairo_t *c = cairo_create(dummy_surface); + cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); + cairo_font_options_set_subpixel_order( + fo, to_cairo_subpixel_order(output->wlr_output->subpixel)); + cairo_set_font_options(c, fo); + get_text_size(c, font, &width, NULL, NULL, scale, "%s", string); + width += 2 * PADDING; + cairo_surface_destroy(dummy_surface); + cairo_destroy(c); + + cairo_surface_t *surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cairo = cairo_create(surface); + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); + cairo_set_font_options(cairo, fo); + cairo_font_options_destroy(fo); + cairo_set_source_rgba(cairo, 0.9, 0.85, 0.85, 1.0); + cairo_paint(cairo); + PangoContext *pango = pango_cairo_create_context(cairo); + cairo_set_source_rgba(cairo, 0, 0, 0, 1); + cairo_set_line_width(cairo, 2); + cairo_rectangle(cairo, 0, 0, width, height); + cairo_stroke(cairo); + cairo_move_to(cairo, PADDING, PADDING); + + pango_printf(cairo, font, scale, "%s", string); + + cairo_surface_flush(surface); + unsigned char *data = cairo_image_surface_get_data(surface); + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + struct wlr_renderer *renderer = + wlr_backend_get_renderer(output->wlr_output->backend); + texture = wlr_texture_from_pixels(renderer, WL_SHM_FORMAT_ARGB8888, stride, + width, height, data); + cairo_surface_destroy(surface); + g_object_unref(pango); + cairo_destroy(cairo); + return texture; +} + +void +message_set_output(struct cg_output *output, const char *string, + struct wlr_box *box, enum cg_message_align align) { + struct cg_message *message = malloc(sizeof(struct cg_message)); + message->message = create_message_texture(string, output); + message->position = box; + wl_list_insert(&output->messages, &message->link); + + int width, height; + wlr_texture_get_size(message->message, &width, &height); + message->position->width = width; + message->position->height = height; + switch(align) { + case CG_MESSAGE_TOP_RIGHT: { + message->position->x -= width; + break; + } + case CG_MESSAGE_BOTTOM_LEFT: { + message->position->y -= height; + break; + } + case CG_MESSAGE_BOTTOM_RIGHT: { + message->position->x -= width; + message->position->y -= height; + break; + } + case CG_MESSAGE_CENTER: { + message->position->x -= width / 2; + message->position->y -= height / 2; + break; + } + case CG_MESSAGE_TOP_LEFT: + default: + break; + } + wlr_output_damage_add_box(output->damage, message->position); +} + +void +message_printf(struct cg_output *output, const char *fmt, ...) { + uint16_t buf_len = 256; + char *buffer = (char *)malloc(buf_len * sizeof(char)); + va_list ap; + + va_start(ap, fmt); + vsnprintf(buffer, buf_len, fmt, ap); + va_end(ap); + + struct wlr_box *box = malloc(sizeof(struct wlr_box)); + struct wlr_box *output_box = wlr_output_layout_get_box( + output->server->output_layout, output->wlr_output); + + box->x = output_box->width; + box->y = 0; + box->width = 0; + box->height = 0; + + message_set_output(output, buffer, box, CG_MESSAGE_TOP_RIGHT); + free(buffer); + alarm(output->server->message_timeout); +} + +void +message_printf_pos(struct cg_output *output, struct wlr_box *position, + const enum cg_message_align align, const char *fmt, ...) { + uint16_t buf_len = 256; + char *buffer = (char *)malloc(buf_len * sizeof(char)); + va_list ap; + + va_start(ap, fmt); + vsnprintf(buffer, buf_len, fmt, ap); + va_end(ap); + + message_set_output(output, buffer, position, align); + free(buffer); + alarm(output->server->message_timeout); +} + +void +message_clear(struct cg_output *output) { + struct cg_message *message, *tmp; + wl_list_for_each_safe(message, tmp, &output->messages, link) { + wl_list_remove(&message->link); + wlr_output_damage_add_box(output->damage, message->position); + wlr_texture_destroy(message->message); + free(message->position); + free(message); + } +} diff --git a/message.h b/message.h new file mode 100644 index 0000000..9a4e53e --- /dev/null +++ b/message.h @@ -0,0 +1,33 @@ +#ifndef MESSAGE_H + +#define MESSAGE_H + +#include + +struct cg_output; +struct wlr_box; +struct wlr_texture; + +enum cg_message_align { + CG_MESSAGE_TOP_LEFT, + CG_MESSAGE_TOP_RIGHT, + CG_MESSAGE_BOTTOM_LEFT, + CG_MESSAGE_BOTTOM_RIGHT, + CG_MESSAGE_CENTER, +}; + +struct cg_message { + struct wlr_box *position; + struct wlr_texture *message; + struct wl_list link; +}; + +void +message_printf(struct cg_output *output, const char *fmt, ...); +void +message_printf_pos(struct cg_output *output, struct wlr_box *position, + enum cg_message_align, const char *fmt, ...); +void +message_clear(struct cg_output *output); + +#endif /* end of include guard MESSAGE_H */ diff --git a/output.c b/output.c new file mode 100644 index 0000000..4fd6960 --- /dev/null +++ b/output.c @@ -0,0 +1,569 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2020 Jente Hidskes + * Copyright (C) 2019 The Sway authors + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200112L + +#include "config.h" +#include + +#include +#include +#include +#include +#include +#if WLR_HAS_X11_BACKEND +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keybinding.h" +#include "message.h" +#include "output.h" +#include "render.h" +#include "seat.h" +#include "server.h" +#include "util.h" +#include "view.h" +#include "workspace.h" +#if CG_HAS_XWAYLAND +#include "xwayland.h" +#endif + +static void +output_for_each_surface(struct cg_output *output, + cg_surface_iterator_func_t iterator, void *user_data); + +struct surface_iterator_data { + cg_surface_iterator_func_t user_iterator; + void *user_data; + + struct cg_output *output; + + /* Output-local coordinates. */ + double ox, oy; +}; + +static bool +intersects_with_output(struct cg_output *output, + const struct wlr_output_layout *output_layout, + const struct wlr_box *surface_box) { + /* Since the surface_box's x- and y-coordinates are already output local, + * the x- and y-coordinates of this box need to be 0 for this function to + * work correctly. */ + struct wlr_box output_box = {0}; + wlr_output_effective_resolution(output->wlr_output, &output_box.width, + &output_box.height); + + struct wlr_box intersection; + return wlr_box_intersection(&intersection, &output_box, surface_box); +} + +static void +output_for_each_surface_iterator(struct wlr_surface *surface, int sx, int sy, + void *user_data) { + struct surface_iterator_data *data = user_data; + struct cg_output *output = data->output; + + if(!wlr_surface_has_buffer(surface)) { + return; + } + + struct wlr_box surface_box = { + .x = data->ox + sx + surface->sx, + .y = data->oy + sy + surface->sy, + .width = surface->current.width, + .height = surface->current.height, + }; + + if(!intersects_with_output(output, output->server->output_layout, + &surface_box)) { + return; + } + + data->user_iterator(data->output, surface, &surface_box, data->user_data); +} + +void +output_surface_for_each_surface(struct cg_output *output, + struct wlr_surface *surface, double ox, + double oy, cg_surface_iterator_func_t iterator, + void *user_data) { + struct surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .output = output, + .ox = ox, + .oy = oy, + }; + + wlr_surface_for_each_surface(surface, output_for_each_surface_iterator, + &data); +} + +static void +output_view_for_each_surface(struct cg_output *output, struct cg_view *view, + cg_surface_iterator_func_t iterator, + void *user_data) { + struct surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .output = output, + .ox = view->ox, + .oy = view->oy, + }; + + view_for_each_surface(view, output_for_each_surface_iterator, &data); +} + +void +output_view_for_each_popup(struct cg_output *output, struct cg_view *view, + cg_surface_iterator_func_t iterator, + void *user_data) { + struct surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .output = output, + .ox = view->ox, + .oy = view->oy, + }; + + view_for_each_popup(view, output_for_each_surface_iterator, &data); +} + +void +output_drag_icons_for_each_surface(struct cg_output *output, + struct wl_list *drag_icons, + cg_surface_iterator_func_t iterator, + void *user_data) { + struct cg_drag_icon *drag_icon; + wl_list_for_each(drag_icon, drag_icons, link) { + if(drag_icon->wlr_drag_icon->mapped) { + double ox = drag_icon->lx; + double oy = drag_icon->ly; + wlr_output_layout_output_coords(output->server->output_layout, + output->wlr_output, &ox, &oy); + output_surface_for_each_surface(output, + drag_icon->wlr_drag_icon->surface, + ox, oy, iterator, user_data); + } + } +} + +static void +output_for_each_surface(struct cg_output *output, + cg_surface_iterator_func_t iterator, void *user_data) { + struct cg_view *view; + wl_list_for_each_reverse( + view, &output->workspaces[output->curr_workspace]->views, link) { + output_view_for_each_surface(output, view, iterator, user_data); + } + + wl_list_for_each_reverse( + view, &output->workspaces[output->curr_workspace]->unmanaged_views, + link) { + output_view_for_each_surface(output, view, iterator, user_data); + } + + output_drag_icons_for_each_surface( + output, &output->server->seat->drag_icons, iterator, user_data); +} + +struct send_frame_done_data { + struct timespec when; +}; + +static void +send_frame_done_iterator(struct cg_output *output, struct wlr_surface *surface, + struct wlr_box *box, void *user_data) { + struct send_frame_done_data *data = user_data; + wlr_surface_send_frame_done(surface, &data->when); +} + +static void +send_frame_done(struct cg_output *output, struct send_frame_done_data *data) { + output_for_each_surface(output, send_frame_done_iterator, data); +} + +struct damage_data { + bool whole; +}; + +static void +count_surface_iterator(struct cg_output *output, struct wlr_surface *surface, + struct wlr_box *_box, void *data) { + size_t *n = data; + n++; +} + +static bool +scan_out_primary_view(struct cg_output *output) { + struct cg_server *server = output->server; + struct wlr_output *wlr_output = output->wlr_output; + + if(!wl_list_empty(&output->messages)) { + return false; + } + + struct cg_drag_icon *drag_icon; + wl_list_for_each(drag_icon, &server->seat->drag_icons, link) { + if(drag_icon->wlr_drag_icon->mapped) { + return false; + } + } + + struct cg_view *view = seat_get_focus(server->seat); + if(view == NULL || view->wlr_surface == NULL) { + return false; + } + + size_t n_surfaces = 0; + output_view_for_each_surface(output, view, count_surface_iterator, + &n_surfaces); + if(n_surfaces > 1) { + return false; + } + +#if CG_HAS_XWAYLAND + if(view->type == CG_XWAYLAND_VIEW) { + struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); + if(!wl_list_empty(&xwayland_view->xwayland_surface->children) || + !wl_list_empty(&view->workspace->unmanaged_views)) { + return false; + } + } +#endif + + struct wlr_surface *surface = view->wlr_surface; + + if(!surface->buffer) { + return false; + } + + if((float)surface->current.scale != wlr_output->scale || + surface->current.transform != wlr_output->transform) { + return false; + } + + if(!wlr_output_attach_buffer(wlr_output, surface->buffer)) { + return false; + } + + return wlr_output_commit(wlr_output); +} + +static void +damage_surface_iterator(struct cg_output *output, struct wlr_surface *surface, + struct wlr_box *box, void *user_data) { + struct wlr_output *wlr_output = output->wlr_output; + struct damage_data *data = (struct damage_data *)user_data; + bool whole = data->whole; + + scale_box(box, output->wlr_output->scale); + + if(whole) { + wlr_output_damage_add_box(output->damage, box); + } else if(pixman_region32_not_empty(&surface->buffer_damage)) { + pixman_region32_t damage; + pixman_region32_init(&damage); + wlr_surface_get_effective_damage(surface, &damage); + + if(ceil(wlr_output->scale) > surface->current.scale) { + /* When scaling up a surface it'll become + blurry, so we need to expand the damage + region. */ + wlr_region_expand(&damage, &damage, + ceil(wlr_output->scale) - surface->current.scale); + } + pixman_region32_translate(&damage, box->x, box->y); + wlr_output_damage_add(output->damage, &damage); + pixman_region32_fini(&damage); + } +} + +void +output_damage_surface(struct cg_output *output, struct wlr_surface *surface, + double ox, double oy, bool whole) { + struct damage_data data = { + .whole = whole, + }; + + output_surface_for_each_surface(output, surface, ox, oy, + damage_surface_iterator, &data); +} + +static void +handle_output_damage_frame(struct wl_listener *listener, void *data) { + struct cg_output *output = wl_container_of(listener, output, damage_frame); + struct send_frame_done_data frame_data = {0}; + + if(!output->wlr_output->enabled) { + return; + } + + /* Check if we can scan-out the primary view. */ + static bool last_scanned_out = false; + bool scanned_out = scan_out_primary_view(output); + + if(scanned_out && !last_scanned_out) { + wlr_log(WLR_DEBUG, "Scanning out primary view"); + } + if(last_scanned_out && !scanned_out) { + wlr_log(WLR_DEBUG, "Stopping primary view scan out"); + if(seat_get_focus(output->server->seat) != NULL) { + view_damage_whole(seat_get_focus(output->server->seat)); + } + } + last_scanned_out = scanned_out; + + if(scanned_out) { + goto frame_done; + } + + bool needs_frame; + pixman_region32_t damage; + pixman_region32_init(&damage); + if(!wlr_output_damage_attach_render(output->damage, &needs_frame, + &damage)) { + wlr_log(WLR_ERROR, "Cannot make damage output current"); + goto damage_finish; + } + + if(!needs_frame) { + wlr_output_rollback(output->wlr_output); + goto damage_finish; + } + + output_render(output, &damage); + +damage_finish: + pixman_region32_fini(&damage); + +frame_done: + clock_gettime(CLOCK_MONOTONIC, &frame_data.when); + send_frame_done(output, &frame_data); +} + +static void +handle_output_transform(struct wl_listener *listener, void *data) { + struct cg_output *output = wl_container_of(listener, output, transform); + + if(!output->wlr_output->enabled) { + return; + } + + struct cg_view *view; + wl_list_for_each(view, &output->workspaces[output->curr_workspace]->views, + link) { + view_position(view); + } +} + +static void +handle_output_mode(struct wl_listener *listener, void *data) { + struct cg_output *output = wl_container_of(listener, output, mode); + + if(!output->wlr_output->enabled) { + return; + } + + struct cg_view *view; + wl_list_for_each(view, &output->workspaces[output->curr_workspace]->views, + link) { + view_position(view); + } +} + +static void +output_destroy(struct cg_output *output) { + struct cg_server *server = output->server; + + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->mode.link); + wl_list_remove(&output->transform.link); + wl_list_remove(&output->damage_frame.link); + wl_list_remove(&output->damage_destroy.link); + + wlr_output_layout_remove(server->output_layout, output->wlr_output); + + if(server->running && server->curr_output == output && + wl_list_length(&server->outputs) > 1) { + keybinding_cycle_outputs(server, false); + } + wl_list_remove(&output->link); + + message_clear(output); + + struct cg_view *view, *view_tmp; + if(server->running) { + for(unsigned int i = 0; i < server->nws; ++i) { + wl_list_for_each_safe(view, view_tmp, &output->workspaces[i]->views, + link) { + wl_list_remove(&view->link); + if(wl_list_empty(&server->outputs)) { + view->impl->destroy(view); + } else { + wl_list_insert( + &server->curr_output + ->workspaces[server->curr_output->curr_workspace] + ->views, + &view->link); + view->workspace = + server->curr_output + ->workspaces[server->curr_output->curr_workspace]; + if(server->seat->focused_view == NULL) { + seat_set_focus(server->seat, view); + } + } + } + wl_list_for_each_safe( + view, view_tmp, &output->workspaces[i]->unmanaged_views, link) { + wl_list_remove(&view->link); + if(wl_list_empty(&server->outputs)) { + view->impl->destroy(view); + } else { + wl_list_insert( + &server->curr_output + ->workspaces[server->curr_output->curr_workspace] + ->views, + &view->link); + view->workspace = + server->curr_output + ->workspaces[server->curr_output->curr_workspace]; + } + } + } + } + + /*Important: due to unfortunate events, "workspace" and "output->workspace" + * have nothing in common. The former is the workspace of a single output, + * whereas the latter is the workspace of the outputs.*/ + for(unsigned int i = 0; i < server->nws; ++i) { + workspace_free(output->workspaces[i]); + } + free(output->workspaces); + + free(output); + + if(wl_list_empty(&server->outputs)) { + wl_display_terminate(server->wl_display); + } +} + +static void +handle_output_damage_destroy(struct wl_listener *listener, void *data) { + struct cg_output *output = + wl_container_of(listener, output, damage_destroy); + output_destroy(output); +} + +static void +handle_output_destroy(struct wl_listener *listener, void *data) { + struct cg_output *output = wl_container_of(listener, output, destroy); + wlr_output_damage_destroy(output->damage); + output_destroy(output); +} + +void +handle_new_output(struct wl_listener *listener, void *data) { + struct cg_server *server = wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + struct cg_output *output = calloc(1, sizeof(struct cg_output)); + if(!output) { + wlr_log(WLR_ERROR, "Failed to allocate output"); + return; + } + + output->wlr_output = wlr_output; + output->server = server; + output->damage = wlr_output_damage_create(wlr_output); + wl_list_insert(&server->outputs, &output->link); + + output->mode.notify = handle_output_mode; + wl_signal_add(&wlr_output->events.mode, &output->mode); + output->transform.notify = handle_output_transform; + wl_signal_add(&wlr_output->events.transform, &output->transform); + output->destroy.notify = handle_output_destroy; + wl_signal_add(&wlr_output->events.destroy, &output->destroy); + output->damage_frame.notify = handle_output_damage_frame; + wl_signal_add(&output->damage->events.frame, &output->damage_frame); + output->damage_destroy.notify = handle_output_damage_destroy; + wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); + + wlr_output_set_transform(wlr_output, server->output_transform); + wlr_output_layout_add_auto(server->output_layout, wlr_output); + + output->workspaces = malloc(server->nws * sizeof(struct cg_workspace *)); + for(unsigned int i = 0; i < server->nws; ++i) { + output->workspaces[i] = full_screen_workspace(output); + wl_list_init(&output->workspaces[i]->views); + wl_list_init(&output->workspaces[i]->unmanaged_views); + } + + output->curr_workspace = 0; + wl_list_init(&output->messages); + + struct wlr_output_mode *preferred_mode = + wlr_output_preferred_mode(wlr_output); + if(preferred_mode) { + wlr_output_set_mode(wlr_output, preferred_mode); + } + + if(wlr_xcursor_manager_load(server->seat->xcursor_manager, + wlr_output->scale)) { + wlr_log(WLR_ERROR, + "Cannot load XCursor theme for output '%s' with scale %f", + wlr_output->name, wlr_output->scale); + } + + wlr_output_damage_add_whole(output->damage); + + wlr_output_enable(wlr_output, true); + wlr_output_commit(wlr_output); + + /* TODO:If you see a way to circumvent having to loop twice, please do so */ + for(unsigned int i = 0; i < server->nws; ++i) { + workspace_free(output->workspaces[i]); + output->workspaces[i] = full_screen_workspace(output); + wl_list_init(&output->workspaces[i]->views); + wl_list_init(&output->workspaces[i]->unmanaged_views); + } + + /* We are the first output. Set the current output to this one. */ + if(server->curr_output == NULL) { + server->curr_output = output; + } + wlr_xcursor_manager_set_cursor_image(server->seat->xcursor_manager, + DEFAULT_XCURSOR, server->seat->cursor); + wlr_cursor_warp(server->seat->cursor, NULL, 0, 0); +} + +void +output_set_window_title(struct cg_output *output, const char *title) { + struct wlr_output *wlr_output = output->wlr_output; + + if(wlr_output_is_wl(wlr_output)) { + wlr_wl_output_set_title(wlr_output, title); +#if WLR_HAS_X11_BACKEND + } else if(wlr_output_is_x11(wlr_output)) { + wlr_x11_output_set_title(wlr_output, title); +#endif + } +} diff --git a/output.h b/output.h new file mode 100644 index 0000000..ffca3e7 --- /dev/null +++ b/output.h @@ -0,0 +1,57 @@ +#ifndef CG_OUTPUT_H +#define CG_OUTPUT_H + +#include + +struct cg_server; +struct cg_view; +struct wlr_output; +struct wlr_output_damage; +struct wlr_surface; +struct wlr_box; + +struct cg_output { + struct cg_server *server; + struct wlr_output *wlr_output; + struct wlr_output_damage *damage; + + struct wl_listener mode; + struct wl_listener transform; + struct wl_listener destroy; + struct wl_listener damage_frame; + struct wl_listener damage_destroy; + struct cg_workspace **workspaces; + struct wl_list messages; + int curr_workspace; + + struct wl_list link; // cg_server::outputs +}; + +typedef void (*cg_surface_iterator_func_t)(struct cg_output *output, + struct wlr_surface *surface, + struct wlr_box *box, + void *user_data); + +void +handle_new_output(struct wl_listener *listener, void *data); +void +output_surface_for_each_surface(struct cg_output *output, + struct wlr_surface *surface, double ox, + double oy, cg_surface_iterator_func_t iterator, + void *user_data); +void +output_view_for_each_popup(struct cg_output *output, struct cg_view *view, + cg_surface_iterator_func_t iterator, + void *user_data); +void +output_drag_icons_for_each_surface(struct cg_output *output, + struct wl_list *drag_icons, + cg_surface_iterator_func_t iterator, + void *user_data); +void +output_damage_surface(struct cg_output *output, struct wlr_surface *surface, + double ox, double oy, bool whole); +void +output_set_window_title(struct cg_output *output, const char *title); + +#endif diff --git a/pango.c b/pango.c new file mode 100644 index 0000000..035ac9b --- /dev/null +++ b/pango.c @@ -0,0 +1,94 @@ +#include "cairo.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char * +lenient_strcat(char *dest, const char *src) { + if(dest && src) { + return strcat(dest, src); + } + return dest; +} + +PangoLayout * +get_pango_layout(cairo_t *cairo, const char *font, const char *text, + double scale) { + PangoLayout *layout = pango_cairo_create_layout(cairo); + PangoAttrList *attrs; + + attrs = pango_attr_list_new(); + pango_layout_set_text(layout, text, -1); + + pango_attr_list_insert(attrs, pango_attr_scale_new(scale)); + PangoFontDescription *desc = pango_font_description_from_string(font); + pango_layout_set_font_description(layout, desc); + pango_layout_set_single_paragraph_mode(layout, 1); + pango_layout_set_attributes(layout, attrs); + pango_attr_list_unref(attrs); + pango_font_description_free(desc); + return layout; +} + +void +get_text_size(cairo_t *cairo, const char *font, int *width, int *height, + int *baseline, double scale, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + // Add one since vsnprintf excludes null terminator. + int length = vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + + char *buf = malloc(length); + if(buf == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate memory"); + return; + } + va_start(args, fmt); + vsnprintf(buf, length, fmt, args); + va_end(args); + + PangoLayout *layout = get_pango_layout(cairo, font, buf, scale); + pango_cairo_update_layout(cairo, layout); + pango_layout_get_pixel_size(layout, width, height); + if(baseline) { + *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE; + } + g_object_unref(layout); + free(buf); +} + +void +pango_printf(cairo_t *cairo, const char *font, double scale, const char *fmt, + ...) { + va_list args; + va_start(args, fmt); + // Add one since vsnprintf excludes null terminator. + int length = vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + + char *buf = malloc(length); + if(buf == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate memory"); + return; + } + va_start(args, fmt); + vsnprintf(buf, length, fmt, args); + va_end(args); + + PangoLayout *layout = get_pango_layout(cairo, font, buf, scale); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_get_font_options(cairo, fo); + pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo); + cairo_font_options_destroy(fo); + pango_cairo_update_layout(cairo, layout); + pango_cairo_show_layout(cairo, layout); + g_object_unref(layout); + free(buf); +} diff --git a/pango.h b/pango.h new file mode 100644 index 0000000..b4c6b55 --- /dev/null +++ b/pango.h @@ -0,0 +1,13 @@ +#ifndef _SWAY_PANGO_H +#define _SWAY_PANGO_H +#include +#include + +void +get_text_size(cairo_t *cairo, const char *font, int *width, int *height, + int *baseline, double scale, const char *fmt, ...); +void +pango_printf(cairo_t *cairo, const char *font, double scale, const char *fmt, + ...); + +#endif diff --git a/parse.c b/parse.c new file mode 100644 index 0000000..e8c7b15 --- /dev/null +++ b/parse.c @@ -0,0 +1,416 @@ +#define _POSIX_C_SOURCE 200812L + +#include +#include +#include + +#include "keybinding.h" +#include "output.h" +#include "parse.h" +#include "server.h" +#include "workspace.h" + +int +parse_action(struct cg_server *server, struct keybinding *keybinding, + char **saveptr) { + char *action = strtok_r(NULL, " ", saveptr); + if(action == NULL) { + wlr_log( + WLR_ERROR, + "Not enough parameters to \"bind\". Expected action to execute"); + return -1; + } + keybinding->data = (union keybinding_params){.c = NULL}; + if(strcmp(action, "vsplit") == 0) { + keybinding->action = KEYBINDING_SPLIT_VERTICAL; + } else if(strcmp(action, "hsplit") == 0) { + keybinding->action = KEYBINDING_SPLIT_HORIZONTAL; + } else if(strcmp(action, "quit") == 0) { + keybinding->action = KEYBINDING_QUIT; + } else if(strcmp(action, "focus") == 0) { + keybinding->action = KEYBINDING_CYCLE_TILES; + keybinding->data.b = false; + } else if(strcmp(action, "focusprev") == 0) { + keybinding->action = KEYBINDING_CYCLE_TILES; + keybinding->data.b = true; + } else if(strcmp(action, "next") == 0) { + keybinding->action = KEYBINDING_CYCLE_VIEWS; + keybinding->data.b = false; + } else if(strcmp(action, "prev") == 0) { + keybinding->action = KEYBINDING_CYCLE_VIEWS; + keybinding->data.b = true; + } else if(strcmp(action, "only") == 0) { + keybinding->action = KEYBINDING_LAYOUT_FULLSCREEN; + } else if(strcmp(action, "abort") == 0) { + keybinding->action = KEYBINDING_NOOP; + } else if(strcmp(action, "time") == 0) { + keybinding->action = KEYBINDING_SHOW_TIME; + } else if(strcmp(action, "nextscreen") == 0) { + keybinding->action = KEYBINDING_CYCLE_OUTPUT; + keybinding->data.b = false; + } else if(strcmp(action, "prevscreen") == 0) { + keybinding->action = KEYBINDING_CYCLE_OUTPUT; + keybinding->data.b = true; + } else if(strcmp(action, "exec") == 0) { + keybinding->action = KEYBINDING_RUN_COMMAND; + if(*saveptr == NULL) { + wlr_log(WLR_ERROR, "Not enough paramaters to \"exec\". Expected " + "string to execute."); + return -1; + } + keybinding->data.c = strdup(*saveptr); + } else if(strcmp(action, "resizeleft") == 0) { + keybinding->action = KEYBINDING_RESIZE_TILE_HORIZONTAL; + keybinding->data.i = -10; + } else if(strcmp(action, "resizeright") == 0) { + keybinding->action = KEYBINDING_RESIZE_TILE_HORIZONTAL; + keybinding->data.i = 10; + } else if(strcmp(action, "resizedown") == 0) { + keybinding->action = KEYBINDING_RESIZE_TILE_VERTICAL; + keybinding->data.i = 10; + } else if(strcmp(action, "resizeup") == 0) { + keybinding->action = KEYBINDING_RESIZE_TILE_VERTICAL; + keybinding->data.i = -10; + } else if(strcmp(action, "workspace") == 0) { + keybinding->action = KEYBINDING_SWITCH_WORKSPACE; + char *nws_str = strtok_r(NULL, " ", saveptr); + if(nws_str == NULL) { + wlr_log(WLR_ERROR, + "Expected argument for \"workspace\" action, got none."); + return -1; + } + + long ws = strtol(nws_str, NULL, 10); + if(!(1 <= ws && ws <= server->nws)) { + wlr_log(WLR_ERROR, + "Requested binding for workspace %li, but have %u", ws, + server->nws); + return -1; + } + keybinding->data.u = ws - 1; + } else if(strcmp(action, "movetoworkspace") == 0) { + keybinding->action = KEYBINDING_MOVE_VIEW_TO_WORKSPACE; + char *nws_str = strtok_r(NULL, " ", saveptr); + if(nws_str == NULL) { + wlr_log(WLR_ERROR, + "Expected argument for \"workspace\" action, got none."); + return -1; + } + + long ws = strtol(nws_str, NULL, 10); + if(!(1 <= ws && ws <= server->nws)) { + wlr_log( + WLR_ERROR, + "Requested binding for moving to workspace %li, but have %u", + ws, server->nws); + return -1; + } + keybinding->data.u = ws - 1; + } else if(strcmp(action, "exchangeleft") == 0) { + keybinding->action = KEYBINDING_SWAP_LEFT; + } else if(strcmp(action, "exchangeright") == 0) { + keybinding->action = KEYBINDING_SWAP_RIGHT; + } else if(strcmp(action, "exchangeup") == 0) { + keybinding->action = KEYBINDING_SWAP_TOP; + } else if(strcmp(action, "exchangedown") == 0) { + keybinding->action = KEYBINDING_SWAP_BOTTOM; + } else if(strcmp(action, "focusleft") == 0) { + keybinding->action = KEYBINDING_FOCUS_LEFT; + } else if(strcmp(action, "focusright") == 0) { + keybinding->action = KEYBINDING_FOCUS_RIGHT; + } else if(strcmp(action, "focusup") == 0) { + keybinding->action = KEYBINDING_FOCUS_TOP; + } else if(strcmp(action, "focusdown") == 0) { + keybinding->action = KEYBINDING_FOCUS_BOTTOM; + } else if(strcmp(action, "movetonextscreen") == 0) { + keybinding->action = KEYBINDING_MOVE_VIEW_TO_NEXT_OUTPUT; + } else if(strcmp(action, "switchvt") == 0) { + keybinding->action = KEYBINDING_CHANGE_TTY; + char *ntty = strtok_r(NULL, " ", saveptr); + if(ntty == NULL) { + wlr_log(WLR_ERROR, + "Expected argument for \"switchvt\" command, got none."); + return -1; + } + long tty = strtol(ntty, NULL, 10); + keybinding->data.u = tty; + } else if(strcmp(action, "mode") == 0) { + keybinding->action = KEYBINDING_SWITCH_MODE; + char *mode = strtok_r(NULL, " ", saveptr); + if(mode == NULL) { + wlr_log(WLR_ERROR, + "Expected mode after \"switch_mode\". Got nothing."); + return -1; + } + int mode_idx = get_mode_index_from_name(server->modes, mode); + if(mode_idx == -1) { + wlr_log(WLR_ERROR, "Unknown mode \"%s\" for switch_mode", mode); + return -1; + } + keybinding->data.u = (unsigned int)mode_idx; + } else if(strcmp(action, "setmode") == 0) { + keybinding->action = KEYBINDING_SWITCH_DEFAULT_MODE; + char *mode = strtok_r(NULL, " ", saveptr); + if(mode == NULL) { + wlr_log( + WLR_ERROR, + "Expected mode after \"switch_default_mode\". Got nothing."); + return -1; + } + int mode_idx = get_mode_index_from_name(server->modes, mode); + if(mode_idx == -1) { + wlr_log(WLR_ERROR, "Unknown mode \"%s\" for switch_default_mode", + mode); + return -1; + } + keybinding->data.u = (unsigned int)mode_idx; + } else { + wlr_log(WLR_ERROR, "Error, unsupported action \"%s\".", action); + return -1; + } + return 0; +} + +/* parses a key definition (e.g. "S-Tab") and sets key and modifiers in + * keybinding respectivly */ +int +parse_key(struct keybinding *keybinding, const char *key_def) { + if(key_def == NULL) { + wlr_log(WLR_ERROR, "Expected key definition, got nothing."); + return -1; + } + keybinding->modifiers = 0; + while(strlen(key_def) > 1 && key_def[1] == '-') { + switch(key_def[0]) { + case 'S': + keybinding->modifiers |= WLR_MODIFIER_SHIFT; + break; + case 'A': + keybinding->modifiers |= WLR_MODIFIER_ALT; + break; + case 'C': + keybinding->modifiers |= WLR_MODIFIER_CTRL; + break; + case 'L': + keybinding->modifiers |= WLR_MODIFIER_LOGO; + break; + case '2': + keybinding->modifiers |= WLR_MODIFIER_MOD2; + break; + case '3': + keybinding->modifiers |= WLR_MODIFIER_MOD3; + break; + case '5': + keybinding->modifiers |= WLR_MODIFIER_MOD5; + break; + default: + wlr_log(WLR_ERROR, "Unknown modifier \"%c\"", key_def[0]); + return -1; + } + key_def += 2; + } + xkb_keysym_t keysym = xkb_keysym_from_name(key_def, XKB_KEYSYM_NO_FLAGS); + if(keysym == XKB_KEY_NoSymbol) { + wlr_log(WLR_ERROR, "Could not convert key \"%s\" to keysym.", key_def); + return -1; + } + keybinding->key = keysym; + return 0; +} + +/* Parse a keybinding definition and return it if successful, else return NULL + */ +struct keybinding * +parse_keybinding(struct cg_server *server, char **saveptr) { + struct keybinding *keybinding = malloc(sizeof(struct keybinding)); + char *key = strtok_r(NULL, " ", saveptr); + if(parse_key(keybinding, key) != 0) { + wlr_log(WLR_ERROR, "Could not parse key definition \"%s\"", key); + free(keybinding); + return NULL; + } + if(parse_action(server, keybinding, saveptr) != 0) { + free(keybinding); + return NULL; + } + return keybinding; +} + +int +parse_bind(struct cg_server *server, struct keybinding_list *list, + char **saveptr) { + struct keybinding *keybinding = parse_keybinding(server, saveptr); + if(keybinding == NULL) { + wlr_log(WLR_ERROR, "Could not parse keybinding for \"bind\"."); + return -1; + } + keybinding->mode = 1; + keybinding_list_push(list, keybinding); + return 0; +} + +int +parse_definekey(struct cg_server *server, struct keybinding_list *list, + char **saveptr) { + char *mode = strtok_r(NULL, " ", saveptr); + if(mode == NULL) { + wlr_log(WLR_ERROR, "Too few arguments to \"definekey\". Expected mode"); + return -1; + } + int mode_idx = get_mode_index_from_name(server->modes, mode); + if(mode_idx == -1) { + wlr_log(WLR_ERROR, "Unknown mode \"%s\"", mode); + return -1; + } + struct keybinding *keybinding = parse_keybinding(server, saveptr); + if(keybinding == NULL) { + wlr_log(WLR_ERROR, "Could not parse keybinding for \"definekey\""); + return -1; + } + keybinding->mode = mode_idx; + keybinding_list_push(list, keybinding); + return 0; +} + +int +parse_and_run_exec(char **saveptr) { + return run_action(KEYBINDING_RUN_COMMAND, NULL, + (union keybinding_params){.c = *saveptr}); +} + +int +parse_background(struct cg_server *server, char **saveptr) { + /* Read rgb numbers */ + for(unsigned int i = 0; i < 3; ++i) { + char *nstr = strtok_r(NULL, " ", saveptr); + if(nstr == NULL) { + wlr_log(WLR_ERROR, + "Expected three space-separated numbers (rgb) for " + "background color setting. Got %d.", + i); + return -1; + } + char *endptr = NULL; + float nval = strtof(nstr, &endptr); + if(endptr == nstr) { + wlr_log( + WLR_ERROR, + "Could not parse number \"%s\" für background color setting.", + nstr); + return -1; + } + server->bg_color[i] = nval; + } + return 0; +} + +int +parse_escape(struct keybinding_list *list, char **saveptr) { + struct keybinding *keybinding = malloc(sizeof(struct keybinding)); + char *key = strtok_r(NULL, " ", saveptr); + if(parse_key(keybinding, key) != 0) { + wlr_log(WLR_ERROR, + "Could not parse key definition \"%s\" for \"escape\"", key); + free(keybinding); + return -1; + } + keybinding->mode = 0; //"top" mode + keybinding->action = KEYBINDING_SWITCH_MODE; + keybinding->data.u = 1; //"root" mode + keybinding_list_push(list, keybinding); + return 0; +} + +int +parse_definemode(char ***modes, char **saveptr) { + int length = 0; + while((*modes)[length++] != NULL) + ; + char **tmp = realloc(*modes, (length + 1) * sizeof(char *)); + if(tmp == NULL) { + wlr_log(WLR_ERROR, "Could not allocate memory for storing modes."); + return -1; + } + *modes = tmp; + (*modes)[length] = NULL; + + char *mode = strtok_r(NULL, " ", saveptr); + if(mode == NULL) { + wlr_log(WLR_ERROR, "Expected mode to succeed \"definemode\" keyword."); + return -1; + } + (*modes)[length - 1] = strdup(mode); + return 0; +} + +int +parse_rc_line(struct cg_server *server, char *line) { + char *saveptr = NULL; // Used internally by strtok_r + char *command = strtok_r(line, " ", &saveptr); + if(command == NULL) { + return 0; + } + if(strcmp(command, "bind") == 0) { + if(parse_bind(server, server->keybindings, &saveptr) != 0) { + return -1; + } + } else if(strcmp(command, "definekey") == 0) { + if(parse_definekey(server, server->keybindings, &saveptr) != 0) { + return -1; + } + } else if(strcmp(command, "background") == 0) { + if(parse_background(server, &saveptr) != 0) { + return -1; + } + } else if(strcmp(command, "escape") == 0) { + if(parse_escape(server->keybindings, &saveptr) != 0) { + return -1; + } + } else if(strcmp(command, "definemode") == 0) { + if(parse_definemode(&server->modes, &saveptr) != 0) { + return -1; + } + } else if(strcmp(command, "exec") == 0) { + if(parse_and_run_exec(&saveptr) != 0) { + return -1; + } + } else if(strcmp(command, "workspaces") == 0) { + char *nws_str = strtok_r(NULL, " ", &saveptr); + if(nws_str == NULL) { + wlr_log(WLR_ERROR, + "Expected argument for \"workspaces\" command, got none."); + return -1; + } + long nws = strtol(nws_str, NULL, 10); + if(!(1 <= nws && nws <= 30)) { + wlr_log(WLR_ERROR, + "More than 30 workspaces are not supported. Received %li", + nws); + return -1; + } + struct cg_output *output; + wl_list_for_each(output, &server->outputs, link) { + for(unsigned int i = nws; i < server->nws; ++i) { + free(output->workspaces[i]); + } + struct cg_workspace **new_workspaces = realloc( + output->workspaces, nws * sizeof(struct cg_workspace *)); + if(new_workspaces == NULL) { + wlr_log(WLR_ERROR, "Error reallocating memory for workspaces."); + return -1; + } + output->workspaces = new_workspaces; + for(unsigned int i = server->nws; i < nws; ++i) { + output->workspaces[i] = full_screen_workspace(output); + wl_list_init(&output->workspaces[i]->views); + wl_list_init(&output->workspaces[i]->unmanaged_views); + } + } + server->nws = nws; + } else { + wlr_log(WLR_ERROR, "Unsupported command \"%s\" in config file", + command); + return -1; + } + return 0; +} diff --git a/parse.h b/parse.h new file mode 100644 index 0000000..622697f --- /dev/null +++ b/parse.h @@ -0,0 +1,10 @@ +#ifndef PARSE_H + +#define PARSE_H + +struct cg_server; + +int +parse_rc_line(struct cg_server *server, char *line); + +#endif /* end of include guard PARSE_H */ diff --git a/render.c b/render.c new file mode 100644 index 0000000..43da1da --- /dev/null +++ b/render.c @@ -0,0 +1,254 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2020 Jente Hidskes + * Copyright (C) 2019 The Sway authors + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "output.h" +#include "seat.h" +#include "server.h" +#include "util.h" +#include "view.h" +#include "workspace.h" + +static void +scissor_output(struct wlr_output *output, pixman_box32_t *rect) { + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + + struct wlr_box box = { + .x = rect->x1, + .y = rect->y1, + .width = rect->x2 - rect->x1, + .height = rect->y2 - rect->y1, + }; + + int output_width, output_height; + wlr_output_transformed_resolution(output, &output_width, &output_height); + enum wl_output_transform transform = + wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, output_width, output_height); + + wlr_renderer_scissor(renderer, &box); +} + +struct render_data { + pixman_region32_t *damage; +}; + +static void +render_texture(struct wlr_output *wlr_output, pixman_region32_t *output_damage, + struct wlr_texture *texture, const struct wlr_box *box, + const float matrix[static 9]) { + struct wlr_renderer *renderer = + wlr_backend_get_renderer(wlr_output->backend); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_union_rect(&damage, &damage, box->x, box->y, box->width, + box->height); + pixman_region32_intersect(&damage, &damage, output_damage); + if(!pixman_region32_not_empty(&damage)) { + goto damage_finish; + } + + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); + for(int i = 0; i < nrects; i++) { + scissor_output(wlr_output, &rects[i]); + wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0F); + } + +damage_finish: + pixman_region32_fini(&damage); +} + +static void +render_surface_iterator(struct cg_output *output, struct wlr_surface *surface, + struct wlr_box *box, void *user_data) { + struct render_data *data = user_data; + struct wlr_output *wlr_output = output->wlr_output; + pixman_region32_t *output_damage = data->damage; + + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if(!texture) { + wlr_log(WLR_DEBUG, "Cannot obtain surface texture"); + return; + } + + scale_box(box, wlr_output->scale); + + float matrix[9]; + enum wl_output_transform transform = + wlr_output_transform_invert(surface->current.transform); + wlr_matrix_project_box(matrix, box, transform, 0.0F, + wlr_output->transform_matrix); + + render_texture(wlr_output, output_damage, texture, box, matrix); +} + +static void +render_drag_icons(struct cg_output *output, pixman_region32_t *damage, + struct wl_list *drag_icons) { + struct render_data data = { + .damage = damage, + }; + output_drag_icons_for_each_surface(output, drag_icons, + render_surface_iterator, &data); +} + +/** + * Render all toplevels without descending into popups. + */ +static void +render_view_toplevels(struct cg_view *view, struct cg_output *output, + pixman_region32_t *damage) { + struct render_data data = { + .damage = damage, + }; + + double ox, oy; + ox = view->ox; + oy = view->oy; + output_surface_for_each_surface(output, view->wlr_surface, ox, oy, + render_surface_iterator, &data); +} + +static void +render_popup_iterator(struct cg_output *output, struct wlr_surface *surface, + struct wlr_box *box, void *data) { + /* Render this popup's surface. */ + render_surface_iterator(output, surface, box, data); + + /* Render this popup's child toplevels. */ + output_surface_for_each_surface(output, surface, box->x, box->y, + render_surface_iterator, data); +} + +static void +render_view_popups(struct cg_view *view, struct cg_output *output, + pixman_region32_t *damage) { + struct render_data data = { + .damage = damage, + }; + output_view_for_each_popup(output, view, render_popup_iterator, &data); +} + +void +output_render(struct cg_output *output, pixman_region32_t *damage) { + struct cg_server *server = output->server; + struct wlr_output *wlr_output = output->wlr_output; + + struct wlr_renderer *renderer = + wlr_backend_get_renderer(wlr_output->backend); + if(!renderer) { + wlr_log(WLR_DEBUG, "Expected the output backend to have a renderer"); + return; + } + + wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); + + if(!pixman_region32_not_empty(damage)) { + wlr_log(WLR_DEBUG, "Output isn't damaged but needs a buffer swap"); + goto renderer_end; + } + +#ifdef DEBUG + if(server->debug_damage_tracking) { + wlr_renderer_clear(renderer, (float[]){1.0F, 0.0F, 0.0F, 1.0F}); + } +#endif + + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); + for(int i = 0; i < nrects; i++) { + scissor_output(wlr_output, &rects[i]); + wlr_renderer_clear(renderer, server->bg_color); + } + + bool first = true; + for(struct cg_tile *tile = + output->workspaces[output->curr_workspace]->focused_tile; + first || + tile != output->workspaces[output->curr_workspace]->focused_tile; + tile = tile->next) { + first = false; + /* Only render visible views */ + if(tile->view != NULL) { + render_view_toplevels(tile->view, output, damage); + } + } + + struct cg_view *view; + wl_list_for_each_reverse( + view, &output->workspaces[output->curr_workspace]->unmanaged_views, + link) { + render_view_toplevels(view, output, damage); + } + + struct cg_view *focused_view = seat_get_focus(server->seat); + if(focused_view != NULL) { + render_view_popups(focused_view, output, damage); + } + + struct cg_message *message; + wl_list_for_each_reverse(message, &output->messages, link) { + float matrix[9]; + wlr_matrix_project_box(matrix, message->position, + WL_OUTPUT_TRANSFORM_NORMAL, 0.0F, + wlr_output->transform_matrix); + render_texture(output->wlr_output, damage, message->message, + message->position, matrix); + } + render_drag_icons(output, damage, &server->seat->drag_icons); + +renderer_end: + /* Draw software cursor in case hardware cursors aren't + available. This is a no-op when they are. */ + wlr_output_render_software_cursors(wlr_output, damage); + wlr_renderer_scissor(renderer, NULL); + wlr_renderer_end(renderer); + + int output_width, output_height; + wlr_output_transformed_resolution(wlr_output, &output_width, + &output_height); + + pixman_region32_t frame_damage; + pixman_region32_init(&frame_damage); + + enum wl_output_transform transform = + wlr_output_transform_invert(wlr_output->transform); + wlr_region_transform(&frame_damage, &output->damage->current, transform, + output_width, output_height); + +#ifdef DEBUG + if(server->debug_damage_tracking) { + pixman_region32_union_rect(&frame_damage, &frame_damage, 0, 0, + output_width, output_height); + } +#endif + + wlr_output_set_damage(wlr_output, &frame_damage); + pixman_region32_fini(&frame_damage); + + if(!wlr_output_commit(wlr_output)) { + wlr_log(WLR_ERROR, "Could not commit output"); + } +} diff --git a/render.h b/render.h new file mode 100644 index 0000000..0a883bd --- /dev/null +++ b/render.h @@ -0,0 +1,9 @@ +#ifndef CG_RENDER_H +#define CG_RENDER_H + +struct cg_output; + +void +output_render(struct cg_output *output, pixman_region32_t *damage); + +#endif diff --git a/seat.c b/seat.c new file mode 100644 index 0000000..75a6a78 --- /dev/null +++ b/seat.c @@ -0,0 +1,1129 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2020 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200112L + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if CG_HAS_XWAYLAND +#include +#endif + +#include "keybinding.h" +#include "message.h" +#include "output.h" +#include "seat.h" +#include "server.h" +#include "view.h" +#include "workspace.h" +#if CG_HAS_XWAYLAND +#include "xwayland.h" +#endif + +static void +drag_icon_update_position(struct cg_drag_icon *drag_icon); + +/* XDG toplevels may have nested surfaces, such as popup windows for context + * menus or tooltips. This function tests if any of those are underneath the + * coordinates lx and ly (in output Layout Coordinates). If so, it sets the + * surface pointer to that wlr_surface and the sx and sy coordinates to the + * coordinates relative to that surface's top-left corner. */ +static bool +view_at(const struct cg_view *view, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + struct wlr_box *output_layout_box = wlr_output_layout_get_box( + view->workspace->output->server->output_layout, + view->workspace->output->wlr_output); + + double view_sx = lx - view->ox - output_layout_box->x; + double view_sy = ly - view->oy - output_layout_box->y; + + double _sx, _sy; + struct wlr_surface *_surface = + view_wlr_surface_at(view, view_sx, view_sy, &_sx, &_sy); + if(_surface != NULL) { + *sx = _sx; + *sy = _sy; + *surface = _surface; + return true; + } + + return false; +} + +/* If desktop_view_at returns a view, there is also a + * surface. There cannot be a surface without a view, either. It's + * both or nothing. */ +static struct cg_view * +desktop_view_at(const struct cg_server *server, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + struct cg_view *view; + wl_list_for_each( + view, + &server->curr_output->workspaces[server->curr_output->curr_workspace] + ->unmanaged_views, + link) { + if(view_at(view, lx, ly, surface, sx, sy)) { + return view; + } + } + + bool first = true; + for(struct cg_tile *tile = + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile; + first || + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile != tile; + tile = tile->next) { + first = false; + if(tile->view != NULL && view_at(tile->view, lx, ly, surface, sx, sy)) { + return tile->view; + } + } + return NULL; +} + +static void +update_capabilities(const struct cg_seat *seat) { + uint32_t caps = 0; + + if(!wl_list_empty(&seat->keyboards)) { + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + } + if(!wl_list_empty(&seat->pointers)) { + caps |= WL_SEAT_CAPABILITY_POINTER; + } + if(!wl_list_empty(&seat->touch)) { + caps |= WL_SEAT_CAPABILITY_TOUCH; + } + wlr_seat_set_capabilities(seat->seat, caps); + + /* Hide cursor if the seat doesn't have pointer capability. */ + if((caps & WL_SEAT_CAPABILITY_POINTER) == 0) { + wlr_cursor_set_image(seat->cursor, NULL, 0, 0, 0, 0, 0, 0); + } else { + wlr_xcursor_manager_set_cursor_image(seat->xcursor_manager, + DEFAULT_XCURSOR, seat->cursor); + } +} + +static void +handle_touch_destroy(struct wl_listener *listener, void *_data) { + struct cg_touch *touch = wl_container_of(listener, touch, destroy); + struct cg_seat *seat = touch->seat; + + wl_list_remove(&touch->link); + wlr_cursor_detach_input_device(seat->cursor, touch->device); + wl_list_remove(&touch->destroy.link); + free(touch); + + update_capabilities(seat); +} + +static void +handle_new_touch(struct cg_seat *seat, struct wlr_input_device *device) { + struct cg_touch *touch = calloc(1, sizeof(struct cg_touch)); + if(!touch) { + wlr_log(WLR_ERROR, "Cannot allocate touch"); + return; + } + + touch->seat = seat; + touch->device = device; + wlr_cursor_attach_input_device(seat->cursor, device); + + wl_list_insert(&seat->touch, &touch->link); + touch->destroy.notify = handle_touch_destroy; + wl_signal_add(&touch->device->events.destroy, &touch->destroy); + + if(device->output_name != NULL) { + struct cg_output *output; + wl_list_for_each(output, &seat->server->outputs, link) { + if(strcmp(device->output_name, output->wlr_output->name) == 0) { + wlr_cursor_map_input_to_output(seat->cursor, device, + output->wlr_output); + break; + } + } + } +} + +static void +handle_pointer_destroy(struct wl_listener *listener, void *_data) { + struct cg_pointer *pointer = wl_container_of(listener, pointer, destroy); + struct cg_seat *seat = pointer->seat; + + wl_list_remove(&pointer->link); + wlr_cursor_detach_input_device(seat->cursor, pointer->device); + wl_list_remove(&pointer->destroy.link); + free(pointer); + + update_capabilities(seat); +} + +static void +handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device) { + struct cg_pointer *pointer = calloc(1, sizeof(struct cg_pointer)); + if(!pointer) { + wlr_log(WLR_ERROR, "Cannot allocate pointer"); + return; + } + + pointer->seat = seat; + pointer->device = device; + wlr_cursor_attach_input_device(seat->cursor, device); + + wl_list_insert(&seat->pointers, &pointer->link); + pointer->destroy.notify = handle_pointer_destroy; + wl_signal_add(&device->events.destroy, &pointer->destroy); + + if(device->output_name != NULL) { + struct cg_output *output; + wl_list_for_each(output, &seat->server->outputs, link) { + if(strcmp(device->output_name, output->wlr_output->name) == 0) { + wlr_cursor_map_input_to_output(seat->cursor, device, + output->wlr_output); + break; + } + } + } +} + +static int +handle_keyboard_repeat(void *data) { + struct cg_keyboard *keyboard = (struct cg_keyboard *)data; + struct wlr_keyboard *wlr_device = keyboard->device->keyboard; + if(keyboard->repeat_keybinding != NULL) { + if(wlr_device->repeat_info.rate > 0) { + if(wl_event_source_timer_update( + keyboard->key_repeat_timer, + 1000 / wlr_device->repeat_info.rate) < 0) { + wlr_log(WLR_DEBUG, "failed to update key repeat timer"); + } + } + + run_action((*keyboard->repeat_keybinding)->action, + keyboard->seat->server, + (*keyboard->repeat_keybinding)->data); + } + return 0; +} + +static void +handle_modifier_event(struct cg_keyboard *keyboard) { + wlr_seat_set_keyboard(keyboard->seat->seat, keyboard->device); + wlr_seat_keyboard_notify_modifiers(keyboard->seat->seat, + &keyboard->device->keyboard->modifiers); + + wlr_idle_notify_activity(keyboard->seat->server->idle, + keyboard->seat->seat); +} + +void +keyboard_disarm_key_repeat(struct cg_keyboard *keyboard) { + if(!keyboard) { + return; + } + keyboard->repeat_keybinding = NULL; + if(wl_event_source_timer_update(keyboard->key_repeat_timer, 0) < 0) { + wlr_log(WLR_DEBUG, "failed to disarm key repeat timer"); + } +} + +static bool +handle_command_key_bindings(struct cg_server *server, xkb_keysym_t sym, + uint32_t modifiers, uint32_t mode, + struct cg_keyboard *keyboard) { + struct keybinding **keybinding = find_keybinding( + server->keybindings, + &(struct keybinding){.key = sym, .mode = mode, .modifiers = modifiers}); + server->seat->mode = + server->seat + ->default_mode; // Return to mode we are currently in by default + if(keybinding) { + wlr_log( + WLR_DEBUG, + "Recognized keybinding pressed (key: %d, mode: %d, modifiers: %d)", + sym, mode, modifiers); + + if(keyboard->device->keyboard->repeat_info.delay > 0) { + keyboard->repeat_keybinding = keybinding; + if(wl_event_source_timer_update( + keyboard->key_repeat_timer, + keyboard->device->keyboard->repeat_info.delay) < 0) { + wlr_log(WLR_DEBUG, "failed to set key repeat timer"); + } + } + message_clear(keyboard->seat->server->curr_output); + run_action((*keybinding)->action, server, (*keybinding)->data); + wlr_idle_notify_activity(server->idle, server->seat->seat); + return true; + } else if(mode != 0) { + run_action(KEYBINDING_NOOP, server, (union keybinding_params){NULL}); + message_printf(server->curr_output, "unbound key pressed"); + wlr_log(WLR_DEBUG, + "Unknown keybinding pressed (key: %d, mode: %d, modifiers: %d)", + sym, mode, modifiers); + return true; + } else { + return false; + } +} + +static bool +key_is_modifier(const xkb_keysym_t key) { + switch(key) { + case XKB_KEY_Shift_L: + case XKB_KEY_Shift_R: + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + case XKB_KEY_Super_L: + case XKB_KEY_Super_R: + case XKB_KEY_Hyper_L: + case XKB_KEY_Hyper_R: + return true; + default: + return false; + } +} + +static void +handle_key_event(struct cg_keyboard *keyboard, void *data) { + struct cg_seat *seat = keyboard->seat; + struct wlr_event_keyboard_key *event = data; + + /* Translate from libinput keycode to an xkbcommon keycode. */ + xkb_keycode_t keycode = event->keycode + 8; + + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state, + keycode, &syms); + + bool handled = false; + + for(int i = 0; i < nsyms; ++i) { + if(event->state == WLR_KEY_PRESSED && !key_is_modifier(syms[i])) { + uint32_t modifiers = + wlr_keyboard_get_modifiers(keyboard->device->keyboard); + /* Get the consumed_modifiers and remove them from the modifier list + */ + xkb_mod_mask_t consumed_modifiers = + xkb_state_key_get_consumed_mods2( + keyboard->device->keyboard->xkb_state, keycode, + XKB_CONSUMED_MODE_XKB); + if(handle_command_key_bindings(seat->server, syms[i], + modifiers & ~consumed_modifiers, + seat->mode, keyboard)) { + handled = true; + } + } else if(keyboard->repeat_keybinding != NULL && handled == false) { + keyboard_disarm_key_repeat(keyboard); + } + } + + if(!handled) { + /* Otherwise, we pass it along to the client. */ + wlr_seat_set_keyboard(seat->seat, keyboard->device); + wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, + event->keycode, event->state); + } + + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +cg_keyboard_group_remove(struct cg_keyboard *keyboard); + +static void +cg_keyboard_destroy(struct cg_keyboard *keyboard) { + struct cg_seat *seat = keyboard->seat; + + if(keyboard->device->keyboard->group != NULL) { + cg_keyboard_group_remove(keyboard); + } + + if(wlr_seat_get_keyboard(seat->seat) == keyboard->device->keyboard) { + wlr_seat_set_keyboard(seat->seat, NULL); + } + + keyboard_disarm_key_repeat(keyboard); + wl_event_source_remove(keyboard->key_repeat_timer); + + free(keyboard); +} + +static void +destroy_empty_wlr_keyboard_group(void *data) { + wlr_keyboard_group_destroy(data); +} + +static void +cg_keyboard_group_remove(struct cg_keyboard *keyboard) { + struct wlr_keyboard_group *wlr_group = keyboard->device->keyboard->group; + + wlr_keyboard_group_remove_keyboard(keyboard->device->keyboard->group, + keyboard->device->keyboard); + + if(wl_list_empty(&wlr_group->devices)) { + wlr_log(WLR_DEBUG, "Destroying empty keyboard group %p", + (void *)wlr_group); + struct cg_keyboard_group *cg_group = wlr_group->data; + wlr_group->data = NULL; + wl_list_remove(&cg_group->link); + wl_list_remove(&cg_group->key.link); + wl_list_remove(&cg_group->modifiers.link); + cg_keyboard_destroy(cg_group->keyboard); + free(cg_group); + + // To prevent use-after-free conditions when handling key events, defer + // freeing the wlr_keyboard_group until idle + if(keyboard->seat->server->running) { + wl_event_loop_add_idle(keyboard->seat->server->event_loop, + destroy_empty_wlr_keyboard_group, wlr_group); + } else { + destroy_empty_wlr_keyboard_group(wlr_group); + } + } +} + +static void +handle_keyboard_destroy(struct wl_listener *listener, void *_data) { + struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); + struct cg_seat *seat = keyboard->seat; + wl_list_remove(&keyboard->link); + wl_list_remove(&keyboard->destroy.link); + cg_keyboard_destroy(keyboard); + update_capabilities(seat); +} + +static void +handle_keyboard_group_key(struct wl_listener *listener, void *data) { + struct cg_keyboard_group *cg_group = + wl_container_of(listener, cg_group, key); + handle_key_event(cg_group->keyboard, data); +} + +static void +handle_keyboard_group_modifiers(struct wl_listener *listener, void *_data) { + struct cg_keyboard_group *group = + wl_container_of(listener, group, modifiers); + handle_modifier_event(group->keyboard); +} + +static bool +keymaps_match(struct xkb_keymap *km1, struct xkb_keymap *km2) { + char *km1_str = xkb_keymap_get_as_string(km1, XKB_KEYMAP_FORMAT_TEXT_V1); + char *km2_str = xkb_keymap_get_as_string(km2, XKB_KEYMAP_FORMAT_TEXT_V1); + bool result = strcmp(km1_str, km2_str) == 0; + free(km1_str); + free(km2_str); + return result; +} + +static bool +repeat_info_match(const struct cg_keyboard *a, const struct wlr_keyboard *b) { + return a->device->keyboard->repeat_info.rate == b->repeat_info.rate && + a->device->keyboard->repeat_info.delay == b->repeat_info.delay; +} + +struct cg_keyboard * +cg_keyboard_from_seat(struct cg_seat *seat, struct wlr_input_device *device) { + struct cg_keyboard *keyboard = calloc(1, sizeof(struct cg_keyboard)); + if(keyboard == NULL) { + wlr_log(WLR_ERROR, "Could not allocate new cg_keyboard."); + return NULL; + } + + keyboard->seat = seat; + keyboard->device = device; + + keyboard->key_repeat_timer = wl_event_loop_add_timer( + seat->server->event_loop, handle_keyboard_repeat, keyboard); + + return keyboard; +} + +static void +keyboard_group_add(const struct cg_keyboard *keyboard) { + struct cg_seat *seat = keyboard->seat; + struct wlr_keyboard *wlr_keyboard = keyboard->device->keyboard; + struct cg_keyboard_group *group; + wl_list_for_each(group, &seat->keyboard_groups, link) { + struct wlr_keyboard_group *wlr_group = group->wlr_group; + if(keymaps_match(wlr_keyboard->keymap, wlr_group->keyboard.keymap) && + repeat_info_match(keyboard, &wlr_group->keyboard)) { + wlr_log(WLR_DEBUG, "Adding keyboard %p to group %p.", + (void *)keyboard, (void *)wlr_group); + wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard); + return; + } + } + /* This is reached if and only if the keyboard could not be inserted into + * any group */ + struct cg_keyboard_group *cg_group = + calloc(1, sizeof(struct cg_keyboard_group)); + if(cg_group == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate keyboard group."); + return; + } + cg_group->wlr_group = wlr_keyboard_group_create(); + if(cg_group->wlr_group == NULL) { + wlr_log(WLR_ERROR, "Failed to create wlr keyboard group."); + goto cleanup; + } + + cg_group->wlr_group->data = cg_group; + wlr_keyboard_set_keymap(&cg_group->wlr_group->keyboard, + keyboard->device->keyboard->keymap); + wlr_keyboard_set_repeat_info(&cg_group->wlr_group->keyboard, + keyboard->device->keyboard->repeat_info.rate, + keyboard->device->keyboard->repeat_info.delay); + wlr_log(WLR_DEBUG, "Created keyboard group %p", + (void *)cg_group->wlr_group); + + cg_group->keyboard = + cg_keyboard_from_seat(seat, cg_group->wlr_group->input_device); + if(cg_group->keyboard == NULL) { + wlr_log(WLR_ERROR, "Failed to create keyboard from seat."); + goto cleanup; + } + + wlr_log(WLR_DEBUG, "Adding keyboard %p to group %p.", (void *)keyboard, + (void *)cg_group->wlr_group); + wlr_keyboard_group_add_keyboard(cg_group->wlr_group, wlr_keyboard); + wl_list_insert(&seat->keyboard_groups, &cg_group->link); + + wl_signal_add(&cg_group->wlr_group->keyboard.events.key, &cg_group->key); + cg_group->key.notify = handle_keyboard_group_key; + + wl_signal_add(&cg_group->wlr_group->keyboard.events.modifiers, + &cg_group->modifiers); + cg_group->modifiers.notify = handle_keyboard_group_modifiers; + return; + +cleanup: + if(cg_group && cg_group->wlr_group) { + wlr_keyboard_group_destroy(cg_group->wlr_group); + } + free(cg_group); +} + +static void +handle_new_keyboard(struct cg_seat *seat, struct wlr_input_device *device) { + struct cg_keyboard *keyboard = calloc(1, sizeof(struct cg_keyboard)); + if(!keyboard) { + wlr_log(WLR_ERROR, "Cannot allocate keyboard"); + return; + } + + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if(!context) { + wlr_log(WLR_ERROR, "Unable to create XBK context"); + free(keyboard); + return; + } + + struct xkb_rule_names rules = {0}; + rules.rules = getenv("XKB_DEFAULT_RULES"); + rules.model = getenv("XKB_DEFAULT_MODEL"); + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + struct xkb_keymap *keymap = + xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + if(!keymap) { + wlr_log(WLR_ERROR, + "Unable to configure keyboard: keymap does not exist"); + free(keyboard); + xkb_context_unref(context); + return; + } + + keyboard->seat = seat; + keyboard->device = device; + wlr_keyboard_set_keymap(device->keyboard, keymap); + + xkb_keymap_unref(keymap); + xkb_context_unref(context); + wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); + + keyboard_group_add(keyboard); + + wlr_seat_set_keyboard(seat->seat, device->keyboard->group->input_device); + + wl_list_insert(&seat->keyboards, &keyboard->link); + keyboard->destroy.notify = handle_keyboard_destroy; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->key_repeat_timer = wl_event_loop_add_timer( + seat->server->event_loop, handle_keyboard_repeat, keyboard); +} + +static void +handle_new_input(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, new_input); + struct wlr_input_device *device = data; + + switch(device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + handle_new_keyboard(seat, device); + break; + case WLR_INPUT_DEVICE_POINTER: + handle_new_pointer(seat, device); + break; + case WLR_INPUT_DEVICE_TOUCH: + handle_new_touch(seat, device); + break; + case WLR_INPUT_DEVICE_SWITCH: + wlr_log(WLR_DEBUG, "Switch input is not implemented"); + return; + case WLR_INPUT_DEVICE_TABLET_TOOL: + case WLR_INPUT_DEVICE_TABLET_PAD: + wlr_log(WLR_DEBUG, "Tablet input is not implemented"); + return; + } + + update_capabilities(seat); +} + +static void +handle_request_set_primary_selection(struct wl_listener *listener, void *data) { + struct cg_seat *seat = + wl_container_of(listener, seat, request_set_primary_selection); + struct wlr_seat_request_set_primary_selection_event *event = data; + + wlr_seat_set_primary_selection(seat->seat, event->source, event->serial); +} + +static void +handle_request_set_selection(struct wl_listener *listener, void *data) { + struct cg_seat *seat = + wl_container_of(listener, seat, request_set_selection); + struct wlr_seat_request_set_selection_event *event = data; + + wlr_seat_set_selection(seat->seat, event->source, event->serial); +} + +static void +handle_request_set_cursor(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, request_set_cursor); + struct wlr_seat_pointer_request_set_cursor_event *event = data; + struct wlr_surface *focused_surface = + event->seat_client->seat->pointer_state.focused_surface; + bool has_focused = + focused_surface != NULL && focused_surface->resource != NULL; + struct wl_client *focused_client = NULL; + if(has_focused) { + focused_client = wl_resource_get_client(focused_surface->resource); + } + + /* This can be sent by any client, so we check to make sure + * this one actually has pointer focus first. */ + if(focused_client == event->seat_client->client) { + wlr_cursor_set_surface(seat->cursor, event->surface, event->hotspot_x, + event->hotspot_y); + } +} + +static void +handle_touch_down(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, touch_down); + struct wlr_event_touch_down *event = data; + + double lx, ly; + wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, event->x, + event->y, &lx, &ly); + + double sx, sy; + struct wlr_surface *surface; + struct cg_view *view = + desktop_view_at(seat->server, lx, ly, &surface, &sx, &sy); + + uint32_t serial = 0; + if(view) { + serial = wlr_seat_touch_notify_down( + seat->seat, surface, event->time_msec, event->touch_id, sx, sy); + } + + if(serial && wlr_seat_touch_num_points(seat->seat) == 1) { + seat->touch_id = event->touch_id; + seat->touch_lx = lx; + seat->touch_ly = ly; + } + + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +handle_touch_up(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, touch_up); + struct wlr_event_touch_up *event = data; + + if(!wlr_seat_touch_get_point(seat->seat, event->touch_id)) { + return; + } + + wlr_seat_touch_notify_up(seat->seat, event->time_msec, event->touch_id); + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +handle_touch_motion(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, touch_motion); + struct wlr_event_touch_motion *event = data; + + if(!wlr_seat_touch_get_point(seat->seat, event->touch_id)) { + return; + } + + double lx, ly; + wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, event->x, + event->y, &lx, &ly); + + double sx, sy; + struct wlr_surface *surface; + struct cg_view *view = + desktop_view_at(seat->server, lx, ly, &surface, &sx, &sy); + + if(view) { + wlr_seat_touch_point_focus(seat->seat, surface, event->time_msec, + event->touch_id, sx, sy); + wlr_seat_touch_notify_motion(seat->seat, event->time_msec, + event->touch_id, sx, sy); + } else { + wlr_seat_touch_point_clear_focus(seat->seat, event->time_msec, + event->touch_id); + } + + if(event->touch_id == seat->touch_id) { + seat->touch_lx = lx; + seat->touch_ly = ly; + } + + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +handle_cursor_frame(struct wl_listener *listener, void *_data) { + struct cg_seat *seat = wl_container_of(listener, seat, cursor_frame); + + wlr_seat_pointer_notify_frame(seat->seat); + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +handle_cursor_axis(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, cursor_axis); + struct wlr_event_pointer_axis *event = data; + + wlr_seat_pointer_notify_axis(seat->seat, event->time_msec, + event->orientation, event->delta, + event->delta_discrete, event->source); + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +handle_cursor_button(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, cursor_button); + struct wlr_event_pointer_button *event = data; + + wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button, + event->state); + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +process_cursor_motion(struct cg_seat *seat, uint32_t time) { + double sx, sy; + struct wlr_seat *wlr_seat = seat->seat; + struct wlr_surface *surface = NULL; + + struct cg_view *view = desktop_view_at(seat->server, seat->cursor->x, + seat->cursor->y, &surface, &sx, &sy); + + if(!view) { + wlr_seat_pointer_clear_focus(wlr_seat); + } else { + wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); + + bool focus_changed = wlr_seat->pointer_state.focused_surface != surface; + if(!focus_changed && time > 0) { + wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); + } + } + + struct cg_drag_icon *drag_icon; + wl_list_for_each(drag_icon, &seat->drag_icons, link) { + drag_icon_update_position(drag_icon); + } + + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +handle_cursor_motion_absolute(struct wl_listener *listener, void *data) { + struct cg_seat *seat = + wl_container_of(listener, seat, cursor_motion_absolute); + struct wlr_event_pointer_motion_absolute *event = data; + + wlr_cursor_warp_absolute(seat->cursor, event->device, event->x, event->y); + process_cursor_motion(seat, event->time_msec); + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +handle_cursor_motion(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion); + struct wlr_event_pointer_motion *event = data; + + wlr_cursor_move(seat->cursor, event->device, event->delta_x, + event->delta_y); + process_cursor_motion(seat, event->time_msec); + wlr_idle_notify_activity(seat->server->idle, seat->seat); +} + +static void +drag_icon_damage(struct cg_drag_icon *drag_icon) { + struct cg_output *output; + wl_list_for_each(output, &drag_icon->seat->server->outputs, link) { + struct wlr_box *output_layout_box = wlr_output_layout_get_box( + output->server->output_layout, output->wlr_output); + output_damage_surface(output, drag_icon->wlr_drag_icon->surface, + drag_icon->lx - output_layout_box->x, + drag_icon->ly - output_layout_box->y, true); + } +} + +static void +drag_icon_update_position(struct cg_drag_icon *drag_icon) { + struct wlr_drag_icon *wlr_icon = drag_icon->wlr_drag_icon; + struct cg_seat *seat = drag_icon->seat; + struct wlr_touch_point *point; + + drag_icon_damage(drag_icon); + + switch(wlr_icon->drag->grab_type) { + case WLR_DRAG_GRAB_KEYBOARD: + return; + case WLR_DRAG_GRAB_KEYBOARD_POINTER: + drag_icon->lx = seat->cursor->x; + drag_icon->ly = seat->cursor->y; + break; + case WLR_DRAG_GRAB_KEYBOARD_TOUCH: + point = wlr_seat_touch_get_point(seat->seat, wlr_icon->drag->touch_id); + if(!point) { + return; + } + drag_icon->lx = seat->touch_lx; + drag_icon->ly = seat->touch_ly; + break; + } + + drag_icon_damage(drag_icon); +} + +static void +handle_drag_icon_destroy(struct wl_listener *listener, void *_data) { + struct cg_drag_icon *drag_icon = + wl_container_of(listener, drag_icon, destroy); + + wl_list_remove(&drag_icon->link); + wl_list_remove(&drag_icon->destroy.link); + free(drag_icon); +} + +static void +handle_request_start_drag(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, request_start_drag); + struct wlr_seat_request_start_drag_event *event = data; + + if(wlr_seat_validate_pointer_grab_serial(seat->seat, event->origin, + event->serial)) { + wlr_seat_start_pointer_drag(seat->seat, event->drag, event->serial); + return; + } + + struct wlr_touch_point *point; + if(wlr_seat_validate_touch_grab_serial(seat->seat, event->origin, + event->serial, &point)) { + wlr_seat_start_touch_drag(seat->seat, event->drag, event->serial, + point); + return; + } + + // TODO: tablet grabs + wlr_log(WLR_DEBUG, + "Ignoring start_drag request: " + "could not validate pointer/touch serial %" PRIu32, + event->serial); + wlr_data_source_destroy(event->drag->source); +} + +static void +handle_start_drag(struct wl_listener *listener, void *data) { + struct cg_seat *seat = wl_container_of(listener, seat, start_drag); + struct wlr_drag *wlr_drag = data; + struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; + if(wlr_drag_icon == NULL) { + return; + } + + struct cg_drag_icon *drag_icon = calloc(1, sizeof(struct cg_drag_icon)); + if(!drag_icon) { + return; + } + drag_icon->seat = seat; + drag_icon->wlr_drag_icon = wlr_drag_icon; + + drag_icon->destroy.notify = handle_drag_icon_destroy; + wl_signal_add(&wlr_drag_icon->events.destroy, &drag_icon->destroy); + + wl_list_insert(&seat->drag_icons, &drag_icon->link); + + drag_icon_update_position(drag_icon); +} + +static void +handle_destroy(struct wl_listener *listener, void *_data) { + struct cg_seat *seat = wl_container_of(listener, seat, destroy); + wl_list_remove(&seat->destroy.link); + + struct cg_keyboard *keyboard, *keyboard_tmp; + wl_list_for_each_safe(keyboard, keyboard_tmp, &seat->keyboards, link) { + handle_keyboard_destroy(&keyboard->destroy, NULL); + } + struct cg_pointer *pointer, *pointer_tmp; + wl_list_for_each_safe(pointer, pointer_tmp, &seat->pointers, link) { + handle_pointer_destroy(&pointer->destroy, NULL); + } + struct cg_touch *touch, *touch_tmp; + wl_list_for_each_safe(touch, touch_tmp, &seat->touch, link) { + handle_touch_destroy(&touch->destroy, NULL); + } + wl_list_remove(&seat->new_input.link); + + wlr_xcursor_manager_destroy(seat->xcursor_manager); + if(seat->cursor) { + wlr_cursor_destroy(seat->cursor); + } + wl_list_remove(&seat->cursor_motion.link); + wl_list_remove(&seat->cursor_motion_absolute.link); + wl_list_remove(&seat->cursor_button.link); + wl_list_remove(&seat->cursor_axis.link); + wl_list_remove(&seat->cursor_frame.link); + wl_list_remove(&seat->touch_down.link); + wl_list_remove(&seat->touch_up.link); + wl_list_remove(&seat->touch_motion.link); + wl_list_remove(&seat->request_set_cursor.link); + wl_list_remove(&seat->request_set_selection.link); + wl_list_remove(&seat->request_set_primary_selection.link); + free(seat); +} + +struct cg_seat * +seat_create(struct cg_server *server, struct wlr_backend *backend) { + struct cg_seat *seat = calloc(1, sizeof(struct cg_seat)); + if(!seat) { + wlr_log(WLR_ERROR, "Cannot allocate seat"); + return NULL; + } + + seat->seat = wlr_seat_create(server->wl_display, "seat0"); + if(!seat->seat) { + wlr_log(WLR_ERROR, "Cannot allocate seat0"); + free(seat); + return NULL; + } + seat->server = server; + seat->destroy.notify = handle_destroy; + wl_signal_add(&seat->seat->events.destroy, &seat->destroy); + + seat->cursor = wlr_cursor_create(); + if(!seat->cursor) { + wlr_log(WLR_ERROR, "Unable to create cursor"); + wl_list_remove(&seat->destroy.link); + free(seat); + return NULL; + } + wlr_cursor_attach_output_layout(seat->cursor, server->output_layout); + + if(!seat->xcursor_manager) { + seat->xcursor_manager = wlr_xcursor_manager_create(NULL, XCURSOR_SIZE); + if(!seat->xcursor_manager) { + wlr_log(WLR_ERROR, "Cannot create XCursor manager"); + wlr_cursor_destroy(seat->cursor); + wl_list_remove(&seat->destroy.link); + free(seat); + return NULL; + } + } + + seat->cursor_motion.notify = handle_cursor_motion; + wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion); + seat->cursor_motion_absolute.notify = handle_cursor_motion_absolute; + wl_signal_add(&seat->cursor->events.motion_absolute, + &seat->cursor_motion_absolute); + seat->cursor_button.notify = handle_cursor_button; + wl_signal_add(&seat->cursor->events.button, &seat->cursor_button); + seat->cursor_axis.notify = handle_cursor_axis; + wl_signal_add(&seat->cursor->events.axis, &seat->cursor_axis); + seat->cursor_frame.notify = handle_cursor_frame; + wl_signal_add(&seat->cursor->events.frame, &seat->cursor_frame); + + seat->touch_down.notify = handle_touch_down; + wl_signal_add(&seat->cursor->events.touch_down, &seat->touch_down); + seat->touch_up.notify = handle_touch_up; + wl_signal_add(&seat->cursor->events.touch_up, &seat->touch_up); + seat->touch_motion.notify = handle_touch_motion; + wl_signal_add(&seat->cursor->events.touch_motion, &seat->touch_motion); + + seat->request_set_cursor.notify = handle_request_set_cursor; + wl_signal_add(&seat->seat->events.request_set_cursor, + &seat->request_set_cursor); + seat->request_set_selection.notify = handle_request_set_selection; + wl_signal_add(&seat->seat->events.request_set_selection, + &seat->request_set_selection); + seat->request_set_primary_selection.notify = + handle_request_set_primary_selection; + wl_signal_add(&seat->seat->events.request_set_primary_selection, + &seat->request_set_primary_selection); + + wl_list_init(&seat->keyboards); + wl_list_init(&seat->keyboard_groups); + wl_list_init(&seat->pointers); + wl_list_init(&seat->touch); + + seat->new_input.notify = handle_new_input; + wl_signal_add(&backend->events.new_input, &seat->new_input); + + wl_list_init(&seat->drag_icons); + seat->request_start_drag.notify = handle_request_start_drag; + wl_signal_add(&seat->seat->events.request_start_drag, + &seat->request_start_drag); + seat->start_drag.notify = handle_start_drag; + wl_signal_add(&seat->seat->events.start_drag, &seat->start_drag); + + seat->mode = 0; + seat->default_mode = 0; + + return seat; +} + +void +seat_destroy(struct cg_seat *seat) { + if(!seat) { + return; + } + + wl_list_remove(&seat->request_start_drag.link); + wl_list_remove(&seat->start_drag.link); + + // Destroying the wlr seat will trigger the destroy handler on our seat, + // which will in turn free it. + wlr_seat_destroy(seat->seat); +} + +/* Important: this function returns NULL if we are focused on the background */ +struct cg_view * +seat_get_focus(const struct cg_seat *seat) { + return seat->focused_view; +} + +void +seat_set_focus(struct cg_seat *seat, struct cg_view *view) { + struct cg_server *server = seat->server; + struct wlr_seat *wlr_seat = seat->seat; + struct cg_view *prev_view = seat_get_focus(seat); + + /* Focusing the background */ + if(view == NULL) { + server->curr_output->workspaces[server->curr_output->curr_workspace] + ->focused_tile->view = NULL; + seat->focused_view = NULL; + if(prev_view != NULL) { + view_activate(prev_view, false); + } + wlr_seat_keyboard_clear_focus(wlr_seat); + return; + } + +#if CG_HAS_XWAYLAND + if(view->type != CG_XWAYLAND_VIEW || xwayland_view_should_manage(view)) +#endif + { + /* Always resize the view, even if prev_view == view */ + if(!view_is_visible(view)) { + struct cg_workspace *curr_workspace = + server->curr_output + ->workspaces[server->curr_output->curr_workspace]; + view_maximize(view, &curr_workspace->focused_tile->tile); + wl_list_remove(&view->link); + wl_list_insert(&curr_workspace->views, &view->link); + curr_workspace->focused_tile->view = view; + } + } + + if(prev_view == view) { + return; + } + +#if CG_HAS_XWAYLAND + if(view->type == CG_XWAYLAND_VIEW) { + const struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); + if(!wlr_xwayland_or_surface_wants_focus( + xwayland_view->xwayland_surface)) { + return; + } + } +#endif + + if(prev_view != NULL) { + view_activate(prev_view, false); + } + + view_activate(view, true); + char *title = view_get_title(view); + + output_set_window_title(server->curr_output, title); + free(title); + + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(wlr_seat); + if(keyboard) { + wlr_seat_keyboard_notify_enter( + wlr_seat, view->wlr_surface, keyboard->keycodes, + keyboard->num_keycodes, &keyboard->modifiers); + } else { + wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, NULL, 0, + NULL); + } + + seat->focused_view = view; + + process_cursor_motion(seat, -1); +} diff --git a/seat.h b/seat.h new file mode 100644 index 0000000..7d1d7c7 --- /dev/null +++ b/seat.h @@ -0,0 +1,115 @@ +#ifndef CG_SEAT_H +#define CG_SEAT_H + +#include + +struct cg_server; +struct cg_view; +struct wlr_cursor; +struct wlr_input_device; +struct wlr_seat; +struct wlr_xcursor_manager; +struct wlr_backend; + +#define DEFAULT_XCURSOR "left_ptr" +#define XCURSOR_SIZE 24 + +struct cg_keyboard_group { + struct cg_keyboard *keyboard; + + struct wlr_keyboard_group *wlr_group; + struct cg_seat *seat; + struct wl_listener key; + struct wl_listener modifiers; + struct wl_list link; +}; + +struct cg_seat { + struct wlr_seat *seat; + struct cg_server *server; + struct wl_listener destroy; + + struct wl_list keyboards; + struct wl_list keyboard_groups; + struct wl_list pointers; + struct wl_list touch; + struct wl_listener new_input; + + struct wlr_cursor *cursor; + struct wlr_xcursor_manager *xcursor_manager; + struct wl_listener cursor_motion; + struct wl_listener cursor_motion_absolute; + struct wl_listener cursor_button; + struct wl_listener cursor_axis; + struct wl_listener cursor_frame; + + int32_t touch_id; + double touch_lx; + double touch_ly; + struct wl_listener touch_down; + struct wl_listener touch_up; + struct wl_listener touch_motion; + + struct wl_list drag_icons; + struct wl_listener request_start_drag; + struct wl_listener start_drag; + + struct wl_listener request_set_cursor; + struct wl_listener request_set_selection; + struct wl_listener request_set_primary_selection; + + uint16_t mode; + uint16_t default_mode; + + struct wl_shm *shm; // Shared memory + + struct cg_view *focused_view; +}; + +struct cg_keyboard { + struct wl_list link; // seat::keyboards + struct cg_seat *seat; + struct wlr_input_device *device; + + struct wl_listener destroy; + struct keybinding **repeat_keybinding; + struct wl_event_source *key_repeat_timer; +}; + +struct cg_pointer { + struct wl_list link; // seat::pointers + struct cg_seat *seat; + struct wlr_input_device *device; + + struct wl_listener destroy; +}; + +struct cg_touch { + struct wl_list link; // seat::touch + struct cg_seat *seat; + struct wlr_input_device *device; + + struct wl_listener destroy; +}; + +struct cg_drag_icon { + struct wl_list link; // seat::drag_icons + struct cg_seat *seat; + struct wlr_drag_icon *wlr_drag_icon; + + /* The drag icon has a position in layout coordinates. */ + double lx, ly; + + struct wl_listener destroy; +}; + +struct cg_seat * +seat_create(struct cg_server *server, struct wlr_backend *backend); +void +seat_destroy(struct cg_seat *seat); +struct cg_view * +seat_get_focus(const struct cg_seat *seat); +void +seat_set_focus(struct cg_seat *seat, struct cg_view *view); + +#endif diff --git a/server.c b/server.c new file mode 100644 index 0000000..bfb59a7 --- /dev/null +++ b/server.c @@ -0,0 +1,49 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2019 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include + +#include "output.h" +#include "seat.h" +#include "server.h" +#include "view.h" +#include "workspace.h" +#include "xdg_shell.h" +#if CG_HAS_XWAYLAND +#include "xwayland.h" +#endif + +void +display_terminate(struct cg_server *server) { + if(server == NULL) { + return; + } + server->running = false; + wl_display_terminate(server->wl_display); +} + +/* Returns the index of a mode given its name or "-1" if the mode is not found. + */ +int +get_mode_index_from_name(char *const *modes, const char *mode_name) { + for(int i = 0; modes[i] != NULL; ++i) { + if(strcmp(modes[i], mode_name) == 0) { + return i; + } + } + return -1; +} diff --git a/server.h b/server.h new file mode 100644 index 0000000..363ef34 --- /dev/null +++ b/server.h @@ -0,0 +1,56 @@ +#ifndef CG_SERVER_H +#define CG_SERVER_H + +#include "config.h" + +#include +#include + +struct cg_seat; +struct cg_output; +struct keybinding_list; +struct wlr_output_layout; +struct wlr_idle_inhibit_manager_v1; + +struct cg_server { + struct wl_display *wl_display; + struct wl_event_loop *event_loop; + + struct cg_seat *seat; + struct wlr_backend* backend; + struct wlr_idle *idle; + struct wlr_idle_inhibit_manager_v1 *idle_inhibit_v1; + struct wl_listener new_idle_inhibitor_v1; + struct wl_list inhibitors; + + struct wlr_output_layout *output_layout; + struct wl_list outputs; + struct cg_output *curr_output; + struct wl_listener new_output; + + struct wl_listener xdg_toplevel_decoration; + struct wl_listener new_xdg_shell_surface; +#if CG_HAS_XWAYLAND + struct wl_listener new_xwayland_surface; +#endif + + struct keybinding_list *keybindings; + + enum wl_output_transform output_transform; + + bool running; + char **modes; + uint16_t nws; + uint16_t message_timeout; + float *bg_color; +#ifdef DEBUG + bool debug_damage_tracking; +#endif +}; + +void +display_terminate(struct cg_server *server); +int +get_mode_index_from_name(char *const *modes, const char *mode_name); + +#endif diff --git a/util.c b/util.c new file mode 100644 index 0000000..a877092 --- /dev/null +++ b/util.c @@ -0,0 +1,35 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2019 The Sway authors + * + * See the LICENSE file accompanying this file. + */ + +#include + +#include "util.h" + +int +scale_length(int length, int offset, double scale) { + /** + * One does not simply multiply the width by the scale. We allow fractional + * scaling, which means the resulting scaled width might be a decimal. + * So we round it. + * + * But even this can produce undesirable results depending on the X or Y + * offset of the box. For example, with a scale of 1.5, a box with + * width=1 should not scale to 2px if its X coordinate is 1, because the + * X coordinate would have scaled to 2px. + */ + return (int)(round((offset + length) * scale) - round(offset * scale)); +} + +void +scale_box(struct wlr_box *box, double scale) { + box->width = scale_length(box->width, box->x, scale); + box->height = scale_length(box->height, box->y, scale); + box->x = (int)round(box->x * scale); + box->y = (int)round(box->y * scale); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..8ddc7be --- /dev/null +++ b/util.h @@ -0,0 +1,13 @@ +#ifndef CG_UTIL_H +#define CG_UTIL_H + +struct wlr_box; + +/** Apply scale to a width or height. */ +int +scale_length(int length, int offset, double scale); + +void +scale_box(struct wlr_box *box, double scale); + +#endif diff --git a/view.c b/view.c new file mode 100644 index 0000000..f849761 --- /dev/null +++ b/view.c @@ -0,0 +1,320 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2020 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "output.h" +#include "seat.h" +#include "server.h" +#include "workspace.h" +#include "xdg_shell.h" +#include "view.h" +#if CG_HAS_XWAYLAND +#include "xwayland.h" +#endif + +struct cg_view* +view_get_prev_view(struct cg_view *view) { + struct cg_view *prev = NULL; + + struct cg_view *it_view; + wl_list_for_each(it_view, &view->workspace->views, link) { + if(!view_is_visible(it_view) && view != it_view) { + prev = it_view; + break; + } + } + + return prev; +} + +static void +view_child_handle_commit(struct wl_listener *listener, void *_data) { + struct cg_view_child *child = wl_container_of(listener, child, commit); + view_damage_part(child->view); +} + +static void +subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface); + +static void +view_child_handle_new_subsurface(struct wl_listener *listener, void *data) { + struct cg_view_child *child = + wl_container_of(listener, child, new_subsurface); + struct wlr_subsurface *wlr_subsurface = data; + subsurface_create(child->view, wlr_subsurface); +} + +void +view_child_finish(struct cg_view_child *child) { + if(!child) { + return; + } + + view_damage_whole(child->view); + + wl_list_remove(&child->link); + wl_list_remove(&child->commit.link); + wl_list_remove(&child->new_subsurface.link); +} + +void +view_child_init(struct cg_view_child *child, struct cg_view *view, + struct wlr_surface *wlr_surface) { + child->view = view; + child->wlr_surface = wlr_surface; + + child->commit.notify = view_child_handle_commit; + wl_signal_add(&wlr_surface->events.commit, &child->commit); + child->new_subsurface.notify = view_child_handle_new_subsurface; + wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface); + + wl_list_insert(&view->children, &child->link); +} + +static void +subsurface_destroy(struct cg_view_child *child) { + if(!child) { + return; + } + + struct cg_subsurface *subsurface = (struct cg_subsurface *)child; + wl_list_remove(&subsurface->destroy.link); + view_child_finish(&subsurface->view_child); + free(subsurface); +} + +static void +subsurface_handle_destroy(struct wl_listener *listener, void *_data) { + struct cg_subsurface *subsurface = + wl_container_of(listener, subsurface, destroy); + struct cg_view_child *view_child = (struct cg_view_child *)subsurface; + subsurface_destroy(view_child); +} + +static void +subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface) { + struct cg_subsurface *subsurface = calloc(1, sizeof(struct cg_subsurface)); + if(!subsurface) { + return; + } + + view_child_init(&subsurface->view_child, view, wlr_subsurface->surface); + subsurface->view_child.destroy = subsurface_destroy; + subsurface->wlr_subsurface = wlr_subsurface; + + subsurface->destroy.notify = subsurface_handle_destroy; + wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); +} + +static void +handle_new_subsurface(struct wl_listener *listener, void *data) { + struct cg_view *view = wl_container_of(listener, view, new_subsurface); + struct wlr_subsurface *wlr_subsurface = data; + subsurface_create(view, wlr_subsurface); +} + +char * +view_get_title(const struct cg_view *view) { + const char *title = view->impl->get_title(view); + if(!title) { + return NULL; + } + return strndup(title, strlen(title)); +} + +bool +view_is_primary(const struct cg_view *view) { + return view->impl->is_primary(view); +} + +struct cg_tile * +view_get_tile(const struct cg_view *view) { + + bool first = true; + for(struct cg_tile *tile = view->workspace->focused_tile; + first || view->workspace->focused_tile != tile; tile = tile->next) { + first = false; + if(tile->view == view) { + return tile; + } + } + return NULL; +} + +bool +view_is_visible(const struct cg_view *view) { +#if CG_HAS_XWAYLAND + if(view->type == CG_XWAYLAND_VIEW && !xwayland_view_should_manage(view)) { + return true; + } +#endif + return view_get_tile(view) != NULL; +} + +void +view_damage(struct cg_view *view, bool whole) { + output_damage_surface(view->workspace->output, view->wlr_surface, view->ox, + view->oy, whole); +} + +void +view_damage_part(struct cg_view *view) { + view_damage(view, false); +} + +void +view_damage_whole(struct cg_view *view) { + view_damage(view, true); +} + +void +view_activate(struct cg_view *view, bool activate) { + if(view != NULL) { + view->impl->activate(view, activate); + } +} + +void +view_maximize(struct cg_view *view, const struct wlr_box *tile_box) { + view->ox = tile_box->x; + view->oy = tile_box->y; + view->impl->maximize(view, tile_box->width, tile_box->height); +} + +void +view_position(struct cg_view *view) { + struct wlr_box *tile_workspace_box = + &view->workspace->output + ->workspaces[view->workspace->output->curr_workspace] + ->focused_tile->tile; + + view_maximize(view, tile_workspace_box); +} + +void +view_for_each_surface(struct cg_view *view, + wlr_surface_iterator_func_t iterator, void *data) { + view->impl->for_each_surface(view, iterator, data); +} + +void +view_for_each_popup(struct cg_view *view, wlr_surface_iterator_func_t iterator, + void *data) { + if(!view->impl->for_each_popup) { + return; + } + view->impl->for_each_popup(view, iterator, data); +} + +void +view_unmap(struct cg_view *view) { +#if CG_HAS_XWAYLAND + if((view->type != CG_XWAYLAND_VIEW || xwayland_view_should_manage(view))) +#endif + { + if(view_is_visible(view)) { + struct cg_view* prev = view_get_prev_view(view); + struct cg_tile* view_tile = view_get_tile(view); + wlr_output_damage_add_box(view_tile->workspace->output->damage, &view_tile->tile); + if(seat_get_focus(view->workspace->server->seat) == view) { + seat_set_focus(view->workspace->server->seat, prev); + } else { + view_tile->view = prev; + if(prev != NULL) { + view_maximize(prev, &view_tile->tile); + } + } + } + } +#if CG_HAS_XWAYLAND + else { + view_damage_whole(view); + } +#endif + + wl_list_remove(&view->link); + + wl_list_remove(&view->new_subsurface.link); + + struct cg_view_child *child, *tmp; + wl_list_for_each_safe(child, tmp, &view->children, link) { + child->destroy(child); + } + + view->wlr_surface = NULL; +} + +void +view_map(struct cg_view *view, struct wlr_surface *surface) { + struct cg_output *output = view->workspace->output; + view->wlr_surface = surface; + + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &view->wlr_surface->subsurfaces, parent_link) { + subsurface_create(view, subsurface); + } + + view->new_subsurface.notify = handle_new_subsurface; + wl_signal_add(&view->wlr_surface->events.new_subsurface, + &view->new_subsurface); + +#if CG_HAS_XWAYLAND + /* We shouldn't position override-redirect windows. They set + their own (x,y) coordinates in handle_wayland_surface_map. */ + if(view->type == CG_XWAYLAND_VIEW && !xwayland_view_should_manage(view)) { + wl_list_insert( + &output->workspaces[output->curr_workspace]->unmanaged_views, + &view->link); + } else +#endif + { + view_position(view); + wl_list_insert(&output->workspaces[output->curr_workspace]->views, + &view->link); + } + seat_set_focus(output->server->seat, view); +} + +void +view_destroy(struct cg_view *view) { + struct cg_output* curr_output = view->workspace->server->curr_output; + if(view->wlr_surface != NULL) { + view_unmap(view); + } + + view->impl->destroy(view); + view_activate(curr_output->workspaces[curr_output->curr_workspace]->focused_tile->view, true); +} + +void +view_init(struct cg_view *view, struct cg_workspace *ws, enum cg_view_type type, + const struct cg_view_impl *impl) { + view->workspace = ws; + view->type = type; + view->impl = impl; + + wl_list_init(&view->children); +} + +struct wlr_surface * +view_wlr_surface_at(const struct cg_view *view, double sx, double sy, + double *sub_x, double *sub_y) { + return view->impl->wlr_surface_at(view, sx, sy, sub_x, sub_y); +} diff --git a/view.h b/view.h new file mode 100644 index 0000000..150e79b --- /dev/null +++ b/view.h @@ -0,0 +1,115 @@ +#ifndef CG_VIEW_H +#define CG_VIEW_H + +#include "config.h" + +#include +#include +#include + +struct cg_server; +struct wlr_box; +struct wlr_surface; + +enum cg_view_type { + CG_XDG_SHELL_VIEW, +#if CG_HAS_XWAYLAND + CG_XWAYLAND_VIEW, +#endif +}; + +struct cg_view { + struct cg_workspace *workspace; + struct wl_list link; // server::views + struct wl_list children; // cg_view_child::link + struct wlr_surface *wlr_surface; + + /* The view has a position in output coordinates. */ + int ox, oy; + + enum cg_view_type type; + const struct cg_view_impl *impl; + + struct wl_listener new_subsurface; +}; + +struct cg_view_impl { + char *(*get_title)(const struct cg_view *view); + void (*get_geometry)(const struct cg_view *view, int *width_out, + int *height_out); + bool (*is_primary)(const struct cg_view *view); + void (*activate)(struct cg_view *view, bool activate); + void (*maximize)(struct cg_view *view, int width, int height); + void (*destroy)(struct cg_view *view); + void (*for_each_surface)(struct cg_view *view, + wlr_surface_iterator_func_t iterator, void *data); + void (*for_each_popup)(struct cg_view *view, + wlr_surface_iterator_func_t iterator, void *data); + struct wlr_surface *(*wlr_surface_at)(const struct cg_view *view, double sx, + double sy, double *sub_x, + double *sub_y); +}; + +struct cg_view_child { + struct cg_view *view; + struct wlr_surface *wlr_surface; + struct wl_list link; + + struct wl_listener commit; + struct wl_listener new_subsurface; + + void (*destroy)(struct cg_view_child *child); +}; + +struct cg_subsurface { + struct cg_view_child view_child; + struct wlr_subsurface *wlr_subsurface; + + struct wl_listener destroy; +}; + +char * +view_get_title(const struct cg_view *view); +struct cg_tile * +view_get_tile(const struct cg_view *view); +bool +view_is_primary(const struct cg_view *view); +bool +view_is_visible(const struct cg_view *view); +void +view_damage_part(struct cg_view *view); +void +view_damage_whole(struct cg_view *view); +void +view_activate(struct cg_view *view, bool activate); +void +view_position(struct cg_view *view); +void +view_for_each_surface(struct cg_view *view, + wlr_surface_iterator_func_t iterator, void *data); +void +view_for_each_popup(struct cg_view *view, wlr_surface_iterator_func_t iterator, + void *data); +void +view_unmap(struct cg_view *view); +void +view_maximize(struct cg_view *view, const struct wlr_box *tile_box); +void +view_map(struct cg_view *view, struct wlr_surface *surface); +void +view_destroy(struct cg_view *view); +void +view_init(struct cg_view *view, struct cg_workspace *ws, enum cg_view_type type, + const struct cg_view_impl *impl); + +struct wlr_surface * +view_wlr_surface_at(const struct cg_view *view, double sx, double sy, + double *sub_x, double *sub_y); + +void +view_child_finish(struct cg_view_child *child); +void +view_child_init(struct cg_view_child *child, struct cg_view *view, + struct wlr_surface *wlr_surface); + +#endif diff --git a/workspace.c b/workspace.c new file mode 100644 index 0000000..8ed010e --- /dev/null +++ b/workspace.c @@ -0,0 +1,70 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2019 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include + +#include "message.h" +#include "output.h" +#include "server.h" +#include "view.h" +#include "workspace.h" + +void +full_screen_workspace_tiles(const struct cg_output *output, + struct cg_workspace *workspace) { + workspace->server = output->server; + workspace->focused_tile = calloc(1, sizeof(struct cg_tile)); + workspace->focused_tile->workspace = workspace; + workspace->focused_tile->next = workspace->focused_tile; + workspace->focused_tile->prev = workspace->focused_tile; + workspace->focused_tile->tile.x = 0; + workspace->focused_tile->tile.y = 0; + struct wlr_box *output_box = wlr_output_layout_get_box( + output->server->output_layout, output->wlr_output); + workspace->focused_tile->tile.width = output_box->width; + workspace->focused_tile->tile.height = output_box->height; + workspace->focused_tile->view = NULL; +} + +struct cg_workspace * +full_screen_workspace(struct cg_output *output) { + struct cg_workspace *workspace = calloc(1, sizeof(struct cg_workspace)); + full_screen_workspace_tiles(output, workspace); + workspace->output = output; + return workspace; +} + +void +workspace_focus_tile(struct cg_workspace *ws, struct cg_tile *tile) { + ws->focused_tile = tile; + struct wlr_box *box = malloc(sizeof(struct wlr_box)); + box->x = tile->tile.x + tile->tile.width / 2; + box->y = tile->tile.y + tile->tile.height / 2; + message_printf_pos(ws->output, box, CG_MESSAGE_CENTER, "Current frame"); +} + +void +workspace_free_tiles(struct cg_workspace *workspace) { + workspace->focused_tile->prev->next = NULL; + while(workspace->focused_tile != NULL) { + struct cg_tile *next = workspace->focused_tile->next; + free(workspace->focused_tile); + workspace->focused_tile = next; + } +} + +void +workspace_free(struct cg_workspace *workspace) { + workspace_free_tiles(workspace); + free(workspace); +} diff --git a/workspace.h b/workspace.h new file mode 100644 index 0000000..069d036 --- /dev/null +++ b/workspace.h @@ -0,0 +1,38 @@ +#ifndef CG_WORKSPACE_H +#define CG_WORKSPACE_H + +#include + +struct cg_output; +struct cg_server; + +struct cg_tile { + struct cg_workspace *workspace; + struct wlr_box tile; + struct cg_view *view; + struct cg_tile *next; + struct cg_tile *prev; +}; + +struct cg_workspace { + struct cg_server *server; + struct wl_list views; + struct wl_list unmanaged_views; + struct cg_output *output; + + struct cg_tile *focused_tile; +}; + +struct cg_workspace * +full_screen_workspace(struct cg_output *output); +void +full_screen_workspace_tiles(const struct cg_output *output, + struct cg_workspace *workspace); +void +workspace_free_tiles(struct cg_workspace *workspace); +void +workspace_free(struct cg_workspace *workspace); +void +workspace_focus_tile(struct cg_workspace *ws, struct cg_tile *tile); + +#endif diff --git a/xdg_shell.c b/xdg_shell.c new file mode 100644 index 0000000..b420795 --- /dev/null +++ b/xdg_shell.c @@ -0,0 +1,355 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2019 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "output.h" +#include "server.h" +#include "view.h" +#include "workspace.h" +#include "xdg_shell.h" + +static void +xdg_decoration_handle_destroy(struct wl_listener *listener, void *_data) { + struct cg_xdg_decoration *xdg_decoration = + wl_container_of(listener, xdg_decoration, destroy); + + wl_list_remove(&xdg_decoration->destroy.link); + wl_list_remove(&xdg_decoration->request_mode.link); + free(xdg_decoration); +} + +static void +xdg_decoration_handle_request_mode(struct wl_listener *listener, void *_data) { + struct cg_xdg_decoration *xdg_decoration = + wl_container_of(listener, xdg_decoration, request_mode); + enum wlr_xdg_toplevel_decoration_v1_mode mode; + + mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + wlr_xdg_toplevel_decoration_v1_set_mode(xdg_decoration->wlr_decoration, + mode); +} + +static void +xdg_popup_destroy(struct cg_view_child *child) { + if(!child) { + return; + } + + struct cg_xdg_popup *popup = (struct cg_xdg_popup *)child; + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->map.link); + wl_list_remove(&popup->unmap.link); + wl_list_remove(&popup->new_popup.link); + view_child_finish(&popup->view_child); + free(popup); +} + +static void +handle_xdg_popup_map(struct wl_listener *listener, void *_data) { + struct cg_xdg_popup *popup = wl_container_of(listener, popup, map); + view_damage_whole(popup->view_child.view); +} + +static void +handle_xdg_popup_unmap(struct wl_listener *listener, void *_data) { + struct cg_xdg_popup *popup = wl_container_of(listener, popup, unmap); + view_damage_whole(popup->view_child.view); +} + +static void +handle_xdg_popup_destroy(struct wl_listener *listener, void *_data) { + struct cg_xdg_popup *popup = wl_container_of(listener, popup, destroy); + struct cg_view_child *view_child = (struct cg_view_child *)popup; + xdg_popup_destroy(view_child); +} + +static void +xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup); + +static void +popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) { + struct cg_xdg_popup *popup = wl_container_of(listener, popup, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + xdg_popup_create(popup->view_child.view, wlr_popup); +} + +static void +popup_unconstrain(struct cg_xdg_popup *popup) { + struct cg_view *view = popup->view_child.view; + struct wlr_box *popup_box = &popup->wlr_popup->geometry; + + struct wlr_output_layout *output_layout = + view->workspace->output->server->output_layout; + struct wlr_box *view_output_box = wlr_output_layout_get_box( + output_layout, view->workspace->output->wlr_output); + struct wlr_output *wlr_output = wlr_output_layout_output_at( + output_layout, view_output_box->x + view->ox + popup_box->x, + view_output_box->y + view->oy + popup_box->y); + struct wlr_box *output_box = + wlr_output_layout_get_box(output_layout, wlr_output); + + struct wlr_box output_toplevel_box = {.x = -view->ox, + .y = -view->oy, + .width = output_box->width, + .height = output_box->height}; + + wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box); +} + +static void +xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup) { + struct cg_xdg_popup *popup = calloc(1, sizeof(struct cg_xdg_popup)); + if(!popup) { + return; + } + + popup->wlr_popup = wlr_popup; + view_child_init(&popup->view_child, view, wlr_popup->base->surface); + popup->view_child.destroy = xdg_popup_destroy; + popup->destroy.notify = handle_xdg_popup_destroy; + wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); + popup->map.notify = handle_xdg_popup_map; + wl_signal_add(&wlr_popup->base->events.map, &popup->map); + popup->unmap.notify = handle_xdg_popup_unmap; + wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); + popup->new_popup.notify = popup_handle_new_xdg_popup; + wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); + + popup_unconstrain(popup); +} + +static void +handle_new_xdg_popup(struct wl_listener *listener, void *data) { + struct cg_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + xdg_popup_create(&xdg_shell_view->view, wlr_popup); +} + +static struct cg_xdg_shell_view * +xdg_shell_view_from_view(struct cg_view *view) { + return (struct cg_xdg_shell_view *)view; +} + +static const struct cg_xdg_shell_view * +xdg_shell_view_from_const_view(const struct cg_view *view) { + return (const struct cg_xdg_shell_view *)view; +} + +static char * +get_title(const struct cg_view *view) { + const struct cg_xdg_shell_view *xdg_shell_view = + xdg_shell_view_from_const_view(view); + return xdg_shell_view->xdg_surface->toplevel->title; +} + +static void +get_geometry(const struct cg_view *view, int *width_out, int *height_out) { + const struct cg_xdg_shell_view *xdg_shell_view = + xdg_shell_view_from_const_view(view); + struct wlr_box geom; + + wlr_xdg_surface_get_geometry(xdg_shell_view->xdg_surface, &geom); + *width_out = geom.width; + *height_out = geom.height; +} + +static bool +is_primary(const struct cg_view *view) { + const struct cg_xdg_shell_view *xdg_shell_view = + xdg_shell_view_from_const_view(view); + struct wlr_xdg_surface *parent = + xdg_shell_view->xdg_surface->toplevel->parent; + /* FIXME: role is 0? */ + return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */ +} + +static void +activate(struct cg_view *view, bool activate) { + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + wlr_xdg_toplevel_set_activated(xdg_shell_view->xdg_surface, activate); +} + +static void +maximize(struct cg_view *view, int width, int height) { + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_surface, width, height); + wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_surface, true); +} + +static void +destroy(struct cg_view *view) { + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + free(xdg_shell_view); +} + +static void +for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, + void *data) { + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + wlr_xdg_surface_for_each_surface(xdg_shell_view->xdg_surface, iterator, + data); +} + +static void +for_each_popup(struct cg_view *view, wlr_surface_iterator_func_t iterator, + void *data) { + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + wlr_xdg_surface_for_each_popup(xdg_shell_view->xdg_surface, iterator, data); +} + +static struct wlr_surface * +wlr_surface_at(const struct cg_view *view, double sx, double sy, double *sub_x, + double *sub_y) { + const struct cg_xdg_shell_view *xdg_shell_view = + xdg_shell_view_from_const_view(view); + return wlr_xdg_surface_surface_at(xdg_shell_view->xdg_surface, sx, sy, + sub_x, sub_y); +} + +static void +handle_xdg_shell_surface_request_fullscreen(struct wl_listener *listener, + void *data) { + struct cg_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, request_fullscreen); + struct wlr_xdg_toplevel_set_fullscreen_event *event = data; + wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_surface, + event->fullscreen); +} + +static void +handle_xdg_shell_surface_commit(struct wl_listener *listener, void *_data) { + struct cg_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, commit); + struct cg_view *view = &xdg_shell_view->view; + view_damage_part(view); +} + +static void +handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *_data) { + struct cg_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, unmap); + struct cg_view *view = &xdg_shell_view->view; + + wl_list_remove(&xdg_shell_view->commit.link); + + view_unmap(view); +} + +static void +handle_xdg_shell_surface_map(struct wl_listener *listener, void *_data) { + struct cg_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, map); + struct cg_view *view = &xdg_shell_view->view; + + xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit; + wl_signal_add(&xdg_shell_view->xdg_surface->surface->events.commit, + &xdg_shell_view->commit); + + view_map(view, xdg_shell_view->xdg_surface->surface); + view_damage_whole(view); +} + +static void +handle_xdg_shell_surface_destroy(struct wl_listener *listener, void *_data) { + struct cg_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, destroy); + struct cg_view *view = &xdg_shell_view->view; + + wl_list_remove(&xdg_shell_view->map.link); + wl_list_remove(&xdg_shell_view->unmap.link); + wl_list_remove(&xdg_shell_view->destroy.link); + wl_list_remove(&xdg_shell_view->request_fullscreen.link); + wl_list_remove(&xdg_shell_view->new_popup.link); + xdg_shell_view->xdg_surface = NULL; + + view_destroy(view); +} + +static const struct cg_view_impl xdg_shell_view_impl = { + .get_title = get_title, + .get_geometry = get_geometry, + .is_primary = is_primary, + .activate = activate, + .maximize = maximize, + .destroy = destroy, + .for_each_surface = for_each_surface, + .for_each_popup = for_each_popup, + .wlr_surface_at = wlr_surface_at, +}; + +void +handle_xdg_shell_surface_new(struct wl_listener *listener, void *data) { + struct cg_server *server = + wl_container_of(listener, server, new_xdg_shell_surface); + struct wlr_xdg_surface *xdg_surface = data; + + if(xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + struct cg_xdg_shell_view *xdg_shell_view = + calloc(1, sizeof(struct cg_xdg_shell_view)); + if(!xdg_shell_view) { + wlr_log(WLR_ERROR, "Failed to allocate XDG Shell view"); + return; + } + view_init( + &xdg_shell_view->view, + server->curr_output->workspaces[server->curr_output->curr_workspace], + CG_XDG_SHELL_VIEW, &xdg_shell_view_impl); + + xdg_shell_view->xdg_surface = xdg_surface; + + xdg_shell_view->map.notify = handle_xdg_shell_surface_map; + wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); + xdg_shell_view->unmap.notify = handle_xdg_shell_surface_unmap; + wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap); + xdg_shell_view->destroy.notify = handle_xdg_shell_surface_destroy; + wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy); + xdg_shell_view->request_fullscreen.notify = + handle_xdg_shell_surface_request_fullscreen; + wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, + &xdg_shell_view->request_fullscreen); + xdg_shell_view->new_popup.notify = handle_new_xdg_popup; + wl_signal_add(&xdg_surface->events.new_popup, &xdg_shell_view->new_popup); +} + +void +handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data) { + struct cg_server *server = + wl_container_of(listener, server, xdg_toplevel_decoration); + struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data; + + struct cg_xdg_decoration *xdg_decoration = + calloc(1, sizeof(struct cg_xdg_decoration)); + if(!xdg_decoration) { + return; + } + + xdg_decoration->wlr_decoration = wlr_decoration; + xdg_decoration->server = server; + + xdg_decoration->destroy.notify = xdg_decoration_handle_destroy; + wl_signal_add(&wlr_decoration->events.destroy, &xdg_decoration->destroy); + xdg_decoration->request_mode.notify = xdg_decoration_handle_request_mode; + wl_signal_add(&wlr_decoration->events.request_mode, + &xdg_decoration->request_mode); + + xdg_decoration_handle_request_mode(&xdg_decoration->request_mode, + wlr_decoration); +} diff --git a/xdg_shell.h b/xdg_shell.h new file mode 100644 index 0000000..601134b --- /dev/null +++ b/xdg_shell.h @@ -0,0 +1,41 @@ +#ifndef CG_XDG_SHELL_H +#define CG_XDG_SHELL_H + +#include "view.h" + +struct cg_xdg_shell_view { + struct cg_view view; + struct wlr_xdg_surface *xdg_surface; + + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener map; + struct wl_listener commit; + struct wl_listener request_fullscreen; + struct wl_listener new_popup; +}; + +struct cg_xdg_popup { + struct cg_view_child view_child; + struct wlr_xdg_popup *wlr_popup; + + struct wl_listener destroy; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener new_popup; +}; + +struct cg_xdg_decoration { + struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration; + struct cg_server *server; + struct wl_listener destroy; + struct wl_listener request_mode; +}; + +void +handle_xdg_shell_surface_new(struct wl_listener *listener, void *data); + +void +handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data); + +#endif diff --git a/xwayland.c b/xwayland.c new file mode 100644 index 0000000..c82cbef --- /dev/null +++ b/xwayland.c @@ -0,0 +1,226 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2020 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "output.h" +#include "server.h" +#include "view.h" +#include "workspace.h" +#include "xwayland.h" + +struct cg_xwayland_view * +xwayland_view_from_view(struct cg_view *view) { + return (struct cg_xwayland_view *)view; +} + +static const struct cg_xwayland_view * +xwayland_view_from_const_view(const struct cg_view *view) { + return (const struct cg_xwayland_view *)view; +} + +bool +xwayland_view_should_manage(const struct cg_view *view) { + const struct cg_xwayland_view *xwayland_view = + xwayland_view_from_const_view(view); + struct wlr_xwayland_surface *xwayland_surface = + xwayland_view->xwayland_surface; + return !xwayland_surface->override_redirect; +} + +static char * +get_title(const struct cg_view *view) { + const struct cg_xwayland_view *xwayland_view = + xwayland_view_from_const_view(view); + return xwayland_view->xwayland_surface->title; +} + +static void +get_geometry(const struct cg_view *view, int *width_out, int *height_out) { + const struct cg_xwayland_view *xwayland_view = + xwayland_view_from_const_view(view); + *width_out = xwayland_view->xwayland_surface->width; + *height_out = xwayland_view->xwayland_surface->height; +} + +static bool +is_primary(const struct cg_view *view) { + const struct cg_xwayland_view *xwayland_view = + xwayland_view_from_const_view(view); + struct wlr_xwayland_surface *parent = + xwayland_view->xwayland_surface->parent; + return parent == NULL; +} + +static void +activate(struct cg_view *view, bool activate) { + struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); + wlr_xwayland_surface_activate(xwayland_view->xwayland_surface, activate); +} + +static void +maximize(struct cg_view *view, int width, int height) { + struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); + struct cg_output *output = view->workspace->output; + + struct wlr_box* box = wlr_output_layout_get_box(view->workspace->server->output_layout, view->workspace->output->wlr_output); + struct wlr_xwayland_surface_size_hints *hints = xwayland_view->xwayland_surface->size_hints; + + if(hints != NULL && hints->flags & PMaxSize) { + if(width > hints->max_width) { + wlr_output_damage_add_box(output->damage, &(struct wlr_box){.x=view->ox + box->x, .y = view->oy + box->y, .width = width, .height=height}); + width = hints->max_width; + } + + if(height > hints->max_height) { + wlr_output_damage_add_box(output->damage, &(struct wlr_box){.x=view->ox + box->x, .y = view->oy + box->y, .width = width, .height=height}); + height = hints->max_height; + } + } + + wlr_xwayland_surface_configure( + xwayland_view->xwayland_surface, view->ox + box->x, + view->oy + box->y, width, height); + wlr_xwayland_surface_set_maximized(xwayland_view->xwayland_surface, true); +} + +static void +destroy(struct cg_view *view) { + struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); + free(xwayland_view); +} + +static void +for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, + void *data) { + wlr_surface_for_each_surface(view->wlr_surface, iterator, data); +} + +static struct wlr_surface * +wlr_surface_at(const struct cg_view *view, double sx, double sy, double *sub_x, + double *sub_y) { + return wlr_surface_surface_at(view->wlr_surface, sx, sy, sub_x, sub_y); +} + +static void +handle_xwayland_surface_request_fullscreen(struct wl_listener *listener, + void *data) { + struct cg_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, request_fullscreen); + struct wlr_xwayland_surface *xwayland_surface = + xwayland_view->xwayland_surface; + wlr_xwayland_surface_set_fullscreen(xwayland_view->xwayland_surface, + xwayland_surface->fullscreen); +} + +static void +handle_xwayland_surface_commit(struct wl_listener *listener, void *_data) { + struct cg_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, commit); + struct cg_view *view = &xwayland_view->view; + view_damage_part(view); +} + +static void +handle_xwayland_surface_unmap(struct wl_listener *listener, void *_data) { + struct cg_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, unmap); + struct cg_view *view = &xwayland_view->view; + + wl_list_remove(&xwayland_view->commit.link); + + view_unmap(view); +} + +static void +handle_xwayland_surface_map(struct wl_listener *listener, void *_data) { + struct cg_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, map); + struct cg_view *view = &xwayland_view->view; + + if(!xwayland_view_should_manage(view)) { + view->ox = xwayland_view->xwayland_surface->x; + view->oy = xwayland_view->xwayland_surface->y; + } + + xwayland_view->commit.notify = handle_xwayland_surface_commit; + wl_signal_add(&xwayland_view->xwayland_surface->surface->events.commit, + &xwayland_view->commit); + + view_map(view, xwayland_view->xwayland_surface->surface); + + view_damage_whole(view); +} + +static void +handle_xwayland_surface_destroy(struct wl_listener *listener, void *_data) { + struct cg_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, destroy); + struct cg_view *view = &xwayland_view->view; + + wl_list_remove(&xwayland_view->map.link); + wl_list_remove(&xwayland_view->unmap.link); + wl_list_remove(&xwayland_view->destroy.link); + wl_list_remove(&xwayland_view->request_fullscreen.link); + xwayland_view->xwayland_surface = NULL; + + view_destroy(view); +} + +static const struct cg_view_impl xwayland_view_impl = { + .get_title = get_title, + .get_geometry = get_geometry, + .is_primary = is_primary, + .activate = activate, + .maximize = maximize, + .destroy = destroy, + .for_each_surface = for_each_surface, + /* XWayland doesn't have a separate popup iterator. */ + .for_each_popup = NULL, + .wlr_surface_at = wlr_surface_at, +}; + +void +handle_xwayland_surface_new(struct wl_listener *listener, void *data) { + struct cg_server *server = + wl_container_of(listener, server, new_xwayland_surface); + struct wlr_xwayland_surface *xwayland_surface = data; + + struct cg_xwayland_view *xwayland_view = + calloc(1, sizeof(struct cg_xwayland_view)); + if(!xwayland_view) { + wlr_log(WLR_ERROR, "Failed to allocate XWayland view"); + return; + } + + view_init( + &xwayland_view->view, + server->curr_output->workspaces[server->curr_output->curr_workspace], + CG_XWAYLAND_VIEW, &xwayland_view_impl); + xwayland_view->xwayland_surface = xwayland_surface; + + xwayland_view->map.notify = handle_xwayland_surface_map; + wl_signal_add(&xwayland_surface->events.map, &xwayland_view->map); + xwayland_view->unmap.notify = handle_xwayland_surface_unmap; + wl_signal_add(&xwayland_surface->events.unmap, &xwayland_view->unmap); + xwayland_view->destroy.notify = handle_xwayland_surface_destroy; + wl_signal_add(&xwayland_surface->events.destroy, &xwayland_view->destroy); + xwayland_view->request_fullscreen.notify = + handle_xwayland_surface_request_fullscreen; + wl_signal_add(&xwayland_surface->events.request_fullscreen, + &xwayland_view->request_fullscreen); +} diff --git a/xwayland.h b/xwayland.h new file mode 100644 index 0000000..78317ea --- /dev/null +++ b/xwayland.h @@ -0,0 +1,26 @@ +#ifndef CG_XWAYLAND_H +#define CG_XWAYLAND_H + +#include "view.h" +#include +#include + +struct cg_xwayland_view { + struct cg_view view; + struct wlr_xwayland_surface *xwayland_surface; + + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener map; + struct wl_listener commit; + struct wl_listener request_fullscreen; +}; + +struct cg_xwayland_view * +xwayland_view_from_view(struct cg_view *view); +bool +xwayland_view_should_manage(const struct cg_view *view); +void +handle_xwayland_surface_new(struct wl_listener *listener, void *data); + +#endif