Merge branch 'next' (3.β is stable now)

This commit is contained in:
Michael Stapelberg
2009-06-26 13:27:06 +02:00
43 changed files with 3218 additions and 764 deletions

250
src/client.c Normal file
View File

@ -0,0 +1,250 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 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 <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"
/*
* 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) {
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
}
}
/*
* 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 == NULL || strcasestr(client->window_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;
}
/*
* Enters fullscreen mode for the given client. This is called by toggle_fullscreen
* and when moving a fullscreen client to another screen.
*
*/
void client_enter_fullscreen(xcb_connection_t *conn, Client *client) {
Workspace *workspace = client->workspace;
if (workspace->fullscreen_client != NULL) {
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
return;
}
client->fullscreen = true;
workspace->fullscreen_client = client;
LOG("Entering fullscreen mode...\n");
/* We just entered fullscreen mode, lets configure the window */
uint32_t mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
uint32_t values[4] = {workspace->rect.x,
workspace->rect.y,
workspace->rect.width,
workspace->rect.height};
LOG("child itself will be at %dx%d with size %dx%d\n",
values[0], values[1], values[2], values[3]);
xcb_configure_window(conn, client->frame, mask, values);
/* Childs coordinates are relative to the parent (=frame) */
values[0] = 0;
values[1] = 0;
xcb_configure_window(conn, client->child, mask, values);
/* Raise the window */
values[0] = XCB_STACK_MODE_ABOVE;
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
Rect child_rect = workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
xcb_flush(conn);
}
/*
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
* screen. When leaving fullscreen, re-rendering the layout is forced.
*
*/
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
/* dock clients cannot enter fullscreen mode */
assert(!client->dock);
Workspace *workspace = client->workspace;
if (!client->fullscreen) {
client_enter_fullscreen(conn, client);
return;
}
LOG("leaving fullscreen mode\n");
client->fullscreen = false;
workspace->fullscreen_client = NULL;
if (client_is_floating(client)) {
/* For floating clients its enough if we just reconfigure that window (in fact,
* re-rendering the layout will not update the client.) */
reposition_client(conn, client);
resize_client(conn, client);
/* redecorate_window flushes */
redecorate_window(conn, client);
} else {
client_set_below_floating(conn, client);
/* Because the coordinates of the window havent changed, it would not be
re-configured if we dont set the following flag */
client->force_reconfigure = true;
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
render_layout(conn);
}
xcb_flush(conn);
}
/*
* 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 */
Client *first_floating = TAILQ_FIRST(&(client->workspace->floating_clients));
if (first_floating != TAILQ_END(&(client->workspace->floating_clients))) {
LOG("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);
}
}
/*
* 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);
}

View File

@ -23,6 +23,9 @@
#include "layout.h"
#include "i3.h"
#include "xinerama.h"
#include "client.h"
#include "floating.h"
#include "xcb.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */
@ -57,10 +60,17 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
int new_row = current_row,
new_col = current_col;
Container *container = CUR_CELL;
Workspace *t_ws = c_ws;
/* Makes sure new_col and new_row are within bounds of the new workspace */
void check_colrow_boundaries() {
if (new_col >= t_ws->cols)
new_col = (t_ws->cols - 1);
if (new_row >= t_ws->rows)
new_row = (t_ws->rows - 1);
}
/* There always is a container. If not, current_col or current_row is wrong */
assert(container != NULL);
@ -103,6 +113,21 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
t_ws = &(workspaces[screen->current_workspace]);
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
}
check_colrow_boundaries();
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
LOG("Cell empty, checking for colspanned client above...\n");
for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
continue;
new_col = cols;
break;
}
LOG("Fixed it to new col %d\n", new_col);
}
} else if (direction == D_LEFT || direction == D_RIGHT) {
if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
new_col = current_col + t_ws->table[current_col][current_row]->colspan;
@ -130,16 +155,27 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
t_ws = &(workspaces[screen->current_workspace]);
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
}
check_colrow_boundaries();
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
LOG("Cell empty, checking for rowspanned client above...\n");
for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
continue;
new_row = rows;
break;
}
LOG("Fixed it to new row %d\n", new_row);
}
} else {
LOG("direction unhandled\n");
return;
}
/* Bounds checking */
if (new_col >= t_ws->cols)
new_col = (t_ws->cols - 1);
if (new_row >= t_ws->rows)
new_row = (t_ws->rows - 1);
check_colrow_boundaries();
if (t_ws->table[new_col][new_row]->currently_focused != NULL)
set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
@ -237,10 +273,13 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
new = CUR_TABLE[current_col][++current_row];
break;
/* To make static analyzers happy: */
default:
return;
}
/* Remove it from the old container and put it into the new one */
remove_client_from_container(conn, current_client, container);
client_remove_from_container(conn, current_client, container, true);
if (new->currently_focused != NULL)
CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
@ -261,7 +300,8 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
/* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, workspace);
render_layout(conn);
render_workspace(conn, workspace->screen, workspace);
xcb_flush(conn);
set_focus(conn, current_client, true);
}
@ -309,6 +349,9 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
new = CUR_TABLE[current_col][++current_row];
break;
/* To make static analyzers happy: */
default:
return;
}
LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
@ -415,11 +458,66 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
container->rowspan++;
break;
}
/* To make static analyzers happy: */
default:
return;
}
render_layout(conn);
}
static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) {
/* t_ws (to workspace) is just a container pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]),
*old_ws = client->workspace;
LOG("moving floating\n");
if (t_ws->screen == NULL) {
LOG("initializing new workspace, setting num to %d\n", workspace-1);
t_ws->screen = c_ws->screen;
/* Copy the dimensions from the virtual screen */
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
} else {
/* Check if there is already a fullscreen client on the destination workspace and
* stop moving if so. */
if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
return;
}
}
floating_assign_to_workspace(client, t_ws);
bool target_invisible = t_ws->screen->current_workspace != t_ws->num;
/* If were moving it to an invisible screen, we need to unmap it */
if (target_invisible) {
LOG("This workspace is not visible, unmapping\n");
xcb_unmap_window(conn, client->frame);
} else {
/* If this is not the case, we move the window to a workspace
* which is on another screen, so we also need to adjust its
* coordinates. */
LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
uint32_t relative_x = client->rect.x - old_ws->rect.x,
relative_y = client->rect.y - old_ws->rect.y;
LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
client->rect.x = t_ws->rect.x + relative_x;
client->rect.y = t_ws->rect.y + relative_y;
LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
reposition_client(conn, client);
xcb_flush(conn);
}
LOG("done\n");
render_layout(conn);
if (!target_invisible)
set_focus(conn, client, true);
}
/*
* Moves the currently selected window to the given workspace
*
@ -461,14 +559,14 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
assert(to_container != NULL);
remove_client_from_container(conn, current_client, container);
client_remove_from_container(conn, current_client, container, true);
if (container->workspace->fullscreen_client == current_client)
container->workspace->fullscreen_client = NULL;
/* TODO: insert it to the correct position */
CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
if (current_client->fullscreen)
t_ws->fullscreen_client = current_client;
LOG("Moved.\n");
current_client->container = to_container;
@ -476,16 +574,26 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
container->currently_focused = to_focus;
to_container->currently_focused = current_client;
bool target_invisible = (to_container->workspace->screen->current_workspace != to_container->workspace->num);
/* If were moving it to an invisible screen, we need to unmap it */
if (to_container->workspace->screen->current_workspace != to_container->workspace->num) {
if (target_invisible) {
LOG("This workspace is not visible, unmapping\n");
xcb_unmap_window(conn, current_client->frame);
} else {
if (current_client->fullscreen) {
LOG("Calling client_enter_fullscreen again\n");
client_enter_fullscreen(conn, current_client);
}
}
/* delete all empty columns/rows */
cleanup_table(conn, container->workspace);
render_layout(conn);
if (!target_invisible)
set_focus(conn, current_client, true);
}
/*
@ -539,22 +647,25 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
/* Check if we need to change something or if were already there */
if (c_ws->screen->current_workspace == (workspace-1)) {
if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true);
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
set_focus(conn, last_focused, true);
if (need_warp) {
warp_pointer_into(conn, CUR_CELL->currently_focused);
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
}
return;
}
t_ws->screen->current_workspace = workspace-1;
/* Unmap all clients of the current workspace */
unmap_workspace(conn, c_ws);
Workspace *old_workspace = c_ws;
c_ws = &workspaces[workspace-1];
/* Unmap all clients of the old workspace */
unmap_workspace(conn, old_workspace);
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("new current row = %d, current col = %d\n", current_row, current_col);
@ -566,6 +677,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients)
xcb_map_window(conn, client->frame);
/* Map all floating clients */
if (!c_ws->floating_hidden)
TAILQ_FOREACH(client, &(c_ws->floating_clients), floating_clients)
xcb_map_window(conn, client->frame);
/* Map all stack windows, if any */
struct Stack_Window *stack_win;
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
@ -575,10 +691,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
ignore_enter_notify_forall(conn, c_ws, false);
/* Restore focus on the new workspace */
if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true);
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
set_focus(conn, last_focused, true);
if (need_warp) {
warp_pointer_into(conn, CUR_CELL->currently_focused);
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
} else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
@ -586,12 +703,161 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
render_layout(conn);
}
/*
* Jumps to the given window class / title.
* Title is matched using strstr, that is, matches if it appears anywhere
* in the string. Regular expressions seem to be a bit overkill here. However,
* if we need them for something else somewhen, we may introduce them here, too.
*
*/
static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
char *classtitle;
Client *client;
/* The first character is a quote, this was checked before */
classtitle = sstrdup(arguments+1);
/* The last character is a quote, we just set it to NULL */
classtitle[strlen(classtitle)-1] = '\0';
if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
free(classtitle);
LOG("No matching client found.\n");
return;
}
free(classtitle);
set_focus(conn, client, true);
}
/*
* Jump directly to the specified workspace, row and col.
* Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you)
*
*/
static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
int ws, row, col;
int result;
result = sscanf(arguments, "%d %d %d", &ws, &col, &row);
LOG("Jump called with %d parameters (\"%s\")\n", result, arguments);
/* No match? Either no arguments were specified, or no numbers */
if (result < 1) {
LOG("At least one valid argument required\n");
return;
}
/* Move to the target workspace */
show_workspace(conn, ws);
if (result < 3)
return;
LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
/* Move to row/col */
if (row >= c_ws->rows)
row = c_ws->rows - 1;
if (col >= c_ws->cols)
col = c_ws->cols - 1;
LOG("Jumping to col %d, row %d\n", col, row);
if (c_ws->table[col][row]->currently_focused != NULL)
set_focus(conn, c_ws->table[col][row]->currently_focused, true);
}
/*
* Travels the focus stack by the given number of times (or once, if no argument
* was specified). That is, selects the window you were in before you focused
* the current window.
*
* The special values 'floating' (select the next floating window), 'tiling'
* (select the next tiling window), 'ft' (if the current window is floating,
* select the next tiling window and vice-versa) are also valid
*
*/
static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
/* Start count at -1 to always skip the first element */
int times, count = -1;
Client *current;
bool floating_criteria;
/* Either its one of the special values… */
if (strcasecmp(arguments, "floating") == 0) {
floating_criteria = true;
} else if (strcasecmp(arguments, "tiling") == 0) {
floating_criteria = false;
} else if (strcasecmp(arguments, "ft") == 0) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
LOG("Cannot select the next floating/tiling client because there is no client at all\n");
return;
}
floating_criteria = !client_is_floating(last_focused);
} else {
/* …or a number was specified */
if (sscanf(arguments, "%u", &times) != 1) {
LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
times = 1;
}
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
if (++count < times) {
LOG("Skipping\n");
continue;
}
LOG("Focussing\n");
set_focus(conn, current, true);
break;
}
return;
}
/* Select the next client matching the criteria parsed above */
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients)
if (client_is_floating(current) == floating_criteria) {
set_focus(conn, current, true);
break;
}
}
/*
* Goes through the list of arguments (for exec()) and checks if the given argument
* is present. If not, it copies the arguments (because we cannot realloc it) and
* appends the given argument.
*
*/
static char **append_argument(char **original, char *argument) {
int num_args;
for (num_args = 0; original[num_args] != NULL; num_args++) {
LOG("original argument: \"%s\"\n", original[num_args]);
/* If the argument is already present we return the original pointer */
if (strcmp(original[num_args], argument) == 0)
return original;
}
/* Copy the original array */
char **result = smalloc((num_args+2) * sizeof(char*));
memcpy(result, original, num_args * sizeof(char*));
result[num_args] = argument;
result[num_args+1] = NULL;
return result;
}
/*
* Parses a command, see file CMDMODE for more information
*
*/
void parse_command(xcb_connection_t *conn, const char *command) {
LOG("--- parsing command \"%s\" ---\n", command);
/* Get the first client from focus stack because floating clients are not
* in any container, therefore CUR_CELL is not appropriate. */
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(c_ws->focus_stack)))
last_focused = NULL;
/* Hmm, just to be sure */
if (command[0] == '\0')
return;
@ -612,37 +878,66 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Is it <restart>? Then restart in place. */
if (STARTS_WITH(command, "restart")) {
LOG("restarting \"%s\"...\n", start_argv[0]);
/* make sure -a is in the argument list or append it */
start_argv = append_argument(start_argv, "-a");
execvp(start_argv[0], start_argv);
/* not reached */
}
if (STARTS_WITH(command, "kill")) {
if (CUR_CELL->currently_focused == NULL) {
if (last_focused == NULL) {
LOG("There is no window to kill\n");
return;
}
LOG("Killing current window\n");
kill_window(conn, CUR_CELL->currently_focused);
client_kill(conn, last_focused);
return;
}
/* Is it a jump to a specified workspace, row, col? */
if (STARTS_WITH(command, "jump ")) {
const char *arguments = command + strlen("jump ");
if (arguments[0] == '"')
jump_to_window(conn, arguments);
else jump_to_container(conn, arguments);
return;
}
/* Should we travel the focus stack? */
if (STARTS_WITH(command, "focus")) {
const char *arguments = command + strlen("focus ");
travel_focus_stack(conn, arguments);
return;
}
/* Is it 'f' for fullscreen? */
if (command[0] == 'f') {
if (CUR_CELL->currently_focused == NULL)
if (last_focused == NULL)
return;
toggle_fullscreen(conn, CUR_CELL->currently_focused);
client_toggle_fullscreen(conn, last_focused);
return;
}
/* Is it just 's' for stacking or 'd' for default? */
if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
if (last_focused == NULL || client_is_floating(last_focused)) {
LOG("not switching, this is a floating client\n");
return;
}
LOG("Switching mode for current container\n");
switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
return;
}
enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW;
if (command[0] == 'H') {
LOG("Hiding all floating windows\n");
floating_toggle_hide(conn, c_ws);
return;
}
enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW;
/* Is it a <with>? */
if (command[0] == 'w') {
@ -651,13 +946,44 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (command[0] == 'c') {
with = WITH_CONTAINER;
command++;
} else if (command[0] == 'w') {
with = WITH_WORKSPACE;
command++;
} else {
LOG("not yet implemented.\n");
return;
}
}
/* It's a normal <cmd> */
/* Is it 't' for toggle tiling/floating? */
if (command[0] == 't') {
if (with == WITH_WORKSPACE) {
c_ws->auto_float = !c_ws->auto_float;
LOG("autofloat is now %d\n", c_ws->auto_float);
return;
}
if (last_focused == NULL) {
LOG("Cannot toggle tiling/floating: workspace empty\n");
return;
}
toggle_floating_mode(conn, last_focused, false);
/* delete all empty columns/rows */
cleanup_table(conn, last_focused->workspace);
/* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, last_focused->workspace);
render_workspace(conn, last_focused->workspace->screen, last_focused->workspace);
/* Re-focus the client because cleanup_table sets the focus to the last
* focused client inside a container only. */
set_focus(conn, last_focused, true);
return;
}
/* Its a normal <cmd> */
char *rest = NULL;
enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
direction_t direction;
@ -668,7 +994,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
if (*rest == '\0') {
/* No rest? This was a tag number, not a times specification */
/* No rest? This was a workspace number, not a times specification */
show_workspace(conn, times);
return;
}
@ -686,7 +1012,20 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
if (*rest == '\0') {
move_current_window_to_workspace(conn, workspace);
if (last_focused != NULL && client_is_floating(last_focused))
move_floating_window_to_workspace(conn, last_focused, workspace);
else move_current_window_to_workspace(conn, workspace);
return;
}
if (last_focused == NULL) {
LOG("Not performing (no window found)\n");
return;
}
if (client_is_floating(last_focused) &&
(action != ACTION_FOCUS && action != ACTION_MOVE)) {
LOG("Not performing (floating)\n");
return;
}
@ -704,18 +1043,32 @@ void parse_command(xcb_connection_t *conn, const char *command) {
LOG("unknown direction: %c\n", *rest);
return;
}
rest++;
if (action == ACTION_FOCUS)
if (action == ACTION_FOCUS) {
if (client_is_floating(last_focused)) {
floating_focus_direction(conn, last_focused, direction);
continue;
}
focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER));
else if (action == ACTION_MOVE) {
continue;
}
if (action == ACTION_MOVE) {
if (client_is_floating(last_focused)) {
floating_move(conn, last_focused, direction);
continue;
}
if (with == WITH_WINDOW)
move_current_window(conn, direction);
else move_current_container(conn, direction);
continue;
}
else if (action == ACTION_SNAP)
snap_current_container(conn, direction);
rest++;
if (action == ACTION_SNAP) {
snap_current_container(conn, direction);
continue;
}
}
LOG("--- done ---\n");

