Remove some dead code (to be re-implemented), rename nc.c to main.c

This commit is contained in:
Michael Stapelberg 2010-07-11 22:12:25 +02:00
parent 61f9a79347
commit a79d33fc7f
14 changed files with 1 additions and 3677 deletions

View File

@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk
# Depend on the object files of all source-files in src/*.c and on all header files # Depend on the object files of all source-files in src/*.c and on all header files
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c
FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c
FILES:=$(FILES:.c=.o) FILES:=$(FILES:.c=.o)
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))

View File

@ -30,7 +30,6 @@
#include <xcb/xcb_icccm.h> #include <xcb/xcb_icccm.h>
#include "util.h" #include "util.h"
#include "commands.h"
#include "ipc.h" #include "ipc.h"
#include "tree.h" #include "tree.h"
#include "log.h" #include "log.h"

View File

@ -1,121 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include "data.h"
#ifndef _CLIENT_H
#define _CLIENT_H
/**
* Removes the given client from the container, either because it will be
* inserted into another one or because it was unmapped
*
*/
void client_remove_from_container(xcb_connection_t *conn, Client *client,
Container *container,
bool remove_from_focusstack);
/**
* Warps the pointer into the given client (in the middle of it, to be
* specific), therefore selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
/**
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void client_kill(xcb_connection_t *conn, Client *window);
/**
* Checks if the given window class and title match the given client Window
* title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
char *to_title_ucs, int to_title_ucs_len);
/**
* Sets the position of the given client in the X stack to the highest (tiling
* layer is always on the same position, so this doesnt matter) below the
* first floating client, so that floating windows are always on top.
*
*/
void client_set_below_floating(xcb_connection_t *conn, Client *client);
/**
* Returns true if the client is floating. Makes the code more beatiful, as
* floating is not simply a boolean, but also saves whether the user selected
* the current state or whether it was automatically set.
*
*/
bool client_is_floating(Client *client);
/**
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b).
*
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type);
/**
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b) without actually re-rendering the layout. Useful
* for calling it when initializing a new client.
*
*/
bool client_init_border(xcb_connection_t *conn, Client *client, char border_type);
/**
* Unmap the client, correctly setting any state which is needed.
*
*/
void client_unmap(xcb_connection_t *conn, Client *client);
/**
* Map the client, correctly restoring any state needed.
*
*/
void client_map(xcb_connection_t *conn, Client *client);
/**
* Set the given mark for this client. Used for jumping to the client
* afterwards (like m<mark> and '<mark> in vim).
*
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark);
/**
* Returns the minimum height of a specific window. The height is calculated
* by using 2 pixels (for the client window itself), possibly padding this to
* comply with the clients base_height and then adding the decoration height.
*
*/
uint32_t client_min_height(Client *client);
/**
* See client_min_height.
*
*/
uint32_t client_min_width(Client *client);
/**
* Pretty-prints the clients information into the logfile.
*
*/
#define CLIENT_LOG(client) do { \
DLOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \
} while (0)
#endif

View File

@ -1,24 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _COMMANDS_H
#define _COMMANDS_H
#include <xcb/xcb.h>
#if 0
bool focus_window_in_container(xcb_connection_t *conn, Container *container,
direction_t direction);
#endif
/** Parses a command, see file CMDMODE for more information */
void parse_command(const char *command);
#endif

View File

@ -1,26 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include "data.h"
#ifndef _CONTAINER_H
#define _CONTAINER_H
/**
* Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
* was passed in order to save a few explicit checks in other places). If
* for_frame was set to true, the special case of having exactly one client
* in a container is handled so that MODE_DEFAULT is returned. For some parts
* of the rendering, this is interesting, other parts need the real mode.
*
*/
int container_mode(Container *con, bool for_frame);
#endif

View File

@ -1,94 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#ifndef _LAYOUT_H
#define _LAYOUT_H
/**
* Gets the unoccupied space (= space which is available for windows which
* were resized by the user) 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);
/** See get_unoccupied_x */
int get_unoccupied_y(Workspace *workspace);
/**
* (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_x, int offset_y);
/**
* 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);
/**
* Pushes the clients x and y coordinates to X11
*
*/
void reposition_client(xcb_connection_t *conn, Client *client);
/**
* Pushes the clients width/height to X11 and resizes the child window. This
* function also updates the clients position, so if you work on tiling clients
* only, you can use this function instead of separate calls to reposition_client
* and resize_client to reduce flickering.
*
*/
void resize_client(xcb_connection_t *conn, Client *client);
/**
* 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 *conn, Container *container);
/**
* Modifies the event mask of all clients on the given workspace to either
* ignore or to handle enter notifies. It is handy to ignore notifies because
* they will be sent when a window is mapped under the cursor, thus when the
* user didnt enter the window actively at all.
*
*/
void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace,
bool ignore_enter_notify);
/**
* Renders the given workspace on the given screen
*
*/
void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws);
/**
* Renders the whole layout, that is: Go through each screen, each workspace,
* each container and render each client. This also renders the bars.
*
* If you dont need to render *everything*, you should call render_container
* on the container you want to refresh.
*
*/
void render_layout(xcb_connection_t *conn);
#endif

View File

@ -1,36 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _RESIZE_H
#define _RESIZE_H
#include <xcb/xcb.h>
typedef enum { O_HORIZONTAL, O_VERTICAL } resize_orientation_t;
/**
* Renders the resize window between the first/second container and resizes
* the table column/row.
*
*/
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first,
int second, resize_orientation_t orientation,
xcb_button_press_event_t *event);
/**
* Resizes a column/row by the given amount of pixels. Called by
* resize_graphical_handler (the user clicked) or parse_resize_command (the
* user issued the command)
*
*/
void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, int pixels);
#endif

View File

