Merge branch 'next'

Conflicts:
	src/manage.c
	src/util.c
	src/xinerama.c
This commit is contained in:
Michael Stapelberg
2009-08-19 15:13:27 +02:00
52 changed files with 16997 additions and 986 deletions

View File

@ -39,9 +39,11 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai
/* 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) {
LOG("Unmapping stack window\n");
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
xcb_flush(conn);
}
}
@ -248,3 +250,67 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) {
bool client_is_floating(Client *client) {
return (client->floating >= FLOATING_AUTO_ON);
}
/*
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b).
*
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type) {
switch (border_type) {
case 'n':
LOG("Changing to normal border\n");
client->titlebar_position = TITLEBAR_TOP;
client->borderless = false;
break;
case 'p':
LOG("Changing to 1px border\n");
client->titlebar_position = TITLEBAR_OFF;
client->borderless = false;
break;
case 'b':
LOG("Changing to borderless\n");
client->titlebar_position = TITLEBAR_OFF;
client->borderless = true;
break;
default:
LOG("Unknown border mode\n");
return;
}
/* Ensure that the childs position inside our window gets updated */
client->force_reconfigure = true;
/* For clients inside a container, we can simply render the container.
* If the client is floating, we need to render the whole layout */
if (client->container != NULL)
render_container(conn, client->container);
else render_layout(conn);
redecorate_window(conn, client);
}
/*
* Unmap the client, correctly setting any state which is needed.
*
*/
void client_unmap(xcb_connection_t *conn, Client *client) {
/* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
xcb_unmap_window(conn, client->frame);
}
/*
* Map the client, correctly restoring any state needed.
*
*/
void client_map(xcb_connection_t *conn, Client *client) {
/* Set WM_STATE_NORMAL because GTK applications dont want to drag & drop if we dont.
* Also, xprop(1) needs that to work. */
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
xcb_map_window(conn, client->frame);
}

View File

@ -26,6 +26,9 @@
#include "client.h"
#include "floating.h"
#include "xcb.h"
#include "config.h"
#include "workspace.h"
#include "commands.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */
@ -53,7 +56,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir
return true;
}
typedef enum { THING_WINDOW, THING_CONTAINER } thing_t;
typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t;
static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
LOG("focusing direction %d\n", direction);
@ -79,6 +82,41 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
return;
}
/* For focusing screens, situation is different: we get the rect
* of the current screen, then get the screen which is on its
* right/left/bottom/top and just switch to the workspace on
* the target screen. */
if (thing == THING_SCREEN) {
i3Screen *cs = c_ws->screen;
assert(cs != NULL);
Rect bounds = cs->rect;
if (direction == D_RIGHT)
bounds.x += bounds.width;
else if (direction == D_LEFT)
bounds.x -= bounds.width;
else if (direction == D_UP)
bounds.y -= bounds.height;
else bounds.y += bounds.height;
i3Screen *target = get_screen_containing(bounds.x, bounds.y);
if (target == NULL) {
LOG("Target screen NULL\n");
/* Wrap around if the target screen is out of bounds */
if (direction == D_RIGHT)
target = get_screen_most(D_LEFT, cs);
else if (direction == D_LEFT)
target = get_screen_most(D_RIGHT, cs);
else if (direction == D_UP)
target = get_screen_most(D_DOWN, cs);
else target = get_screen_most(D_UP, cs);
}
LOG("Switching to ws %d\n", target->current_workspace + 1);
workspace_show(conn, target->current_workspace + 1);
return;
}
/* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */
if (direction == D_UP || direction == D_DOWN) {
if (thing == THING_WINDOW)
@ -108,7 +146,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
if ((screen = get_screen_containing(container->x, destination_y)) == NULL) {
LOG("Wrapping screen around vertically\n");
/* No screen found? Then wrap */
screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP));
screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen);
}
t_ws = &(workspaces[screen->current_workspace]);
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
@ -150,7 +188,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1));
if ((screen = get_screen_containing(destination_x, container->y)) == NULL) {
LOG("Wrapping screen around horizontally\n");
screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT));
screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen);
}
t_ws = &(workspaces[screen->current_workspace]);
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
@ -489,12 +527,10 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
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) {
if (!workspace_is_visible(t_ws)) {
LOG("This workspace is not visible, unmapping\n");
xcb_unmap_window(conn, client->frame);
client_unmap(conn, client);
} 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
@ -514,7 +550,7 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
render_layout(conn);
if (!target_invisible)
if (workspace_is_visible(t_ws))
set_focus(conn, client, true);
}
@ -574,12 +610,10 @@ 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 (target_invisible) {
if (!workspace_is_visible(to_container->workspace)) {
LOG("This workspace is not visible, unmapping\n");
xcb_unmap_window(conn, current_client->frame);
client_unmap(conn, current_client);
} else {
if (current_client->fullscreen) {
LOG("Calling client_enter_fullscreen again\n");
@ -592,117 +626,10 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
render_layout(conn);
if (!target_invisible)
if (workspace_is_visible(to_container->workspace))
set_focus(conn, current_client, true);
}
/*
* Switches to the given workspace
*
*/
void show_workspace(xcb_connection_t *conn, int workspace) {
Client *client;
bool need_warp = false;
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
/* t_ws (to workspace) is just a convenience pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]);
LOG("show_workspace(%d)\n", workspace);
/* Store current_row/current_col */
c_ws->current_row = current_row;
c_ws->current_col = current_col;
/* Check if the workspace has not been used yet */
if (t_ws->screen == NULL) {
LOG("initializing new workspace, setting num to %d\n", workspace);
t_ws->screen = c_ws->screen;
/* Copy the dimensions from the virtual screen */
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
}
if (c_ws->screen != t_ws->screen) {
/* We need to switch to the other screen first */
LOG("moving over to other screen.\n");
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
c_ws = &(workspaces[t_ws->screen->current_workspace]);
current_col = c_ws->current_col;
current_row = c_ws->current_row;
if (CUR_CELL->currently_focused != NULL)
need_warp = true;
else {
Rect *dims = &(c_ws->screen->rect);
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
dims->x + (dims->width / 2), dims->y + (dims->height / 2));
}
/* Re-decorate the old client, its not focused anymore */
if ((old_client != NULL) && !old_client->dock)
redecorate_window(conn, old_client);
else xcb_flush(conn);
}
/* Check if we need to change something or if were already there */
if (c_ws->screen->current_workspace == (workspace-1)) {
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) {
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
}
return;
}
t_ws->screen->current_workspace = workspace-1;
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);
ignore_enter_notify_forall(conn, c_ws, true);
/* Map all clients on the new workspace */
FOR_TABLE(c_ws)
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)
if (stack_win->container->workspace == c_ws)
xcb_map_window(conn, stack_win->window);
ignore_enter_notify_forall(conn, c_ws, false);
/* Restore focus on the new workspace */
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) {
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);
render_layout(conn);
}
/*
* Jumps to the given window class / title.
* Title is matched using strstr, that is, matches if it appears anywhere
@ -748,7 +675,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
}
/* Move to the target workspace */
show_workspace(conn, ws);
workspace_show(conn, ws);
if (result < 3)
return;
@ -846,6 +773,38 @@ static char **append_argument(char **original, char *argument) {
return result;
}
/*
* Switch to next or previous existing workspace
*
*/
static void next_previous_workspace(xcb_connection_t *conn, int direction) {
Workspace *t_ws;
int i;
if (direction == 'n') {
/* If we are on the last workspace, we cannot go any further */
if (c_ws->num == 9)
return;
for (i = c_ws->num + 1; i <= 9; i++) {
t_ws = &(workspaces[i]);
if (t_ws->screen != NULL)
break;
}
} else if (direction == 'p') {
if (c_ws->num == 0)
return;
for (i = c_ws->num - 1; i >= 0 ; i--) {
t_ws = &(workspaces[i]);
if (t_ws->screen != NULL)
break;
}
}
if (t_ws->screen != NULL)
workspace_show(conn, i+1);
}
/*
* Parses a command, see file CMDMODE for more information
*
@ -875,6 +834,12 @@ void parse_command(xcb_connection_t *conn, const char *command) {
exit(EXIT_SUCCESS);
}
/* Is it a <reload>? */
if (STARTS_WITH(command, "reload")) {
load_configuration(conn, NULL, true);
return;
}
/* Is it <restart>? Then restart in place. */
if (STARTS_WITH(command, "restart")) {
LOG("restarting \"%s\"...\n", start_argv[0]);
@ -922,7 +887,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* 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)) {
if (last_focused != NULL && client_is_floating(last_focused)) {
LOG("not switching, this is a floating client\n");
return;
}
@ -931,13 +896,23 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return;
}
/* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */
if (command[0] == 'b') {
if (last_focused == NULL) {
LOG("No window focused, cannot change border type\n");
return;
}
client_change_border(conn, last_focused, command[1]);
return;
}
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;
enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE, WITH_SCREEN } with = WITH_WINDOW;
/* Is it a <with>? */
if (command[0] == 'w') {
@ -949,6 +924,9 @@ void parse_command(xcb_connection_t *conn, const char *command) {
} else if (command[0] == 'w') {
with = WITH_WORKSPACE;
command++;
} else if (command[0] == 's') {
with = WITH_SCREEN;
command++;
} else {
LOG("not yet implemented.\n");
return;
@ -983,6 +961,12 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return;
}
/* Is it 'n' or 'p' for next/previous workspace? (nw) */
if ((command[0] == 'n' || command[0] == 'p') && command[1] == 'w') {
next_previous_workspace(conn, command[0]);
return;
}
/* Its a normal <cmd> */
char *rest = NULL;
enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
@ -995,7 +979,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (*rest == '\0') {
/* No rest? This was a workspace number, not a times specification */
show_workspace(conn, times);
workspace_show(conn, times);
return;
}
@ -1046,6 +1030,10 @@ void parse_command(xcb_connection_t *conn, const char *command) {
rest++;
if (action == ACTION_FOCUS) {
if (with == WITH_SCREEN) {
focus_thing(conn, direction, THING_SCREEN);
continue;
}
if (client_is_floating(last_focused)) {
floating_focus_direction(conn, last_focused, direction);
continue;
@ -1055,6 +1043,13 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
if (action == ACTION_MOVE) {
if (with == WITH_SCREEN) {
/* TODO: this should swap the screens contents
* (e.g. all workspaces) with the next/previous/…
* screen */
LOG("Not yet implemented\n");
continue;
}
if (client_is_floating(last_focused)) {
floating_move(conn, last_focused, direction);
continue;
@ -1066,10 +1061,12 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
if (action == ACTION_SNAP) {
if (with == WITH_SCREEN) {
LOG("You cannot snap a screen (it makes no sense).\n");
continue;
}
snap_current_container(conn, direction);
continue;
}
}
LOG("--- done ---\n");
}

View File

@ -14,10 +14,17 @@
#include <stdlib.h>
#include <glob.h>
/* We need Xlib for XStringToKeysym */
#include <X11/Xlib.h>
#include <xcb/xcb_keysyms.h>
#include "i3.h"
#include "util.h"
#include "config.h"
#include "xcb.h"
#include "table.h"
#include "workspace.h"
Config config;
@ -43,9 +50,7 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
/* 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;
@ -57,6 +62,75 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
}
}
/**
* Ungrabs all keys, to be called before re-grabbing the keys because of a
* mapping_notify event or a configuration file reload
*
*/
void ungrab_all_keys(xcb_connection_t *conn) {
LOG("Ungrabbing all keys\n");
xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY);
}
static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
LOG("Grabbing %d\n", keycode);
if ((bind->mods & BIND_MODE_SWITCH) != 0)
xcb_grab_key(conn, 0, root, 0, keycode,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
else {
/* Grab the key in all combinations */
#define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, keycode, \
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC)
GRAB_KEY(bind->mods);
GRAB_KEY(bind->mods | xcb_numlock_mask);
GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
}
}
/*
* Grab the bound keys (tell X to send us keypress events for those keycodes)
*
*/
void grab_all_keys(xcb_connection_t *conn) {
Binding *bind;
TAILQ_FOREACH(bind, &bindings, bindings) {
/* The easy case: the user specified a keycode directly. */
if (bind->keycode > 0) {
grab_keycode_for_binding(conn, bind, bind->keycode);
continue;
}
/* We need to translate the symbol to a keycode */
xcb_keysym_t keysym = XStringToKeysym(bind->symbol);
if (keysym == NoSymbol) {
LOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol);
continue;
}
xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym);
if (keycodes == NULL) {
LOG("Could not translate symbol \"%s\"\n", bind->symbol);
continue;
}
uint32_t last_keycode = 0;
bind->number_keycodes = 0;
for (xcb_keycode_t *walk = keycodes; *walk != 0; walk++) {
/* We hope duplicate keycodes will be returned in order
* and skip them */
if (last_keycode == *walk)
continue;
grab_keycode_for_binding(conn, bind, *walk);
last_keycode = *walk;
bind->number_keycodes++;
}
LOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes);
bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t));
memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t));
free(keycodes);
}
}
/*
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
*
@ -64,7 +138,29 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
* configuration file.
*
*/
void load_configuration(xcb_connection_t *conn, const char *override_configpath) {
void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
if (reload) {
/* First ungrab the keys */
ungrab_all_keys(conn);
/* Clear the old binding and assignment lists */
Binding *bind;
while (!TAILQ_EMPTY(&bindings)) {
bind = TAILQ_FIRST(&bindings);
TAILQ_REMOVE(&bindings, bind, bindings);
FREE(bind->command);
FREE(bind);
}
struct Assignment *assign;
while (!TAILQ_EMPTY(&assignments)) {
assign = TAILQ_FIRST(&assignments);
FREE(assign->windowclass_title);
TAILQ_REMOVE(&assignments, assign, assignments);
FREE(assign);
}
}
SLIST_HEAD(variables_head, Variable) variables;
#define OPTION_STRING(name) \
@ -176,7 +272,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
}
/* key bindings */
if (strcasecmp(key, "bind") == 0) {
if (strcasecmp(key, "bind") == 0 || strcasecmp(key, "bindsym") == 0) {
#define CHECK_MODIFIER(name) \
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
modifiers |= BIND_##name; \
@ -201,14 +297,25 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
break;
}
/* Now check for the keycode */
int keycode = strtol(walk, &rest, 10);
if (!rest || *rest != ' ')
die("Invalid binding\n");
Binding *new = scalloc(sizeof(Binding));
/* Now check for the keycode or copy the symbol */
if (strcasecmp(key, "bind") == 0) {
int keycode = strtol(walk, &rest, 10);
if (!rest || *rest != ' ')
die("Invalid binding (keycode)\n");
new->keycode = keycode;
} else {
rest = walk;
char *sym = rest;
while (*rest != '\0' && *rest != ' ')
rest++;
if (*rest != ' ')
die("Invalid binding (keysym)\n");
new->symbol = strndup(sym, (rest - sym));
}
rest++;
LOG("keycode = %d, modifiers = %d, command = *%s*\n", keycode, modifiers, rest);
Binding *new = smalloc(sizeof(Binding));
new->keycode = keycode;
LOG("keycode = %d, symbol = %s, modifiers = %d, command = *%s*\n", new->keycode, new->symbol, modifiers, rest);
new->mods = modifiers;
new->command = sstrdup(rest);
TAILQ_INSERT_TAIL(&bindings, new, bindings);
@ -239,26 +346,81 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
continue;
}
/* workspace "workspace number" [screen <screen>] ["name of the workspace"]
* with screen := <number> | <position>, e.g. screen 1280 or screen 1 */
if (strcasecmp(key, "name") == 0 || strcasecmp(key, "workspace") == 0) {
LOG("workspace: %s\n",value);
char *ws_str = sstrdup(value);
char *end = strchr(ws_str, ' ');
if (end == NULL)
die("Malformed name, couln't find terminating space\n");
*end = '\0';
/* Strip trailing whitespace */
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
value[strlen(value)-1] = '\0';
int ws_num = atoi(ws_str);
if (ws_num < 1 || ws_num > 10)
die("Malformed name, invalid workspace number\n");
/* find the name */
char *name = value;
name += strlen(ws_str) + 1;
if (strncasecmp(name, "screen ", strlen("screen ")) == 0) {
char *screen = strdup(name + strlen("screen "));
if ((end = strchr(screen, ' ')) != NULL)
*end = '\0';
LOG("Setting preferred screen for workspace %d to \"%s\"\n", ws_num, screen);
workspaces[ws_num - 1].preferred_screen = screen;
name += strlen("screen ") + strlen(screen);
}
/* Strip leading whitespace */
while (*name != '\0' && *name == ' ')
name++;
LOG("rest to parse = %s\n", name);
if (name == '\0') {
free(ws_str);
continue;
}
LOG("setting name to \"%s\"\n", name);
workspace_set_name(&(workspaces[ws_num - 1]), name);
free(ws_str);
continue;
}
/* assign window class[/window title] → workspace */
if (strcasecmp(key, "assign") == 0) {
LOG("assign: \"%s\"\n", value);
char *class_title = sstrdup(value);
char *class_title;
char *target;
char *end;
/* 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';
if (value[0] == '"') {
class_title = sstrdup(value+1);
end = strchr(class_title, '"');
} else {
class_title = sstrdup(value);
/* 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';
end = strchr(class_title, ' ');
}
if (end == NULL)
die("Malformed assignment, couldn't find terminating quote\n");
*end = '\0';
/* Strip trailing whitespace */
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
value[strlen(value)-1] = '\0';
/* Strip trailing whitespace */
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
@ -266,20 +428,36 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
/* The target is the last argument separated by a space */
if ((target = strrchr(value, ' ')) == NULL)
die("Malformed assignment, couldn't find target\n");
die("Malformed assignment, couldn't find target (\"%s\")\n", value);
target++;
if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10))
if (strchr(target, '~') == NULL && (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);
if (strchr(target, '~') != NULL)
new->floating = ASSIGN_FLOATING_ONLY;
while (*target == '~')
target++;
if (atoi(target) >= 1 && atoi(target) <= 10) {
if (new->floating == ASSIGN_FLOATING_ONLY)
new->floating = ASSIGN_FLOATING;
new->workspace = atoi(target);
}
TAILQ_INSERT_TAIL(&assignments, new, assignments);
LOG("Assignment loaded: \"%s\":\n", class_title);
if (new->floating != ASSIGN_FLOATING_ONLY)
LOG(" to workspace %d\n", new->workspace);
if (new->floating != ASSIGN_FLOATING_NO)
LOG(" will be floating\n");
continue;
}
@ -303,14 +481,21 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
continue;
}
if (strcasecmp(key, "ipc-socket") == 0) {
config.ipc_socket_path = sstrdup(value);
continue;
}
die("Unknown configfile option: %s\n", key);
}
/* now grab all keys again */
if (reload)
grab_all_keys(conn);
fclose(handle);
REQUIRED_OPTION(terminal);
REQUIRED_OPTION(font);
while (!SLIST_EMPTY(&variables)) {
struct Variable *v = SLIST_FIRST(&variables);
SLIST_REMOVE_HEAD(&variables, variables);
@ -318,6 +503,21 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
free(v->value);
free(v);
}
/* Set an empty name for every workspace which got no name */
for (int i = 0; i < 10; i++) {
Workspace *ws = &(workspaces[i]);
if (ws->name != NULL) {
/* If the font was not specified when the workspace name
* was loaded, we need to predict the text width now */
if (ws->text_width == 0)
ws->text_width = predict_text_width(global_conn,
config.font, ws->name, ws->name_len);
continue;
}
workspace_set_name(&(workspaces[i]), NULL);
}
return;
}

