diff --git a/include/action.h b/include/action.h index b23a0ec9..e5506033 100644 --- a/include/action.h +++ b/include/action.h @@ -2,19 +2,23 @@ #ifndef __LABWC_ACTION_H #define __LABWC_ACTION_H -struct server; struct view; +struct server; +struct wl_list; struct action { - uint32_t type; - char *arg; - struct wl_list link; + struct wl_list link; /* struct keybinding.actions, + * struct mousebinding.actions, + * struct menuitem.actions */ + + uint32_t type; /* enum action_type */ + struct wl_list args; /* struct action_arg.link */ }; struct action *action_create(const char *action_name); -void action_list_free(struct wl_list *action_list); - +void action_arg_add_str(struct action *action, char *key, const char *value); void actions_run(struct view *activator, struct server *server, struct wl_list *actions, uint32_t resize_edges); +void action_list_free(struct wl_list *action_list); -#endif +#endif /* __LABWC_ACTION_H */ diff --git a/include/config/keybind.h b/include/config/keybind.h index 86eb4b52..b11914ed 100644 --- a/include/config/keybind.h +++ b/include/config/keybind.h @@ -11,8 +11,8 @@ struct keybind { uint32_t modifiers; xkb_keysym_t *keysyms; size_t keysyms_len; - struct wl_list actions; - struct wl_list link; + struct wl_list actions; /* struct action.link */ + struct wl_list link; /* struct rcxml.keybinds */ }; /** diff --git a/include/config/mousebind.h b/include/config/mousebind.h index ae66532a..af73dd4f 100644 --- a/include/config/mousebind.h +++ b/include/config/mousebind.h @@ -26,9 +26,9 @@ struct mousebind { /* ex: doubleclick, press, drag */ enum mouse_event mouse_event; - struct wl_list actions; + struct wl_list actions; /* struct action.link */ - struct wl_list link; /* rcxml::mousebinds */ + struct wl_list link; /* struct rcxml.mousebinds */ bool pressed_in_context; /* used in click events */ }; diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 41382de2..5827f51a 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -37,11 +37,11 @@ struct rcxml { /* keyboard */ int repeat_rate; int repeat_delay; - struct wl_list keybinds; + struct wl_list keybinds; /* struct keybind.link */ /* mouse */ - long doubleclick_time; /* in ms */ - struct wl_list mousebinds; + long doubleclick_time; /* in ms */ + struct wl_list mousebinds; /* struct mousebind.link */ /* libinput */ struct wl_list libinput_categories; diff --git a/include/labwc.h b/include/labwc.h index 416fb03c..0af693ea 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -306,13 +306,14 @@ struct view { bool been_mapped; bool minimized; bool maximized; + uint32_t tiled; /* private, enum view_edge in src/view.c */ struct wlr_output *fullscreen; /* geometry of the wlr_surface contained within the view */ int x, y, w, h; - /* geometry before maximize */ - struct wlr_box unmaximized_geometry; + /* user defined geometry before maximize / tiling / fullscreen */ + struct wlr_box natural_geometry; /* * margin refers to the space between the extremities of the @@ -364,6 +365,7 @@ struct view { struct xwayland_unmanaged { struct server *server; struct wlr_xwayland_surface *xwayland_surface; + struct wlr_scene_node *node; struct wl_list link; int lx, ly; @@ -460,6 +462,7 @@ void foreign_toplevel_handle_create(struct view *view); void desktop_move_to_front(struct view *view); void desktop_move_to_back(struct view *view); void desktop_focus_and_activate_view(struct seat *seat, struct view *view); +void desktop_arrange_all_views(struct server *server); enum lab_cycle_dir { LAB_CYCLE_DIR_NONE, diff --git a/include/private/action.h b/include/private/action.h new file mode 100644 index 00000000..0b8f60d0 --- /dev/null +++ b/include/private/action.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __LABWC_PRIVATE_ACTION_H +#define __LABWC_PRIVATE_ACTION_H + +/* Don't include ourself as search path starts at current directory */ +#include "../action.h" + +enum action_arg_type { + LAB_ACTION_ARG_STR = 0, +}; + +struct action_arg { + struct wl_list link; /* struct action.args */ + + const char *key; /* May be NULL if there is just one arg */ + enum action_arg_type type; +}; + +struct action_arg_str { + struct action_arg base; + char *value; +}; + +#endif /* __LABWC_PRIVATE_ACTION_H */ diff --git a/include/ssd.h b/include/ssd.h index 8b5e7359..e8bfdc12 100644 --- a/include/ssd.h +++ b/include/ssd.h @@ -134,8 +134,10 @@ struct ssd_hover_state { /* Public SSD API */ void ssd_create(struct view *view); + void ssd_hide(struct view *view); void ssd_set_active(struct view *view, bool activated); + void ssd_update_title(struct view *view); void ssd_update_geometry(struct view *view); void ssd_reload(struct view *view); diff --git a/src/action.c b/src/action.c index d29048fb..3b6dd4d4 100644 --- a/src/action.c +++ b/src/action.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L +#include #include +#include #include #include #include @@ -9,8 +11,8 @@ #include "debug.h" #include "labwc.h" #include "menu/menu.h" +#include "private/action.h" #include "ssd.h" -#include "action.h" #include "workspaces.h" enum action_type { @@ -64,6 +66,24 @@ const char *action_names[] = { NULL }; +static char * +action_str_from_arg(struct action_arg *arg) +{ + assert(arg->type == LAB_ACTION_ARG_STR); + return ((struct action_arg_str *)arg)->value; +} + +static struct action_arg * +action_get_first_arg(struct action *action) +{ + struct action_arg *arg; + struct wl_list *item = action->args.next; + if (item == &action->args) { + return NULL; + } + return wl_container_of(item, arg, link); +} + static enum action_type action_type_from_str(const char *action_name) { @@ -85,15 +105,26 @@ action_create(const char *action_name) } struct action *action = calloc(1, sizeof(struct action)); action->type = action_type_from_str(action_name); + wl_list_init(&action->args); return action; } void action_list_free(struct wl_list *action_list) { + struct action_arg *arg, *arg_tmp; struct action *action, *action_tmp; + /* Free actions */ wl_list_for_each_safe(action, action_tmp, action_list, link) { wl_list_remove(&action->link); - zfree(action->arg); + /* Free args */ + wl_list_for_each_safe(arg, arg_tmp, &action->args, link) { + wl_list_remove(&arg->link); + zfree(arg->key); + if (arg->type == LAB_ACTION_ARG_STR) { + free(action_str_from_arg(arg)); + } + zfree(arg); + } zfree(action); } } @@ -151,9 +182,13 @@ actions_run(struct view *activator, struct server *server, struct view *view; struct action *action; + struct action_arg *arg; wl_list_for_each(action, actions, link) { - wlr_log(WLR_DEBUG, "Handling action %s (%u) with arg %s", - action_names[action->type], action->type, action->arg); + wlr_log(WLR_DEBUG, "Handling action %s (%u)", + action_names[action->type], action->type); + + /* Get arg now so we don't have to repeat every time we only need one */ + arg = action_get_first_arg(action); /* * Refetch view because it may have been changed due to the @@ -171,28 +206,30 @@ actions_run(struct view *activator, struct server *server, debug_dump_scene(server); break; case ACTION_TYPE_EXECUTE: - { - struct buf cmd; - buf_init(&cmd); - buf_add(&cmd, action->arg); - buf_expand_shell_variables(&cmd); - spawn_async_no_shell(cmd.buf); - free(cmd.buf); + if (!arg) { + wlr_log(WLR_ERROR, "Missing argument for Execute"); + break; } + struct buf cmd; + buf_init(&cmd); + buf_add(&cmd, action_str_from_arg(arg)); + buf_expand_shell_variables(&cmd); + spawn_async_no_shell(cmd.buf); + free(cmd.buf); break; case ACTION_TYPE_EXIT: wl_display_terminate(server->wl_display); break; case ACTION_TYPE_MOVE_TO_EDGE: - if (action->arg) { - view_move_to_edge(view, action->arg); + if (arg) { + view_move_to_edge(view, action_str_from_arg(arg)); } else { wlr_log(WLR_ERROR, "Missing argument for MoveToEdge"); } break; case ACTION_TYPE_SNAP_TO_EDGE: - if (action->arg) { - view_snap_to_edge(view, action->arg); + if (arg) { + view_snap_to_edge(view, action_str_from_arg(arg)); } else { wlr_log(WLR_ERROR, "Missing argument for SnapToEdge"); } @@ -211,7 +248,11 @@ actions_run(struct view *activator, struct server *server, kill(getpid(), SIGHUP); break; case ACTION_TYPE_SHOW_MENU: - show_menu(server, view, action->arg); + if (arg) { + show_menu(server, view, action_str_from_arg(arg)); + } else { + wlr_log(WLR_ERROR, "Missing argument for ShowMenu"); + } break; case ACTION_TYPE_TOGGLE_MAXIMIZE: if (view) { @@ -263,27 +304,33 @@ actions_run(struct view *activator, struct server *server, } break; case ACTION_TYPE_GO_TO_DESKTOP: - { - struct workspace *target; - target = workspaces_find(server->workspace_current, action->arg); - if (target) { - workspaces_switch_to(target); - } + if (!arg) { + wlr_log(WLR_ERROR, "Missing argument for GoToDesktop"); + break; + } + struct workspace *target; + char *target_name = action_str_from_arg(arg); + target = workspaces_find(server->workspace_current, target_name); + if (target) { + workspaces_switch_to(target); } break; case ACTION_TYPE_SEND_TO_DESKTOP: + if (!arg) { + wlr_log(WLR_ERROR, "Missing argument for SendToDesktop"); + break; + } if (view) { struct workspace *target; - target = workspaces_find(view->workspace, action->arg); + char *target_name = action_str_from_arg(arg); + target = workspaces_find(view->workspace, target_name); if (target) { workspaces_send_to(view, target); } } break; case ACTION_TYPE_NONE: - wlr_log(WLR_ERROR, - "Not executing unknown action with arg %s", - action->arg); + wlr_log(WLR_ERROR, "Not executing unknown action"); break; default: /* @@ -292,9 +339,21 @@ actions_run(struct view *activator, struct server *server, * adding a new action without installing a handler here. */ wlr_log(WLR_ERROR, - "Not executing invalid action (%u) with arg %s" - " This is a BUG. Please report.", - action->type, action->arg); + "Not executing invalid action (%u)" + " This is a BUG. Please report.", action->type); } } } + +void +action_arg_add_str(struct action *action, char *key, const char *value) +{ + assert(value && "Tried to add NULL action string argument"); + struct action_arg_str *arg = calloc(1, sizeof(*arg)); + arg->base.type = LAB_ACTION_ARG_STR; + if (key) { + arg->base.key = strdup(key); + } + arg->value = strdup(value); + wl_list_insert(action->args.prev, &arg->base.link); +} diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 19bd2133..1d950f7a 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -71,21 +71,18 @@ fill_keybind(char *nodename, char *content) } else if (!current_keybind_action) { wlr_log(WLR_ERROR, "expect element first. " "nodename: '%s' content: '%s'", nodename, content); - } else if (current_keybind_action->arg) { - wlr_log(WLR_ERROR, "Action argument already set: %s", - current_keybind_action->arg); } else if (!strcmp(nodename, "command.action")) { /* Execute */ - current_keybind_action->arg = strdup(content); + action_arg_add_str(current_keybind_action, NULL, content); } else if (!strcmp(nodename, "direction.action")) { /* MoveToEdge, SnapToEdge */ - current_keybind_action->arg = strdup(content); + action_arg_add_str(current_keybind_action, NULL, content); } else if (!strcmp(nodename, "menu.action")) { /* ShowMenu */ - current_keybind_action->arg = strdup(content); + action_arg_add_str(current_keybind_action, NULL, content); } else if (!strcmp(nodename, "to.action")) { /* GoToDesktop, SendToDesktop */ - current_keybind_action->arg = strdup(content); + action_arg_add_str(current_keybind_action, NULL, content); } } @@ -134,15 +131,12 @@ fill_mousebind(char *nodename, char *content) } else if (!current_mousebind_action) { wlr_log(WLR_ERROR, "expect element first. " "nodename: '%s' content: '%s'", nodename, content); - } else if (current_mousebind_action->arg) { - wlr_log(WLR_ERROR, "Action argument already set: %s", - current_mousebind_action->arg); } else if (!strcmp(nodename, "command.action")) { - current_mousebind_action->arg = strdup(content); + action_arg_add_str(current_mousebind_action, NULL, content); } else if (!strcmp(nodename, "direction.action")) { - current_mousebind_action->arg = strdup(content); + action_arg_add_str(current_mousebind_action, NULL, content); } else if (!strcmp(nodename, "menu.action")) { - current_mousebind_action->arg = strdup(content); + action_arg_add_str(current_mousebind_action, NULL, content); } } @@ -544,7 +538,7 @@ load_default_key_bindings(void) wl_list_insert(k->actions.prev, &action->link); if (key_combos[i].command) { - action->arg = strdup(key_combos[i].command); + action_arg_add_str(action, NULL, key_combos[i].command); } } } @@ -604,7 +598,7 @@ load_default_mouse_bindings(void) wl_list_insert(m->actions.prev, &action->link); if (mouse_combos[i].command) { - action->arg = strdup(mouse_combos[i].command); + action_arg_add_str(action, NULL, mouse_combos[i].command); } } } diff --git a/src/desktop.c b/src/desktop.c index b19cd4be..5a3ad8bc 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -99,6 +99,16 @@ deactivate_all_views(struct server *server) } } +void +desktop_arrange_all_views(struct server *server) +{ + /* Adjust window positions/sizes */ + struct view *view; + wl_list_for_each(view, &server->views, link) { + view_adjust_for_layout_change(view); + } +} + void desktop_focus_and_activate_view(struct seat *seat, struct view *view) { diff --git a/src/interactive.c b/src/interactive.c index 8ade121a..d0d8f808 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -17,30 +17,51 @@ max_move_scale(double pos_cursor, double pos_current, void interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) { - if (view->maximized) { + if (mode == LAB_INPUT_STATE_MOVE && view->fullscreen) { + /** + * We don't allow moving fullscreen windows. + * + * If you think there is a good reason to allow it + * feel free to open an issue explaining your use-case. + */ + return; + } + if (mode == LAB_INPUT_STATE_RESIZE + && (view->fullscreen || view->maximized)) { + /* We don't allow resizing while in maximized or fullscreen state */ + return; + } + if (view->maximized || view->tiled) { if (mode == LAB_INPUT_STATE_MOVE) { + /* Exit maximized or tiled mode */ int new_x = max_move_scale(view->server->seat.cursor->x, - view->x, view->w, view->unmaximized_geometry.width); + view->x, view->w, view->natural_geometry.width); int new_y = max_move_scale(view->server->seat.cursor->y, - view->y, view->h, view->unmaximized_geometry.height); - view->unmaximized_geometry.x = new_x; - view->unmaximized_geometry.y = new_y; - view_maximize(view, false); - /* - * view_maximize() indirectly calls view->impl->configure - * which is async but we are using the current values in - * server->grab_box. We pretend the configure already - * happened by setting them manually. + view->y, view->h, view->natural_geometry.height); + view->natural_geometry.x = new_x; + view->natural_geometry.y = new_y; + if (view->maximized) { + view_maximize(view, false); + } + if (view->tiled) { + view_move_resize(view, view->natural_geometry); + } + /** + * view_maximize() / view_move_resize() indirectly calls + * view->impl->configure which is async but we are using + * the current values in server->grab_box. We pretend the + * configure already happened by setting them manually. */ view->x = new_x; view->y = new_y; - view->w = view->unmaximized_geometry.width; - view->h = view->unmaximized_geometry.height; - } else { - return; + view->w = view->natural_geometry.width; + view->h = view->natural_geometry.height; } } + /* Moving or resizing always resets tiled state */ + view->tiled = 0; + /* * This function sets up an interactive move or resize operation, where * the compositor stops propagating pointer events to clients and @@ -101,9 +122,9 @@ interactive_end(struct view *view) * When unmaximizing later on restore * original position */ - view->unmaximized_geometry.x = + view->natural_geometry.x = view->server->grab_box.x; - view->unmaximized_geometry.y = + view->natural_geometry.y = view->server->grab_box.y; } else { view_snap_to_edge(view, "up"); diff --git a/src/layers.c b/src/layers.c index d36e11a5..674deaa9 100644 --- a/src/layers.c +++ b/src/layers.c @@ -25,6 +25,7 @@ layers_arrange(struct output *output) wlr_output_effective_resolution(output->wlr_output, &full_area.width, &full_area.height); struct wlr_box usable_area = full_area; + struct wlr_box old_usable_area = output->usable_area; struct server *server = output->server; struct wlr_scene_output *scene_output = @@ -37,11 +38,29 @@ layers_arrange(struct output *output) int nr_layers = sizeof(output->layers) / sizeof(output->layers[0]); for (int i = 0; i < nr_layers; i++) { struct lab_layer_surface *lab_layer_surface; + + /* + * First we go over the list of surfaces that have + * exclusive_zone set (e.g. statusbars) because we have to + * determine the usable area before processing regular layouts. + */ wl_list_for_each(lab_layer_surface, &output->layers[i], link) { struct wlr_scene_layer_surface_v1 *scene_layer_surface = lab_layer_surface->scene_layer_surface; - wlr_scene_layer_surface_v1_configure( - scene_layer_surface, &full_area, &usable_area); + if (scene_layer_surface->layer_surface->current.exclusive_zone) { + wlr_scene_layer_surface_v1_configure( + scene_layer_surface, &full_area, &usable_area); + } + } + + /* Now we process regular layouts */ + wl_list_for_each(lab_layer_surface, &output->layers[i], link) { + struct wlr_scene_layer_surface_v1 *scene_layer_surface = + lab_layer_surface->scene_layer_surface; + if (!scene_layer_surface->layer_surface->current.exclusive_zone) { + wlr_scene_layer_surface_v1_configure( + scene_layer_surface, &full_area, &usable_area); + } } wlr_scene_node_set_position(&output->layer_tree[i]->node, @@ -80,7 +99,12 @@ layers_arrange(struct output *output) !seat->focused_layer->current.keyboard_interactive) { seat_set_focus_layer(seat, NULL); } - /* FIXME: should we call a desktop_arrange_all_views() here? */ + + /* Finally re-arrange all views based on usable_area */ + if (old_usable_area.width != output->usable_area.width + || old_usable_area.height != output->usable_area.height) { + desktop_arrange_all_views(server); + } } static void @@ -350,7 +374,7 @@ new_layer_surface_notify(struct wl_listener *listener, void *data) return; } - wl_list_insert(&output->layers[layer_surface->pending.layer], + wl_list_insert(output->layers[layer_surface->pending.layer].prev, &surface->link); /* * Temporarily set the layer's current state to pending so that diff --git a/src/menu/menu.c b/src/menu/menu.c index bb79295e..a2677c8b 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -238,16 +238,16 @@ fill_item(char *nodename, char *content) wlr_log(WLR_ERROR, "expect element first. " "nodename: '%s' content: '%s'", nodename, content); } else if (!strcmp(nodename, "command.action")) { - current_item_action->arg = strdup(content); + action_arg_add_str(current_item_action, NULL, content); } else if (!strcmp(nodename, "execute.action")) { /* * foo * is deprecated, but we support it anyway for backward * compatibility with old openbox-menu generators */ - current_item_action->arg = strdup(content); + action_arg_add_str(current_item_action, NULL, content); } else if (!strcmp(nodename, "to.action")) { - current_item_action->arg = strdup(content); + action_arg_add_str(current_item_action, NULL, content); } } diff --git a/src/output.c b/src/output.c index d2634cf7..688640f3 100644 --- a/src/output.c +++ b/src/output.c @@ -209,10 +209,7 @@ static void output_update_for_layout_change(struct server *server) { /* Adjust window positions/sizes */ - struct view *view; - wl_list_for_each(view, &server->views, link) { - view_adjust_for_layout_change(view); - } + desktop_arrange_all_views(server); /* * "Move" each wlr_output_cursor (in per-output coordinates) to diff --git a/src/server.c b/src/server.c index df9d8935..857d3e49 100644 --- a/src/server.c +++ b/src/server.c @@ -42,7 +42,6 @@ reload_config_and_theme(void) if (!view->mapped || !view->ssd.enabled) { continue; } - view->margin = ssd_thickness(view); ssd_reload(view); } diff --git a/src/ssd/ssd.c b/src/ssd/ssd.c index 64925177..201a17a1 100644 --- a/src/ssd/ssd.c +++ b/src/ssd/ssd.c @@ -17,6 +17,10 @@ struct border ssd_thickness(struct view *view) { + if (!view->ssd.enabled) { + struct border border = { 0 }; + return border; + } struct theme *theme = view->server->theme; struct border border = { .top = theme->title_height + theme->border_width, @@ -159,6 +163,7 @@ ssd_create(struct view *view) ssd_extents_create(view); ssd_border_create(view); ssd_titlebar_create(view); + view->margin = ssd_thickness(view); } void @@ -171,10 +176,12 @@ ssd_update_geometry(struct view *view) if (!view->ssd.enabled) { if (view->ssd.tree->node.enabled) { wlr_scene_node_set_enabled(&view->ssd.tree->node, false); + view->margin = ssd_thickness(view); } return; } else if (!view->ssd.tree->node.enabled) { wlr_scene_node_set_enabled(&view->ssd.tree->node, true); + view->margin = ssd_thickness(view); } int width = view->w; @@ -198,15 +205,6 @@ ssd_update_geometry(struct view *view) view->ssd.state.y = view->y; } -void -ssd_hide(struct view *view) -{ - if (!view->ssd.tree) { - return; - } - wlr_scene_node_set_enabled(&view->ssd.tree->node, false); -} - void ssd_reload(struct view *view) { if (!view->ssd.tree) { diff --git a/src/view.c b/src/view.c index 1bd0cbdd..5546f3a3 100644 --- a/src/view.c +++ b/src/view.c @@ -8,8 +8,93 @@ #include "menu/menu.h" #include "workspaces.h" +#define LAB_FALLBACK_WIDTH 640 +#define LAB_FALLBACK_HEIGHT 480 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) +/** + * All view_apply_xxx_geometry() functions must *not* modify + * any state besides repositioning or resizing the view. + * + * They may be called repeatably during output layout changes. + */ + +enum view_edge { + VIEW_EDGE_INVALID = 0, + + VIEW_EDGE_LEFT, + VIEW_EDGE_RIGHT, + VIEW_EDGE_UP, + VIEW_EDGE_DOWN, + VIEW_EDGE_CENTER, +}; + +static enum view_edge +view_edge_invert(enum view_edge edge) +{ + switch (edge) { + case VIEW_EDGE_LEFT: + return VIEW_EDGE_RIGHT; + case VIEW_EDGE_RIGHT: + return VIEW_EDGE_LEFT; + case VIEW_EDGE_UP: + return VIEW_EDGE_DOWN; + case VIEW_EDGE_DOWN: + return VIEW_EDGE_UP; + case VIEW_EDGE_CENTER: + case VIEW_EDGE_INVALID: + default: + return VIEW_EDGE_INVALID; + } +} + +static struct wlr_box +view_get_edge_snap_box(struct view *view, struct output *output, + enum view_edge edge) +{ + struct wlr_box usable = output_usable_area_in_layout_coords(output); + if (usable.height == output->wlr_output->height + && output->wlr_output->scale != 1) { + usable.height /= output->wlr_output->scale; + } + if (usable.width == output->wlr_output->width + && output->wlr_output->scale != 1) { + usable.width /= output->wlr_output->scale; + } + + int x_offset = edge == VIEW_EDGE_RIGHT + ? (usable.width + rc.gap) / 2 : rc.gap; + int y_offset = edge == VIEW_EDGE_DOWN + ? (usable.height + rc.gap) / 2 : rc.gap; + + int base_width, base_height; + switch (edge) { + case VIEW_EDGE_LEFT: + case VIEW_EDGE_RIGHT: + base_width = (usable.width - 3 * rc.gap) / 2; + base_height = usable.height - 2 * rc.gap; + break; + case VIEW_EDGE_UP: + case VIEW_EDGE_DOWN: + base_width = usable.width - 2 * rc.gap; + base_height = (usable.height - 3 * rc.gap) / 2; + break; + default: + case VIEW_EDGE_CENTER: + base_width = usable.width - 2 * rc.gap; + base_height = usable.height - 2 * rc.gap; + break; + } + struct wlr_box dst = { + .x = x_offset + usable.x + view->margin.left, + .y = y_offset + usable.y + view->margin.top, + .width = base_width - view->margin.left - view->margin.right, + .height = base_height - view->margin.top - view->margin.bottom, + }; + + return dst; +} + void view_set_activated(struct view *view, bool activated) { @@ -176,6 +261,38 @@ view_compute_centered_position(struct view *view, int w, int h, int *x, int *y) return true; } +static void +set_fallback_geometry(struct view *view) +{ + view->natural_geometry.width = LAB_FALLBACK_WIDTH; + view->natural_geometry.height = LAB_FALLBACK_HEIGHT; + view_compute_centered_position(view, + view->natural_geometry.width, + view->natural_geometry.height, + &view->natural_geometry.x, + &view->natural_geometry.y); +} +#undef LAB_FALLBACK_WIDTH +#undef LAB_FALLBACK_HEIGHT + +static void +view_store_natural_geometry(struct view *view) +{ + /** + * If an application was started maximized or fullscreened, its + * natural_geometry width/height may still be zero in which case we set + * some fallback values. This is the case with foot and Qt applications. + */ + if (!view->w || !view->h) { + set_fallback_geometry(view); + } else { + view->natural_geometry.x = view->x; + view->natural_geometry.y = view->y; + view->natural_geometry.width = view->w; + view->natural_geometry.height = view->h; + } +} + void view_center(struct view *view) { @@ -185,6 +302,27 @@ view_center(struct view *view) } } +static void +view_apply_tiled_geometry(struct view *view, struct output *output) +{ + assert(view->tiled); + if (!output) { + output = view_output(view); + } + if (!output) { + wlr_log(WLR_ERROR, "Can't tile: no output"); + return; + } + + struct wlr_box dst = view_get_edge_snap_box(view, output, view->tiled); + if (view->w == dst.width && view->h == dst.height) { + /* move horizontally/vertically without changing size */ + view_move(view, dst.x, dst.y); + } else { + view_move_resize(view, dst); + } +} + static void view_apply_fullscreen_geometry(struct view *view, struct wlr_output *wlr_output) { @@ -234,41 +372,17 @@ view_apply_maximized_geometry(struct view *view) view_move_resize(view, box); } -#define LAB_FALLBACK_WIDTH (640) -#define LAB_FALLBACK_HEIGHT (480) - -static void -set_fallback_geometry(struct view *view) -{ - view->unmaximized_geometry.width = LAB_FALLBACK_WIDTH; - view->unmaximized_geometry.height = LAB_FALLBACK_HEIGHT; - view_compute_centered_position(view, - view->unmaximized_geometry.width, - view->unmaximized_geometry.height, - &view->unmaximized_geometry.x, - &view->unmaximized_geometry.y); -} - static void view_apply_unmaximized_geometry(struct view *view) { - /* - * If an application was started maximized, its unmaximized_geometry - * width/height may still be zero in which case we set some fallback - * values. This is the case with foot and Qt applications. - */ - if (wlr_box_empty(&view->unmaximized_geometry)) { - set_fallback_geometry(view); - } - struct wlr_output_layout *layout = view->server->output_layout; if (wlr_output_layout_intersects(layout, NULL, - &view->unmaximized_geometry)) { + &view->natural_geometry)) { /* restore to original geometry */ - view_move_resize(view, view->unmaximized_geometry); + view_move_resize(view, view->natural_geometry); } else { /* reposition if original geometry is offscreen */ - struct wlr_box box = view->unmaximized_geometry; + struct wlr_box box = view->natural_geometry; if (view_compute_centered_position(view, box.width, box.height, &box.x, &box.y)) { view_move_resize(view, box); @@ -294,16 +408,18 @@ view_maximize(struct view *view, bool maximize) } if (maximize) { interactive_end(view); - view->unmaximized_geometry.x = view->x; - view->unmaximized_geometry.y = view->y; - view->unmaximized_geometry.width = view->w; - view->unmaximized_geometry.height = view->h; - + if (!view->tiled) { + view_store_natural_geometry(view); + } view_apply_maximized_geometry(view); view->maximized = true; } else { /* unmaximize */ - view_apply_unmaximized_geometry(view); + if (view->tiled) { + view_apply_tiled_geometry(view, NULL); + } else { + view_apply_unmaximized_geometry(view); + } view->maximized = false; } } @@ -322,6 +438,8 @@ view_toggle_decorations(struct view *view) ssd_update_geometry(view); if (view->maximized) { view_apply_maximized_geometry(view); + } else if (view->tiled) { + view_apply_tiled_geometry(view, NULL); } } } @@ -353,6 +471,8 @@ view_set_decorations(struct view *view, bool decorations) ssd_update_geometry(view); if (view->maximized) { view_apply_maximized_geometry(view); + } else if (view->tiled) { + view_apply_tiled_geometry(view, NULL); } } } @@ -381,11 +501,8 @@ view_set_fullscreen(struct view *view, bool fullscreen, wlr_output = view_wlr_output(view); } if (fullscreen) { - if (!view->maximized) { - view->unmaximized_geometry.x = view->x; - view->unmaximized_geometry.y = view->y; - view->unmaximized_geometry.width = view->w; - view->unmaximized_geometry.height = view->h; + if (!view->maximized && !view->tiled) { + view_store_natural_geometry(view); } view->fullscreen = wlr_output; view_apply_fullscreen_geometry(view, view->fullscreen); @@ -393,6 +510,8 @@ view_set_fullscreen(struct view *view, bool fullscreen, /* restore to normal */ if (view->maximized) { view_apply_maximized_geometry(view); + } else if (view->tiled) { + view_apply_tiled_geometry(view, NULL); } else { view_apply_unmaximized_geometry(view); } @@ -421,6 +540,9 @@ view_adjust_for_layout_change(struct view *view) } else if (view->maximized) { /* recompute maximized geometry */ view_apply_maximized_geometry(view); + } else if (view->tiled) { + /* recompute tiled geometry */ + view_apply_tiled_geometry(view, NULL); } else { /* reposition view if it's offscreen */ struct wlr_box box = { view->x, view->y, view->w, view->h }; @@ -515,35 +637,6 @@ view_move_to_edge(struct view *view, const char *direction) view_move(view, x, y); } -enum view_edge { - VIEW_EDGE_INVALID, - - VIEW_EDGE_LEFT, - VIEW_EDGE_RIGHT, - VIEW_EDGE_UP, - VIEW_EDGE_DOWN, - VIEW_EDGE_CENTER, -}; - -static enum view_edge -view_edge_invert(enum view_edge edge) -{ - switch (edge) { - case VIEW_EDGE_LEFT: - return VIEW_EDGE_RIGHT; - case VIEW_EDGE_RIGHT: - return VIEW_EDGE_LEFT; - case VIEW_EDGE_UP: - return VIEW_EDGE_DOWN; - case VIEW_EDGE_DOWN: - return VIEW_EDGE_UP; - case VIEW_EDGE_CENTER: - case VIEW_EDGE_INVALID: - default: - return VIEW_EDGE_INVALID; - } -} - static enum view_edge view_edge_parse(const char *direction) { @@ -565,53 +658,6 @@ view_edge_parse(const char *direction) } } -static struct wlr_box -view_get_edge_snap_box(struct view *view, struct output *output, - enum view_edge edge) -{ - struct wlr_box usable = output_usable_area_in_layout_coords(output); - if (usable.height == output->wlr_output->height - && output->wlr_output->scale != 1) { - usable.height /= output->wlr_output->scale; - } - if (usable.width == output->wlr_output->width - && output->wlr_output->scale != 1) { - usable.width /= output->wlr_output->scale; - } - - int x_offset = edge == VIEW_EDGE_RIGHT - ? (usable.width + rc.gap) / 2 : rc.gap; - int y_offset = edge == VIEW_EDGE_DOWN - ? (usable.height + rc.gap) / 2 : rc.gap; - - int base_width, base_height; - switch (edge) { - case VIEW_EDGE_LEFT: - case VIEW_EDGE_RIGHT: - base_width = (usable.width - 3 * rc.gap) / 2; - base_height = usable.height - 2 * rc.gap; - break; - case VIEW_EDGE_UP: - case VIEW_EDGE_DOWN: - base_width = usable.width - 2 * rc.gap; - base_height = (usable.height - 3 * rc.gap) / 2; - break; - default: - case VIEW_EDGE_CENTER: - base_width = usable.width - 2 * rc.gap; - base_height = usable.height - 2 * rc.gap; - break; - } - struct wlr_box dst = { - .x = x_offset + usable.x + view->margin.left, - .y = y_offset + usable.y + view->margin.top, - .width = base_width - view->margin.left - view->margin.right, - .height = base_height - view->margin.top - view->margin.bottom, - }; - - return dst; -} - void view_snap_to_edge(struct view *view, const char *direction) { @@ -619,6 +665,9 @@ view_snap_to_edge(struct view *view, const char *direction) wlr_log(WLR_ERROR, "no view"); return; } + if (view->fullscreen) { + return; + } struct output *output = view_output(view); if (!output) { wlr_log(WLR_ERROR, "no output"); @@ -630,50 +679,50 @@ view_snap_to_edge(struct view *view, const char *direction) return; } - struct wlr_box dst = view_get_edge_snap_box(view, output, edge); - - if (view->x == dst.x && view->y == dst.y && view->w == dst.width - && view->h == dst.height) { - /* Move over to the next screen if this is already snapped. */ - struct wlr_box usable = - output_usable_area_in_layout_coords(output); + if (view->tiled == edge) { + /* We are already tiled for this edge and thus should switch outputs */ + struct wlr_output *new_output = NULL; + struct wlr_output *current_output = output->wlr_output; + struct wlr_output_layout *layout = view->server->output_layout; switch (edge) { case VIEW_EDGE_LEFT: - dst.x -= (usable.width / 2) + 1; + new_output = wlr_output_layout_adjacent_output( + layout, WLR_DIRECTION_LEFT, current_output, 1, 0); break; case VIEW_EDGE_RIGHT: - dst.x += (usable.width / 2) + 1; + new_output = wlr_output_layout_adjacent_output( + layout, WLR_DIRECTION_RIGHT, current_output, 1, 0); break; case VIEW_EDGE_UP: - dst.y -= (usable.height / 2) + 1; + new_output = wlr_output_layout_adjacent_output( + layout, WLR_DIRECTION_UP, current_output, 0, 1); break; case VIEW_EDGE_DOWN: - dst.y += (usable.height / 2) + 1; + new_output = wlr_output_layout_adjacent_output( + layout, WLR_DIRECTION_DOWN, current_output, 0, 1); break; default: break; } - - struct wlr_output *new_wlr_output = wlr_output_layout_output_at( - view->server->output_layout, dst.x, dst.y); - struct output *new_output = - output_from_wlr_output(view->server, new_wlr_output); - - if (new_output == output || !new_output - || edge == VIEW_EDGE_CENTER) { + if (new_output && new_output != current_output) { + /* Move to next output */ + edge = view_edge_invert(edge); + output = output_from_wlr_output(view->server, new_output); + } else { + /* No more output to move to */ return; } - - dst = view_get_edge_snap_box(view, new_output, - view_edge_invert(edge)); } - if (view->w == dst.width && view->h == dst.height) { - /* move horizontally/vertically without changing size */ - view_move(view, dst.x, dst.y); - } else { - view_move_resize(view, dst); + if (view->maximized) { + /* Unmaximize + keep using existing natural_geometry */ + view_maximize(view, false); + } else if (!view->tiled) { + /* store current geometry as new natural_geometry */ + view_store_natural_geometry(view); } + view->tiled = edge; + view_apply_tiled_geometry(view, output); } const char * diff --git a/src/xdg.c b/src/xdg.c index 611691c6..b7c9edf3 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -298,7 +298,6 @@ xdg_toplevel_view_map(struct view *view) view->ssd.enabled = has_ssd(view); if (view->ssd.enabled) { - view->margin = ssd_thickness(view); ssd_create(view); } @@ -311,7 +310,6 @@ xdg_toplevel_view_map(struct view *view) } view_discover_output(view); - view->been_mapped = true; } diff --git a/src/xwayland-unmanaged.c b/src/xwayland-unmanaged.c index 68fe4a0a..4c68223b 100644 --- a/src/xwayland-unmanaged.c +++ b/src/xwayland-unmanaged.c @@ -20,6 +20,8 @@ unmanaged_handle_commit(struct wl_listener *listener, void *data) struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; unmanaged->lx = xsurface->x; unmanaged->ly = xsurface->y; + wlr_scene_node_set_position(unmanaged->node, + unmanaged->lx, unmanaged->ly); } static void @@ -33,6 +35,8 @@ unmanaged_handle_set_geometry(struct wl_listener *listener, void *data) wlr_log(WLR_DEBUG, "xwayland-unmanaged surface has moved"); unmanaged->lx = xsurface->x; unmanaged->ly = xsurface->y; + wlr_scene_node_set_position(unmanaged->node, + unmanaged->lx, unmanaged->ly); } } @@ -59,10 +63,11 @@ unmanaged_handle_map(struct wl_listener *listener, void *data) } /* node will be destroyed automatically once surface is destroyed */ - struct wlr_scene_node *node = &wlr_scene_surface_create( + unmanaged->node = &wlr_scene_surface_create( unmanaged->server->unmanaged_tree, xsurface->surface)->buffer->node; - wlr_scene_node_set_position(node, unmanaged->lx, unmanaged->ly); + wlr_scene_node_set_position(unmanaged->node, + unmanaged->lx, unmanaged->ly); } static void diff --git a/src/xwayland.c b/src/xwayland.c index 334dd3b2..9c516433 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -144,6 +144,14 @@ handle_request_configure(struct wl_listener *listener, void *data) int height = event->height; view_adjust_size(view, &width, &height); + view->pending_move_resize.update_x = event->x != view->x; + view->pending_move_resize.update_y = event->y != view->y; + view->pending_move_resize.x = event->x; + view->pending_move_resize.y = event->y; + view->pending_move_resize.width = width; + view->pending_move_resize.height = height; + + wlr_scene_node_set_position(&view->scene_tree->node, event->x, event->y); wlr_xwayland_surface_configure(view->xwayland_surface, event->x, event->y, width, height); } @@ -216,6 +224,13 @@ move(struct view *view, double x, double y) { view->x = x; view->y = y; + + /* override any previous pending move */ + view->pending_move_resize.update_x = false; + view->pending_move_resize.update_y = false; + view->pending_move_resize.x = x; + view->pending_move_resize.y = y; + struct wlr_xwayland_surface *s = view->xwayland_surface; wlr_xwayland_surface_configure(s, (int16_t)x, (int16_t)y, (uint16_t)s->width, (uint16_t)s->height); @@ -329,7 +344,7 @@ map(struct view *view) if (!view->been_mapped) { view->ssd.enabled = want_deco(view); if (view->ssd.enabled) { - view->margin = ssd_thickness(view); + ssd_create(view); } if (!view->maximized && !view->fullscreen) { @@ -341,11 +356,6 @@ map(struct view *view) } view_discover_output(view); - - if (view->ssd.enabled) { - /* Create ssd after view_disover_output() had been called */ - ssd_create(view); - } view->been_mapped = true; }