@ -1,328 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* client.c: holds all client-specific functions
*
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "data.h"
#include "i3.h"
#include "xcb.h"
#include "util.h"
#include "queue.h"
#include "layout.h"
#include "client.h"
#include "table.h"
#include "workspace.h"
#include "config.h"
#include "log.h"
/*
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
*
*/
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
CIRCLEQ_REMOVE(&(container->clients), client, clients);
if (remove_from_focusstack)
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
/* If the container will be empty now and is in stacking mode, we need to
unmap the stack_win */
if (CIRCLEQ_EMPTY(&(container->clients)) &&
(container->mode == MODE_STACK ||
container->mode == MODE_TABBED)) {
DLOG("Unmapping stack window\n");
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
xcb_flush(conn);
}
}
/*
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
int mid_x = client->rect.width / 2,
mid_y = client->rect.height / 2;
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
}
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
*/
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
xcb_get_property_cookie_t cookie;
xcb_get_wm_protocols_reply_t protocols;
bool result = false;
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
return false;
/* Check if the clients protocols have the requested atom set */
for (uint32_t i = 0; i < protocols.atoms_len; i++)
if (protocols.atoms[i] == atom)
result = true;
xcb_get_wm_protocols_reply_wipe(&protocols);
return result;
}
/*
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void client_kill(xcb_connection_t *conn, Client *window) {
/* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
LOG("Killing window the hard way\n");
xcb_kill_client(conn, window->child);
return;
}
xcb_client_message_event_t ev;
memset(&ev, 0, sizeof(xcb_client_message_event_t));
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = window->child;
ev.type = atoms[WM_PROTOCOLS];
ev.format = 32;
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
ev.data.data32[1] = XCB_CURRENT_TIME;
LOG("Sending WM_DELETE to the client\n");
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
xcb_flush(conn);
}
/*
* Checks if the given window class and title match the given client
* Window title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
char *to_title_ucs, int to_title_ucs_len) {
/* Check if the given class is part of the window class */
if ((client->window_class_instance == NULL ||
strcasestr(client->window_class_instance, to_class) == NULL) &&
(client->window_class_class == NULL ||
strcasestr(client->window_class_class, to_class) == NULL))
return false;
/* If no title was given, were done */
if (to_title == NULL)
return true;
if (client->name_len > -1) {
/* UCS-2 converted window titles */
if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
return false;
} else {
/* Legacy hints */
if (client->name == NULL || strcasestr(client->name, to_title) == NULL)
return false;
}
return true;
}
/*
* Sets the position of the given client in the X stack to the highest (tiling layer is always
* on the same position, so this doesnt matter) below the first floating client, so that
* floating windows are always on top.
*
*/
void client_set_below_floating(xcb_connection_t *conn, Client *client) {
/* Ensure that it is below all floating clients */
Workspace *ws = client->workspace;
Client *first_floating = TAILQ_FIRST(&(ws->floating_clients));
if (first_floating == TAILQ_END(&(ws->floating_clients)))
return;
DLOG("Setting below floating\n");
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
if (client->workspace->fullscreen_client == NULL)
return;
DLOG("(and below fullscreen)\n");
/* Ensure that the window is still below the fullscreen window */
values[0] = client->workspace->fullscreen_client->frame;
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
/*
* Returns true if the client is floating. Makes the code more beatiful, as floating
* is not simply a boolean, but also saves whether the user selected the current state
* or whether it was automatically set.
*
*/
bool client_is_floating(Client *client) {
return (client->floating >= FLOATING_AUTO_ON);
}
/*
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b) without actually re-rendering the layout. Useful
* for calling it when initializing a new client.
*
*/
bool client_init_border(xcb_connection_t *conn, Client *client, char border_type) {
switch (border_type) {
case 'n':
LOG("Changing to normal border\n");
client->titlebar_position = TITLEBAR_TOP;
client->borderless = false;
return true;
case 'p':
LOG("Changing to 1px border\n");
client->titlebar_position = TITLEBAR_OFF;
client->borderless = false;
return true;
case 'b':
LOG("Changing to borderless\n");
client->titlebar_position = TITLEBAR_OFF;
client->borderless = true;
return true;
default:
LOG("Unknown border mode\n");
return false;
}
}
/*
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b).
*
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type) {
if (!client_init_border(conn, client, border_type))
return;
/* Ensure that the childs position inside our window gets updated */
client->force_reconfigure = true;
/* For clients inside a container, we can simply render the container */
if (client->container != NULL)
render_container(conn, client->container);
else {
/* If the client is floating, directly push its size */
if (client_is_floating(client))
resize_client(conn, client);
/* Otherwise, it may be a dock client, thus render the whole layout */
else render_layout(conn);
}
redecorate_window(conn, client);
}
/*
* Unmap the client, correctly setting any state which is needed.
*
*/
void client_unmap(xcb_connection_t *conn, Client *client) {
/* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
xcb_unmap_window(conn, client->frame);
}
/*
* Map the client, correctly restoring any state needed.
*
*/
void client_map(xcb_connection_t *conn, Client *client) {
/* Set WM_STATE_NORMAL because GTK applications dont want to drag & drop if we dont.
* Also, xprop(1) needs that to work. */
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
xcb_map_window(conn, client->frame);
}
/*
* Set the given mark for this client. Used for jumping to the client
* afterwards (like m<mark> and '<mark> in vim).
*
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
if (client->mark != NULL)
free(client->mark);
client->mark = sstrdup(mark);
/* Make sure no other client has this mark set */
Client *current;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
if (current == client ||
current->mark == NULL ||
strcmp(current->mark, mark) != 0)
continue;
free(current->mark);
current->mark = NULL;
/* We can break here since there can only be one other
* client with this mark. */
break;
}
}
/*
* Returns the minimum height of a specific window. The height is calculated
* by using 2 pixels (for the client window itself), possibly padding this to
* comply with the clients base_height and then adding the decoration height.
*
*/
uint32_t client_min_height(Client *client) {
uint32_t height = max(2, client->base_height);
i3Font *font = load_font(global_conn, config.font);
if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
return height;
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
return height + 2;
return height + font->height + 2 + 2;
}
/*
* See client_min_height.
*
*/
uint32_t client_min_width(Client *client) {
uint32_t width = max(2, client->base_width);
if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
return width;
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
return width + 2;
return width + 2 + 2;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include "data.h"
#include "log.h"
/*
* Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
* was passed in order to save a few explicit checks in other places). If
* for_frame was set to true, the special case of having exactly one client
* in a container is handled so that MODE_DEFAULT is returned. For some parts
* of the rendering, this is interesting, other parts need the real mode.
*
*/
int container_mode(Container *con, bool for_frame) {
int num_clients = 0;
Client *client;
if (con == NULL || con->mode == MODE_DEFAULT)
return MODE_DEFAULT;
if (!for_frame)
return con->mode;
CIRCLEQ_FOREACH(client, &(con->clients), clients)
num_clients++;
/* If the container contains only one client, mode is irrelevant */
if (num_clients == 1) {
DLOG("mode to default\n");
return MODE_DEFAULT;
}
return con->mode;
}

View File

@ -1,779 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 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 <math.h>
#include "config.h"
#include "i3.h"
#include "xcb.h"
#include "table.h"
#include "util.h"
#include "randr.h"
#include "layout.h"
#include "client.h"
#include "floating.h"
#include "handlers.h"
#include "workspace.h"
#include "log.h"
#include "container.h"
/*
* 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) {
double unoccupied = workspace->rect.width;
double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width;
DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
for (int cols = 0; cols < workspace->cols; cols++) {
DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied);
if (workspace->width_factor[cols] == 0)
unoccupied -= workspace->rect.width * default_factor;
}
DLOG("unoccupied space: %f\n", unoccupied);
return unoccupied;
}
/* See get_unoccupied_x() */
int get_unoccupied_y(Workspace *workspace) {
int height = workspace_height(workspace);
double unoccupied = height;
double default_factor = ((float)height / workspace->rows) / height;
DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
for (int rows = 0; rows < workspace->rows; rows++) {
DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied);
if (workspace->height_factor[rows] == 0)
unoccupied -= height * default_factor;
}
DLOG("unoccupied space: %f\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 != NULL &&
(client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED)) {
render_container(conn, client->container);
/* We clear the frame to generate exposure events, because the color used
in drawing may be different */
xcb_clear_area(conn, true, client->frame, 0, 0, client->rect.width, client->rect.height);
} else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
xcb_flush(conn);
}
/*
* (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_x, int offset_y) {
i3Font *font = load_font(conn, config.font);
int decoration_height = font->height + 2 + 2;
struct Colortriple *color;
Client *last_focused;
/* Clients without a container (docks) wont get decorated */
if (client->dock)
return;
last_focused = SLIST_FIRST(&(client->workspace->focus_stack));
/* Is the window urgent? */
if (client->urgent)
color = &(config.client.urgent);
else {
if (client_is_floating(client)) {
if (last_focused == client)
color = &(config.client.focused);
else color = &(config.client.unfocused);
} else {
if (client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
if (last_focused == client && c_ws == client->workspace)
color = &(config.client.focused);
/* …or if it is the focused window in a not focused container */
else color = &(config.client.focused_inactive);
} else color = &(config.client.unfocused);
}
}
/* Our plan is the following:
- Draw a rect around the whole client in color->background
- Draw two lines in a lighter color
- Draw the windows title
*/
int mode = container_mode(client->container, true);
/* Draw a rectangle in background color around the window */
if (client->borderless && mode == MODE_DEFAULT)
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
/* In stacking mode, we only render the rect for this specific decoration */
if (mode == MODE_STACK || mode == MODE_TABBED) {
/* We need to use the containers width because it is the more recent value - when
in stacking mode, clients get reconfigured only on demand (the not active client
is not reconfigured), so the clients rect.width would be wrong */
xcb_rectangle_t rect = {offset_x, offset_y,
offset_x + client->container->width,
offset_y + decoration_height };
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
} else {
xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
/* Draw the inner background to have a black frame around clients (such as mplayer)
which cannot be resized exactly in our frames and therefore are centered */
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
if (client->titlebar_position == TITLEBAR_OFF && client->borderless) {
xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else {
xcb_rectangle_t crect = {2, decoration_height,
client->rect.width - (2 + 2), client->rect.height - 2 - decoration_height};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
}
}
mode = container_mode(client->container, false);
if (client->titlebar_position != TITLEBAR_OFF) {
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y);
xcb_draw_line(conn, drawable, gc, color->border,
offset_x + 2, /* x */
offset_y + font->height + 3, /* y */
offset_x + client->rect.width - 3, /* to_x */
offset_y + font->height + 3 /* to_y */);
}
/* If the client has a title, we draw it */
if (client->name != NULL &&
(mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) {
/* Draw the font */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
uint32_t values[] = { color->text, color->background, font->id };
xcb_change_gc(conn, gc, mask, values);
/* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME,
and we dont handle the old window name (COMPOUND_TEXT) but only _NET_WM_NAME, which
is UTF-8 */
if (client->name_len == -1)
xcb_image_text_8(conn, strlen(client->name), drawable, gc, offset_x + 3 /* X */,
offset_y + font->height /* Y = baseline of font */, client->name);
else
xcb_image_text_16(conn, client->name_len, drawable, gc, offset_x + 3 /* X */,
offset_y + font->height /* Y = baseline of font */, (xcb_char2b_t*)client->name);
}
}
/*
* Pushes the clients x and y coordinates to X11
*
*/
void reposition_client(xcb_connection_t *conn, Client *client) {
Output *output;
DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, 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(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
if (!client_is_floating(client))
return;
/* If the client is floating, we need to check if we moved it to a different workspace */
output = get_output_containing(client->rect.x + (client->rect.width / 2),
client->rect.y + (client->rect.height / 2));
if (client->workspace->output == output)
return;
if (output == NULL) {
DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y);
return;
}
if (output->current_workspace == NULL) {
DLOG("Boundary checking deferred, no current workspace on output\n");
client->force_reconfigure = true;
return;
}
DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output);
DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output);
floating_assign_to_workspace(client, output->current_workspace);
set_focus(conn, client, true);
}
/*
* Pushes the clients width/height to X11 and resizes the child window. This
* function also updates the clients position, so if you work on tiling clients
* only, you can use this function instead of separate calls to reposition_client
* and resize_client to reduce flickering.
*
*/
void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font);
DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
xcb_set_window_rect(conn, client->frame, client->rect);
/* 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 */
Rect *rect = &(client->child_rect);
switch (container_mode(client->container, true)) {
case MODE_STACK:
case MODE_TABBED:
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 && client->borderless) {
rect->x = 0;
rect->y = 0;
rect->width = client->rect.width;
rect->height = client->rect.height;
} else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
rect->x = 1;
rect->y = 1;
rect->width = client->rect.width - 1 - 1;
rect->height = client->rect.height - 1 - 1;
} 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;
}
rect->width -= (2 * client->border_width);
rect->height -= (2 * client->border_width);
/* Obey the ratio, if any */
if (client->proportional_height != 0 &&
client->proportional_width != 0) {
DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width);
double new_height = rect->height + 1;
int new_width = rect->width;
while (new_height > rect->height) {
new_height = ((double)client->proportional_height / client->proportional_width) * new_width;
if (new_height > rect->height)
new_width--;
}
/* Center the window */
rect->y += ceil(rect->height / 2) - floor(new_height / 2);
rect->x += ceil(rect->width / 2) - floor(new_width / 2);
rect->height = new_height;
rect->width = new_width;
DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
}
if (client->height_increment > 1) {
int old_height = rect->height;
rect->height -= (rect->height - client->base_height) % client->height_increment;
DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
old_height - rect->height, client->height_increment, client->base_height);
}
if (client->width_increment > 1) {
int old_width = rect->width;
rect->width -= (rect->width - client->base_width) % client->width_increment;
DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
old_width - rect->width, client->width_increment, client->base_width);
}
DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
xcb_set_window_rect(conn, client->child, *rect);
/* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3).
* This is necessary to inform the client of its position relative to the root window,
* not relative to its frame (as done in the configure_notify_event by the x server). */
fake_absolute_configure_notify(conn, client);
/* Force redrawing after resizing the window because any now lost
* pixels could contain old garbage. */
xcb_expose_event_t generated;
generated.window = client->frame;
generated.count = 0;
handle_expose_event(NULL, conn, &generated);
}
/*
* 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 *conn, Container *container) {
Client *client;
int num_clients = 0, current_client = 0;
CIRCLEQ_FOREACH(client, &(container->clients), clients)
num_clients++;
if (container->mode == MODE_DEFAULT) {
int height = (container->height / max(1, num_clients));
int rest_pixels = (container->height % max(1, num_clients));
DLOG("height per client = %d, rest = %d\n", height, rest_pixels);
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
if (container->workspace->fullscreen_client == client) {
current_client++;
continue;
}
/* If we have some pixels left to distribute, add one
* pixel to each client as long as possible. */
int this_height = height;
if (rest_pixels > 0) {
height++;
rest_pixels--;
}
/* 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 |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y +
(container->height / num_clients) * current_client) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), this_height))
resize_client(conn, client);
/* TODO: vertical default layout */
client->force_reconfigure = false;
current_client++;
}
} else {
i3Font *font = load_font(conn, config.font);
int decoration_height = (font->height + 2 + 2);
struct Stack_Window *stack_win = &(container->stack_win);
/* The size for each tab (width), necessary as a separate variable
* because num_clients gets fixed to 1 in tabbed mode. */
int size_each = (num_clients == 0 ? container->width : container->width / num_clients);
int stack_lines = num_clients;
/* 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->rect.height == 0 && num_clients > 1) {
DLOG("remapping stack win\n");
xcb_map_window(conn, stack_win->window);
} else DLOG("not remapping stackwin, height = %d, num_clients = %d\n",
stack_win->rect.height, num_clients);
if (container->mode == MODE_TABBED) {
/* By setting num_clients to 1 we force that the stack window will be only one line
* high. The rest of the code is useful in both cases. */
DLOG("tabbed mode, setting num_clients = 1\n");
if (stack_lines > 1)
stack_lines = 1;
}
if (container->stack_limit == STACK_LIMIT_COLS) {
stack_lines = ceil((float)num_clients / container->stack_limit_value);
} else if (container->stack_limit == STACK_LIMIT_ROWS) {
stack_lines = min(num_clients, container->stack_limit_value);
}
int height = decoration_height * stack_lines;
if (num_clients == 1) {
height = 0;
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
DLOG("Just one client, setting height to %d\n", height);
}
/* Check if we need to reconfigure our stack title window */
if (height > 0 && (
update_if_necessary(&(stack_win->rect.x), container->x) |
update_if_necessary(&(stack_win->rect.y), container->y) |
update_if_necessary(&(stack_win->rect.width), container->width) |
update_if_necessary(&(stack_win->rect.height), height))) {
/* Configuration can happen in two slightly different ways:
If there is no client in fullscreen mode, 5 parameters are passed
(x, y, width, height, stack mode is set to above which means top-most position).
If there is a fullscreen client, the fourth parameter is set to to the
fullscreen window as sibling and the stack mode is set to below, which means
that the stack_window will be placed just below the sibling, that is, under
the fullscreen window.
*/
uint32_t values[] = { stack_win->rect.x, stack_win->rect.y,
stack_win->rect.width, stack_win->rect.height,
XCB_STACK_MODE_ABOVE, XCB_STACK_MODE_BELOW };
uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE;
/* Raise the stack window, but keep it below the first floating client
* and below the fullscreen client (if any) */
Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients));
if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) {
mask |= XCB_CONFIG_WINDOW_SIBLING;
values[4] = first_floating->frame;
} else if (container->workspace->fullscreen_client != NULL) {
mask |= XCB_CONFIG_WINDOW_SIBLING;
values[4] = container->workspace->fullscreen_client->frame;
}
xcb_configure_window(conn, stack_win->window, mask, values);
}
/* Prepare the pixmap for usage */
if (num_clients > 1)
cached_pixmap_prepare(conn, &(stack_win->pixmap));
int current_row = 0, current_col = 0;
int wrap = 0;
if (container->stack_limit == STACK_LIMIT_COLS) {
/* wrap stores the number of rows after which we will
* wrap to a new column. */
wrap = ceil((float)num_clients / container->stack_limit_value);
} else if (container->stack_limit == STACK_LIMIT_ROWS) {
/* When limiting rows, the wrap variable serves a
* slightly different purpose: it holds the number of
* pixels which each client will get. This is constant
* during the following loop, so it saves us some
* divisions and ceil()ing. */
wrap = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
}
/* Render the decorations of all clients */
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
if (container->workspace->fullscreen_client == client) {
current_client++;
continue;
}
/* Check if we changed client->x or client->y by updating it.
* Note the bitwise OR instead of logical OR to force evaluation of all statements */
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y + height) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height - height))
resize_client(conn, client);
client->force_reconfigure = false;
int offset_x = 0;
int offset_y = 0;
if (container->mode == MODE_STACK ||
(container->mode == MODE_TABBED &&
container->stack_limit == STACK_LIMIT_COLS)) {
if (container->stack_limit == STACK_LIMIT_COLS) {
offset_x = current_col * (stack_win->rect.width / container->stack_limit_value);
offset_y = current_row * decoration_height;
current_row++;
if ((current_row % wrap) == 0) {
current_col++;
current_row = 0;
}
} else if (container->stack_limit == STACK_LIMIT_ROWS) {
offset_x = current_col * wrap;
offset_y = current_row * decoration_height;
current_row++;
if ((current_row % container->stack_limit_value) == 0) {
current_col++;
current_row = 0;
}
} else {
offset_y = current_client * decoration_height;
}
current_client++;
} else if (container->mode == MODE_TABBED) {
if (container->stack_limit == STACK_LIMIT_ROWS) {
LOG("You limited a tabbed container in its rows. "
"This makes no sense in tabbing mode.\n");
}
offset_x = current_client++ * size_each;
}
if (stack_win->pixmap.id == XCB_NONE)
continue;
decorate_window(conn, client, stack_win->pixmap.id,
stack_win->pixmap.gc, offset_x, offset_y);
}
/* Check if we need to fill one column because of an uneven
* amount of windows */
if (container->mode == MODE_STACK) {
if (container->stack_limit == STACK_LIMIT_COLS && (current_col % 2) != 0) {
xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
int offset_x = current_col * (stack_win->rect.width / container->stack_limit_value);
int offset_y = current_row * decoration_height;
xcb_rectangle_t rect = {offset_x, offset_y,
offset_x + container->width,
offset_y + decoration_height };
xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect);
} else if (container->stack_limit == STACK_LIMIT_ROWS && (current_row % 2) != 0) {
xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
int offset_x = current_col * wrap;
int offset_y = current_row * decoration_height;
xcb_rectangle_t rect = {offset_x, offset_y,
offset_x + container->width,
offset_y + decoration_height };
xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect);
}
}
if (stack_win->pixmap.id == XCB_NONE)
return;
xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc,
0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height);
}
}
static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
Client *client;
SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) {
DLOG("client is at %d, should be at %d\n", client->rect.y, *height);
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), r_ws->rect.x) |
update_if_necessary(&(client->rect.y), *height))
reposition_client(conn, client);
if (client->force_reconfigure |
update_if_necessary(&(client->rect.width), width) |
update_if_necessary(&(client->rect.height), client->desired_height))
resize_client(conn, client);
client->force_reconfigure = false;
DLOG("desired_height = %d\n", client->desired_height);
*height += client->desired_height;
}
}
static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
i3Font *font = load_font(conn, config.font);
Output *output = r_ws->output;
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
/* Fill the whole bar in black */
xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_rectangle_t rect = {0, 0, width, height};
xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect);
/* Set font */
xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id);
int drawn = 0;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->output != output)
continue;
struct Colortriple *color;
if (output->current_workspace == ws)
color = &(config.bar.focused);
else if (ws->urgent)
color = &(config.bar.urgent);
else color = &(config.bar.unfocused);
/* Draw the outer rect */
xcb_draw_rect(conn, output->bar, output->bargc, color->border,
drawn, /* x */
1, /* y */
ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */
height - 2 /* height = max. height - 1 px upper and 1 px bottom border */);
/* Draw the background of this rect */
xcb_draw_rect(conn, output->bar, output->bargc, color->background,
drawn + 1,
2,
ws->text_width + 4 + 4,
height - 4);
xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text);
xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background);
xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */,
font->height + 1 /* Y = baseline of font */,
(xcb_char2b_t*)ws->name);
drawn += ws->text_width + 12;
}
}
/*
* Modifies the event mask of all clients on the given workspace to either ignore or to handle
* enter notifies. It is handy to ignore notifies because they will be sent when a window is mapped
* under the cursor, thus when the user didnt enter the window actively at all.
*
*/
void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bool ignore_enter_notify) {
Client *client;
uint32_t values[1];
FOR_TABLE(workspace) {
if (workspace->table[cols][rows] == NULL)
continue;
CIRCLEQ_FOREACH(client, &(workspace->table[cols][rows]->clients), clients) {
/* Change event mask for the decorations */
values[0] = FRAME_EVENT_MASK;
if (ignore_enter_notify)
values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW);
xcb_change_window_attributes(conn, client->frame, XCB_CW_EVENT_MASK, values);
/* Change event mask for the child itself */
values[0] = CHILD_EVENT_MASK;
if (ignore_enter_notify)
values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW);
xcb_change_window_attributes(conn, client->child, XCB_CW_EVENT_MASK, values);
}
}
}
/*
* Renders the given workspace on the given screen
*
*/
void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) {
i3Font *font = load_font(conn, config.font);
int width = r_ws->rect.width;
int height = r_ws->rect.height;
/* Reserve space for dock clients */
Client *client;
SLIST_FOREACH(client, &(output->dock_clients), dock_clients)
height -= client->desired_height;
/* Space for the internal bar */
if (!config.disable_workspace_bar)
height -= (font->height + 6);
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;
ignore_enter_notify_forall(conn, r_ws, true);
/* Go through the whole table and render whats necessary */
FOR_TABLE(r_ws) {
Container *container = r_ws->table[cols][rows];
if (container == NULL)
continue;
int single_width = -1, single_height = -1;
/* Update position of the container */
container->row = rows;
container->col = cols;
container->x = xoffset[rows];
container->y = yoffset[cols];
container->width = 0;
for (int c = 0; c < container->colspan; c++) {
if (r_ws->width_factor[cols+c] == 0)
container->width += (width / r_ws->cols);
else container->width += get_unoccupied_x(r_ws) * r_ws->width_factor[cols+c];
if (single_width == -1)
single_width = container->width;
}
DLOG("height is %d\n", height);
container->height = 0;
for (int c = 0; c < container->rowspan; c++) {
if (r_ws->height_factor[rows+c] == 0)
container->height += (height / r_ws->rows);
else container->height += get_unoccupied_y(r_ws) * r_ws->height_factor[rows+c];
if (single_height == -1)
single_height = container->height;
}
/* Render the container if it is not empty */
render_container(conn, container);
xoffset[rows] += single_width;
yoffset[cols] += single_height;
}
/* Reposition all floating clients with force_reconfigure == true */
TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) {
if (!client->force_reconfigure)
continue;
client->force_reconfigure = false;
reposition_client(conn, client);
resize_client(conn, client);
}
ignore_enter_notify_forall(conn, r_ws, false);
render_bars(conn, r_ws, width, &height);
if (!config.disable_workspace_bar)
render_internal_bar(conn, r_ws, width, font->height + 6);
}
/*
* Renders the whole layout, that is: Go through each screen, each workspace, each container
* and render each client. This also renders the bars.
*
* If you dont need to render *everything*, you should call render_container on the container
* you want to refresh.
*
*/
void render_layout(xcb_connection_t *conn) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->current_workspace != NULL)
render_workspace(conn, output, output->current_workspace);
xcb_flush(conn);
}