View File

@ -415,8 +415,8 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
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);
client_unmap(conn, client);
else client_map(conn, client);
}
/* If we just unmapped all floating windows we should ensure that the focus

View File

@ -35,6 +35,7 @@
#include "client.h"
#include "manage.h"
#include "floating.h"
#include "workspace.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
@ -47,8 +48,6 @@ static void add_ignore_event(const int sequence) {
event->sequence = sequence;
event->added = time(NULL);
LOG("Adding sequence %d to ignorelist\n", sequence);
SLIST_INSERT_HEAD(&ignore_events, event, ignore_events);
}
@ -70,7 +69,6 @@ static bool event_is_ignored(const int sequence) {
SLIST_FOREACH(event, &ignore_events, ignore_events) {
if (event->sequence == sequence) {
LOG("Ignoring event (sequence %d)\n", sequence);
SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
free(event);
return true;
@ -86,7 +84,6 @@ static bool event_is_ignored(const int sequence) {
*
*/
int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
LOG("got key release, just passing\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, event->time);
xcb_flush(conn);
return 1;
@ -119,9 +116,24 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
/* Find the binding */
Binding *bind;
TAILQ_FOREACH(bind, &bindings, bindings)
if (bind->keycode == event->detail && bind->mods == state_filtered)
break;
TAILQ_FOREACH(bind, &bindings, bindings) {
/* First compare the modifiers */
if (bind->mods != state_filtered)
continue;
/* If a symbol was specified by the user, we need to look in
* the array of translated keycodes for the events keycode */
if (bind->symbol != NULL) {
if (memmem(bind->translated_to,
bind->number_keycodes * sizeof(xcb_keycode_t),
&(event->detail), sizeof(xcb_keycode_t)) != NULL)
break;
} else {
/* This case is easier: The user specified a keycode */
if (bind->keycode == event->detail)
break;
}
}
/* No match? Then it was an actively grabbed key, that is with Mode_switch, and
the user did not press Mode_switch, so just pass it… */
@ -140,6 +152,29 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
return 1;
}
/*
* Called with coordinates of an enter_notify event or motion_notify event
* to check if the user crossed virtual screen boundaries and adjust the
* current workspace, if so.
*
*/
static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
i3Screen *screen;
if ((screen = get_screen_containing(x, y)) == NULL) {
LOG("ERROR: No such screen\n");
return;
}
if (screen == c_ws->screen)
return;
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = &workspaces[screen->current_workspace];
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("We're now on virtual screen number %d\n", screen->num);
}
/*
* When the user moves the mouse pointer onto a window, this callback gets called.
@ -176,17 +211,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
/* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
if (client == NULL) {
LOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
if (screen == NULL) {
LOG("ERROR: No such screen\n");
return 0;
}
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = &workspaces[screen->current_workspace];
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("We're now on virtual screen number %d\n", screen->num);
check_crossing_screen_boundary(event->root_x, event->root_y);
return 1;
}
@ -212,6 +237,44 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
return 1;
}
/*
* When the user moves the mouse but does not change the active window
* (e.g. when having no windows opened but moving mouse on the root screen
* and crossing virtual screen boundaries), this callback gets called.
*
*/
int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event) {
/* Skip events where the pointer was over a child window, we are only
* interested in events on the root window. */
if (event->child != 0)
return 1;
check_crossing_screen_boundary(event->root_x, event->root_y);
return 1;
}
/*
* Called when the keyboard mapping changes (for example by using Xmodmap),
* we need to update our key bindings then (re-translate symbols).
*
*/
int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event) {
if (event->request != XCB_MAPPING_KEYBOARD &&
event->request != XCB_MAPPING_MODIFIER)
return 0;
LOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
xcb_refresh_keyboard_mapping(keysyms, event);
xcb_get_numlock_mask(conn);
ungrab_all_keys(conn);
grab_all_keys(conn);
return 0;
}
/*
* Checks if the button press was on a stack window, handles focus setting and returns true
* if so, or false otherwise.
@ -271,21 +334,27 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
int add = (event->detail == XCB_BUTTON_INDEX_4 ? -1 : 1);
for (int i = c_ws->num + add; (i >= 0) && (i < 10); i += add)
if (workspaces[i].screen == screen) {
show_workspace(conn, i+1);
workspace_show(conn, i+1);
return true;
}
return true;
}
i3Font *font = load_font(conn, config.font);
int workspace = event->event_x / (font->height + 6),
c = 0;
int drawn = 0;
/* Because workspaces can be on different screens, we need to loop
through all of them and decide to count it based on its ->screen */
for (int i = 0; i < 10; i++)
if ((workspaces[i].screen == screen) && (c++ == workspace)) {
show_workspace(conn, i+1);
for (int i = 0; i < 10; i++) {
if (workspaces[i].screen != screen)
continue;
LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
i, drawn, workspaces[i].text_width);
if (event->event_x > (drawn + 1) &&
event->event_x <= (drawn + 1 + workspaces[i].text_width + 5 + 5)) {
workspace_show(conn, i+1);
return true;
}
drawn += workspaces[i].text_width + 5 + 5 + 2;
}
return true;
}
@ -293,8 +362,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);
LOG("Button %d pressed\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;
@ -308,7 +376,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
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");
LOG("Not handling, floating_modifier was pressed and no client found\n");
return 1;
}
if (client_is_floating(client)) {
@ -348,6 +416,18 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
/* Some clients (xfontsel for example) seem to pass clicks on their
* window to the parent window, thus we receive an event here which in
* reality is a border_click. Check for the position and fix state. */
if (border_click &&
event->event_x >= client->child_rect.x &&
event->event_x <= (client->child_rect.x + client->child_rect.width) &&
event->event_y >= client->child_rect.y &&
event->event_y <= (client->child_rect.y + client->child_rect.height)) {
LOG("Fixing border_click = false because of click in child\n");
border_click = false;
}
if (!border_click) {
LOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
@ -421,22 +501,13 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
*/
int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event) {
xcb_get_window_attributes_cookie_t cookie;
xcb_get_window_attributes_reply_t *reply;
cookie = xcb_get_window_attributes_unchecked(conn, event->window);
if ((reply = xcb_get_window_attributes_reply(conn, cookie, NULL)) == NULL) {
LOG("Could not get window attributes\n");
return -1;
}
window_attributes_t wa = { TAG_VALUE };
LOG("override_redirect = %d\n", reply->override_redirect);
wa.u.override_redirect = reply->override_redirect;
LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
add_ignore_event(event->sequence);
manage_window(prophs, conn, event->window, wa);
manage_window(prophs, conn, event->window, cookie, false);
return 1;
}
@ -447,13 +518,11 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
*
*/
int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) {
LOG("configure-request, serial %d\n", event->sequence);
LOG("event->window = %08x\n", event->window);
LOG("application wants to be at %dx%d with %dx%d\n", event->x, event->y, event->width, event->height);
LOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
event->window, event->x, event->y, event->width, event->height);
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");
uint32_t mask = 0;
uint32_t values[7];
int c = 0;
@ -478,6 +547,16 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
return 1;
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
Rect child_rect = client->workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
return 1;
}
/* Floating clients can be reconfigured */
if (client_is_floating(client)) {
i3Font *font = load_font(conn, config.font);
@ -512,16 +591,6 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
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;
}
fake_absolute_configure_notify(conn, client);
return 1;
@ -535,15 +604,12 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
LOG("handle_configure_event for window %08x\n", event->window);
LOG("event->type = %d, \n", event->response_type);
LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height);
/* We ignore this sequence twice because events for child and frame should be ignored */
add_ignore_event(event->sequence);
add_ignore_event(event->sequence);
if (event->event == root) {
LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height);
LOG("reconfigure of the root window, need to xinerama\n");
/* FIXME: Somehow, this is occuring too often. Therefore, we check for 0/0,
but is there a better way? */
@ -569,7 +635,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
/* First, we need to check if the client is awaiting an unmap-request which
was generated by us reparenting the window. In that case, we just ignore it. */
if (client != NULL && client->awaiting_useless_unmap) {
LOG("Dropping this unmap request, it was generated by reparenting\n");
client->awaiting_useless_unmap = false;
return 1;
}
@ -620,6 +685,9 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
LOG("child of 0x%08x.\n", client->frame);
xcb_reparent_window(conn, client->child, root, 0, 0);
client_unmap(conn, client);
xcb_destroy_window(conn, client->frame);
xcb_flush(conn);
table_remove(&by_parent, client->frame);
@ -644,11 +712,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
break;
}
if (workspace_empty) {
LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num,
client->workspace);
if (workspace_empty)
client->workspace->screen = NULL;
}
FREE(client->window_class);
FREE(client->name);
@ -669,7 +734,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
*/
int handle_windowname_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's name changed.\n");
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("_NET_WM_NAME not specified, not changing\n");
return 1;
@ -684,7 +748,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop));
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
char *ucs2_name = convert_utf8_to_ucs2(new_name, &new_len);
LOG("Name should change to \"%s\"\n", new_name);
LOG("_NET_WM_NAME changed to \"%s\"\n", new_name);
free(new_name);
/* Check if they are the same and dont update if so.
@ -694,7 +758,6 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
if ((new_len == client->name_len) &&
(client->name != NULL) &&
(memcmp(client->name, ucs2_name, new_len * 2) == 0)) {
LOG("Name did not change, not updating\n");
free(ucs2_name);
return 1;
}
@ -731,7 +794,6 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
*/
int handle_windowname_change_legacy(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's name changed (legacy).\n");
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
return 1;
@ -740,10 +802,9 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
if (client == NULL)
return 1;
if (client->uses_net_wm_name) {
LOG("This client is capable of _NET_WM_NAME, ignoring legacy name\n");
/* Client capable of _NET_WM_NAME, ignore legacy name changes */
if (client->uses_net_wm_name)
return 1;
}
/* Save the old pointer to make the update atomic */
char *new_name;
@ -753,18 +814,17 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
return 1;
}
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
LOG("Name should change to \"%s\"\n", new_name);
LOG("WM_NAME changed to \"%s\"\n", new_name);
/* Check if they are the same and dont update if so. */
if (client->name != NULL &&
strlen(new_name) == strlen(client->name) &&
strcmp(client->name, new_name) == 0) {
LOG("Name did not change, not updating\n");
free(new_name);
return 1;
}
LOG("Using legacy window title. Note that in order to get Unicode window titles in i3,"
LOG("Using legacy window title. Note that in order to get Unicode window titles in i3, "
"the application has to set _NET_WM_NAME which is in UTF-8 encoding.\n");
char *old_name = client->name;
@ -792,7 +852,6 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
*/
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;
@ -807,15 +866,13 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
return 1;
}
LOG("changed to %s\n", new_class);
LOG("WM_CLASS 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");
if (!client->initialized)
return 1;
}
if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) {
LOG("tool/dialog window, should we put it floating?\n");
@ -856,11 +913,8 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
return 1;
}
LOG("got client %s\n", client->name);
if (client->dock) {
LOG("this is a dock\n");
if (client->dock)
return 1;
}
if (client->container == NULL || client->container->mode != MODE_STACK)
decorate_window(conn, client, client->frame, client->titlegc, 0);
@ -897,14 +951,10 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
*
*/
int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event) {
LOG("client_message\n");
if (event->type == atoms[_NET_WM_STATE]) {
if (event->format != 32 || event->data.data32[1] != atoms[_NET_WM_STATE_FULLSCREEN])
return 0;
LOG("fullscreen\n");
Client *client = table_get(&by_child, event->window);
if (client == NULL)
return 0;
@ -942,14 +992,14 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
*/
int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply) {
LOG("handle_normal_hints\n");
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("No such client\n");
LOG("Received WM_SIZE_HINTS for unknown client\n");
return 1;
}
xcb_size_hints_t size_hints;
LOG("client is %08x / child %08x\n", client->frame, client->child);
CLIENT_LOG(client);
/* If the hints were already in this event, use them, if not, request them */
if (reply != NULL)
@ -958,20 +1008,24 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
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);
// TODO: Minimum size is not yet implemented
//LOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
}
if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
if (size_hints.width_inc > 0)
client->width_increment = size_hints.width_inc;
if (size_hints.height_inc > 0)
client->height_increment = size_hints.height_inc;
}
/* 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) ||
(size_hints.min_aspect_den <= 0)) {
LOG("No aspect ratio set, ignoring\n");
return 1;
}
LOG("window is %08x / %s\n", client->child, client->name);
int base_width = 0, base_height = 0;
/* base_width/height are the desired size of the window.
@ -991,7 +1045,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
LOG("min_aspect = %f, max_aspect = %f\n", min_aspect, max_aspect);
LOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
LOG("width = %f, height = %f\n", width, height);
/* Sanity checks, this is user-input, in a way */
@ -1026,7 +1080,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
*/
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");
@ -1036,16 +1089,12 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
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");
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply))
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");
&transient_for, NULL))
return 1;
}
}
if (client->floating == FLOATING_AUTO_OFF) {
@ -1055,3 +1104,32 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
return 1;
}
/*
* Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a
* toolwindow (or similar) and to which window it belongs (logical parent).
*
*/
int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *prop) {
if (prop == NULL) {
prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL);
if (prop == NULL)
return 1;
}
Client *client = table_get(&by_child, window);
if (client == NULL)
return 1;
xcb_window_t *leader = xcb_get_property_value(prop);
if (leader == NULL || *leader == 0)
return 1;
LOG("Client leader changed to %08x\n", *leader);
client->leader = *leader;
return 1;
}

