#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; } }