Implement floating (please test and find bugs)

Details which are missing: A command to hide/show all floating clients,
moving/resizing clients with your mouse holding Mod1 (click anywhere
in the client, not just on its borders), resize/move by keyboard, select
next/previous client by keyboard
next
Michael Stapelberg 2009-05-23 16:34:03 +02:00
parent 6bb0c82588
commit 5b8e2ecb18
12 changed files with 525 additions and 60 deletions

View File

@ -15,6 +15,9 @@ bind Mod1+43 s
# Default (Mod1+e) # Default (Mod1+e)
bind Mod1+26 d bind Mod1+26 d
# Toggle tiling/floating of the current window
bind Mod1+Shift+65 t
# Focus (Mod1+j/k/l/;) # Focus (Mod1+j/k/l/;)
bind Mod1+44 h bind Mod1+44 h
bind Mod1+45 j bind Mod1+45 j

View File

@ -253,6 +253,8 @@ struct Client {
/* x, y, width, height of the frame */ /* x, y, width, height of the frame */
Rect rect; Rect rect;
/* Position in floating mode and in tiling mode are saved separately */
Rect floating_rect;
/* x, y, width, height of the child (relative to its frame) */ /* x, y, width, height of the child (relative to its frame) */
Rect child_rect; Rect child_rect;
@ -282,6 +284,9 @@ struct Client {
/* fullscreen is pretty obvious */ /* fullscreen is pretty obvious */
bool fullscreen; bool fullscreen;
/* floating? (= not in tiling layout) */
bool floating;
/* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */ /* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */
enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position; enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position;

37
include/floating.h Normal file
View File

@ -0,0 +1,37 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _FLOATING_H
#define _FLOATING_H
/**
* Enters 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.
*
*/
void toggle_floating_mode(xcb_connection_t *conn, 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);
/**
* 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);
#endif

View File

@ -36,6 +36,18 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
*/ */
void redecorate_window(xcb_connection_t *conn, Client *client); void redecorate_window(xcb_connection_t *conn, Client *client);
/**
* Pushes the clients x and y coordinates to X11
*
*/
void reposition_client(xcb_connection_t *conn, Client *client);
/**
* Pushes the clients width/height to X11 and resizes the child window
*
*/
void resize_client(xcb_connection_t *conn, Client *client);
/** /**
* Renders the given container. Is called by render_layout() or individually (for example * Renders the given container. Is called by render_layout() or individually (for example
* when focus changes in a stacking container) * when focus changes in a stacking container)

View File

@ -126,4 +126,10 @@ void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client);
*/ */
void xcb_get_numlock_mask(xcb_connection_t *conn); void xcb_get_numlock_mask(xcb_connection_t *conn);
/**
* Raises the given window (typically client->frame) above all other windows
*
*/
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
#endif #endif

View File

@ -24,6 +24,7 @@
#include "i3.h" #include "i3.h"
#include "xinerama.h" #include "xinerama.h"
#include "client.h" #include "client.h"
#include "floating.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */ /* If this container is empty, were done */
@ -418,6 +419,49 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
render_layout(conn); render_layout(conn);
} }
static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) {
/* t_ws (to workspace) is just a container pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]);
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;
}
}
/* Remove from focus stack */
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
if (client->workspace->fullscreen_client == client)
client->workspace->fullscreen_client = NULL;
/* Insert into destination focus stack */
client->workspace = t_ws;
SLIST_INSERT_HEAD(&(t_ws->focus_stack), client, focus_clients);
if (client->fullscreen)
t_ws->fullscreen_client = client;
/* If were moving it to an invisible screen, we need to unmap it */
if (t_ws->screen->current_workspace != t_ws->num) {
LOG("This workspace is not visible, unmapping\n");
xcb_unmap_window(conn, client->frame);
}
LOG("done\n");
render_layout(conn);
}
/* /*
* Moves the currently selected window to the given workspace * Moves the currently selected window to the given workspace
* *
@ -463,7 +507,9 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
if (container->workspace->fullscreen_client == current_client) if (container->workspace->fullscreen_client == current_client)
container->workspace->fullscreen_client = NULL; container->workspace->fullscreen_client = NULL;
/* TODO: insert it to the correct position */
CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
if (current_client->fullscreen) if (current_client->fullscreen)
t_ws->fullscreen_client = current_client; t_ws->fullscreen_client = current_client;
@ -564,6 +610,14 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients) CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients)
xcb_map_window(conn, client->frame); xcb_map_window(conn, client->frame);
/* Map all floating clients */
SLIST_FOREACH(client, &(c_ws->focus_stack), focus_clients) {
if (!client->floating)
continue;
xcb_map_window(conn, client->frame);
}
/* Map all stack windows, if any */ /* Map all stack windows, if any */
struct Stack_Window *stack_win; struct Stack_Window *stack_win;
SLIST_FOREACH(stack_win, &stack_wins, stack_windows) SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
@ -683,6 +737,12 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
*/ */
void parse_command(xcb_connection_t *conn, const char *command) { void parse_command(xcb_connection_t *conn, const char *command) {
LOG("--- parsing command \"%s\" ---\n", 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 */ /* Hmm, just to be sure */
if (command[0] == '\0') if (command[0] == '\0')
return; return;
@ -708,17 +768,17 @@ void parse_command(xcb_connection_t *conn, const char *command) {
} }
if (STARTS_WITH(command, "kill")) { if (STARTS_WITH(command, "kill")) {
if (CUR_CELL->currently_focused == NULL) { if (last_focused == NULL) {
LOG("There is no window to kill\n"); LOG("There is no window to kill\n");
return; return;
} }
LOG("Killing current window\n"); LOG("Killing current window\n");
client_kill(conn, CUR_CELL->currently_focused); client_kill(conn, last_focused);
return; return;
} }
/* Is it a jump to a specified workspae, row, col? */ /* Is it a jump to a specified workspace, row, col? */
if (STARTS_WITH(command, "jump ")) { if (STARTS_WITH(command, "jump ")) {
const char *arguments = command + strlen("jump "); const char *arguments = command + strlen("jump ");
if (arguments[0] == '"') if (arguments[0] == '"')
@ -736,19 +796,34 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Is it 'f' for fullscreen? */ /* Is it 'f' for fullscreen? */
if (command[0] == 'f') { if (command[0] == 'f') {
if (CUR_CELL->currently_focused == NULL) if (last_focused == NULL)
return; return;
toggle_fullscreen(conn, CUR_CELL->currently_focused); toggle_fullscreen(conn, last_focused);
return; return;
} }
/* Is it just 's' for stacking or 'd' for default? */ /* Is it just 's' for stacking or 'd' for default? */
if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) { if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
if (last_focused->floating) {
LOG("not switching, this is a floating client\n");
return;
}
LOG("Switching mode for current container\n"); LOG("Switching mode for current container\n");
switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT)); switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
return; return;
} }
/* Is it 't' for toggle tiling/floating? */
if (command[0] == 't') {
if (last_focused == NULL) {
LOG("Cannot toggle tiling/floating: workspace empty\n");
return;
}
toggle_floating_mode(conn, last_focused);
return;
}
enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW; enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW;
/* Is it a <with>? */ /* Is it a <with>? */
@ -764,7 +839,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
} }
} }
/* It's a normal <cmd> */ /* Its a normal <cmd> */
char *rest = NULL; char *rest = NULL;
enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
direction_t direction; direction_t direction;
@ -793,7 +868,14 @@ void parse_command(xcb_connection_t *conn, const char *command) {
} }
if (*rest == '\0') { if (*rest == '\0') {
move_current_window_to_workspace(conn, workspace); if (last_focused->floating)
move_floating_window_to_workspace(conn, last_focused, workspace);
else move_current_window_to_workspace(conn, workspace);
return;
}
if (last_focused->floating) {
LOG("Not performing because this is a floating window\n");
return; return;
} }