232
src/ipc.c Normal file
View File

@ -0,0 +1,232 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* ipc.c: Everything about the UNIX domain sockets for IPC
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <ev.h>
#include "queue.h"
#include "i3/ipc.h"
#include "i3.h"
#include "util.h"
#include "commands.h"
typedef struct ipc_client {
int fd;
TAILQ_ENTRY(ipc_client) clients;
} ipc_client;
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
/*
* Puts the given socket file descriptor into non-blocking mode or dies if
* setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
* IPC model because we should by no means block the window manager.
*
*/
static void set_nonblock(int sockfd) {
int flags = fcntl(sockfd, F_GETFL, 0);
flags |= O_NONBLOCK;
if (fcntl(sockfd, F_SETFL, flags) < 0)
err(-1, "Could not set O_NONBLOCK");
}
#if 0
void broadcast(EV_P_ struct ev_timer *t, int revents) {
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
write(current->fd, "hi there!\n", strlen("hi there!\n"));
}
}
#endif
/*
* Decides what to do with the received message.
*
* message is the raw packet, as received from the UNIX domain socket. size
* is the remaining size of bytes for this packet.
*
* message_size is the size of the message as the sender specified it.
* message_type is the type of the message as the sender specified it.
*
*/
static void ipc_handle_message(uint8_t *message, int size,
uint32_t message_size, uint32_t message_type) {
LOG("handling message of size %d\n", size);
LOG("sender specified size %d\n", message_size);
LOG("sender specified type %d\n", message_type);
LOG("payload as a string = %s\n", message);
switch (message_type) {
case I3_IPC_MESSAGE_TYPE_COMMAND:
parse_command(global_conn, (const char*)message);
break;
default:
LOG("unhandled ipc message\n");
break;
}
}
/*
* Handler for activity on a client connection, receives a message from a
* client.
*
* For now, the maximum message size is 2048. Im not sure for what the
* IPC interface will be used in the future, thus Im not implementing a
* mechanism for arbitrarily long messages, as it seems like overkill
* at the moment.
*
*/
static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
char buf[2048];
int n = read(w->fd, buf, sizeof(buf));
/* On error or an empty message, we close the connection */
if (n <= 0) {
#if 0
/* FIXME: I get these when closing a client socket,
* therefore we just treat them as an error. Is this
* correct? */
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
#endif
/* If not, there was some kind of error. We dont bother
* and close the connection */
close(w->fd);
/* Delete the client from the list of clients */
struct ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
if (current->fd != w->fd)
continue;
/* We can call TAILQ_REMOVE because we break out of the
* TAILQ_FOREACH afterwards */
TAILQ_REMOVE(&all_clients, current, clients);
break;
}
ev_io_stop(EV_A_ w);
LOG("IPC: client disconnected\n");
return;
}
/* Terminate the message correctly */
buf[n] = '\0';
/* Check if the message starts with the i3 IPC magic code */
if (n < strlen(I3_IPC_MAGIC)) {
LOG("IPC: message too short, ignoring\n");
return;
}
if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
LOG("IPC: message does not start with the IPC magic\n");
return;
}
uint8_t *message = (uint8_t*)buf;
message += strlen(I3_IPC_MAGIC);
n -= strlen(I3_IPC_MAGIC);
/* The next 32 bit after the magic are the message size */
uint32_t message_size = *((uint32_t*)message);
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
/* The last 32 bits of the header are the message type */
uint32_t message_type = *((uint32_t*)message);
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
ipc_handle_message(message, n, message_size, message_type);
}
/*
* Handler for activity on the listening socket, meaning that a new client
* has just connected and we should accept() him. Sets up the event handler
* for activity on the new connection and inserts the file descriptor into
* the list of clients.
*
*/
void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
struct sockaddr_un peer;
socklen_t len = sizeof(struct sockaddr_un);
int client;
if ((client = accept(w->fd, (struct sockaddr*)&peer, &len)) < 0) {
if (errno == EINTR)
return;
else perror("accept()");
return;
}
set_nonblock(client);
struct ev_io *package = calloc(sizeof(struct ev_io), 1);
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
LOG("IPC: new client connected\n");
struct ipc_client *new = calloc(sizeof(struct ipc_client), 1);
new->fd = client;
TAILQ_INSERT_TAIL(&all_clients, new, clients);
}
/*
* Creates the UNIX domain socket at the given path, sets it to non-blocking
* mode, bind()s and listen()s on it.
*
*/
int ipc_create_socket(const char *filename) {
int sockfd;
/* Unlink the unix domain socket before */
unlink(filename);
if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
perror("socket()");
return -1;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, filename);
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
perror("bind()");
return -1;
}
set_nonblock(sockfd);
if (listen(sockfd, 5) < 0) {
perror("listen()");
return -1;
}
return sockfd;
}

