i3/src/layout.c
2009-03-03 23:51:02 +01:00

408 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* layout.c: Functions handling layout/drawing of window decorations
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <xcb/xcb.h>
#include <assert.h>
#include "config.h"
#include "font.h"
#include "i3.h"
#include "xcb.h"
#include "table.h"
#include "util.h"
#include "xinerama.h"
#include "layout.h"
/* This macro copies the old value of the given variable, changes the variable to contain
the new one and returns true if it changed.
Note that when combining multiple HAS_CHANGED statements, you need to use different variables.
If someone by chance knows why this is necessary (order of expressions in gcc?) and/or can
come up with a fix, please mail me. */
#define HAS_CHANGED(temp, value, new) (temp = value, temp != (value = new))
static int old_value_1;
static int old_value_2;
/*
* Gets the unoccupied space (= space which is available for windows which were resized by the user)
* for the given row. This is necessary to render both, customly resized windows and never touched
* windows correctly, meaning that the aspect ratio will be maintained when opening new windows.
*
*/
int get_unoccupied_x(Workspace *workspace, int row) {
int unoccupied = workspace->rect.width;
float default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width;
printf("get_unoccupied_x(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
for (int cols = 0; cols < workspace->cols;) {
Container *con = workspace->table[cols][row];
printf("width_factor[%d][%d] = %f, colspan = %d\n", cols, row, con->width_factor, con->colspan);
if (con->width_factor == 0)
unoccupied -= workspace->rect.width * default_factor * con->colspan;
cols += con->colspan;
}
assert(unoccupied != 0);
printf("unoccupied space: %d\n", unoccupied);
return unoccupied;
}
/* See get_unoccupied_x() */
int get_unoccupied_y(Workspace *workspace, int col) {
int unoccupied = workspace->rect.height;
float default_factor = ((float)workspace->rect.height / workspace->rows) / workspace->rect.height;
printf("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
for (int rows = 0; rows < workspace->rows;) {
Container *con = workspace->table[col][rows];
printf("height_factor[%d][%d] = %f, rowspan %d\n", col, rows, con->height_factor, con->rowspan);
if (con->height_factor == 0)
unoccupied -= workspace->rect.height * default_factor * con->rowspan;
rows += con->rowspan;
}
assert(unoccupied != 0);
printf("unoccupied space: %d\n", unoccupied);
return unoccupied;
}
/*
* Redecorates the given client correctly by checking if its in a stacking container and
* re-rendering the stack window or just calling decorate_window if its not in a stacking
* container.
*
*/
void redecorate_window(xcb_connection_t *conn, Client *client) {
if (client->container->mode == MODE_STACK) {
render_container(conn, client->container);
xcb_flush(conn);
} else decorate_window(conn, client, client->frame, client->titlegc, 0);
}
/*
* (Re-)draws window decorations for a given Client onto the given drawable/graphic context.
* When in stacking mode, the window decorations are drawn onto an own window.
*
*/
void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
i3Font *font = load_font(conn, config.font);
uint32_t background_color,
text_color,
border_color;
/* Clients without a container (docks) wont get decorated */
if (client->container == NULL)
return;
if (client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
if (CUR_CELL->currently_focused == client)
background_color = get_colorpixel(conn, client, client->frame, "#285577");
/* …or if it is the focused window in a not focused container */
else background_color = get_colorpixel(conn, client, client->frame, "#555555");
text_color = get_colorpixel(conn, client, client->frame, "#ffffff");
border_color = get_colorpixel(conn, client, client->frame, "#4c7899");
} else {
background_color = get_colorpixel(conn, client, client->frame, "#222222");
text_color = get_colorpixel(conn, client, client->frame, "#888888");
border_color = get_colorpixel(conn, client, client->frame, "#333333");
}
/* Our plan is the following:
- Draw a rect around the whole client in background_color
- Draw two lines in a lighter color
- Draw the windows title
*/
/* Draw a rectangle in background color around the window */
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
xcb_rectangle_t rect = {0, offset, client->rect.width, offset + client->rect.height};
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
2 + client->rect.width, offset + font->height + 3);
/* Draw the font */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
uint32_t values[] = { text_color, background_color, font->id };
xcb_change_gc(conn, gc, mask, values);
/* TODO: utf8? */
char *label;
asprintf(&label, "(%08x) %.*s", client->frame, client->name_len, client->name);
xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), drawable,
gc, 3 /* X */, offset + font->height /* Y = baseline of font */, label);
check_error(conn, text_cookie, "Could not draw client's title");
free(label);
}
/*
* Pushes the clients x and y coordinates to X11
*
*/
static void reposition_client(xcb_connection_t *connection, Client *client) {
printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
/* Note: We can use a pointer to client->x like an array of uint32_ts
because it is followed by client->y by definition */
xcb_configure_window(connection, client->frame,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
}
/*
* Pushes the clients width/height to X11 and resizes the child window
*
*/
static void resize_client(xcb_connection_t *connection, Client *client) {
i3Font *font = load_font(connection, config.font);
printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
xcb_configure_window(connection, client->frame,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.width));
/* Adjust the position of the child inside its frame.
* The coordinates of the child are relative to its frame, we
* add a border of 2 pixel to each value */
uint32_t mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
Rect *rect = &(client->child_rect);
switch (client->container->mode) {
case MODE_STACK:
rect->x = 2;
rect->y = 0;
rect->width = client->rect.width - (2 + 2);
rect->height = client->rect.height - 2;
break;
default:
if (client->titlebar_position == TITLEBAR_OFF) {
rect->x = 0;
rect->y = 0;
rect->width = client->rect.width;
rect->height = client->rect.height;
} else {
rect->x = 2;
rect->y = font->height + 2 + 2;
rect->width = client->rect.width - (2 + 2);
rect->height = client->rect.height - ((font->height + 2 + 2) + 2);
}
break;
}
printf("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
xcb_configure_window(connection, client->child, mask, &(rect->x));
/* After configuring a child window we need to fake a configure_notify_event according
to ICCCM 4.2.3. This seems rather broken, especially since X sends exactly the same
configure_notify_event automatically according to xtrace. Anyone knows details? */
xcb_configure_notify_event_t event;
event.event = client->child;
event.window = client->child;
event.response_type = XCB_CONFIGURE_NOTIFY;
event.x = rect->x;
event.y = rect->y;
event.width = rect->width;
event.height = rect->height;
event.border_width = 0;
event.above_sibling = XCB_NONE;
event.override_redirect = false;
xcb_send_event(connection, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
}
/*
* Renders the given container. Is called by render_layout() or individually (for example
* when focus changes in a stacking container)
*
*/
void render_container(xcb_connection_t *connection, Container *container) {
Client *client;
int num_clients = 0, current_client = 0;
if (container->currently_focused == NULL)
return;
CIRCLEQ_FOREACH(client, &(container->clients), clients)
num_clients++;
if (container->mode == MODE_DEFAULT) {
printf("got %d clients in this default container.\n", num_clients);
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* Check if we changed client->x or client->y by updating it.
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
if (client->force_reconfigure |
HAS_CHANGED(old_value_1, client->rect.x, container->x) |
HAS_CHANGED(old_value_2, client->rect.y, container->y +
(container->height / num_clients) * current_client))
reposition_client(connection, client);
/* TODO: vertical default layout */
if (client->force_reconfigure |
HAS_CHANGED(old_value_1, client->rect.width, container->width) |
HAS_CHANGED(old_value_2, client->rect.height, container->height / num_clients))
resize_client(connection, client);
client->force_reconfigure = false;
current_client++;
}
} else {
i3Font *font = load_font(connection, config.font);
int decoration_height = (font->height + 2 + 2);
struct Stack_Window *stack_win = &(container->stack_win);
/* Check if we need to remap our stack title window, it gets unmapped when the container
is empty in src/handlers.c:unmap_notify() */
if (stack_win->height == 0)
xcb_map_window(connection, stack_win->window);
/* Check if we need to reconfigure our stack title window */
if (HAS_CHANGED(old_value_1, stack_win->width, container->width) |
HAS_CHANGED(old_value_2, stack_win->height, decoration_height * num_clients)) {
xcb_configure_window(connection, stack_win->window,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, &(stack_win->width));
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(connection, stack_win->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
}
/* Reconfigure the currently focused client, if necessary. It is the only visible one */
client = container->currently_focused;
/* Check if we changed client->x or client->y by updating it.
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
if (client->force_reconfigure |
HAS_CHANGED(old_value_1, client->rect.x, container->x) |
HAS_CHANGED(old_value_2, client->rect.y, container->y + (decoration_height * num_clients)))
reposition_client(connection, client);
if (client->force_reconfigure |
HAS_CHANGED(old_value_1, client->rect.width, container->width) |
HAS_CHANGED(old_value_2, client->rect.height, container->height - (decoration_height * num_clients)))
resize_client(connection, client);
client->force_reconfigure = false;
if (container->workspace->fullscreen_client == NULL) {
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(connection, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
}
/* Render the decorations of all clients */
CIRCLEQ_FOREACH(client, &(container->clients), clients)
decorate_window(connection, client, stack_win->window, stack_win->gc,
current_client++ * decoration_height);
}
}
static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width, int height) {
Client *client;
SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
if (client->force_reconfigure |
HAS_CHANGED(old_value_1, client->rect.x, 0) |
HAS_CHANGED(old_value_2, client->rect.y, height))
reposition_client(connection, client);
if (client->force_reconfigure |
HAS_CHANGED(old_value_1, client->rect.width, width) |
HAS_CHANGED(old_value_2, client->rect.height, client->desired_height))
resize_client(connection, client);
client->force_reconfigure = false;
height += client->desired_height;
}
}
void render_layout(xcb_connection_t *connection) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
/* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
Workspace *r_ws = &(workspaces[screen->current_workspace]);
printf("Rendering screen %d\n", screen->num);
if (r_ws->fullscreen_client != NULL)
/* This is easy: A client has entered fullscreen mode, so we dont render at all */
continue;
int width = r_ws->rect.width;
int height = r_ws->rect.height;
/* Reserve space for dock clients */
Client *client;
SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
height -= client->desired_height;
printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
int xoffset[r_ws->rows];
int yoffset[r_ws->cols];
/* Initialize offsets */
for (int cols = 0; cols < r_ws->cols; cols++)
yoffset[cols] = r_ws->rect.y;
for (int rows = 0; rows < r_ws->rows; rows++)
xoffset[rows] = r_ws->rect.x;
/* Go through the whole table and render whats necessary */
for (int cols = 0; cols < r_ws->cols; cols++)
for (int rows = 0; rows < r_ws->rows; rows++) {
Container *container = r_ws->table[cols][rows];
printf("\n========\ncontainer has %d colspan, %d rowspan\n",
container->colspan, container->rowspan);
printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
/* Update position of the container */
container->row = rows;
container->col = cols;
container->x = xoffset[rows];
container->y = yoffset[cols];
if (container->width_factor == 0)
container->width = (width / r_ws->cols);
else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
container->width *= container->colspan;
if (container->height_factor == 0)
container->height = (height / r_ws->rows);
else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
container->height *= container->rowspan;
/* Render the container if it is not empty */
render_container(connection, container);
xoffset[rows] += container->width;
yoffset[cols] += container->height;
printf("==========\n");
}
render_bars(connection, r_ws, width, height);
}
xcb_flush(connection);
}