267
src/floating.c Normal file
View File

@ -0,0 +1,267 @@
/*
* 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 "data.h"
#include "util.h"
#include "xcb.h"
#include "debug.h"
#include "layout.h"
/* On which border was the dragging initiated? */
typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t;
/* Callback for dragging */
typedef void(*callback_t)(xcb_connection_t*, Client*, border_t, Rect*, xcb_button_press_event_t*, uint32_t, uint32_t);
/* Forward definitions */
static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
border_t border, callback_t callback);
/*
* 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.
*
*/
void toggle_floating_mode(xcb_connection_t *conn, Client *client) {
Container *con = client->container;
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 (!next_tiling->floating)
break;
/* If there are no tiling clients on this workspace, there can only be one
* container: the first one */
if (next_tiling == SLIST_END(&(client->workspace->focus_stack)))
con = client->workspace->table[0][0];
else con = next_tiling->container;
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 = false;
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;
render_container(conn, con);
xcb_flush(conn);
return;
}
LOG("Entering floating for client %08x\n", client->child);
/* Remove the client of its container */
CIRCLEQ_REMOVE(&(con->clients), client, clients);
client->container = NULL;
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);
}
client->floating = true;
/* Initialize the floating position from the position in tiling mode, if this
* client never was floating (width == 0) */
if (client->floating_rect.width == 0) {
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
LOG("(%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 */
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);
}
/*
* Callback for resizing windows
*
*/
static void resize_callback(xcb_connection_t *conn, Client *client, border_t border, Rect *old_rect,
xcb_button_press_event_t *event, uint32_t new_x, uint32_t new_y) {
switch (border) {
case BORDER_RIGHT:
client->rect.width = old_rect->width + (new_x - event->root_x);
break;
case BORDER_BOTTOM:
client->rect.height = old_rect->height + (new_y - event->root_y);
break;
case BORDER_TOP:
client->rect.y = old_rect->y + (new_y - event->root_y);
client->rect.height = old_rect->height + (event->root_y - new_y);
break;
case BORDER_LEFT:
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.width = old_rect->width + (event->root_x - new_x);
break;
}
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
}
/*
* 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;
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 > 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, border, resize_callback);
return 1;
}
static void drag_window_callback(xcb_connection_t *conn, Client *client, border_t border, Rect *old_rect,
xcb_button_press_event_t *event, 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);
xcb_flush(conn);
}
/*
* 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");
drag_pointer(conn, client, event, 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).
*
*/
static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
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;
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 */
XCB_NONE, /* confine_to = in which window should the cursor stay */
XCB_NONE, /* dont display a special cursor */
XCB_CURRENT_TIME);
/* Go into our own event loop */
xcb_flush(conn);
xcb_generic_event_t *inside_event;
/* Ive always wanted to have my own eventhandler… */
while ((inside_event = xcb_wait_for_event(conn))) {
/* Same as get_event_handler in xcb */
int nr = inside_event->response_type;
if (nr == 0) {
/* An error occured */
handle_event(NULL, conn, inside_event);
free(inside_event);
continue;
}
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:
new_x = ((xcb_motion_notify_event_t*)inside_event)->root_x;
new_y = ((xcb_motion_notify_event_t*)inside_event)->root_y;
callback(conn, client, border, &old_rect, event, new_x, new_y);
break;
default:
LOG("Passing to original handler\n");
/* Use original handler */
xcb_event_handle(&evenths, inside_event);
break;
}
free(inside_event);
}
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
xcb_flush(conn);
}

