Merge branch 'next' (3.β is stable now)
This commit is contained in:
250
src/client.c
Normal file
250
src/client.c
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* client.c: holds all client-specific functions
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
|
||||
#include "data.h"
|
||||
#include "i3.h"
|
||||
#include "xcb.h"
|
||||
#include "util.h"
|
||||
#include "queue.h"
|
||||
#include "layout.h"
|
||||
#include "client.h"
|
||||
|
||||
/*
|
||||
* Removes the given client from the container, either because it will be inserted into another
|
||||
* one or because it was unmapped
|
||||
*
|
||||
*/
|
||||
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
|
||||
CIRCLEQ_REMOVE(&(container->clients), client, clients);
|
||||
|
||||
if (remove_from_focusstack)
|
||||
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
|
||||
|
||||
/* If the container will be empty now and is in stacking mode, we need to
|
||||
unmap the stack_win */
|
||||
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
|
||||
struct Stack_Window *stack_win = &(container->stack_win);
|
||||
stack_win->rect.height = 0;
|
||||
xcb_unmap_window(conn, stack_win->window);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
|
||||
* selecting it
|
||||
*
|
||||
*/
|
||||
void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
|
||||
int mid_x = client->rect.width / 2,
|
||||
mid_y = client->rect.height / 2;
|
||||
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
|
||||
*
|
||||
*/
|
||||
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
|
||||
xcb_get_property_cookie_t cookie;
|
||||
xcb_get_wm_protocols_reply_t protocols;
|
||||
bool result = false;
|
||||
|
||||
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
|
||||
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
|
||||
return false;
|
||||
|
||||
/* Check if the client’s protocols have the requested atom set */
|
||||
for (uint32_t i = 0; i < protocols.atoms_len; i++)
|
||||
if (protocols.atoms[i] == atom)
|
||||
result = true;
|
||||
|
||||
xcb_get_wm_protocols_reply_wipe(&protocols);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
|
||||
*
|
||||
*/
|
||||
void client_kill(xcb_connection_t *conn, Client *window) {
|
||||
/* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
|
||||
if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
|
||||
LOG("Killing window the hard way\n");
|
||||
xcb_kill_client(conn, window->child);
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_client_message_event_t ev;
|
||||
|
||||
memset(&ev, 0, sizeof(xcb_client_message_event_t));
|
||||
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = window->child;
|
||||
ev.type = atoms[WM_PROTOCOLS];
|
||||
ev.format = 32;
|
||||
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
|
||||
ev.data.data32[1] = XCB_CURRENT_TIME;
|
||||
|
||||
LOG("Sending WM_DELETE to the client\n");
|
||||
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the given window class and title match the given client
|
||||
* Window title is passed as "normal" string and as UCS-2 converted string for
|
||||
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
|
||||
*
|
||||
*/
|
||||
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
|
||||
char *to_title_ucs, int to_title_ucs_len) {
|
||||
/* Check if the given class is part of the window class */
|
||||
if (client->window_class == NULL || strcasestr(client->window_class, to_class) == NULL)
|
||||
return false;
|
||||
|
||||
/* If no title was given, we’re done */
|
||||
if (to_title == NULL)
|
||||
return true;
|
||||
|
||||
if (client->name_len > -1) {
|
||||
/* UCS-2 converted window titles */
|
||||
if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
|
||||
return false;
|
||||
} else {
|
||||
/* Legacy hints */
|
||||
if (client->name == NULL || strcasestr(client->name, to_title) == NULL)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enters fullscreen mode for the given client. This is called by toggle_fullscreen
|
||||
* and when moving a fullscreen client to another screen.
|
||||
*
|
||||
*/
|
||||
void client_enter_fullscreen(xcb_connection_t *conn, Client *client) {
|
||||
Workspace *workspace = client->workspace;
|
||||
|
||||
if (workspace->fullscreen_client != NULL) {
|
||||
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
client->fullscreen = true;
|
||||
workspace->fullscreen_client = client;
|
||||
LOG("Entering fullscreen mode...\n");
|
||||
/* We just entered fullscreen mode, let’s configure the window */
|
||||
uint32_t mask = XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT;
|
||||
uint32_t values[4] = {workspace->rect.x,
|
||||
workspace->rect.y,
|
||||
workspace->rect.width,
|
||||
workspace->rect.height};
|
||||
|
||||
LOG("child itself will be at %dx%d with size %dx%d\n",
|
||||
values[0], values[1], values[2], values[3]);
|
||||
|
||||
xcb_configure_window(conn, client->frame, mask, values);
|
||||
|
||||
/* Child’s coordinates are relative to the parent (=frame) */
|
||||
values[0] = 0;
|
||||
values[1] = 0;
|
||||
xcb_configure_window(conn, client->child, mask, values);
|
||||
|
||||
/* Raise the window */
|
||||
values[0] = XCB_STACK_MODE_ABOVE;
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
||||
Rect child_rect = workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
fake_configure_notify(conn, child_rect, client->child);
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Toggles fullscreen mode for the given client. It updates the data structures and
|
||||
* reconfigures (= resizes/moves) the client and its frame to the full size of the
|
||||
* screen. When leaving fullscreen, re-rendering the layout is forced.
|
||||
*
|
||||
*/
|
||||
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
|
||||
/* dock clients cannot enter fullscreen mode */
|
||||
assert(!client->dock);
|
||||
|
||||
Workspace *workspace = client->workspace;
|
||||
|
||||
if (!client->fullscreen) {
|
||||
client_enter_fullscreen(conn, client);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("leaving fullscreen mode\n");
|
||||
client->fullscreen = false;
|
||||
workspace->fullscreen_client = NULL;
|
||||
if (client_is_floating(client)) {
|
||||
/* For floating clients it’s enough if we just reconfigure that window (in fact,
|
||||
* re-rendering the layout will not update the client.) */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
/* redecorate_window flushes */
|
||||
redecorate_window(conn, client);
|
||||
} else {
|
||||
client_set_below_floating(conn, client);
|
||||
|
||||
/* Because the coordinates of the window haven’t changed, it would not be
|
||||
re-configured if we don’t set the following flag */
|
||||
client->force_reconfigure = true;
|
||||
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
|
||||
render_layout(conn);
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the position of the given client in the X stack to the highest (tiling layer is always
|
||||
* on the same position, so this doesn’t matter) below the first floating client, so that
|
||||
* floating windows are always on top.
|
||||
*
|
||||
*/
|
||||
void client_set_below_floating(xcb_connection_t *conn, Client *client) {
|
||||
/* Ensure that it is below all floating clients */
|
||||
Client *first_floating = TAILQ_FIRST(&(client->workspace->floating_clients));
|
||||
if (first_floating != TAILQ_END(&(client->workspace->floating_clients))) {
|
||||
LOG("Setting below floating\n");
|
||||
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the client is floating. Makes the code more beatiful, as floating
|
||||
* is not simply a boolean, but also saves whether the user selected the current state
|
||||
* or whether it was automatically set.
|
||||
*
|
||||
*/
|
||||
bool client_is_floating(Client *client) {
|
||||
return (client->floating >= FLOATING_AUTO_ON);
|
||||
}
|
423
src/commands.c
423
src/commands.c
@ -23,6 +23,9 @@
|
||||
#include "layout.h"
|
||||
#include "i3.h"
|
||||
#include "xinerama.h"
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
#include "xcb.h"
|
||||
|
||||
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
|
||||
/* If this container is empty, we’re done */
|
||||
@ -57,10 +60,17 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
||||
|
||||
int new_row = current_row,
|
||||
new_col = current_col;
|
||||
|
||||
Container *container = CUR_CELL;
|
||||
Workspace *t_ws = c_ws;
|
||||
|
||||
/* Makes sure new_col and new_row are within bounds of the new workspace */
|
||||
void check_colrow_boundaries() {
|
||||
if (new_col >= t_ws->cols)
|
||||
new_col = (t_ws->cols - 1);
|
||||
if (new_row >= t_ws->rows)
|
||||
new_row = (t_ws->rows - 1);
|
||||
}
|
||||
|
||||
/* There always is a container. If not, current_col or current_row is wrong */
|
||||
assert(container != NULL);
|
||||
|
||||
@ -103,6 +113,21 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
||||
t_ws = &(workspaces[screen->current_workspace]);
|
||||
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
|
||||
}
|
||||
|
||||
check_colrow_boundaries();
|
||||
|
||||
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
|
||||
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
|
||||
LOG("Cell empty, checking for colspanned client above...\n");
|
||||
for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
|
||||
if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
|
||||
continue;
|
||||
|
||||
new_col = cols;
|
||||
break;
|
||||
}
|
||||
LOG("Fixed it to new col %d\n", new_col);
|
||||
}
|
||||
} else if (direction == D_LEFT || direction == D_RIGHT) {
|
||||
if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
|
||||
new_col = current_col + t_ws->table[current_col][current_row]->colspan;
|
||||
@ -130,16 +155,27 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
||||
t_ws = &(workspaces[screen->current_workspace]);
|
||||
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
|
||||
}
|
||||
|
||||
check_colrow_boundaries();
|
||||
|
||||
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
|
||||
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
|
||||
LOG("Cell empty, checking for rowspanned client above...\n");
|
||||
for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
|
||||
if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
|
||||
continue;
|
||||
|
||||
new_row = rows;
|
||||
break;
|
||||
}
|
||||
LOG("Fixed it to new row %d\n", new_row);
|
||||
}
|
||||
} else {
|
||||
LOG("direction unhandled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Bounds checking */
|
||||
if (new_col >= t_ws->cols)
|
||||
new_col = (t_ws->cols - 1);
|
||||
if (new_row >= t_ws->rows)
|
||||
new_row = (t_ws->rows - 1);
|
||||
check_colrow_boundaries();
|
||||
|
||||
if (t_ws->table[new_col][new_row]->currently_focused != NULL)
|
||||
set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
|
||||
@ -237,10 +273,13 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
|
||||
|
||||
new = CUR_TABLE[current_col][++current_row];
|
||||
break;
|
||||
/* To make static analyzers happy: */
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove it from the old container and put it into the new one */
|
||||
remove_client_from_container(conn, current_client, container);
|
||||
client_remove_from_container(conn, current_client, container, true);
|
||||
|
||||
if (new->currently_focused != NULL)
|
||||
CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
|
||||
@ -261,7 +300,8 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
|
||||
/* Fix colspan/rowspan if it’d overlap */
|
||||
fix_colrowspan(conn, workspace);
|
||||
|
||||
render_layout(conn);
|
||||
render_workspace(conn, workspace->screen, workspace);
|
||||
xcb_flush(conn);
|
||||
|
||||
set_focus(conn, current_client, true);
|
||||
}
|
||||
@ -309,6 +349,9 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
|
||||
|
||||
new = CUR_TABLE[current_col][++current_row];
|
||||
break;
|
||||
/* To make static analyzers happy: */
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
|
||||
@ -415,11 +458,66 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
|
||||
container->rowspan++;
|
||||
break;
|
||||
}
|
||||
/* To make static analyzers happy: */
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
render_layout(conn);
|
||||
}
|
||||
|
||||
static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) {
|
||||
/* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */
|
||||
Workspace *t_ws = &(workspaces[workspace-1]),
|
||||
*old_ws = client->workspace;
|
||||
|
||||
LOG("moving floating\n");
|
||||
|
||||
if (t_ws->screen == NULL) {
|
||||
LOG("initializing new workspace, setting num to %d\n", workspace-1);
|
||||
t_ws->screen = c_ws->screen;
|
||||
/* Copy the dimensions from the virtual screen */
|
||||
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
|
||||
} else {
|
||||
/* Check if there is already a fullscreen client on the destination workspace and
|
||||
* stop moving if so. */
|
||||
if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
|
||||
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
floating_assign_to_workspace(client, t_ws);
|
||||
|
||||
bool target_invisible = t_ws->screen->current_workspace != t_ws->num;
|
||||
|
||||
/* If we’re moving it to an invisible screen, we need to unmap it */
|
||||
if (target_invisible) {
|
||||
LOG("This workspace is not visible, unmapping\n");
|
||||
xcb_unmap_window(conn, client->frame);
|
||||
} else {
|
||||
/* If this is not the case, we move the window to a workspace
|
||||
* which is on another screen, so we also need to adjust its
|
||||
* coordinates. */
|
||||
LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
|
||||
uint32_t relative_x = client->rect.x - old_ws->rect.x,
|
||||
relative_y = client->rect.y - old_ws->rect.y;
|
||||
LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
|
||||
client->rect.x = t_ws->rect.x + relative_x;
|
||||
client->rect.y = t_ws->rect.y + relative_y;
|
||||
LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
|
||||
reposition_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
LOG("done\n");
|
||||
|
||||
render_layout(conn);
|
||||
|
||||
if (!target_invisible)
|
||||
set_focus(conn, client, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the currently selected window to the given workspace
|
||||
*
|
||||
@ -461,14 +559,14 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
|
||||
|
||||
assert(to_container != NULL);
|
||||
|
||||
remove_client_from_container(conn, current_client, container);
|
||||
client_remove_from_container(conn, current_client, container, true);
|
||||
if (container->workspace->fullscreen_client == current_client)
|
||||
container->workspace->fullscreen_client = NULL;
|
||||
|
||||
/* TODO: insert it to the correct position */
|
||||
CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
|
||||
|
||||
SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
|
||||
if (current_client->fullscreen)
|
||||
t_ws->fullscreen_client = current_client;
|
||||
LOG("Moved.\n");
|
||||
|
||||
current_client->container = to_container;
|
||||
@ -476,16 +574,26 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
|
||||
container->currently_focused = to_focus;
|
||||
to_container->currently_focused = current_client;
|
||||
|
||||
bool target_invisible = (to_container->workspace->screen->current_workspace != to_container->workspace->num);
|
||||
|
||||
/* If we’re moving it to an invisible screen, we need to unmap it */
|
||||
if (to_container->workspace->screen->current_workspace != to_container->workspace->num) {
|
||||
if (target_invisible) {
|
||||
LOG("This workspace is not visible, unmapping\n");
|
||||
xcb_unmap_window(conn, current_client->frame);
|
||||
} else {
|
||||
if (current_client->fullscreen) {
|
||||
LOG("Calling client_enter_fullscreen again\n");
|
||||
client_enter_fullscreen(conn, current_client);
|
||||
}
|
||||
}
|
||||
|
||||
/* delete all empty columns/rows */
|
||||
cleanup_table(conn, container->workspace);
|
||||
|
||||
render_layout(conn);
|
||||
|
||||
if (!target_invisible)
|
||||
set_focus(conn, current_client, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -539,22 +647,25 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
|
||||
|
||||
/* Check if we need to change something or if we’re already there */
|
||||
if (c_ws->screen->current_workspace == (workspace-1)) {
|
||||
if (CUR_CELL->currently_focused != NULL) {
|
||||
set_focus(conn, CUR_CELL->currently_focused, true);
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
|
||||
set_focus(conn, last_focused, true);
|
||||
if (need_warp) {
|
||||
warp_pointer_into(conn, CUR_CELL->currently_focused);
|
||||
client_warp_pointer_into(conn, last_focused);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
t_ws->screen->current_workspace = workspace-1;
|
||||
|
||||
/* Unmap all clients of the current workspace */
|
||||
unmap_workspace(conn, c_ws);
|
||||
|
||||
Workspace *old_workspace = c_ws;
|
||||
c_ws = &workspaces[workspace-1];
|
||||
|
||||
/* Unmap all clients of the old workspace */
|
||||
unmap_workspace(conn, old_workspace);
|
||||
|
||||
current_row = c_ws->current_row;
|
||||
current_col = c_ws->current_col;
|
||||
LOG("new current row = %d, current col = %d\n", current_row, current_col);
|
||||
@ -566,6 +677,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
|
||||
CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients)
|
||||
xcb_map_window(conn, client->frame);
|
||||
|
||||
/* Map all floating clients */
|
||||
if (!c_ws->floating_hidden)
|
||||
TAILQ_FOREACH(client, &(c_ws->floating_clients), floating_clients)
|
||||
xcb_map_window(conn, client->frame);
|
||||
|
||||
/* Map all stack windows, if any */
|
||||
struct Stack_Window *stack_win;
|
||||
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
|
||||
@ -575,10 +691,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
|
||||
ignore_enter_notify_forall(conn, c_ws, false);
|
||||
|
||||
/* Restore focus on the new workspace */
|
||||
if (CUR_CELL->currently_focused != NULL) {
|
||||
set_focus(conn, CUR_CELL->currently_focused, true);
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
|
||||
set_focus(conn, last_focused, true);
|
||||
if (need_warp) {
|
||||
warp_pointer_into(conn, CUR_CELL->currently_focused);
|
||||
client_warp_pointer_into(conn, last_focused);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
} else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
|
||||
@ -586,12 +703,161 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
|
||||
render_layout(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Jumps to the given window class / title.
|
||||
* Title is matched using strstr, that is, matches if it appears anywhere
|
||||
* in the string. Regular expressions seem to be a bit overkill here. However,
|
||||
* if we need them for something else somewhen, we may introduce them here, too.
|
||||
*
|
||||
*/
|
||||
static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
|
||||
char *classtitle;
|
||||
Client *client;
|
||||
|
||||
/* The first character is a quote, this was checked before */
|
||||
classtitle = sstrdup(arguments+1);
|
||||
/* The last character is a quote, we just set it to NULL */
|
||||
classtitle[strlen(classtitle)-1] = '\0';
|
||||
|
||||
if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
|
||||
free(classtitle);
|
||||
LOG("No matching client found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
free(classtitle);
|
||||
set_focus(conn, client, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Jump directly to the specified workspace, row and col.
|
||||
* Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you)
|
||||
*
|
||||
*/
|
||||
static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
|
||||
int ws, row, col;
|
||||
int result;
|
||||
|
||||
result = sscanf(arguments, "%d %d %d", &ws, &col, &row);
|
||||
LOG("Jump called with %d parameters (\"%s\")\n", result, arguments);
|
||||
|
||||
/* No match? Either no arguments were specified, or no numbers */
|
||||
if (result < 1) {
|
||||
LOG("At least one valid argument required\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Move to the target workspace */
|
||||
show_workspace(conn, ws);
|
||||
|
||||
if (result < 3)
|
||||
return;
|
||||
|
||||
LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
|
||||
|
||||
/* Move to row/col */
|
||||
if (row >= c_ws->rows)
|
||||
row = c_ws->rows - 1;
|
||||
if (col >= c_ws->cols)
|
||||
col = c_ws->cols - 1;
|
||||
|
||||
LOG("Jumping to col %d, row %d\n", col, row);
|
||||
if (c_ws->table[col][row]->currently_focused != NULL)
|
||||
set_focus(conn, c_ws->table[col][row]->currently_focused, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Travels the focus stack by the given number of times (or once, if no argument
|
||||
* was specified). That is, selects the window you were in before you focused
|
||||
* the current window.
|
||||
*
|
||||
* The special values 'floating' (select the next floating window), 'tiling'
|
||||
* (select the next tiling window), 'ft' (if the current window is floating,
|
||||
* select the next tiling window and vice-versa) are also valid
|
||||
*
|
||||
*/
|
||||
static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
|
||||
/* Start count at -1 to always skip the first element */
|
||||
int times, count = -1;
|
||||
Client *current;
|
||||
bool floating_criteria;
|
||||
|
||||
/* Either it’s one of the special values… */
|
||||
if (strcasecmp(arguments, "floating") == 0) {
|
||||
floating_criteria = true;
|
||||
} else if (strcasecmp(arguments, "tiling") == 0) {
|
||||
floating_criteria = false;
|
||||
} else if (strcasecmp(arguments, "ft") == 0) {
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
|
||||
LOG("Cannot select the next floating/tiling client because there is no client at all\n");
|
||||
return;
|
||||
}
|
||||
|
||||
floating_criteria = !client_is_floating(last_focused);
|
||||
} else {
|
||||
/* …or a number was specified */
|
||||
if (sscanf(arguments, "%u", ×) != 1) {
|
||||
LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
|
||||
times = 1;
|
||||
}
|
||||
|
||||
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
|
||||
if (++count < times) {
|
||||
LOG("Skipping\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG("Focussing\n");
|
||||
set_focus(conn, current, true);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Select the next client matching the criteria parsed above */
|
||||
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients)
|
||||
if (client_is_floating(current) == floating_criteria) {
|
||||
set_focus(conn, current, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Goes through the list of arguments (for exec()) and checks if the given argument
|
||||
* is present. If not, it copies the arguments (because we cannot realloc it) and
|
||||
* appends the given argument.
|
||||
*
|
||||
*/
|
||||
static char **append_argument(char **original, char *argument) {
|
||||
int num_args;
|
||||
for (num_args = 0; original[num_args] != NULL; num_args++) {
|
||||
LOG("original argument: \"%s\"\n", original[num_args]);
|
||||
/* If the argument is already present we return the original pointer */
|
||||
if (strcmp(original[num_args], argument) == 0)
|
||||
return original;
|
||||
}
|
||||
/* Copy the original array */
|
||||
char **result = smalloc((num_args+2) * sizeof(char*));
|
||||
memcpy(result, original, num_args * sizeof(char*));
|
||||
result[num_args] = argument;
|
||||
result[num_args+1] = NULL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses a command, see file CMDMODE for more information
|
||||
*
|
||||
*/
|
||||
void parse_command(xcb_connection_t *conn, const char *command) {
|
||||
LOG("--- parsing command \"%s\" ---\n", command);
|
||||
/* Get the first client from focus stack because floating clients are not
|
||||
* in any container, therefore CUR_CELL is not appropriate. */
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused == SLIST_END(&(c_ws->focus_stack)))
|
||||
last_focused = NULL;
|
||||
|
||||
/* Hmm, just to be sure */
|
||||
if (command[0] == '\0')
|
||||
return;
|
||||
@ -612,37 +878,66 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
||||
/* Is it <restart>? Then restart in place. */
|
||||
if (STARTS_WITH(command, "restart")) {
|
||||
LOG("restarting \"%s\"...\n", start_argv[0]);
|
||||
/* make sure -a is in the argument list or append it */
|
||||
start_argv = append_argument(start_argv, "-a");
|
||||
|
||||
execvp(start_argv[0], start_argv);
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
if (STARTS_WITH(command, "kill")) {
|
||||
if (CUR_CELL->currently_focused == NULL) {
|
||||
if (last_focused == NULL) {
|
||||
LOG("There is no window to kill\n");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Killing current window\n");
|
||||
kill_window(conn, CUR_CELL->currently_focused);
|
||||
client_kill(conn, last_focused);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Is it a jump to a specified workspace, row, col? */
|
||||
if (STARTS_WITH(command, "jump ")) {
|
||||
const char *arguments = command + strlen("jump ");
|
||||
if (arguments[0] == '"')
|
||||
jump_to_window(conn, arguments);
|
||||
else jump_to_container(conn, arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Should we travel the focus stack? */
|
||||
if (STARTS_WITH(command, "focus")) {
|
||||
const char *arguments = command + strlen("focus ");
|
||||
travel_focus_stack(conn, arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Is it 'f' for fullscreen? */
|
||||
if (command[0] == 'f') {
|
||||
if (CUR_CELL->currently_focused == NULL)
|
||||
if (last_focused == NULL)
|
||||
return;
|
||||
toggle_fullscreen(conn, CUR_CELL->currently_focused);
|
||||
client_toggle_fullscreen(conn, last_focused);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Is it just 's' for stacking or 'd' for default? */
|
||||
if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
|
||||
if (last_focused == NULL || client_is_floating(last_focused)) {
|
||||
LOG("not switching, this is a floating client\n");
|
||||
return;
|
||||
}
|
||||
LOG("Switching mode for current container\n");
|
||||
switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
|
||||
return;
|
||||
}
|
||||
|
||||
enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW;
|
||||
if (command[0] == 'H') {
|
||||
LOG("Hiding all floating windows\n");
|
||||
floating_toggle_hide(conn, c_ws);
|
||||
return;
|
||||
}
|
||||
|
||||
enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW;
|
||||
|
||||
/* Is it a <with>? */
|
||||
if (command[0] == 'w') {
|
||||
@ -651,13 +946,44 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
||||
if (command[0] == 'c') {
|
||||
with = WITH_CONTAINER;
|
||||
command++;
|
||||
} else if (command[0] == 'w') {
|
||||
with = WITH_WORKSPACE;
|
||||
command++;
|
||||
} else {
|
||||
LOG("not yet implemented.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* It's a normal <cmd> */
|
||||
/* Is it 't' for toggle tiling/floating? */
|
||||
if (command[0] == 't') {
|
||||
if (with == WITH_WORKSPACE) {
|
||||
c_ws->auto_float = !c_ws->auto_float;
|
||||
LOG("autofloat is now %d\n", c_ws->auto_float);
|
||||
return;
|
||||
}
|
||||
if (last_focused == NULL) {
|
||||
LOG("Cannot toggle tiling/floating: workspace empty\n");
|
||||
return;
|
||||
}
|
||||
|
||||
toggle_floating_mode(conn, last_focused, false);
|
||||
/* delete all empty columns/rows */
|
||||
cleanup_table(conn, last_focused->workspace);
|
||||
|
||||
/* Fix colspan/rowspan if it’d overlap */
|
||||
fix_colrowspan(conn, last_focused->workspace);
|
||||
|
||||
render_workspace(conn, last_focused->workspace->screen, last_focused->workspace);
|
||||
|
||||
/* Re-focus the client because cleanup_table sets the focus to the last
|
||||
* focused client inside a container only. */
|
||||
set_focus(conn, last_focused, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* It’s a normal <cmd> */
|
||||
char *rest = NULL;
|
||||
enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
|
||||
direction_t direction;
|
||||
@ -668,7 +994,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
||||
}
|
||||
|
||||
if (*rest == '\0') {
|
||||
/* No rest? This was a tag number, not a times specification */
|
||||
/* No rest? This was a workspace number, not a times specification */
|
||||
show_workspace(conn, times);
|
||||
return;
|
||||
}
|
||||
@ -686,7 +1012,20 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
||||
}
|
||||
|
||||
if (*rest == '\0') {
|
||||
move_current_window_to_workspace(conn, workspace);
|
||||
if (last_focused != NULL && client_is_floating(last_focused))
|
||||
move_floating_window_to_workspace(conn, last_focused, workspace);
|
||||
else move_current_window_to_workspace(conn, workspace);
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_focused == NULL) {
|
||||
LOG("Not performing (no window found)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (client_is_floating(last_focused) &&
|
||||
(action != ACTION_FOCUS && action != ACTION_MOVE)) {
|
||||
LOG("Not performing (floating)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -704,18 +1043,32 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
||||
LOG("unknown direction: %c\n", *rest);
|
||||
return;
|
||||
}
|
||||
rest++;
|
||||
|
||||
if (action == ACTION_FOCUS)
|
||||
if (action == ACTION_FOCUS) {
|
||||
if (client_is_floating(last_focused)) {
|
||||
floating_focus_direction(conn, last_focused, direction);
|
||||
continue;
|
||||
}
|
||||
focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER));
|
||||
else if (action == ACTION_MOVE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action == ACTION_MOVE) {
|
||||
if (client_is_floating(last_focused)) {
|
||||
floating_move(conn, last_focused, direction);
|
||||
continue;
|
||||
}
|
||||
if (with == WITH_WINDOW)
|
||||
move_current_window(conn, direction);
|
||||
else move_current_container(conn, direction);
|
||||
continue;
|
||||
}
|
||||
else if (action == ACTION_SNAP)
|
||||
snap_current_container(conn, direction);
|
||||
|
||||
rest++;
|
||||
if (action == ACTION_SNAP) {
|
||||
snap_current_container(conn, direction);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("--- done ---\n");
|
||||
|
201
src/config.c
201
src/config.c
@ -17,6 +17,7 @@
|
||||
#include "i3.h"
|
||||
#include "util.h"
|
||||
#include "config.h"
|
||||
#include "xcb.h"
|
||||
|
||||
Config config;
|
||||
|
||||
@ -25,14 +26,36 @@ Config config;
|
||||
*
|
||||
*/
|
||||
static char *glob_path(const char *path) {
|
||||
static glob_t globbuf;
|
||||
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
|
||||
die("glob() failed");
|
||||
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
|
||||
globfree(&globbuf);
|
||||
return result;
|
||||
static glob_t globbuf;
|
||||
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
|
||||
die("glob() failed");
|
||||
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
|
||||
globfree(&globbuf);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function does a very simple replacement of each instance of key with value.
|
||||
*
|
||||
*/
|
||||
static void replace_variable(char *buffer, const char *key, const char *value) {
|
||||
char *pos;
|
||||
/* To prevent endless recursions when the user makes an error configuring,
|
||||
* we stop after 100 replacements. That should be vastly more than enough. */
|
||||
int c = 0;
|
||||
LOG("Replacing %s with %s\n", key, value);
|
||||
while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
|
||||
LOG("replacing variable %s in \"%s\" with \"%s\"\n", key, buffer, value);
|
||||
char *rest = pos + strlen(key);
|
||||
*pos = '\0';
|
||||
char *replaced;
|
||||
asprintf(&replaced, "%s%s%s", buffer, value, rest);
|
||||
/* Hm, this is a bit ugly, but sizeof(buffer) = 4, as it’s just a pointer.
|
||||
* So we need to hard-code the dimensions here. */
|
||||
strncpy(buffer, replaced, 1026);
|
||||
free(replaced);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
|
||||
@ -41,7 +64,9 @@ static char *glob_path(const char *path) {
|
||||
* configuration file.
|
||||
*
|
||||
*/
|
||||
void load_configuration(const char *override_configpath) {
|
||||
void load_configuration(xcb_connection_t *conn, const char *override_configpath) {
|
||||
SLIST_HEAD(variables_head, Variable) variables;
|
||||
|
||||
#define OPTION_STRING(name) \
|
||||
if (strcasecmp(key, #name) == 0) { \
|
||||
config.name = sstrdup(value); \
|
||||
@ -52,9 +77,51 @@ void load_configuration(const char *override_configpath) {
|
||||
if (config.name == NULL) \
|
||||
die("You did not specify required configuration option " #name "\n");
|
||||
|
||||
#define OPTION_COLORTRIPLE(opt, name) \
|
||||
if (strcasecmp(key, opt) == 0) { \
|
||||
char border[8], background[8], text[8]; \
|
||||
memset(border, 0, sizeof(border)); \
|
||||
memset(background, 0, sizeof(background)); \
|
||||
memset(text, 0, sizeof(text)); \
|
||||
border[0] = background[0] = text[0] = '#'; \
|
||||
if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \
|
||||
border + 1, background + 1, text + 1) != 3 || \
|
||||
strlen(border) != 7 || \
|
||||
strlen(background) != 7 || \
|
||||
strlen(text) != 7) \
|
||||
die("invalid color code line: %s\n", value); \
|
||||
config.name.border = get_colorpixel(conn, border); \
|
||||
config.name.background = get_colorpixel(conn, background); \
|
||||
config.name.text = get_colorpixel(conn, text); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
/* Clear the old config or initialize the data structure */
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
SLIST_INIT(&variables);
|
||||
|
||||
/* Initialize default colors */
|
||||
config.client.focused.border = get_colorpixel(conn, "#4c7899");
|
||||
config.client.focused.background = get_colorpixel(conn, "#285577");
|
||||
config.client.focused.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
config.client.focused_inactive.border = get_colorpixel(conn, "#4c7899");
|
||||
config.client.focused_inactive.background = get_colorpixel(conn, "#555555");
|
||||
config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
config.client.unfocused.border = get_colorpixel(conn, "#333333");
|
||||
config.client.unfocused.background = get_colorpixel(conn, "#222222");
|
||||
config.client.unfocused.text = get_colorpixel(conn, "#888888");
|
||||
|
||||
config.bar.focused.border = get_colorpixel(conn, "#4c7899");
|
||||
config.bar.focused.background = get_colorpixel(conn, "#285577");
|
||||
config.bar.focused.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
config.bar.unfocused.border = get_colorpixel(conn, "#333333");
|
||||
config.bar.unfocused.background = get_colorpixel(conn, "#222222");
|
||||
config.bar.unfocused.text = get_colorpixel(conn, "#888888");
|
||||
|
||||
FILE *handle;
|
||||
if (override_configpath != NULL) {
|
||||
if ((handle = fopen(override_configpath, "r")) == NULL)
|
||||
@ -77,6 +144,14 @@ void load_configuration(const char *override_configpath) {
|
||||
die("Could not read configuration file\n");
|
||||
}
|
||||
|
||||
if (config.terminal != NULL)
|
||||
replace_variable(buffer, "$terminal", config.terminal);
|
||||
|
||||
/* Replace all custom variables */
|
||||
struct Variable *current;
|
||||
SLIST_FOREACH(current, &variables, variables)
|
||||
replace_variable(buffer, current->key, current->value);
|
||||
|
||||
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
|
||||
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
|
||||
key[0] == '#' || strlen(key) < 3)
|
||||
@ -85,6 +160,22 @@ void load_configuration(const char *override_configpath) {
|
||||
OPTION_STRING(terminal);
|
||||
OPTION_STRING(font);
|
||||
|
||||
/* Colors */
|
||||
OPTION_COLORTRIPLE("client.focused", client.focused);
|
||||
OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
|
||||
OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
|
||||
OPTION_COLORTRIPLE("bar.focused", bar.focused);
|
||||
OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
|
||||
|
||||
/* exec-lines (autostart) */
|
||||
if (strcasecmp(key, "exec") == 0) {
|
||||
struct Autostart *new = smalloc(sizeof(struct Autostart));
|
||||
new->command = sstrdup(value);
|
||||
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* key bindings */
|
||||
if (strcasecmp(key, "bind") == 0) {
|
||||
#define CHECK_MODIFIER(name) \
|
||||
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
|
||||
@ -124,13 +215,105 @@ void load_configuration(const char *override_configpath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unknown configfile option: %s\n", key);
|
||||
exit(1);
|
||||
if (strcasecmp(key, "floating_modifier") == 0) {
|
||||
char *walk = value;
|
||||
uint32_t modifiers = 0;
|
||||
|
||||
while (*walk != '\0') {
|
||||
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
|
||||
CHECK_MODIFIER(SHIFT);
|
||||
CHECK_MODIFIER(CONTROL);
|
||||
CHECK_MODIFIER(MODE_SWITCH);
|
||||
CHECK_MODIFIER(MOD1);
|
||||
CHECK_MODIFIER(MOD2);
|
||||
CHECK_MODIFIER(MOD3);
|
||||
CHECK_MODIFIER(MOD4);
|
||||
CHECK_MODIFIER(MOD5);
|
||||
|
||||
/* No modifier found? Then we’re done with this step */
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("Floating modifiers = %d\n", modifiers);
|
||||
config.floating_modifier = modifiers;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* assign window class[/window title] → workspace */
|
||||
if (strcasecmp(key, "assign") == 0) {
|
||||
LOG("assign: \"%s\"\n", value);
|
||||
char *class_title = sstrdup(value);
|
||||
char *target;
|
||||
|
||||
/* If the window class/title is quoted we skip quotes */
|
||||
if (class_title[0] == '"') {
|
||||
class_title++;
|
||||
char *end = strchr(class_title, '"');
|
||||
if (end == NULL)
|
||||
die("Malformed assignment, couldn't find terminating quote\n");
|
||||
*end = '\0';
|
||||
} else {
|
||||
/* If it is not quoted, we terminate it at the first space */
|
||||
char *end = strchr(class_title, ' ');
|
||||
if (end == NULL)
|
||||
die("Malformed assignment, couldn't find terminating space\n");
|
||||
*end = '\0';
|
||||
}
|
||||
|
||||
/* The target is the last argument separated by a space */
|
||||
if ((target = strrchr(value, ' ')) == NULL)
|
||||
die("Malformed assignment, couldn't find target\n");
|
||||
target++;
|
||||
|
||||
if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10))
|
||||
die("Malformed assignment, invalid workspace number\n");
|
||||
|
||||
LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target);
|
||||
|
||||
struct Assignment *new = scalloc(sizeof(struct Assignment));
|
||||
new->windowclass_title = class_title;
|
||||
if (*target == '~')
|
||||
new->floating = true;
|
||||
else new->workspace = atoi(target);
|
||||
TAILQ_INSERT_TAIL(&assignments, new, assignments);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* set a custom variable */
|
||||
if (strcasecmp(key, "set") == 0) {
|
||||
if (value[0] != '$')
|
||||
die("Malformed variable assignment, name has to start with $\n");
|
||||
|
||||
/* get key/value for this variable */
|
||||
char *v_key = value, *v_value;
|
||||
if ((v_value = strstr(value, " ")) == NULL)
|
||||
die("Malformed variable assignment, need a value\n");
|
||||
|
||||
*(v_value++) = '\0';
|
||||
|
||||
struct Variable *new = scalloc(sizeof(struct Variable));
|
||||
new->key = sstrdup(v_key);
|
||||
new->value = sstrdup(v_value);
|
||||
SLIST_INSERT_HEAD(&variables, new, variables);
|
||||
LOG("Got new variable %s = %s\n", v_key, v_value);
|
||||
continue;
|
||||
}
|
||||
|
||||
die("Unknown configfile option: %s\n", key);
|
||||
}
|
||||
fclose(handle);
|
||||
|
||||
REQUIRED_OPTION(terminal);
|
||||
REQUIRED_OPTION(font);
|
||||
|
||||
|
||||
while (!SLIST_EMPTY(&variables)) {
|
||||
struct Variable *v = SLIST_FIRST(&variables);
|
||||
SLIST_REMOVE_HEAD(&variables, variables);
|
||||
free(v->key);
|
||||
free(v->value);
|
||||
free(v);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
433
src/floating.c
Normal file
433
src/floating.c
Normal file
@ -0,0 +1,433 @@
|
||||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* src/floating.c: contains all functions for handling floating clients
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_event.h>
|
||||
|
||||
#include "i3.h"
|
||||
#include "config.h"
|
||||
#include "data.h"
|
||||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "debug.h"
|
||||
#include "layout.h"
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
|
||||
/*
|
||||
* Toggles floating mode for the given client.
|
||||
* Correctly takes care of the position/size (separately stored for tiling/floating mode)
|
||||
* and repositions/resizes/redecorates the client.
|
||||
*
|
||||
* If the automatic flag is set to true, this was an automatic update by a change of the
|
||||
* window class from the application which can be overwritten by the user.
|
||||
*
|
||||
*/
|
||||
void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) {
|
||||
Container *con = client->container;
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
if (con == NULL) {
|
||||
LOG("This client is already in floating (container == NULL), re-inserting\n");
|
||||
Client *next_tiling;
|
||||
SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients)
|
||||
if (!client_is_floating(next_tiling))
|
||||
break;
|
||||
/* If there are no tiling clients on this workspace, there can only be one
|
||||
* container: the first one */
|
||||
if (next_tiling == TAILQ_END(&(client->workspace->focus_stack)))
|
||||
con = client->workspace->table[0][0];
|
||||
else con = next_tiling->container;
|
||||
|
||||
/* Remove the client from the list of floating clients */
|
||||
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
|
||||
|
||||
LOG("destination container = %p\n", con);
|
||||
Client *old_focused = con->currently_focused;
|
||||
/* Preserve position/size */
|
||||
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
|
||||
|
||||
client->floating = FLOATING_USER_OFF;
|
||||
client->container = con;
|
||||
|
||||
if (old_focused != NULL && !old_focused->dock)
|
||||
CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
|
||||
else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
|
||||
|
||||
LOG("Re-inserted the client into the matrix.\n");
|
||||
con->currently_focused = client;
|
||||
|
||||
client_set_below_floating(conn, client);
|
||||
|
||||
render_container(conn, con);
|
||||
xcb_flush(conn);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Entering floating for client %08x\n", client->child);
|
||||
|
||||
/* Remove the client of its container */
|
||||
client_remove_from_container(conn, client, con, false);
|
||||
client->container = NULL;
|
||||
|
||||
/* Add the client to the list of floating clients for its workspace */
|
||||
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
|
||||
|
||||
if (con->currently_focused == client) {
|
||||
LOG("Need to re-adjust currently_focused\n");
|
||||
/* Get the next client in the focus stack for this particular container */
|
||||
con->currently_focused = get_last_focused_client(conn, con, NULL);
|
||||
}
|
||||
|
||||
if (automatic)
|
||||
client->floating = FLOATING_AUTO_ON;
|
||||
else client->floating = FLOATING_USER_ON;
|
||||
|
||||
/* Initialize the floating position from the position in tiling mode, if this
|
||||
* client never was floating (x == -1) */
|
||||
if (client->floating_rect.x == -1) {
|
||||
/* Copy over the position */
|
||||
client->floating_rect.x = client->rect.x;
|
||||
client->floating_rect.y = client->rect.y;
|
||||
|
||||
/* Copy size the other direction */
|
||||
client->child_rect.width = client->floating_rect.width;
|
||||
client->child_rect.height = client->floating_rect.height;
|
||||
|
||||
client->rect.width = client->child_rect.width + 2 + 2;
|
||||
client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
|
||||
|
||||
LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
|
||||
client->floating_rect.width, client->floating_rect.height);
|
||||
} else {
|
||||
/* If the client was already in floating before we restore the old position / size */
|
||||
LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
|
||||
client->floating_rect.width, client->floating_rect.height);
|
||||
memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
|
||||
}
|
||||
|
||||
/* Raise the client */
|
||||
xcb_raise_window(conn, client->frame);
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
/* redecorate_window flushes */
|
||||
redecorate_window(conn, client);
|
||||
|
||||
/* Re-render the tiling layout of this container */
|
||||
render_container(conn, con);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the floating client from its workspace and attaches it to the new workspace.
|
||||
* This is centralized here because it may happen if you move it via keyboard and
|
||||
* if you move it using your mouse.
|
||||
*
|
||||
*/
|
||||
void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
|
||||
/* Remove from focus stack and list of floating clients */
|
||||
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
|
||||
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
|
||||
|
||||
if (client->workspace->fullscreen_client == client)
|
||||
client->workspace->fullscreen_client = NULL;
|
||||
|
||||
/* Insert into destination focus stack and list of floating clients */
|
||||
client->workspace = new_workspace;
|
||||
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
|
||||
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
|
||||
if (client->fullscreen)
|
||||
client->workspace->fullscreen_client = client;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Called whenever the user clicks on a border (not the titlebar!) of a floating window.
|
||||
* Determines on which border the user clicked and launches the drag_pointer function
|
||||
* with the resize_callback.
|
||||
*
|
||||
*/
|
||||
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
|
||||
|
||||
LOG("floating border click\n");
|
||||
|
||||
border_t border;
|
||||
|
||||
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
switch (border) {
|
||||
case BORDER_RIGHT: {
|
||||
int new_width = old_rect->width + (new_x - event->root_x);
|
||||
if ((new_width < 0) ||
|
||||
(new_width < 50 && client->rect.width >= new_width))
|
||||
return;
|
||||
client->rect.width = new_width;
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_BOTTOM: {
|
||||
int new_height = old_rect->height + (new_y - event->root_y);
|
||||
if ((new_height < 0) ||
|
||||
(new_height < 20 && client->rect.height >= new_height))
|
||||
return;
|
||||
client->rect.height = old_rect->height + (new_y - event->root_y);
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_TOP: {
|
||||
int new_height = old_rect->height + (event->root_y - new_y);
|
||||
if ((new_height < 0) ||
|
||||
(new_height < 20 && client->rect.height >= new_height))
|
||||
return;
|
||||
|
||||
client->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
client->rect.height = new_height;
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_LEFT: {
|
||||
int new_width = old_rect->width + (event->root_x - new_x);
|
||||
if ((new_width < 0) ||
|
||||
(new_width < 50 && client->rect.width >= new_width))
|
||||
return;
|
||||
client->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
client->rect.width = new_width;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Push the new position/size to X11 */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
if (event->event_y < 2)
|
||||
border = BORDER_TOP;
|
||||
else if (event->event_y >= (client->rect.height - 2))
|
||||
border = BORDER_BOTTOM;
|
||||
else if (event->event_x <= 2)
|
||||
border = BORDER_LEFT;
|
||||
else if (event->event_x >= (client->rect.width - 2))
|
||||
border = BORDER_RIGHT;
|
||||
else {
|
||||
LOG("Not on any border, not doing anything.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG("border = %d\n", border);
|
||||
|
||||
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Called when the user clicked on the titlebar of a floating window.
|
||||
* Calls the drag_pointer function with the drag_window callback
|
||||
*
|
||||
*/
|
||||
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
|
||||
LOG("floating_drag_window\n");
|
||||
|
||||
void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
/* Reposition the client correctly while moving */
|
||||
client->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
client->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
reposition_client(conn, client);
|
||||
/* Because reposition_client does not send a faked configure event (only resize does),
|
||||
* we need to initiate that on our own */
|
||||
fake_absolute_configure_notify(conn, client);
|
||||
/* fake_absolute_configure_notify flushes */
|
||||
}
|
||||
|
||||
|
||||
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function grabs your pointer and lets you drag stuff around (borders).
|
||||
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
|
||||
* and the given callback will be called with the parameters specified (client,
|
||||
* border on which the click originally was), the original rect of the client,
|
||||
* the event and the new coordinates (x, y).
|
||||
*
|
||||
*/
|
||||
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
|
||||
xcb_window_t confine_to, border_t border, callback_t callback) {
|
||||
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
|
||||
uint32_t new_x, new_y;
|
||||
Rect old_rect;
|
||||
if (client != NULL)
|
||||
memcpy(&old_rect, &(client->rect), sizeof(Rect));
|
||||
|
||||
/* Grab the pointer */
|
||||
/* TODO: returncode */
|
||||
xcb_grab_pointer(conn,
|
||||
false, /* get all pointer events specified by the following mask */
|
||||
root, /* grab the root window */
|
||||
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
|
||||
XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
|
||||
XCB_GRAB_MODE_ASYNC, /* keyboard mode */
|
||||
confine_to, /* confine_to = in which window should the cursor stay */
|
||||
XCB_NONE, /* don’t display a special cursor */
|
||||
XCB_CURRENT_TIME);
|
||||
|
||||
/* Go into our own event loop */
|
||||
xcb_flush(conn);
|
||||
|
||||
xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
|
||||
/* I’ve always wanted to have my own eventhandler… */
|
||||
while ((inside_event = xcb_wait_for_event(conn))) {
|
||||
/* We now handle all events we can get using xcb_poll_for_event */
|
||||
do {
|
||||
/* Same as get_event_handler in xcb */
|
||||
int nr = inside_event->response_type;
|
||||
if (nr == 0) {
|
||||
/* An error occured */
|
||||
handle_event(NULL, conn, inside_event);
|
||||
free(inside_event);
|
||||
continue;
|
||||
}
|
||||
assert(nr < 256);
|
||||
nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
|
||||
assert(nr >= 2);
|
||||
|
||||
switch (nr) {
|
||||
case XCB_BUTTON_RELEASE:
|
||||
goto done;
|
||||
|
||||
case XCB_MOTION_NOTIFY:
|
||||
/* motion_notify events are saved for later */
|
||||
FREE(last_motion_notify);
|
||||
last_motion_notify = inside_event;
|
||||
|
||||
break;
|
||||
default:
|
||||
LOG("Passing to original handler\n");
|
||||
/* Use original handler */
|
||||
xcb_event_handle(&evenths, inside_event);
|
||||
break;
|
||||
}
|
||||
if (last_motion_notify != inside_event)
|
||||
free(inside_event);
|
||||
} while ((inside_event = xcb_poll_for_event(conn)) != NULL);
|
||||
|
||||
if (last_motion_notify == NULL)
|
||||
continue;
|
||||
|
||||
new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
|
||||
new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
|
||||
|
||||
callback(&old_rect, new_x, new_y);
|
||||
FREE(last_motion_notify);
|
||||
}
|
||||
done:
|
||||
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Changes focus in the given direction for floating clients.
|
||||
*
|
||||
* Changing to the left/right means going to the previous/next floating client,
|
||||
* changing to top/bottom means cycling through the Z-index.
|
||||
*
|
||||
*/
|
||||
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
|
||||
LOG("floating focus\n");
|
||||
|
||||
if (direction == D_LEFT || direction == D_RIGHT) {
|
||||
/* Go to the next/previous floating client */
|
||||
Client *client;
|
||||
|
||||
while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) :
|
||||
TAILQ_NEXT(currently_focused, floating_clients))) !=
|
||||
TAILQ_END(&(currently_focused->workspace->floating_clients))) {
|
||||
if (!client->floating)
|
||||
continue;
|
||||
set_focus(conn, client, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the client 10px to the specified direction.
|
||||
*
|
||||
*/
|
||||
void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
|
||||
LOG("floating move\n");
|
||||
|
||||
switch (direction) {
|
||||
case D_LEFT:
|
||||
if (currently_focused->rect.x < 10)
|
||||
return;
|
||||
currently_focused->rect.x -= 10;
|
||||
break;
|
||||
case D_RIGHT:
|
||||
currently_focused->rect.x += 10;
|
||||
break;
|
||||
case D_UP:
|
||||
if (currently_focused->rect.y < 10)
|
||||
return;
|
||||
currently_focused->rect.y -= 10;
|
||||
break;
|
||||
case D_DOWN:
|
||||
currently_focused->rect.y += 10;
|
||||
break;
|
||||
/* to make static analyzers happy */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
reposition_client(conn, currently_focused);
|
||||
|
||||
/* Because reposition_client does not send a faked configure event (only resize does),
|
||||
* we need to initiate that on our own */
|
||||
fake_absolute_configure_notify(conn, currently_focused);
|
||||
/* fake_absolute_configure_notify flushes */
|
||||
}
|
||||
|
||||
/*
|
||||
* Hides all floating clients (or show them if they are currently hidden) on
|
||||
* the specified workspace.
|
||||
*
|
||||
*/
|
||||
void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
|
||||
Client *client;
|
||||
|
||||
workspace->floating_hidden = !workspace->floating_hidden;
|
||||
LOG("floating_hidden is now: %d\n", workspace->floating_hidden);
|
||||
TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
|
||||
if (workspace->floating_hidden)
|
||||
xcb_unmap_window(conn, client->frame);
|
||||
else xcb_map_window(conn, client->frame);
|
||||
}
|
||||
|
||||
/* If we just unmapped all floating windows we should ensure that the focus
|
||||
* is set correctly, that ist, to the first non-floating client in stack */
|
||||
if (workspace->floating_hidden)
|
||||
SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) {
|
||||
if (client_is_floating(client))
|
||||
continue;
|
||||
set_focus(conn, client, true);
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
257
src/handlers.c
257
src/handlers.c
@ -32,6 +32,9 @@
|
||||
#include "config.h"
|
||||
#include "queue.h"
|
||||
#include "resize.h"
|
||||
#include "client.h"
|
||||
#include "manage.h"
|
||||
#include "floating.h"
|
||||
|
||||
/* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
|
||||
since it’d trigger an infinite loop of switching between the different windows when
|
||||
@ -149,7 +152,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
|
||||
return 1;
|
||||
}
|
||||
/* Some events are not interesting, because they were not generated actively by the
|
||||
user, but be reconfiguration of windows */
|
||||
user, but by reconfiguration of windows */
|
||||
if (event_is_ignored(event->sequence))
|
||||
return 1;
|
||||
|
||||
@ -196,6 +199,14 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) {
|
||||
/* This can happen when a client gets assigned to a different workspace than
|
||||
* the current one (see src/mainx.c:reparent_window). Shortly after it was created,
|
||||
* an enter_notify will follow. */
|
||||
LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
set_focus(conn, client, false);
|
||||
|
||||
return 1;
|
||||
@ -283,6 +294,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
|
||||
|
||||
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
|
||||
LOG("button press!\n");
|
||||
LOG("state = %d\n", event->state);
|
||||
/* This was either a focus for a client’s parent (= titlebar)… */
|
||||
Client *client = table_get(&by_child, event->event);
|
||||
bool border_click = false;
|
||||
@ -290,6 +302,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
||||
client = table_get(&by_parent, event->event);
|
||||
border_click = true;
|
||||
}
|
||||
/* See if this was a click with the configured modifier. If so, we need
|
||||
* to move around the client if it was floating. if not, we just process
|
||||
* as usual. */
|
||||
if (config.floating_modifier != 0 &&
|
||||
(event->state & config.floating_modifier) != 0) {
|
||||
if (client == NULL) {
|
||||
LOG("Not handling, Mod1 was pressed and no client found\n");
|
||||
return 1;
|
||||
}
|
||||
if (client_is_floating(client)) {
|
||||
floating_drag_window(conn, client, event);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (client == NULL) {
|
||||
/* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */
|
||||
if (button_press_stackwin(conn, event))
|
||||
@ -312,7 +339,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
||||
Container *con = client->container;
|
||||
int first, second;
|
||||
|
||||
if (con == NULL) {
|
||||
if (client->dock) {
|
||||
LOG("dock. done.\n");
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
xcb_flush(conn);
|
||||
@ -324,6 +351,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
||||
if (!border_click) {
|
||||
LOG("client. done.\n");
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
/* Floating clients should be raised on click */
|
||||
if (client_is_floating(client))
|
||||
xcb_raise_window(conn, client->frame);
|
||||
xcb_flush(conn);
|
||||
return 1;
|
||||
}
|
||||
@ -332,9 +362,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
|
||||
LOG("click on titlebar\n");
|
||||
|
||||
/* Floating clients can be dragged by grabbing their titlebar */
|
||||
if (client_is_floating(client)) {
|
||||
/* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
|
||||
xcb_raise_window(conn, client->frame);
|
||||
xcb_flush(conn);
|
||||
|
||||
floating_drag_window(conn, client, event);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client_is_floating(client))
|
||||
return floating_border_click(conn, client, event);
|
||||
|
||||
if (event->event_y < 2) {
|
||||
/* This was a press on the top border */
|
||||
if (con->row == 0)
|
||||
@ -412,8 +454,61 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
|
||||
Client *client = table_get(&by_child, event->window);
|
||||
if (client == NULL) {
|
||||
LOG("This client is not mapped, so we don't care and just tell the client that he will get its size\n");
|
||||
Rect rect = {event->x, event->y, event->width, event->height};
|
||||
fake_configure_notify(conn, rect, event->window);
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[7];
|
||||
int c = 0;
|
||||
#define COPY_MASK_MEMBER(mask_member, event_member) do { \
|
||||
if (event->value_mask & mask_member) { \
|
||||
mask |= mask_member; \
|
||||
values[c++] = event->event_member; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
|
||||
|
||||
xcb_configure_window(conn, event->window, mask, values);
|
||||
xcb_flush(conn);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Floating clients can be reconfigured */
|
||||
if (client_is_floating(client)) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_X)
|
||||
client->rect.x = event->x;
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_Y)
|
||||
client->rect.y = event->y;
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH)
|
||||
client->rect.width = event->width + 2 + 2;
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
|
||||
client->rect.height = event->height + (font->height + 2 + 2) + 2;
|
||||
|
||||
LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
|
||||
client->rect.x, client->rect.y, client->rect.width, client->rect.height);
|
||||
|
||||
/* Push the new position/size to X11 */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client->fullscreen) {
|
||||
LOG("Client is in fullscreen mode\n");
|
||||
|
||||
Rect child_rect = client->container->workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
fake_configure_notify(conn, child_rect, client->child);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -495,18 +590,16 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
||||
|
||||
client = table_remove(&by_child, event->window);
|
||||
|
||||
if (client->name != NULL)
|
||||
free(client->name);
|
||||
/* If this was the fullscreen client, we need to unset it */
|
||||
if (client->fullscreen)
|
||||
client->workspace->fullscreen_client = NULL;
|
||||
|
||||
/* Clients without a container are either floating or dock windows */
|
||||
if (client->container != NULL) {
|
||||
Container *con = client->container;
|
||||
|
||||
/* If this was the fullscreen client, we need to unset it */
|
||||
if (client->fullscreen)
|
||||
con->workspace->fullscreen_client = NULL;
|
||||
|
||||
/* Remove the client from the list of clients */
|
||||
remove_client_from_container(conn, client, con);
|
||||
client_remove_from_container(conn, client, con, true);
|
||||
|
||||
/* Set focus to the last focused client in this container */
|
||||
con->currently_focused = get_last_focused_client(conn, con, NULL);
|
||||
@ -514,6 +607,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
||||
/* Only if this is the active container, we need to really change focus */
|
||||
if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
|
||||
set_focus(conn, con->currently_focused, true);
|
||||
} else if (client_is_floating(client)) {
|
||||
LOG("Removing from floating clients\n");
|
||||
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
|
||||
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
|
||||
}
|
||||
|
||||
if (client->dock) {
|
||||
@ -528,32 +625,41 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
||||
table_remove(&by_parent, client->frame);
|
||||
|
||||
if (client->container != NULL) {
|
||||
cleanup_table(conn, client->container->workspace);
|
||||
fix_colrowspan(conn, client->container->workspace);
|
||||
Workspace *workspace = client->container->workspace;
|
||||
cleanup_table(conn, workspace);
|
||||
fix_colrowspan(conn, workspace);
|
||||
}
|
||||
|
||||
/* Let’s see how many clients there are left on the workspace to delete it if it’s empty */
|
||||
bool workspace_empty = true;
|
||||
FOR_TABLE(client->workspace)
|
||||
if (!CIRCLEQ_EMPTY(&(client->workspace->table[cols][rows]->clients))) {
|
||||
workspace_empty = false;
|
||||
break;
|
||||
}
|
||||
bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack));
|
||||
bool workspace_active = false;
|
||||
Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL);
|
||||
|
||||
/* If this workspace is currently active, we don’t delete it */
|
||||
i3Screen *screen;
|
||||
TAILQ_FOREACH(screen, virtual_screens, screens)
|
||||
if (screen->current_workspace == client->workspace->num) {
|
||||
workspace_active = true;
|
||||
workspace_empty = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (workspace_empty)
|
||||
if (workspace_empty) {
|
||||
LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num,
|
||||
client->workspace);
|
||||
client->workspace->screen = NULL;
|
||||
}
|
||||
|
||||
FREE(client->window_class);
|
||||
FREE(client->name);
|
||||
free(client);
|
||||
|
||||
render_layout(conn);
|
||||
|
||||
/* Ensure the focus is set to the next client in the focus stack */
|
||||
if (workspace_active && to_focus != NULL)
|
||||
set_focus(conn, to_focus, true);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -598,14 +704,13 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
|
||||
client->name_len = new_len;
|
||||
client->uses_net_wm_name = true;
|
||||
|
||||
if (old_name != NULL)
|
||||
free(old_name);
|
||||
FREE(old_name);
|
||||
|
||||
/* If the client is a dock window, we don’t need to render anything */
|
||||
if (client->dock)
|
||||
return 1;
|
||||
|
||||
if (client->container->mode == MODE_STACK)
|
||||
if (client->container != NULL && client->container->mode == MODE_STACK)
|
||||
render_container(conn, client->container);
|
||||
else decorate_window(conn, client, client->frame, client->titlegc, 0);
|
||||
xcb_flush(conn);
|
||||
@ -673,7 +778,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
|
||||
if (client->dock)
|
||||
return 1;
|
||||
|
||||
if (client->container->mode == MODE_STACK)
|
||||
if (client->container != NULL && client->container->mode == MODE_STACK)
|
||||
render_container(conn, client->container);
|
||||
else decorate_window(conn, client, client->frame, client->titlegc, 0);
|
||||
xcb_flush(conn);
|
||||
@ -681,6 +786,46 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the client’s WM_CLASS property
|
||||
*
|
||||
*/
|
||||
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
|
||||
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
|
||||
LOG("window class changed\n");
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
LOG("prop == NULL\n");
|
||||
return 1;
|
||||
}
|
||||
Client *client = table_get(&by_child, window);
|
||||
if (client == NULL)
|
||||
return 1;
|
||||
char *new_class;
|
||||
if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
|
||||
perror("Could not get window class");
|
||||
LOG("Could not get window class\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG("changed to %s\n", new_class);
|
||||
char *old_class = client->window_class;
|
||||
client->window_class = new_class;
|
||||
FREE(old_class);
|
||||
|
||||
if (!client->initialized) {
|
||||
LOG("Client is not yet initialized, not putting it to floating\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) {
|
||||
LOG("tool/dialog window, should we put it floating?\n");
|
||||
if (client->floating == FLOATING_AUTO_OFF)
|
||||
toggle_floating_mode(conn, client, true);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expose event means we should redraw our windows (= title bar)
|
||||
*
|
||||
@ -717,15 +862,15 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client->container->mode != MODE_STACK)
|
||||
if (client->container == NULL || client->container->mode != MODE_STACK)
|
||||
decorate_window(conn, client, client->frame, client->titlegc, 0);
|
||||
else {
|
||||
uint32_t background_color;
|
||||
/* Distinguish if the window is currently focused… */
|
||||
if (CUR_CELL->currently_focused == client)
|
||||
background_color = get_colorpixel(conn, "#285577");
|
||||
background_color = config.client.focused.background;
|
||||
/* …or if it is the focused window in a not focused container */
|
||||
else background_color = get_colorpixel(conn, "#555555");
|
||||
else background_color = config.client.focused_inactive.background;
|
||||
|
||||
/* Set foreground color to current focused color, line width to 2 */
|
||||
uint32_t values[] = {background_color, 2};
|
||||
@ -771,7 +916,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
|
||||
(!client->fullscreen &&
|
||||
(event->data.data32[0] == _NET_WM_STATE_ADD ||
|
||||
event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
|
||||
toggle_fullscreen(conn, client);
|
||||
client_toggle_fullscreen(conn, client);
|
||||
} else {
|
||||
LOG("unhandled clientmessage\n");
|
||||
return 0;
|
||||
@ -804,6 +949,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
||||
return 1;
|
||||
}
|
||||
xcb_size_hints_t size_hints;
|
||||
LOG("client is %08x / child %08x\n", client->frame, client->child);
|
||||
|
||||
/* If the hints were already in this event, use them, if not, request them */
|
||||
if (reply != NULL)
|
||||
@ -811,6 +957,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
||||
else
|
||||
xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL);
|
||||
|
||||
if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
|
||||
LOG("min size set\n");
|
||||
LOG("gots min_width = %d, min_height = %d\n", size_hints.min_width, size_hints.min_height);
|
||||
}
|
||||
|
||||
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
|
||||
if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
|
||||
(size_hints.min_aspect_num <= 0) ||
|
||||
@ -821,8 +972,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
||||
|
||||
LOG("window is %08x / %s\n", client->child, client->name);
|
||||
|
||||
int base_width = 0, base_height = 0,
|
||||
min_width = 0, min_height = 0;
|
||||
int base_width = 0, base_height = 0;
|
||||
|
||||
/* base_width/height are the desired size of the window.
|
||||
We check if either the program-specified size or the program-specified
|
||||
@ -835,14 +985,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
||||
base_height = size_hints.min_height;
|
||||
}
|
||||
|
||||
if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
|
||||
min_width = size_hints.min_width;
|
||||
min_height = size_hints.min_height;
|
||||
} else if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) {
|
||||
min_width = size_hints.base_width;
|
||||
min_height = size_hints.base_height;
|
||||
}
|
||||
|
||||
double width = client->rect.width - base_width;
|
||||
double height = client->rect.height - base_height;
|
||||
/* Convert numerator/denominator to a double */
|
||||
@ -874,3 +1016,42 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles the transient for hints set by a window, signalizing that this window is a popup window
|
||||
* for some other window.
|
||||
*
|
||||
* See ICCCM 4.1.2.6 for more details
|
||||
*
|
||||
*/
|
||||
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
|
||||
xcb_atom_t name, xcb_get_property_reply_t *reply) {
|
||||
LOG("Transient hint!\n");
|
||||
Client *client = table_get(&by_child, window);
|
||||
if (client == NULL) {
|
||||
LOG("No such client\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
xcb_window_t transient_for;
|
||||
|
||||
if (reply != NULL) {
|
||||
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) {
|
||||
LOG("Not transient for any window\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window),
|
||||
&transient_for, NULL)) {
|
||||
LOG("Not transient for any window\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (client->floating == FLOATING_AUTO_OFF) {
|
||||
LOG("This is a popup window, putting into floating\n");
|
||||
toggle_floating_mode(conn, client, true);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
112
src/layout.c
112
src/layout.c
@ -24,6 +24,8 @@
|
||||
#include "util.h"
|
||||
#include "xinerama.h"
|
||||
#include "layout.h"
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
|
||||
/*
|
||||
* Updates *destination with new_value and returns true if it was changed or false
|
||||
@ -83,7 +85,7 @@ int get_unoccupied_y(Workspace *workspace, int col) {
|
||||
*
|
||||
*/
|
||||
void redecorate_window(xcb_connection_t *conn, Client *client) {
|
||||
if (client->container->mode == MODE_STACK) {
|
||||
if (client->container != NULL && client->container->mode == MODE_STACK) {
|
||||
render_container(conn, client->container);
|
||||
/* We clear the frame to generate exposure events, because the color used
|
||||
in drawing may be different */
|
||||
@ -100,47 +102,39 @@ void redecorate_window(xcb_connection_t *conn, Client *client) {
|
||||
void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
int decoration_height = font->height + 2 + 2;
|
||||
uint32_t background_color,
|
||||
text_color,
|
||||
border_color;
|
||||
struct Colortriple *color;
|
||||
|
||||
/* Clients without a container (docks) won’t get decorated */
|
||||
if (client->container == NULL)
|
||||
if (client->dock)
|
||||
return;
|
||||
|
||||
if (client->container->currently_focused == client) {
|
||||
LOG("redecorating child %08x\n", client->child);
|
||||
if (client_is_floating(client) || client->container->currently_focused == client) {
|
||||
/* Distinguish if the window is currently focused… */
|
||||
if (CUR_CELL->currently_focused == client)
|
||||
background_color = get_colorpixel(conn, "#285577");
|
||||
if (client_is_floating(client) || CUR_CELL->currently_focused == client)
|
||||
color = &(config.client.focused);
|
||||
/* …or if it is the focused window in a not focused container */
|
||||
else background_color = get_colorpixel(conn, "#555555");
|
||||
|
||||
text_color = get_colorpixel(conn, "#ffffff");
|
||||
border_color = get_colorpixel(conn, "#4c7899");
|
||||
} else {
|
||||
background_color = get_colorpixel(conn, "#222222");
|
||||
text_color = get_colorpixel(conn, "#888888");
|
||||
border_color = get_colorpixel(conn, "#333333");
|
||||
}
|
||||
else color = &(config.client.focused_inactive);
|
||||
} else color = &(config.client.unfocused);
|
||||
|
||||
/* Our plan is the following:
|
||||
- Draw a rect around the whole client in background_color
|
||||
- Draw a rect around the whole client in color->background
|
||||
- Draw two lines in a lighter color
|
||||
- Draw the window’s title
|
||||
*/
|
||||
|
||||
/* Draw a rectangle in background color around the window */
|
||||
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
|
||||
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
|
||||
|
||||
/* In stacking mode, we only render the rect for this specific decoration */
|
||||
if (client->container->mode == MODE_STACK) {
|
||||
xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
|
||||
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
|
||||
} else {
|
||||
if (client->container != NULL && client->container->mode == MODE_STACK) {
|
||||
/* We need to use the container’s width because it is the more recent value - when
|
||||
in stacking mode, clients get reconfigured only on demand (the not active client
|
||||
is not reconfigured), so the client’s rect.width would be wrong */
|
||||
xcb_rectangle_t rect = {0, 0, client->container->width, client->rect.height};
|
||||
xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
|
||||
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
|
||||
} else {
|
||||
xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
|
||||
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
|
||||
|
||||
/* Draw the inner background to have a black frame around clients (such as mplayer)
|
||||
@ -152,15 +146,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
||||
}
|
||||
|
||||
/* Draw the lines */
|
||||
xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
|
||||
xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
|
||||
2 + client->rect.width, offset + font->height + 3);
|
||||
xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
|
||||
xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3,
|
||||
client->rect.width - 3, offset + font->height + 3);
|
||||
|
||||
/* If the client has a title, we draw it */
|
||||
if (client->name != NULL) {
|
||||
/* Draw the font */
|
||||
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
|
||||
uint32_t values[] = { text_color, background_color, font->id };
|
||||
uint32_t values[] = { color->text, color->background, font->id };
|
||||
xcb_change_gc(conn, gc, mask, values);
|
||||
|
||||
/* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME,
|
||||
@ -179,18 +173,37 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
||||
* Pushes the client’s x and y coordinates to X11
|
||||
*
|
||||
*/
|
||||
static void reposition_client(xcb_connection_t *conn, Client *client) {
|
||||
void reposition_client(xcb_connection_t *conn, Client *client) {
|
||||
i3Screen *screen;
|
||||
|
||||
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
|
||||
/* Note: We can use a pointer to client->x like an array of uint32_ts
|
||||
because it is followed by client->y by definition */
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
|
||||
|
||||
if (!client_is_floating(client))
|
||||
return;
|
||||
|
||||
/* If the client is floating, we need to check if we moved it to a different workspace */
|
||||
if (client->workspace->screen == (screen = get_screen_containing(client->rect.x, client->rect.y)))
|
||||
return;
|
||||
|
||||
if (screen == NULL) {
|
||||
LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen);
|
||||
LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen);
|
||||
floating_assign_to_workspace(client, &workspaces[screen->current_workspace]);
|
||||
LOG("fixed that\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Pushes the client’s width/height to X11 and resizes the child window
|
||||
*
|
||||
*/
|
||||
static void resize_client(xcb_connection_t *conn, Client *client) {
|
||||
void resize_client(xcb_connection_t *conn, Client *client) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
|
||||
@ -335,8 +348,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
||||
XCB_CONFIG_WINDOW_STACK_MODE;
|
||||
|
||||
/* If there is no fullscreen client, we raise the stack window */
|
||||
if (container->workspace->fullscreen_client != NULL) {
|
||||
/* Raise the stack window, but keep it below the first floating client
|
||||
* and below the fullscreen client (if any) */
|
||||
Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients));
|
||||
if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) {
|
||||
mask |= XCB_CONFIG_WINDOW_SIBLING;
|
||||
values[4] = first_floating->frame;
|
||||
} else if (container->workspace->fullscreen_client != NULL) {
|
||||
mask |= XCB_CONFIG_WINDOW_SIBLING;
|
||||
values[4] = container->workspace->fullscreen_client->frame;
|
||||
}
|
||||
@ -344,9 +362,6 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
||||
xcb_configure_window(conn, stack_win->window, mask, values);
|
||||
}
|
||||
|
||||
/* Reconfigure the currently focused client, if necessary. It is the only visible one */
|
||||
client = container->currently_focused;
|
||||
|
||||
/* Render the decorations of all clients */
|
||||
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
|
||||
/* If the client is in fullscreen mode, it does not get reconfigured */
|
||||
@ -400,24 +415,10 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
i3Screen *screen = r_ws->screen;
|
||||
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
|
||||
uint32_t background_color[2],
|
||||
text_color[2],
|
||||
border_color[2],
|
||||
black;
|
||||
char label[3];
|
||||
|
||||
black = get_colorpixel(conn, "#000000");
|
||||
|
||||
background_color[SET_NORMAL] = get_colorpixel(conn, "#222222");
|
||||
text_color[SET_NORMAL] = get_colorpixel(conn, "#888888");
|
||||
border_color[SET_NORMAL] = get_colorpixel(conn, "#333333");
|
||||
|
||||
background_color[SET_FOCUSED] = get_colorpixel(conn, "#285577");
|
||||
text_color[SET_FOCUSED] = get_colorpixel(conn, "#ffffff");
|
||||
border_color[SET_FOCUSED] = get_colorpixel(conn, "#4c7899");
|
||||
|
||||
/* Fill the whole bar in black */
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
|
||||
xcb_rectangle_t rect = {0, 0, width, height};
|
||||
xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
|
||||
|
||||
@ -429,16 +430,17 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
|
||||
if (workspaces[c].screen != screen)
|
||||
continue;
|
||||
|
||||
int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
|
||||
struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) :
|
||||
&(config.bar.unfocused));
|
||||
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set],
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
|
||||
drawn * height, 1, height - 2, height - 2);
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set],
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, color->background,
|
||||
drawn * height + 1, 2, height - 4, height - 4);
|
||||
|
||||
snprintf(label, sizeof(label), "%d", c+1);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background);
|
||||
xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn * height + 5 /* X */,
|
||||
font->height + 1 /* Y = baseline of font */, label);
|
||||
drawn++;
|
||||
|
337
src/mainx.c
337
src/mainx.c
@ -31,6 +31,8 @@
|
||||
#include <xcb/xcb_icccm.h>
|
||||
#include <xcb/xinerama.h>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "data.h"
|
||||
#include "debug.h"
|
||||
@ -42,6 +44,7 @@
|
||||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "xinerama.h"
|
||||
#include "manage.h"
|
||||
|
||||
/* This is the path to i3, copied from argv[0] when starting up */
|
||||
char **start_argv;
|
||||
@ -52,6 +55,12 @@ Display *xkbdpy;
|
||||
/* The list of key bindings */
|
||||
struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
|
||||
|
||||
/* The list of exec-lines */
|
||||
struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
|
||||
|
||||
/* The list of assignments */
|
||||
struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
|
||||
|
||||
/* This is a list of Stack_Windows, global, for easier/faster access on expose events */
|
||||
struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
|
||||
|
||||
@ -63,273 +72,40 @@ xcb_atom_t atoms[NUM_ATOMS];
|
||||
int num_screens = 0;
|
||||
|
||||
/*
|
||||
* Do some sanity checks and then reparent the window.
|
||||
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
|
||||
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
|
||||
*
|
||||
*/
|
||||
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
|
||||
LOG("managing window.\n");
|
||||
xcb_drawable_t d = { window };
|
||||
xcb_get_geometry_cookie_t geomc;
|
||||
xcb_get_geometry_reply_t *geom;
|
||||
xcb_get_window_attributes_reply_t *attr = 0;
|
||||
|
||||
if (wa.tag == TAG_COOKIE) {
|
||||
/* Check if the window is mapped (it could be not mapped when intializing and
|
||||
calling manage_window() for every window) */
|
||||
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
|
||||
return;
|
||||
|
||||
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
|
||||
goto out;
|
||||
|
||||
wa.tag = TAG_VALUE;
|
||||
wa.u.override_redirect = attr->override_redirect;
|
||||
}
|
||||
|
||||
/* Don’t manage clients with the override_redirect flag */
|
||||
if (wa.u.override_redirect)
|
||||
goto out;
|
||||
|
||||
/* Check if the window is already managed */
|
||||
if (table_get(&by_child, window))
|
||||
goto out;
|
||||
|
||||
/* Get the initial geometry (position, size, …) */
|
||||
geomc = xcb_get_geometry(conn, d);
|
||||
if (!attr) {
|
||||
wa.tag = TAG_COOKIE;
|
||||
wa.u.cookie = xcb_get_window_attributes(conn, window);
|
||||
attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
|
||||
}
|
||||
geom = xcb_get_geometry_reply(conn, geomc, 0);
|
||||
if (attr && geom) {
|
||||
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
|
||||
geom->x, geom->y, geom->width, geom->height);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
|
||||
}
|
||||
|
||||
free(geom);
|
||||
out:
|
||||
free(attr);
|
||||
return;
|
||||
static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
|
||||
/* empty, because xcb_prepare_cb and xcb_check_cb are used */
|
||||
}
|
||||
|
||||
/*
|
||||
* reparent_window() gets called when a new window was opened and becomes a child of the root
|
||||
* window, or it gets called by us when we manage the already existing windows at startup.
|
||||
*
|
||||
* Essentially, this is the point where we take over control.
|
||||
* Flush before blocking (and waiting for new events)
|
||||
*
|
||||
*/
|
||||
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
|
||||
int16_t x, int16_t y, uint16_t width, uint16_t height) {
|
||||
|
||||
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[3];
|
||||
|
||||
/* We are interested in property changes */
|
||||
mask = XCB_CW_EVENT_MASK;
|
||||
values[0] = CHILD_EVENT_MASK;
|
||||
xcb_change_window_attributes(conn, child, mask, values);
|
||||
|
||||
/* Map the window first to avoid flickering */
|
||||
xcb_map_window(conn, child);
|
||||
|
||||
/* Place requests for properties ASAP */
|
||||
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
|
||||
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
|
||||
state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
|
||||
|
||||
Client *new = table_get(&by_child, child);
|
||||
|
||||
/* Events for already managed windows should already be filtered in manage_window() */
|
||||
assert(new == NULL);
|
||||
|
||||
LOG("reparenting new client\n");
|
||||
new = calloc(sizeof(Client), 1);
|
||||
new->force_reconfigure = true;
|
||||
|
||||
/* Update the data structures */
|
||||
Client *old_focused = CUR_CELL->currently_focused;
|
||||
|
||||
new->container = CUR_CELL;
|
||||
new->workspace = new->container->workspace;
|
||||
|
||||
new->frame = xcb_generate_id(conn);
|
||||
new->child = child;
|
||||
new->rect.width = width;
|
||||
new->rect.height = height;
|
||||
|
||||
mask = 0;
|
||||
|
||||
/* Don’t generate events for our new window, it should *not* be managed */
|
||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||
values[0] = 1;
|
||||
|
||||
/* We want to know when… */
|
||||
mask |= XCB_CW_EVENT_MASK;
|
||||
values[1] = FRAME_EVENT_MASK;
|
||||
|
||||
LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
|
||||
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
width = min(width, c_ws->rect.x + c_ws->rect.width);
|
||||
height = min(height, c_ws->rect.y + c_ws->rect.height);
|
||||
|
||||
Rect framerect = {x, y,
|
||||
width + 2 + 2, /* 2 px border at each side */
|
||||
height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
|
||||
|
||||
/* Yo dawg, I heard you like windows, so I create a window around your window… */
|
||||
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
|
||||
|
||||
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
|
||||
|
||||
/* Put the client inside the save set. Upon termination (whether killed or normal exit
|
||||
does not matter) of the window manager, these clients will be correctly reparented
|
||||
to their most closest living ancestor (= cleanup) */
|
||||
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
|
||||
|
||||
/* Generate a graphics context for the titlebar */
|
||||
new->titlegc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
|
||||
|
||||
/* Moves the original window into the new frame we've created for it */
|
||||
new->awaiting_useless_unmap = true;
|
||||
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
|
||||
if (xcb_request_check(conn, cookie) != NULL) {
|
||||
LOG("Could not reparent the window, aborting\n");
|
||||
xcb_destroy_window(conn, new->frame);
|
||||
free(new);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Put our data structure (Client) into the table */
|
||||
table_put(&by_parent, new->frame, new);
|
||||
table_put(&by_child, child, new);
|
||||
|
||||
/* We need to grab the mouse buttons for click to focus */
|
||||
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
|
||||
1 /* left mouse button */,
|
||||
XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
|
||||
|
||||
/* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
|
||||
xcb_atom_t *atom;
|
||||
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
||||
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
|
||||
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
|
||||
LOG("Window is a dock.\n");
|
||||
new->dock = true;
|
||||
new->titlebar_position = TITLEBAR_OFF;
|
||||
new->force_reconfigure = true;
|
||||
new->container = NULL;
|
||||
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
|
||||
}
|
||||
}
|
||||
|
||||
if (new->dock) {
|
||||
/* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
|
||||
uint32_t *strut;
|
||||
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
|
||||
if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
|
||||
/* We only use a subset of the provided values, namely the reserved space at the top/bottom
|
||||
of the screen. This is because the only possibility for bars is at to be at the top/bottom
|
||||
with maximum horizontal size.
|
||||
TODO: bars at the top */
|
||||
new->desired_height = strut[3];
|
||||
if (new->desired_height == 0) {
|
||||
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
|
||||
new->desired_height = height;
|
||||
}
|
||||
LOG("the client wants to be %d pixels high\n", new->desired_height);
|
||||
} else {
|
||||
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
|
||||
new->desired_height = height;
|
||||
}
|
||||
}
|
||||
|
||||
/* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
|
||||
if (CUR_CELL->workspace->fullscreen_client == NULL) {
|
||||
if (!new->dock) {
|
||||
CUR_CELL->currently_focused = new;
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
|
||||
}
|
||||
} else {
|
||||
/* If we are in fullscreen, we should lower the window to not be annoying */
|
||||
uint32_t values[] = { XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
|
||||
/* Insert into the currently active container, if it’s not a dock window */
|
||||
if (!new->dock) {
|
||||
/* Insert after the old active client, if existing. If it does not exist, the
|
||||
container is empty and it does not matter, where we insert it */
|
||||
if (old_focused != NULL && !old_focused->dock)
|
||||
CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
|
||||
else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
|
||||
|
||||
SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
|
||||
}
|
||||
|
||||
/* Check if the window already got the fullscreen hint set */
|
||||
xcb_atom_t *state;
|
||||
if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
|
||||
(state = xcb_get_property_value(preply)) != NULL)
|
||||
/* Check all set _NET_WM_STATEs */
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
|
||||
if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
|
||||
/* If the window got the fullscreen state, we just toggle fullscreen
|
||||
and don’t event bother to redraw the layout – that would not change
|
||||
anything anyways */
|
||||
toggle_fullscreen(conn, new);
|
||||
return;
|
||||
}
|
||||
|
||||
render_layout(conn);
|
||||
static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
|
||||
xcb_flush(evenths.c);
|
||||
}
|
||||
|
||||
/*
|
||||
* Go through all existing windows (if the window manager is restarted) and manage them
|
||||
* Instead of polling the X connection socket we leave this to
|
||||
* xcb_poll_for_event() which knows better than we can ever know.
|
||||
*
|
||||
*/
|
||||
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
|
||||
xcb_query_tree_reply_t *reply;
|
||||
int i, len;
|
||||
xcb_window_t *children;
|
||||
xcb_get_window_attributes_cookie_t *cookies;
|
||||
static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
|
||||
xcb_generic_event_t *event;
|
||||
|
||||
/* Get the tree of windows whose parent is the root window (= all) */
|
||||
if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
|
||||
return;
|
||||
|
||||
len = xcb_query_tree_children_length(reply);
|
||||
cookies = smalloc(len * sizeof(*cookies));
|
||||
|
||||
/* Request the window attributes for every window */
|
||||
children = xcb_query_tree_children(reply);
|
||||
for(i = 0; i < len; ++i)
|
||||
cookies[i] = xcb_get_window_attributes(conn, children[i]);
|
||||
|
||||
/* Call manage_window with the attributes for every window */
|
||||
for(i = 0; i < len; ++i) {
|
||||
window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
|
||||
manage_window(prophs, conn, children[i], wa);
|
||||
while ((event = xcb_poll_for_event(evenths.c)) != NULL) {
|
||||
xcb_event_handle(&evenths, event);
|
||||
free(event);
|
||||
}
|
||||
|
||||
free(reply);
|
||||
free(cookies);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[], char *env[]) {
|
||||
int i, screens, opt;
|
||||
char *override_configpath = NULL;
|
||||
bool autostart = true;
|
||||
xcb_connection_t *conn;
|
||||
xcb_property_handlers_t prophs;
|
||||
xcb_window_t root;
|
||||
@ -343,8 +119,12 @@ int main(int argc, char *argv[], char *env[]) {
|
||||
|
||||
start_argv = argv;
|
||||
|
||||
while ((opt = getopt(argc, argv, "c:v")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "c:va")) != -1) {
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
LOG("Autostart disabled using -a\n");
|
||||
autostart = false;
|
||||
break;
|
||||
case 'c':
|
||||
override_configpath = sstrdup(optarg);
|
||||
break;
|
||||
@ -365,10 +145,13 @@ int main(int argc, char *argv[], char *env[]) {
|
||||
memset(&evenths, 0, sizeof(xcb_event_handlers_t));
|
||||
memset(&prophs, 0, sizeof(xcb_property_handlers_t));
|
||||
|
||||
load_configuration(override_configpath);
|
||||
|
||||
conn = xcb_connect(NULL, &screens);
|
||||
|
||||
if (xcb_connection_has_error(conn))
|
||||
die("Cannot open display\n");
|
||||
|
||||
load_configuration(conn, override_configpath);
|
||||
|
||||
/* Place requests for the atoms we need as soon as possible */
|
||||
#define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
|
||||
|
||||
@ -380,6 +163,10 @@ int main(int argc, char *argv[], char *env[]) {
|
||||
REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
|
||||
REQUEST_ATOM(_NET_WM_DESKTOP);
|
||||
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
|
||||
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
|
||||
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
|
||||
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
|
||||
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
|
||||
REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
|
||||
REQUEST_ATOM(WM_PROTOCOLS);
|
||||
REQUEST_ATOM(WM_DELETE_WINDOW);
|
||||
@ -406,6 +193,27 @@ int main(int argc, char *argv[], char *env[]) {
|
||||
}
|
||||
/* end of ugliness */
|
||||
|
||||
/* Initialize event loop using libev */
|
||||
struct ev_loop *loop = ev_default_loop(0);
|
||||
if (loop == NULL)
|
||||
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
|
||||
|
||||
struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
|
||||
struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
|
||||
struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
|
||||
|
||||
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
|
||||
ev_io_start(loop, xcb_watcher);
|
||||
|
||||
ev_check_init(xcb_check, xcb_check_cb);
|
||||
ev_check_start(loop, xcb_check);
|
||||
|
||||
ev_prepare_init(xcb_prepare, xcb_prepare_cb);
|
||||
ev_prepare_start(loop, xcb_prepare);
|
||||
|
||||
/* Grab the server to delay any events until we enter the eventloop */
|
||||
xcb_grab_server(conn);
|
||||
|
||||
xcb_event_handlers_init(conn, &evenths);
|
||||
|
||||
/* DEBUG: Trap all events and print them */
|
||||
@ -483,6 +291,10 @@ int main(int argc, char *argv[], char *env[]) {
|
||||
GET_ATOM(_NET_WM_WINDOW_TYPE);
|
||||
GET_ATOM(_NET_WM_DESKTOP);
|
||||
GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
|
||||
GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
|
||||
GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
|
||||
GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
|
||||
GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
|
||||
GET_ATOM(_NET_WM_STRUT_PARTIAL);
|
||||
GET_ATOM(WM_PROTOCOLS);
|
||||
GET_ATOM(WM_DELETE_WINDOW);
|
||||
@ -495,9 +307,15 @@ int main(int argc, char *argv[], char *env[]) {
|
||||
/* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
|
||||
xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
|
||||
|
||||
/* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */
|
||||
xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL);
|
||||
|
||||
/* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
|
||||
xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
|
||||
|
||||
/* Watch WM_CLASS (= class of the window) */
|
||||
xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL);
|
||||
|
||||
/* Set up the atoms we support */
|
||||
check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
|
||||
ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
|
||||
@ -523,6 +341,15 @@ int main(int argc, char *argv[], char *env[]) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Autostarting exec-lines */
|
||||
struct Autostart *exec;
|
||||
if (autostart) {
|
||||
TAILQ_FOREACH(exec, &autostarts, autostarts) {
|
||||
LOG("auto-starting %s\n", exec->command);
|
||||
start_application(exec->command);
|
||||
}
|
||||
}
|
||||
|
||||
/* check for Xinerama */
|
||||
LOG("Checking for Xinerama...\n");
|
||||
initialize_xinerama(conn);
|
||||
@ -548,8 +375,12 @@ int main(int argc, char *argv[], char *env[]) {
|
||||
c_ws = &workspaces[screen->current_workspace];
|
||||
}
|
||||
|
||||
/* Enter xcb’s event handler */
|
||||
xcb_event_wait_for_event_loop(&evenths);
|
||||
/* Handle the events which arrived until now */
|
||||
xcb_check_cb(NULL, NULL, 0);
|
||||
|
||||
/* Ungrab the server to receive events and enter libev’s eventloop */
|
||||
xcb_ungrab_server(conn);
|
||||
ev_loop(loop, 0);
|
||||
|
||||
/* not reached */
|
||||
return 0;
|
||||
|
425
src/manage.c
Normal file
425
src/manage.c
Normal file
@ -0,0 +1,425 @@
|
||||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* src/manage.c: Contains all functions for initially managing new windows
|
||||
* (or existing ones on restart).
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
|
||||
#include "xcb.h"
|
||||
#include "data.h"
|
||||
#include "util.h"
|
||||
#include "i3.h"
|
||||
#include "table.h"
|
||||
#include "config.h"
|
||||
#include "handlers.h"
|
||||
#include "layout.h"
|
||||
#include "manage.h"
|
||||
#include "floating.h"
|
||||
#include "client.h"
|
||||
|
||||
/*
|
||||
* Go through all existing windows (if the window manager is restarted) and manage them
|
||||
*
|
||||
*/
|
||||
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
|
||||
xcb_query_tree_reply_t *reply;
|
||||
int i, len;
|
||||
xcb_window_t *children;
|
||||
xcb_get_window_attributes_cookie_t *cookies;
|
||||
|
||||
/* Get the tree of windows whose parent is the root window (= all) */
|
||||
if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
|
||||
return;
|
||||
|
||||
len = xcb_query_tree_children_length(reply);
|
||||
cookies = smalloc(len * sizeof(*cookies));
|
||||
|
||||
/* Request the window attributes for every window */
|
||||
children = xcb_query_tree_children(reply);
|
||||
for(i = 0; i < len; ++i)
|
||||
cookies[i] = xcb_get_window_attributes(conn, children[i]);
|
||||
|
||||
/* Call manage_window with the attributes for every window */
|
||||
for(i = 0; i < len; ++i) {
|
||||
window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
|
||||
manage_window(prophs, conn, children[i], wa);
|
||||
}
|
||||
|
||||
free(reply);
|
||||
free(cookies);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do some sanity checks and then reparent the window.
|
||||
*
|
||||
*/
|
||||
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
|
||||
LOG("managing window.\n");
|
||||
xcb_drawable_t d = { window };
|
||||
xcb_get_geometry_cookie_t geomc;
|
||||
xcb_get_geometry_reply_t *geom;
|
||||
xcb_get_window_attributes_reply_t *attr = 0;
|
||||
|
||||
if (wa.tag == TAG_COOKIE) {
|
||||
/* Check if the window is mapped (it could be not mapped when intializing and
|
||||
calling manage_window() for every window) */
|
||||
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
|
||||
return;
|
||||
|
||||
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
|
||||
goto out;
|
||||
|
||||
wa.tag = TAG_VALUE;
|
||||
wa.u.override_redirect = attr->override_redirect;
|
||||
}
|
||||
|
||||
/* Don’t manage clients with the override_redirect flag */
|
||||
if (wa.u.override_redirect)
|
||||
goto out;
|
||||
|
||||
/* Check if the window is already managed */
|
||||
if (table_get(&by_child, window))
|
||||
goto out;
|
||||
|
||||
/* Get the initial geometry (position, size, …) */
|
||||
geomc = xcb_get_geometry(conn, d);
|
||||
if (!attr) {
|
||||
wa.tag = TAG_COOKIE;
|
||||
wa.u.cookie = xcb_get_window_attributes(conn, window);
|
||||
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
|
||||
return;
|
||||
}
|
||||
if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
|
||||
goto out;
|
||||
|
||||
/* Reparent the window and add it to our list of managed windows */
|
||||
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
|
||||
geom->x, geom->y, geom->width, geom->height);
|
||||
|
||||
/* Generate callback events for every property we watch */
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
|
||||
|
||||
free(geom);
|
||||
out:
|
||||
free(attr);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* reparent_window() gets called when a new window was opened and becomes a child of the root
|
||||
* window, or it gets called by us when we manage the already existing windows at startup.
|
||||
*
|
||||
* Essentially, this is the point where we take over control.
|
||||
*
|
||||
*/
|
||||
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
|
||||
int16_t x, int16_t y, uint16_t width, uint16_t height) {
|
||||
|
||||
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
|
||||
utf8_title_cookie, title_cookie, class_cookie;
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[3];
|
||||
uint16_t original_height = height;
|
||||
|
||||
/* We are interested in property changes */
|
||||
mask = XCB_CW_EVENT_MASK;
|
||||
values[0] = CHILD_EVENT_MASK;
|
||||
xcb_change_window_attributes(conn, child, mask, values);
|
||||
|
||||
/* Map the window first to avoid flickering */
|
||||
xcb_map_window(conn, child);
|
||||
|
||||
/* Place requests for properties ASAP */
|
||||
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
|
||||
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
|
||||
state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
|
||||
utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
|
||||
title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
|
||||
class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
|
||||
|
||||
Client *new = table_get(&by_child, child);
|
||||
|
||||
/* Events for already managed windows should already be filtered in manage_window() */
|
||||
assert(new == NULL);
|
||||
|
||||
LOG("reparenting new client\n");
|
||||
LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
|
||||
new = calloc(sizeof(Client), 1);
|
||||
new->force_reconfigure = true;
|
||||
|
||||
/* Update the data structures */
|
||||
Client *old_focused = CUR_CELL->currently_focused;
|
||||
|
||||
new->container = CUR_CELL;
|
||||
new->workspace = new->container->workspace;
|
||||
|
||||
/* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
|
||||
width = max(width, 75);
|
||||
height = max(height, 50);
|
||||
|
||||
new->frame = xcb_generate_id(conn);
|
||||
new->child = child;
|
||||
new->rect.width = width;
|
||||
new->rect.height = height;
|
||||
/* Pre-initialize the values for floating */
|
||||
new->floating_rect.x = -1;
|
||||
new->floating_rect.width = width;
|
||||
new->floating_rect.height = height;
|
||||
|
||||
mask = 0;
|
||||
|
||||
/* Don’t generate events for our new window, it should *not* be managed */
|
||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||
values[0] = 1;
|
||||
|
||||
/* We want to know when… */
|
||||
mask |= XCB_CW_EVENT_MASK;
|
||||
values[1] = FRAME_EVENT_MASK;
|
||||
|
||||
LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
|
||||
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
width = min(width, c_ws->rect.x + c_ws->rect.width);
|
||||
height = min(height, c_ws->rect.y + c_ws->rect.height);
|
||||
|
||||
Rect framerect = {x, y,
|
||||
width + 2 + 2, /* 2 px border at each side */
|
||||
height + 2 + 2 + font->height}; /* 2 px border plus font’s 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 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) */
|
||||
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
|
||||
|
||||
/* Generate a graphics context for the titlebar */
|
||||
new->titlegc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
|
||||
|
||||
/* Moves the original window into the new frame we've created for it */
|
||||
new->awaiting_useless_unmap = true;
|
||||
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
|
||||
if (xcb_request_check(conn, cookie) != NULL) {
|
||||
LOG("Could not reparent the window, aborting\n");
|
||||
xcb_destroy_window(conn, new->frame);
|
||||
free(new);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Put our data structure (Client) into the table */
|
||||
table_put(&by_parent, new->frame, new);
|
||||
table_put(&by_child, child, new);
|
||||
|
||||
/* We need to grab the mouse buttons for click to focus */
|
||||
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
|
||||
1 /* left mouse button */,
|
||||
XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
|
||||
|
||||
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
|
||||
1 /* left mouse button */, XCB_MOD_MASK_1);
|
||||
|
||||
/* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
|
||||
xcb_atom_t *atom;
|
||||
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
||||
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
|
||||
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
|
||||
LOG("Window is a dock.\n");
|
||||
new->dock = true;
|
||||
new->titlebar_position = TITLEBAR_OFF;
|
||||
new->force_reconfigure = true;
|
||||
new->container = NULL;
|
||||
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
|
||||
/* If it’s a dock we can’t make it float, so we break */
|
||||
break;
|
||||
} else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
|
||||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
|
||||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
|
||||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
|
||||
/* Set the dialog window to automatically floating, will be used below */
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
LOG("dialog/utility/toolbar/splash window, automatically floating\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (new->workspace->auto_float) {
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
LOG("workspace is in autofloat mode, setting floating\n");
|
||||
}
|
||||
|
||||
if (new->dock) {
|
||||
/* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
|
||||
uint32_t *strut;
|
||||
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
|
||||
if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
|
||||
/* We only use a subset of the provided values, namely the reserved space at the top/bottom
|
||||
of the screen. This is because the only possibility for bars is at to be at the top/bottom
|
||||
with maximum horizontal size.
|
||||
TODO: bars at the top */
|
||||
new->desired_height = strut[3];
|
||||
if (new->desired_height == 0) {
|
||||
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
|
||||
new->desired_height = original_height;
|
||||
}
|
||||
LOG("the client wants to be %d pixels high\n", new->desired_height);
|
||||
} else {
|
||||
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
|
||||
new->desired_height = original_height;
|
||||
}
|
||||
} else {
|
||||
/* If it’s not a dock, we can check on which workspace we should put it. */
|
||||
|
||||
/* Firstly, we need to get the window’s class / title. We asked for the properties at the
|
||||
* top of this function, get them now and pass them to our callback function for window class / title
|
||||
* changes. It is important that the client was already inserted into the by_child table,
|
||||
* because the callbacks won’t work otherwise. */
|
||||
preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
|
||||
handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
|
||||
|
||||
preply = xcb_get_property_reply(conn, title_cookie, NULL);
|
||||
handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
|
||||
|
||||
preply = xcb_get_property_reply(conn, class_cookie, NULL);
|
||||
handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
|
||||
|
||||
LOG("DEBUG: should have all infos now\n");
|
||||
struct Assignment *assign;
|
||||
TAILQ_FOREACH(assign, &assignments, assignments) {
|
||||
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
|
||||
continue;
|
||||
|
||||
if (assign->floating) {
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
LOG("Assignment matches, putting client into floating mode\n");
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
|
||||
assign->windowclass_title, assign->workspace);
|
||||
|
||||
if (c_ws->screen->current_workspace == (assign->workspace-1)) {
|
||||
LOG("We are already there, no need to do anything\n");
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("Changin container/workspace and unmapping the client\n");
|
||||
Workspace *t_ws = &(workspaces[assign->workspace-1]);
|
||||
if (t_ws->screen == NULL) {
|
||||
LOG("initializing new workspace, setting num to %d\n", assign->workspace);
|
||||
t_ws->screen = c_ws->screen;
|
||||
/* Copy the dimensions from the virtual screen */
|
||||
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
|
||||
}
|
||||
|
||||
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
|
||||
new->workspace = t_ws;
|
||||
old_focused = new->container->currently_focused;
|
||||
|
||||
xcb_unmap_window(conn, new->frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (CUR_CELL->workspace->fullscreen_client != NULL) {
|
||||
if (new->container == CUR_CELL) {
|
||||
/* If we are in fullscreen, we should lower the window to not be annoying */
|
||||
uint32_t values[] = { XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
} else if (!new->dock) {
|
||||
/* Focus the new window if we’re 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 it’s not a dock window */
|
||||
if (!new->dock && !client_is_floating(new)) {
|
||||
/* Insert after the old active client, if existing. If it does not exist, the
|
||||
container is empty and it does not matter, where we insert it */
|
||||
if (old_focused != NULL && !old_focused->dock)
|
||||
CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
|
||||
else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
|
||||
|
||||
if (new->container->workspace->fullscreen_client != NULL)
|
||||
SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
|
||||
else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
|
||||
|
||||
client_set_below_floating(conn, new);
|
||||
}
|
||||
|
||||
if (client_is_floating(new)) {
|
||||
SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
|
||||
|
||||
/* Add the client to the list of floating clients for its workspace */
|
||||
TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
|
||||
|
||||
new->container = NULL;
|
||||
|
||||
new->floating_rect.x = new->rect.x = x;
|
||||
new->floating_rect.y = new->rect.y = y;
|
||||
new->rect.width = new->floating_rect.width + 2 + 2;
|
||||
new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
|
||||
LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
|
||||
new->floating_rect.x, new->floating_rect.y,
|
||||
new->floating_rect.width, new->floating_rect.height);
|
||||
LOG("outer rect (%d, %d) size (%d, %d)\n",
|
||||
new->rect.x, new->rect.y, new->rect.width, new->rect.height);
|
||||
|
||||
/* Make sure it is on top of the other windows */
|
||||
xcb_raise_window(conn, new->frame);
|
||||
reposition_client(conn, new);
|
||||
resize_client(conn, new);
|
||||
/* redecorate_window flushes */
|
||||
redecorate_window(conn, new);
|
||||
}
|
||||
|
||||
new->initialized = true;
|
||||
|
||||
/* Check if the window already got the fullscreen hint set */
|
||||
xcb_atom_t *state;
|
||||
if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
|
||||
(state = xcb_get_property_value(preply)) != NULL)
|
||||
/* Check all set _NET_WM_STATEs */
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
|
||||
if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
|
||||
continue;
|
||||
/* If the window got the fullscreen state, we just toggle fullscreen
|
||||
and don’t event bother to redraw the layout – that would not change
|
||||
anything anyways */
|
||||
client_toggle_fullscreen(conn, new);
|
||||
return;
|
||||
}
|
||||
|
||||
render_layout(conn);
|
||||
}
|
77
src/resize.c
77
src/resize.c
@ -24,6 +24,9 @@
|
||||
#include "xcb.h"
|
||||
#include "debug.h"
|
||||
#include "layout.h"
|
||||
#include "xinerama.h"
|
||||
#include "config.h"
|
||||
#include "floating.h"
|
||||
|
||||
/*
|
||||
* Renders the resize window between the first/second container and resizes
|
||||
@ -33,8 +36,14 @@
|
||||
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
|
||||
resize_orientation_t orientation, xcb_button_press_event_t *event) {
|
||||
int new_position;
|
||||
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
|
||||
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
||||
i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
|
||||
if (screen == NULL) {
|
||||
LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
|
||||
|
||||
/* FIXME: horizontal resizing causes empty spaces to exist */
|
||||
if (orientation == O_HORIZONTAL) {
|
||||
@ -57,9 +66,9 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
|
||||
Rect helprect;
|
||||
if (orientation == O_VERTICAL) {
|
||||
helprect.x = event->root_x;
|
||||
helprect.y = 0;
|
||||
helprect.y = screen->rect.y;
|
||||
helprect.width = 2;
|
||||
helprect.height = root_screen->height_in_pixels;
|
||||
helprect.height = screen->rect.height;
|
||||
new_position = event->root_x;
|
||||
} else {
|
||||
helprect.x = 0;
|
||||
@ -70,7 +79,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
|
||||
}
|
||||
|
||||
mask = XCB_CW_BACK_PIXEL;
|
||||
values[0] = get_colorpixel(conn, "#4c7899");
|
||||
values[0] = config.client.focused.border;
|
||||
|
||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||
values[1] = 1;
|
||||
@ -82,52 +91,32 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
|
||||
|
||||
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
|
||||
|
||||
xcb_grab_pointer(conn, false, root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION,
|
||||
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, grabwin, XCB_NONE, XCB_CURRENT_TIME);
|
||||
|
||||
xcb_flush(conn);
|
||||
|
||||
xcb_generic_event_t *inside_event;
|
||||
/* I’ve always wanted to have my own eventhandler… */
|
||||
while ((inside_event = xcb_wait_for_event(conn))) {
|
||||
/* Same as get_event_handler in xcb */
|
||||
int nr = inside_event->response_type;
|
||||
if (nr == 0) {
|
||||
/* An error occured */
|
||||
handle_event(NULL, conn, inside_event);
|
||||
free(inside_event);
|
||||
continue;
|
||||
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
LOG("new x = %d, y = %d\n", new_x, new_y);
|
||||
if (orientation == O_VERTICAL) {
|
||||
/* Check if the new coordinates are within screen boundaries */
|
||||
if (new_x > (screen->rect.x + screen->rect.width - 25) ||
|
||||
new_x < (screen->rect.x + 25))
|
||||
return;
|
||||
|
||||
values[0] = new_position = new_x;
|
||||
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
|
||||
} else {
|
||||
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
|
||||
new_y < (screen->rect.y + 25))
|
||||
return;
|
||||
|
||||
values[0] = new_position = new_y;
|
||||
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
|
||||
}
|
||||
assert(nr < 256);
|
||||
nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
|
||||
assert(nr >= 2);
|
||||
|
||||
/* Check if we need to escape this loop */
|
||||
if (nr == XCB_BUTTON_RELEASE)
|
||||
break;
|
||||
|
||||
switch (nr) {
|
||||
case XCB_MOTION_NOTIFY:
|
||||
if (orientation == O_VERTICAL) {
|
||||
values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x;
|
||||
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
|
||||
} else {
|
||||
values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y;
|
||||
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
break;
|
||||
default:
|
||||
LOG("Passing to original handler\n");
|
||||
/* Use original handler */
|
||||
xcb_event_handle(&evenths, inside_event);
|
||||
break;
|
||||
}
|
||||
free(inside_event);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
||||
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback);
|
||||
|
||||
xcb_destroy_window(conn, helpwin);
|
||||
xcb_destroy_window(conn, grabwin);
|
||||
xcb_flush(conn);
|
||||
|
@ -43,6 +43,7 @@ void init_table() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
workspaces[i].screen = NULL;
|
||||
workspaces[i].num = i;
|
||||
TAILQ_INIT(&(workspaces[i].floating_clients));
|
||||
expand_table_cols(&(workspaces[i]));
|
||||
expand_table_rows(&(workspaces[i]));
|
||||
}
|
||||
|
277
src/util.c
277
src/util.c
@ -27,6 +27,7 @@
|
||||
#include "layout.h"
|
||||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "client.h"
|
||||
|
||||
static iconv_t conversion_descriptor = 0;
|
||||
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
|
||||
@ -213,34 +214,17 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
|
||||
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
|
||||
if (rc == (size_t)-1) {
|
||||
perror("Converting to UCS-2 failed");
|
||||
*real_strlen = 0;
|
||||
if (real_strlen != NULL)
|
||||
*real_strlen = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*real_strlen = ((buffer_size - output_size) / 2) - 1;
|
||||
if (real_strlen != NULL)
|
||||
*real_strlen = ((buffer_size - output_size) / 2) - 1;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the given client from the container, either because it will be inserted into another
|
||||
* one or because it was unmapped
|
||||
*
|
||||
*/
|
||||
void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container) {
|
||||
CIRCLEQ_REMOVE(&(container->clients), client, clients);
|
||||
|
||||
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
|
||||
|
||||
/* If the container will be empty now and is in stacking mode, we need to
|
||||
unmap the stack_win */
|
||||
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
|
||||
struct Stack_Window *stack_win = &(container->stack_win);
|
||||
stack_win->rect.height = 0;
|
||||
xcb_unmap_window(conn, stack_win->window);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the client which comes next in focus stack (= was selected before) for
|
||||
* the given container, optionally excluding the given client.
|
||||
@ -270,19 +254,43 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) {
|
||||
/* Ignore notify events because they would cause focus to be changed */
|
||||
ignore_enter_notify_forall(conn, u_ws, true);
|
||||
|
||||
/* Unmap all clients of the current workspace */
|
||||
/* Unmap all clients of the given workspace */
|
||||
int unmapped_clients = 0;
|
||||
FOR_TABLE(u_ws)
|
||||
CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
|
||||
LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
|
||||
xcb_unmap_window(conn, client->frame);
|
||||
unmapped_clients++;
|
||||
}
|
||||
|
||||
/* If we did not unmap any clients, the workspace is empty and we can destroy it */
|
||||
if (unmapped_clients == 0)
|
||||
u_ws->screen = NULL;
|
||||
/* To find floating clients, we traverse the focus stack */
|
||||
SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
|
||||
if (!client_is_floating(client))
|
||||
continue;
|
||||
|
||||
/* Unmap the stack windows on the current workspace, if any */
|
||||
LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
|
||||
|
||||
xcb_unmap_window(conn, client->frame);
|
||||
unmapped_clients++;
|
||||
}
|
||||
|
||||
/* If we did not unmap any clients, the workspace is empty and we can destroy it, at least
|
||||
* if it is not the current workspace. */
|
||||
if (unmapped_clients == 0 && u_ws != c_ws) {
|
||||
/* Re-assign the workspace of all dock clients which use this workspace */
|
||||
Client *dock;
|
||||
LOG("workspace %p is empty\n", u_ws);
|
||||
SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) {
|
||||
if (dock->workspace != u_ws)
|
||||
continue;
|
||||
|
||||
LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
|
||||
dock->workspace = c_ws;
|
||||
}
|
||||
u_ws->screen = NULL;
|
||||
}
|
||||
|
||||
/* Unmap the stack windows on the given workspace, if any */
|
||||
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
|
||||
if (stack_win->container->workspace == u_ws)
|
||||
xcb_unmap_window(conn, stack_win->window);
|
||||
@ -302,7 +310,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
|
||||
return;
|
||||
|
||||
/* Store the old client */
|
||||
Client *old_client = CUR_CELL->currently_focused;
|
||||
Client *old_client = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
|
||||
/* Check if the focus needs to be changed at all */
|
||||
if (!set_anyways && (old_client == client)) {
|
||||
@ -313,47 +321,54 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
|
||||
/* Store current_row/current_col */
|
||||
c_ws->current_row = current_row;
|
||||
c_ws->current_col = current_col;
|
||||
c_ws = client->container->workspace;
|
||||
c_ws = client->workspace;
|
||||
/* Load current_col/current_row if we switch to a client without a container */
|
||||
current_col = c_ws->current_col;
|
||||
current_row = c_ws->current_row;
|
||||
|
||||
/* Update container */
|
||||
client->container->currently_focused = client;
|
||||
if (client->container != NULL) {
|
||||
client->container->currently_focused = client;
|
||||
|
||||
current_col = client->container->col;
|
||||
current_row = client->container->row;
|
||||
current_col = client->container->col;
|
||||
current_row = client->container->row;
|
||||
}
|
||||
|
||||
LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name);
|
||||
/* Set focus to the entered window, and flush xcb buffer immediately */
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
|
||||
//xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
|
||||
|
||||
/* Get the client which was last focused in this particular container, it may be a different
|
||||
one than old_client */
|
||||
Client *last_focused = get_last_focused_client(conn, client->container, NULL);
|
||||
if (client->container != NULL) {
|
||||
/* Get the client which was last focused in this particular container, it may be a different
|
||||
one than old_client */
|
||||
Client *last_focused = get_last_focused_client(conn, client->container, NULL);
|
||||
|
||||
/* In stacking containers, raise the client in respect to the one which was focused before */
|
||||
if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) {
|
||||
/* We need to get the client again, this time excluding the current client, because
|
||||
* we might have just gone into stacking mode and need to raise */
|
||||
Client *last_focused = get_last_focused_client(conn, client->container, client);
|
||||
/* In stacking containers, raise the client in respect to the one which was focused before */
|
||||
if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) {
|
||||
/* We need to get the client again, this time excluding the current client, because
|
||||
* we might have just gone into stacking mode and need to raise */
|
||||
Client *last_focused = get_last_focused_client(conn, client->container, client);
|
||||
|
||||
if (last_focused != NULL) {
|
||||
LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
|
||||
uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
if (last_focused != NULL) {
|
||||
LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
|
||||
uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If it is the same one as old_client, we save us the unnecessary redecorate */
|
||||
if ((last_focused != NULL) && (last_focused != old_client))
|
||||
redecorate_window(conn, last_focused);
|
||||
/* If it is the same one as old_client, we save us the unnecessary redecorate */
|
||||
if ((last_focused != NULL) && (last_focused != old_client))
|
||||
redecorate_window(conn, last_focused);
|
||||
}
|
||||
|
||||
/* If we’re in stacking mode, this renders the container to update changes in the title
|
||||
bars and to raise the focused client */
|
||||
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
|
||||
redecorate_window(conn, old_client);
|
||||
|
||||
SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients);
|
||||
SLIST_INSERT_HEAD(&(client->container->workspace->focus_stack), client, focus_clients);
|
||||
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
|
||||
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
|
||||
|
||||
/* redecorate_window flushes, so we don’t need to */
|
||||
redecorate_window(conn, client);
|
||||
@ -390,7 +405,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
|
||||
/* When entering stacking mode, we need to open a window on which we can draw the
|
||||
title bars of the clients, it has height 1 because we don’t bother here with
|
||||
calculating the correct height - it will be adjusted when rendering anyways. */
|
||||
Rect rect = {container->x, container->y, container->width, 1 };
|
||||
Rect rect = {container->x, container->y, container->width, 1};
|
||||
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[2];
|
||||
@ -429,129 +444,79 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
|
||||
|
||||
render_layout(conn);
|
||||
|
||||
if (container->currently_focused != NULL)
|
||||
set_focus(conn, container->currently_focused, true);
|
||||
}
|
||||
if (container->currently_focused != NULL) {
|
||||
/* We need to make sure that this client is above *each* of the
|
||||
* other clients in this container */
|
||||
Client *last_focused = get_last_focused_client(conn, container, container->currently_focused);
|
||||
|
||||
/*
|
||||
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
|
||||
* selecting it
|
||||
*
|
||||
*/
|
||||
void warp_pointer_into(xcb_connection_t *conn, Client *client) {
|
||||
int mid_x = client->rect.width / 2,
|
||||
mid_y = client->rect.height / 2;
|
||||
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
|
||||
}
|
||||
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
|
||||
if (client == container->currently_focused || client == last_focused)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Toggles fullscreen mode for the given client. It updates the data structures and
|
||||
* reconfigures (= resizes/moves) the client and its frame to the full size of the
|
||||
* screen. When leaving fullscreen, re-rendering the layout is forced.
|
||||
*
|
||||
*/
|
||||
void toggle_fullscreen(xcb_connection_t *conn, Client *client) {
|
||||
/* clients without a container (docks) cannot be focused */
|
||||
assert(client->container != NULL);
|
||||
|
||||
Workspace *workspace = client->container->workspace;
|
||||
|
||||
if (!client->fullscreen) {
|
||||
if (workspace->fullscreen_client != NULL) {
|
||||
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
|
||||
return;
|
||||
LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
|
||||
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, client->frame,
|
||||
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
client->fullscreen = true;
|
||||
workspace->fullscreen_client = client;
|
||||
LOG("Entering fullscreen mode...\n");
|
||||
/* We just entered fullscreen mode, let’s configure the window */
|
||||
uint32_t mask = XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT;
|
||||
uint32_t values[4] = {workspace->rect.x,
|
||||
workspace->rect.y,
|
||||
workspace->rect.width,
|
||||
workspace->rect.height};
|
||||
|
||||
LOG("child itself will be at %dx%d with size %dx%d\n",
|
||||
values[0], values[1], values[2], values[3]);
|
||||
if (last_focused != NULL) {
|
||||
LOG("Putting last_focused directly underneath the currently focused\n");
|
||||
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, last_focused->frame,
|
||||
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
|
||||
xcb_configure_window(conn, client->frame, mask, values);
|
||||
|
||||
/* Child’s coordinates are relative to the parent (=frame) */
|
||||
values[0] = 0;
|
||||
values[1] = 0;
|
||||
xcb_configure_window(conn, client->child, mask, values);
|
||||
|
||||
/* Raise the window */
|
||||
values[0] = XCB_STACK_MODE_ABOVE;
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
||||
Rect child_rect = workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
fake_configure_notify(conn, child_rect, client->child);
|
||||
} else {
|
||||
LOG("leaving fullscreen mode\n");
|
||||
client->fullscreen = false;
|
||||
workspace->fullscreen_client = NULL;
|
||||
/* Because the coordinates of the window haven’t changed, it would not be
|
||||
re-configured if we don’t set the following flag */
|
||||
client->force_reconfigure = true;
|
||||
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
|
||||
render_layout(conn);
|
||||
set_focus(conn, container->currently_focused, true);
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
|
||||
* Gets the first matching client for the given window class/window title.
|
||||
* If the paramater specific is set to a specific client, only this one
|
||||
* will be checked.
|
||||
*
|
||||
*/
|
||||
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
|
||||
xcb_get_property_cookie_t cookie;
|
||||
xcb_get_wm_protocols_reply_t protocols;
|
||||
bool result = false;
|
||||
Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
|
||||
Client *specific) {
|
||||
char *to_class, *to_title, *to_title_ucs = NULL;
|
||||
int to_title_ucs_len = 0;
|
||||
Client *matching = NULL;
|
||||
|
||||
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
|
||||
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
|
||||
return false;
|
||||
to_class = sstrdup(window_classtitle);
|
||||
|
||||
/* Check if the client’s protocols have the requested atom set */
|
||||
for (uint32_t i = 0; i < protocols.atoms_len; i++)
|
||||
if (protocols.atoms[i] == atom)
|
||||
result = true;
|
||||
|
||||
xcb_get_wm_protocols_reply_wipe(&protocols);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
|
||||
*
|
||||
*/
|
||||
void kill_window(xcb_connection_t *conn, Client *window) {
|
||||
/* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
|
||||
if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
|
||||
LOG("Killing window the hard way\n");
|
||||
xcb_kill_client(conn, window->child);
|
||||
return;
|
||||
/* If a title was specified, split both strings at the slash */
|
||||
if ((to_title = strstr(to_class, "/")) != NULL) {
|
||||
*(to_title++) = '\0';
|
||||
/* Convert to UCS-2 */
|
||||
to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
|
||||
}
|
||||
|
||||
xcb_client_message_event_t ev;
|
||||
/* If we were given a specific client we only check if that one matches */
|
||||
if (specific != NULL) {
|
||||
if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len))
|
||||
matching = specific;
|
||||
goto done;
|
||||
}
|
||||
|
||||
memset(&ev, 0, sizeof(xcb_client_message_event_t));
|
||||
LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
|
||||
for (int workspace = 0; workspace < 10; workspace++) {
|
||||
if (workspaces[workspace].screen == NULL)
|
||||
continue;
|
||||
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = window->child;
|
||||
ev.type = atoms[WM_PROTOCOLS];
|
||||
ev.format = 32;
|
||||
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
|
||||
ev.data.data32[1] = XCB_CURRENT_TIME;
|
||||
Client *client;
|
||||
SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) {
|
||||
LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name);
|
||||
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
|
||||
continue;
|
||||
|
||||
LOG("Sending WM_DELETE to the client\n");
|
||||
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
|
||||
xcb_flush(conn);
|
||||
matching = client;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
free(to_class);
|
||||
FREE(to_title_ucs);
|
||||
return matching;
|
||||
}
|
||||
|
49
src/xcb.c
49
src/xcb.c
@ -22,7 +22,6 @@
|
||||
#include "xcb.h"
|
||||
|
||||
TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
|
||||
SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels;
|
||||
unsigned int xcb_numlock_mask;
|
||||
|
||||
/*
|
||||
@ -74,42 +73,14 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) {
|
||||
*
|
||||
*/
|
||||
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
|
||||
/* Lookup this colorpixel in the cache */
|
||||
struct Colorpixel *colorpixel;
|
||||
SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels)
|
||||
if (strcmp(colorpixel->hex, hex) == 0)
|
||||
return colorpixel->pixel;
|
||||
|
||||
#define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255)
|
||||
char strgroups[3][3] = {{hex[1], hex[2], '\0'},
|
||||
{hex[3], hex[4], '\0'},
|
||||
{hex[5], hex[6], '\0'}};
|
||||
int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)),
|
||||
RGB_8_TO_16(strtol(strgroups[1], NULL, 16)),
|
||||
RGB_8_TO_16(strtol(strgroups[2], NULL, 16))};
|
||||
uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
|
||||
(strtol(strgroups[1], NULL, 16)),
|
||||
(strtol(strgroups[2], NULL, 16))};
|
||||
|
||||
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
||||
xcb_alloc_color_reply_t *reply;
|
||||
|
||||
reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap,
|
||||
rgb16[0], rgb16[1], rgb16[2]), NULL);
|
||||
|
||||
if (!reply) {
|
||||
LOG("Could not allocate color\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
uint32_t pixel = reply->pixel;
|
||||
free(reply);
|
||||
|
||||
/* Store the result in the cache */
|
||||
struct Colorpixel *cache_pixel = scalloc(sizeof(struct Colorpixel));
|
||||
cache_pixel->hex = sstrdup(hex);
|
||||
cache_pixel->pixel = pixel;
|
||||
|
||||
SLIST_INSERT_HEAD(&(colorpixels), cache_pixel, colorpixels);
|
||||
|
||||
return pixel;
|
||||
return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
|
||||
}
|
||||
|
||||
/*
|
||||
@ -267,10 +238,11 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
|
||||
/* For now, we only use the first keysymbol. */
|
||||
xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
|
||||
xcb_keycode_t numlock = *numlock_syms;
|
||||
free(numlock_syms);
|
||||
#endif
|
||||
|
||||
/* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
|
||||
for (mask = 0; mask < sizeof(masks); mask++)
|
||||
for (mask = 0; mask < 8; mask++)
|
||||
for (i = 0; i < reply->keycodes_per_modifier; i++)
|
||||
if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
|
||||
xcb_numlock_mask = masks[mask];
|
||||
@ -278,3 +250,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
|
||||
xcb_key_symbols_free(keysyms);
|
||||
free(reply);
|
||||
}
|
||||
|
||||
/*
|
||||
* Raises the given window (typically client->frame) above all other windows
|
||||
*
|
||||
*/
|
||||
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
|
||||
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
|
||||
xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
|
@ -100,6 +100,31 @@ i3Screen *get_screen_most(direction_t direction) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
workspace->screen = screen;
|
||||
screen->current_workspace = workspace->num;
|
||||
|
||||
/* Create a bar for each screen */
|
||||
Rect bar_rect = {screen->rect.x,
|
||||
screen->rect.height - (font->height + 6),
|
||||
screen->rect.x + screen->rect.width,
|
||||
font->height + 6};
|
||||
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
|
||||
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
|
||||
screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
|
||||
screen->bargc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
|
||||
|
||||
SLIST_INIT(&(screen->dock_clients));
|
||||
|
||||
/* Copy dimensions */
|
||||
memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
|
||||
LOG("that is virtual screen at %d x %d with %d x %d\n",
|
||||
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fills virtual_screens with exactly one screen with width/height of the whole X server.
|
||||
*
|
||||
@ -114,6 +139,10 @@ static void disable_xinerama(xcb_connection_t *conn) {
|
||||
s->rect.width = root_screen->width_in_pixels;
|
||||
s->rect.height = root_screen->height_in_pixels;
|
||||
|
||||
num_screens = 1;
|
||||
s->num = 0;
|
||||
initialize_screen(conn, s, &(workspaces[0]));
|
||||
|
||||
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
|
||||
|
||||
xinerama_enabled = false;
|
||||
@ -170,31 +199,6 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
|
||||
}
|
||||
}
|
||||
|
||||
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
workspace->screen = screen;
|
||||
screen->current_workspace = workspace->num;
|
||||
|
||||
/* Create a bar for each screen */
|
||||
Rect bar_rect = {screen->rect.x,
|
||||
screen->rect.height - (font->height + 6),
|
||||
screen->rect.x + screen->rect.width,
|
||||
font->height + 6};
|
||||
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
|
||||
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
|
||||
screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
|
||||
screen->bargc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
|
||||
|
||||
SLIST_INIT(&(screen->dock_clients));
|
||||
|
||||
/* Copy dimensions */
|
||||
memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
|
||||
LOG("that is virtual screen at %d x %d with %d x %d\n",
|
||||
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
|
||||
}
|
||||
|
||||
/*
|
||||
* We have just established a connection to the X server and need the initial Xinerama
|
||||
* information to setup workspaces for each screen.
|
||||
|
Reference in New Issue
Block a user