summaryrefslogtreecommitdiff
path: root/openbox/tiling.c
diff options
context:
space:
mode:
Diffstat (limited to 'openbox/tiling.c')
-rw-r--r--openbox/tiling.c989
1 files changed, 989 insertions, 0 deletions
diff --git a/openbox/tiling.c b/openbox/tiling.c
new file mode 100644
index 00000000..ec24b0df
--- /dev/null
+++ b/openbox/tiling.c
@@ -0,0 +1,989 @@
+#include "tiling.h"
+#include "client.h"
+#include "config.h"
+#include "event.h"
+#include "focus.h"
+#include "frame.h"
+#include "moveresize.h"
+#include "openbox.h"
+#include "popup.h"
+#include "screen.h"
+#include "stacking.h"
+#include "window.h"
+#include "obt/display.h"
+#include "obt/prop.h"
+#include "obrender/render.h"
+
+#define MAX_TILES 32
+#define DROP_EDGE_PERCENT 10
+#define OUTLINE_WIDTH 3
+#define SHARE_SCALE 10000
+
+typedef struct _TileLayout TileLayout;
+
+typedef struct {
+ ObClient *client;
+ TileLayout *layout;
+ guint stack;
+ guint row;
+ gdouble height_weight;
+ Rect saved;
+ Rect pre_max;
+ gboolean max_horz;
+ gboolean max_vert;
+ gboolean snapped_left;
+ gboolean snapped_right;
+} Tile;
+
+struct _TileLayout {
+ guint monitor;
+ guint desktop;
+ gboolean enabled;
+ gboolean main_right;
+ guint main_share;
+ guint count[2];
+ Tile *slots[2][MAX_TILES];
+};
+
+static GList *layouts;
+static GList *tiles;
+static ObClient *keys[12];
+static ObInternalWindow outline[4];
+static guint pending;
+static gboolean arranging;
+static ObClient *preview_client;
+static gint preview_x;
+static gint preview_y;
+static ObClient *resize_client;
+static guint resize_share;
+static gint resize_client_width;
+static gint resize_column_width;
+static gint resize_client_height;
+static gint resize_client_y;
+static guint resize_count;
+static Tile *resize_members[MAX_TILES];
+static gdouble resize_weights[MAX_TILES];
+static gint resize_heights[MAX_TILES];
+static ObPopup *notification;
+static guint notification_timer;
+static gchar *notification_text;
+
+static gboolean hide_notification(gpointer data)
+{
+ popup_hide(notification);
+ notification_timer = 0;
+ return FALSE;
+}
+
+static void show_notification(guint monitor, const gchar *text)
+{
+ const Rect *area;
+
+ if (!config_desktop_popup_time || monitor >= screen_num_monitors)
+ return;
+ if (!notification) {
+ notification = popup_new();
+ popup_height(notification, POPUP_HEIGHT);
+ popup_set_text_align(notification, RR_JUSTIFY_CENTER);
+ }
+ if (notification_timer)
+ g_source_remove(notification_timer);
+ g_free(notification_text);
+ notification_text = g_strdup(text);
+ area = screen_physical_area_monitor(monitor);
+ popup_position(notification, CenterGravity,
+ area->x + area->width / 2, area->y + area->height / 2);
+ popup_max_width(notification, MIN(area->width,
+ MAX(area->width / 3, POPUP_WIDTH)));
+ OBT_PROP_SETS(notification->bg, NET_WM_NAME, notification_text);
+ popup_show(notification, notification_text);
+ notification_timer = g_timeout_add(config_desktop_popup_time,
+ hide_notification, NULL);
+}
+
+static Tile *find_tile(ObClient *client)
+{
+ GList *it;
+
+ for (it = tiles; it; it = it->next) {
+ Tile *tile = it->data;
+ if (tile->client == client)
+ return tile;
+ }
+ return NULL;
+}
+
+static TileLayout *find_layout(guint monitor, guint desktop, gboolean create)
+{
+ GList *it;
+ TileLayout *layout;
+
+ if (monitor >= screen_num_monitors || desktop >= screen_num_desktops)
+ return NULL;
+ for (it = layouts; it; it = it->next) {
+ layout = it->data;
+ if (layout->monitor == monitor && layout->desktop == desktop)
+ return layout;
+ }
+ if (!create)
+ return NULL;
+ layout = g_new0(TileLayout, 1);
+ layout->monitor = monitor;
+ layout->desktop = desktop;
+ layout->main_share = SHARE_SCALE * 3 / 5;
+ layout->count[0] = layout->count[1] = 1;
+ layouts = g_list_append(layouts, layout);
+ return layout;
+}
+
+static gboolean eligible(ObClient *client)
+{
+ return client && client->managed && client->frame &&
+ client->type == OB_CLIENT_TYPE_NORMAL && !client->transient &&
+ !client->above && !client->fullscreen && !client->iconic &&
+ !client->shaded && client->desktop != DESKTOP_ALL &&
+ (client->functions & OB_CLIENT_FUNC_MOVE) &&
+ (client->functions & OB_CLIENT_FUNC_RESIZE);
+}
+
+static Rect slot_area_for_count(TileLayout *layout, guint stack, guint row,
+ guint count)
+{
+ Rect *work = screen_area(layout->desktop, layout->monitor, NULL);
+ Rect result = *work;
+ gint split = ((gint64)work->width * layout->main_share + SHARE_SCALE / 2) /
+ SHARE_SCALE;
+ gdouble total = 0, before = 0, through = 0;
+ guint i;
+ gint top, bottom;
+
+ for (i = 0; i < count; ++i) {
+ Tile *tile = layout->slots[stack][i];
+ gdouble weight = tile ? tile->height_weight : 1.0;
+
+ total += weight;
+ if (i < row)
+ before += weight;
+ if (i <= row)
+ through += weight;
+ }
+ top = work->height * before / total;
+ bottom = row + 1 == count ? work->height : work->height * through / total;
+
+ if (layout->main_right) {
+ if (stack == 0) {
+ result.x += work->width - split;
+ result.width = split;
+ } else
+ result.width = work->width - split;
+ } else {
+ if (stack == 0)
+ result.width = split;
+ else {
+ result.x += split;
+ result.width -= split;
+ }
+ }
+ result.y += top;
+ result.height = bottom - top;
+ g_slice_free(Rect, work);
+ return result;
+}
+
+static Rect slot_area(TileLayout *layout, guint stack, guint row)
+{
+ return slot_area_for_count(layout, stack, row, layout->count[stack]);
+}
+
+static void hide_outline(void)
+{
+ guint i;
+ gulong serial;
+
+ if (!outline[0].window)
+ return;
+ serial = event_start_ignore_all_enters();
+
+ for (i = 0; i < G_N_ELEMENTS(outline); ++i)
+ if (outline[i].window)
+ XUnmapWindow(obt_display, outline[i].window);
+ event_end_ignore_all_enters(serial);
+}
+
+static void show_outline(const Rect *area)
+{
+ guint i;
+ Rect edges[4];
+ XSetWindowAttributes attr;
+ gulong serial = event_start_ignore_all_enters();
+ gint border = MIN(OUTLINE_WIDTH, MIN(area->width, area->height));
+
+ RECT_SET(edges[0], area->x, area->y, area->width, border);
+ RECT_SET(edges[1], area->x, area->y, border, area->height);
+ RECT_SET(edges[2], area->x + area->width - border, area->y,
+ border, area->height);
+ RECT_SET(edges[3], area->x, area->y + area->height - border,
+ area->width, border);
+ attr.override_redirect = True;
+ attr.background_pixel = WhitePixel(obt_display, ob_screen);
+ for (i = 0; i < G_N_ELEMENTS(outline); ++i) {
+ if (!outline[i].window) {
+ outline[i].type = OB_WINDOW_CLASS_INTERNAL;
+ outline[i].window = XCreateWindow(
+ obt_display, obt_root(ob_screen), 0, 0, 1, 1, 0,
+ CopyFromParent, InputOutput, CopyFromParent,
+ CWOverrideRedirect | CWBackPixel, &attr);
+ stacking_add(INTERNAL_AS_WINDOW(&outline[i]));
+ }
+ XMoveResizeWindow(obt_display, outline[i].window,
+ edges[i].x, edges[i].y,
+ edges[i].width, edges[i].height);
+ XMapWindow(obt_display, outline[i].window);
+ stacking_raise(INTERNAL_AS_WINDOW(&outline[i]));
+ }
+ event_end_ignore_all_enters(serial);
+}
+
+static void remove_tile(Tile *tile, gboolean restore)
+{
+ ObClient *client = tile->client;
+
+ tile->layout->slots[tile->stack][tile->row] = NULL;
+ tiles = g_list_remove(tiles, tile);
+ if (restore && client->managed) {
+ if (client->fullscreen) {
+ client->pre_fullscreen_area = tile->saved;
+ client->pre_fullscreen_max_horz = tile->max_horz;
+ client->pre_fullscreen_max_vert = tile->max_vert;
+ if (tile->max_horz) {
+ client->pre_fullscreen_area.x = tile->pre_max.x;
+ client->pre_fullscreen_area.width = tile->pre_max.width;
+ }
+ if (tile->max_vert) {
+ client->pre_fullscreen_area.y = tile->pre_max.y;
+ client->pre_fullscreen_area.height = tile->pre_max.height;
+ }
+ } else {
+ client_configure(client, tile->saved.x, tile->saved.y,
+ tile->saved.width, tile->saved.height,
+ TRUE, TRUE, FALSE);
+ if (tile->max_horz)
+ client_maximize(client, TRUE, 1);
+ if (tile->max_vert)
+ client_maximize(client, TRUE, 2);
+ }
+ client->pre_max_area = tile->pre_max;
+ client->snapped_left = tile->snapped_left;
+ client->snapped_right = tile->snapped_right;
+ }
+ g_free(tile);
+}
+
+static gboolean place_tile(Tile *tile)
+{
+ ObClient *client = tile->client;
+ Rect area = slot_area(tile->layout, tile->stack, tile->row);
+ gint x = area.x;
+ gint y = area.y;
+ gint width = MAX(1, area.width - client->frame->size.left -
+ client->frame->size.right);
+ gint height = MAX(1, area.height - client->frame->size.top -
+ client->frame->size.bottom);
+ gint logical_width, logical_height;
+
+ frame_frame_gravity(client->frame, &x, &y);
+ client_try_configure(client, &x, &y, &width, &height,
+ &logical_width, &logical_height, TRUE);
+ if (width + client->frame->size.left + client->frame->size.right >
+ area.width ||
+ height + client->frame->size.top + client->frame->size.bottom >
+ area.height)
+ return FALSE;
+ client_configure(client, x, y, width, height, TRUE, TRUE, FALSE);
+ return TRUE;
+}
+
+static void compact_stack(TileLayout *layout, guint stack)
+{
+ guint row, next = 0;
+
+ for (row = 0; row < layout->count[stack]; ++row) {
+ Tile *tile = layout->slots[stack][row];
+
+ layout->slots[stack][row] = NULL;
+ if (tile) {
+ tile->row = next;
+ layout->slots[stack][next++] = tile;
+ }
+ }
+ layout->count[stack] = MAX(1, next);
+}
+
+static void queue_arrange(void);
+
+static gboolean arrange(gpointer data)
+{
+ GList *it, *next;
+ gboolean retry = FALSE;
+
+ pending = 0;
+ arranging = TRUE;
+ for (it = tiles; it; it = next) {
+ Tile *tile = it->data;
+ ObClient *client = tile->client;
+ TileLayout *layout = tile->layout;
+ next = it->next;
+
+ if (moveresize_in_progress && moveresize_client == client)
+ continue;
+ if (!layout->enabled || layout->monitor >= screen_num_monitors ||
+ layout->desktop >= screen_num_desktops) {
+ remove_tile(tile, TRUE);
+ continue;
+ }
+ if (client->above) {
+ remove_tile(tile, TRUE);
+ continue;
+ }
+ if (client->desktop != layout->desktop) {
+ remove_tile(tile, TRUE);
+ continue;
+ }
+ if (client->iconic || client->fullscreen || client->shaded)
+ continue;
+ if (!eligible(client) || client_monitor(client) != layout->monitor ||
+ client->max_horz || client->max_vert) {
+ remove_tile(tile, FALSE);
+ continue;
+ }
+ }
+ for (it = layouts; it; it = it->next) {
+ TileLayout *layout = it->data;
+
+ if (layout->enabled) {
+ compact_stack(layout, 0);
+ compact_stack(layout, 1);
+ }
+ }
+ for (it = tiles; it; it = next) {
+ Tile *tile = it->data;
+ next = it->next;
+
+ if (!eligible(tile->client) ||
+ (moveresize_in_progress && moveresize_client == tile->client))
+ continue;
+ if (!place_tile(tile)) {
+ remove_tile(tile, TRUE);
+ retry = TRUE;
+ }
+ }
+ arranging = FALSE;
+ if (retry)
+ queue_arrange();
+ return FALSE;
+}
+
+static void queue_arrange(void)
+{
+ if (!arranging && !pending && layouts)
+ pending = g_idle_add(arrange, NULL);
+}
+
+static Tile *add_tile(ObClient *client, TileLayout *layout,
+ guint stack, guint row)
+{
+ Tile *tile = g_new0(Tile, 1);
+
+ tile->client = client;
+ tile->layout = layout;
+ tile->stack = stack;
+ tile->row = row;
+ tile->height_weight = 1.0;
+ tile->saved = client->area;
+ tile->pre_max = client->pre_max_area;
+ tile->max_horz = client->max_horz;
+ tile->max_vert = client->max_vert;
+ tile->snapped_left = client->snapped_left;
+ tile->snapped_right = client->snapped_right;
+ client->snapped_left = client->snapped_right = FALSE;
+ if (client->max_horz || client->max_vert)
+ client_maximize(client, FALSE, 0);
+ layout->slots[stack][row] = tile;
+ tiles = g_list_append(tiles, tile);
+ return tile;
+}
+
+static void put_client(ObClient *client, TileLayout *layout,
+ guint stack, guint row)
+{
+ Tile *tile = find_tile(client);
+ Tile *displaced = layout->slots[stack][row];
+
+ if (tile == displaced && tile) {
+ stacking_raise(CLIENT_AS_WINDOW(client));
+ return;
+ }
+ if (displaced && (displaced->client->fullscreen ||
+ displaced->client->iconic || displaced->client->shaded)) {
+ remove_tile(displaced, TRUE);
+ displaced = NULL;
+ }
+ if (tile) {
+ TileLayout *old = tile->layout;
+ guint old_stack = tile->stack;
+ guint old_row = tile->row;
+
+ old->slots[old_stack][old_row] = displaced;
+ if (displaced) {
+ displaced->layout = old;
+ displaced->stack = old_stack;
+ displaced->row = old_row;
+ if (place_tile(displaced))
+ stacking_raise(CLIENT_AS_WINDOW(displaced->client));
+ }
+ tile->layout = layout;
+ tile->stack = stack;
+ tile->row = row;
+ layout->slots[stack][row] = tile;
+ } else {
+ if (displaced)
+ remove_tile(displaced, TRUE);
+ tile = add_tile(client, layout, stack, row);
+ }
+ if (place_tile(tile))
+ stacking_raise(CLIENT_AS_WINDOW(client));
+ queue_arrange();
+}
+
+static guint stack_members(TileLayout *layout, guint stack, Tile *exclude)
+{
+ guint row, count = 0;
+
+ for (row = 0; row < layout->count[stack]; ++row)
+ if (layout->slots[stack][row] && layout->slots[stack][row] != exclude)
+ ++count;
+ return count;
+}
+
+static void append_client(ObClient *client, TileLayout *layout, guint stack)
+{
+ Tile *tile = find_tile(client);
+ guint row, next = 0;
+ guint count = stack_members(layout, stack, tile);
+
+ if (count == MAX_TILES)
+ return;
+ if (tile)
+ tile->layout->slots[tile->stack][tile->row] = NULL;
+ for (row = 0; row < layout->count[stack]; ++row) {
+ Tile *member = layout->slots[stack][row];
+
+ layout->slots[stack][row] = NULL;
+ if (member) {
+ member->row = next;
+ layout->slots[stack][next++] = member;
+ }
+ }
+ layout->count[stack] = count + 1;
+ if (tile) {
+ tile->layout = layout;
+ tile->stack = stack;
+ tile->row = count;
+ layout->slots[stack][count] = tile;
+ } else
+ tile = add_tile(client, layout, stack, count);
+ if (place_tile(tile))
+ stacking_raise(CLIENT_AS_WINDOW(client));
+ queue_arrange();
+}
+
+static ObClient *next_floating(TileLayout *layout)
+{
+ GList *it;
+
+ if (eligible(focus_client) && !find_tile(focus_client) &&
+ focus_client->desktop == layout->desktop &&
+ client_monitor(focus_client) == layout->monitor)
+ return focus_client;
+ for (it = client_list; it; it = it->next) {
+ ObClient *client = it->data;
+ if (eligible(client) && !find_tile(client) &&
+ client->desktop == layout->desktop &&
+ client_monitor(client) == layout->monitor)
+ return client;
+ }
+ return NULL;
+}
+
+void tiling_toggle(guint monitor)
+{
+ TileLayout *layout = find_layout(monitor, screen_desktop, TRUE);
+ guint stack, row;
+
+ if (!layout || moveresize_in_progress)
+ return;
+ layout->enabled = !layout->enabled;
+ for (stack = 0; stack < 2; ++stack)
+ for (row = 0; row < layout->count[stack]; ++row) {
+ if (!layout->enabled) {
+ if (layout->slots[stack][row])
+ remove_tile(layout->slots[stack][row], TRUE);
+ } else {
+ ObClient *client = next_floating(layout);
+ if (client)
+ put_client(client, layout, stack, row);
+ }
+ }
+ queue_arrange();
+ hide_outline();
+ show_notification(monitor, layout->enabled ? "tiling: on" : "tiling: off");
+}
+
+void tiling_flip(guint monitor)
+{
+ TileLayout *layout = find_layout(monitor, screen_desktop, TRUE);
+
+ if (layout && !moveresize_in_progress) {
+ layout->main_right = !layout->main_right;
+ queue_arrange();
+ }
+}
+
+void tiling_change_count(guint monitor, gboolean main_stack, gint delta)
+{
+ TileLayout *layout = find_layout(monitor, screen_desktop, TRUE);
+ guint stack = main_stack ? 0 : 1;
+ guint old, count, row;
+ gchar *message;
+
+ if (!layout || moveresize_in_progress)
+ return;
+ old = layout->count[stack];
+ count = CLAMP((gint)old + CLAMP(delta, -MAX_TILES, MAX_TILES), 1, MAX_TILES);
+ for (row = count; row < old; ++row)
+ if (layout->slots[stack][row])
+ remove_tile(layout->slots[stack][row], TRUE);
+ layout->count[stack] = count;
+ if (layout->enabled)
+ for (row = old; row < count; ++row) {
+ ObClient *client = next_floating(layout);
+ if (client)
+ put_client(client, layout, stack, row);
+ }
+ queue_arrange();
+ if (layout->enabled) {
+ compact_stack(layout, 0);
+ compact_stack(layout, 1);
+ }
+ message = g_strdup_printf("main: %u | side: %u",
+ layout->count[0], layout->count[1]);
+ show_notification(monitor, message);
+ g_free(message);
+}
+
+void tiling_promote(ObClient *client)
+{
+ TileLayout *layout;
+
+ if (!eligible(client) || moveresize_in_progress)
+ return;
+ layout = find_layout(client_monitor(client), client->desktop, FALSE);
+ if (layout && layout->enabled)
+ put_client(client, layout, 0, 0);
+}
+
+guint tiling_key(ObClient *client)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS(keys); ++i)
+ if (keys[i] == client)
+ return i + 1;
+ return 0;
+}
+
+void tiling_assign(ObClient *client, guint key)
+{
+ guint old;
+ ObClient *previous;
+ gchar *message;
+
+ if (!client || !client->managed || key > G_N_ELEMENTS(keys))
+ return;
+ old = tiling_key(client);
+ if (old)
+ keys[old - 1] = NULL;
+ previous = key ? keys[key - 1] : NULL;
+ if (key)
+ keys[key - 1] = client;
+ if (previous && previous != client)
+ client_update_title(previous);
+ client_update_title(client);
+ message = key ? g_strdup_printf("[F%u] -> %s", key, client->original_title) :
+ g_strdup("window key cleared");
+ show_notification(client_monitor(client), message);
+ g_free(message);
+}
+
+void tiling_recall(guint key)
+{
+ ObClient *client;
+
+ if (!key || key > G_N_ELEMENTS(keys) || moveresize_in_progress)
+ return;
+ client = keys[key - 1];
+ if (client) {
+ client_activate(client, TRUE, FALSE, TRUE, TRUE, TRUE);
+ tiling_promote(client);
+ }
+}
+
+static TileLayout *drop_target(ObClient *client, gint x, gint y,
+ guint *stack, guint *row, Rect *area)
+{
+ TileLayout *layout;
+ guint monitor;
+ const Rect *physical;
+ gint edge;
+ gboolean right;
+ TileLayout preview;
+ Tile incoming;
+ Tile *moving_tile;
+ guint i, next = 0;
+
+ if (!eligible(client))
+ return NULL;
+ monitor = screen_find_monitor_point(x, y);
+ layout = find_layout(monitor, client->desktop, FALSE);
+ if (!layout || !layout->enabled)
+ return NULL;
+ physical = screen_physical_area_monitor(monitor);
+ if (!RECT_CONTAINS(*physical, x, y))
+ return NULL;
+ edge = MAX(1, physical->width * DROP_EDGE_PERCENT / 100);
+ right = x >= physical->x + physical->width - edge;
+ if (!right && x >= physical->x + edge)
+ return NULL;
+ *stack = right == layout->main_right ? 0 : 1;
+ *row = stack_members(layout, *stack, find_tile(client));
+ if (*row == MAX_TILES)
+ return NULL;
+ preview = *layout;
+ moving_tile = find_tile(client);
+ for (i = 0; i < MAX_TILES; ++i)
+ preview.slots[*stack][i] = NULL;
+ for (i = 0; i < layout->count[*stack]; ++i) {
+ Tile *member = layout->slots[*stack][i];
+
+ if (member && member != moving_tile)
+ preview.slots[*stack][next++] = member;
+ }
+ incoming.height_weight = moving_tile ? moving_tile->height_weight : 1.0;
+ preview.slots[*stack][*row] = &incoming;
+ preview.count[*stack] = *row + 1;
+ *area = slot_area(&preview, *stack, *row);
+ return layout;
+}
+
+gboolean tiling_move_preview(ObClient *client, gint x, gint y)
+{
+ guint stack, row;
+ Rect area;
+ TileLayout *layout = find_layout(screen_find_monitor_point(x, y),
+ client->desktop, FALSE);
+
+ preview_client = client;
+ preview_x = x;
+ preview_y = y;
+ if (drop_target(client, x, y, &stack, &row, &area))
+ show_outline(&area);
+ else
+ hide_outline();
+ return find_tile(client) || (layout && layout->enabled);
+}
+
+void tiling_move_end(ObClient *client, gboolean cancel)
+{
+ guint stack, row;
+ Rect area;
+ TileLayout *layout;
+ Tile *tile;
+
+ hide_outline();
+ if (cancel || preview_client != client) {
+ preview_client = NULL;
+ tile = find_tile(client);
+ if (tile)
+ place_tile(tile);
+ return;
+ }
+ layout = drop_target(client, preview_x, preview_y, &stack, &row, &area);
+ preview_client = NULL;
+ if (layout)
+ append_client(client, layout, stack);
+ else {
+ tile = find_tile(client);
+ if (tile)
+ remove_tile(tile, FALSE);
+ }
+ queue_arrange();
+}
+
+void tiling_action_moved(ObClient *client)
+{
+ TileLayout *layout;
+ Tile *tile;
+ Rect *work;
+ gint left, right, edge;
+ guint stack;
+
+ if (!eligible(client) || client->max_horz || client->max_vert ||
+ moveresize_in_progress)
+ return;
+ tile = find_tile(client);
+ layout = find_layout(client_monitor(client), client->desktop, FALSE);
+ if (!layout || !layout->enabled) {
+ if (tile)
+ remove_tile(tile, FALSE);
+ queue_arrange();
+ return;
+ }
+ work = screen_area(layout->desktop, layout->monitor, NULL);
+ left = ABS(client->frame->area.x - work->x);
+ right = ABS(RECT_RIGHT(client->frame->area) - RECT_RIGHT(*work));
+ edge = MAX(1, work->width * DROP_EDGE_PERCENT / 100);
+ g_slice_free(Rect, work);
+ if (MIN(left, right) > edge) {
+ if (tile)
+ remove_tile(tile, FALSE);
+ queue_arrange();
+ return;
+ }
+ stack = (right < left) == layout->main_right ? 0 : 1;
+ if (tile && tile->layout == layout && tile->stack == stack) {
+ stacking_raise(CLIENT_AS_WINDOW(client));
+ queue_arrange();
+ } else
+ append_client(client, layout, stack);
+}
+
+void tiling_resize_begin(ObClient *client)
+{
+ Tile *tile = find_tile(client);
+ guint row;
+
+ resize_client = NULL;
+ if (tile) {
+ Rect area = slot_area(tile->layout, tile->stack, tile->row);
+
+ resize_client = client;
+ resize_share = tile->layout->main_share;
+ resize_client_width = client->area.width;
+ resize_column_width = area.width;
+ resize_client_height = client->area.height;
+ resize_client_y = client->area.y;
+ resize_count = tile->layout->count[tile->stack];
+ for (row = 0; row < resize_count; ++row) {
+ Tile *member = tile->layout->slots[tile->stack][row];
+ Rect slot = slot_area(tile->layout, tile->stack, row);
+
+ resize_members[row] = member;
+ resize_weights[row] = member ? member->height_weight : 1.0;
+ resize_heights[row] = slot.height;
+ }
+ }
+}
+
+static gint stack_min_width(TileLayout *layout, guint stack)
+{
+ guint row;
+ gint width = 1;
+
+ for (row = 0; row < layout->count[stack]; ++row) {
+ Tile *tile = layout->slots[stack][row];
+
+ if (tile) {
+ ObClient *client = tile->client;
+ width = MAX(width, client->min_size.width +
+ client->frame->size.left + client->frame->size.right);
+ }
+ }
+ return width;
+}
+
+static void restore_resize_weights(Tile *tile)
+{
+ guint row, old;
+
+ for (row = 0; row < tile->layout->count[tile->stack]; ++row) {
+ Tile *member = tile->layout->slots[tile->stack][row];
+
+ if (member)
+ for (old = 0; old < resize_count; ++old)
+ if (member == resize_members[old]) {
+ member->height_weight = resize_weights[old];
+ break;
+ }
+ }
+}
+
+static gint tile_min_height(Tile *tile)
+{
+ ObClient *client = tile->client;
+
+ return MAX(1, client->min_size.height) + client->frame->size.top +
+ client->frame->size.bottom;
+}
+
+static void resize_vertical(Tile *tile, gint y, gint height)
+{
+ guint row, neighbor;
+ Tile *other;
+ gint pair_height, minimum, maximum, requested;
+ gdouble pair_weight;
+
+ if (resize_count != tile->layout->count[tile->stack])
+ return;
+ for (row = 0; row < resize_count; ++row)
+ if (tile->layout->slots[tile->stack][row] != resize_members[row])
+ return;
+ restore_resize_weights(tile);
+ if (y != resize_client_y) {
+ if (!tile->row)
+ return;
+ neighbor = tile->row - 1;
+ } else {
+ if (tile->row + 1 == resize_count)
+ return;
+ neighbor = tile->row + 1;
+ }
+ other = tile->layout->slots[tile->stack][neighbor];
+ if (!other)
+ return;
+ pair_height = resize_heights[tile->row] + resize_heights[neighbor];
+ /* Keep rounding from violating either window's minimum height. */
+ minimum = tile_min_height(tile) + 1;
+ maximum = pair_height - tile_min_height(other) - 1;
+ if (minimum > maximum || height == resize_client_height)
+ return;
+ requested = CLAMP(resize_heights[tile->row] + height - resize_client_height,
+ minimum, maximum);
+ pair_weight = resize_weights[tile->row] + resize_weights[neighbor];
+ tile->height_weight = pair_weight * requested / pair_height;
+ other->height_weight = pair_weight - tile->height_weight;
+}
+
+void tiling_resize_adjust(ObClient *client, gint *x, gint *y,
+ gint *width, gint *height)
+{
+ Tile *tile = find_tile(client);
+ TileLayout *layout;
+ Rect *work, area;
+ gint column_width, minimum, maximum;
+
+ if (client != resize_client || !tile)
+ return;
+ layout = tile->layout;
+ work = screen_area(layout->desktop, layout->monitor, NULL);
+ minimum = stack_min_width(layout, 0);
+ maximum = work->width - stack_min_width(layout, 1);
+ if (tile->stack == 0 && minimum <= maximum && work->width > 0) {
+ column_width = CLAMP(resize_column_width + *width - resize_client_width,
+ minimum, maximum);
+ layout->main_share = CLAMP(
+ ((gint64)column_width * SHARE_SCALE + work->width / 2) / work->width,
+ 1, SHARE_SCALE - 1);
+ }
+ g_slice_free(Rect, work);
+ resize_vertical(tile, *y, *height);
+ area = slot_area(layout, tile->stack, tile->row);
+ *x = area.x;
+ *y = area.y;
+ *width = MAX(1, area.width - client->frame->size.left - client->frame->size.right);
+ *height = MAX(1, area.height - client->frame->size.top - client->frame->size.bottom);
+ frame_frame_gravity(client->frame, x, y);
+ queue_arrange();
+}
+
+void tiling_resize_end(ObClient *client, gboolean cancel)
+{
+ Tile *tile = find_tile(client);
+
+ if (resize_client == client && tile && cancel) {
+ tile->layout->main_share = resize_share;
+ restore_resize_weights(tile);
+ }
+ resize_client = NULL;
+ queue_arrange();
+}
+
+void tiling_forget(ObClient *client)
+{
+ Tile *tile = find_tile(client);
+ guint key = tiling_key(client);
+
+ if (key)
+ keys[key - 1] = NULL;
+ if (resize_client == client)
+ resize_client = NULL;
+ if (preview_client == client) {
+ preview_client = NULL;
+ hide_outline();
+ }
+ if (tile)
+ remove_tile(tile, FALSE);
+ queue_arrange();
+}
+
+void tiling_client_changed(ObClient *client)
+{
+ if (find_tile(client))
+ queue_arrange();
+}
+
+void tiling_screen_changed(void)
+{
+ hide_outline();
+ queue_arrange();
+}
+
+void tiling_shutdown(gboolean reconfigure)
+{
+ guint i;
+
+ if (notification_timer) {
+ g_source_remove(notification_timer);
+ notification_timer = 0;
+ }
+ popup_free(notification);
+ notification = NULL;
+ g_free(notification_text);
+ notification_text = NULL;
+ if (reconfigure)
+ return;
+
+ if (pending) {
+ g_source_remove(pending);
+ pending = 0;
+ }
+ arranging = TRUE;
+ while (tiles)
+ remove_tile(tiles->data, TRUE);
+ arranging = FALSE;
+ while (layouts) {
+ g_free(layouts->data);
+ layouts = g_list_delete_link(layouts, layouts);
+ }
+ for (i = 0; i < G_N_ELEMENTS(keys); ++i) {
+ ObClient *client = keys[i];
+ keys[i] = NULL;
+ if (client)
+ client_update_title(client);
+ }
+ for (i = 0; i < G_N_ELEMENTS(outline); ++i)
+ if (outline[i].window) {
+ stacking_remove(INTERNAL_AS_WINDOW(&outline[i]));
+ XDestroyWindow(obt_display, outline[i].window);
+ outline[i].window = None;
+ }
+}