View File

@ -1,600 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdbool.h>
#include <assert.h>
#include <limits.h>
#include <locale.h>
#include <fcntl.h>
#include <getopt.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_property.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xcb_icccm.h>
#include <ev.h>
#include "config.h"
#include "data.h"
#include "debug.h"
#include "handlers.h"
#include "click.h"
#include "i3.h"
#include "layout.h"
#include "queue.h"
#include "table.h"
#include "util.h"
#include "xcb.h"
#include "randr.h"
#include "xinerama.h"
#include "manage.h"
#include "ipc.h"
#include "log.h"
#include "sighandler.h"
static int xkb_event_base;
int xkb_current_group;
xcb_connection_t *global_conn;
/* This is the path to i3, copied from argv[0] when starting up */
char **start_argv;
/* This is our connection to X11 for use with XKB */
Display *xkbdpy;
xcb_key_symbols_t *keysyms;
/* The list of key bindings */
struct bindings_head *bindings;
/* The list of exec-lines */
struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
/* The list of assignments */
struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
/* This is a list of Stack_Windows, global, for easier/faster access on expose events */
struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
/* The event handlers need to be global because they are accessed by our custom event handler
in handle_button_press(), needed for graphical resizing */
xcb_event_handlers_t evenths;
xcb_atom_t atoms[NUM_ATOMS];
xcb_window_t root;
int num_screens = 0;
/* The depth of the root screen (used e.g. for creating new pixmaps later) */
uint8_t root_depth;
/* We hope that XKB is supported and set this to false */
bool xkb_supported = true;
/*
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
*
*/
static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
/* empty, because xcb_prepare_cb and xcb_check_cb are used */
}
/*
* Flush before blocking (and waiting for new events)
*
*/
static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
xcb_flush(evenths.c);
}
/*
* Instead of polling the X connection socket we leave this to
* xcb_poll_for_event() which knows better than we can ever know.
*
*/
static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
xcb_generic_event_t *event;
while ((event = xcb_poll_for_event(evenths.c)) != NULL) {
xcb_event_handle(&evenths, event);
free(event);
}
}
/*
* When using xmodmap to change the keyboard mapping, this event
* is only sent via XKB. Therefore, we need this special handler.
*
*/
static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
DLOG("Handling XKB event\n");
XkbEvent ev;
/* When using xmodmap, every change (!) gets an own event.
* Therefore, we just read all events and only handle the
* mapping_notify once. */
bool mapping_changed = false;
while (XPending(xkbdpy)) {
XNextEvent(xkbdpy, (XEvent*)&ev);
/* While we should never receive a non-XKB event,
* better do sanity checking */
if (ev.type != xkb_event_base)
continue;
if (ev.any.xkb_type == XkbMapNotify) {
mapping_changed = true;
continue;
}
if (ev.any.xkb_type != XkbStateNotify) {
ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type);
continue;
}
/* See The XKB Extension: Library Specification, section 14.1 */
/* We check if the current group (each group contains
* two levels) has been changed. Mode_switch activates
* group XkbGroup2Index */
if (xkb_current_group == ev.state.group)
continue;
xkb_current_group = ev.state.group;
if (ev.state.group == XkbGroup2Index) {
DLOG("Mode_switch enabled\n");
grab_all_keys(global_conn, true);
}
if (ev.state.group == XkbGroup1Index) {
DLOG("Mode_switch disabled\n");
ungrab_all_keys(global_conn);
grab_all_keys(global_conn, false);
}
}
if (!mapping_changed)
return;
DLOG("Keyboard mapping changed, updating keybindings\n");
xcb_key_symbols_free(keysyms);
keysyms = xcb_key_symbols_alloc(global_conn);
xcb_get_numlock_mask(global_conn);
ungrab_all_keys(global_conn);
DLOG("Re-grabbing...\n");
translate_keysyms();
grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index));
DLOG("Done\n");
}
int main(int argc, char *argv[], char *env[]) {
int i, screens, opt;
char *override_configpath = NULL;
bool autostart = true;
bool only_check_config = false;
bool force_xinerama = false;
xcb_connection_t *conn;
xcb_property_handlers_t prophs;
xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
static struct option long_options[] = {
{"no-autostart", no_argument, 0, 'a'},
{"config", required_argument, 0, 'c'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"force-xinerama", no_argument, 0, 0},
{0, 0, 0, 0}
};
int option_index = 0;
setlocale(LC_ALL, "");
/* Disable output buffering to make redirects in .xsession actually useful for debugging */
if (!isatty(fileno(stdout)))
setbuf(stdout, NULL);
start_argv = argv;
while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
autostart = false;
break;
case 'c':
override_configpath = sstrdup(optarg);
break;
case 'C':
LOG("Checking configuration file only (-C)\n");
only_check_config = true;
break;
case 'v':
printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
exit(EXIT_SUCCESS);
case 'V':
set_verbosity(true);
break;
case 'd':
LOG("Enabling debug loglevel %s\n", optarg);
add_loglevel(optarg);
break;
case 'l':
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
break;
case 0:
if (strcmp(long_options[option_index].name, "force-xinerama") == 0) {
force_xinerama = true;
ELOG("Using Xinerama instead of RandR. This option should be "
"avoided at all cost because it does not refresh the list "
"of screens, so you cannot configure displays at runtime. "
"Please check if your driver really does not support RandR "
"and disable this option as soon as you can.\n");
break;
}
/* fall-through */
default:
fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "-a: disable autostart\n");
fprintf(stderr, "-v: display version and exit\n");
fprintf(stderr, "-V: enable verbose mode\n");
fprintf(stderr, "-d <loglevel>: enable debug loglevel <loglevel>\n");
fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
fprintf(stderr, "-C: check configuration file and exit\n");
fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This "
"option should only be used if you are stuck with the "
"nvidia closed source driver which does not support RandR.\n");
exit(EXIT_FAILURE);
}
}
LOG("i3 version " I3_VERSION " starting\n");
/* Initialize the table data structures for each workspace */
init_table();
memset(&evenths, 0, sizeof(xcb_event_handlers_t));
memset(&prophs, 0, sizeof(xcb_property_handlers_t));
conn = global_conn = xcb_connect(NULL, &screens);
if (xcb_connection_has_error(conn))
die("Cannot open display\n");
load_configuration(conn, override_configpath, false);
if (only_check_config) {
LOG("Done checking configuration file. Exiting.\n");
exit(0);
}
/* Create the initial container on the first workspace. This used to
* be part of init_table, but since it possibly requires an X
* connection and a loaded configuration (default mode for new
* containers may be stacking, which requires a new window to be
* created), it had to be delayed. */
expand_table_cols(TAILQ_FIRST(workspaces));
expand_table_rows(TAILQ_FIRST(workspaces));
/* Place requests for the atoms we need as soon as possible */
#define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
REQUEST_ATOM(_NET_SUPPORTED);
REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
REQUEST_ATOM(_NET_WM_NAME);
REQUEST_ATOM(_NET_WM_STATE);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
REQUEST_ATOM(_NET_WM_DESKTOP);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
REQUEST_ATOM(WM_PROTOCOLS);
REQUEST_ATOM(WM_DELETE_WINDOW);
REQUEST_ATOM(UTF8_STRING);
REQUEST_ATOM(WM_STATE);
REQUEST_ATOM(WM_CLIENT_LEADER);
REQUEST_ATOM(_NET_CURRENT_DESKTOP);
REQUEST_ATOM(_NET_ACTIVE_WINDOW);
REQUEST_ATOM(_NET_WORKAREA);
/* TODO: this has to be more beautiful somewhen */
int major, minor, error;
major = XkbMajorVersion;
minor = XkbMinorVersion;
int errBase;
if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) {
ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n");
xkb_supported = false;
}
if (xkb_supported) {
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
return 1;
}
int i1;
if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n");
return 1;
}
/* end of ugliness */
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
XkbMapNotifyMask | XkbStateNotifyMask,
XkbMapNotifyMask | XkbStateNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
}
}
/* Initialize event loop using libev */
struct ev_loop *loop = ev_loop_new(0);
if (loop == NULL)
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
struct ev_io *xkb = scalloc(sizeof(struct ev_io));
struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(loop, xcb_watcher);
if (xkb_supported) {
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
ev_io_start(loop, xkb);
/* Flush the buffer so that libev can properly get new events */
XFlush(xkbdpy);
}
ev_check_init(xcb_check, xcb_check_cb);
ev_check_start(loop, xcb_check);
ev_prepare_init(xcb_prepare, xcb_prepare_cb);
ev_prepare_start(loop, xcb_prepare);
/* Grab the server to delay any events until we enter the eventloop */
xcb_grab_server(conn);
xcb_event_handlers_init(conn, &evenths);
/* DEBUG: Trap all events and print them */
for (i = 2; i < 128; ++i)
xcb_event_set_handler(&evenths, i, handle_event, 0);
for (i = 0; i < 256; ++i)
xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
/* Expose = an Application should redraw itself, in this case its our titlebars. */
xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
/* Key presses are pretty obvious, I think */
xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
/* Enter window = user moved his mouse over the window */
xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
/* Button press = user pushed a mouse button over one of our windows */
xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
/* Map notify = there is a new window */
xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
/* Unmap notify = window disappeared. When sent from a client, we dont manage
it any longer. Usually, the client destroys the window shortly afterwards. */
xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
/* Destroy notify is handled the same as unmap notify */
xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
/* Configure notify = windows configuration (geometry, stacking, …). We only need
it to set up ignore the following enter_notify events */
xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
/* Configure request = window tried to change size on its own */
xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
/* Motion notify = user moved his cursor (over the root window and may
* cross virtual screen boundaries doing that) */
xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL);
/* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */
xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL);
/* Client message are sent to the root window. The only interesting client message
for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */
xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
/* Initialize the property handlers */
xcb_property_handlers_init(&prophs, &evenths);
/* Watch size hints (to obey correct aspect ratio) */
xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
/* Get the root window and set the event mask */
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
root = root_screen->root;
root_depth = root_screen->root_depth;
uint32_t mask = XCB_CW_EVENT_MASK;
uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
projector), the root window gets a
ConfigureNotify */
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW };
xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
check_error(conn, cookie, "Another window manager seems to be running");
/* Setup NetWM atoms */
#define GET_ATOM(name) { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
if (!reply) { \
ELOG("Could not get atom " #name "\n"); \
exit(-1); \
} \
atoms[name] = reply->atom; \
free(reply); \
}
GET_ATOM(_NET_SUPPORTED);
GET_ATOM(_NET_WM_STATE_FULLSCREEN);
GET_ATOM(_NET_SUPPORTING_WM_CHECK);
GET_ATOM(_NET_WM_NAME);
GET_ATOM(_NET_WM_STATE);
GET_ATOM(_NET_WM_WINDOW_TYPE);
GET_ATOM(_NET_WM_DESKTOP);
GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
GET_ATOM(_NET_WM_STRUT_PARTIAL);
GET_ATOM(WM_PROTOCOLS);
GET_ATOM(WM_DELETE_WINDOW);
GET_ATOM(UTF8_STRING);
GET_ATOM(WM_STATE);
GET_ATOM(WM_CLIENT_LEADER);
GET_ATOM(_NET_CURRENT_DESKTOP);
GET_ATOM(_NET_ACTIVE_WINDOW);
GET_ATOM(_NET_WORKAREA);
xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
/* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
/* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
/* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */
xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL);
/* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
/* Watch WM_CLASS (= class of the window) */
xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL);
/* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */
xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL);
/* Watch WM_HINTS (contains the urgent property) */
xcb_property_set_handler(&prophs, WM_HINTS, UINT_MAX, handle_hints, NULL);
/* Set up the atoms we support */
check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
/* Set up the window managers name */
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
keysyms = xcb_key_symbols_alloc(conn);
xcb_get_numlock_mask(conn);
translate_keysyms();
grab_all_keys(conn, false);
int randr_base;
if (force_xinerama) {
initialize_xinerama(conn);
} else {
DLOG("Checking for XRandR...\n");
initialize_randr(conn, &randr_base);
xcb_event_set_handler(&evenths,
randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY,
handle_screen_change,
NULL);
}
xcb_flush(conn);
/* Get pointer position to see on which screen were starting */
xcb_query_pointer_reply_t *reply;
if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
ELOG("Could not get pointer position\n");
return 1;
}
Output *screen = get_output_containing(reply->root_x, reply->root_y);
if (screen == NULL) {
ELOG("ERROR: No screen at %d x %d, starting on the first screen\n",
reply->root_x, reply->root_y);
screen = get_first_output();
}
DLOG("Starting on %p\n", screen->current_workspace);
c_ws = screen->current_workspace;
manage_existing_windows(conn, &prophs, root);
/* Create the UNIX domain socket for IPC */
if (config.ipc_socket_path != NULL) {
int ipc_socket = ipc_create_socket(config.ipc_socket_path);
if (ipc_socket == -1) {
ELOG("Could not create the IPC socket, IPC disabled\n");
} else {
struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
ev_io_start(loop, ipc_io);
}
}
/* Handle the events which arrived until now */
xcb_check_cb(NULL, NULL, 0);
setup_signal_handler();
/* Ignore SIGPIPE to survive errors when an IPC client disconnects
* while we are sending him a message */
signal(SIGPIPE, SIG_IGN);
/* Ungrab the server to receive events and enter libevs eventloop */
xcb_ungrab_server(conn);
/* Autostarting exec-lines */
struct Autostart *exec;
if (autostart) {
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
}
}
ev_loop(loop, 0);
/* not reached */
return 0;
}

