Implement putting clients onto specific workspaces ("assign" in the configfile)

This closes ticket #39
This commit is contained in:
Michael Stapelberg 2009-05-16 17:32:36 +02:00
parent 3ab4ecdb01
commit e79cca8f72
12 changed files with 461 additions and 195 deletions

View File

@ -1,5 +1,5 @@
all: hacking-howto.html debugging.html all: hacking-howto.html debugging.html userguide.html
hacking-howto.html: hacking-howto hacking-howto.html: hacking-howto
asciidoc -a toc -n $< asciidoc -a toc -n $<
@ -7,6 +7,8 @@ hacking-howto.html: hacking-howto
debugging.html: debugging debugging.html: debugging
asciidoc -n $< asciidoc -n $<
userguide.html: userguide
asciidoc -a toc -n $<
clean: clean:
rm -f */*.{aux,log,toc,bm,pdf,dvi} rm -f */*.{aux,log,toc,bm,pdf,dvi}

59
docs/userguide Normal file
View File

@ -0,0 +1,59 @@
i3 Users Guide
===============
Michael Stapelberg <michael+i3@stapelberg.de>
May 2009
This document contains all information you need to configuring and using the i3 window
manager. If it does not, please contact me on IRC, Jabber or E-Mail and Ill help you out.
== Configuring i3
TODO: document the other options, implement variables before
terminal::
Specifies the terminal emulator program you prefer. It will be started by default when
you press Mod1+Enter, but you can overwrite this. Refer to it as +$terminal+ to keep things
modular.
font::
Specifies the default font you want i3 to use. Use an X core font descriptor here, like
+-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can use +xfontsel(1)+
to pick one.
=== Keyboard bindings
TODO
*Syntax*:
--------------------------------
bind [Modifiers+]keycode command
--------------------------------
*Examples*:
--------------------------------
# Fullscreen
bind Mod1+41 f
# Restart
bind Mod1+Shift+27 restart
--------------------------------
=== Automatically putting clients on specific workspaces
It is recommended that you match on window classes whereever possible because some applications
first create their window and then care about setting the correct title. Firefox with Vimperator
comes to mind, as the window starts up being named Firefox and only when Vimperator is loaded,
the title changes. As i3 will get the title as soon as the application maps the window (mapping
means actually displaying it on the screen), youd need to have to match on Firefox in this case.
*Syntax*:
----------------------------------------------------
assign ["]window class[/window title]["] [→] workspace
----------------------------------------------------
*Examples*:
----------------------
assign urxvt 2
assign urxvt → 2
assign "urxvt" → 2
assign "urxvt/VIM" → 3
----------------------

47
include/client.h Normal file
View File