View File

@ -103,19 +103,26 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
i3Font *font = load_font(conn, config.font);
int decoration_height = font->height + 2 + 2;
struct Colortriple *color;
Client *last_focused;
/* Clients without a container (docks) wont get decorated */
if (client->dock)
return;
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 (client_is_floating(client) || CUR_CELL->currently_focused == client)
last_focused = SLIST_FIRST(&(client->workspace->focus_stack));
if (client_is_floating(client)) {
if (last_focused == client)
color = &(config.client.focused);
/* …or if it is the focused window in a not focused container */
else color = &(config.client.focused_inactive);
} else color = &(config.client.unfocused);
else color = &(config.client.unfocused);
} else {
if (client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
if (last_focused == client && c_ws == client->workspace)
color = &(config.client.focused);
/* …or if it is the focused window in a not focused container */
else color = &(config.client.focused_inactive);
} else color = &(config.client.unfocused);
}
/* Our plan is the following:
- Draw a rect around the whole client in color->background
@ -145,10 +152,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
}
/* Draw the lines */
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 (client->titlebar_position != TITLEBAR_OFF) {
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
if ((client->container == NULL ||
client->container->mode != MODE_STACK ||
CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL))
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) {
@ -196,20 +208,26 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
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
* Pushes the clients width/height to X11 and resizes the child window. This
* function also updates the clients position, so if you work on tiling clients
* only, you can use this function instead of separate calls to reposition_client
* and resize_client to reduce flickering.
*
*/
void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font);
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.width));
XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.x));
/* Adjust the position of the child inside its frame.
* The coordinates of the child are relative to its frame, we
@ -227,11 +245,16 @@ void resize_client(xcb_connection_t *conn, Client *client) {
rect->height = client->rect.height - 2;
break;
default:
if (client->titlebar_position == TITLEBAR_OFF) {
if (client->titlebar_position == TITLEBAR_OFF && client->borderless) {
rect->x = 0;
rect->y = 0;
rect->width = client->rect.width;
rect->height = client->rect.height;
} else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
rect->x = 1;
rect->y = 1;
rect->width = client->rect.width - 1 - 1;
rect->height = client->rect.height - 1 - 1;
} else {
rect->x = 2;
rect->y = font->height + 2 + 2;
@ -263,6 +286,20 @@ void resize_client(xcb_connection_t *conn, Client *client) {
LOG("new_height = %f, new_width = %d\n", new_height, new_width);
}
if (client->height_increment > 1) {
int old_height = rect->height;
rect->height = ((int)(rect->height / client->height_increment) * client->height_increment) + 1;
LOG("Lost %d pixel due to client's height_increment (%d px)\n",
old_height - rect->height, client->height_increment);
}
if (client->width_increment > 1) {
int old_width = rect->width;
rect->width = ((int)(rect->width / client->width_increment) * client->width_increment) + 1;
LOG("Lost %d pixel due to client's width_increment (%d px)\n",
old_width - rect->width, client->width_increment);
}
LOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
xcb_configure_window(conn, client->child, mask, &(rect->x));
@ -282,14 +319,10 @@ void render_container(xcb_connection_t *conn, Container *container) {
Client *client;
int num_clients = 0, current_client = 0;
if (container->currently_focused == NULL)
return;
CIRCLEQ_FOREACH(client, &(container->clients), clients)
num_clients++;
if (container->mode == MODE_DEFAULT) {
LOG("got %d clients in this default container.\n", num_clients);
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
if (container->workspace->fullscreen_client == client) {
@ -302,15 +335,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y +
(container->height / num_clients) * current_client))
reposition_client(conn, client);
/* TODO: vertical default layout */
if (client->force_reconfigure |
(container->height / num_clients) * current_client) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height / num_clients))
resize_client(conn, client);
/* TODO: vertical default layout */
client->force_reconfigure = false;
current_client++;
@ -322,7 +353,7 @@ void render_container(xcb_connection_t *conn, Container *container) {
/* Check if we need to remap our stack title window, it gets unmapped when the container
is empty in src/handlers.c:unmap_notify() */
if (stack_win->rect.height == 0)
if (stack_win->rect.height == 0 && num_clients > 0)
xcb_map_window(conn, stack_win->window);
/* Check if we need to reconfigure our stack title window */
@ -362,6 +393,9 @@ void render_container(xcb_connection_t *conn, Container *container) {
xcb_configure_window(conn, stack_win->window, mask, values);
}
/* Prepare the pixmap for usage */
cached_pixmap_prepare(conn, &(stack_win->pixmap));
/* Render the decorations of all clients */
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
@ -374,19 +408,19 @@ void render_container(xcb_connection_t *conn, Container *container) {
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y + (decoration_height * num_clients)))
reposition_client(conn, client);
if (client->force_reconfigure |
update_if_necessary(&(client->rect.y), container->y + (decoration_height * num_clients)) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height - (decoration_height * num_clients)))
resize_client(conn, client);
client->force_reconfigure = false;
decorate_window(conn, client, stack_win->window, stack_win->gc,
decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc,
current_client++ * decoration_height);
}
xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc,
0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height);
}
}
@ -395,7 +429,7 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int
SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) {
LOG("client is at %d, should be at %d\n", client->rect.y, *height);
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), 0) |
update_if_necessary(&(client->rect.x), r_ws->rect.x) |
update_if_necessary(&(client->rect.y), *height))
reposition_client(conn, client);
@ -411,11 +445,9 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int
}
static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
LOG("Rendering internal bar\n");
i3Font *font = load_font(conn, config.font);
i3Screen *screen = r_ws->screen;
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
char label[3];
/* Fill the whole bar in black */
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
@ -432,21 +464,29 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) :
&(config.bar.unfocused));
Workspace *ws = &workspaces[c];
/* Draw the outer rect */
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, color->background,
drawn * height + 1, 2, height - 4, height - 4);
drawn, /* x */
1, /* y */
ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */
height - 2 /* height = max. height - 1 px upper and 1 px bottom border */);
/* Draw the background of this rect */
xcb_draw_rect(conn, screen->bar, screen->bargc, color->background,
drawn + 1,
2,
ws->text_width + 4 + 4,
height - 4);
snprintf(label, sizeof(label), "%d", c+1);
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++;
xcb_image_text_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */,
font->height + 1 /* Y = baseline of font */,
(xcb_char2b_t*)ws->name);
drawn += ws->text_width + 12;
}
LOG("done rendering internal\n");
}
/*
@ -459,8 +499,6 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
Client *client;
uint32_t values[1];
LOG("Ignore enter_notify = %d\n", ignore_enter_notify);
FOR_TABLE(workspace)
CIRCLEQ_FOREACH(client, &(workspace->table[cols][rows]->clients), clients) {
/* Change event mask for the decorations */
@ -494,8 +532,6 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
/* Space for the internal bar */
height -= (font->height + 6);
LOG("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
int xoffset[r_ws->rows];
int yoffset[r_ws->cols];
/* Initialize offsets */
@ -504,19 +540,12 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
for (int rows = 0; rows < r_ws->rows; rows++)
xoffset[rows] = r_ws->rect.x;
dump_table(conn, r_ws);
ignore_enter_notify_forall(conn, r_ws, true);
/* Go through the whole table and render whats necessary */
FOR_TABLE(r_ws) {
Container *container = r_ws->table[cols][rows];
int single_width = -1, single_height;
LOG("\n");
LOG("========\n");
LOG("container has %d colspan, %d rowspan\n",
container->colspan, container->rowspan);
LOG("container at %d, %d\n", xoffset[rows], yoffset[cols]);
/* Update position of the container */
container->row = rows;
container->col = cols;
@ -544,7 +573,6 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
xoffset[rows] += single_width;
yoffset[cols] += single_height;
LOG("==========\n");
}
ignore_enter_notify_forall(conn, r_ws, false);
@ -564,10 +592,8 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
void render_layout(xcb_connection_t *conn) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
LOG("Rendering screen %d\n", screen->num);
TAILQ_FOREACH(screen, virtual_screens, screens)
render_workspace(conn, screen, &(workspaces[screen->current_workspace]));
}
xcb_flush(conn);
}