View File

@ -17,6 +17,7 @@
#include "i3.h"
#include "util.h"
#include "config.h"
#include "xcb.h"
Config config;
@ -25,14 +26,36 @@ Config config;
*
*/
static char *glob_path(const char *path) {
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
die("glob() failed");
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
globfree(&globbuf);
return result;
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
die("glob() failed");
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
globfree(&globbuf);
return result;
}
/*
* This function does a very simple replacement of each instance of key with value.
*
*/
static void replace_variable(char *buffer, const char *key, const char *value) {
char *pos;
/* To prevent endless recursions when the user makes an error configuring,
* we stop after 100 replacements. That should be vastly more than enough. */
int c = 0;
LOG("Replacing %s with %s\n", key, value);
while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
LOG("replacing variable %s in \"%s\" with \"%s\"\n", key, buffer, value);
char *rest = pos + strlen(key);
*pos = '\0';
char *replaced;
asprintf(&replaced, "%s%s%s", buffer, value, rest);
/* Hm, this is a bit ugly, but sizeof(buffer) = 4, as its just a pointer.
* So we need to hard-code the dimensions here. */
strncpy(buffer, replaced, 1026);
free(replaced);
}
}
/*
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
@ -41,7 +64,9 @@ static char *glob_path(const char *path) {
* configuration file.
*
*/
void load_configuration(const char *override_configpath) {
void load_configuration(xcb_connection_t *conn, const char *override_configpath) {
SLIST_HEAD(variables_head, Variable) variables;
#define OPTION_STRING(name) \
if (strcasecmp(key, #name) == 0) { \
config.name = sstrdup(value); \
@ -52,9 +77,51 @@ void load_configuration(const char *override_configpath) {
if (config.name == NULL) \
die("You did not specify required configuration option " #name "\n");
#define OPTION_COLORTRIPLE(opt, name) \
if (strcasecmp(key, opt) == 0) { \
char border[8], background[8], text[8]; \
memset(border, 0, sizeof(border)); \
memset(background, 0, sizeof(background)); \
memset(text, 0, sizeof(text)); \
border[0] = background[0] = text[0] = '#'; \
if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \
border + 1, background + 1, text + 1) != 3 || \
strlen(border) != 7 || \
strlen(background) != 7 || \
strlen(text) != 7) \
die("invalid color code line: %s\n", value); \
config.name.border = get_colorpixel(conn, border); \
config.name.background = get_colorpixel(conn, background); \
config.name.text = get_colorpixel(conn, text); \
continue; \
}
/* Clear the old config or initialize the data structure */
memset(&config, 0, sizeof(config));
SLIST_INIT(&variables);
/* Initialize default colors */
config.client.focused.border = get_colorpixel(conn, "#4c7899");
config.client.focused.background = get_colorpixel(conn, "#285577");
config.client.focused.text = get_colorpixel(conn, "#ffffff");
config.client.focused_inactive.border = get_colorpixel(conn, "#4c7899");
config.client.focused_inactive.background = get_colorpixel(conn, "#555555");
config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
config.client.unfocused.border = get_colorpixel(conn, "#333333");
config.client.unfocused.background = get_colorpixel(conn, "#222222");
config.client.unfocused.text = get_colorpixel(conn, "#888888");
config.bar.focused.border = get_colorpixel(conn, "#4c7899");
config.bar.focused.background = get_colorpixel(conn, "#285577");
config.bar.focused.text = get_colorpixel(conn, "#ffffff");
config.bar.unfocused.border = get_colorpixel(conn, "#333333");
config.bar.unfocused.background = get_colorpixel(conn, "#222222");
config.bar.unfocused.text = get_colorpixel(conn, "#888888");
FILE *handle;
if (override_configpath != NULL) {
if ((handle = fopen(override_configpath, "r")) == NULL)
@ -77,6 +144,14 @@ void load_configuration(const char *override_configpath) {
die("Could not read configuration file\n");
}
if (config.terminal != NULL)
replace_variable(buffer, "$terminal", config.terminal);
/* Replace all custom variables */
struct Variable *current;
SLIST_FOREACH(current, &variables, variables)
replace_variable(buffer, current->key, current->value);
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
key[0] == '#' || strlen(key) < 3)
@ -85,6 +160,22 @@ void load_configuration(const char *override_configpath) {
OPTION_STRING(terminal);
OPTION_STRING(font);
/* Colors */
OPTION_COLORTRIPLE("client.focused", client.focused);
OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
OPTION_COLORTRIPLE("bar.focused", bar.focused);
OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
/* exec-lines (autostart) */
if (strcasecmp(key, "exec") == 0) {
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup(value);
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
continue;
}
/* key bindings */
if (strcasecmp(key, "bind") == 0) {
#define CHECK_MODIFIER(name) \
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
@ -124,13 +215,105 @@ void load_configuration(const char *override_configpath) {
continue;
}
fprintf(stderr, "Unknown configfile option: %s\n", key);
exit(1);
if (strcasecmp(key, "floating_modifier") == 0) {
char *walk = value;
uint32_t modifiers = 0;
while (*walk != '\0') {
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
CHECK_MODIFIER(SHIFT);
CHECK_MODIFIER(CONTROL);
CHECK_MODIFIER(MODE_SWITCH);
CHECK_MODIFIER(MOD1);
CHECK_MODIFIER(MOD2);
CHECK_MODIFIER(MOD3);
CHECK_MODIFIER(MOD4);
CHECK_MODIFIER(MOD5);
/* No modifier found? Then were done with this step */
break;
}
LOG("Floating modifiers = %d\n", modifiers);
config.floating_modifier = modifiers;
continue;
}
/* assign window class[/window title] → workspace */
if (strcasecmp(key, "assign") == 0) {
LOG("assign: \"%s\"\n", value);
char *class_title = sstrdup(value);
char *target;
/* If the window class/title is quoted we skip quotes */
if (class_title[0] == '"') {
class_title++;
char *end = strchr(class_title, '"');
if (end == NULL)
die("Malformed assignment, couldn't find terminating quote\n");
*end = '\0';
} else {
/* If it is not quoted, we terminate it at the first space */
char *end = strchr(class_title, ' ');
if (end == NULL)
die("Malformed assignment, couldn't find terminating space\n");
*end = '\0';
}
/* The target is the last argument separated by a space */
if ((target = strrchr(value, ' ')) == NULL)
die("Malformed assignment, couldn't find target\n");
target++;
if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10))
die("Malformed assignment, invalid workspace number\n");
LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target);
struct Assignment *new = scalloc(sizeof(struct Assignment));
new->windowclass_title = class_title;
if (*target == '~')
new->floating = true;
else new->workspace = atoi(target);
TAILQ_INSERT_TAIL(&assignments, new, assignments);
continue;
}
/* set a custom variable */
if (strcasecmp(key, "set") == 0) {
if (value[0] != '$')
die("Malformed variable assignment, name has to start with $\n");
/* get key/value for this variable */
char *v_key = value, *v_value;
if ((v_value = strstr(value, " ")) == NULL)
die("Malformed variable assignment, need a value\n");
*(v_value++) = '\0';
struct Variable *new = scalloc(sizeof(struct Variable));
new->key = sstrdup(v_key);
new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables);
LOG("Got new variable %s = %s\n", v_key, v_value);
continue;
}
die("Unknown configfile option: %s\n", key);
}
fclose(handle);
REQUIRED_OPTION(terminal);
REQUIRED_OPTION(font);
while (!SLIST_EMPTY(&variables)) {
struct Variable *v = SLIST_FIRST(&variables);
SLIST_REMOVE_HEAD(&variables, variables);
free(v->key);
free(v->value);
free(v);
}
return;
}

