commit de6c07ca2a6a307bf66e9068fb54ad4e2fa97d10 Author: Cagebreak Signing Key 1 Date: Mon Feb 17 17:28:35 2020 +0000 Release 1.0.0 - Add basic tiling compositor functionality 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