View File

@ -18,6 +18,7 @@
#include <assert.h>
#include <limits.h>
#include <locale.h>
#include <fcntl.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
@ -45,6 +46,9 @@
#include "xcb.h"
#include "xinerama.h"
#include "manage.h"
#include "ipc.h"
xcb_connection_t *global_conn;
/* This is the path to i3, copied from argv[0] when starting up */
char **start_argv;
@ -52,6 +56,8 @@ char **start_argv;
/* This is our connection to X11 for use with XKB */
Display *xkbdpy;
xcb_key_symbols_t *keysyms;
/* The list of key bindings */
struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
@ -69,8 +75,12 @@ struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
xcb_event_handlers_t evenths;
xcb_atom_t atoms[NUM_ATOMS];
xcb_window_t root;
int num_screens = 0;
/* The depth of the root screen (used e.g. for creating new pixmaps later) */
uint8_t root_depth;
/*
* 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
@ -102,13 +112,40 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
}
}
/*
* When using xmodmap to change the keyboard mapping, this event
* is only sent via XKB. Therefore, we need this special handler.
*
*/
static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
LOG("got xkb event, yay\n");
XEvent ev;
/* When using xmodmap, every change (!) gets an own event.
* Therefore, we just read all events and only handle the
* mapping_notify once (we do not receive any other XKB
* events anyway). */
while (XPending(xkbdpy))
XNextEvent(xkbdpy, &ev);
xcb_key_symbols_free(keysyms);
keysyms = xcb_key_symbols_alloc(global_conn);
xcb_get_numlock_mask(global_conn);
ungrab_all_keys(global_conn);
LOG("Re-grabbing...\n");
grab_all_keys(global_conn);
LOG("Done\n");
}
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;
xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
setlocale(LC_ALL, "");
@ -145,12 +182,12 @@ int main(int argc, char *argv[], char *env[]) {
memset(&evenths, 0, sizeof(xcb_event_handlers_t));
memset(&prophs, 0, sizeof(xcb_property_handlers_t));
conn = xcb_connect(NULL, &screens);
conn = global_conn = xcb_connect(NULL, &screens);
if (xcb_connection_has_error(conn))
die("Cannot open display\n");
load_configuration(conn, override_configpath);
load_configuration(conn, override_configpath, false);
/* 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);
@ -172,6 +209,7 @@ int main(int argc, char *argv[], char *env[]) {
REQUEST_ATOM(WM_DELETE_WINDOW);
REQUEST_ATOM(UTF8_STRING);
REQUEST_ATOM(WM_STATE);
REQUEST_ATOM(WM_CLIENT_LEADER);
/* TODO: this has to be more beautiful somewhen */
int major, minor, error;
@ -186,6 +224,11 @@ int main(int argc, char *argv[], char *env[]) {
return 1;
}
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
return 1;
}
int i1;
if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n");
@ -193,18 +236,30 @@ int main(int argc, char *argv[], char *env[]) {
}
/* end of ugliness */
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
}
/* 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_io *xkb = scalloc(sizeof(struct ev_io));
struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(loop, xcb_watcher);
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
ev_io_start(loop, xkb);
/* Flush the buffer so that libev can properly get new events */
XFlush(xkbdpy);
ev_check_init(xcb_check, xcb_check_cb);
ev_check_start(loop, xcb_check);
@ -250,6 +305,13 @@ int main(int argc, char *argv[], char *env[]) {
/* Configure request = window tried to change size on its own */
xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
/* Motion notify = user moved his cursor (over the root window and may
* cross virtual screen boundaries doing that) */
xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL);
/* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */
xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL);
/* Client message are sent to the root window. The only interesting client message
for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */
xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
@ -261,13 +323,16 @@ int main(int argc, char *argv[], char *env[]) {
xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
/* Get the root window and set the event mask */
root = xcb_aux_get_screen(conn, screens)->root;
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
root = root_screen->root;
root_depth = root_screen->root_depth;
uint32_t mask = XCB_CW_EVENT_MASK;
uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
projector), the root window gets a
ConfigureNotify */
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW };
xcb_change_window_attributes(conn, root, mask, values);
@ -300,6 +365,7 @@ int main(int argc, char *argv[], char *env[]) {
GET_ATOM(WM_DELETE_WINDOW);
GET_ATOM(UTF8_STRING);
GET_ATOM(WM_STATE);
GET_ATOM(WM_CLIENT_LEADER);
xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
/* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
@ -316,6 +382,9 @@ int main(int argc, char *argv[], char *env[]) {
/* Watch WM_CLASS (= class of the window) */
xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL);
/* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */
xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL);
/* 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");
@ -323,23 +392,11 @@ int main(int argc, char *argv[], char *env[]) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
keysyms = xcb_key_symbols_alloc(conn);
xcb_get_numlock_mask(conn);
/* Grab the bound keys */
Binding *bind;
TAILQ_FOREACH(bind, &bindings, bindings) {
LOG("Grabbing %d\n", bind->keycode);
if (bind->mods & BIND_MODE_SWITCH)
xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
else {
/* Grab the key in all combinations */
#define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC)
GRAB_KEY(bind->mods);
GRAB_KEY(bind->mods | xcb_numlock_mask);
GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
}
}
grab_all_keys(conn);
/* Autostarting exec-lines */
struct Autostart *exec;
@ -356,8 +413,6 @@ int main(int argc, char *argv[], char *env[]) {
xcb_flush(conn);
manage_existing_windows(conn, &prophs, root);
/* Get pointer position to see on which screen were starting */
xcb_query_pointer_reply_t *reply;
if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
@ -370,9 +425,22 @@ int main(int argc, char *argv[], char *env[]) {
LOG("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
return 0;
}
if (screen->current_workspace != 0) {
LOG("Ok, I need to go to the other workspace\n");
c_ws = &workspaces[screen->current_workspace];
LOG("Starting on %d\n", screen->current_workspace);
c_ws = &workspaces[screen->current_workspace];
manage_existing_windows(conn, &prophs, root);
/* Create the UNIX domain socket for IPC */
if (config.ipc_socket_path != NULL) {
int ipc_socket = ipc_create_socket(config.ipc_socket_path);
if (ipc_socket == -1) {
LOG("Could not create the IPC socket, IPC disabled\n");
} else {
struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
ev_io_start(loop, ipc_io);
}
}
/* Handle the events which arrived until now */

View File

@ -29,6 +29,7 @@
#include "manage.h"
#include "floating.h"
#include "client.h"
#include "workspace.h"
/*
* Go through all existing windows (if the window manager is restarted) and manage them
@ -53,10 +54,8 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
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);
}
for(i = 0; i < len; ++i)
manage_window(prophs, conn, children[i], cookies[i], true);
free(reply);
free(cookies);
@ -66,28 +65,28 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
* 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");
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
bool needs_to_be_mapped) {
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;
geomc = xcb_get_geometry(conn, d);
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
goto out;
wa.tag = TAG_VALUE;
wa.u.override_redirect = attr->override_redirect;
/* 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, cookie, 0)) == NULL) {
LOG("Could not get attributes\n");
return;
}
if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE)
goto out;
/* Dont manage clients with the override_redirect flag */
if (wa.u.override_redirect)
if (attr->override_redirect)
goto out;
/* Check if the window is already managed */
@ -95,13 +94,6 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_
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;
@ -114,6 +106,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_
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[WM_CLIENT_LEADER]);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
free(geom);
@ -134,33 +127,33 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
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;
utf8_title_cookie, title_cookie,
class_cookie, leader_cookie;
uint32_t mask = 0;
uint32_t values[3];
uint16_t original_height = height;
bool map_frame = true;
/* 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);
leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
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);
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("Reparenting window 0x%08x\n", child);
LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
new = calloc(sizeof(Client), 1);
new->force_reconfigure = true;
@ -179,6 +172,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
new->child = child;
new->rect.width = width;
new->rect.height = height;
new->width_increment = 1;
new->height_increment = 1;
/* Pre-initialize the values for floating */
new->floating_rect.x = -1;
new->floating_rect.width = width;
@ -194,8 +189,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
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);
@ -205,12 +198,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
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);
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
/* 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
@ -253,11 +241,13 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
LOG("Window is a dock.\n");
new->dock = true;
new->borderless = 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 */
new->floating = FLOATING_AUTO_OFF;
break;
} else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
@ -269,6 +259,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
}
}
/* All clients which have a leader should be floating */
if (!new->dock && !client_is_floating(new) && new->leader != 0) {
LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
new->floating = FLOATING_AUTO_ON;
}
if (new->workspace->auto_float) {
new->floating = FLOATING_AUTO_ON;
LOG("workspace is in autofloat mode, setting floating\n");
@ -309,16 +305,20 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
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");
preply = xcb_get_property_reply(conn, leader_cookie, NULL);
handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
struct Assignment *assign;
TAILQ_FOREACH(assign, &assignments, assignments) {
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
continue;
if (assign->floating) {
if (assign->floating == ASSIGN_FLOATING_ONLY ||
assign->floating == ASSIGN_FLOATING) {
new->floating = FLOATING_AUTO_ON;
LOG("Assignment matches, putting client into floating mode\n");
break;
if (assign->floating == ASSIGN_FLOATING_ONLY)
break;
}
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
@ -329,21 +329,15 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
break;
}
LOG("Changin container/workspace and unmapping the client\n");
LOG("Changing 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));
}
workspace_initialize(t_ws, c_ws->screen);
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
new->workspace = t_ws;
old_focused = new->container->currently_focused;
if (t_ws->screen->current_workspace != t_ws->num)
xcb_unmap_window(conn, new->frame);
map_frame = workspace_is_visible(t_ws);
break;
}
}
@ -354,14 +348,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
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 */
@ -387,10 +373,27 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
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;
/* Some clients (like GIMPs color picker window) get mapped
* to (0, 0), so we push them to a reasonable position
* (centered over their leader) */
if (new->leader != 0 && x == 0 && y == 0) {
LOG("Floating client wants to (0x0), moving it over its leader instead\n");
Client *leader = table_get(&by_child, new->leader);
if (leader == NULL) {
LOG("leader is NULL, centering it over current workspace\n");
x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
} else {
x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
}
}
new->floating_rect.x = new->rect.x = x;
new->floating_rect.y = new->rect.y = y;
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);
@ -423,4 +426,21 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
}
render_layout(conn);
/* Map the window first to avoid flickering */
xcb_map_window(conn, child);
if (map_frame)
client_map(conn, new);
if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) {
/* Focus the new window if were not in fullscreen mode and if it is not a dock window */
if (new->workspace->fullscreen_client == NULL) {
if (!client_is_floating(new))
new->container->currently_focused = new;
if (new->container == CUR_CELL || client_is_floating(new))
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
}
}
xcb_flush(conn);
}