433
src/floating.c Normal file
View File

@ -0,0 +1,433 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/floating.c: contains all functions for handling floating clients
*
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include "i3.h"
#include "config.h"
#include "data.h"
#include "util.h"
#include "xcb.h"
#include "debug.h"
#include "layout.h"
#include "client.h"
#include "floating.h"
/*
* Toggles floating mode for the given client.
* Correctly takes care of the position/size (separately stored for tiling/floating mode)
* and repositions/resizes/redecorates the client.
*
* If the automatic flag is set to true, this was an automatic update by a change of the
* window class from the application which can be overwritten by the user.
*
*/
void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) {
Container *con = client->container;
i3Font *font = load_font(conn, config.font);
if (con == NULL) {
LOG("This client is already in floating (container == NULL), re-inserting\n");
Client *next_tiling;
SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients)
if (!client_is_floating(next_tiling))
break;
/* If there are no tiling clients on this workspace, there can only be one
* container: the first one */
if (next_tiling == TAILQ_END(&(client->workspace->focus_stack)))
con = client->workspace->table[0][0];
else con = next_tiling->container;
/* Remove the client from the list of floating clients */
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
LOG("destination container = %p\n", con);
Client *old_focused = con->currently_focused;
/* Preserve position/size */
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
client->floating = FLOATING_USER_OFF;
client->container = con;
if (old_focused != NULL && !old_focused->dock)
CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
LOG("Re-inserted the client into the matrix.\n");
con->currently_focused = client;
client_set_below_floating(conn, client);
render_container(conn, con);
xcb_flush(conn);
return;
}
LOG("Entering floating for client %08x\n", client->child);
/* Remove the client of its container */
client_remove_from_container(conn, client, con, false);
client->container = NULL;
/* Add the client to the list of floating clients for its workspace */
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
if (con->currently_focused == client) {
LOG("Need to re-adjust currently_focused\n");
/* Get the next client in the focus stack for this particular container */
con->currently_focused = get_last_focused_client(conn, con, NULL);
}
if (automatic)
client->floating = FLOATING_AUTO_ON;
else client->floating = FLOATING_USER_ON;
/* Initialize the floating position from the position in tiling mode, if this
* client never was floating (x == -1) */
if (client->floating_rect.x == -1) {
/* Copy over the position */
client->floating_rect.x = client->rect.x;
client->floating_rect.y = client->rect.y;
/* Copy size the other direction */
client->child_rect.width = client->floating_rect.width;
client->child_rect.height = client->floating_rect.height;
client->rect.width = client->child_rect.width + 2 + 2;
client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
client->floating_rect.width, client->floating_rect.height);
} else {
/* If the client was already in floating before we restore the old position / size */
LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
client->floating_rect.width, client->floating_rect.height);
memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
}
/* Raise the client */
xcb_raise_window(conn, client->frame);
reposition_client(conn, client);
resize_client(conn, client);
/* redecorate_window flushes */
redecorate_window(conn, client);
/* Re-render the tiling layout of this container */
render_container(conn, con);
xcb_flush(conn);
}
/*
* Removes the floating client from its workspace and attaches it to the new workspace.
* This is centralized here because it may happen if you move it via keyboard and
* if you move it using your mouse.
*
*/
void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
/* Remove from focus stack and list of floating clients */
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
if (client->workspace->fullscreen_client == client)
client->workspace->fullscreen_client = NULL;
/* Insert into destination focus stack and list of floating clients */
client->workspace = new_workspace;
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
if (client->fullscreen)
client->workspace->fullscreen_client = client;
}
/*
* Called whenever the user clicks on a border (not the titlebar!) of a floating window.
* Determines on which border the user clicked and launches the drag_pointer function
* with the resize_callback.
*
*/
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating border click\n");
border_t border;
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
switch (border) {
case BORDER_RIGHT: {
int new_width = old_rect->width + (new_x - event->root_x);
if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width))
return;
client->rect.width = new_width;
break;
}
case BORDER_BOTTOM: {
int new_height = old_rect->height + (new_y - event->root_y);
if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height))
return;
client->rect.height = old_rect->height + (new_y - event->root_y);
break;
}
case BORDER_TOP: {
int new_height = old_rect->height + (event->root_y - new_y);
if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height))
return;
client->rect.y = old_rect->y + (new_y - event->root_y);
client->rect.height = new_height;
break;
}
case BORDER_LEFT: {
int new_width = old_rect->width + (event->root_x - new_x);
if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width))
return;
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.width = new_width;
break;
}
}
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
}
if (event->event_y < 2)
border = BORDER_TOP;
else if (event->event_y >= (client->rect.height - 2))
border = BORDER_BOTTOM;
else if (event->event_x <= 2)
border = BORDER_LEFT;
else if (event->event_x >= (client->rect.width - 2))
border = BORDER_RIGHT;
else {
LOG("Not on any border, not doing anything.\n");
return 1;
}
LOG("border = %d\n", border);
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback);
return 1;
}
/*
* Called when the user clicked on the titlebar of a floating window.
* Calls the drag_pointer function with the drag_window callback
*
*/
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating_drag_window\n");
void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
/* Reposition the client correctly while moving */
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.y = old_rect->y + (new_y - event->root_y);
reposition_client(conn, client);
/* Because reposition_client does not send a faked configure event (only resize does),
* we need to initiate that on our own */
fake_absolute_configure_notify(conn, client);
/* fake_absolute_configure_notify flushes */
}
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
}
/*
* This function grabs your pointer and lets you drag stuff around (borders).
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
* and the given callback will be called with the parameters specified (client,
* border on which the click originally was), the original rect of the client,
* the event and the new coordinates (x, y).
*
*/
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
uint32_t new_x, new_y;
Rect old_rect;
if (client != NULL)
memcpy(&old_rect, &(client->rect), sizeof(Rect));
/* Grab the pointer */
/* TODO: returncode */
xcb_grab_pointer(conn,
false, /* get all pointer events specified by the following mask */
root, /* grab the root window */
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
XCB_GRAB_MODE_ASYNC, /* keyboard mode */
confine_to, /* confine_to = in which window should the cursor stay */
XCB_NONE, /* dont display a special cursor */
XCB_CURRENT_TIME);
/* Go into our own event loop */
xcb_flush(conn);
xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
/* Ive always wanted to have my own eventhandler… */
while ((inside_event = xcb_wait_for_event(conn))) {
/* We now handle all events we can get using xcb_poll_for_event */
do {
/* Same as get_event_handler in xcb */
int nr = inside_event->response_type;
if (nr == 0) {
/* An error occured */
handle_event(NULL, conn, inside_event);
free(inside_event);
continue;
}
assert(nr < 256);
nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
assert(nr >= 2);
switch (nr) {
case XCB_BUTTON_RELEASE:
goto done;
case XCB_MOTION_NOTIFY:
/* motion_notify events are saved for later */
FREE(last_motion_notify);
last_motion_notify = inside_event;
break;
default:
LOG("Passing to original handler\n");
/* Use original handler */
xcb_event_handle(&evenths, inside_event);
break;
}
if (last_motion_notify != inside_event)
free(inside_event);
} while ((inside_event = xcb_poll_for_event(conn)) != NULL);
if (last_motion_notify == NULL)
continue;
new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
callback(&old_rect, new_x, new_y);
FREE(last_motion_notify);
}
done:
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
xcb_flush(conn);
}
/*
* Changes focus in the given direction for floating clients.
*
* Changing to the left/right means going to the previous/next floating client,
* changing to top/bottom means cycling through the Z-index.
*
*/
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
LOG("floating focus\n");
if (direction == D_LEFT || direction == D_RIGHT) {
/* Go to the next/previous floating client */
Client *client;
while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) :
TAILQ_NEXT(currently_focused, floating_clients))) !=
TAILQ_END(&(currently_focused->workspace->floating_clients))) {
if (!client->floating)
continue;
set_focus(conn, client, true);
return;
}
}
}
/*
* Moves the client 10px to the specified direction.
*
*/
void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
LOG("floating move\n");
switch (direction) {
case D_LEFT:
if (currently_focused->rect.x < 10)
return;
currently_focused->rect.x -= 10;
break;
case D_RIGHT:
currently_focused->rect.x += 10;
break;
case D_UP:
if (currently_focused->rect.y < 10)
return;
currently_focused->rect.y -= 10;
break;
case D_DOWN:
currently_focused->rect.y += 10;
break;
/* to make static analyzers happy */
default:
break;
}
reposition_client(conn, currently_focused);
/* Because reposition_client does not send a faked configure event (only resize does),
* we need to initiate that on our own */
fake_absolute_configure_notify(conn, currently_focused);
/* fake_absolute_configure_notify flushes */
}
/*
* Hides all floating clients (or show them if they are currently hidden) on
* the specified workspace.
*
*/
void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
Client *client;
workspace->floating_hidden = !workspace->floating_hidden;
LOG("floating_hidden is now: %d\n", workspace->floating_hidden);
TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
if (workspace->floating_hidden)
xcb_unmap_window(conn, client->frame);
else xcb_map_window(conn, client->frame);
}
/* If we just unmapped all floating windows we should ensure that the focus
* is set correctly, that ist, to the first non-floating client in stack */
if (workspace->floating_hidden)
SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) {
if (client_is_floating(client))
continue;
set_focus(conn, client, true);
return;
}
xcb_flush(conn);
}

