diff options
Diffstat (limited to 'openbox')
| -rw-r--r-- | openbox/actions/all.c | 1 | ||||
| -rw-r--r-- | openbox/actions/all.h | 1 | ||||
| -rw-r--r-- | openbox/actions/growtoedge.c | 3 | ||||
| -rw-r--r-- | openbox/actions/moverelative.c | 2 | ||||
| -rw-r--r-- | openbox/actions/moveresizeto.c | 6 | ||||
| -rw-r--r-- | openbox/actions/movetoedge.c | 10 | ||||
| -rw-r--r-- | openbox/actions/resizerelative.c | 2 | ||||
| -rw-r--r-- | openbox/actions/tiling.c | 134 | ||||
| -rw-r--r-- | openbox/client.c | 10 | ||||
| -rw-r--r-- | openbox/moveresize.c | 17 | ||||
| -rw-r--r-- | openbox/openbox.c | 3 | ||||
| -rw-r--r-- | openbox/screen.c | 2 | ||||
| -rw-r--r-- | openbox/tiling.c | 989 | ||||
| -rw-r--r-- | openbox/tiling.h | 28 |
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 |