View File

@ -43,6 +43,8 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
return 1;
}
LOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x);
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 */
@ -61,7 +63,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
/* Open a new window, the resizebar. Grab the pointer and move the window around
as the user moves the pointer. */
Rect grabrect = {0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels};
xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, mask, values);
xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, true, mask, values);
Rect helprect;
if (orientation == O_VERTICAL) {
@ -87,7 +89,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT,
(orientation == O_VERTICAL ?
XCB_CURSOR_SB_V_DOUBLE_ARROW :
XCB_CURSOR_SB_H_DOUBLE_ARROW), mask, values);
XCB_CURSOR_SB_H_DOUBLE_ARROW), true, mask, values);
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);

View File

@ -62,20 +62,6 @@ void slog(char *fmt, ...) {
va_end(args);
}
/*
* Prints the message (see printf()) to stderr, then exits the program.
*
*/
void die(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(EXIT_FAILURE);
}
/*
* The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of
* the called functions returns NULL, meaning that there is no more memory available
@ -83,19 +69,19 @@ void die(char *fmt, ...) {
*/
void *smalloc(size_t size) {
void *result = malloc(size);
exit_if_null(result, "Too less memory for malloc(%d)\n", size);
exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size);
return result;
}
void *scalloc(size_t size) {
void *result = calloc(size, 1);
exit_if_null(result, "Too less memory for calloc(%d)\n", size);
exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size);
return result;
}
char *sstrdup(const char *str) {
char *result = strdup(str);
exit_if_null(result, "Too less memory for strdup()\n");
exit_if_null(result, "Error: out of memory (strdup())\n");
return result;
}
@ -238,65 +224,6 @@ Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Cl
return NULL;
}
/*
* Unmaps all clients (and stack windows) of the given workspace.
*
* This needs to be called separately when temporarily rendering
* a workspace which is not the active workspace to force
* reconfiguration of all clients, like in src/xinerama.c when
* re-assigning a workspace to another screen.
*
*/
void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) {
Client *client;
struct Stack_Window *stack_win;
/* Ignore notify events because they would cause focus to be changed */
ignore_enter_notify_forall(conn, u_ws, true);
/* 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++;
}
/* To find floating clients, we traverse the focus stack */
SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
if (!client_is_floating(client))
continue;
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);
ignore_enter_notify_forall(conn, u_ws, false);
}
/*
* Sets the given client as focused by updating the data structures correctly,
@ -313,10 +240,8 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
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)) {
LOG("old_client == client, not changing focus\n");
if (!set_anyways && (old_client == client))
return;
}
/* Store current_row/current_col */
c_ws->current_row = current_row;
@ -334,7 +259,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
current_row = client->container->row;
}
LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name);
CLIENT_LOG(client);
/* 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);
@ -362,11 +287,6 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
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);
/* If the last client was a floating client, we need to go to the next
* tiling client in stack and re-decorate it. */
if (old_client != NULL && client_is_floating(old_client)) {
@ -386,6 +306,11 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
/* 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);
/* redecorate_window flushes, so we dont need to */
redecorate_window(conn, client);
}
@ -401,7 +326,8 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container) {
SLIST_REMOVE(&stack_wins, stack_win, Stack_Window, stack_windows);
xcb_free_gc(conn, stack_win->gc);
xcb_free_gc(conn, stack_win->pixmap.gc);
xcb_free_pixmap(conn, stack_win->pixmap.id);
xcb_destroy_window(conn, stack_win->window);
stack_win->rect.width = -1;
@ -437,11 +363,15 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
XCB_EVENT_MASK_EXPOSURE; /* …our window needs to be redrawn */
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
/* Generate a graphics context for the titlebar */
stack_win->gc = xcb_generate_id(conn);
xcb_create_gc(conn, stack_win->gc, stack_win->window, 0, 0);
stack_win->rect.height = 0;
/* Initialize the entry for our cached pixmap. It will be
* created as soon as its needed (see cached_pixmap_prepare). */
memset(&(stack_win->pixmap), 0, sizeof(struct Cached_Pixmap));
stack_win->pixmap.referred_rect = &stack_win->rect;
stack_win->pixmap.referred_drawable = stack_win->window;
stack_win->container = container;

353
src/workspace.c Normal file
View File

@ -0,0 +1,353 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* workspace.c: Functions for modifying workspaces
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <err.h>
#include "util.h"
#include "data.h"
#include "i3.h"
#include "config.h"
#include "xcb.h"
#include "table.h"
#include "xinerama.h"
#include "layout.h"
#include "workspace.h"
#include "client.h"
/*
* Sets the name (or just its number) for the given workspace. This has to
* be called for every workspace as the rendering function
* (render_internal_bar) relies on workspace->name and workspace->name_len
* being ready-to-use.
*
*/
void workspace_set_name(Workspace *ws, const char *name) {
char *label;
int ret;
if (name != NULL)
ret = asprintf(&label, "%d: %s", ws->num + 1, name);
else ret = asprintf(&label, "%d", ws->num + 1);
if (ret == -1)
errx(1, "asprintf() failed");
FREE(ws->name);
ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
if (config.font != NULL)
ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
else ws->text_width = 0;
free(label);
}
/*
* Returns true if the workspace is currently visible. Especially important for
* multi-monitor environments, as they can have multiple currenlty active
* workspaces.
*
*/
bool workspace_is_visible(Workspace *ws) {
return (ws->screen->current_workspace == ws->num);
}
/*
* Switches to the given workspace
*
*/
void workspace_show(xcb_connection_t *conn, int workspace) {
bool need_warp = false;
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
/* t_ws (to workspace) is just a convenience pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]);
LOG("show_workspace(%d)\n", workspace);
/* Store current_row/current_col */
c_ws->current_row = current_row;
c_ws->current_col = current_col;
/* Check if the workspace has not been used yet */
workspace_initialize(t_ws, c_ws->screen);
if (c_ws->screen != t_ws->screen) {
/* We need to switch to the other screen first */
LOG("moving over to other screen.\n");
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
c_ws = &(workspaces[t_ws->screen->current_workspace]);
current_col = c_ws->current_col;
current_row = c_ws->current_row;
if (CUR_CELL->currently_focused != NULL)
need_warp = true;
else {
Rect *dims = &(c_ws->screen->rect);
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
dims->x + (dims->width / 2), dims->y + (dims->height / 2));
}
/* Re-decorate the old client, its not focused anymore */
if ((old_client != NULL) && !old_client->dock)
redecorate_window(conn, old_client);
else xcb_flush(conn);
}
/* Check if we need to change something or if were already there */
if (c_ws->screen->current_workspace == (workspace-1)) {
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) {
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
}
return;
}
t_ws->screen->current_workspace = workspace-1;
Workspace *old_workspace = c_ws;
c_ws = &workspaces[workspace-1];
/* Unmap all clients of the old workspace */
workspace_unmap_clients(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);
workspace_map_clients(conn, c_ws);
/* Restore focus on the new workspace */
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) {
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);
render_layout(conn);
}
/*
* Parses the preferred_screen property of a workspace. You can either specify
* the screen number (it is not given that the screen numbering always stays
* the same) or the screen coordinates (exact coordinates, e.g. 1280 will match
* the screen starting at x=1280, but 1281 will not). For coordinates, you can
* either specify an x coordinate ("1280") or an y coordinate ("x800") or both
* ("1280x800").
*
*/
static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) {
i3Screen *screen;
char *rest;
int preferred_screen = strtol(preference, &rest, 10);
LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen);
if ((rest == preference) || (preferred_screen >= num_screens)) {
int x = INT_MAX, y = INT_MAX;
if (strchr(preference, 'x') != NULL) {
/* Check if only the y coordinate was specified */
if (*preference == 'x')
y = atoi(preference+1);
else {
x = atoi(preference);
y = atoi(strchr(preference, 'x') + 1);
}
} else {
x = atoi(preference);
}
LOG("Looking for screen at %d x %d\n", x, y);
TAILQ_FOREACH(screen, slist, screens)
if ((x == INT_MAX || screen->rect.x == x) &&
(y == INT_MAX || screen->rect.y == y)) {
LOG("found %p\n", screen);
return screen;
}
LOG("none found\n");
return NULL;
} else {
int c = 0;
TAILQ_FOREACH(screen, slist, screens)
if (c++ == preferred_screen)
return screen;
}
return NULL;
}
/*
* Initializes the given workspace if it is not already initialized. The given
* screen is to be understood as a fallback, if the workspace itself either
* was not assigned to a particular screen or cannot be placed there because
* the screen is not attached at the moment.
*
*/
void workspace_initialize(Workspace *ws, i3Screen *screen) {
if (ws->screen != NULL) {
LOG("Workspace already initialized\n");
return;
}
/* If this workspace has no preferred screen or if the screen it wants
* to be on is not available at the moment, we initialize it with
* the screen which was given */
if (ws->preferred_screen == NULL ||
(ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL)
ws->screen = screen;
else { LOG("yay, found assignment\n"); }
/* Copy the dimensions from the virtual screen */
memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect));
}
/*
* Gets the first unused workspace for the given screen, taking into account
* the preferred_screen setting of every workspace (workspace assignments).
*
*/
Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) {
Workspace *result = NULL;
for (int c = 0; c < 10; c++) {
Workspace *ws = &(workspaces[c]);
if (ws->preferred_screen == NULL ||
!screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen))
continue;
result = ws;
break;
}
if (result == NULL) {
/* No assignment found, returning first unused workspace */
for (int c = 0; c < 10; c++) {
if (workspaces[c].screen != NULL)
continue;
result = &(workspaces[c]);
break;
}
}
if (result != NULL) {
workspace_initialize(result, screen);
return result;
}
LOG("WARNING: No free workspace found to assign!\n");
return NULL;
}
/*
* Maps all clients (and stack windows) of the given workspace.
*
*/
void workspace_map_clients(xcb_connection_t *conn, Workspace *ws) {
Client *client;
ignore_enter_notify_forall(conn, ws, true);
/* Map all clients on the new workspace */
FOR_TABLE(ws)
CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
client_map(conn, client);
/* Map all floating clients */
if (!ws->floating_hidden)
TAILQ_FOREACH(client, &(ws->floating_clients), floating_clients)
client_map(conn, client);
/* Map all stack windows, if any */
struct Stack_Window *stack_win;
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
if (stack_win->container->workspace == ws)
xcb_map_window(conn, stack_win->window);
ignore_enter_notify_forall(conn, ws, false);
}
/*
* Unmaps all clients (and stack windows) of the given workspace.
*
* This needs to be called separately when temporarily rendering
* a workspace which is not the active workspace to force
* reconfiguration of all clients, like in src/xinerama.c when
* re-assigning a workspace to another screen.
*
*/
void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
Client *client;
struct Stack_Window *stack_win;
/* Ignore notify events because they would cause focus to be changed */
ignore_enter_notify_forall(conn, u_ws, true);
/* 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);
client_unmap(conn, client);
unmapped_clients++;
}
/* To find floating clients, we traverse the focus stack */
SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
if (!client_is_floating(client))
continue;
LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
client_unmap(conn, client);
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);
ignore_enter_notify_forall(conn, u_ws, false);
}