View File

@ -32,6 +32,9 @@
#include "config.h"
#include "queue.h"
#include "resize.h"
#include "client.h"
#include "manage.h"
#include "floating.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when
@ -149,7 +152,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
return 1;
}
/* Some events are not interesting, because they were not generated actively by the
user, but be reconfiguration of windows */
user, but by reconfiguration of windows */
if (event_is_ignored(event->sequence))
return 1;
@ -196,6 +199,14 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
return 1;
}
if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) {
/* This can happen when a client gets assigned to a different workspace than
* the current one (see src/mainx.c:reparent_window). Shortly after it was created,
* an enter_notify will follow. */
LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
return 1;
}
set_focus(conn, client, false);
return 1;
@ -283,6 +294,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
LOG("button press!\n");
LOG("state = %d\n", event->state);
/* This was either a focus for a clients parent (= titlebar)… */
Client *client = table_get(&by_child, event->event);
bool border_click = false;
@ -290,6 +302,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
client = table_get(&by_parent, event->event);
border_click = true;
}
/* See if this was a click with the configured modifier. If so, we need
* to move around the client if it was floating. if not, we just process
* as usual. */
if (config.floating_modifier != 0 &&
(event->state & config.floating_modifier) != 0) {
if (client == NULL) {
LOG("Not handling, Mod1 was pressed and no client found\n");
return 1;
}
if (client_is_floating(client)) {
floating_drag_window(conn, client, event);
return 1;
}
}
if (client == NULL) {
/* The client was neither on a clients titlebar nor on a client itself, maybe on a stack_window? */
if (button_press_stackwin(conn, event))
@ -312,7 +339,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
Container *con = client->container;
int first, second;
if (con == NULL) {
if (client->dock) {
LOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
@ -324,6 +351,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
if (!border_click) {
LOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
/* Floating clients should be raised on click */
if (client_is_floating(client))
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
return 1;
}
@ -332,9 +362,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
i3Font *font = load_font(conn, config.font);
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
LOG("click on titlebar\n");
/* Floating clients can be dragged by grabbing their titlebar */
if (client_is_floating(client)) {
/* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
floating_drag_window(conn, client, event);
}
return 1;
}
if (client_is_floating(client))
return floating_border_click(conn, client, event);
if (event->event_y < 2) {
/* This was a press on the top border */
if (con->row == 0)
@ -412,8 +454,61 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
Client *client = table_get(&by_child, event->window);
if (client == NULL) {
LOG("This client is not mapped, so we don't care and just tell the client that he will get its size\n");
Rect rect = {event->x, event->y, event->width, event->height};
fake_configure_notify(conn, rect, event->window);
uint32_t mask = 0;
uint32_t values[7];
int c = 0;
#define COPY_MASK_MEMBER(mask_member, event_member) do { \
if (event->value_mask & mask_member) { \
mask |= mask_member; \
values[c++] = event->event_member; \
} \
} while (0)
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
xcb_configure_window(conn, event->window, mask, values);
xcb_flush(conn);
return 1;
}
/* Floating clients can be reconfigured */
if (client_is_floating(client)) {
i3Font *font = load_font(conn, config.font);
if (event->value_mask & XCB_CONFIG_WINDOW_X)
client->rect.x = event->x;
if (event->value_mask & XCB_CONFIG_WINDOW_Y)
client->rect.y = event->y;
if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH)
client->rect.width = event->width + 2 + 2;
if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
client->rect.height = event->height + (font->height + 2 + 2) + 2;
LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
client->rect.x, client->rect.y, client->rect.width, client->rect.height);
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
return 1;
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
Rect child_rect = client->container->workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
return 1;
}
@ -495,18 +590,16 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
client = table_remove(&by_child, event->window);
if (client->name != NULL)
free(client->name);
/* If this was the fullscreen client, we need to unset it */
if (client->fullscreen)
client->workspace->fullscreen_client = NULL;
/* Clients without a container are either floating or dock windows */
if (client->container != NULL) {
Container *con = client->container;
/* If this was the fullscreen client, we need to unset it */
if (client->fullscreen)
con->workspace->fullscreen_client = NULL;
/* Remove the client from the list of clients */
remove_client_from_container(conn, client, con);
client_remove_from_container(conn, client, con, true);
/* Set focus to the last focused client in this container */
con->currently_focused = get_last_focused_client(conn, con, NULL);
@ -514,6 +607,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
/* Only if this is the active container, we need to really change focus */
if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
set_focus(conn, con->currently_focused, true);
} else if (client_is_floating(client)) {
LOG("Removing from floating clients\n");
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
}
if (client->dock) {
@ -528,32 +625,41 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
table_remove(&by_parent, client->frame);
if (client->container != NULL) {
cleanup_table(conn, client->container->workspace);
fix_colrowspan(conn, client->container->workspace);
Workspace *workspace = client->container->workspace;
cleanup_table(conn, workspace);
fix_colrowspan(conn, workspace);
}
/* Lets see how many clients there are left on the workspace to delete it if its empty */
bool workspace_empty = true;
FOR_TABLE(client->workspace)
if (!CIRCLEQ_EMPTY(&(client->workspace->table[cols][rows]->clients))) {
workspace_empty = false;
break;
}
bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack));
bool workspace_active = false;
Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL);
/* If this workspace is currently active, we dont delete it */
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->current_workspace == client->workspace->num) {
workspace_active = true;
workspace_empty = false;
break;
}
if (workspace_empty)
if (workspace_empty) {
LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num,
client->workspace);
client->workspace->screen = NULL;
}
FREE(client->window_class);
FREE(client->name);
free(client);
render_layout(conn);
/* Ensure the focus is set to the next client in the focus stack */
if (workspace_active && to_focus != NULL)
set_focus(conn, to_focus, true);
return 1;
}
@ -598,14 +704,13 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
client->name_len = new_len;
client->uses_net_wm_name = true;
if (old_name != NULL)
free(old_name);
FREE(old_name);
/* If the client is a dock window, we dont need to render anything */
if (client->dock)
return 1;
if (client->container->mode == MODE_STACK)
if (client->container != NULL && client->container->mode == MODE_STACK)
render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0);
xcb_flush(conn);
@ -673,7 +778,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
if (client->dock)
return 1;
if (client->container->mode == MODE_STACK)
if (client->container != NULL && client->container->mode == MODE_STACK)
render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0);
xcb_flush(conn);
@ -681,6 +786,46 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
return 1;
}
/*
* Updates the clients WM_CLASS property
*
*/
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
LOG("window class changed\n");
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
return 1;
}
Client *client = table_get(&by_child, window);
if (client == NULL)
return 1;
char *new_class;
if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
perror("Could not get window class");
LOG("Could not get window class\n");
return 1;
}
LOG("changed to %s\n", new_class);
char *old_class = client->window_class;
client->window_class = new_class;
FREE(old_class);
if (!client->initialized) {
LOG("Client is not yet initialized, not putting it to floating\n");
return 1;
}
if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) {
LOG("tool/dialog window, should we put it floating?\n");
if (client->floating == FLOATING_AUTO_OFF)
toggle_floating_mode(conn, client, true);
}
return 1;
}
/*
* Expose event means we should redraw our windows (= title bar)
*
@ -717,15 +862,15 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
return 1;
}
if (client->container->mode != MODE_STACK)
if (client->container == NULL || client->container->mode != MODE_STACK)
decorate_window(conn, client, client->frame, client->titlegc, 0);
else {
uint32_t background_color;
/* Distinguish if the window is currently focused… */
if (CUR_CELL->currently_focused == client)
background_color = get_colorpixel(conn, "#285577");
background_color = config.client.focused.background;
/* …or if it is the focused window in a not focused container */
else background_color = get_colorpixel(conn, "#555555");
else background_color = config.client.focused_inactive.background;
/* Set foreground color to current focused color, line width to 2 */
uint32_t values[] = {background_color, 2};
@ -771,7 +916,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
(!client->fullscreen &&
(event->data.data32[0] == _NET_WM_STATE_ADD ||
event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
toggle_fullscreen(conn, client);
client_toggle_fullscreen(conn, client);
} else {
LOG("unhandled clientmessage\n");
return 0;
@ -804,6 +949,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
return 1;
}
xcb_size_hints_t size_hints;
LOG("client is %08x / child %08x\n", client->frame, client->child);
/* If the hints were already in this event, use them, if not, request them */
if (reply != NULL)
@ -811,6 +957,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
else
xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL);
if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
LOG("min size set\n");
LOG("gots min_width = %d, min_height = %d\n", size_hints.min_width, size_hints.min_height);
}
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
(size_hints.min_aspect_num <= 0) ||
@ -821,8 +972,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
LOG("window is %08x / %s\n", client->child, client->name);
int base_width = 0, base_height = 0,
min_width = 0, min_height = 0;
int base_width = 0, base_height = 0;
/* base_width/height are the desired size of the window.
We check if either the program-specified size or the program-specified
@ -835,14 +985,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
base_height = size_hints.min_height;
}
if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
min_width = size_hints.min_width;
min_height = size_hints.min_height;
} else if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) {
min_width = size_hints.base_width;
min_height = size_hints.base_height;
}
double width = client->rect.width - base_width;
double height = client->rect.height - base_height;
/* Convert numerator/denominator to a double */
@ -874,3 +1016,42 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
return 1;
}
/*
* Handles the transient for hints set by a window, signalizing that this window is a popup window
* for some other window.
*
* See ICCCM 4.1.2.6 for more details
*
*/
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply) {
LOG("Transient hint!\n");
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("No such client\n");
return 1;
}
xcb_window_t transient_for;
if (reply != NULL) {
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) {
LOG("Not transient for any window\n");
return 1;
}
} else {
if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window),
&transient_for, NULL)) {
LOG("Not transient for any window\n");
return 1;
}
}
if (client->floating == FLOATING_AUTO_OFF) {
LOG("This is a popup window, putting into floating\n");
toggle_floating_mode(conn, client, true);
}
return 1;
}

