Merge 1.0.6 for compatibility testing

This commit is contained in:
Cagebreak Signing Key 1 2020-03-29 11:47:44 +00:00
commit 4bdc2640a7
8 changed files with 194 additions and 236 deletions

29
Bugs.md
View file

@ -26,3 +26,32 @@ Steps to reproduce:
* enter text into the "Tags" field
* if autocompletion of tags pops up, the keyboard will not enter further
keystrokes into the text field
### Issue 2
* Github issue number: N/A
* Fixed: 1.0.4
This issue fixes wrong consumption of modifier key on some keyboards.
### Issue 3
* github issue number: N/A
* Fixed: 1.0.5
This issue is a bug causing crashes in cagebreak under specific circumstances.
Steps to reproduce:
* start cagebreak
* vertical split
* open a terminal on the left pane
* open firefox on the right pane
* focus the left pane
* close the terminal
* close firefox using the mouse while keeping focus on the left pane
* this causes a crash with the following error message:
```
(EE) failed to read Wayland events: Broken pipe
```

View file

@ -74,13 +74,19 @@ 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
git checkout development
git pull origin development
git checkout master
git merge --squash development
git tag -u keyid version HEAD
git tag -v version
git push --tags origin master
```
and a log message roughly describing the features is added in the commit.
In the past, our git history did not always reflect this scheme.
### Releases
Release checklist
@ -99,6 +105,8 @@ Release checklist
* [ ] wiki
* [ ] Changelog in README
* [ ] Document fixed bugs in Bugs.md
* [ ] Update hashes of the binary
* [ ] Update signature of the binary
* [ ] Signature
* [ ] Branching Strategy
@ -120,10 +128,36 @@ The full public keys can be found in `keys/` along with any revocation certifica
### 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.
Cagebreak offers reproducible builds given the exact library versions specified
in `meson.build`. Should a version mismatch occur, a warning will be emitted. We have
decided on this compromise to allow flexibility and security. In general we will
adapt the versions to the packages available under archlinux at the time of
release.
#### Reproducible Build Instructions
All hashes and signatures are provided for the following build instructions.
```
meson build -Dxwayland=true --buildtype=release
ninja -C build
```
#### Hashes for Builds
For every release after 1.0.5, hashes will be provided.
1.0.6
* sha 256: 712ae9a8f17a9e589e108f0d503da203cc5eaf1c4a6ca6efb5b4c83b432ce0b8
* sha 512: d574003023a00cfd6623aac986a5a7f397cfd0bc9114017629a8c72731b0df3977c4a31768502dfa8a6607be06930089b2ccf6ffca9b5bcd1096b7ca0aede226
#### GPG Signatures
For every release after 1.0.5, a GPG signature will be provided in `signatures`.
The current signature is called `cagebreak.sig`, whereas all older signatures
will be named after their release version.
### Fuzzing

View file

@ -1,5 +1,5 @@
project('cagebreak', 'c',
version: '1.0.2',
version: '1.0.6',
license: 'MIT',
default_options: [
'c_std=c11',
@ -101,18 +101,10 @@ else
have_xwayland = false
endif
version = '@0@'.format(meson.project_version())
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
if get_option('version_override') != ''
version = '@0@'.format(get_option('version_override'))
else
version = '@0@'.format(meson.project_version())
endif
conf_data = configuration_data()
@ -173,21 +165,49 @@ endforeach
foreach header : cagebreak_header_strings
cagebreak_headers += files(header)
endforeach
cagebreak_dependencies_dict = {
'server_protos': server_protos,
'wayland_server': wayland_server,
'wayland_client': wayland_client,
'wayland_cursor': wayland_cursor,
'wlroots': wlroots,
'xkbcommon': xkbcommon,
'fontconfig': fontconfig,
'pixman': pixman,
'pango': pango,
'cairo': cairo,
'pangocairo': pangocairo,
'math': math
}
cagebreak_dependencies = [
server_protos,
wayland_server,
wayland_client,
wayland_cursor,
wlroots,
xkbcommon,
fontconfig,
pixman,
math,
pango,
cairo,
pangocairo,
]
reproducible_build_versions = {
'server_protos': '1.0.6',
'wayland_server': '1.18.0',
'wayland_client': '1.18.0',
'wayland_cursor': '1.18.0',
'wlroots': '0.10.1',
'xkbcommon': '0.10.0',
'fontconfig': '2.13.91',
'pixman': '0.38.4',
'pango': '1.44.7',
'cairo': '1.17.3',
'pangocairo': '1.44.7',
'math': '-1'
}
cagebreak_dependencies = []
foreach name, dep : cagebreak_dependencies_dict
cagebreak_dependencies += dep
endforeach
foreach name, dep : cagebreak_dependencies_dict
if reproducible_build_versions[name] != '-1' and reproducible_build_versions[name] != dep.version()
warning('The installed version of "' + name + '" on your machine (' + dep.version() + ') differs from the one used to generate the binary specified in the README section "Reproducible Builds" (' + reproducible_build_versions[name] + '). Cagebreak does not guarantee a reproducible build for this configuration.'
)
break
endif
endforeach
executable(
meson.project_name(),

View file

@ -1,2 +1,3 @@
option('xwayland', type: 'boolean', value: 'false', description: 'Enable support for X11 applications')
option('fuzz', type: 'boolean', value: 'false', description: 'Enable building fuzzer targets')
option('version_override', type: 'string', description: 'Set the project version to the string specified. Used for creating hashes for reproducible builds.')

243
seat.c
View file

@ -112,7 +112,7 @@ static void
update_capabilities(const struct cg_seat *seat) {
uint32_t caps = 0;
if(!wl_list_empty(&seat->keyboards)) {
if(!wl_list_empty(&seat->keyboard_groups)) {
caps |= WL_SEAT_CAPABILITY_KEYBOARD;
}
if(!wl_list_empty(&seat->pointers)) {
@ -216,41 +216,41 @@ handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device) {
static int
handle_keyboard_repeat(void *data) {
struct cg_keyboard *keyboard = (struct cg_keyboard *)data;
struct wlr_keyboard *wlr_device = keyboard->device->keyboard;
if(keyboard->repeat_keybinding != NULL) {
struct cg_keyboard_group *cg_group = data;
struct wlr_keyboard *wlr_device =
cg_group->wlr_group->input_device->keyboard;
if(cg_group->repeat_keybinding != NULL) {
if(wlr_device->repeat_info.rate > 0) {
if(wl_event_source_timer_update(
keyboard->key_repeat_timer,
cg_group->key_repeat_timer,
1000 / wlr_device->repeat_info.rate) < 0) {
wlr_log(WLR_DEBUG, "failed to update key repeat timer");
}
}
run_action((*keyboard->repeat_keybinding)->action,
keyboard->seat->server,
(*keyboard->repeat_keybinding)->data);
run_action((*cg_group->repeat_keybinding)->action,
cg_group->seat->server,
(*cg_group->repeat_keybinding)->data);
}
return 0;
}
static void
handle_modifier_event(struct cg_keyboard *keyboard) {
wlr_seat_set_keyboard(keyboard->seat->seat, keyboard->device);
wlr_seat_keyboard_notify_modifiers(keyboard->seat->seat,
&keyboard->device->keyboard->modifiers);
handle_modifier_event(struct wlr_input_device *device, struct cg_seat *seat) {
wlr_seat_set_keyboard(seat->seat, device);
wlr_seat_keyboard_notify_modifiers(seat->seat,
&device->keyboard->modifiers);
wlr_idle_notify_activity(keyboard->seat->server->idle,
keyboard->seat->seat);
wlr_idle_notify_activity(seat->server->idle, seat->seat);
}
void
keyboard_disarm_key_repeat(struct cg_keyboard *keyboard) {
if(!keyboard) {
keyboard_disarm_key_repeat(struct cg_keyboard_group *group) {
if(!group) {
return;
}
keyboard->repeat_keybinding = NULL;
if(wl_event_source_timer_update(keyboard->key_repeat_timer, 0) < 0) {
group->repeat_keybinding = NULL;
if(wl_event_source_timer_update(group->key_repeat_timer, 0) < 0) {
wlr_log(WLR_DEBUG, "failed to disarm key repeat timer");
}
}
@ -258,7 +258,7 @@ keyboard_disarm_key_repeat(struct cg_keyboard *keyboard) {
static bool
handle_command_key_bindings(struct cg_server *server, xkb_keysym_t sym,
uint32_t modifiers, uint32_t mode,
struct cg_keyboard *keyboard) {
struct cg_keyboard_group *group) {
struct keybinding **keybinding = find_keybinding(
server->keybindings,
&(struct keybinding){.key = sym, .mode = mode, .modifiers = modifiers});
@ -271,15 +271,16 @@ handle_command_key_bindings(struct cg_server *server, xkb_keysym_t sym,
"Recognized keybinding pressed (key: %d, mode: %d, modifiers: %d)",
sym, mode, modifiers);
if(keyboard->device->keyboard->repeat_info.delay > 0) {
keyboard->repeat_keybinding = keybinding;
if(wl_event_source_timer_update(
keyboard->key_repeat_timer,
keyboard->device->keyboard->repeat_info.delay) < 0) {
if(group->wlr_group->input_device->keyboard->repeat_info.delay > 0) {
group->repeat_keybinding = keybinding;
if(wl_event_source_timer_update(group->key_repeat_timer,
group->wlr_group->input_device
->keyboard->repeat_info.delay) <
0) {
wlr_log(WLR_DEBUG, "failed to set key repeat timer");
}
}
message_clear(keyboard->seat->server->curr_output);
message_clear(group->seat->server->curr_output);
run_action((*keybinding)->action, server, (*keybinding)->data);
wlr_idle_notify_activity(server->idle, server->seat->seat);
return true;
@ -315,42 +316,42 @@ key_is_modifier(const xkb_keysym_t key) {
}
static void
handle_key_event(struct cg_keyboard *keyboard, void *data) {
struct cg_seat *seat = keyboard->seat;
handle_key_event(struct cg_keyboard_group *group, struct cg_seat *seat,
void *data) {
struct wlr_event_keyboard_key *event = data;
struct wlr_input_device *device = group->wlr_group->input_device;
/* Translate from libinput keycode to an xkbcommon keycode. */
xkb_keycode_t keycode = event->keycode + 8;
const xkb_keysym_t *syms;
int nsyms = xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state,
keycode, &syms);
int nsyms =
xkb_state_key_get_syms(device->keyboard->xkb_state, keycode, &syms);
bool handled = false;
for(int i = 0; i < nsyms; ++i) {
if(event->state == WLR_KEY_PRESSED && !key_is_modifier(syms[i])) {
uint32_t modifiers =
wlr_keyboard_get_modifiers(keyboard->device->keyboard);
uint32_t modifiers = wlr_keyboard_get_modifiers(device->keyboard);
/* Get the consumed_modifiers and remove them from the modifier list
*/
xkb_mod_mask_t consumed_modifiers =
xkb_state_key_get_consumed_mods2(
keyboard->device->keyboard->xkb_state, keycode,
XKB_CONSUMED_MODE_XKB);
xkb_state_key_get_consumed_mods2(device->keyboard->xkb_state,
keycode,
XKB_CONSUMED_MODE_GTK);
if(handle_command_key_bindings(seat->server, syms[i],
modifiers & ~consumed_modifiers,
seat->mode, keyboard)) {
seat->mode, group)) {
handled = true;
}
} else if(keyboard->repeat_keybinding != NULL && handled == false) {
keyboard_disarm_key_repeat(keyboard);
} else if(group->repeat_keybinding != NULL && handled == false) {
keyboard_disarm_key_repeat(group);
}
}
if(!handled) {
/* Otherwise, we pass it along to the client. */
wlr_seat_set_keyboard(seat->seat, keyboard->device);
wlr_seat_set_keyboard(seat->seat, device);
wlr_seat_keyboard_notify_key(seat->seat, event->time_msec,
event->keycode, event->state);
}
@ -358,130 +359,28 @@ handle_key_event(struct cg_keyboard *keyboard, void *data) {
wlr_idle_notify_activity(seat->server->idle, seat->seat);
}
static void
cg_keyboard_group_remove(struct cg_keyboard *keyboard);
static void
cg_keyboard_destroy(struct cg_keyboard *keyboard) {
struct cg_seat *seat = keyboard->seat;
if(keyboard->device->keyboard->group != NULL) {
cg_keyboard_group_remove(keyboard);
}
if(wlr_seat_get_keyboard(seat->seat) == keyboard->device->keyboard) {
wlr_seat_set_keyboard(seat->seat, NULL);
}
keyboard_disarm_key_repeat(keyboard);
wl_event_source_remove(keyboard->key_repeat_timer);
free(keyboard);
}
static void
destroy_empty_wlr_keyboard_group(void *data) {
wlr_keyboard_group_destroy(data);
}
static void
cg_keyboard_group_remove(struct cg_keyboard *keyboard) {
struct wlr_keyboard_group *wlr_group = keyboard->device->keyboard->group;
wlr_keyboard_group_remove_keyboard(keyboard->device->keyboard->group,
keyboard->device->keyboard);
if(wl_list_empty(&wlr_group->devices)) {
wlr_log(WLR_DEBUG, "Destroying empty keyboard group %p",
(void *)wlr_group);
struct cg_keyboard_group *cg_group = wlr_group->data;
wlr_group->data = NULL;
wl_list_remove(&cg_group->link);
wl_list_remove(&cg_group->key.link);
wl_list_remove(&cg_group->modifiers.link);
cg_keyboard_destroy(cg_group->keyboard);
free(cg_group);
// To prevent use-after-free conditions when handling key events, defer
// freeing the wlr_keyboard_group until idle
if(keyboard->seat->server->running) {
wl_event_loop_add_idle(keyboard->seat->server->event_loop,
destroy_empty_wlr_keyboard_group, wlr_group);
} else {
destroy_empty_wlr_keyboard_group(wlr_group);
}
}
}
static void
handle_keyboard_destroy(struct wl_listener *listener, void *_data) {
struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, destroy);
struct cg_seat *seat = keyboard->seat;
wl_list_remove(&keyboard->link);
wl_list_remove(&keyboard->destroy.link);
cg_keyboard_destroy(keyboard);
update_capabilities(seat);
}
static void
handle_keyboard_group_key(struct wl_listener *listener, void *data) {
struct cg_keyboard_group *cg_group =
wl_container_of(listener, cg_group, key);
handle_key_event(cg_group->keyboard, data);
handle_key_event(cg_group, cg_group->seat, data);
}
static void
handle_keyboard_group_modifiers(struct wl_listener *listener, void *_data) {
struct cg_keyboard_group *group =
wl_container_of(listener, group, modifiers);
handle_modifier_event(group->keyboard);
}
static bool
keymaps_match(struct xkb_keymap *km1, struct xkb_keymap *km2) {
char *km1_str = xkb_keymap_get_as_string(km1, XKB_KEYMAP_FORMAT_TEXT_V1);
char *km2_str = xkb_keymap_get_as_string(km2, XKB_KEYMAP_FORMAT_TEXT_V1);
bool result = strcmp(km1_str, km2_str) == 0;
free(km1_str);
free(km2_str);
return result;
}
static bool
repeat_info_match(const struct cg_keyboard *a, const struct wlr_keyboard *b) {
return a->device->keyboard->repeat_info.rate == b->repeat_info.rate &&
a->device->keyboard->repeat_info.delay == b->repeat_info.delay;
}
struct cg_keyboard *
cg_keyboard_from_seat(struct cg_seat *seat, struct wlr_input_device *device) {
struct cg_keyboard *keyboard = calloc(1, sizeof(struct cg_keyboard));
if(keyboard == NULL) {
wlr_log(WLR_ERROR, "Could not allocate new cg_keyboard.");
return NULL;
}
keyboard->seat = seat;
keyboard->device = device;
keyboard->key_repeat_timer = wl_event_loop_add_timer(
seat->server->event_loop, handle_keyboard_repeat, keyboard);
return keyboard;
handle_modifier_event(group->wlr_group->input_device, group->seat);
}
static void
keyboard_group_add(const struct cg_keyboard *keyboard) {
struct cg_seat *seat = keyboard->seat;
struct wlr_keyboard *wlr_keyboard = keyboard->device->keyboard;
cg_keyboard_group_add(struct wlr_input_device *device, struct cg_seat *seat) {
struct wlr_keyboard *wlr_keyboard = device->keyboard;
struct cg_keyboard_group *group;
wl_list_for_each(group, &seat->keyboard_groups, link) {
struct wlr_keyboard_group *wlr_group = group->wlr_group;
if(keymaps_match(wlr_keyboard->keymap, wlr_group->keyboard.keymap) &&
repeat_info_match(keyboard, &wlr_group->keyboard)) {
wlr_log(WLR_DEBUG, "Adding keyboard %p to group %p.",
(void *)keyboard, (void *)wlr_group);
wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard);
if(wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard)) {
wlr_log(WLR_DEBUG, "Adding keyboard to existing group.");
return;
}
}
@ -493,6 +392,7 @@ keyboard_group_add(const struct cg_keyboard *keyboard) {
wlr_log(WLR_ERROR, "Failed to allocate keyboard group.");
return;
}
cg_group->seat = seat;
cg_group->wlr_group = wlr_keyboard_group_create();
if(cg_group->wlr_group == NULL) {
wlr_log(WLR_ERROR, "Failed to create wlr keyboard group.");
@ -501,22 +401,12 @@ keyboard_group_add(const struct cg_keyboard *keyboard) {
cg_group->wlr_group->data = cg_group;
wlr_keyboard_set_keymap(&cg_group->wlr_group->keyboard,
keyboard->device->keyboard->keymap);
device->keyboard->keymap);
wlr_keyboard_set_repeat_info(&cg_group->wlr_group->keyboard,
keyboard->device->keyboard->repeat_info.rate,
keyboard->device->keyboard->repeat_info.delay);
wlr_log(WLR_DEBUG, "Created keyboard group %p",
(void *)cg_group->wlr_group);
device->keyboard->repeat_info.rate,
device->keyboard->repeat_info.delay);
wlr_log(WLR_DEBUG, "Created keyboard group.");
cg_group->keyboard =
cg_keyboard_from_seat(seat, cg_group->wlr_group->input_device);
if(cg_group->keyboard == NULL) {
wlr_log(WLR_ERROR, "Failed to create keyboard from seat.");
goto cleanup;
}
wlr_log(WLR_DEBUG, "Adding keyboard %p to group %p.", (void *)keyboard,
(void *)cg_group->wlr_group);
wlr_keyboard_group_add_keyboard(cg_group->wlr_group, wlr_keyboard);
wl_list_insert(&seat->keyboard_groups, &cg_group->link);
@ -525,6 +415,10 @@ keyboard_group_add(const struct cg_keyboard *keyboard) {
wl_signal_add(&cg_group->wlr_group->keyboard.events.modifiers,
&cg_group->modifiers);
cg_group->key_repeat_timer = wl_event_loop_add_timer(
seat->server->event_loop, handle_keyboard_repeat, cg_group);
cg_group->modifiers.notify = handle_keyboard_group_modifiers;
return;
@ -537,16 +431,9 @@ cleanup:
static void
handle_new_keyboard(struct cg_seat *seat, struct wlr_input_device *device) {
struct cg_keyboard *keyboard = calloc(1, sizeof(struct cg_keyboard));
if(!keyboard) {
wlr_log(WLR_ERROR, "Cannot allocate keyboard");
return;
}
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if(!context) {
wlr_log(WLR_ERROR, "Unable to create XBK context");
free(keyboard);
return;
}
@ -561,28 +448,19 @@ handle_new_keyboard(struct cg_seat *seat, struct wlr_input_device *device) {
if(!keymap) {
wlr_log(WLR_ERROR,
"Unable to configure keyboard: keymap does not exist");
free(keyboard);
xkb_context_unref(context);
return;
}
keyboard->seat = seat;
keyboard->device = device;
wlr_keyboard_set_keymap(device->keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
wlr_keyboard_set_repeat_info(device->keyboard, 25, 600);
keyboard_group_add(keyboard);
cg_keyboard_group_add(device, seat);
wlr_seat_set_keyboard(seat->seat, device->keyboard->group->input_device);
wl_list_insert(&seat->keyboards, &keyboard->link);
keyboard->destroy.notify = handle_keyboard_destroy;
wl_signal_add(&device->events.destroy, &keyboard->destroy);
keyboard->key_repeat_timer = wl_event_loop_add_timer(
seat->server->event_loop, handle_keyboard_repeat, keyboard);
wlr_seat_set_keyboard(seat->seat, device);
}
static void
@ -914,9 +792,11 @@ handle_destroy(struct wl_listener *listener, void *_data) {
struct cg_seat *seat = wl_container_of(listener, seat, destroy);
wl_list_remove(&seat->destroy.link);
struct cg_keyboard *keyboard, *keyboard_tmp;
wl_list_for_each_safe(keyboard, keyboard_tmp, &seat->keyboards, link) {
handle_keyboard_destroy(&keyboard->destroy, NULL);
struct cg_keyboard_group *group, *group_tmp;
wl_list_for_each_safe(group, group_tmp, &seat->keyboard_groups, link) {
wlr_keyboard_group_destroy(group->wlr_group);
wl_event_source_remove(group->key_repeat_timer);
free(group);
}
struct cg_pointer *pointer, *pointer_tmp;
wl_list_for_each_safe(pointer, pointer_tmp, &seat->pointers, link) {
@ -1014,7 +894,6 @@ seat_create(struct cg_server *server, struct wlr_backend *backend) {
wl_signal_add(&seat->seat->events.request_set_primary_selection,
&seat->request_set_primary_selection);
wl_list_init(&seat->keyboards);
wl_list_init(&seat->keyboard_groups);
wl_list_init(&seat->pointers);
wl_list_init(&seat->touch);

23
seat.h
View file

@ -14,22 +14,11 @@ 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;
@ -66,14 +55,16 @@ struct cg_seat {
struct cg_view *focused_view;
};
struct cg_keyboard {
struct wl_list link; // seat::keyboards
struct cg_keyboard_group {
struct wlr_keyboard_group *wlr_group;
struct cg_seat *seat;
struct wlr_input_device *device;
struct wl_listener destroy;
struct keybinding **repeat_keybinding;
struct wl_listener key;
struct wl_listener modifiers;
struct wl_list link;
struct wl_event_source *key_repeat_timer;
struct keybinding **repeat_keybinding;
};
struct cg_pointer {

BIN
signatures/cagebreak.sig Normal file

Binary file not shown.

12
view.c
View file

@ -234,10 +234,14 @@ view_unmap(struct cg_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(view->workspace->server->seat->seat->keyboard_state
.focused_surface == NULL ||
view->workspace->server->seat->seat->keyboard_state
.focused_surface == view->wlr_surface) {
if((view->workspace->server->seat->seat->keyboard_state
.focused_surface == NULL ||
view->workspace->server->seat->seat->keyboard_state
.focused_surface == view->wlr_surface) &&
view->workspace->server->curr_output
->workspaces[view->workspace->server->curr_output
->curr_workspace]
->focused_tile == view_tile) {
seat_set_focus(view->workspace->server->seat, prev);
} else {
view_tile->view = prev;