@ -0,0 +1,47 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include "data.h"
#ifndef _CLIENT_H
#define _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);
/**
* 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);
/**
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void client_kill(xcb_connection_t *conn, Client *window);
/**
* 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);
#endif

View File

@ -48,7 +48,6 @@ typedef struct Font i3Font;
typedef struct Container Container; typedef struct Container Container;
typedef struct Client Client; typedef struct Client Client;
typedef struct Binding Binding; typedef struct Binding Binding;
typedef struct Autostart Autostart;
typedef struct Workspace Workspace; typedef struct Workspace Workspace;
typedef struct Rect Rect; typedef struct Rect Rect;
typedef struct Screen i3Screen; typedef struct Screen i3Screen;
@ -208,6 +207,17 @@ struct Autostart {
TAILQ_ENTRY(Autostart) autostarts; TAILQ_ENTRY(Autostart) autostarts;
}; };
/*
* Holds an assignment for a given window class/title to a specific workspace
* (see src/config.c)
*
*/
struct Assignment {
char *windowclass_title;
int workspace;
TAILQ_ENTRY(Assignment) assignments;
};
/* /*
* Data structure for cached font information: * Data structure for cached font information:
* - font id in X11 (load it once) * - font id in X11 (load it once)

View File

@ -26,6 +26,7 @@ extern char **start_argv;
extern Display *xkbdpy; extern Display *xkbdpy;
extern TAILQ_HEAD(bindings_head, Binding) bindings; extern TAILQ_HEAD(bindings_head, Binding) bindings;
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
extern TAILQ_HEAD(assignments_head, Assignment) assignments;
extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
extern xcb_event_handlers_t evenths; extern xcb_event_handlers_t evenths;
extern int num_screens; extern int num_screens;

View File

@ -123,13 +123,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
*/ */
char *convert_utf8_to_ucs2(char *input, int *real_strlen); char *convert_utf8_to_ucs2(char *input, int *real_strlen);
/**
* 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);
/** /**
* Returns the client which comes next in focus stack (= was selected before) for * Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client. * the given container, optionally excluding the given client.
@ -169,13 +162,6 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container);
*/ */
void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
/**
* 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);
/** /**
* Toggles fullscreen mode for the given client. It updates the data structures and * 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 * reconfigures (= resizes/moves) the client and its frame to the full size of the
@ -185,9 +171,12 @@ void warp_pointer_into(xcb_connection_t *conn, Client *client);
void toggle_fullscreen(xcb_connection_t *conn, Client *client); void toggle_fullscreen(xcb_connection_t *conn, Client *client);
/** /**
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_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.
* *
*/ */
void kill_window(xcb_connection_t *conn, Client *window); Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
Client *specific);
#endif #endif

133
src/client.c Normal file
View File

@ -0,0 +1,133 @@
/*
* 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 <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "data.h"
#include "i3.h"
#include "xcb.h"
#include "util.h"
#include "queue.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) {
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);
}
}
/*
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
int mid_x = client->rect.width / 2,
mid_y = client->rect.height / 2;
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
}
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
*/
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
xcb_get_property_cookie_t cookie;
xcb_get_wm_protocols_reply_t protocols;
bool result = false;
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
return false;
/* Check if the clients protocols have the requested atom set */
for (uint32_t i = 0; i < protocols.atoms_len; i++)
if (protocols.atoms[i] == atom)
result = true;
xcb_get_wm_protocols_reply_wipe(&protocols);
return result;
}
/*
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void client_kill(xcb_connection_t *conn, Client *window) {
/* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
LOG("Killing window the hard way\n");
xcb_kill_client(conn, window->child);
return;
}
xcb_client_message_event_t ev;
memset(&ev, 0, sizeof(xcb_client_message_event_t));
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = window->child;
ev.type = atoms[WM_PROTOCOLS];
ev.format = 32;
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
ev.data.data32[1] = XCB_CURRENT_TIME;
LOG("Sending WM_DELETE to the client\n");
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
xcb_flush(conn);
}
/*
* Checks if the given window class and title match the given client
* Window title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
char *to_title_ucs, int to_title_ucs_len) {
/* Check if the given class is part of the window class */
if (strcasestr(client->window_class, to_class) == NULL)
return false;
/* If no title was given, were done */
if (to_title == NULL)
return true;
if (client->name_len > -1) {
/* UCS-2 converted window titles */
if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
return false;
} else {
/* Legacy hints */
if (strcasestr(client->name, to_title) == NULL)
return false;
}
return true;
}

View File

