Release 1.0.0

- Add basic tiling compositor functionality
This commit is contained in:
Cagebreak Signing Key 1 2020-02-17 17:28:35 +00:00
commit de6c07ca2a
45 changed files with 6864 additions and 0 deletions

11
.clang-format Normal file
View file

@ -0,0 +1,11 @@
---
BasedOnStyle: LLVM
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
IndentWidth: 4
ObjCBlockIndentWidth: 4
TabWidth: 4
UseTab: ForIndentation
AlwaysBreakAfterReturnType: TopLevel
SpaceBeforeParens: Never
...

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
build

12
Bugs.md Normal file
View file

@ -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

21
LICENSE Normal file
View file

@ -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.

155
README.md Normal file
View file

@ -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)

537
cagebreak.c Normal file
View file

@ -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 <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/types/wlr_server_decoration.h>
#if CG_HAS_XWAYLAND
#include <wlr/types/wlr_xcursor_manager.h>
#endif
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#if CG_HAS_XWAYLAND
#include <wlr/xwayland.h>
#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;
}

8
config.h.in Normal file
View file

@ -0,0 +1,8 @@
#ifndef CG_CONFIG_H
#define CG_CONFIG_H
#mesondefine CG_HAS_XWAYLAND
#mesondefine CG_VERSION
#endif

72
examples/config Normal file
View file

@ -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

11
fuzz/execl_override.c Normal file
View file

@ -0,0 +1,11 @@
/* This file is used by the fuzzer in order to prevent executing shell commands.
*/
#define _GNU_SOURCE
#include <time.h>
struct tm *(*orig_localtime)(const time_t *timep);
int
execl(const char *pathname, const char *arg, ...) {
return 0;
}

86
fuzz/fuzz-parse.c Normal file
View file

@ -0,0 +1,86 @@
#define _POSIX_C_SOURCE 200812L
#define FUZZING
#include "../keybinding.h"
#include "../parse.h"
#include "../seat.h"
#include "../server.h"
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-server-core.h>
#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;
}

32
fuzz/meson.build Normal file
View file

@ -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,
)

65
idle_inhibit_v1.c Normal file
View file

@ -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 <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#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);
}

9
idle_inhibit_v1.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef CG_IDLE_INHIBIT_H
#define CG_IDLE_INHIBIT_H
#include <wayland-server-core.h>
void
handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data);
#endif

818
keybinding.c Normal file
View file

