summaryrefslogtreecommitdiff
path: root/openbox
diff options
context:
space:
mode:
Diffstat (limited to 'openbox')
-rw-r--r--openbox/actions/all.c1
-rw-r--r--openbox/actions/all.h1
-rw-r--r--openbox/actions/growtoedge.c3
-rw-r--r--openbox/actions/moverelative.c2
-rw-r--r--openbox/actions/moveresizeto.c6
-rw-r--r--openbox/actions/movetoedge.c10
-rw-r--r--openbox/actions/resizerelative.c2
-rw-r--r--openbox/actions/tiling.c134
-rw-r--r--openbox/client.c10
-rw-r--r--openbox/moveresize.c17
-rw-r--r--openbox/openbox.c3
-rw-r--r--openbox/screen.c2
-rw-r--r--openbox/tiling.c989
-rw-r--r--openbox/tiling.h28
14 files changed, 1202 insertions, 6 deletions
diff --git a/openbox/actions/all.c b/openbox/actions/all.c
index 332e79ca..002813dc 100644
--- a/openbox/actions/all.c
+++ b/openbox/actions/all.c
@@ -39,6 +39,7 @@ void action_all_startup(void)
action_growtoedge_startup();
action_if_startup();
action_focustobottom_startup();
+ action_tiling_startup();
/* 3.4-compatibility */
action_shadelowerraise_startup();
}
diff --git a/openbox/actions/all.h b/openbox/actions/all.h
index 54d63195..0e5c4730 100644
--- a/openbox/actions/all.h
+++ b/openbox/actions/all.h
@@ -40,6 +40,7 @@ void action_movetoedge_startup(void);
void action_growtoedge_startup(void);
void action_if_startup(void);
void action_focustobottom_startup(void);
+void action_tiling_startup(void);
/* 3.4-compatibility */
void action_shadelowerraise_startup(void);
diff --git a/openbox/actions/growtoedge.c b/openbox/actions/growtoedge.c
index acfbcfab..02ac63ac 100644
--- a/openbox/actions/growtoedge.c
+++ b/openbox/actions/growtoedge.c
@@ -1,4 +1,5 @@
#include "openbox/actions.h"
+#include "openbox/tiling.h"
#include "openbox/misc.h"
#include "openbox/client.h"
#include "openbox/frame.h"
@@ -117,6 +118,7 @@ static gboolean do_grow(ObActionsData *data, gint x, gint y, gint w, gint h)
{
actions_client_move(data, TRUE);
client_move_resize(data->client, x, y, realw, realh);
+ tiling_action_moved(data->client);
actions_client_move(data, FALSE);
return TRUE;
}
@@ -171,6 +173,7 @@ static gboolean do_grow_all_edges(ObActionsData* data,
actions_client_move(data, TRUE);
client_move_resize(data->client, x, y, w, h);
+ tiling_action_moved(data->client);
actions_client_move(data, FALSE);
return TRUE;
}
diff --git a/openbox/actions/moverelative.c b/openbox/actions/moverelative.c
index b67b5cf1..456bb19f 100644
--- a/openbox/actions/moverelative.c
+++ b/openbox/actions/moverelative.c
@@ -1,4 +1,5 @@
#include "openbox/actions.h"
+#include "openbox/tiling.h"
#include "openbox/client.h"
#include "openbox/screen.h"
#include "openbox/frame.h"
@@ -77,6 +78,7 @@ static gboolean run_func(ObActionsData *data, gpointer options)
actions_client_move(data, TRUE);
client_configure(c, x, y, w, h, TRUE, TRUE, FALSE);
+ tiling_action_moved(data->client);
actions_client_move(data, FALSE);
}
diff --git a/openbox/actions/moveresizeto.c b/openbox/actions/moveresizeto.c
index 95de0e98..73de6a0f 100644
--- a/openbox/actions/moveresizeto.c
+++ b/openbox/actions/moveresizeto.c
@@ -1,4 +1,5 @@
#include "openbox/actions.h"
+#include "openbox/tiling.h"
#include "openbox/client.h"
#include "openbox/screen.h"
#include "openbox/frame.h"
@@ -118,7 +119,9 @@ static gboolean run_func(ObActionsData *data, gpointer options)
case PREV_MONITOR:
mon = (cmon == 0) ? (screen_num_monitors - 1) : (cmon - 1); break;
default:
- g_assert_not_reached();
+ if (mon >= screen_num_monitors)
+ mon = cmon;
+ break;
}
area = screen_area(c->desktop, mon, NULL);
@@ -193,6 +196,7 @@ static gboolean run_func(ObActionsData *data, gpointer options)
actions_client_move(data, TRUE);
client_configure(c, x, y, w, h, TRUE, TRUE, FALSE);
+ tiling_action_moved(data->client);
actions_client_move(data, FALSE);
g_slice_free(Rect, area);
diff --git a/openbox/actions/movetoedge.c b/openbox/actions/movetoedge.c
index ef5b6920..d72f446b 100644
--- a/openbox/actions/movetoedge.c
+++ b/openbox/actions/movetoedge.c
@@ -1,4 +1,5 @@
#include "openbox/actions.h"
+#include "openbox/tiling.h"
#include "openbox/misc.h"
#include "openbox/client.h"
#include "openbox/frame.h"
@@ -70,11 +71,11 @@ static gboolean run_func(ObActionsData *data, gpointer options)
gint x, y;
client_find_move_directional(data->client, o->dir, &x, &y);
- if (x != data->client->area.x || y != data->client->area.y) {
- actions_client_move(data, TRUE);
+ actions_client_move(data, TRUE);
+ if (x != data->client->area.x || y != data->client->area.y)
client_move(data->client, x, y);
- actions_client_move(data, FALSE);
- }
+ tiling_action_moved(data->client);
+ actions_client_move(data, FALSE);
}
return FALSE;
@@ -108,4 +109,3 @@ static gpointer setup_west_func(xmlNodePtr node)
o->dir = OB_DIRECTION_WEST;
return o;
}
-
diff --git a/openbox/actions/resizerelative.c b/openbox/actions/resizerelative.c
index 74a3bd74..2683a515 100644
--- a/openbox/actions/resizerelative.c
+++ b/openbox/actions/resizerelative.c
@@ -1,4 +1,5 @@
#include "openbox/actions.h"
+#include "openbox/tiling.h"
#include "openbox/client.h"
#include "openbox/screen.h"
#include "openbox/frame.h"
@@ -107,6 +108,7 @@ static gboolean run_func(ObActionsData *data, gpointer options)
actions_client_move(data, TRUE);
client_move_resize(c, x + xoff, y + yoff, nw, nh);
+ tiling_action_moved(data->client);
actions_client_move(data, FALSE);
}
diff --git a/openbox/actions/tiling.c b/openbox/actions/tiling.c
new file mode 100644
index 00000000..91aa7ded
--- /dev/null
+++ b/openbox/actions/tiling.c
@@ -0,0 +1,134 @@
+#include "openbox/actions.h"
+#include "openbox/client.h"
+#include "openbox/screen.h"
+#include "openbox/tiling.h"
+
+typedef struct {
+ gboolean main_stack;
+ gint delta;
+ guint key;
+} Options;
+
+static gpointer setup(xmlNodePtr node)
+{
+ Options *options = g_new0(Options, 1);
+ xmlNodePtr child;
+
+ options->main_stack = TRUE;
+ options->delta = 1;
+ if ((child = obt_xml_find_node(node, "stack")))
+ options->main_stack = !obt_xml_node_contains(child, "side");
+ if ((child = obt_xml_find_node(node, "delta")))
+ options->delta = obt_xml_node_int(child);
+ if ((child = obt_xml_find_node(node, "key")))
+ options->key = obt_xml_node_int(child);
+ return options;
+}
+
+#define KEY_SETUP(number) \
+ static gpointer setup_key_##number(xmlNodePtr node) \
+ { \
+ Options *options = g_new0(Options, 1); \
+ options->key = number; \
+ return options; \
+ }
+
+KEY_SETUP(0)
+KEY_SETUP(1)
+KEY_SETUP(2)
+KEY_SETUP(3)
+KEY_SETUP(4)
+KEY_SETUP(5)
+KEY_SETUP(6)
+KEY_SETUP(7)
+KEY_SETUP(8)
+KEY_SETUP(9)
+KEY_SETUP(10)
+KEY_SETUP(11)
+KEY_SETUP(12)
+
+#undef KEY_SETUP
+
+static guint monitor(ObActionsData *data)
+{
+ return data->client ? client_monitor(data->client) : screen_monitor_pointer();
+}
+
+static gboolean toggle(ObActionsData *data, gpointer options)
+{
+ actions_client_move(data, TRUE);
+ tiling_toggle(monitor(data));
+ actions_client_move(data, FALSE);
+ return FALSE;
+}
+
+static gboolean flip(ObActionsData *data, gpointer options)
+{
+ actions_client_move(data, TRUE);
+ tiling_flip(monitor(data));
+ actions_client_move(data, FALSE);
+ return FALSE;
+}
+
+static gboolean count(ObActionsData *data, gpointer options)
+{
+ Options *o = options;
+
+ actions_client_move(data, TRUE);
+ tiling_change_count(monitor(data), o->main_stack, o->delta);
+ actions_client_move(data, FALSE);
+ return FALSE;
+}
+
+static gboolean promote(ObActionsData *data, gpointer options)
+{
+ actions_client_move(data, TRUE);
+ tiling_promote(data->client);
+ actions_client_move(data, FALSE);
+ return FALSE;
+}
+
+static gboolean assign(ObActionsData *data, gpointer options)
+{
+ Options *o = options;
+
+ tiling_assign(data->client, o->key);
+ return FALSE;
+}
+
+static gboolean recall(ObActionsData *data, gpointer options)
+{
+ Options *o = options;
+
+ actions_client_move(data, TRUE);
+ tiling_recall(o->key);
+ actions_client_move(data, FALSE);
+ return FALSE;
+}
+
+void action_tiling_startup(void)
+{
+ static const ObActionsDataSetupFunc key_setups[] = {
+ setup_key_0, setup_key_1, setup_key_2, setup_key_3, setup_key_4,
+ setup_key_5, setup_key_6, setup_key_7, setup_key_8, setup_key_9,
+ setup_key_10, setup_key_11, setup_key_12
+ };
+ guint key;
+
+ actions_register("ToggleTiling", NULL, NULL, toggle);
+ actions_register("FlipTiling", NULL, NULL, flip);
+ actions_register("ChangeTileCount", setup, g_free, count);
+ actions_register("MakeMainTile", NULL, NULL, promote);
+ actions_register("AssignWindowKey", setup, g_free, assign);
+ actions_register("RecallWindowKey", setup, g_free, recall);
+ actions_register("AssignWindow0", setup_key_0, g_free, assign);
+ for (key = 1; key < G_N_ELEMENTS(key_setups); ++key) {
+ gchar *name = g_strdup_printf("AssignWindow%u", key);
+
+ actions_register(name, key_setups[key], g_free, assign);
+ g_free(name);
+ name = g_strdup_printf("RecallWindow%u", key);
+ actions_register(name, key_setups[key], g_free, recall);
+ g_free(name);
+ }
+}
diff --git a/openbox/client.c b/openbox/client.c
index 85aaf6e4..d3bef6c7 100644
--- a/openbox/client.c
+++ b/openbox/client.c
@@ -20,6 +20,7 @@
#include "client.h"
#include "debug.h"
#include "startupnotify.h"
+#include "tiling.h"
#include "dock.h"
#include "screen.h"
#include "moveresize.h"
@@ -608,6 +609,7 @@ void client_unmanage(ObClient *self)
mouse_grab_for_client(self, FALSE);
self->managed = FALSE;
+ tiling_forget(self);
/* remove the window from our save set, unless we are managing an internal
ObPrompt window */
@@ -2132,6 +2134,12 @@ void client_update_title(ObClient *self)
g_free(data);
}
+ if (tiling_key(self)) {
+ data = visible;
+ visible = g_strdup_printf("[F%u] %s", tiling_key(self), data);
+ g_free(data);
+ }
+
OBT_PROP_SETS(self->window, NET_WM_VISIBLE_NAME, visible);
self->title = visible;
@@ -2557,6 +2565,7 @@ static void client_change_state(ObClient *self)
if (self->frame)
frame_adjust_state(self->frame);
+ tiling_client_changed(self);
}
ObClient *client_search_focus_tree(ObClient *self)
@@ -3318,6 +3327,7 @@ void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
{
client_calc_layer(self);
}
+ tiling_client_changed(self);
}
void client_fullscreen(ObClient *self, gboolean fs)
diff --git a/openbox/moveresize.c b/openbox/moveresize.c
index 0cdf267d..0f9db110 100644
--- a/openbox/moveresize.c
+++ b/openbox/moveresize.c
@@ -27,6 +27,7 @@
#include "resist.h"
#include "popup.h"
#include "moveresize.h"
+#include "tiling.h"
#include "config.h"
#include "event.h"
#include "debug.h"
@@ -266,6 +267,8 @@ void moveresize_start(ObClient *c, gint x, gint y, guint b, guint32 cnr)
cur_h = start_ch;
moveresize_in_progress = TRUE;
+ if (!moving)
+ tiling_resize_begin(c);
waiting_for_sync = 0;
#ifdef SYNC
@@ -367,6 +370,10 @@ void moveresize_end(gboolean cancel)
cancel_edge_warp();
moveresize_in_progress = FALSE;
+ if (moving)
+ tiling_move_end(moveresize_client, cancel);
+ else
+ tiling_resize_end(moveresize_client, cancel);
moveresize_client = NULL;
}
@@ -382,6 +389,14 @@ static void do_move(gboolean keyboard, gint keydist)
screen_pointer_pos(&x, &y);
+ if (tiling_move_preview(moveresize_client,
+ keyboard ? cur_x + cur_w / 2 : x,
+ keyboard ? cur_y + cur_h / 2 : y)) {
+ client_configure(moveresize_client, cur_x, cur_y, cur_w, cur_h,
+ TRUE, FALSE, FALSE);
+ return;
+ }
+
const Rect* a = screen_physical_area_all_monitors();
gint h = RECT_BOTTOM(*a) - RECT_TOP(*a);
gint w = RECT_RIGHT(*a) - RECT_LEFT(*a);
@@ -461,6 +476,8 @@ static void do_resize(void)
{
gint x, y, w, h, lw, lh;
+ tiling_resize_adjust(moveresize_client, &cur_x, &cur_y, &cur_w, &cur_h);
+
/* see if it is actually going to resize
USE cur_x AND cur_y HERE ! Otherwise the try_configure won't know
what struts to use !!
diff --git a/openbox/openbox.c b/openbox/openbox.c
index 1671a0d3..35fc856e 100644
--- a/openbox/openbox.c
+++ b/openbox/openbox.c
@@ -25,6 +25,7 @@
#include "menu.h"
#include "client.h"
#include "screen.h"
+#include "tiling.h"
#include "actions.h"
#include "startupnotify.h"
#include "focus.h"
@@ -388,6 +389,8 @@ gint main(gint argc, gchar **argv)
xmlprompt = NULL;
}
+ tiling_shutdown(reconfigure);
+
if (!reconfigure)
window_unmanage_all();
diff --git a/openbox/screen.c b/openbox/screen.c
index e758ada1..895c5e49 100644
--- a/openbox/screen.c
+++ b/openbox/screen.c
@@ -25,6 +25,7 @@
#include "moveresize.h"
#include "config.h"
#include "screen.h"
+#include "tiling.h"
#include "client.h"
#include "session.h"
#include "frame.h"
@@ -1504,6 +1505,7 @@ void screen_update_areas(void)
client_reconfigure(it->data, FALSE);
g_free(dims);
+ tiling_screen_changed();
}
#if 0
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;
+ }
+}
diff --git a/openbox/tiling.h b/openbox/tiling.h
new file mode 100644
index 00000000..9ba774e7
--- /dev/null
+++ b/openbox/tiling.h
@@ -0,0 +1,28 @@
+#ifndef OB_TILING_H
+#define OB_TILING_H
+
+#include <glib.h>
+
+struct _ObClient;
+
+void tiling_toggle(guint monitor);
+void tiling_flip(guint monitor);
+void tiling_change_count(guint monitor, gboolean main_stack, gint delta);
+void tiling_promote(struct _ObClient *client);
+void tiling_assign(struct _ObClient *client, guint key);
+void tiling_recall(guint key);
+guint tiling_key(struct _ObClient *client);
+void tiling_forget(struct _ObClient *client);
+void tiling_client_changed(struct _ObClient *client);
+void tiling_action_moved(struct _ObClient *client);
+void tiling_screen_changed(void);
+void tiling_shutdown(gboolean reconfigure);
+
+gboolean tiling_move_preview(struct _ObClient *client, gint x, gint y);
+void tiling_move_end(struct _ObClient *client, gboolean cancel);
+void tiling_resize_begin(struct _ObClient *client);
+void tiling_resize_adjust(struct _ObClient *client, gint *x, gint *y,
+ gint *width, gint *height);
+void tiling_resize_end(struct _ObClient *client, gboolean cancel);
+
+#endif