View File

@ -1,323 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* This file contains the functions for resizing table columns/rows because
* its actually lots of work, compared to the other handlers.
*
*/
#include <stdlib.h>
#include <assert.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include "i3.h"
#include "data.h"
#include "resize.h"
#include "util.h"
#include "xcb.h"
#include "debug.h"
#include "layout.h"
#include "randr.h"
#include "config.h"
#include "floating.h"
#include "workspace.h"
#include "log.h"
/*
* This is an ugly data structure which we need because there is no standard
* way of having nested functions (only available as a gcc extension at the
* moment, clang doesnt support it) or blocks (only available as a clang
* extension and only on Mac OS X systems at the moment).
*
*/
struct callback_params {
resize_orientation_t orientation;
Output *screen;
xcb_window_t helpwin;
uint32_t *new_position;
};
DRAGGING_CB(resize_callback) {
struct callback_params *params = extra;
Output *screen = params->screen;
DLOG("new x = %d, y = %d\n", new_x, new_y);
if (params->orientation == O_VERTICAL) {
/* Check if the new coordinates are within screen boundaries */
if (new_x > (screen->rect.x + screen->rect.width - 25) ||
new_x < (screen->rect.x + 25))
return;
*(params->new_position) = new_x;
xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position);
} else {
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
new_y < (screen->rect.y + 25))
return;
*(params->new_position) = new_y;
xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position);
}
xcb_flush(conn);
}
/*
* Renders the resize window between the first/second container and resizes
* the table column/row.
*
*/
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, xcb_button_press_event_t *event) {
uint32_t new_position;
Output *screen = get_output_containing(event->root_x, event->root_y);
if (screen == NULL) {
ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
return 1;
}
/* We cannot use the X root window's width_in_pixels or height_in_pixels
* attributes here since they are not updated when you configure new
* screens during runtime. Instead, we just use the most right and most
* bottom Xinerama screen and use their position + width/height to get
* the area of pixels currently in use */
Output *most_right = get_output_most(D_RIGHT, screen),
*most_bottom = get_output_most(D_DOWN, screen);
DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x);
DLOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
uint32_t mask = 0;
uint32_t values[2];
mask = XCB_CW_OVERRIDE_REDIRECT;
values[0] = 1;
/* Open a new window, the resizebar. Grab the pointer and move the window around
as the user moves the pointer. */
Rect grabrect = {0,
0,
most_right->rect.x + most_right->rect.width,
most_bottom->rect.x + most_bottom->rect.height};
xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, true, mask, values);
Rect helprect;
if (orientation == O_VERTICAL) {
helprect.x = event->root_x;
helprect.y = screen->rect.y;
helprect.width = 2;
helprect.height = screen->rect.height;
new_position = event->root_x;
} else {
helprect.x = screen->rect.x;
helprect.y = event->root_y;
helprect.width = screen->rect.width;
helprect.height = 2;
new_position = event->root_y;
}
mask = XCB_CW_BACK_PIXEL;
values[0] = config.client.focused.border;
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[1] = 1;
xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT,
(orientation == O_VERTICAL ?
XCB_CURSOR_SB_H_DOUBLE_ARROW :
XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values);
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
xcb_flush(conn);
struct callback_params params = { orientation, screen, helpwin, &new_position };
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin);
xcb_flush(conn);
int pixels;
if (orientation == O_VERTICAL)
pixels = (new_position - event->root_x);
else pixels = (new_position - event->root_y);
resize_container(conn, ws, first, second, orientation, pixels);
return 1;
}
/*
* Resizes a column/row by the given amount of pixels. Called by
* resize_graphical_handler (the user clicked) or parse_resize_command (the
* user issued the command)
*
*/
void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, int pixels) {
/* TODO: refactor this, both blocks are very identical */
if (orientation == O_VERTICAL) {
int default_width = ws->rect.width / ws->cols;
int old_unoccupied_x = get_unoccupied_x(ws);
/* We pre-calculate the unoccupied space to see if we need to adapt sizes before
* doing the resize */
int new_unoccupied_x = old_unoccupied_x;
if (old_unoccupied_x == 0)
old_unoccupied_x = ws->rect.width;
if (ws->width_factor[first] == 0)
new_unoccupied_x += default_width;
if (ws->width_factor[second] == 0)
new_unoccupied_x += default_width;
DLOG("\n\n\n");
DLOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x);
int cols_without_wf = 0;
int old_width, old_second_width;
for (int col = 0; col < ws->cols; col++)
if (ws->width_factor[col] == 0)
cols_without_wf++;
DLOG("old_unoccupied_x = %d\n", old_unoccupied_x);
DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->width_factor[first] == 0)
old_width = (old_unoccupied_x / max(cols_without_wf, 1));
else old_width = ws->width_factor[first] * old_unoccupied_x;
DLOG("second (before = %f)\n", ws->width_factor[second]);
if (ws->width_factor[second] == 0)
old_second_width = (old_unoccupied_x / max(cols_without_wf, 1));
else old_second_width = ws->width_factor[second] * old_unoccupied_x;
DLOG("middle = %f\n", ws->width_factor[first]);
/* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */
if (new_unoccupied_x != old_unoccupied_x)
for (int col = 0; col < ws->cols; col++) {
if (ws->width_factor[col] == 0)
continue;
DLOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]);
ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x;
DLOG("to %f\n", ws->width_factor[col]);
}
DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->width_factor[first] == 0)
ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
DLOG("first->width = %d, pixels = %d\n", old_width, pixels);
ws->width_factor[first] *= (float)(old_width + pixels) / old_width;
DLOG("-> %f\n", ws->width_factor[first]);
DLOG("Updating second (before = %f)\n", ws->width_factor[second]);
if (ws->width_factor[second] == 0)
ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
DLOG("middle = %f\n", ws->width_factor[second]);
DLOG("second->width = %d, pixels = %d\n", old_second_width, pixels);
ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width;
DLOG("-> %f\n", ws->width_factor[second]);
DLOG("new unoccupied_x = %d\n", get_unoccupied_x(ws));
DLOG("\n\n\n");
} else {
int ws_height = workspace_height(ws);
int default_height = ws_height / ws->rows;
int old_unoccupied_y = get_unoccupied_y(ws);
/* We pre-calculate the unoccupied space to see if we need to adapt sizes before
* doing the resize */
int new_unoccupied_y = old_unoccupied_y;
if (old_unoccupied_y == 0)
old_unoccupied_y = ws_height;
if (ws->height_factor[first] == 0)
new_unoccupied_y += default_height;
if (ws->height_factor[second] == 0)
new_unoccupied_y += default_height;
int cols_without_hf = 0;
int old_height, old_second_height;
for (int row = 0; row < ws->rows; row++)
if (ws->height_factor[row] == 0)
cols_without_hf++;
DLOG("old_unoccupied_y = %d\n", old_unoccupied_y);
DLOG("Updating first (before = %f)\n", ws->height_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0)
old_height = (old_unoccupied_y / max(cols_without_hf, 1));
else old_height = ws->height_factor[first] * old_unoccupied_y;
DLOG("second (before = %f)\n", ws->height_factor[second]);
if (ws->height_factor[second] == 0)
old_second_height = (old_unoccupied_y / max(cols_without_hf, 1));
else old_second_height = ws->height_factor[second] * old_unoccupied_y;
DLOG("middle = %f\n", ws->height_factor[first]);
DLOG("\n\n\n");
DLOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y);
/* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */
if (new_unoccupied_y != old_unoccupied_y)
for (int row = 0; row < ws->rows; row++) {
if (ws->height_factor[row] == 0)
continue;
DLOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]);
ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y;
DLOG("to %f\n", ws->height_factor[row]);
}
DLOG("Updating first (before = %f)\n", ws->height_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0)
ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y;
DLOG("first->width = %d, pixels = %d\n", old_height, pixels);
ws->height_factor[first] *= (float)(old_height + pixels) / old_height;
DLOG("-> %f\n", ws->height_factor[first]);
DLOG("Updating second (before = %f)\n", ws->height_factor[second]);
if (ws->height_factor[second] == 0)
ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y;
DLOG("middle = %f\n", ws->height_factor[second]);
DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels);
ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height;
DLOG("-> %f\n", ws->height_factor[second]);
DLOG("new unoccupied_y = %d\n", get_unoccupied_y(ws));
DLOG("\n\n\n");
}
render_layout(conn);
}