View File

@ -24,6 +24,8 @@
#include "util.h"
#include "xinerama.h"
#include "layout.h"
#include "client.h"
#include "floating.h"
/*
* Updates *destination with new_value and returns true if it was changed or false
@ -83,7 +85,7 @@ int get_unoccupied_y(Workspace *workspace, int col) {
*
*/
void redecorate_window(xcb_connection_t *conn, Client *client) {
if (client->container->mode == MODE_STACK) {
if (client->container != NULL && client->container->mode == MODE_STACK) {
render_container(conn, client->container);
/* We clear the frame to generate exposure events, because the color used
in drawing may be different */
@ -100,47 +102,39 @@ void redecorate_window(xcb_connection_t *conn, Client *client) {
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);
int decoration_height = font->height + 2 + 2;
uint32_t background_color,
text_color,
border_color;
struct Colortriple *color;
/* Clients without a container (docks) wont get decorated */
if (client->container == NULL)
if (client->dock)
return;
if (client->container->currently_focused == client) {
LOG("redecorating child %08x\n", client->child);
if (client_is_floating(client) || client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
if (CUR_CELL->currently_focused == client)
background_color = get_colorpixel(conn, "#285577");
if (client_is_floating(client) || CUR_CELL->currently_focused == client)
color = &(config.client.focused);
/* …or if it is the focused window in a not focused container */
else background_color = get_colorpixel(conn, "#555555");
text_color = get_colorpixel(conn, "#ffffff");
border_color = get_colorpixel(conn, "#4c7899");
} else {
background_color = get_colorpixel(conn, "#222222");
text_color = get_colorpixel(conn, "#888888");
border_color = get_colorpixel(conn, "#333333");
}
else color = &(config.client.focused_inactive);
} else color = &(config.client.unfocused);
/* Our plan is the following:
- Draw a rect around the whole client in background_color
- Draw a rect around the whole client in color->background
- 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_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
/* In stacking mode, we only render the rect for this specific decoration */
if (client->container->mode == MODE_STACK) {
xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
} else {
if (client->container != NULL && client->container->mode == MODE_STACK) {
/* 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 = {0, 0, client->container->width, client->rect.height};
xcb_rectangle_t rect = {0, offset, client->container->width, offset + 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)
@ -152,15 +146,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
}
/* 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);
xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3,
client->rect.width - 3, offset + font->height + 3);
/* If the client has a title, we draw it */
if (client->name != NULL) {
/* Draw the font */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
uint32_t values[] = { text_color, background_color, font->id };
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,
@ -179,18 +173,37 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
* Pushes the clients x and y coordinates to X11
*
*/
static void reposition_client(xcb_connection_t *conn, Client *client) {
void reposition_client(xcb_connection_t *conn, Client *client) {
i3Screen *screen;
LOG("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 */
if (client->workspace->screen == (screen = get_screen_containing(client->rect.x, client->rect.y)))
return;
if (screen == NULL) {
LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y);
return;
}
LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen);
LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen);
floating_assign_to_workspace(client, &workspaces[screen->current_workspace]);
LOG("fixed that\n");
}
/*
* Pushes the clients width/height to X11 and resizes the child window
*
*/
static void resize_client(xcb_connection_t *conn, Client *client) {
void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font);
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
@ -335,8 +348,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE;
/* If there is no fullscreen client, we raise the stack window */
if (container->workspace->fullscreen_client != NULL) {
/* 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;
}
@ -344,9 +362,6 @@ void render_container(xcb_connection_t *conn, Container *container) {
xcb_configure_window(conn, stack_win->window, mask, values);
}
/* Reconfigure the currently focused client, if necessary. It is the only visible one */
client = container->currently_focused;
/* Render the decorations of all clients */
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
@ -400,24 +415,10 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
i3Font *font = load_font(conn, config.font);
i3Screen *screen = r_ws->screen;
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
uint32_t background_color[2],
text_color[2],
border_color[2],
black;
char label[3];
black = get_colorpixel(conn, "#000000");
background_color[SET_NORMAL] = get_colorpixel(conn, "#222222");
text_color[SET_NORMAL] = get_colorpixel(conn, "#888888");
border_color[SET_NORMAL] = get_colorpixel(conn, "#333333");
background_color[SET_FOCUSED] = get_colorpixel(conn, "#285577");
text_color[SET_FOCUSED] = get_colorpixel(conn, "#ffffff");
border_color[SET_FOCUSED] = get_colorpixel(conn, "#4c7899");
/* Fill the whole bar in black */
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_rectangle_t rect = {0, 0, width, height};
xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
@ -429,16 +430,17 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
if (workspaces[c].screen != screen)
continue;
int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) :
&(config.bar.unfocused));
xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set],
xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
drawn * height, 1, height - 2, height - 2);
xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set],
xcb_draw_rect(conn, screen->bar, screen->bargc, color->background,
drawn * height + 1, 2, height - 4, height - 4);
snprintf(label, sizeof(label), "%d", c+1);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background);
xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn * height + 5 /* X */,
font->height + 1 /* Y = baseline of font */, label);
drawn++;

View File