@ -0,0 +1,818 @@
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/util/log.h>
#include <wlr/backend/session.h>
#include <wlr/backend/multi.h>
#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
// a<b
bool
is_between_strict(int a, int b, int x) {
return a < x && x < b;
}
bool
resize_allowed(struct cg_tile *tile, 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 true;
} else if(*get_dim(tile) - coord_offset + dim_offset <= 0) {
return false;
}
for(struct cg_tile *it = tile->next; 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 != &current_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 && &current_view->link != &curr_workspace->views) {
wl_list_remove(&current_view->link);
wl_list_insert(curr_workspace->views.prev, &current_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;
}

86
keybinding.h Normal file
View file

@ -0,0 +1,86 @@
#ifndef KEYBINDING_H
#define KEYBINDING_H
#include "config.h"
#include <sys/types.h>
#include <wlr/types/wlr_keyboard.h>
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 */

View file

@ -0,0 +1,65 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
Rw==
=IZJF
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,64 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
MI0SwN7gPGlOudIlzBJwygPPhSnZXtDYJA==
=Dz6g
-----END PGP PUBLIC KEY BLOCK-----

266
man/cagebreak-config.5.md Normal file
View file

@ -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 <key> <keybinding>` is equivalent to
> `definekey root <key> <action>`
**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 <mode> <key> <action>`
**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 <mode>`
**escape**
> Defines the key with which the current mode can be changed to "root".
> `escape <key>` is equivalent to `definekey top <key> switch_mode root`
**exec**
> Executes the supplied shell command using *`sh -c "<command>"`*. Synopsis:
>
>> `exec <command>`
**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 <n>**
> Change to the n-th workspace
**movetoworkspace <n>**
> 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 <n>**
> Switch to tty n
**mode <mode>**
> Enter mode "`<mode>`". After a keybinding is processed, return to default mode
**setmode <mode>**
> Set the default mode to `<mode>`
# 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:
> `<mod>-<key>`
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: <https://github.com/project-repo/cagebreak/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.

90
man/cagebreak.1.md Normal file
View file

@ -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: <https://github.com/project-repo/cagebreak/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.

231
meson.build Normal file
View file

@ -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 <wlr/config.h>', 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))

2
meson_options.txt Normal file
View file

@ -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')

179
message.c Normal file
View file

@ -0,0 +1,179 @@
#include "pango.h"
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <stdlib.h>
#include <unistd.h>
#include <wlr/backend.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/types/wlr_output_layout.h>
#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);
}
}

33
message.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef MESSAGE_H
#define MESSAGE_H
#include <wayland-server-core.h>
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 */

569
output.c Normal file
View file

@ -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 <wlr/config.h>
#include <stdlib.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/wayland.h>
#if WLR_HAS_X11_BACKEND
#include <wlr/backend/x11.h>
#endif
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_matrix.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#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
}
}

57
output.h Normal file
View file

@ -0,0 +1,57 @@
#ifndef CG_OUTPUT_H
#define CG_OUTPUT_H
#include <wayland-server-core.h>
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

94
pango.c Normal file
View file

@ -0,0 +1,94 @@
#include "cairo.h"
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wlr/util/log.h>
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);
}

13
pango.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef _SWAY_PANGO_H
#define _SWAY_PANGO_H
#include <cairo/cairo.h>
#include <stdbool.h>
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

416
parse.c Normal file
View file

@ -0,0 +1,416 @@
#define _POSIX_C_SOURCE 200812L
#include <stdlib.h>
#include <string.h>
#include <wlr/util/log.h>
#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;
}

10
parse.h Normal file
View file

@ -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 */

254
render.c Normal file
View file

@ -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 <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_matrix.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#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");
}
}

9
render.h Normal file
View file

@ -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

1129
seat.c Normal file

File diff suppressed because it is too large Load diff

115
seat.h Normal file
View file

@ -0,0 +1,115 @@
#ifndef CG_SEAT_H
#define CG_SEAT_H
#include <wayland-server-core.h>
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

49
server.c Normal file
View file

@ -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 <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_surface.h>
#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;
}

56
server.h Normal file
View file

@ -0,0 +1,56 @@
#ifndef CG_SERVER_H
#define CG_SERVER_H
#include "config.h"
#include <wayland-server-core.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
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

35
util.c Normal file
View file

@ -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 <wlr/types/wlr_box.h>
#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);
}

13
util.h Normal file
View file

@ -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

320
view.c Normal file
View file

@ -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 <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_output_damage.h>
#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);
}

115
view.h Normal file
View file

@ -0,0 +1,115 @@
#ifndef CG_VIEW_H
#define CG_VIEW_H
#include "config.h"
#include <stdbool.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_surface.h>
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

70
workspace.c Normal file
View file

@ -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 <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_output_layout.h>
#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);
}

38
workspace.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef CG_WORKSPACE_H
#define CG_WORKSPACE_H
#include <wlr/types/wlr_box.h>
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

355
xdg_shell.c Normal file
View file

@ -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 <stdbool.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#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);
}

41
xdg_shell.h Normal file
View file

@ -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

226
xwayland.c Normal file
View file

@ -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 <stdbool.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/util/log.h>
#include <wlr/xwayland.h>
#include <xcb/xproto.h>
#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);
}

26
xwayland.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef CG_XWAYLAND_H
#define CG_XWAYLAND_H
#include "view.h"
#include <wayland-server-core.h>
#include <wlr/xwayland.h>
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