*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type);
+/**
+ * Unmap the client, correctly setting any state which is needed.
+ *
+ */
+void client_unmap(xcb_connection_t *conn, Client *client);
+
+/**
+ * Map the client, correctly restoring any state needed.
+ *
+ */
+void client_map(xcb_connection_t *conn, Client *client);
+
#endif
bool focus_window_in_container(xcb_connection_t *conn, Container *container,
direction_t direction);
-/** Switches to the given workspace */
-void show_workspace(xcb_connection_t *conn, int workspace);
-
/** Parses a command, see file CMDMODE for more information */
void parse_command(xcb_connection_t *conn, const char *command);
*
*/
void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload);
+
+/**
+ * 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);
+
+/**
+ * Grab the bound keys (tell X to send us keypress events for those keycodes)
+ *
+ */
void grab_all_keys(xcb_connection_t *conn);
#endif
/** Are the floating clients on this workspace currently hidden? */
bool floating_hidden;
+ /** A <screen> specifier on which this workspace would like to be (if
+ * the screen is available). screen := <number> | <position> */
+ char *preferred_screen;
+
+ /** Temporary flag needed for re-querying xinerama screens */
+ bool reassigned;
+
/** the client who is started in fullscreen mode on this workspace,
* NULL if there is none */
Client *fullscreen_client;
*
*/
struct Binding {
+ /** Symbol the user specified in configfile, if any. This needs to be
+ * stored with the binding to be able to re-convert it into a keycode
+ * if the keyboard mapping changes (using Xmodmap for example) */
+ char *symbol;
+
+ /** Only in use if symbol != NULL. Gets set to the value to which the
+ * symbol got translated when binding. Useful for unbinding and
+ * checking which binding was used when a key press event comes in.
+ *
+ * This is an array of number_keycodes size. */
+ xcb_keycode_t *translated_to;
+
+ uint32_t number_keycodes;
+
/** Keycode to bind */
uint32_t keycode;
+
/** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
uint32_t mods;
+
/** Command, like in command mode */
char *command;
+
TAILQ_ENTRY(Binding) bindings;
};
int handle_enter_notify(void *ignored, xcb_connection_t *conn,
xcb_enter_notify_event_t *event);
+/**
+ * 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);
+
+/**
+ * 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);
+
/**
* Checks if the button press was on a stack window, handles focus setting and
* returns true if so, or false otherwise.
#include <xcb/xcb.h>
#include <xcb/xcb_property.h>
#include <xcb/xcb_event.h>
+#include <xcb/xcb_keysyms.h>
#include <X11/XKBlib.h>
#define NUM_ATOMS 18
extern xcb_connection_t *global_conn;
+extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
extern Display *xkbdpy;
extern TAILQ_HEAD(bindings_head, Binding) bindings;
void reposition_client(xcb_connection_t *conn, Client *client);
/**
- * Pushes the client’s width/height to X11 and resizes the child window
+ * Pushes the client’s width/height to X11 and resizes the child window. This
+ * function also updates the client’s 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);
Client *get_last_focused_client(xcb_connection_t *conn, Container *container,
Client *exclude);
-/**
- * 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);
-
-/**
- * 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);
-
/**
* Sets the given client as focused by updating the data structures correctly,
* updating the X input focus and finally re-decorating both windows (to
#include <xcb/xcb.h>
#include "data.h"
+#include "xinerama.h"
#ifndef _WORKSPACE_H
#define _WORKSPACE_H
*/
bool workspace_is_visible(Workspace *ws);
+/** Switches to the given workspace */
+void workspace_show(xcb_connection_t *conn, int workspace);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+
+void workspace_map_clients(xcb_connection_t *conn, Workspace *ws);
+
#endif
TAILQ_HEAD(screens_head, Screen);
extern struct screens_head *virtual_screens;
+/**
+ * Returns true if both screen objects describe the same screen (checks their
+ * size and position).
+ *
+ */
+bool screens_are_equal(i3Screen *screen1, i3Screen *screen2);
+
/**
* We have just established a connection to the X server and need the initial
* Xinerama information to setup workspaces for each screen.
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 don’t want to drag & drop if we don’t.
+ * 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);
+}
}
LOG("Switching to ws %d\n", target->current_workspace + 1);
- show_workspace(conn, target->current_workspace + 1);
+ workspace_show(conn, target->current_workspace + 1);
return;
}
/* If we’re moving it to an invisible screen, we need to unmap it */
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
/* If we’re moving it to an invisible screen, we need to unmap it */
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");
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 we’re 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, it’s 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 we’re 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
}
/* Move to the target workspace */
- show_workspace(conn, ws);
+ workspace_show(conn, ws);
if (result < 3)
return;
}
if (t_ws->screen != NULL)
- show_workspace(conn, i+1);
+ workspace_show(conn, i+1);
}
/*
if (*rest == '\0') {
/* No rest? This was a workspace number, not a times specification */
- show_workspace(conn, times);
+ workspace_show(conn, times);
return;
}
#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"
}
}
-/*
- * Ungrab the bound keys
+/**
+ * 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) {
- Binding *bind;
- TAILQ_FOREACH(bind, &bindings, bindings) {
- LOG("Ungrabbing %d\n", bind->keycode);
- xcb_ungrab_key(conn, bind->keycode, root, bind->keycode);
+ 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);
}
}
void grab_all_keys(xcb_connection_t *conn) {
Binding *bind;
TAILQ_FOREACH(bind, &bindings, bindings) {
- LOG("Grabbing %d\n", bind->keycode);
- if ((bind->mods & BIND_MODE_SWITCH) != 0)
- 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);
+ /* 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 */
+ LOG("Translating symbol to keycode (\"%s\")\n", bind->symbol);
+ 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;
+ 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("Got %d different keycodes\n", 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);
}
}
}
/* 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; \
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);
continue;
}
- /* name "workspace number" "name of the workspace" */
- if (strcasecmp(key, "name") == 0) {
- LOG("name workspace: %s\n",value);
+ /* 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)
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;
/* Set an empty name for every workspace which got no name */
for (int i = 0; i < 10; i++) {
- if (workspaces[i].name != NULL)
+ 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);
}
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
#include "client.h"
#include "manage.h"
#include "floating.h"
+#include "workspace.h"
/* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
since it’d trigger an infinite loop of switching between the different windows when
/* 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 event’s 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… */
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.
/* 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;
}
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) {
+ LOG("pointer motion notify, getting screen at %d x %d\n", event->root_x, event->root_y);
+
+ 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) {
+ LOG("\n\nmapping notify\n\n");
+
+ if (event->request != XCB_MAPPING_KEYBOARD &&
+ event->request != XCB_MAPPING_MODIFIER)
+ return 0;
+
+ xcb_refresh_keyboard_mapping(keysyms, event);
+
+ xcb_get_numlock_mask(conn);
+
+ ungrab_all_keys(conn);
+ LOG("Re-grabbing...\n");
+ grab_all_keys(conn);
+ LOG("Done\n");
+
+ return 0;
+}
+
/*
* Checks if the button press was on a stack window, handles focus setting and returns true
* if so, or false otherwise.
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;
i, drawn, workspaces[i].text_width);
if (event->event_x > (drawn + 1) &&
event->event_x <= (drawn + 1 + workspaces[i].text_width + 5 + 5)) {
- show_workspace(conn, i+1);
+ workspace_show(conn, i+1);
return true;
}
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);
} else {
if (client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
- if (last_focused == client)
+ 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);
if (client->titlebar_position != TITLEBAR_OFF) {
/* 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->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 */
}
/*
- * Pushes the client’s width/height to X11 and resizes the child window
+ * Pushes the client’s width/height to X11 and resizes the child window. This
+ * function also updates the client’s 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
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++;
* 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);
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);
#include <assert.h>
#include <limits.h>
#include <locale.h>
+#include <fcntl.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
/* 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);
}
}
+/*
+ * 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;
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");
}
/* 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);
/* 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);
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);
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_all_keys(conn);
xcb_flush(conn);
- manage_existing_windows(conn, &prophs, root);
-
/* Get pointer position to see on which screen we’re starting */
xcb_query_pointer_reply_t *reply;
if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
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) {
/* 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, false, mask, values);
- /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
- * Also, xprop(1) needs that to work. */
- long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
- xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
-
/* Put the client inside the save set. Upon termination (whether killed or normal exit
does not matter) of the window manager, these clients will be correctly reparented
to their most closest living ancestor (= cleanup) */
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;
/* Map the window first to avoid flickering */
xcb_map_window(conn, child);
- if (map_frame)
- xcb_map_window(conn, new->frame);
+ if (map_frame) {
+ LOG("Mapping client\n");
+ client_map(conn, new);
+ }
if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) {
/* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
if (new->workspace->fullscreen_client == NULL) {
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,
*/
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
#include <err.h>
#include "util.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
FREE(ws->name);
ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
- ws->text_width = predict_text_width(global_conn, config.font, ws->name, 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);
}
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 we’re 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, it’s 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 we’re 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);
+}
+
#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) */
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
*
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);
}
num_screens = 1;
s->num = 0;
- initialize_screen(conn, s, &(workspaces[0]));
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
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);
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);
}
}
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 it’s available */
- if (workspaces[c].screen->current_workspace)
- screen->current_workspace = workspaces[c].screen->current_workspace;
-
- if (screen->current_workspace == -1)
- screen->current_workspace = c;
-
- /* Re-use the old bar window */
- screen->bar = workspaces[c].screen->bar;
- screen->bargc = workspaces[c].screen->bargc;
-
- Rect bar_rect = {screen->rect.x,
- screen->rect.height - (font->height + 6),
- screen->rect.x + screen->rect.width,
- font->height + 6};
-
- 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));
-
- /* Copy the list head for the dock clients */
- screen->dock_clients = workspaces[c].screen->dock_clients;
-
- /* Update the dimensions */
- memcpy(&(workspaces[c].rect), &(screen->rect), sizeof(Rect));
- workspaces[c].screen = screen;
+
+ TAILQ_FOREACH(old_screen, virtual_screens, screens) {
+ if (old_screen->num != screen_count)
+ continue;
+
+ LOG("Found a matching screen\n");
+ /* Use the same workspace */
+ screen->current_workspace = old_screen->current_workspace;
+
+ /* 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);
+
+ Rect bar_rect = {screen->rect.x,
+ screen->rect.height - (font->height + 6),
+ screen->rect.x + screen->rect.width,
+ font->height + 6};
+
+ 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));
+
+ /* 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, it’s 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);
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);