@ -31,6 +31,8 @@
#include <xcb/xcb_icccm.h>
#include <xcb/xinerama.h>
#include <ev.h>
#include "config.h"
#include "data.h"
#include "debug.h"
@ -42,6 +44,7 @@
#include "util.h"
#include "xcb.h"
#include "xinerama.h"
#include "manage.h"
/* This is the path to i3, copied from argv[0] when starting up */
char **start_argv;
@ -52,6 +55,12 @@ Display *xkbdpy;
/* The list of key bindings */
struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(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);
@ -63,273 +72,40 @@ xcb_atom_t atoms[NUM_ATOMS];
int num_screens = 0;
/*
* Do some sanity checks and then reparent the window.
* 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
*
*/
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
LOG("managing window.\n");
xcb_drawable_t d = { window };
xcb_get_geometry_cookie_t geomc;
xcb_get_geometry_reply_t *geom;
xcb_get_window_attributes_reply_t *attr = 0;
if (wa.tag == TAG_COOKIE) {
/* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
goto out;
wa.tag = TAG_VALUE;
wa.u.override_redirect = attr->override_redirect;
}
/* Dont manage clients with the override_redirect flag */
if (wa.u.override_redirect)
goto out;
/* Check if the window is already managed */
if (table_get(&by_child, window))
goto out;
/* Get the initial geometry (position, size, …) */
geomc = xcb_get_geometry(conn, d);
if (!attr) {
wa.tag = TAG_COOKIE;
wa.u.cookie = xcb_get_window_attributes(conn, window);
attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
}
geom = xcb_get_geometry_reply(conn, geomc, 0);
if (attr && geom) {
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
geom->x, geom->y, geom->width, geom->height);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
}
free(geom);
out:
free(attr);
return;
static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
/* empty, because xcb_prepare_cb and xcb_check_cb are used */
}
/*
* reparent_window() gets called when a new window was opened and becomes a child of the root
* window, or it gets called by us when we manage the already existing windows at startup.
*
* Essentially, this is the point where we take over control.
* Flush before blocking (and waiting for new events)
*
*/
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
int16_t x, int16_t y, uint16_t width, uint16_t height) {
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
uint32_t mask = 0;
uint32_t values[3];
/* We are interested in property changes */
mask = XCB_CW_EVENT_MASK;
values[0] = CHILD_EVENT_MASK;
xcb_change_window_attributes(conn, child, mask, values);
/* Map the window first to avoid flickering */
xcb_map_window(conn, child);
/* Place requests for properties ASAP */
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
Client *new = table_get(&by_child, child);
/* Events for already managed windows should already be filtered in manage_window() */
assert(new == NULL);
LOG("reparenting new client\n");
new = calloc(sizeof(Client), 1);
new->force_reconfigure = true;
/* Update the data structures */
Client *old_focused = CUR_CELL->currently_focused;
new->container = CUR_CELL;
new->workspace = new->container->workspace;
new->frame = xcb_generate_id(conn);
new->child = child;
new->rect.width = width;
new->rect.height = height;
mask = 0;
/* Dont generate events for our new window, it should *not* be managed */
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[0] = 1;
/* We want to know when… */
mask |= XCB_CW_EVENT_MASK;
values[1] = FRAME_EVENT_MASK;
LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
i3Font *font = load_font(conn, config.font);
width = min(width, c_ws->rect.x + c_ws->rect.width);
height = min(height, c_ws->rect.y + c_ws->rect.height);
Rect framerect = {x, y,
width + 2 + 2, /* 2 px border at each side */
height + 2 + 2 + font->height}; /* 2 px border plus fonts height */
/* Yo dawg, I heard you like windows, so I create a window around your window… */
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
/* Put the client inside the save set. Upon termination (whether killed or normal exit
does not matter) of the window manager, these clients will be correctly reparented
to their most closest living ancestor (= cleanup) */
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
/* Generate a graphics context for the titlebar */
new->titlegc = xcb_generate_id(conn);
xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
/* Moves the original window into the new frame we've created for it */
new->awaiting_useless_unmap = true;
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
if (xcb_request_check(conn, cookie) != NULL) {
LOG("Could not reparent the window, aborting\n");
xcb_destroy_window(conn, new->frame);
free(new);
return;
}
/* Put our data structure (Client) into the table */
table_put(&by_parent, new->frame, new);
table_put(&by_child, child, new);
/* We need to grab the mouse buttons for click to focus */
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
1 /* left mouse button */,
XCB_BUTTON_MASK_ANY /* dont filter for any modifiers */);
/* Get _NET_WM_WINDOW_TYPE (to see if its a dock) */
xcb_atom_t *atom;
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
LOG("Window is a dock.\n");
new->dock = true;
new->titlebar_position = TITLEBAR_OFF;
new->force_reconfigure = true;
new->container = NULL;
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
}
}
if (new->dock) {
/* Get _NET_WM_STRUT_PARTIAL to determine the clients requested height */
uint32_t *strut;
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
/* We only use a subset of the provided values, namely the reserved space at the top/bottom
of the screen. This is because the only possibility for bars is at to be at the top/bottom
with maximum horizontal size.
TODO: bars at the top */
new->desired_height = strut[3];
if (new->desired_height == 0) {
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
new->desired_height = height;
}
LOG("the client wants to be %d pixels high\n", new->desired_height);
} else {
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
new->desired_height = height;
}
}
/* Focus the new window if were not in fullscreen mode and if it is not a dock window */
if (CUR_CELL->workspace->fullscreen_client == NULL) {
if (!new->dock) {
CUR_CELL->currently_focused = new;
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
}
} else {
/* If we are in fullscreen, we should lower the window to not be annoying */
uint32_t values[] = { XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
}
/* Insert into the currently active container, if its not a dock window */
if (!new->dock) {
/* Insert after the old active client, if existing. If it does not exist, the
container is empty and it does not matter, where we insert it */
if (old_focused != NULL && !old_focused->dock)
CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
}
/* Check if the window already got the fullscreen hint set */
xcb_atom_t *state;
if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
(state = xcb_get_property_value(preply)) != NULL)
/* Check all set _NET_WM_STATEs */
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
/* If the window got the fullscreen state, we just toggle fullscreen
and dont event bother to redraw the layout that would not change
anything anyways */
toggle_fullscreen(conn, new);
return;
}
render_layout(conn);
static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
xcb_flush(evenths.c);
}
/*
* Go through all existing windows (if the window manager is restarted) and manage them
* Instead of polling the X connection socket we leave this to
* xcb_poll_for_event() which knows better than we can ever know.
*
*/
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
xcb_query_tree_reply_t *reply;
int i, len;
xcb_window_t *children;
xcb_get_window_attributes_cookie_t *cookies;
static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
xcb_generic_event_t *event;
/* Get the tree of windows whose parent is the root window (= all) */
if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
return;
len = xcb_query_tree_children_length(reply);
cookies = smalloc(len * sizeof(*cookies));
/* Request the window attributes for every window */
children = xcb_query_tree_children(reply);
for(i = 0; i < len; ++i)
cookies[i] = xcb_get_window_attributes(conn, children[i]);
/* Call manage_window with the attributes for every window */
for(i = 0; i < len; ++i) {
window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
manage_window(prophs, conn, children[i], wa);
while ((event = xcb_poll_for_event(evenths.c)) != NULL) {
xcb_event_handle(&evenths, event);
free(event);
}
free(reply);
free(cookies);
}
int main(int argc, char *argv[], char *env[]) {
int i, screens, opt;
char *override_configpath = NULL;
bool autostart = true;
xcb_connection_t *conn;
xcb_property_handlers_t prophs;
xcb_window_t root;
@ -343,8 +119,12 @@ int main(int argc, char *argv[], char *env[]) {
start_argv = argv;
while ((opt = getopt(argc, argv, "c:v")) != -1) {
while ((opt = getopt(argc, argv, "c:va")) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
autostart = false;
break;
case 'c':
override_configpath = sstrdup(optarg);
break;
@ -365,10 +145,13 @@ int main(int argc, char *argv[], char *env[]) {
memset(&evenths, 0, sizeof(xcb_event_handlers_t));
memset(&prophs, 0, sizeof(xcb_property_handlers_t));
load_configuration(override_configpath);
conn = xcb_connect(NULL, &screens);
if (xcb_connection_has_error(conn))
die("Cannot open display\n");
load_configuration(conn, override_configpath);
/* 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);
@ -380,6 +163,10 @@ int main(int argc, char *argv[], char *env[]) {
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);
@ -406,6 +193,27 @@ int main(int argc, char *argv[], char *env[]) {
}
/* end of ugliness */
/* Initialize event loop using libev */
struct ev_loop *loop = ev_default_loop(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_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);
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 */
@ -483,6 +291,10 @@ int main(int argc, char *argv[], char *env[]) {
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);
@ -495,9 +307,15 @@ int main(int argc, char *argv[], char *env[]) {
/* 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);
/* 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");
@ -523,6 +341,15 @@ int main(int argc, char *argv[], char *env[]) {
}
}
/* Autostarting exec-lines */
struct Autostart *exec;
if (autostart) {
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
}
}
/* check for Xinerama */
LOG("Checking for Xinerama...\n");
initialize_xinerama(conn);
@ -548,8 +375,12 @@ int main(int argc, char *argv[], char *env[]) {
c_ws = &workspaces[screen->current_workspace];
}
/* Enter xcbs event handler */
xcb_event_wait_for_event_loop(&evenths);
/* Handle the events which arrived until now */
xcb_check_cb(NULL, NULL, 0);
/* Ungrab the server to receive events and enter libevs eventloop */
xcb_ungrab_server(conn);
ev_loop(loop, 0);
/* not reached */
return 0;

425
src/manage.c Normal file
View File

@ -0,0 +1,425 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/manage.c: Contains all functions for initially managing new windows
* (or existing ones on restart).
*
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "xcb.h"
#include "data.h"
#include "util.h"
#include "i3.h"
#include "table.h"
#include "config.h"
#include "handlers.h"
#include "layout.h"
#include "manage.h"
#include "floating.h"
#include "client.h"
/*
* Go through all existing windows (if the window manager is restarted) and manage them
*
*/
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
xcb_query_tree_reply_t *reply;
int i, len;
xcb_window_t *children;
xcb_get_window_attributes_cookie_t *cookies;
/* Get the tree of windows whose parent is the root window (= all) */
if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
return;
len = xcb_query_tree_children_length(reply);
cookies = smalloc(len * sizeof(*cookies));
/* Request the window attributes for every window */
children = xcb_query_tree_children(reply);
for(i = 0; i < len; ++i)
cookies[i] = xcb_get_window_attributes(conn, children[i]);
/* Call manage_window with the attributes for every window */
for(i = 0; i < len; ++i) {
window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
manage_window(prophs, conn, children[i], wa);
}
free(reply);
free(cookies);
}
/*
* Do some sanity checks and then reparent the window.
*
*/
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
LOG("managing window.\n");
xcb_drawable_t d = { window };
xcb_get_geometry_cookie_t geomc;
xcb_get_geometry_reply_t *geom;
xcb_get_window_attributes_reply_t *attr = 0;
if (wa.tag == TAG_COOKIE) {
/* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
goto out;
wa.tag = TAG_VALUE;
wa.u.override_redirect = attr->override_redirect;
}
/* Dont manage clients with the override_redirect flag */
if (wa.u.override_redirect)
goto out;
/* Check if the window is already managed */
if (table_get(&by_child, window))
goto out;
/* Get the initial geometry (position, size, …) */
geomc = xcb_get_geometry(conn, d);
if (!attr) {
wa.tag = TAG_COOKIE;
wa.u.cookie = xcb_get_window_attributes(conn, window);
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
}
if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
goto out;
/* Reparent the window and add it to our list of managed windows */
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
geom->x, geom->y, geom->width, geom->height);
/* Generate callback events for every property we watch */
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
free(geom);
out:
free(attr);
return;
}
/*
* reparent_window() gets called when a new window was opened and becomes a child of the root
* window, or it gets called by us when we manage the already existing windows at startup.
*
* Essentially, this is the point where we take over control.
*
*/
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
int16_t x, int16_t y, uint16_t width, uint16_t height) {
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
utf8_title_cookie, title_cookie, class_cookie;
uint32_t mask = 0;
uint32_t values[3];
uint16_t original_height = height;
/* We are interested in property changes */
mask = XCB_CW_EVENT_MASK;
values[0] = CHILD_EVENT_MASK;
xcb_change_window_attributes(conn, child, mask, values);
/* Map the window first to avoid flickering */
xcb_map_window(conn, child);
/* Place requests for properties ASAP */
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
Client *new = table_get(&by_child, child);
/* Events for already managed windows should already be filtered in manage_window() */
assert(new == NULL);
LOG("reparenting new client\n");
LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
new = calloc(sizeof(Client), 1);
new->force_reconfigure = true;
/* Update the data structures */
Client *old_focused = CUR_CELL->currently_focused;
new->container = CUR_CELL;
new->workspace = new->container->workspace;
/* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
width = max(width, 75);
height = max(height, 50);
new->frame = xcb_generate_id(conn);
new->child = child;
new->rect.width = width;
new->rect.height = height;
/* Pre-initialize the values for floating */
new->floating_rect.x = -1;
new->floating_rect.width = width;
new->floating_rect.height = height;
mask = 0;
/* Dont generate events for our new window, it should *not* be managed */
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[0] = 1;
/* We want to know when… */
mask |= XCB_CW_EVENT_MASK;
values[1] = FRAME_EVENT_MASK;
LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
i3Font *font = load_font(conn, config.font);
width = min(width, c_ws->rect.x + c_ws->rect.width);
height = min(height, c_ws->rect.y + c_ws->rect.height);
Rect framerect = {x, y,
width + 2 + 2, /* 2 px border at each side */
height + 2 + 2 + font->height}; /* 2 px border plus fonts height */
/* Yo dawg, I heard you like windows, so I create a window around your window… */
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
/* 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, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
/* Put the client inside the save set. Upon termination (whether killed or normal exit
does not matter) of the window manager, these clients will be correctly reparented
to their most closest living ancestor (= cleanup) */
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
/* Generate a graphics context for the titlebar */
new->titlegc = xcb_generate_id(conn);
xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
/* Moves the original window into the new frame we've created for it */
new->awaiting_useless_unmap = true;
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
if (xcb_request_check(conn, cookie) != NULL) {
LOG("Could not reparent the window, aborting\n");
xcb_destroy_window(conn, new->frame);
free(new);
return;
}
/* Put our data structure (Client) into the table */
table_put(&by_parent, new->frame, new);
table_put(&by_child, child, new);
/* We need to grab the mouse buttons for click to focus */
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
1 /* left mouse button */,
XCB_BUTTON_MASK_ANY /* dont filter for any modifiers */);
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
1 /* left mouse button */, XCB_MOD_MASK_1);
/* Get _NET_WM_WINDOW_TYPE (to see if its a dock) */
xcb_atom_t *atom;
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
LOG("Window is a dock.\n");
new->dock = true;
new->titlebar_position = TITLEBAR_OFF;
new->force_reconfigure = true;
new->container = NULL;
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
/* If its a dock we cant make it float, so we break */
break;
} else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
/* Set the dialog window to automatically floating, will be used below */
new->floating = FLOATING_AUTO_ON;
LOG("dialog/utility/toolbar/splash window, automatically floating\n");
}
}
if (new->workspace->auto_float) {
new->floating = FLOATING_AUTO_ON;
LOG("workspace is in autofloat mode, setting floating\n");
}
if (new->dock) {
/* Get _NET_WM_STRUT_PARTIAL to determine the clients requested height */
uint32_t *strut;
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
/* We only use a subset of the provided values, namely the reserved space at the top/bottom
of the screen. This is because the only possibility for bars is at to be at the top/bottom
with maximum horizontal size.
TODO: bars at the top */
new->desired_height = strut[3];
if (new->desired_height == 0) {
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
new->desired_height = original_height;
}
LOG("the client wants to be %d pixels high\n", new->desired_height);
} else {
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
new->desired_height = original_height;
}
} else {
/* If its not a dock, we can check on which workspace we should put it. */
/* Firstly, we need to get the windows class / title. We asked for the properties at the
* top of this function, get them now and pass them to our callback function for window class / title
* changes. It is important that the client was already inserted into the by_child table,
* because the callbacks wont work otherwise. */
preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
preply = xcb_get_property_reply(conn, title_cookie, NULL);
handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
preply = xcb_get_property_reply(conn, class_cookie, NULL);
handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
LOG("DEBUG: should have all infos now\n");
struct Assignment *assign;
TAILQ_FOREACH(assign, &assignments, assignments) {
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
continue;
if (assign->floating) {
new->floating = FLOATING_AUTO_ON;
LOG("Assignment matches, putting client into floating mode\n");
break;
}
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
assign->windowclass_title, assign->workspace);
if (c_ws->screen->current_workspace == (assign->workspace-1)) {
LOG("We are already there, no need to do anything\n");
break;
}
LOG("Changin container/workspace and unmapping the client\n");
Workspace *t_ws = &(workspaces[assign->workspace-1]);
if (t_ws->screen == NULL) {
LOG("initializing new workspace, setting num to %d\n", assign->workspace);
t_ws->screen = c_ws->screen;
/* Copy the dimensions from the virtual screen */
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
}
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
new->workspace = t_ws;
old_focused = new->container->currently_focused;
xcb_unmap_window(conn, new->frame);
break;
}
}
if (CUR_CELL->workspace->fullscreen_client != NULL) {
if (new->container == CUR_CELL) {
/* If we are in fullscreen, we should lower the window to not be annoying */
uint32_t values[] = { XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
}
} else if (!new->dock) {
/* Focus the new window if were not in fullscreen mode and if it is not a dock window */
if (new->container->workspace->fullscreen_client == NULL) {
if (!client_is_floating(new))
new->container->currently_focused = new;
if (new->container == CUR_CELL)
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
}
}
/* Insert into the currently active container, if its not a dock window */
if (!new->dock && !client_is_floating(new)) {
/* Insert after the old active client, if existing. If it does not exist, the
container is empty and it does not matter, where we insert it */
if (old_focused != NULL && !old_focused->dock)
CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
if (new->container->workspace->fullscreen_client != NULL)
SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
client_set_below_floating(conn, new);
}
if (client_is_floating(new)) {
SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
/* Add the client to the list of floating clients for its workspace */
TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
new->container = NULL;
new->floating_rect.x = new->rect.x = x;
new->floating_rect.y = new->rect.y = y;
new->rect.width = new->floating_rect.width + 2 + 2;
new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
new->floating_rect.x, new->floating_rect.y,
new->floating_rect.width, new->floating_rect.height);
LOG("outer rect (%d, %d) size (%d, %d)\n",
new->rect.x, new->rect.y, new->rect.width, new->rect.height);
/* Make sure it is on top of the other windows */
xcb_raise_window(conn, new->frame);
reposition_client(conn, new);
resize_client(conn, new);
/* redecorate_window flushes */
redecorate_window(conn, new);
}
new->initialized = true;
/* Check if the window already got the fullscreen hint set */
xcb_atom_t *state;
if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
(state = xcb_get_property_value(preply)) != NULL)
/* Check all set _NET_WM_STATEs */
for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
continue;
/* If the window got the fullscreen state, we just toggle fullscreen
and dont event bother to redraw the layout that would not change
anything anyways */
client_toggle_fullscreen(conn, new);
return;
}
render_layout(conn);
}