@ -23,6 +23,7 @@
#include "layout.h" #include "layout.h"
#include "i3.h" #include "i3.h"
#include "xinerama.h" #include "xinerama.h"
#include "client.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 */
@ -240,7 +241,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
} }
/* Remove it from the old container and put it into the new one */ /* 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);
if (new->currently_focused != NULL) if (new->currently_focused != NULL)
CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients); CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
@ -458,7 +459,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
assert(to_container != NULL); assert(to_container != NULL);
remove_client_from_container(conn, current_client, container); client_remove_from_container(conn, current_client, container);
if (container->workspace->fullscreen_client == current_client) if (container->workspace->fullscreen_client == current_client)
container->workspace->fullscreen_client = NULL; container->workspace->fullscreen_client = NULL;
@ -538,7 +539,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
if (CUR_CELL->currently_focused != NULL) { if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true); set_focus(conn, CUR_CELL->currently_focused, true);
if (need_warp) { if (need_warp) {
warp_pointer_into(conn, CUR_CELL->currently_focused); client_warp_pointer_into(conn, CUR_CELL->currently_focused);
xcb_flush(conn); xcb_flush(conn);
} }
} }
@ -574,7 +575,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
if (CUR_CELL->currently_focused != NULL) { if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true); set_focus(conn, CUR_CELL->currently_focused, true);
if (need_warp) { if (need_warp) {
warp_pointer_into(conn, CUR_CELL->currently_focused); client_warp_pointer_into(conn, CUR_CELL->currently_focused);
xcb_flush(conn); xcb_flush(conn);
} }
} else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); } else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
@ -582,35 +583,6 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
render_layout(conn); render_layout(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.
*
*/
static 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 (strcasestr(client->window_class, to_class) == NULL)
return false;
/* If no title was given, were done */
if (to_title == NULL)
return true;
if (client->name_len > -1) {
/* UCS-2 converted window titles */
if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
return false;
} else {
/* Legacy hints */
if (strcasestr(client->name, to_title) == NULL)
return false;
}
return true;
}
/* /*
* Jumps to the given window class / title. * Jumps to the given window class / title.
* Title is matched using strstr, that is, matches if it appears anywhere * Title is matched using strstr, that is, matches if it appears anywhere
@ -619,44 +591,22 @@ static bool client_matches_class_name(Client *client, char *to_class, char *to_t
* *
*/ */
static void jump_to_window(xcb_connection_t *conn, const char *arguments) { static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
char *to_class, *to_title, *to_title_ucs = NULL; char *classtitle;
int to_title_ucs_len;
/* The first character is a quote, this was checked before */
to_class = sstrdup(arguments+1);
/* The last character is a quote, we just set it to NULL */
to_class[strlen(to_class)-1] = '\0';
/* 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);
}
LOG("Should jump to class \"%s\" / title \"%s\"\n", to_class, to_title);
for (int workspace = 0; workspace < 10; workspace++) {
if (workspaces[workspace].screen == NULL)
continue;
FOR_TABLE(&(workspaces[workspace])) {
Container *con = workspaces[workspace].table[cols][rows];
Client *client; Client *client;
CIRCLEQ_FOREACH(client, &(con->clients), clients) { /* The first character is a quote, this was checked before */
LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); classtitle = sstrdup(arguments+1);
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) /* The last character is a quote, we just set it to NULL */
continue; 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); set_focus(conn, client, true);
goto done;
}
}
}
done:
free(to_class);
FREE(to_title_ucs);
} }
/* /*
@ -763,7 +713,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
} }
LOG("Killing current window\n"); LOG("Killing current window\n");
kill_window(conn, CUR_CELL->currently_focused); client_kill(conn, CUR_CELL->currently_focused);
return; return;
} }

View File

@ -87,7 +87,7 @@ void load_configuration(const char *override_configpath) {
/* exec-lines (autostart) */ /* exec-lines (autostart) */
if (strcasecmp(key, "exec") == 0) { if (strcasecmp(key, "exec") == 0) {
Autostart *new = smalloc(sizeof(Autostart)); struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup(value); new->command = sstrdup(value);
TAILQ_INSERT_TAIL(&autostarts, new, autostarts); TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
continue; continue;
@ -133,6 +133,44 @@ void load_configuration(const char *override_configpath) {
continue; 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("Malformatted assignment, couldn't find finishing 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 (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 = smalloc(sizeof(struct Assignment));
new->windowclass_title = class_title;
new->workspace = atoi(target);
TAILQ_INSERT_TAIL(&assignments, new, assignments);
continue;
}
fprintf(stderr, "Unknown configfile option: %s\n", key); fprintf(stderr, "Unknown configfile option: %s\n", key);
exit(1); exit(1);
} }

View File

@ -32,6 +32,7 @@
#include "config.h" #include "config.h"
#include "queue.h" #include "queue.h"
#include "resize.h" #include "resize.h"
#include "client.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
@ -501,7 +502,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
con->workspace->fullscreen_client = NULL; con->workspace->fullscreen_client = NULL;
/* Remove the client from the list of clients */ /* Remove the client from the list of clients */
remove_client_from_container(conn, client, con); client_remove_from_container(conn, client, con);
/* Set focus to the last focused client in this container */ /* Set focus to the last focused client in this container */
con->currently_focused = get_last_focused_client(conn, con, NULL); con->currently_focused = get_last_focused_client(conn, con, NULL);

View File

@ -55,6 +55,9 @@ struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
/* The list of exec-lines */ /* The list of exec-lines */
struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); 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 */ /* 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); struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
@ -102,17 +105,21 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_
if (!attr) { if (!attr) {
wa.tag = TAG_COOKIE; wa.tag = TAG_COOKIE;
wa.u.cookie = xcb_get_window_attributes(conn, window); wa.u.cookie = xcb_get_window_attributes(conn, window);
attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0); if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
} }
geom = xcb_get_geometry_reply(conn, geomc, 0); if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
if (attr && geom) { goto out;
/* Reparent the window and add it to our list of managed windows */
reparent_window(conn, window, attr->visual, geom->root, geom->depth, reparent_window(conn, window, attr->visual, geom->root, geom->depth,
geom->x, geom->y, geom->width, geom->height); 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_CLASS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); 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_NORMAL_HINTS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
}
free(geom); free(geom);
out: out:
@ -131,7 +138,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_visualid_t visual, xcb_window_t root, uint8_t depth, xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
int16_t x, int16_t y, uint16_t width, uint16_t height) { int16_t x, int16_t y, uint16_t width, uint16_t height) {
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie; 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 mask = 0;
uint32_t values[3]; uint32_t values[3];
@ -147,6 +155,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); 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); 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); 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); Client *new = table_get(&by_child, child);
@ -191,6 +202,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
/* Yo dawg, I heard you like windows, so I create a window around your window… */ /* 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); new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
/* Set WM_STATE_NORMAL because GTK applications dont want to drag & drop if we dont.
* Also, xprop(1) needs that to work. */
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; 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); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
@ -227,8 +240,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_atom_t *atom; xcb_atom_t *atom;
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); 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))) { 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++) for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK])
continue;
LOG("Window is a dock.\n"); LOG("Window is a dock.\n");
new->dock = true; new->dock = true;
new->titlebar_position = TITLEBAR_OFF; new->titlebar_position = TITLEBAR_OFF;
@ -257,27 +271,74 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height); LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
new->desired_height = height; new->desired_height = height;
} }
} else {
/* If its not a dock, we can check on which workspace we should put it. */
/* Firstly, we need to get the windows class / title. We asked for the properties at the
* top of this function, get them now and pass them to our callback function for window class / title
* changes. It is important that the client was already inserted into the by_child table,
* because the callbacks wont work otherwise. */
preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
preply = xcb_get_property_reply(conn, title_cookie, NULL);
handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
preply = xcb_get_property_reply(conn, class_cookie, NULL);
handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
LOG("DEBUG: should have all infos now\n");
struct Assignment *assign;
TAILQ_FOREACH(assign, &assignments, assignments) {
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
continue;
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;
} }
/* Focus the new window if were not in fullscreen mode and if it is not a dock window */ LOG("Changin container/workspace and unmapping the client\n");
if (CUR_CELL->workspace->fullscreen_client == NULL) { Workspace *t_ws = &(workspaces[assign->workspace-1]);
if (!new->dock) { if (t_ws->screen == NULL) {
CUR_CELL->currently_focused = new; LOG("initializing new workspace, setting num to %d\n", assign->workspace);
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); t_ws->screen = c_ws->screen;
/* Copy the dimensions from the virtual screen */
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
} }
} else {
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
new->workspace = t_ws;
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 */ /* If we are in fullscreen, we should lower the window to not be annoying */
uint32_t values[] = { XCB_STACK_MODE_BELOW }; uint32_t values[] = { XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
} }
} else if (!new->dock) {
/* Focus the new window if were not in fullscreen mode and if it is not a dock window */
if (new->container->workspace->fullscreen_client == NULL) {
new->container->currently_focused = new;
if (new->container == CUR_CELL)
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
}
}
/* Insert into the currently active container, if its not a dock window */ /* Insert into the currently active container, if its not a dock window */
if (!new->dock) { if (!new->dock) {
/* Insert after the old active client, if existing. If it does not exist, the /* 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 */ container is empty and it does not matter, where we insert it */
if (old_focused != NULL && !old_focused->dock) if (old_focused != NULL && !old_focused->dock)
CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients); CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->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);
} }
@ -287,8 +348,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
(state = xcb_get_property_value(preply)) != NULL) (state = xcb_get_property_value(preply)) != NULL)
/* Check all set _NET_WM_STATEs */ /* Check all set _NET_WM_STATEs */
for (int i = 0; i < xcb_get_property_value_length(preply); i++) for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) { if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
continue;
/* If the window got the fullscreen state, we just toggle fullscreen /* If the window got the fullscreen state, we just toggle fullscreen
and dont event bother to redraw the layout that would not change and dont event bother to redraw the layout that would not change
anything anyways */ anything anyways */
@ -531,7 +593,7 @@ int main(int argc, char *argv[], char *env[]) {
} }
/* Autostarting exec-lines */ /* Autostarting exec-lines */
Autostart *exec; struct Autostart *exec;
TAILQ_FOREACH(exec, &autostarts, autostarts) { TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command); LOG("auto-starting %s\n", exec->command);
start_application(exec->command); start_application(exec->command);