108
src/xcb.c
View File

@ -18,6 +18,7 @@
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#include "i3.h"
#include "util.h"
#include "xcb.h"
@ -89,7 +90,7 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
*
*/
xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class, int cursor,
uint32_t mask, uint32_t *values) {
bool map, uint32_t mask, uint32_t *values) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
xcb_window_t result = xcb_generate_id(conn);
xcb_cursor_t cursor_id = xcb_generate_id(conn);
@ -120,7 +121,8 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
/* Map the window (= make it visible) */
xcb_map_window(conn, result);
if (map)
xcb_map_window(conn, result);
return result;
}
@ -179,8 +181,6 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window)
xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&generated_event);
xcb_flush(conn);
LOG("Told the client it is at %dx%d with %dx%d\n", r.x, r.y, r.width, r.height);
}
/*
@ -259,3 +259,103 @@ 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);
}
/*
*
* Prepares the given Cached_Pixmap for usage (checks whether the size of the
* object this pixmap is related to (e.g. a window) has changed and re-creates
* the pixmap if so).
*
*/
void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) {
LOG("preparing pixmap\n");
/* If the Rect did not change, the pixmap does not need to be recreated */
if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0)
return;
memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect));
if (pixmap->id == 0 || pixmap->gc == 0) {
LOG("Creating new pixmap...\n");
pixmap->id = xcb_generate_id(conn);
pixmap->gc = xcb_generate_id(conn);
} else {
LOG("Re-creating this pixmap...\n");
xcb_free_gc(conn, pixmap->gc);
xcb_free_pixmap(conn, pixmap->id);
}
xcb_create_pixmap(conn, root_depth, pixmap->id,
pixmap->referred_drawable, pixmap->rect.width, pixmap->rect.height);
xcb_create_gc(conn, pixmap->gc, pixmap->id, 0, 0);
}
/*
* Returns the xcb_charinfo_t for the given character (specified by row and
* column in the lookup table) if existing, otherwise the minimum bounds.
*
*/
static xcb_charinfo_t *get_charinfo(int col, int row, xcb_query_font_reply_t *font_info,
xcb_charinfo_t *table, bool dont_fallback) {
xcb_charinfo_t *result;
/* Bounds checking */
if (row < font_info->min_byte1 || row > font_info->max_byte1 ||
col < font_info->min_char_or_byte2 || col > font_info->max_char_or_byte2)
return NULL;
/* If we dont have a table to lookup the infos per character, return the
* minimum bounds */
if (table == NULL)
return &font_info->min_bounds;
result = &table[((row - font_info->min_byte1) *
(font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
(col - font_info->min_char_or_byte2)];
/* If the character has an entry in the table, return it */
if (result->character_width != 0 ||
(result->right_side_bearing |
result->left_side_bearing |
result->ascent |
result->descent) != 0)
return result;
/* Otherwise, get the default character and return its charinfo */
if (dont_fallback)
return NULL;
return get_charinfo((font_info->default_char >> 8),
(font_info->default_char & 0xFF),
font_info,
table,
true);
}
/*
* Calculate the width of the given text (16-bit characters, UCS) with given
* real length (amount of glyphs) using the given font.
*
*/
int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length) {
xcb_query_font_reply_t *font_info;
xcb_charinfo_t *table;
int i, width = 0;
i3Font *font = load_font(conn, font_pattern);
font_info = xcb_query_font_reply(conn, xcb_query_font_unchecked(conn, font->id), NULL);
table = xcb_query_font_char_infos(font_info);
for (i = 0; i < 2 * length; i += 2) {
xcb_charinfo_t *info = get_charinfo(text[i+1], text[i], font_info, table, false);
if (info == NULL)
continue;
width += info->character_width;
}
free(font_info);
return width;
}

View File

@ -27,6 +27,7 @@
#include "layout.h"
#include "xcb.h"
#include "config.h"
#include "workspace.h"
/* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens
* (xrandr --same-as) */
@ -34,6 +35,25 @@ struct screens_head *virtual_screens;
static bool xinerama_enabled = true;
/*
* Returns true if both screen objects describe the same screen (checks their
* size and position).
*
*/
bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) {
/* If one of both objects (or both) are NULL, we cannot compare them */
if (screen1 == NULL || screen2 == NULL)
return false;
/* If the pointers are equal, take the short-circuit */
if (screen1 == screen2)
return true;
/* Compare their size - other properties are not relevant to determine
* if a screen is equal to another one */
return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0);
}
/*
* Looks in virtual_screens for the i3Screen whose start coordinates are x, y
*
@ -72,7 +92,7 @@ i3Screen *get_screen_containing(int x, int y) {
* This function always returns a screen.
*
*/
i3Screen *get_screen_most(direction_t direction) {
i3Screen *get_screen_most(direction_t direction, i3Screen *current) {
i3Screen *screen, *candidate = NULL;
int position = 0;
TAILQ_FOREACH(screen, virtual_screens, screens) {
@ -84,6 +104,14 @@ i3Screen *get_screen_most(direction_t direction) {
} \
break;
if (((direction == D_UP) || (direction == D_DOWN)) &&
(current->rect.x != screen->rect.x))
continue;
if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
(current->rect.y != screen->rect.y))
continue;
switch (direction) {
case D_UP:
WIN(screen->rect.y, <= position);
@ -114,14 +142,12 @@ static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspac
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->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, 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);
}
@ -142,7 +168,6 @@ static void disable_xinerama(xcb_connection_t *conn) {
num_screens = 1;
s->num = 0;
initialize_screen(conn, s, &(workspaces[0]));
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
@ -175,7 +200,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
for (int screen = 0; screen < screens; screen++) {
i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist);
if (s!= NULL) {
if (s != NULL) {
/* This screen already exists. We use the littlest screen so that the user
can always see the complete workspace */
s->rect.width = min(s->rect.width, screen_info[screen].width);
@ -235,13 +260,14 @@ void initialize_xinerama(xcb_connection_t *conn) {
FREE(reply);
i3Screen *s;
i3Screen *screen;
num_screens = 0;
/* Just go through each workspace and associate as many screens as we can. */
TAILQ_FOREACH(s, virtual_screens, screens) {
s->num = num_screens;
initialize_screen(conn, s, &(workspaces[num_screens]));
TAILQ_FOREACH(screen, virtual_screens, screens) {
screen->num = num_screens;
num_screens++;
Workspace *ws = get_first_workspace_for_screen(virtual_screens, screen);
initialize_screen(conn, screen, ws);
}
}
@ -268,65 +294,85 @@ void xinerama_requery_screens(xcb_connection_t *conn) {
query_screens(conn, new_screens);
i3Screen *first = TAILQ_FIRST(new_screens),
*screen;
*screen,
*old_screen;
int screen_count = 0;
/* Mark each workspace which currently is assigned to a screen, so we
* can garbage-collect afterwards */
for (int c = 0; c < 10; c++)
workspaces[c].reassigned = (workspaces[c].screen == NULL);
TAILQ_FOREACH(screen, new_screens, screens) {
screen->num = screen_count;
screen->current_workspace = -1;
for (int c = 0; c < 10; c++)
if ((workspaces[c].screen != NULL) &&
(workspaces[c].screen->num == screen_count)) {
LOG("Found a matching screen\n");
/* Try to use the same workspace, if its available */
if (workspaces[c].screen->current_workspace)
screen->current_workspace = workspaces[c].screen->current_workspace;
if (screen->current_workspace == -1)
screen->current_workspace = c;
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
if (old_screen->num != screen_count)
continue;
/* Re-use the old bar window */
screen->bar = workspaces[c].screen->bar;
screen->bargc = workspaces[c].screen->bargc;
LOG("Found a matching screen\n");
/* Use the same workspace */
screen->current_workspace = old_screen->current_workspace;
Rect bar_rect = {screen->rect.x,
screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
/* Re-use the old bar window */
screen->bar = old_screen->bar;
screen->bargc = old_screen->bargc;
LOG("old_screen->bar = %p\n", old_screen->bar);
xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x));
Rect bar_rect = {screen->rect.x,
screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
/* Copy the list head for the dock clients */
screen->dock_clients = workspaces[c].screen->dock_clients;
LOG("configuring bar to be at %d x %d with %d x %d\n",
bar_rect.x, bar_rect.y, bar_rect.height, bar_rect.width);
xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x));
/* Update the dimensions */
memcpy(&(workspaces[c].rect), &(screen->rect), sizeof(Rect));
workspaces[c].screen = screen;
/* Copy the list head for the dock clients */
screen->dock_clients = old_screen->dock_clients;
/* Update the dimensions */
for (int c = 0; c < 10; c++) {
Workspace *ws = &(workspaces[c]);
if (ws->screen != old_screen)
continue;
LOG("re-assigning ws %d\n", ws->num);
memcpy(&(ws->rect), &(screen->rect), sizeof(Rect));
ws->screen = screen;
ws->reassigned = true;
}
break;
}
if (screen->current_workspace == -1) {
/* Create a new workspace for this screen, its new */
for (int c = 0; c < 10; c++)
if (workspaces[c].screen == NULL) {
LOG("fix: initializing new workspace, setting num to %d\n", c);
initialize_screen(conn, screen, &(workspaces[c]));
break;
}
/* Find the first unused workspace, preferring the ones
* which are assigned to this screen and initialize
* the screen with it. */
LOG("getting first ws for screen %p\n", screen);
Workspace *ws = get_first_workspace_for_screen(new_screens, screen);
initialize_screen(conn, screen, ws);
/* As this workspace just got visible (we got a new screen
* without workspace), we need to map its clients */
workspace_map_clients(conn, ws);
}
screen_count++;
}
/* Check for workspaces which are out of bounds */
for (int c = 0; c < 10; c++) {
if ((workspaces[c].screen == NULL) || (workspaces[c].screen->num < num_screens))
if (workspaces[c].reassigned)
continue;
/* f_ws is a shortcut to the workspace to fix */
Workspace *f_ws = &(workspaces[c]);
Client *client;
LOG("Closing bar window\n");
LOG("Closing bar window (%p)\n", f_ws->screen->bar);
xcb_destroy_window(conn, f_ws->screen->bar);
LOG("Workspace %d's screen out of bounds, assigning to first screen\n", c+1);
@ -342,10 +388,15 @@ void xinerama_requery_screens(xcb_connection_t *conn) {
render_workspace(conn, first, f_ws);
/* …unless we want to see them at the moment, we should hide that workspace */
if (first->current_workspace == c)
if (workspace_is_visible(f_ws))
continue;
unmap_workspace(conn, f_ws);
workspace_unmap_clients(conn, f_ws);
if (c_ws == f_ws) {
LOG("Need to adjust c_ws...\n");
c_ws = &(workspaces[first->current_workspace]);
}
}
xcb_flush(conn);