View File

@ -24,6 +24,9 @@
#include "xcb.h"
#include "debug.h"
#include "layout.h"
#include "xinerama.h"
#include "config.h"
#include "floating.h"
/*
* Renders the resize window between the first/second container and resizes
@ -33,8 +36,14 @@
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, xcb_button_press_event_t *event) {
int new_position;
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
if (screen == NULL) {
LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
return 1;
}
LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
/* FIXME: horizontal resizing causes empty spaces to exist */
if (orientation == O_HORIZONTAL) {
@ -57,9 +66,9 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
Rect helprect;
if (orientation == O_VERTICAL) {
helprect.x = event->root_x;
helprect.y = 0;
helprect.y = screen->rect.y;
helprect.width = 2;
helprect.height = root_screen->height_in_pixels;
helprect.height = screen->rect.height;
new_position = event->root_x;
} else {
helprect.x = 0;
@ -70,7 +79,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
}
mask = XCB_CW_BACK_PIXEL;
values[0] = get_colorpixel(conn, "#4c7899");
values[0] = config.client.focused.border;
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[1] = 1;
@ -82,52 +91,32 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
xcb_grab_pointer(conn, false, root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION,
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, grabwin, XCB_NONE, XCB_CURRENT_TIME);
xcb_flush(conn);
xcb_generic_event_t *inside_event;
/* Ive always wanted to have my own eventhandler… */
while ((inside_event = xcb_wait_for_event(conn))) {
/* Same as get_event_handler in xcb */
int nr = inside_event->response_type;
if (nr == 0) {
/* An error occured */
handle_event(NULL, conn, inside_event);
free(inside_event);
continue;
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
LOG("new x = %d, y = %d\n", new_x, new_y);
if (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;
values[0] = new_position = new_x;
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
} else {
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
new_y < (screen->rect.y + 25))
return;
values[0] = new_position = new_y;
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
}
assert(nr < 256);
nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
assert(nr >= 2);
/* Check if we need to escape this loop */
if (nr == XCB_BUTTON_RELEASE)
break;
switch (nr) {
case XCB_MOTION_NOTIFY:
if (orientation == O_VERTICAL) {
values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x;
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
} else {
values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y;
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
}
xcb_flush(conn);
break;
default:
LOG("Passing to original handler\n");
/* Use original handler */
xcb_event_handle(&evenths, inside_event);
break;
}
free(inside_event);
xcb_flush(conn);
}
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback);
xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin);
xcb_flush(conn);

View File