View File

@ -27,6 +27,7 @@
#include "layout.h" #include "layout.h"
#include "util.h" #include "util.h"
#include "xcb.h" #include "xcb.h"
#include "client.h"
static iconv_t conversion_descriptor = 0; static iconv_t conversion_descriptor = 0;
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
@ -224,25 +225,6 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
return buffer; 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 * Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client. * the given container, optionally excluding the given client.
@ -435,17 +417,6 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
set_focus(conn, container->currently_focused, true); set_focus(conn, container->currently_focused, true);
} }
/*
* 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);
}
/* /*
* Toggles fullscreen mode for the given client. It updates the data structures and * 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 * reconfigures (= resizes/moves) the client and its frame to the full size of the
@ -508,52 +479,55 @@ void toggle_fullscreen(xcb_connection_t *conn, Client *client) {
} }
/* /*
* 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) { Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
xcb_get_property_cookie_t cookie; Client *specific) {
xcb_get_wm_protocols_reply_t protocols; char *to_class, *to_title, *to_title_ucs = NULL;
bool result = false; int to_title_ucs_len;
Client *matching = NULL;
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]); to_class = sstrdup(window_classtitle);
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
return false;
/* Check if the clients protocols have the requested atom set */ /* If a title was specified, split both strings at the slash */
for (uint32_t i = 0; i < protocols.atoms_len; i++) if ((to_title = strstr(to_class, "/")) != NULL) {
if (protocols.atoms[i] == atom) *(to_title++) = '\0';
result = true; /* Convert to UCS-2 */
to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
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;
} }
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; FOR_TABLE(&(workspaces[workspace])) {
ev.window = window->child; Container *con = workspaces[workspace].table[cols][rows];
ev.type = atoms[WM_PROTOCOLS]; Client *client;
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"); CIRCLEQ_FOREACH(client, &(con->clients), clients) {
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name);
xcb_flush(conn); if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue;
matching = client;
goto done;
}
}
}
done:
free(to_class);
FREE(to_title_ucs);
return matching;
} }