View File

@ -34,6 +34,7 @@
#include "resize.h" #include "resize.h"
#include "client.h" #include "client.h"
#include "manage.h" #include "manage.h"
#include "floating.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it, /* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when since itd trigger an infinite loop of switching between the different windows when
@ -322,7 +323,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
Container *con = client->container; Container *con = client->container;
int first, second; int first, second;
if (con == NULL) { if (client->dock) {
LOG("dock. done.\n"); LOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn); xcb_flush(conn);
@ -334,6 +335,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
if (!border_click) { if (!border_click) {
LOG("client. done.\n"); LOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
/* Floating clients should be raised on click */
if (client->floating)
xcb_raise_window(conn, client->frame);
xcb_flush(conn); xcb_flush(conn);
return 1; return 1;
} }
@ -342,9 +346,22 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
i3Font *font = load_font(conn, config.font); i3Font *font = load_font(conn, config.font);
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
LOG("click on titlebar\n"); LOG("click on titlebar\n");
/* Floating clients can be dragged by grabbing their titlebar */
if (client->floating) {
/* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
xcb_flush(conn);
floating_drag_window(conn, client, event);
}
return 1; return 1;
} }
if (client->floating)
return floating_border_click(conn, client, event);
if (event->event_y < 2) { if (event->event_y < 2) {
/* This was a press on the top border */ /* This was a press on the top border */
if (con->row == 0) if (con->row == 0)
@ -508,6 +525,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
if (client->name != NULL) if (client->name != NULL)
free(client->name); free(client->name);
/* Clients without a container are either floating or dock windows */
if (client->container != NULL) { if (client->container != NULL) {
Container *con = client->container; Container *con = client->container;
@ -524,6 +542,8 @@ 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 */ /* Only if this is the active container, we need to really change focus */
if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
set_focus(conn, con->currently_focused, true); set_focus(conn, con->currently_focused, true);
} else if (client->floating) {
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
} }
if (client->dock) { if (client->dock) {
@ -543,13 +563,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
} }
/* Lets see how many clients there are left on the workspace to delete it if its empty */ /* Lets see how many clients there are left on the workspace to delete it if its empty */
bool workspace_empty = true; bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack));
FOR_TABLE(client->workspace) Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL);
if (!CIRCLEQ_EMPTY(&(client->workspace->table[cols][rows]->clients))) {
workspace_empty = false;
break;
}
/* If this workspace is currently active, we dont delete it */
i3Screen *screen; i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->current_workspace == client->workspace->num) { if (screen->current_workspace == client->workspace->num) {
@ -564,6 +581,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
render_layout(conn); render_layout(conn);
/* Ensure the focus is set to the next client in the focus stack */
if (to_focus != NULL)
set_focus(conn, to_focus, true);
return 1; return 1;
} }
@ -755,7 +776,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
return 1; 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); decorate_window(conn, client, client->frame, client->titlegc, 0);
else { else {
uint32_t background_color; uint32_t background_color;

View File

@ -83,7 +83,7 @@ int get_unoccupied_y(Workspace *workspace, int col) {
* *
*/ */
void redecorate_window(xcb_connection_t *conn, Client *client) { 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); render_container(conn, client->container);
/* We clear the frame to generate exposure events, because the color used /* We clear the frame to generate exposure events, because the color used
in drawing may be different */ in drawing may be different */
@ -105,12 +105,13 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
border_color; border_color;
/* Clients without a container (docks) wont get decorated */ /* Clients without a container (docks) wont get decorated */
if (client->container == NULL) if (client->dock)
return; return;
if (client->container->currently_focused == client) { LOG("redecorating child %08x\n", client->child);
if (client->floating || client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */ /* Distinguish if the window is currently focused… */
if (CUR_CELL->currently_focused == client) if (client->floating || CUR_CELL->currently_focused == client)
background_color = get_colorpixel(conn, "#285577"); background_color = get_colorpixel(conn, "#285577");
/* …or if it is the focused window in a not focused container */ /* …or if it is the focused window in a not focused container */
else background_color = get_colorpixel(conn, "#555555"); else background_color = get_colorpixel(conn, "#555555");
@ -133,14 +134,14 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color); xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
/* In stacking mode, we only render the rect for this specific decoration */ /* In stacking mode, we only render the rect for this specific decoration */
if (client->container->mode == MODE_STACK) { if (client->container != NULL && 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 {
/* We need to use the containers width because it is the more recent value - when /* We need to use the containers width because it is the more recent value - when
in stacking mode, clients get reconfigured only on demand (the not active client in stacking mode, clients get reconfigured only on demand (the not active client
is not reconfigured), so the clients rect.width would be wrong */ is not reconfigured), so the clients rect.width would be wrong */
xcb_rectangle_t rect = {0, 0, client->container->width, client->rect.height}; xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
} else {
xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
/* Draw the inner background to have a black frame around clients (such as mplayer) /* Draw the inner background to have a black frame around clients (such as mplayer)
@ -179,7 +180,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
* Pushes the clients x and y coordinates to X11 * Pushes the clients x and y coordinates to X11
* *
*/ */
static void reposition_client(xcb_connection_t *conn, Client *client) { void reposition_client(xcb_connection_t *conn, Client *client) {
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); 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 /* Note: We can use a pointer to client->x like an array of uint32_ts
because it is followed by client->y by definition */ because it is followed by client->y by definition */
@ -190,7 +191,7 @@ static void reposition_client(xcb_connection_t *conn, Client *client) {
* Pushes the clients width/height to X11 and resizes the child window * Pushes the clients width/height to X11 and resizes the child window
* *
*/ */
static void resize_client(xcb_connection_t *conn, Client *client) { void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font); 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); LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);

View File

@ -27,6 +27,7 @@
#include "handlers.h" #include "handlers.h"
#include "layout.h" #include "layout.h"
#include "manage.h" #include "manage.h"
#include "floating.h"
/* /*
* Go through all existing windows (if the window manager is restarted) and manage them * Go through all existing windows (if the window manager is restarted) and manage them
@ -333,6 +334,18 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
/* Ensure that it is below all floating clients */
Client *first_floating;
SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients)
if (first_floating->floating)
break;
if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) {
LOG("Setting below floating\n");
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
} }
/* Check if the window already got the fullscreen hint set */ /* Check if the window already got the fullscreen hint set */

View File

@ -262,6 +262,15 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) {
unmapped_clients++; unmapped_clients++;
} }
/* To find floating clients, we traverse the focus stack */
SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
if (!client->floating)
continue;
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 we did not unmap any clients, the workspace is empty and we can destroy it */
if (unmapped_clients == 0) { if (unmapped_clients == 0) {
/* Re-assign the workspace of all dock clients which use this workspace */ /* Re-assign the workspace of all dock clients which use this workspace */
@ -296,7 +305,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
return; return;
/* Store the old client */ /* 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 */ /* Check if the focus needs to be changed at all */
if (!set_anyways && (old_client == client)) { if (!set_anyways && (old_client == client)) {
@ -307,47 +316,51 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
/* Store current_row/current_col */ /* Store current_row/current_col */
c_ws->current_row = current_row; c_ws->current_row = current_row;
c_ws->current_col = current_col; c_ws->current_col = current_col;
c_ws = client->container->workspace; c_ws = client->workspace;
/* Update container */ /* Update container */
client->container->currently_focused = client; if (client->container != NULL) {
client->container->currently_focused = client;
current_col = client->container->col; current_col = client->container->col;
current_row = client->container->row; current_row = client->container->row;
}
LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name); 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 */ /* 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_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); //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 if (client->container != NULL) {
one than old_client */ /* Get the client which was last focused in this particular container, it may be a different
Client *last_focused = get_last_focused_client(conn, client->container, NULL); 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 */ /* 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) { 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 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 */ * we might have just gone into stacking mode and need to raise */
Client *last_focused = get_last_focused_client(conn, client->container, client); Client *last_focused = get_last_focused_client(conn, client->container, client);
if (last_focused != NULL) { if (last_focused != NULL) {
LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; 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); 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 it is the same one as old_client, we save us the unnecessary redecorate */
if ((last_focused != NULL) && (last_focused != old_client)) if ((last_focused != NULL) && (last_focused != old_client))
redecorate_window(conn, last_focused); redecorate_window(conn, last_focused);
}
/* If were in stacking mode, this renders the container to update changes in the title /* If were in stacking mode, this renders the container to update changes in the title
bars and to raise the focused client */ bars and to raise the focused client */
if ((old_client != NULL) && (old_client != client) && !old_client->dock) if ((old_client != NULL) && (old_client != client) && !old_client->dock)
redecorate_window(conn, old_client); redecorate_window(conn, old_client);
SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients); SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
SLIST_INSERT_HEAD(&(client->container->workspace->focus_stack), client, focus_clients); SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
/* redecorate_window flushes, so we dont need to */ /* redecorate_window flushes, so we dont need to */
redecorate_window(conn, client); redecorate_window(conn, client);
@ -521,18 +534,14 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl
if (workspaces[workspace].screen == NULL) if (workspaces[workspace].screen == NULL)
continue; continue;
FOR_TABLE(&(workspaces[workspace])) { Client *client;
Container *con = workspaces[workspace].table[cols][rows]; SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) {
Client *client; 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;
CIRCLEQ_FOREACH(client, &(con->clients), clients) { matching = client;
LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); goto done;
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue;
matching = client;
goto done;
}
} }
} }

View File

@ -278,3 +278,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
xcb_key_symbols_free(keysyms); xcb_key_symbols_free(keysyms);
free(reply); 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);
}