@ -43,6 +43,7 @@ void init_table() {
for (int i = 0; i < 10; i++) {
workspaces[i].screen = NULL;
workspaces[i].num = i;
TAILQ_INIT(&(workspaces[i].floating_clients));
expand_table_cols(&(workspaces[i]));
expand_table_rows(&(workspaces[i]));
}

View File

@ -27,6 +27,7 @@
#include "layout.h"
#include "util.h"
#include "xcb.h"
#include "client.h"
static iconv_t conversion_descriptor = 0;
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
@ -213,34 +214,17 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
*real_strlen = 0;
if (real_strlen != NULL)
*real_strlen = 0;
return NULL;
}
*real_strlen = ((buffer_size - output_size) / 2) - 1;
if (real_strlen != NULL)
*real_strlen = ((buffer_size - output_size) / 2) - 1;
return buffer;
}
/*
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
*
*/
void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container) {
CIRCLEQ_REMOVE(&(container->clients), client, clients);
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) {
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
}
}
/*
* Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client.
@ -270,19 +254,43 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) {
/* Ignore notify events because they would cause focus to be changed */
ignore_enter_notify_forall(conn, u_ws, true);
/* Unmap all clients of the current workspace */
/* Unmap all clients of the given workspace */
int unmapped_clients = 0;
FOR_TABLE(u_ws)
CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
xcb_unmap_window(conn, client->frame);
unmapped_clients++;
}
/* If we did not unmap any clients, the workspace is empty and we can destroy it */
if (unmapped_clients == 0)
u_ws->screen = NULL;
/* To find floating clients, we traverse the focus stack */
SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
if (!client_is_floating(client))
continue;
/* Unmap the stack windows on the current workspace, if any */
LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
xcb_unmap_window(conn, client->frame);
unmapped_clients++;
}
/* If we did not unmap any clients, the workspace is empty and we can destroy it, at least
* if it is not the current workspace. */
if (unmapped_clients == 0 && u_ws != c_ws) {
/* Re-assign the workspace of all dock clients which use this workspace */
Client *dock;
LOG("workspace %p is empty\n", u_ws);
SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) {
if (dock->workspace != u_ws)
continue;
LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
dock->workspace = c_ws;
}
u_ws->screen = NULL;
}
/* Unmap the stack windows on the given workspace, if any */
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
if (stack_win->container->workspace == u_ws)
xcb_unmap_window(conn, stack_win->window);
@ -302,7 +310,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
return;
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
Client *old_client = SLIST_FIRST(&(c_ws->focus_stack));
/* Check if the focus needs to be changed at all */
if (!set_anyways && (old_client == client)) {
@ -313,47 +321,54 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
/* Store current_row/current_col */
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = client->container->workspace;
c_ws = client->workspace;
/* Load current_col/current_row if we switch to a client without a container */
current_col = c_ws->current_col;
current_row = c_ws->current_row;
/* Update container */
client->container->currently_focused = client;
if (client->container != NULL) {
client->container->currently_focused = client;
current_col = client->container->col;
current_row = client->container->row;
current_col = client->container->col;
current_row = client->container->row;
}
LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name);
/* Set focus to the entered window, and flush xcb buffer immediately */
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
//xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
/* Get the client which was last focused in this particular container, it may be a different
one than old_client */
Client *last_focused = get_last_focused_client(conn, client->container, NULL);
if (client->container != NULL) {
/* Get the client which was last focused in this particular container, it may be a different
one than old_client */
Client *last_focused = get_last_focused_client(conn, client->container, NULL);
/* In stacking containers, raise the client in respect to the one which was focused before */
if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) {
/* We need to get the client again, this time excluding the current client, because
* we might have just gone into stacking mode and need to raise */
Client *last_focused = get_last_focused_client(conn, client->container, client);
/* In stacking containers, raise the client in respect to the one which was focused before */
if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) {
/* We need to get the client again, this time excluding the current client, because
* we might have just gone into stacking mode and need to raise */
Client *last_focused = get_last_focused_client(conn, client->container, client);
if (last_focused != NULL) {
LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
if (last_focused != NULL) {
LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
}
}
/* If it is the same one as old_client, we save us the unnecessary redecorate */
if ((last_focused != NULL) && (last_focused != old_client))
redecorate_window(conn, last_focused);
/* If it is the same one as old_client, we save us the unnecessary redecorate */
if ((last_focused != NULL) && (last_focused != old_client))
redecorate_window(conn, last_focused);
}
/* If were in stacking mode, this renders the container to update changes in the title
bars and to raise the focused client */
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
redecorate_window(conn, old_client);
SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients);
SLIST_INSERT_HEAD(&(client->container->workspace->focus_stack), client, focus_clients);
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
/* redecorate_window flushes, so we dont need to */
redecorate_window(conn, client);
@ -390,7 +405,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
/* When entering stacking mode, we need to open a window on which we can draw the
title bars of the clients, it has height 1 because we dont bother here with
calculating the correct height - it will be adjusted when rendering anyways. */
Rect rect = {container->x, container->y, container->width, 1 };
Rect rect = {container->x, container->y, container->width, 1};
uint32_t mask = 0;
uint32_t values[2];
@ -429,129 +444,79 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
render_layout(conn);
if (container->currently_focused != NULL)
set_focus(conn, container->currently_focused, true);
}
if (container->currently_focused != NULL) {
/* We need to make sure that this client is above *each* of the
* other clients in this container */
Client *last_focused = get_last_focused_client(conn, container, container->currently_focused);
/*
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void 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);
}
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
if (client == container->currently_focused || client == last_focused)
continue;
/*
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
* screen. When leaving fullscreen, re-rendering the layout is forced.
*
*/
void toggle_fullscreen(xcb_connection_t *conn, Client *client) {
/* clients without a container (docks) cannot be focused */
assert(client->container != NULL);
Workspace *workspace = client->container->workspace;
if (!client->fullscreen) {
if (workspace->fullscreen_client != NULL) {
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
return;
LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
client->fullscreen = true;
workspace->fullscreen_client = client;
LOG("Entering fullscreen mode...\n");
/* We just entered fullscreen mode, lets configure the window */
uint32_t mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
uint32_t values[4] = {workspace->rect.x,
workspace->rect.y,
workspace->rect.width,
workspace->rect.height};
LOG("child itself will be at %dx%d with size %dx%d\n",
values[0], values[1], values[2], values[3]);
if (last_focused != NULL) {
LOG("Putting last_focused directly underneath the currently focused\n");
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, last_focused->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
xcb_configure_window(conn, client->frame, mask, values);
/* Childs coordinates are relative to the parent (=frame) */
values[0] = 0;
values[1] = 0;
xcb_configure_window(conn, client->child, mask, values);
/* Raise the window */
values[0] = XCB_STACK_MODE_ABOVE;
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
Rect child_rect = workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
} else {
LOG("leaving fullscreen mode\n");
client->fullscreen = false;
workspace->fullscreen_client = NULL;
/* Because the coordinates of the window havent changed, it would not be
re-configured if we dont set the following flag */
client->force_reconfigure = true;
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
render_layout(conn);
set_focus(conn, container->currently_focused, true);
}
xcb_flush(conn);
}
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
* Gets the first matching client for the given window class/window title.
* If the paramater specific is set to a specific client, only this one
* will be checked.
*
*/
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;
Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
Client *specific) {
char *to_class, *to_title, *to_title_ucs = NULL;
int to_title_ucs_len = 0;
Client *matching = NULL;
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;
to_class = sstrdup(window_classtitle);
/* 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 kill_window(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;
/* If a title was specified, split both strings at the slash */
if ((to_title = strstr(to_class, "/")) != NULL) {
*(to_title++) = '\0';
/* Convert to UCS-2 */
to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
}
xcb_client_message_event_t ev;
/* If we were given a specific client we only check if that one matches */
if (specific != NULL) {
if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len))
matching = specific;
goto done;
}
memset(&ev, 0, sizeof(xcb_client_message_event_t));
LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
for (int workspace = 0; workspace < 10; workspace++) {
if (workspaces[workspace].screen == NULL)
continue;
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;
Client *client;
SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) {
LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name);
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue;
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);
matching = client;
goto done;
}
}
done:
free(to_class);
FREE(to_title_ucs);
return matching;
}

View File

@ -22,7 +22,6 @@
#include "xcb.h"
TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels;
unsigned int xcb_numlock_mask;
/*
@ -74,42 +73,14 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) {
*
*/
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
/* Lookup this colorpixel in the cache */
struct Colorpixel *colorpixel;
SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels)
if (strcmp(colorpixel->hex, hex) == 0)
return colorpixel->pixel;
#define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255)
char strgroups[3][3] = {{hex[1], hex[2], '\0'},
{hex[3], hex[4], '\0'},
{hex[5], hex[6], '\0'}};
int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)),
RGB_8_TO_16(strtol(strgroups[1], NULL, 16)),
RGB_8_TO_16(strtol(strgroups[2], NULL, 16))};
uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
(strtol(strgroups[1], NULL, 16)),
(strtol(strgroups[2], NULL, 16))};
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
xcb_alloc_color_reply_t *reply;
reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap,
rgb16[0], rgb16[1], rgb16[2]), NULL);
if (!reply) {
LOG("Could not allocate color\n");
exit(1);
}
uint32_t pixel = reply->pixel;
free(reply);
/* Store the result in the cache */
struct Colorpixel *cache_pixel = scalloc(sizeof(struct Colorpixel));
cache_pixel->hex = sstrdup(hex);
cache_pixel->pixel = pixel;
SLIST_INSERT_HEAD(&(colorpixels), cache_pixel, colorpixels);
return pixel;
return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
}
/*
@ -267,10 +238,11 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
/* For now, we only use the first keysymbol. */
xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
xcb_keycode_t numlock = *numlock_syms;
free(numlock_syms);
#endif
/* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
for (mask = 0; mask < sizeof(masks); mask++)
for (mask = 0; mask < 8; mask++)
for (i = 0; i < reply->keycodes_per_modifier; i++)
if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
xcb_numlock_mask = masks[mask];
@ -278,3 +250,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
xcb_key_symbols_free(keysyms);
free(reply);
}
/*
* Raises the given window (typically client->frame) above all other windows
*
*/
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
}

View File

@ -100,6 +100,31 @@ i3Screen *get_screen_most(direction_t direction) {
return candidate;
}
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
i3Font *font = load_font(conn, config.font);
workspace->screen = screen;
screen->current_workspace = workspace->num;
/* Create a bar for each screen */
Rect bar_rect = {screen->rect.x,
screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
screen->bargc = xcb_generate_id(conn);
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
SLIST_INIT(&(screen->dock_clients));
/* Copy dimensions */
memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
LOG("that is virtual screen at %d x %d with %d x %d\n",
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
}
/*
* Fills virtual_screens with exactly one screen with width/height of the whole X server.
*
@ -114,6 +139,10 @@ static void disable_xinerama(xcb_connection_t *conn) {
s->rect.width = root_screen->width_in_pixels;
s->rect.height = root_screen->height_in_pixels;
num_screens = 1;
s->num = 0;
initialize_screen(conn, s, &(workspaces[0]));
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
xinerama_enabled = false;
@ -170,31 +199,6 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
}
}
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
i3Font *font = load_font(conn, config.font);
workspace->screen = screen;
screen->current_workspace = workspace->num;
/* Create a bar for each screen */
Rect bar_rect = {screen->rect.x,
screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
screen->bargc = xcb_generate_id(conn);
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
SLIST_INIT(&(screen->dock_clients));
/* Copy dimensions */
memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
LOG("that is virtual screen at %d x %d with %d x %d\n",
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
}
/*
* We have just established a connection to the X server and need the initial Xinerama
* information to setup workspaces for each screen.